Skip to content

Spill floating-point status registers in stackman_switch. - #23

Open
kristjanvalur wants to merge 9 commits into
stackless-dev:masterfrom
kristjanvalur:fp-spill
Open

kristjanvalur wants to merge 9 commits into
stackless-dev:masterfrom
kristjanvalur:fp-spill

Conversation

@kristjanvalur

Copy link
Copy Markdown
Collaborator

Where a thread switch saves a floating-point control or status register, stackman_switch now saves the whole register and writes it back after the restore callback, before the epilogue pops the callee-saved registers. The caller-saved flag bits are included. stackman_call does not touch these registers.

  • armhf: FPSCR, in an 8-byte slot below the pointer passed to the callback.
  • aarch64 and darwin_arm64: FPCR and FPSR, in a 16-byte slot at that pointer. The x29 adjustment is taken from the lowered stack pointer.
  • win_arm64: FPSR next to the existing FPCR spill. The frame grows from 176 to 192 bytes. No SEH unwind codes were added.
  • riscv64: fcsr, in the existing 8-byte pad at the published stack pointer. The frame stays 208 bytes.

Softfp (-mfloat-abi=softfp) is not a supported platform. It still builds as arm32 and runs, and that assembly does not preserve FPSCR.

qemu-arm ran the armhf tests. qemu-aarch64 ran a two-stack switch of the Linux assembly. qemu-riscv64 ran the test suite, and a separate two-stack probe checked fcsr. The Darwin object assembled and was not executed. Windows ARM64 was not assembled here.

