Skip to content

Test stackman_call's return value and previous stack pointer. - #21

Merged
kristjanvalur merged 15 commits into
stackless-dev:masterfrom
kristjanvalur:audit
Oct 14, 2026
Merged

kristjanvalur merged 15 commits into
stackless-dev:masterfrom
kristjanvalur:audit

Conversation

@kristjanvalur

Copy link
Copy Markdown
Collaborator

test_04 and test_05 now check that the callback result comes back unchanged, that a caller local survives writes on the alternate stack, and that the pointer argument is the previous stack. The audit note records the register findings these checks come from.

qemu-riscv64 is expected to fail until switch_riscv64_gcc.S skips a null stack and passes the previous stack pointer.

test_04 and test_05 now check that the callback result comes back
unchanged, that a caller local survives writes on the alternate stack,
and that the pointer argument is the previous stack. The audit note
records the register findings these checks come from.

qemu-riscv64 is expected to fail until switch_riscv64_gcc.S skips a
null stack and passes the previous stack pointer.
A null stack was installed as sp, which faulted test_05, and the callback
received that new pointer instead of the previous one, which failed
test_04. Skip a zero stack and pass the saved stack pointer.
x18 is the TEB for the whole time user code runs. stackman_switch now
keeps the callback in x20, and stackman_call keeps the old stack pointer
in x19. Both routines still spill and restore x18 so a switched context
gets the value it saved. SEH unwind codes are unchanged.
stackman_switch now applies the stack delta to x29 before the restore
callback, in the checked-in assembly and in the inline sequence that
generates it. The epilogue still reloads the saved x29. SME lazy save
is left for a target that enables SME.
The control bits of MXCSR are non-volatile. stackman_switch stores the
register in the unused 8-byte slot at the top of the xmm frame and loads
it back before releasing that frame, including after the stack has moved.
stackman_call returns to the same caller and does not spill it.
SysV stores it with fstcw at the pointer passed to the callback, in both
the checked-in assembly and the inline block it is copied from. Windows
does the same inside stackman_switch_raw, next to the callee-saved
pushes, and the C wrapper still restores the SEH chain. XMM registers
stay unsaved.
The checked-in assembly is soft-float and does not spill d8-d15, so a
__ARM_PCS_VFP include is now a compile error. stackman_switch applies
the stack delta to r11 before the restore callback. The RISC-V s0 delta
stays omitted: s0 is not a frame chain there, and stackman_call uses it
for the previous stack pointer.
CHANGELOG 1.0.0 already dropped win_arm. The sources were still selected
for _M_ARM, and the VS projects still built them. That assembly pushed an
odd number of registers and then vpush'd at 4 mod 8. Delete the sources,
the platform branch, the Debug|ARM project configurations, and the
prebuilt lib/win_arm archive. Windows ARM64 is unchanged.
The Copilot file still described version 1.0.1, seven ABIs, and six tests, and told agents to trust it ahead of the tree. AGENTS.md points at the current sources instead. The Copilot path is a short pointer so that file does not drift again.
Comment thread stackman/platforms/switch_arm_gcc.h Outdated
__asm__ (
"sub r1, sp, %[result]\n\t"
"mov sp, %[result]\n\t"
"sub r11, r11, r1"

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[bug] sub r11, r11, r1 is not attached to a live frame pointer. stackman_switch is built with optimize("O", "omit-frame-pointer") (line 75), r11 is an ordinary member of NV_REGISTERS, and the checked-in prologue in switch_arm_gcc.S is frame_needed = 0: it pushes fp but never writes it. Unlike switch_aarch64_gcc.S, where mov x29, sp makes the delta land on this frame, the live r11 here is still whatever the caller left in it (or whatever GCC parked there). The asm clobbers only r1 and the comment forbids listing r11, so a STACKMAN_INLINE_ASM build (tests/test_static.c forces that and make test runs it) may keep the callback or the new stack pointer in r11; a non-zero delta then corrupts it before STACKMAN_OP_RESTORE. The frozen .S currently happens to use r4/r5 instead, but sub r11, r11, r1 there still only perturbs the incoming register across the second blx. pop {..., fp, pc} reloads the unadjusted saved value, so the caller never observes the delta. AGENTS.md repeats the same "do not clobber r11" rule.

Suggestion: Drop the r11 adjust from this header and from switch_arm_gcc.S (the integer spill is already complete). Do not copy the AArch64 sequence here unless the function is changed to no-omit-frame-pointer, r11 is removed from NV_REGISTERS, and the prologue actually sets r11 to this frame the way x29 is set. Leave AArch64 as it is.

Comment thread stackman/platforms/switch_x86_gcc.h Outdated
* ebp, ebx, esi, edi are callee-preserved
* ebp, ebx, esi, edi and the x87 control word are callee-preserved.
* The control word is stored at the stack pointer passed to the callback
* so a copied stack image carries it. fstcw does not wait on pending

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] The new comment says fstcw does not wait on pending exceptions. It does the opposite. FSTCW is FWAIT plus FNSTCW and handles pending unmasked x87 exceptions before the store; only FNSTCW skips that check. The mnemonic does match switch_x86_64_gcc.S, so the "matching" half of the sentence is right and the spill placement at 0x10(%esp) / [esp] is the right slot for the copied image. The exception claim is not.

Suggestion: Delete the "does not wait" sentence, or switch both i386 sites and the SysV x86-64 file to fnstcw if the intent was to avoid an FWAIT in the middle of the switch. Do not leave the comment describing the wrong instruction.

stackman_switch is built with omit-frame-pointer. The prologue pushes fp and never sets it, so live r11 is not this frame, and the epilogue pops the saved value. The inline build can keep the callback or the new stack pointer in r11, and the delta then corrupts it before the restore callback. AArch64 still adjusts x29, which the prologue sets from sp.
fstcw is FWAIT plus FNSTCW, so it does wait on a pending unmasked x87 exception. The spill stays fstcw, matching switch_x86_64_gcc.S, which delivers that exception on this stack before the switch.
@kristjanvalur
kristjanvalur merged commit bae6b74 into stackless-dev:master Oct 14, 2026
15 checks passed
@kristjanvalur
kristjanvalur deleted the audit branch October 14, 2026 16:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant