Low-level C library for stack switching. Two functions, no dependencies.
Read the sources. This file is a map, not a second copy of them. The
version is STACKMAN_VERSION in stackman/stackman.h.
stackman_switch saves callee-saved state, calls the callback with
STACKMAN_OP_SAVE, installs the stack pointer that callback returns,
calls it again with STACKMAN_OP_RESTORE, then restores the saved state
and returns that second result. It may resume a different caller, so the
spill is the whole non-volatile set for that ABI.
stackman_call runs the callback on another stack and returns to the
same caller. A null stack is left unchanged. The pointer passed to the
callback is the previous stack pointer. It only needs to preserve
registers it modifies.
User code includes stackman.h. The library implementation includes
stackman_impl.h once. platforms/platform.h selects
switch_<abi>_<compiler>.
STACKMAN_HAVE_CALL and STACKMAN_STACK_ALIGN must be visible from a
normal #include "stackman.h". If they sit under
#ifdef STACKMAN_SWITCH_IMPL, tests/test.c compiles stackman_call
out and CI looks green.
Align a stack with STACKMAN_SP_ALIGN. It follows STACKMAN_STACK_ALIGN
and the stack direction.
stackman_block_call turns a malloc block, a low address and a size, into
the pointer stackman_call takes and the byte count toward near. It is a
static inline in stackman.h.
Linux: sysv_i386, sysv_amd64, arm32 (soft-float AAPCS), armhf
(AAPCS-VFP), aarch64, riscv64 (LP64D). macOS: darwin_x86_64,
darwin_arm64. Windows:
win_x86, win_x64, win_arm64. win_aarch64 is accepted and
normalized to win_arm64. FreeBSD, OpenBSD, and NetBSD use the Linux
sysv_amd64 sources. Their workflow runs gmake.
Windows ARM32 (win_arm) was removed in 1.0.0. Do not add it back.
The default build uses the checked-in .S and .asm files.
STACKMAN_INLINE_ASM is unset. RISC-V always uses
switch_riscv64_gcc.S. When a .S is generated from a header, change
the header and the .S together. gen_asm.c defines
STACKMAN_INLINE_ASM as 1. A .S # line comment is a snapshot of the
header line and goes stale if lines are inserted above the asm. Leave
those comments unless you are regenerating the file.
Volatile registers may change. Do not add a test that treats them as preserved.
Current spills are in docs/platform-abi.md.
docs/abi-register-audit.md is the audit from when those gaps were
found and fixed. Do not edit the audit for a later port or spill.
Limits that are easy to fix the wrong way:
arm32is the soft-float assembly (switch_arm_gcc.S,.arch armv5t,.fpu softvfp). It does not spilld8–d15, and__ARM_PCS_VFPis a compile error in that header.armhf(switch_armhf_gcc.S) is the hard-float port and is what__ARM_PCS_VFPselects. It spillsd8–d15and the wholeFPSCRinstackman_switch. Softfp (-mfloat-abi=softfp) is not a supported platform. It still builds asarm32and runs, and that assembly does not preserveFPSCR. Do not add anFPSCRspill toswitch_arm_gcc.Sto cover it.aarch64,darwin_arm64, andwin_arm64do not preserve SME ZA acrossstackman_switch. Do not call__arm_za_disable. That helper commits the outgoing caller's ZA and turns ZA off. Nothing in the switch installs the incoming stack's ZA.stackman_callreturns to the same caller and does not need it. Windows ARM64 is a different runtime. Do not call the AAPCS64 helper there.- On Windows ARM64,
x18is the TEB for the whole time user code runs. Spill and restore the saved value. Do not write the live register. Do not invent.seh_*names for this file. - A live frame pointer (
x29on AArch64) is adjusted by the stack delta inside the asm. Do not listx29as a clobber. Do not adjust ARM32r11there:stackman_switchis built withomit-frame-pointer, the prologue pushesr11and never sets it, and the epilogue reloads the saved value. On RISC-V,s0is not a frame chain instackman_switch, andstackman_callusess0for the previous stack pointer. Do not adjust it there. stackman_switchspillsFPCRandFPSRonaarch64,darwin_arm64, andwin_arm64, and spillsfcsronriscv64. The RISC-V slot is the existing 8-byte pad at the published stack pointer.stackman_calldoes not touch these registers.
make test # native ABI. Builds lib/$(ABI)/libstackman.a and runs the tests.
make abiname
make PLATFORMFLAGS=-m32 test
make PLATFORM_PREFIX=arm-linux-gnueabi EMULATOR=qemu-arm test
make PLATFORM_PREFIX=arm-linux-gnueabihf EMULATOR=qemu-arm testmake clean only when switching PLATFORMFLAGS or PLATFORM_PREFIX.
Success ends with *** All test suites passed ***.
Five programs: bin/test, bin/test_cc, bin/test_static,
bin/test_asm, and bin/test_block_asc. The last one compiles
stackman_block_call with STACKMAN_STACK_DIR set to 1. No supported
ABI grows upward. tests/test.c is test_01 through test_09. test_04
and test_05 are the stackman_call contract. test_07 and test_08
pressure the compiler. They pass even when a callee-saved register is
dropped, if the compiler kept the values in memory. test_09 checks
that stackman_switch restores the caller's rounding mode, including
across a stack switch. Soft-float arm32 does not, and that test
expects the callback's mode to stick. The test programs link -lm
for fesetround.
The linker warning missing .note.GNU-stack section on
tests/test_asm_s.o is expected.
Intel CET: the Makefile adds -fcf-protection=none through
./disable_cet. Stack switching is incompatible with the shadow stack.
Regenerating asm uses the same flag (gcc -S -fcf-protection=none).
Windows, as CI runs it: msbuild vs2022\stackman.sln /p:Platform=x64
(also x86 and arm64). Native tests are vs2022\x64\Debug\test.exe
and vs2022\Win32\Debug\test.exe. The arm64 build is not run. After
tools/strip-lib.py, CI rebuilds.
Do not commit rebuilt lib/ archives, *.o, bin/, or tmp/.
Checked-in libraries under lib/ are deprecated and go away in a future
major version. commit-artifacts in
.github/workflows/buildcommit.yml is if: false.
.github/workflows/buildcommit.yml, on pull requests to master:
build-linux-gnu: AMD64, i386 (-m32), arm (qemu-arm), armhf (qemu-arm), aarch64 (qemu-aarch64), riscv64 (qemu-riscv64). Each runsmake test.build-macos:macos-15-intel(darwin_x86_64) andmacos-latest(darwin_arm64).build-windows: VS2022x86,x64, andarm64. Tests run only wherenativeis set (x86andx64).
.github/workflows/buildbsd.yml builds and tests FreeBSD, OpenBSD, and
NetBSD with gmake on the sysv_amd64 sources.
The test steps use timeout-minutes: 1. A passing run finishes in a few
seconds. On Windows an assert opens a CRT dialog and does not exit, so
that limit is what ends a failed test.exe.
A green native make test says nothing about another ABI. An assembly
change is checked by the job that builds that file.
Keep changes small. Do not add a dependency. Do not bump stackman.h or
CHANGELOG.md unless asked.
Prefer the checked-in assembly over turning STACKMAN_INLINE_ASM on.
switch_template.h is the skeleton for a new port.
There is no main branch. master is the default on both origin
(kristjanvalur/stackman) and upstream (stackless-dev/stackman).