Linux saves and restores the whole FPSCR on a VFP context switch.
stackman_switch stores it in an 8-byte slot below the pointer passed
to the callback and writes it back after the restore callback, ahead
of the d8-d15 pop. The caller-saved flag bits travel with that copy.
stackman_call does not change FPSCR.
A thread switch saves both registers. Linux and macOS stackman_switch
store them in a 16-byte slot at the pointer passed to the callback and
write them back after the restore callback, before the epilogue pops
the callee-saved registers. Windows already stored FPCR and now stores
FPSR at [sp, #176]; the frame is 192 bytes. The caller-saved status
bits are included. stackman_call does not touch either register.

qemu-aarch64 ran a two-stack switch of the Linux assembly. The Darwin
object assembled and was not executed. Windows ARM64 was not assembled
here.
A softfp build does not define __ARM_PCS_VFP, so it selects arm32 and
runs. The soft-float assembly does not preserve FPSCR. Do not add that
spill to switch_arm_gcc.S: the file is softvfp, and vmrs fpscr is an
undefined instruction without VFP.
A thread switch saves the whole fcsr. stackman_switch stores it in the
existing 8-byte pad at the pointer passed to the callback and writes it
back after the restore callback, before the epilogue pops fs0-fs11.
The caller-saved exception flags are included. stackman_call does not
touch fcsr.

qemu-riscv64 ran a two-stack switch of this assembly.
The squash of the armhf port said FPSCR was still not spilled. This
branch stores the whole register, so those applied-fix sentences now
say so.
Later ports were being written into docs/abi-register-audit.md. That
file is restored to the audit as it was merged. Current ABI, switching,
and spill rules for each platform are in docs/platform-abi.md.
Later ports stay in docs/platform-abi.md.
The 16-byte spill lowered sp without telling the CFI, so callee-saved
slots were described 16 bytes low across both callbacks. The CFA is
sp+176 until the slot is popped. The macros sit outside the shared
asm block, so the Darwin assembly stays empty there.

The armhf note says the published pointer is the FPSCR slot. The
Windows ARM64 note puts FPSR at [sp, #176] and says the epilogue
reloads x18. The audit banner says the body is the PR 21 outcome
and is not the current tree.
test_09 sets FE_DOWNWARD, lets the callback switch to FE_TOWARDZERO, and
checks that the caller sees FE_DOWNWARD again on ABIs that spill the
floating-point control register. A second check saves a stack while
that mode is current, switches away with FE_UPWARD live, and checks
that the resumed copy still has FE_DOWNWARD. Soft-float arm32 does not
spill FPSCR, and the test expects the callback's mode to remain. The
test programs link libm for fesetround.
// 70 "../platforms/switch_aarch64_gcc.h" 1
mrs x1, fpcr
mrs x2, fpsr
stp x1, x2, [sp, #-16]!

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] stp x1, x2, [sp, #-16]! lowers sp by 16 for both callbacks, but the FDE still has CFI_DEF_CFA_OFFSET(160) from the prologue until the epilogue. Callee-saved slots are therefore described 16 bytes low: x30 is recorded at sp+8 (the FPSR half of the new slot) instead of sp+24. This is a regression. Before this push, sp pointed at the 160-byte compiler frame on both the save callback and, after mov sp, x0, the other stack's matching frame, so SP-relative unwind of stackman_switch itself was consistent. x29 is unchanged by the push (it still names the real frame record), but the shipping CFI is SP-based, which is what .eh_frame consumers use. Darwin is unaffected because the CFI_* macros are empty there. The same inline sequence in switch_aarch64_gcc.h has the same hole for an STACKMAN_INLINE_ASM build.

Suggestion: After the spill, set the CFA to sp+176, and set it back to sp+160 after ldp x1, x2, [sp], #16. Use the existing CFI_DEF_CFA_OFFSET macros in this file so the Darwin assembly stays a no-op. Do not emit a raw .cfi_* inside the shared #APP block; macOS assembles that block without .cfi_startproc. A gcc -S regen will not emit the adjustment, because the compiler does not see the sp write.

Comment thread docs/platform-abi.md Outdated
this ABI. It does not link with `arm32`.

`stackman_switch` spills `r4`–`r11`, `lr`, `d8`–`d15`, and the whole
`FPSCR`. `FPSCR` sits in an 8-byte slot below the pointer passed to the

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 armhf contract says FPSCR sits in an 8-byte slot below the pointer passed to the callback. The assembly does str r2, [sp, #-8]! and then passes that updated sp, and the restore is ldr r2, [sp], #8. The slot is at the published pointer, which is what a copy from that pointer upward must include. The same file describes the AArch64 FPCR/FPSR pair correctly as a 16-byte slot at the published pointer. As written, the armhf sentence tells a stack copier to look 8 bytes below the pointer, outside the image stackman_switch actually publishes.

Suggestion: Say the published pointer is the 8-byte FPSCR slot (below the d8–d15 spill), matching switch_armhf_gcc.h and the AArch64 wording.

Comment thread docs/platform-abi.md Outdated
`stackman_switch` spills `x18`–`x28`, `x29`, `x30`, `d8`–`d15`, `FPCR`,
and `FPSR`. The frame is 192 bytes. `FPSR` is at `[sp, #176]`, next to
the `FPCR` spill. `x18` is the TEB for the whole time user code runs:
it is stored and reloaded, and the live register is never written. The

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 Windows ARM64 paragraph says the live x18 is never written. The epilogue still does ldp x2, x18, [sp, #16], which writes the spilled TEB back after the restore callback. That reload is pre-existing and is what puts a switched context's saved x18 back; the new FPSR instructions do not themselves write x18. The same paragraph says FPSR at [sp, #176] is next to the FPCR spill. FPCR remains the stp x2, x18 pair at [sp, #16]. [sp, #176] is adjacent to d15 ([sp, #160]), with 8 bytes of alignment padding at [sp, #184]. The offset and the 192-byte frame are right; the adjacency is not.

Suggestion: Say the body never uses x18 as a scratch and that the epilogue reloads the spilled value. Say FPSR is the 8-byte slot at [sp, #176], not next to FPCR.

A hard-float port is a new assembly, alongside the soft-float file. It
spills `d8`–`d15` in `stackman_switch`, has its own ABI name, and is what
a `__ARM_PCS_VFP` build selects. The `#error` stays until that port
exists. Softfp is not that port: it does not define `__ARM_PCS_VFP`, and

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] This edit puts the historical audit back into the present tense and denies the armhf port that this branch builds on. It says __ARM_PCS_VFP keeps erroring until a hard-float port exists, and the switch_arm_gcc.S row now says armhf is not a configured ABI. platform.h already selects switch_armhf_gcc.h and the armhf ABI when __ARM_PCS_VFP is set, and this PR spills FPSCR in that port. The applied-fix note that a hard-float port is still on the roadmap has the same problem. The banner says later ports do not belong in this file; rewriting it so the current tree looks unfinished is worse than leaving the post-armhf text.

Suggestion: Leave the audit as the PR 21 record. Point current armhf and FPSCR behavior only at docs/platform-abi.md, and drop the new claim that armhf is not configured.

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