diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 5d36dae..d508ec1 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,139 +1,4 @@ -# Copilot Instructions for stackman +# Copilot instructions -## Overview -Low-level C library for stack manipulation (continuations/co-routines). ~600 lines of C + assembly. Zero dependencies. **Current version: 1.0.1** - -**Supported Platforms (7 ABIs):** -- Linux: sysv_amd64, sysv_i386, arm32 (AAPCS), aarch64 (AAPCS64) -- Windows: win_x86, win_x64, win_arm64 - -**Note:** Windows ARM32 (win_arm) support was removed in v1.0.0 (Microsoft SDK 10.0.26100.0+ dropped support) - -**Toolchains:** GCC, Clang, MSVC (VS2017, VS2019, VS2022) - -## Build Commands (FAST: <1 second) - -**Always run in order:** -```bash -make clean # Clean build artifacts -make all # Build library (0.1s) → lib/[ABI]/libstackman.a -make test # Build + run 4 test suites (0.7s) -make abiname # Print platform ABI (e.g., sysv_amd64) -``` - -**Success output:** `*** All test suites passed ***` - -**Cross-compile x86:** `make PLATFORMFLAGS=-m32 test` -**Cross-compile ARM:** `make PLATFORM_PREFIX=arm-linux-gnueabi- EMULATOR=qemu-arm test` -**Windows:** `msbuild vs2022\stackman.sln /p:Platform=x64` then `vs2022\x64\Debug\test.exe` - -## Critical Build Notes - -1. **Intel CET:** `-fcf-protection=none` flag REQUIRED (auto-added by disable_cet script). Stack switching incompatible with Shadow Stack. -2. **Libraries Deprecated in Repo:** As of v1.0.1, pre-built libraries are NO LONGER automatically committed. Download from [GitHub Releases](https://github.com/stackless-dev/stackman/releases) instead. `lib/` directory will be removed in v2.0.0. -3. **Expected Warning:** Linker warning "missing .note.GNU-stack section" in test_asm is NORMAL - ignore it. -4. **Artifacts:** `*.o`, `bin/`, `tmp/` NOT committed. Libraries available via GitHub Releases. -5. **Incremental OK:** After code changes, just `make test`. Only clean when switching platforms. - -## Project Structure - -**Key Directories:** -- `stackman/` - Main source: `stackman.h` (API + version macros), `stackman_switch.h`, `stackman_impl.h`, `platforms/` (15+ platform files) -- `tests/` - 4 test files: `test.c` (6 tests), `test_cc.cc`, `test_static.c`, `test_asm.c/.S` -- `lib/[ABI]/` - Pre-built libraries (DEPRECATED - see lib/README.md, will be removed in v2.0.0) -- `vs2017/`, `vs2019/`, `vs2022/` - Visual Studio projects -- `tools/` - `abiname.sh`, `abiname.c`, `strip-lib.py` - -**Core API (2 functions only):** -```c -void *stackman_switch(stackman_cb_t callback, void *context); // Main stack switch -void *stackman_call(stackman_cb_t callback, void *context, void *stack); // Call with different stack -``` - -**Architecture:** `platforms/platform.h` detects OS/arch/compiler → includes appropriate `switch_[abi]_[compiler].h/S/asm` - -## CI Validation (.github/workflows/buildcommit.yml) - -**Triggers:** Push to master/dev, PRs to master, tags (v*.*.*) - -**Jobs:** -1. **build-linux-gnu** (sysv_amd64, sysv_i386, arm32, aarch64) - installs cross-tools → `make all` → `make test` (qemu for ARM) -2. **build-windows** (win_x86, win_x64, win_arm64) - MSBuild → strip-lib.py → rebuild (MUST rebuild after strip!) -3. **commit-artifacts** (DISABLED as of v1.0.1) - Previously committed libs to repo, now disabled (if: false) -4. **create-release** (tags only) - Creates GitHub Release with `stackman-{version}.tar.gz` containing all platforms - -**Release Archive Contents:** -- `stackman/` - Headers -- `lib/{abi}/` - Pre-built libraries for all 7 platforms -- `tools/` - Platform detection tools -- `Makefile` - For `make abiname` -- Documentation (README.md, LICENSE, CHANGELOG.md) - -**Local validation:** -```bash -make clean && make test # Test native platform -git status # Verify no bin/, tmp/, *.o tracked -``` - -## Key Patterns & Workarounds - -**Making Changes:** -- Platform code: edit `stackman/platforms/switch_*.h` or `.S` - reference `switch_template.h` -- Always run `make test` after changes (fast: 0.7s) -- Test on actual hardware if modifying assembly (arch-specific!) - -**Known Issues/Workarounds:** -- **CET:** `-fcf-protection=none` REQUIRED (auto-added by disable_cet script) -- **Inline asm:** May be inlined by optimizer → use separate .S files or volatile pointer (see stackman_impl.h) -- **Stack align:** Use `STACKMAN_SP_ALIGN` macro - -**Testing:** 4 test executables, 6 tests each (assertions fail hard). Success: "test_XX ok" + "*** All test suites passed ***" - -**Include patterns:** -- User code: `#include "stackman.h"` -- Library impl: `#include "stackman_impl.h"` (once) - -## Configuration Files -- **Build:** Makefile (Linux), vs2022/*.vcxproj (Windows) -- **CI:** .github/workflows/buildcommit.yml -- **Versioning:** stackman/stackman.h - STACKMAN_VERSION_MAJOR/MINOR/PATCH macros + STACKMAN_VERSION string -- **Changelog:** CHANGELOG.md (documents all releases) -- **Linting:** None configured -- **Testing:** `make test` target -- **.gitignore:** Excludes *.o, bin/, tmp/ BUT still includes lib/**/*.a, lib/**/*.lib (deprecated, slated for removal) - -## Development Tips - -1. **Trust these instructions first** - search only if info incomplete/incorrect -2. **Build is FAST** - rebuild freely (clean+test <1s) -3. **Test after every change** - `make test` is fast and comprehensive -4. **Cross-compilation optional** - CI validates all platforms, native x64 sufficient for most changes -5. **No binary commits** - lib/ directory deprecated, use GitHub Releases -6. **Zero dependencies** - don't add any -7. **Minimal changes** - stable library, surgical edits only -8. **Low-level code** - assembly is platform-specific, test on actual hardware - -## Versioning & Releases - -**Version Location:** `stackman/stackman.h` -```c -#define STACKMAN_VERSION_MAJOR 1 -#define STACKMAN_VERSION_MINOR 0 -#define STACKMAN_VERSION_PATCH 1 -#define STACKMAN_VERSION "1.0.1" -``` - -**Release Process:** -1. Update version in `stackman/stackman.h` (MAJOR.MINOR.PATCH) -2. Update `CHANGELOG.md` with changes -3. Commit changes: `git commit -m "Bump version to X.Y.Z"` -4. Create tag: `git tag -a vX.Y.Z -m "Release vX.Y.Z"` -5. Push: `git push upstream master && git push upstream vX.Y.Z` -6. CI automatically creates GitHub Release with pre-built libraries - -**Version History:** -- v0.1 (2020-05-18) - Initial release -- v0.2 (retroactive tag) - Pre-1.0 development state -- v1.0.0 (2025-11-16) - First stable release, removed Windows ARM32 -- v1.0.1 (2025-11-16) - Disabled library commits, use GitHub Releases -- v2.0.0 (future) - Will remove lib/ directory entirely +The project instructions are in [AGENTS.md](../AGENTS.md) at the +repository root. Read that file. Do not keep a second copy here. diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..47b5ec1 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,143 @@ +# Agent notes for stackman + +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`. + +## What the library does + +`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__`. + +`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. + +## Platforms + +Linux: `sysv_i386`, `sysv_amd64`, `arm32` (soft-float AAPCS), `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. + +## Register contract + +Volatile registers may change. Do not add a test that treats them as +preserved. + +Details and the original audit are in `docs/abi-register-audit.md`. +Limits that are easy to fix the wrong way: + +- ARM32 hard-float is unsupported. `switch_arm_gcc.S` is `.arch armv5t` + and `.fpu softvfp`, and it does not spill `d8`–`d15`. `__ARM_PCS_VFP` + is a compile error. A hard-float port is a new assembly file with its + own ABI name. Softfp does not define `__ARM_PCS_VFP`, and the compiler + can still use `d8`–`d15`. +- `aarch64`, `darwin_arm64`, and `win_arm64` do not preserve SME ZA + across `stackman_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_call` returns 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, `x18` is 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 (`x29` on AArch64) is adjusted by the stack delta + inside the asm. Do not list `x29` as a clobber. Do not adjust ARM32 + `r11` there: `stackman_switch` is built with `omit-frame-pointer`, the + prologue pushes `r11` and never sets it, and the epilogue reloads the + saved value. On RISC-V, `s0` is not a frame chain in + `stackman_switch`, and `stackman_call` uses `s0` for the previous + stack pointer. Do not adjust it there. +- AAPCS64 FPCR and RISC-V `fcsr` are thread state. They are not per-call + spills. + +## Build and test + +```bash +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 clean` only when switching `PLATFORMFLAGS` or `PLATFORM_PREFIX`. +Success ends with `*** All test suites passed ***`. + +Four programs: `bin/test`, `bin/test_cc`, `bin/test_static`, +`bin/test_asm`. `tests/test.c` is `test_01` through `test_08`. `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. + +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`. + +## CI + +`.github/workflows/buildcommit.yml`, on pull requests to `master`: + +- `build-linux-gnu`: AMD64, i386 (`-m32`), arm (`qemu-arm`), aarch64 + (`qemu-aarch64`), riscv64 (`qemu-riscv64`). Each runs `make test`. +- `build-macos`: `macos-15-intel` (`darwin_x86_64`) and `macos-latest` + (`darwin_arm64`). +- `build-windows`: VS2022 `x86`, `x64`, and `arm64`. Tests run only + where `native` is set (`x86` and `x64`). + +`.github/workflows/buildbsd.yml` builds and tests FreeBSD, OpenBSD, and +NetBSD with `gmake` on the `sysv_amd64` sources. + +A green native `make test` says nothing about another ABI. An assembly +change is checked by the job that builds that file. + +## Edits + +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`). diff --git a/README.md b/README.md index 1e770b8..9202264 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ platforms so that no assembly steps are required by users. straightforward implementation on most platforms. - Complex logic and branching is delegated to the C callback. - Custom platform assembly code must only do three things: - 1. Save and restore volatile registers and stack state on the stack + 1. Save and restore callee-saved (non-volatile) registers and stack state on the stack 2. Call the callback twice with the current stack pointer 3. Set the stack pointer to the value returned by the first callback. @@ -105,6 +105,9 @@ calling convention, plus archive format: - **FreeBSD, OpenBSD, NetBSD (System V ABI)** - sysv_amd64 (64-bit x86_64) +The 64-bit ARM ABIs (`aarch64`, `darwin_arm64`, and `win_arm64`) do not +preserve SME ZA context across `stackman_switch`. + All platforms are automatically built and tested by GitHub Actions CI on every commit. The BSDs share the `sysv_amd64` ABI with Linux and therefore need no separate @@ -157,7 +160,9 @@ use. 1. It saves the current CPU stack pointer. If `stack` is non-zero, it will replace the stackpointer with that value. -2. It calls the callback function with the opcode `STACKMAN_OP_CALL`. +2. It calls the callback function with the opcode `STACKMAN_OP_CALL`. The + stack pointer argument is the previous stack pointer, the one in use before + this call. A null `stack` leaves that pointer unchanged. 3. It replaces the stack pointer with the previously saved value and returns the return value from the callback. This function is useful for at least three things: diff --git a/docs/abi-register-audit.md b/docs/abi-register-audit.md new file mode 100644 index 0000000..8268fad --- /dev/null +++ b/docs/abi-register-audit.md @@ -0,0 +1,541 @@ +# ABI register audit + +Audit of the assembly that implements `stackman_switch` and `stackman_call`. +The question is whether each routine spills every non-volatile register to the +stack before calling out, and loads that image back before returning. + +`stackman_switch` must spill the whole non-volatile set. The callback may +copy or replace the stack, and the epilogue may return to a different caller. +`stackman_call` returns to the same caller, so it only has to preserve the +non-volatile registers it modifies. + +Volatile (caller-saved) registers are free to change across either call. +A test that expects them to keep their values is checking the wrong contract. + +Sources: AAPCS64 (2025Q4 / 2026Q3), Microsoft ARM64 and ARM32 ABI notes, +Microsoft x64 calling convention, System V i386 and AMD64 ABIs, RISC-V psABI +LP64D. + +The findings below are the audit as it stood when the gaps were found. Each +section keeps that text. **Applied fix** records what was changed afterward. + +## Existing documentation + +There is no file that lists, for each ABI, which registers `stackman_switch` +spills. Those lists live in the assembly and in the clobber comments of the +inline headers. This note is the cross-check. + +The API is described in two places: + +- `README.md`, section "API", describes both functions. The `stackman_call` + steps now say that the callback receives the previous stack pointer. The + "Features" list now tells a new port to save callee-saved registers. It + previously said "volatile", which is the wrong set. +- `stackman/stackman_switch.h` is the implementation contract. Steps 1–6 for + `stackman_switch` match the assembly: spill callee-saved state, call with + `STACKMAN_OP_SAVE`, install the returned stack pointer, call with + `STACKMAN_OP_RESTORE`, pop the state, return the second result. The + `stackman_call` comment says the third argument is the previous stack + pointer. The `STACKMAN_OP_CALL` enumerator comment said the opposite (the + pointer passed into `stackman_call`). It now matches the function comment. + `switch_riscv64_gcc.S` now follows it: a null stack is left unchanged, + and the callback receives the previous stack pointer. + +Per-platform comments, checked against the code at audit time: + +| Place | Claim | Matches the code | +| --- | --- | --- | +| `switch_x86_64_gcc.h` | `rbx`, `rbp`, `r12`–`r15`, x87 control word, MXCSR | Yes. The comment used to say "mmx"; the instruction is `stmxcsr`. | +| `switch_x86_gcc.h` | `ebx`, `esi`, `edi`, and `ebp` via the frame | Yes for what it saves. It does not mention the x87 control word. | +| `switch_aarch64_gcc.h` | `x19`–`x28`, `x29` via the frame, `d8`–`d15` | Yes for the base AAPCS64 set. Silent on `x18` and SME. | +| `switch_arm_gcc.h` | `r4`–`r11`, and `d8`–`d15` unless Thumb. `FPSCR` is not saved. | The integer list matches `switch_arm_gcc.S`. That `.S` is soft-float and does not save `d8`–`d15`, so `CP_REGISTERS` describes the inline path only. | +| `switch_riscv64_gcc.S` | `s0`–`s11`, `fs0`–`fs11`, `ra` for `stackman_switch` | Yes for that function. `stackman_call` skips a null stack and passes the previous stack pointer. | +| `switch_x64_msvc.asm` | "all registers that the x64 ABI specifies as non-volatile", plus xmm | The GPRs and `xmm6`–`xmm15` match. MXCSR[6:15] is non-volatile on Windows and is not saved, so the comment overclaims. | +| `switch_arm64_msvc.asm` | `x18`–`x28`, `d8`–`d15`, `FPCR` are pushed | The spill matches the comment. The routine then uses `x18` as a scratch register, which the Windows ABI does not allow. | + +`lib/README.md` is about prebuilt archives, not the API or the register sets. +Its platform list omits `riscv64` and the Darwin ABIs that the main README +lists. `CHANGELOG.md` records Windows ARM32 as dropped in 1.0.0. +`platform.h` still selects `switch_arm_msvc.h` when `_M_ARM` is set. + +**Applied fix.** The i386 header comment now names the x87 control word. +The Windows x64 comment names the non-volatile GPRs, `xmm6`–`xmm15`, and +MXCSR. The ARM32 header rejects `__ARM_PCS_VFP` and says `CP_REGISTERS` is +the inline clobber only. The Windows ARM64 routines no longer write live +`x18`. The `_M_ARM` branch, the Debug|ARM project configurations, and the +prebuilt `lib/win_arm` archive were removed. Windows ARM64 stays. The +`lib/README.md` platform list was left as it was. + +## AArch64 + +Base non-volatile set, shared by Linux, macOS, and Windows: + +- `x19`–`x28`, `x29` (`fp`), `x30` (`lr`) +- low 64 bits of `v8`–`v15` (`d8`–`d15`) + +The high 64 bits of `v8`–`v15` are volatile. `x18` is the platform register. +Portable assembly leaves it alone; spilling it and writing it back later does +not make a temporary use of it safe. On Windows user mode it is the TEB pointer +for the whole time user code runs. + +`FPCR` control bits are global thread state in AAPCS64. On Windows those same +bits (AHP, DN, FZ, RMode, and the trap-enable bits) are non-volatile. Full SVE +`z8`–`z23` and predicates `p4`–`p15` are callee-saved only for a function that +itself takes or returns scalable arguments. `stackman_switch` is an ordinary C +function, so the base rule applies. `FPMR`, `FFR`, and SME `ZT0` are +caller-saved. + +### `switch_aarch64_gcc.S` (Linux `aarch64`, macOS `darwin_arm64`) + +`stackman_switch` matches the base set. It allocates 160 bytes and spills +`x19`–`x28`, `x29`, `x30`, and `d8`–`d15`, then loads those slots back. It +never writes `x18`. `stackman_call` spills `x29`, `x30`, and `x19`, and holds +the old stack pointer in `x19`. + +Two gaps that are not missing base registers: + +- After `mov sp, x0` the live `x29` is left pointing at the old stack. The + Windows ARM64 and SysV x86-64 routines add the same delta to the frame + pointer. The epilogue still reloads the saved `x29`, so the caller receives + the stacked value. The frame chain is wrong only during + `STACKMAN_OP_RESTORE`. +- A stack switch is an asynchronous transfer of control. AAPCS64 says + `setjmp` / `longjmp` call `__arm_za_disable` so the SME lazy-save buffer + (`TPIDR2_EL0`) is not left aimed at the wrong stack. This file does not. + +**Applied fix.** `stackman_switch` applies the stack delta to the live +`x29` before `STACKMAN_OP_RESTORE` (`sub x1, sp, x0` / `mov sp, x0` / +`sub x29, x29, x1`). The same three instructions are in +`switch_aarch64_gcc.h`, so regenerating the `.S` from `gen_asm.c` keeps +them. The epilogue still reloads the saved `x29`. A callback that relocates +a stack still has to rewrite saved frame-pointer slots in the copied bytes. +`__arm_za_disable` is not called. See Roadmap. + +### `switch_arm64_msvc.asm` (Windows `win_arm64`) + +The spill set matches the Windows document: `x19`–`x28`, `x29`, `x30`, +`d8`–`d15`, `FPCR`, and `x18`. The bug is the live value of `x18` during the +callbacks. + +`stackman_switch` stores the callback in `x18` and calls through it: + +```asm +mov x19, x0 +mov x18, x3 +blr x3 ; first callback, x18 is no longer the TEB +... +blr x18 ; second callback +``` + +`stackman_call` parks the old stack pointer in `x18` across `blr x3`. TLS, +the stack limit, `__chkstk`, and any Win32 call read the TEB through `x18`. +The epilogue writes the saved value back, so the original caller is repaired +only when the callback returns. + +Fix: keep `x18` unchanged. `stackman_switch` already spills `x19`–`x28`; use +one of those for the callback. `stackman_call` should spill some other +non-volatile (for example `x19`) and keep the old stack pointer there. + +The Windows file adjusts `fp` across the switch, which the SysV file does not. +It has no SEH unwind opcodes. The x64 MASM uses `NESTED_ENTRY` and +`.pushreg` / `save_xmm128`. That is an unwinding gap, separate from the +register values. + +**Applied fix.** `stackman_switch` keeps the context in `x19` and the +callback in `x20`. The first call is `blr x3` after `x20` has been loaded +from `x3`. The second call is `blr x20`. The `x18` store and reload stay, +so a restored context gets the value it spilled. `stackman_call` spills +`x19` at `[sp, #16]` and keeps the old stack pointer there. The SysV +AArch64 file now adjusts `x29` the same way this file adjusts `fp`. SEH +unwind codes were left unchanged. The Windows ARM64 test job compiles this +file and does not run it. + +## Other platforms + +| File | What it spills | Result | +| --- | --- | --- | +| `switch_x86_64_gcc.S` | `rbx`, `rbp`, `r12`–`r15`, x87 control word, MXCSR | Complete for SysV AMD64. XMM registers are volatile there. The frame pointer is adjusted with the stack delta. | +| `switch_x64_msvc.asm` | `rbx`, `rbp`, `rdi`, `rsi`, `r12`–`r15`, `xmm6`–`xmm15` | Those registers match the Windows x64 ABI. MXCSR[6:15] is non-volatile on Windows and is not spilled, so a changed rounding mode does not travel with the stack. The SysV file does save MXCSR. | +| `switch_x86_gcc.S` | `ebx`, `esi`, `edi`, `ebp` | Integer set is complete. There is no callee-saved XMM on i386 SysV. The x87 control word is callee-saved in that ABI and is not saved. | +| `switch_x86_msvc.asm` | `ebx`, `esi`, `edi`, `ebp`, plus the C wrapper saves `fs:[0]` | Integer set is complete for cdecl. Same x87 control-word gap. | +| `switch_arm_gcc.S` | `r4`–`r11`, `lr` | Complete for the soft-float `arm-linux-gnueabi` ABI this tree ships (`.arch armv5t`, `.fpu softvfp`). `d8`–`d15` are absent. The inline header clobbers them, so a hard-float or softfp build of this `.S` would drop callee-saved VFP state. `armhf` is not a configured ABI. The live frame pointer is not adjusted. | +| `switch_arm_msvc.asm` | `r4`–`r11`, `lr`, `q4`–`q7` (`d8`–`d15`), `FPSCR` | The set matches 32-bit Windows ARM. Alignment does not. `push {r4-r10, fp, lr}` is 9 words, so `sp` is 4-byte aligned for `vpush {q4-q7}`. A doubleword `VPUSH` requires 8-byte alignment. `stackman_call` pushes 3 registers and then `blx`s with `sp` still 4 mod 8. The changelog records this ABI as dropped; the file is still wrong if it is built. | +| `switch_riscv64_gcc.S` | `ra`, `s0`–`s11`, `fs0`–`fs11` | `stackman_switch` is complete for LP64D. `gp` and `tp` are left alone. `fcsr` is thread `fenv` state, not a per-call spill. | + +**Applied fix.** + +- `switch_x86_64_gcc.S` was already complete. Nothing was changed. +- `switch_x64_msvc.asm` stores MXCSR at `[rsp+160]`, the pad in the + 168-byte xmm frame, with `stmxcsr` after `alloc_stack` and `ldmxcsr` + after the GPR pops. `stackman_call` still does not spill MXCSR, because + it returns to the same caller. +- `switch_x86_gcc.S` and the inline block in `switch_x86_gcc.h` store the + x87 control word with `fstcw` at `0x10(%esp)`, the pointer passed to the + callback, and reload it with `fldcw` after the restore callback. `fstcw` + is the same instruction `switch_x86_64_gcc.S` uses. XMM registers stay + unsaved. +- `switch_x86_msvc.asm` stores the control word in `stackman_switch_raw`, + in a slot allocated with `ebx`, `esi`, and `edi`, at the pointer passed + to the callback. The C wrapper still restores `fs:[0]`. +- `switch_arm_gcc.h` is a compile error when `__ARM_PCS_VFP` is set. + `d8`–`d15` were not added. `CP_REGISTERS` is marked as the inline-only + clobber. An `r11` stack delta was added and then removed. + `stackman_switch` is compiled with `omit-frame-pointer`, and the + prologue pushes `fp` without setting it, so live `r11` is not this + frame. The epilogue pops the saved value. An inline build can also + keep the callback or the new stack pointer in `r11` across that asm. + AArch64 still adjusts `x29`, which the prologue sets with + `mov x29, sp`. +- Windows ARM32 was removed: `switch_arm_msvc.asm`, `switch_arm_msvc.h`, + the `_M_ARM` branch in `platform.h`, the Debug|ARM configurations in + vs2017, vs2019, and vs2022, and `lib/win_arm/stackman.lib`. The + alignment alternative was not used. Windows ARM64 stays. +- The RISC-V register spill was already complete. The optional `s0` frame + delta was left unchanged: `s0` is not a frame chain in `stackman_switch`, + and `stackman_call` uses it to hold the previous stack pointer. + +### RISC-V `stackman_call` + +The register spill in `stackman_call` (`ra` and `s0`) is enough for the +registers that routine writes. It used to switch `sp` even when the +requested stack was null, and it passed that new pointer as the third +argument. `test_04` then accepted `passed == stack2`, and `test_05` faulted +in the callback prologue. + +**Applied fix.** It now matches the other ports: a null stack is left in +place, and the callback receives the previous stack pointer (`s0`, captured +after the 16-byte frame allocation). The return value stays in `a0`. + +## Suggested fixes + +Each item is a local edit. The spill lists that already match the ABI stay +as they are. `stackman_call` only needs a new spill when the fix starts +using a callee-saved register it does not already save. + +The steps under each heading are the ones the audit proposed. The applied +fix at the end of the heading says what landed. + +### Windows ARM64: stop writing `x18` + +File: `stackman/platforms/switch_arm64_msvc.asm`. + +`stackman_switch` already spills `x19`–`x28` before it assigns them. Keep +the context in `x19`. Keep the callback in `x20` instead of `x18`, and make +the second call `blr x20`. The first call can stay `blr x3` only if `x20` +was loaded from `x3` first; the callback clobbers `x3`. Leave the `x18` +store and reload in place so a restored context gets the value it spilled, +and do not write `x18` between them. + +`stackman_call` should spill `x19` in the slot that now holds `x18` +(`str`/`ldr` at `[sp, #16]`), keep the old stack pointer in `x19`, and +delete every `x18` access. The 32-byte frame is already large enough. + +Unwind data is a separate change. Describe the prologue with the ARM64 +counterparts of the `NESTED_ENTRY` / `save_xmm128` macros in +`switch_x64_msvc.asm`, one unwind code per store, so `RtlUnwind` can find +`x19`–`x30`, `d8`–`d15`, and the `FPCR` slot. The Windows ARM64 test job +compiles this file and does not run it, so the `x18` fix needs a Windows +ARM64 machine or an emulator before it can be considered tested. + +**Applied fix.** The `x18` change was made as written above. Unwind codes +were not added. + +### AArch64 SysV and Apple: keep `x29` attached to the new stack + +File: `stackman/platforms/switch_aarch64_gcc.S`, in `stackman_switch`, +immediately after the first `blr` and before `mov w1, 1`. + +`x0` is the new stack pointer. `x19` is the callback and `x20` is the +context. `x1` is free until it becomes the opcode: + +```asm +sub x1, sp, x0 +mov sp, x0 +sub x29, x29, x1 +``` + +That is the same delta the Windows ARM64 file applies to `fp`. The epilogue +still reloads `x29` from the stack, so the caller receives the saved value, +not this adjusted one. The adjustment matters only while +`STACKMAN_OP_RESTORE` runs. A callback that relocates a stack still has to +rewrite saved frame-pointer slots in the copied bytes; this edit does not do +that. + +The checked-in `.S` is what default builds use (`STACKMAN_INLINE_ASM` is +unset). The inline sequence in `switch_aarch64_gcc.h` is the generator. Put +the same three instructions in that header if the `.S` is regenerated from +`gen_asm.c`, or the next regeneration will drop the delta. + +The same three-instruction pattern applies to `switch_arm_gcc.S` +(`sub r1, sp, r0` / `mov sp, r0` / `sub r11, r11, r1`) and to +`switch_riscv64_gcc.S` (`sub t0, sp, a0` before `mv sp, a0`, then add that +delta to `s0` only when `s0` is the live frame pointer). On RISC-V `s0` is +just a saved register in `stackman_switch`, not a frame chain, so the +integer spill is already complete and the delta is optional. `stackman_call` +uses `s0` to hold the previous stack pointer across the callback, so do not +adjust it there. + +**Applied fix.** The `x29` delta is in `switch_aarch64_gcc.S` and in +`switch_aarch64_gcc.h`. The `r11` delta was added to `switch_arm_gcc.S` +and `switch_arm_gcc.h`, then removed: that function omits the frame +pointer, so the delta does not land on this frame. The RISC-V `s0` delta +was left unchanged. + +### AArch64 SME lazy save + +On entry to `stackman_switch`, before `sp` changes, call `__arm_za_disable` +through a weak reference when the symbol is present. That is what AAPCS64 +requires of `setjmp` / `longjmp`: commit the lazy ZA buffer and clear +`TPIDR2_EL0` before the stack that holds the buffer is abandoned. Do not +spill the ZA array in this file. `FPMR`, `FFR`, and `ZT0` stay +caller-saved. Skip the call on the Windows ARM64 file; this helper is the +AAPCS64 one. This can wait until a supported target actually enables SME. + +**Applied fix.** None. `__arm_za_disable` is the wrong tool for this +switch. See Roadmap. + +### Windows x64: spill MXCSR with the xmm frame + +File: `stackman/platforms/switch_x64_msvc.asm`, `stackman_switch` only. + +`alloc_stack 168` covers `xmm6`–`xmm15` (160 bytes) plus an 8-byte pad at +`[rsp+160]`. After `alloc_stack` and before the `push_reg`s, `stmxcsr` +that dword. After the epilogue pops the GPRs and before `add rsp, 168`, +`ldmxcsr` the same slot. Restoring the whole register also restores the +volatile status flags, which is what `switch_x86_64_gcc.S` already does. +`stackman_call` does not need this: it returns to the same caller, and a +conforming callback restores MXCSR itself. + +Narrow the comment that says every non-volatile register is saved, once +MXCSR is included, or the comment stays ahead of the code. + +**Applied fix.** MXCSR is spilled and restored at `[rsp+160]` as described. +The comment now names the non-volatile GPRs, `xmm6`–`xmm15`, and MXCSR. + +### i386: spill the x87 control word + +The integer spills are complete. The gap is `fnstcw` / `fldcw` around the +two callbacks in `stackman_switch`, stored inside the frame that the +callback is asked to copy. + +- `switch_x86_gcc.S` and the inline block in `switch_x86_gcc.h` must + change together. The `.S` is a copy of the header's asm string. The + `subl $0x1c, %esp` frame has unused bytes at `0x10(%esp)`, `0x14(%esp)`, + and `0x18(%esp)`, and `0x10(%esp)` is the pointer passed to the callback. + `fnstcw` a word at that pointer before the first `call`, and `fldcw` it + after the second `call`, before `addl $0x1c, %esp`. After + `leal -0x10(%eax), %esp` the same word is at `0x10(%esp)` on the new + stack. Regenerating with `gcc -S -m32 -fcf-protection=none` from + `gen_asm.c` is the project's usual way to refresh the `.S`. +- `switch_x86_msvc.asm`: `fnstcw` / `fldcw` in `stackman_switch_raw`, in a + slot allocated with `ebx` / `esi` / `edi`, so the word is on the stack + image rather than in the C wrapper's `seh_state` local. The wrapper + already restores `fs:[0]` and should keep doing that. + +XMM registers stay unsaved: they are volatile for SysV i386 and for +Windows cdecl. `__vectorcall` is not the convention this library uses. + +**Applied fix.** Both gcc files and `stackman_switch_raw` store and reload +the control word in the frame the callback is asked to copy. The +instruction is `fstcw` / `fldcw`, matching `switch_x86_64_gcc.S`. The +wrapper still restores `fs:[0]`. + +### ARM32 soft-float: refuse a hard-float build + +`switch_arm_gcc.S` is `.arch armv5t` and `.fpu softvfp`, which matches +`arm-linux-gnueabi`. Do not add `d8`–`d15` until there is an `armhf` ABI. +Do stop a hard-float compile from silently using this file. In +`switch_arm_gcc.h`, `#error` when `__ARM_PCS_VFP` is set, with a line that +says the checked-in assembly does not spill `d8`–`d15`. Leave +`CP_REGISTERS` as the inline-only clobber, or drop it, so it cannot be read +as a description of `switch_arm_gcc.S`. + +The live `r11` adjustment in the AArch64 section above is optional here. +The shipped integer set (`r4`–`r11`, `lr`) needs no new stores. + +**Applied fix.** `__ARM_PCS_VFP` is a compile error, and `CP_REGISTERS` is +commented as inline-only. The optional `r11` adjustment was applied and +then removed. See the frame-pointer applied fix above. `d8`–`d15` were +not added. A separate hard-float port is on the roadmap. + +### Windows ARM32: delete it, or align `sp` before `vpush` + +`CHANGELOG.md` says 1.0.0 dropped `win_arm`. The sources are still wired +up: `switch_arm_msvc.asm`, `switch_arm_msvc.h`, the `_M_ARM` branch in +`platform.h`, and the `win_arm` output directories in the VS projects. +Preferred fix: delete that wiring so the file cannot be built. + +If the file stays, the entry `sp` is 8-byte aligned and +`push {r4-r10, fp, lr}` subtracts 36 bytes, so `vpush {q4-q7}` runs at +4 mod 8. Push a pad so the count is even, then pad `FPSCR` the same way, +or the `blx` is 4 mod 8 again: + +```asm +push {r3, r4, r5, r6, r7, r8, r9, r10, fp, lr} ; 40 bytes +add fp, sp, #32 ; saved fp is the 9th word +vpush {q4-q7} +vmrs r2, fpscr +push {r2, r3} ; fpscr plus a pad +``` + +Pop in reverse, with `pc` in place of `lr`. `r3` is a pad; it does not +have to be preserved beyond the pop. + +`stackman_call` has the same call-site bug. +`push {r4, fp, lr}` is 12 bytes. Push one more register +(`push {r3, r4, fp, lr}`) and point `fp` at the saved frame pointer with +`add fp, sp, #8`. + +**Applied fix.** The preferred option was taken. The sources, the +`_M_ARM` branch, the Debug|ARM configurations in vs2017, vs2019, and +vs2022, and `lib/win_arm/stackman.lib` were deleted. The alignment +alternative was not used. + +## Roadmap + +Two items from the audit are still open. + +### ARM32 hard-float + +The shipped `switch_arm_gcc.S` is soft-float (`arm-linux-gnueabi`, +`.arch armv5t`, `.fpu softvfp`) and does not spill `d8`–`d15`. +`__ARM_PCS_VFP` is a compile error, so a hard-float build cannot use that +file by mistake. + +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 +the compiler can still use `d8`–`d15`. + +### AArch64 SME lazy save + +SME (Scalable Matrix Extension) adds ZA, a matrix accumulator that is much +larger than the general-purpose and SIMD registers. A function that has ZA +live can avoid copying it on every call by leaving it dormant: +`TPIDR2_EL0` points at a save buffer on that function's stack, and a +normal callee leaves the buffer alone. `stackman_switch` can abandon that +stack, so the pointer would dangle. A later use of ZA would write through +it. + +`__arm_za_disable` is the helper `setjmp` uses. If the thread has SME, it +writes ZA into the buffer of the function that called `setjmp`, clears +`TPIDR2_EL0`, and turns ZA off. The compiler at that same call site turns +ZA back on and copies the buffer back. The address is a local in that +frame, so clearing `TPIDR2_EL0` does not lose it. `longjmp` returns to +that same frame, and the reload puts back the snapshot from `setjmp`. + +That is a single call site resuming itself. `stackman_switch` resumes +whichever stack the callback selects. The incoming activation's ZA is not +in the callee-saved spill, and its call site was not compiled to reload a +lazy-save buffer. Calling the helper on the way out would commit the +outgoing caller's ZA and turn ZA off, and nothing in the switch would +install the incoming caller's ZA. + +Leave the helper uncalled. A stack that uses SME would need its own ZA +image saved and restored with that stack, which this port does not do. +`stackman_call` returns to the same caller and does not need it either. + +## What the current tests cover + +`tests/test.c` already exercises a lot of the control path: + +- `test_01` checks that `stackman_switch` returns the `STACKMAN_OP_RESTORE` + result. The no-switch case returns `(void*)1`. +- `test_02` / `test_03` copy a stack out and jump back, which depends on the + non-volatile image being in the copied bytes. +- `test_04` calls `stackman_call` on a fresh block. The callback writes 256 + bytes on that stack and returns `(void *)(intptr_t)0x1234`. The test + checks the return value, that a caller local is unchanged, that a local in + the callback sits on the new block, and that the pointer argument is not + the stack passed to `stackman_call` and does not fall inside + `[block, stack)`. The old `STACKMAN_SP_LE(stack2, passed)` check was true + when `passed == stack2`, which is what the old RISC-V `stackman_call` did. +- `test_05` calls `stackman_call(..., 0)` and checks the same return value + and caller local. The pointer argument must be non-null and at or below a + caller local. The old RISC-V code set `sp` to zero and faulted here. +- `test_07` / `test_08` do floating-point and integer work around a no-op + `stackman_switch`. They hope the compiler keeps values in callee-saved + registers. If it keeps them in volatile registers or in memory, the test + passes even when the assembly drops a callee-saved register. + +Reading the epilogues, every port leaves the callback result in the ABI +return register (`x0`, `r0`, `a0`, `rax`, `eax`). `test_04` and `test_05` +now guard that. + +Linux CI (`.github/workflows/buildcommit.yml`, job `build-linux-gnu`) already +runs `make test` natively for x86-64, with `-m32` for i386, and under +`qemu-arm`, `qemu-aarch64`, and `qemu-riscv64`. macOS runs Intel and Apple +Silicon natively. Windows runs the test binary for x86 and x64 only. The +`arm64` Windows build is compiled and not executed (`matrix.native` is unset). + +## Tests + +The `stackman_call` contract checks are in `tests/test.c` (`test_04`, +`test_05`). They run on the existing Linux, qemu, and macOS jobs with no new +toolchains. On the first run of those tests, `qemu-riscv64` still printed +`test_03 ok` and then `test_06 ok`: `test_04` and `test_05` were not in the +binary. `STACKMAN_HAVE_CALL` lived inside the `STACKMAN_SWITCH_IMPL` branch +of `switch_riscv64_gcc.h`, so a normal `#include "stackman.h"` never defined +it and `tests/test.c` compiled the calls out. The macro is now set on that +include, with `STACKMAN_STACK_ALIGN` 16, matching the other platform headers. +The following `qemu-riscv64` run failed in `test_04` and `test_05`. +`switch_riscv64_gcc.S` now skips a null stack and passes the previous stack +pointer, which is the contract those tests check. + +Do not add a per-register assembly harness yet. + +### Assembly clobber harness + +A post-return sentinel check is the usual way to prove callee-saved +registers survive. It is a poor fit here. + +- It misses the Windows `x18` bug. That routine saves `x18` and writes it + back before `ret`. The damage is the live TEB during the callback. The + harness would see the restored value and pass. Sampling `x18` inside the + callback only means something on Windows, and the Windows ARM64 job does + not run tests. +- It needs a different `.S` or `.asm` for each ABI, kept in lockstep with + the spill list. The known mistakes above were found by reading those + lists against the ABI. A harness that encodes the same list will not + notice a register that both the implementation and the test omit. +- `test_07` and `test_08` already attempt the C version of this and can pass + for the wrong reason. Extending them will not make the signal much + stronger. +- SVE, SME, and shadow stacks need a specific qemu CPU model and a compiler + that emits that state. They are out of scope for a unit test of this + library's C ABI. + +If a harness is added later, limit it to the Linux ABIs the qemu jobs +already run (`sysv_amd64`, `sysv_i386`, `arm32`, `aarch64`, `riscv64`), and +only for the no-op switch: + +1. Assembly sets each callee-saved register to a sentinel, after saving the + real values for its own caller. +2. An assembly callback, not a C callback, overwrites those same registers. + A C callback is required to preserve them, so it cannot expose a missing + spill. +3. `stackman_switch` returns the same stack pointer. +4. The harness compares the sentinels, then restores the original registers. + +That catches "forgot to spill `d8`" on AArch64. It still does not catch +"`x18` was wrong for the duration of the call", and it does not belong in +the first patch. + +**Applied fix.** The `x18` bug itself was fixed, as described above. No +harness was added. A post-return sentinel would still miss a bug of that +shape. + +### Optional `fenv` check, still in C + +`fesetround` before `stackman_switch`, a callback that changes the rounding +mode and returns, then read the mode again. Expect the original mode where +the assembly saves the control word (SysV x86-64). Expect the callback's +mode to stick where the ABI treats that register as thread state and the +assembly does not save it (AAPCS64 `FPCR`, RISC-V `fcsr`). This is +ABI-specific and easy to get wrong; land the `stackman_call` contract tests +first. + +**Applied fix.** No `fenv` test was added. After the spills above, the +original mode should also come back on SysV i386 (x87 control word), +Windows x86 (x87 control word), and Windows x64 (MXCSR). diff --git a/lib/win_arm/stackman.lib b/lib/win_arm/stackman.lib deleted file mode 100644 index b45daad..0000000 Binary files a/lib/win_arm/stackman.lib and /dev/null differ diff --git a/stackman/platforms/platform.h b/stackman/platforms/platform.h index 90b0dd1..33b3917 100644 --- a/stackman/platforms/platform.h +++ b/stackman/platforms/platform.h @@ -37,10 +37,6 @@ #include "switch_x64_msvc.h" /* MS Visual Studio on X64 */ #define _STACKMAN_PLATFORM x64_msvc #define _STACKMAN_ABI win_x64 -#elif defined(_M_ARM) -#include "switch_arm_msvc.h" /* MS Visual Studio on ARM */ -#define _STACKMAN_PLATFORM arm_msvc -#define _STACKMAN_ABI win_arm #elif defined(_M_ARM64) #include "switch_arm64_msvc.h" /* MS Visual Studio on ARM */ #define _STACKMAN_PLATFORM arm64_msvc diff --git a/stackman/platforms/switch_aarch64_gcc.S b/stackman/platforms/switch_aarch64_gcc.S index 67fb984..d2b9120 100644 --- a/stackman/platforms/switch_aarch64_gcc.S +++ b/stackman/platforms/switch_aarch64_gcc.S @@ -89,8 +89,10 @@ LABEL(stackman_switch) blr x3 mov x2, x0 #APP -// 63 "../platforms/switch_aarch64_gcc.h" 1 +// 67 "../platforms/switch_aarch64_gcc.h" 1 + sub x1, sp, x0 mov sp, x0 + sub x29, x29, x1 // 0 "" 2 #NO_APP mov w1, 1 diff --git a/stackman/platforms/switch_aarch64_gcc.h b/stackman/platforms/switch_aarch64_gcc.h index 87d2a45..8e7c15c 100644 --- a/stackman/platforms/switch_aarch64_gcc.h +++ b/stackman/platforms/switch_aarch64_gcc.h @@ -59,8 +59,16 @@ void *STACKMAN_SWITCH_INASM_NAME(stackman_cb_t callback, void *context) __asm__ ("mov %[result], sp" : [result] "=r" (sp)); /* store stack */ sp = callback(context, STACKMAN_OP_SAVE, sp); - /* set stack pointer from sp using assembly */ - __asm__ ("mov sp, %[result]" : : [result] "r" (sp)); + /* Keep the live frame pointer attached to the new stack. + * x1 is free until the compiler assigns the RESTORE opcode. + * x29 is not an asm clobber: a clobber would make gcc save + * and restore it around this block and undo the delta. + */ + __asm__ ( + "sub x1, sp, %[result]\n\t" + "mov sp, %[result]\n\t" + "sub x29, x29, x1" + : : [result] "r" (sp) : "x1"); sp = callback(context, STACKMAN_OP_RESTORE, sp); return sp; } diff --git a/stackman/platforms/switch_arm64_msvc.asm b/stackman/platforms/switch_arm64_msvc.asm index b4b1f13..0745540 100644 --- a/stackman/platforms/switch_arm64_msvc.asm +++ b/stackman/platforms/switch_arm64_msvc.asm @@ -2,7 +2,6 @@ ; this code is adopted from the switch_aarch64_gcc.S ; which again was generated from switch_arm_gcc.h -; This code is also modelled on the hand-customized switch_arm_msvc.asm ; see https://docs.microsoft.com/en-us/cpp/build/arm64-windows-abi-conventions?view=msvc-160 ;ARM64 @@ -30,14 +29,17 @@ stp d14, d15, [sp, #160] ; args are x0=callback, x1=context - ; shuffle calling arguments into r0, r1 and r2 + ; shuffle calling arguments into x0, x1 and x2 ; for the call to the callback. + ; x18 is the TEB for the whole time user code runs. Spill it + ; above, and do not write the live register. x19 and x20 are + ; already spilled. mov x3, x0 mov x0, x1 ;context mov x2, sp ;stack pointer mov x1, #0 ;operation - mov x19, x0 ;store these a bit in nv registers - mov x18, x3 + mov x19, x0 ;context + mov x20, x3 ;callback (x3 is clobbered by the call) blr x3 ;first callback mov x2, x0 ;store new stackpointer in register, for next cb, ; switch stack, maintaining fp offset @@ -47,7 +49,7 @@ mov x1, #1 ;operation mov x0, x19 ;context - blr x18 ;second callback + blr x20 ;second callback ; restore registers from stack ldp x2, x18, [sp, #16] @@ -69,20 +71,18 @@ EXPORT |stackman_call| ALIGN |stackman_call| PROC -; args "callback", "context" are in x0, x1 - ; push non-volatile x18-x28 registers, in addition to fp,rl pair. - ; x29 is fp, x30 is lr. +; args "callback", "context", "stack" are in x0, x1, x2 + ; x29 is fp, x30 is lr. x18 is the TEB and is not touched. stp x29, x30, [sp, #-32]! ; store fp,lr pair, allocate stack space mov x29, sp ; and set fp pointing to old fp on stack - ; push non-volatile register 18 - str x18, [sp, #16] + str x19, [sp, #16] ; args are x0 = callback, x1 = context, x2=stack mov x3, x0 mov x0, x1 ;context mov x1, x2 ;stack - ; store current sp in nv register - mov x18, sp + ; store current sp in a spilled non-volatile + mov x19, sp mov x2, sp ; old stack @@ -94,10 +94,10 @@ nullptr blr x3 ; call callback, with context, opcode, old stack ; restore stack (could do: sub sp, fp #12) - mov sp, x18 + mov sp, x19 ; return - ldr x18, [sp, #16] + ldr x19, [sp, #16] ldp x29, x30, [sp], #32 ret ENDP diff --git a/stackman/platforms/switch_arm_gcc.S b/stackman/platforms/switch_arm_gcc.S index 8e11645..84929f8 100644 --- a/stackman/platforms/switch_arm_gcc.S +++ b/stackman/platforms/switch_arm_gcc.S @@ -43,7 +43,7 @@ stackman_switch: blx r3 mov r2, r0 .syntax divided -@ 88 "../platforms/switch_arm_gcc.h" 1 +@ 93 "../platforms/switch_arm_gcc.h" 1 mov sp, r0 @ 0 "" 2 .arm diff --git a/stackman/platforms/switch_arm_gcc.h b/stackman/platforms/switch_arm_gcc.h index 5fd867c..2ad70a6 100644 --- a/stackman/platforms/switch_arm_gcc.h +++ b/stackman/platforms/switch_arm_gcc.h @@ -5,6 +5,13 @@ * */ +/* The checked-in assembly is armv5t soft-float and does not spill d8-d15. + * __ARM_PCS_VFP is the hard-float (gnueabihf) ABI. Softfp does not define it. + */ +#if defined(__ARM_PCS_VFP) +#error The checked-in ARM32 assembly does not spill d8-d15. Hard-float (__ARM_PCS_VFP) is not supported. +#endif + /* clang cannot perform inline assembly using specific __attr__ * instructions, and so it may use a base pointer and other * things. We must force it to use the pre-build assembler @@ -56,12 +63,10 @@ */ #define NV_REGISTERS "r4", "r5","r6", "r7", "r8", "r9", "r10" , "r11" -/* These are the floating point extension registers. Same applies, we must preserve them in - * case the calling function was doing any floating point logic. - * it may be optionally disabled to store these floating point registers by not - * defining them here. - * Note that we do not - * preserve the FPSCR and VPR registers since they have complex rules about preservation. +/* Inline assembly only. switch_arm_gcc.S is .fpu softvfp and does not + * spill these. A hard-float build is rejected above. + * FPSCR and VPR are not preserved; their preservation rules are more + * specific than a blanket spill. */ #ifndef __thumb__ #define CP_REGISTERS "d8","d9","d10","d11","d12","d13","d14","d15" diff --git a/stackman/platforms/switch_arm_msvc.asm b/stackman/platforms/switch_arm_msvc.asm deleted file mode 100644 index 5caca3c..0000000 --- a/stackman/platforms/switch_arm_msvc.asm +++ /dev/null @@ -1,72 +0,0 @@ - - -; this code is adopted from the switch_arm_gcc.S -; which again was generated from switch_arm_gcc.h - - THUMB - - EXPORT |stackman_switch| - ALIGN -|stackman_switch| PROC - push {r4 - r10, fp, lr} ;9 x 4bytes - add fp, sp, #28 ; (4x7) fp must point to the previous fp on the stack - - ; also push floating point registers and fpscr (status&control register) - vpush {q4-q7} - vmrs r2,fpscr - push {r2} ;5 x 4bytes - ; total of 14 words pushed, satisfying 8 byte stack alignment. - - ; args are r0=callback, r1=context - ; shuffle calling arguments into r0, r1 and r2 - ; for the call to the callback. - mov r3, r0 - mov r0, r1 ;context - mov r2, sp ;stack pointer - mov r1, #0 ;operation - mov r5, r0 ;store these a bit - mov r4, r3 - blx r3 ;first callback - mov r2, r0 ;store new stackpointer in register, for next cb, - ; switch stack pointer, maintaining fp offset - sub r1, sp, r0 - mov sp, r0 ;switch stack pointer - sub fp, fp, r1 - - mov r1, #1 ;operation - mov r0, r5 ;context - blx r4 ;second callback - ; pop and return - pop {r2} - vmsr fpscr, r2 - vpop {q4, q5, q6, q7} - pop {r4-r9, r10, fp, pc} - ENDP - - EXPORT |stackman_call| - ALIGN -|stackman_call| PROC - ; args are r0 = callback, r1 = context, r2=stack - push {r4, fp, lr} - add fp, sp, #4 ;fp must always point to the last fp (r11) - mov r3, r0 - mov r0, r1 ;context - mov r1, r2 ;stack - ; store current sp in nv register - mov r4, sp - mov r2, sp ; old stack - - ; change stack if non-zero - cmp r1, #0 - beq nullptr - mov sp, r1 -nullptr - mov r1, #2 ; callback opcode - blx r3 ; call callback, with context, opcode, old stack - ; restore stack (could do: sub sp, fp #12) - mov sp, r4 - ; return - pop {r4, fp, pc} - ENDP - - END diff --git a/stackman/platforms/switch_arm_msvc.h b/stackman/platforms/switch_arm_msvc.h deleted file mode 100644 index 02025c6..0000000 --- a/stackman/platforms/switch_arm_msvc.h +++ /dev/null @@ -1,8 +0,0 @@ -/* The actual stack saving function, which just stores the stack, - * this declared in an .asm file - */ -#ifndef STACKMAN_ASSEMBLY_SRC -#define STACKMAN_ASSEMBLY_SRC switch_arm_msvc.asm -#define STACKMAN_HAVE_CALL 1 -#define STACKMAN_STACK_ALIGN 8 -#endif diff --git a/stackman/platforms/switch_riscv64_gcc.S b/stackman/platforms/switch_riscv64_gcc.S index 54d97c1..3a504ec 100644 --- a/stackman/platforms/switch_riscv64_gcc.S +++ b/stackman/platforms/switch_riscv64_gcc.S @@ -166,15 +166,20 @@ LABEL(stackman_switch) .LFE0: SIZE_FUNCTION(stackman_switch) -/* +/* * void *stackman_call(stackman_cb_t callback, void *context, void *new_stack) - * - * Similar to stackman_switch but uses explicitly provided stack pointer. - * + * + * Calls callback(context, STACKMAN_OP_CALL, old_sp). A non-zero new_stack + * replaces sp for that call. Zero leaves the current stack in place. + * The third argument is the stack pointer from before the switch. + * * Register usage: - * - a0: callback function pointer - * - a1: context pointer - * - a2: new stack pointer + * - a0: callback function pointer, then context + * - a1: context pointer, then opcode + * - a2: requested stack pointer, then the previous stack pointer + * - s0: previous stack pointer (callee-saved) + * - t0: callback + * - t1: requested stack pointer */ .align 1 FUNCTION(stackman_call) @@ -195,15 +200,20 @@ LABEL(stackman_call) /* Save old stack pointer in s0 (callee-saved register) */ mv s0, sp - /* Setup args for callback and switch to new stack */ + /* Preserve the callback and the requested stack. Both are + * caller-saved, and both are consumed before jalr. */ mv t0, a0 /* t0 = callback */ - mv t1, a2 /* t1 = new_stack (for arg) */ - mv sp, a2 /* switch to new stack */ + mv t1, a2 /* t1 = requested stack, or zero */ - /* Call callback(context, opcode, sp) */ + /* callback(context, STACKMAN_OP_CALL, previous sp) */ mv a0, a1 /* a0 = context */ li a1, 2 /* a1 = STACKMAN_OP_CALL */ - mv a2, t1 /* a2 = new_stack */ + mv a2, s0 /* a2 = previous stack pointer */ + + /* A null stack means "keep the current one". */ + beqz t1, .Lcall_keep_sp + mv sp, t1 +.Lcall_keep_sp: jalr t0 /* call callback */ /* Restore old stack pointer from s0 */ diff --git a/stackman/platforms/switch_riscv64_gcc.h b/stackman/platforms/switch_riscv64_gcc.h index 8e08597..a534f6c 100644 --- a/stackman/platforms/switch_riscv64_gcc.h +++ b/stackman/platforms/switch_riscv64_gcc.h @@ -10,17 +10,20 @@ #define STACKMAN_ASSEMBLY_SRC "platforms/switch_riscv64_gcc.S" #endif -#ifndef STACKMAN_SWITCH_IMPL -/* Just check if platform is supported */ -#define STACKMAN_SWITCH_INCLUDED -#else - +/* Visible to tests and other callers of stackman.h, not only to the + * translation unit that defines STACKMAN_SWITCH_IMPL. stackman.h + * otherwise leaves STACKMAN_STACK_ALIGN at 4 and tests/test.c compiles + * out test_04 and test_05. */ #ifndef STACKMAN_HAVE_CALL #define STACKMAN_HAVE_CALL 1 -#undef STACKMAN_STACK_ALIGN #define STACKMAN_STACK_ALIGN 16 #endif +#ifndef STACKMAN_SWITCH_IMPL +/* Just check if platform is supported */ +#define STACKMAN_SWITCH_INCLUDED +#else + #if __ASSEMBLER__ && defined(STACKMAN_ASSEMBLY_SRC) /* Include pre-generated assembly code */ #include STACKMAN_ASSEMBLY_SRC diff --git a/stackman/platforms/switch_x64_msvc.asm b/stackman/platforms/switch_x64_msvc.asm index 1ff34dc..be0e757 100644 --- a/stackman/platforms/switch_x64_msvc.asm +++ b/stackman/platforms/switch_x64_msvc.asm @@ -20,10 +20,11 @@ endm ;arguments callback, context, are passed in rcx, rdx, respectively ;stackman_switch PROC FRAME NESTED_ENTRY stackman_switch, _TEXT$00 - ; save all registers that the x64 ABI specifies as non-volatile. - ; This includes some mmx registers. May not always be necessary, - ; unless our application is doing 3D, but better safe than sorry. - alloc_stack 168; 10 * 16 bytes, plus 8 bytes to make stack 16 byte aligned + ; Save the non-volatile GPRs, xmm6-xmm15, and MXCSR. + ; Upper ymm/zmm bits are volatile. MXCSR status flags are volatile + ; too; saving the whole register matches the SysV file. + alloc_stack 168; xmm6-xmm15 (160 bytes) plus an 8-byte MXCSR slot + stmxcsr dword ptr [rsp+160] save_xmm128 xmm15, 144 save_xmm128 xmm14, 128 save_xmm128 xmm13, 112 @@ -94,6 +95,7 @@ NESTED_ENTRY stackman_switch, _TEXT$00 load_xmm128 xmm8, 32 load_xmm128 xmm7, 16 load_xmm128 xmm6, 0 + ldmxcsr dword ptr [rsp+160] add rsp, 168 ret diff --git a/stackman/platforms/switch_x86_64_gcc.h b/stackman/platforms/switch_x86_64_gcc.h index 6c89514..0357b79 100644 --- a/stackman/platforms/switch_x86_64_gcc.h +++ b/stackman/platforms/switch_x86_64_gcc.h @@ -51,7 +51,7 @@ void *STACKMAN_SWITCH_INASM_NAME(stackman_cb_t callback, void *context) { void *stack_pointer, *stack_pointer2; ptrdiff_t diff; - /* assembly to save non-volatile registers, including x87 and mmx */ + /* assembly to save non-volatile registers, including the x87 control word and MXCSR */ int mxcsr; short x87cw; __asm__ volatile ( "fstcw %[cw]\n\t" diff --git a/stackman/platforms/switch_x86_gcc.S b/stackman/platforms/switch_x86_gcc.S index 671a6b5..558b7b9 100644 --- a/stackman/platforms/switch_x86_gcc.S +++ b/stackman/platforms/switch_x86_gcc.S @@ -30,11 +30,12 @@ stackman_switch: movl 8(%ebp), %eax movl 12(%ebp), %edx #APP -# 50 "../platforms/switch_x86_gcc.h" 1 +# 53 "../platforms/switch_x86_gcc.h" 1 subl $0x1c, %esp movl %eax, %esi movl %edx, %edi leal 0x10(%esp), %ebx +fstcw 0x10(%esp) movl %ebx, 8(%esp) movl $0, 4(%esp) movl %edi, 0(%esp) @@ -48,6 +49,7 @@ movl $1, 4(%esp) movl %edi, 0(%esp) call *%esi movl %eax, %eax +fldcw 0x10(%esp) addl $0x1c, %esp # 0 "" 2 diff --git a/stackman/platforms/switch_x86_gcc.h b/stackman/platforms/switch_x86_gcc.h index 14a3858..d93ded1 100644 --- a/stackman/platforms/switch_x86_gcc.h +++ b/stackman/platforms/switch_x86_gcc.h @@ -21,7 +21,11 @@ /* Follow System V i386 abi, including 16 byte stack alignment * https://wiki.osdev.org/System_V_ABI#i386 * eax, ecx, edx are scratch regs, - * 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 matches switch_x86_64_gcc.S. + * It is FWAIT plus FNSTCW, so a pending unmasked exception is delivered + * on this stack before the switch. * We have compiler construct a frame and push ebp * an then we fix an aligned stack pointer and just store * the arguments in proper places. @@ -65,6 +69,7 @@ void *result; /* first call */ "leal 0x10(%%esp), %%ebx\n" /* arg 2 adjusted sp (also in nv. reg) */ + "fstcw 0x10(%%esp)\n" /* x87 control word, on the copied image */ "movl %%ebx, 8(%%esp)\n" "movl $0, 4(%%esp)\n" /* arg 1 opcode STACKMAN_OP_SAVE */ "movl %%edi, 0(%%esp)\n" /* arg 0 context */ @@ -86,6 +91,7 @@ void *result; "movl %%eax, %[result]\n" + "fldcw 0x10(%%esp)\n" "addl $0x1c, %%esp\n" : [result] "=r" (result) /* output variables */ : [cb] "r" (callback), /* input variables */ diff --git a/stackman/platforms/switch_x86_msvc.asm b/stackman/platforms/switch_x86_msvc.asm index 2f28cf9..e24f4a1 100644 --- a/stackman/platforms/switch_x86_msvc.asm +++ b/stackman/platforms/switch_x86_msvc.asm @@ -11,6 +11,11 @@ stackman_switch_raw PROC callback:DWORD, context:DWORD push ebx push esi push edi + ; x87 control word sits at the pointer passed to the callback, + ; so the copied stack image carries it. Not in the C wrapper: + ; that frame does not travel with a real switch. + sub esp, 4 + fstcw word ptr [esp] mov esi, callback ; /* save 'callback' for later */ mov edi, context ; /* save 'context' for later */ @@ -35,6 +40,9 @@ stackman_switch_raw PROC callback:DWORD, context:DWORD call esi ; /* call callback() */ add esp, 12 + fldcw word ptr [esp] + add esp, 4 + pop edi pop esi pop ebx diff --git a/stackman/stackman_switch.h b/stackman/stackman_switch.h index cb83a3a..968f615 100644 --- a/stackman/stackman_switch.h +++ b/stackman/stackman_switch.h @@ -98,7 +98,8 @@ typedef enum stackman_op_t { STACKMAN_OP_RESTORE = 1, /* The callback is being invoked from a stackman_call() invocation. - * Stack_pointer is the pointer that was provided to stackman_call() + * stack_pointer is the stack pointer from before the call. A null + * stack argument to stackman_call() leaves that pointer unchanged. */ STACKMAN_OP_CALL = 2, diff --git a/tests/test.c b/tests/test.c index f8a0057..6acd77e 100644 --- a/tests/test.c +++ b/tests/test.c @@ -192,21 +192,30 @@ void test_03(void *stack_marker) #define TEST_04 #endif #ifdef TEST_04 +/* Sentinel returned by stackman_call. The epilogue must leave it intact. */ +static void *const stackman_call_result = (void *)(intptr_t)0x1234; + /* test retrieval of stack pointer */ void *test_04_cb(void* context, int _opcode, void *old_sp) { + unsigned char scratch[256]; assert_align(old_sp); ctxt01 *c = (ctxt01*)context; stackman_op_t opcode = (stackman_op_t)_opcode; assert(opcode == STACKMAN_OP_CALL || opcode == 100); - /* one level of recursion here so that we actually use some of the new stack */ - if (opcode == STACKMAN_OP_CALL) + /* Write the stack the callback is running on. The caller's locals + * must still be intact when stackman_call returns. */ + if (opcode == STACKMAN_OP_CALL) { + memset(scratch, 0xa5, sizeof scratch); + assert(scratch[0] == 0xa5 && scratch[sizeof scratch - 1] == 0xa5); + /* one level of recursion here so that we actually use some of the new stack */ test_04_cb(context, 100, old_sp); + } c->sp[0] = &c; c->sp[1] = old_sp; - return 0; + return stackman_call_result; } /* test stackman_call() with a non-null stack pointer */ @@ -215,6 +224,8 @@ void test_04(void) char *block, *stack, *stack2; intptr_t stacksize=1024; int i, cnt; + int caller_local = 0x51a7; + void *result; ctxt01 ctxt; assert(STACKMAN_STACK_FULL_DESCENDING); @@ -232,14 +243,20 @@ void test_04(void) stack2 = stack-64; /* perform the call */ - stackman_call(test_04_cb, &ctxt, stack2); + result = stackman_call(test_04_cb, &ctxt, stack2); + assert(result == stackman_call_result); + assert(caller_local == 0x51a7); /* verify that the called function saw stack in the right range */ assert(STACKMAN_SP_LE(block, ctxt.sp[0])); assert(STACKMAN_SP_LS(ctxt.sp[0], stack2)); - /* verify that old stack was outside the stack we allocated */ - assert(STACKMAN_SP_LE(stack2, ctxt.sp[1]) || STACKMAN_SP_LS(ctxt.sp[1], block)); + /* The pointer passed to the callback is the previous stack pointer. + * It is not the stack given to stackman_call, and it does not fall + * inside the new block [block, stack). */ + assert(ctxt.sp[1] != (void *)stack2); + assert(STACKMAN_SP_LS(ctxt.sp[1], block) || + STACKMAN_SP_LE((void *)stack, ctxt.sp[1])); /* verify that the guard memory is correct */ cnt = 0; @@ -257,17 +274,21 @@ void test_04(void) void test_05(void) { ctxt01 ctxt; + int caller_local = 0x51a7; + void *result; assert(STACKMAN_STACK_FULL_DESCENDING); /* perform the call */ - stackman_call(test_04_cb, &ctxt, 0); + result = stackman_call(test_04_cb, &ctxt, 0); + assert(result == stackman_call_result); + assert(caller_local == 0x51a7); - /* verify that it was passed a stack */ + /* A null stack leaves the caller in place. The callback receives + * that live stack pointer, which is at or below the caller's locals. */ assert(ctxt.sp[1]); assert(STACKMAN_SP_LE(ctxt.sp[0], ctxt.sp[1])); - - /* and that it was passed valid lower stack pointer */ + assert(STACKMAN_SP_LE(ctxt.sp[1], (void *)&caller_local)); assert(STACKMAN_SP_LE(ctxt.sp[1], &ctxt)); } diff --git a/vs2017/stackman.sln b/vs2017/stackman.sln index 6dff39b..3a11a17 100644 --- a/vs2017/stackman.sln +++ b/vs2017/stackman.sln @@ -12,22 +12,17 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "stackman", "stackman\stackm EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|ARM = Debug|ARM Debug|ARM64 = Debug|ARM64 Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {CAFDBD3E-9D0D-4E90-BB6D-6C2A19F5EF53}.Debug|ARM.ActiveCfg = Debug|ARM - {CAFDBD3E-9D0D-4E90-BB6D-6C2A19F5EF53}.Debug|ARM.Build.0 = Debug|ARM {CAFDBD3E-9D0D-4E90-BB6D-6C2A19F5EF53}.Debug|ARM64.ActiveCfg = Debug|ARM64 {CAFDBD3E-9D0D-4E90-BB6D-6C2A19F5EF53}.Debug|ARM64.Build.0 = Debug|ARM64 {CAFDBD3E-9D0D-4E90-BB6D-6C2A19F5EF53}.Debug|x64.ActiveCfg = Debug|x64 {CAFDBD3E-9D0D-4E90-BB6D-6C2A19F5EF53}.Debug|x64.Build.0 = Debug|x64 {CAFDBD3E-9D0D-4E90-BB6D-6C2A19F5EF53}.Debug|x86.ActiveCfg = Debug|Win32 {CAFDBD3E-9D0D-4E90-BB6D-6C2A19F5EF53}.Debug|x86.Build.0 = Debug|Win32 - {BF7D0638-AC4F-4206-B426-66CDDD468281}.Debug|ARM.ActiveCfg = Debug|ARM - {BF7D0638-AC4F-4206-B426-66CDDD468281}.Debug|ARM.Build.0 = Debug|ARM {BF7D0638-AC4F-4206-B426-66CDDD468281}.Debug|ARM64.ActiveCfg = Debug|ARM64 {BF7D0638-AC4F-4206-B426-66CDDD468281}.Debug|ARM64.Build.0 = Debug|ARM64 {BF7D0638-AC4F-4206-B426-66CDDD468281}.Debug|x64.ActiveCfg = Debug|x64 diff --git a/vs2017/stackman/stackman.vcxproj b/vs2017/stackman/stackman.vcxproj index be7106e..76a073a 100644 --- a/vs2017/stackman/stackman.vcxproj +++ b/vs2017/stackman/stackman.vcxproj @@ -1,10 +1,6 @@ - - Debug - ARM - Debug ARM64 @@ -39,12 +35,6 @@ v141 Unicode - - StaticLibrary - true - v141 - Unicode - StaticLibrary true @@ -64,9 +54,6 @@ - - - @@ -80,10 +67,6 @@ true $(SolutionDir)\..\lib\win_x64\ - - true - $(SolutionDir)\..\lib\win_arm\ - true $(SolutionDir)\..\lib\win_arm64\ @@ -128,26 +111,6 @@ /Brepro %(AdditionalOptions) - - - Level3 - true - _DEBUG;_LIB;%(PreprocessorDefinitions) - true - /Zl %(AdditionalOptions) - NotUsing - pch.h - OldStyle - - - - - true - - - /Brepro %(AdditionalOptions) - - Level3 @@ -171,7 +134,6 @@ - @@ -185,44 +147,30 @@ AssemblyCode - AssemblyCode AssemblyCode false - false false Default - Default Default false - false false true - true true true - - false - true - true - true - Document - true true true true - true true true - true true diff --git a/vs2017/stackman/stackman.vcxproj.filters b/vs2017/stackman/stackman.vcxproj.filters index 9f57a1f..d4051af 100644 --- a/vs2017/stackman/stackman.vcxproj.filters +++ b/vs2017/stackman/stackman.vcxproj.filters @@ -33,9 +33,6 @@ Header Files - - Header Files - Header Files @@ -57,9 +54,6 @@ - - Source Files - Source Files diff --git a/vs2017/test/test.vcxproj b/vs2017/test/test.vcxproj index 9d14c4b..283f20c 100644 --- a/vs2017/test/test.vcxproj +++ b/vs2017/test/test.vcxproj @@ -1,10 +1,6 @@ - - Debug - ARM - Debug ARM64 @@ -38,12 +34,6 @@ v141 Unicode - - Application - true - v141 - Unicode - Application true @@ -61,9 +51,6 @@ - - - @@ -76,9 +63,6 @@ true - - true - true @@ -113,21 +97,6 @@ ..\..\lib\win_x64;%(AdditionalLibraryDirectories) - - - Level3 - true - _DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - ..\..\stackman - - - Console - true - stackman.lib;%(AdditionalDependencies) - ..\..\lib\win_arm;%(AdditionalLibraryDirectories) - - Level3 diff --git a/vs2019/stackman.sln b/vs2019/stackman.sln index 6dff39b..3a11a17 100644 --- a/vs2019/stackman.sln +++ b/vs2019/stackman.sln @@ -12,22 +12,17 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "stackman", "stackman\stackm EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|ARM = Debug|ARM Debug|ARM64 = Debug|ARM64 Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {CAFDBD3E-9D0D-4E90-BB6D-6C2A19F5EF53}.Debug|ARM.ActiveCfg = Debug|ARM - {CAFDBD3E-9D0D-4E90-BB6D-6C2A19F5EF53}.Debug|ARM.Build.0 = Debug|ARM {CAFDBD3E-9D0D-4E90-BB6D-6C2A19F5EF53}.Debug|ARM64.ActiveCfg = Debug|ARM64 {CAFDBD3E-9D0D-4E90-BB6D-6C2A19F5EF53}.Debug|ARM64.Build.0 = Debug|ARM64 {CAFDBD3E-9D0D-4E90-BB6D-6C2A19F5EF53}.Debug|x64.ActiveCfg = Debug|x64 {CAFDBD3E-9D0D-4E90-BB6D-6C2A19F5EF53}.Debug|x64.Build.0 = Debug|x64 {CAFDBD3E-9D0D-4E90-BB6D-6C2A19F5EF53}.Debug|x86.ActiveCfg = Debug|Win32 {CAFDBD3E-9D0D-4E90-BB6D-6C2A19F5EF53}.Debug|x86.Build.0 = Debug|Win32 - {BF7D0638-AC4F-4206-B426-66CDDD468281}.Debug|ARM.ActiveCfg = Debug|ARM - {BF7D0638-AC4F-4206-B426-66CDDD468281}.Debug|ARM.Build.0 = Debug|ARM {BF7D0638-AC4F-4206-B426-66CDDD468281}.Debug|ARM64.ActiveCfg = Debug|ARM64 {BF7D0638-AC4F-4206-B426-66CDDD468281}.Debug|ARM64.Build.0 = Debug|ARM64 {BF7D0638-AC4F-4206-B426-66CDDD468281}.Debug|x64.ActiveCfg = Debug|x64 diff --git a/vs2019/stackman/stackman.vcxproj b/vs2019/stackman/stackman.vcxproj index cfe06e8..fc969f6 100644 --- a/vs2019/stackman/stackman.vcxproj +++ b/vs2019/stackman/stackman.vcxproj @@ -1,10 +1,6 @@ - - Debug - ARM - Debug ARM64 @@ -39,12 +35,6 @@ v142 Unicode - - StaticLibrary - true - v142 - Unicode - StaticLibrary true @@ -64,9 +54,6 @@ - - - @@ -80,10 +67,6 @@ true $(SolutionDir)\..\lib\win_x64\ - - true - $(SolutionDir)\..\lib\win_arm\ - true $(SolutionDir)\..\lib\win_arm64\ @@ -128,26 +111,6 @@ /Brepro %(AdditionalOptions) - - - Level3 - true - _DEBUG;_LIB;%(PreprocessorDefinitions) - true - /Zl %(AdditionalOptions) - NotUsing - pch.h - OldStyle - - - - - true - - - /Brepro %(AdditionalOptions) - - Level3 @@ -171,7 +134,6 @@ - @@ -185,44 +147,30 @@ AssemblyCode - AssemblyCode AssemblyCode false - false false Default - Default Default false - false false true - true false true - - false - true - true - true - Document - true true true true - true true true - true true diff --git a/vs2019/stackman/stackman.vcxproj.filters b/vs2019/stackman/stackman.vcxproj.filters index 9f57a1f..d4051af 100644 --- a/vs2019/stackman/stackman.vcxproj.filters +++ b/vs2019/stackman/stackman.vcxproj.filters @@ -33,9 +33,6 @@ Header Files - - Header Files - Header Files @@ -57,9 +54,6 @@ - - Source Files - Source Files diff --git a/vs2019/test/test.vcxproj b/vs2019/test/test.vcxproj index 93dc2cf..54109b3 100644 --- a/vs2019/test/test.vcxproj +++ b/vs2019/test/test.vcxproj @@ -1,10 +1,6 @@ - - Debug - ARM - Debug ARM64 @@ -38,12 +34,6 @@ v142 Unicode - - Application - true - v142 - Unicode - Application true @@ -61,9 +51,6 @@ - - - @@ -76,9 +63,6 @@ true - - true - true @@ -113,21 +97,6 @@ ..\..\lib\win_x64;%(AdditionalLibraryDirectories) - - - Level3 - true - _DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - ..\..\stackman - - - Console - true - stackman.lib;%(AdditionalDependencies) - ..\..\lib\win_arm;%(AdditionalLibraryDirectories) - - Level3 diff --git a/vs2022/stackman.sln b/vs2022/stackman.sln index 6dff39b..3a11a17 100644 --- a/vs2022/stackman.sln +++ b/vs2022/stackman.sln @@ -12,22 +12,17 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "stackman", "stackman\stackm EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|ARM = Debug|ARM Debug|ARM64 = Debug|ARM64 Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {CAFDBD3E-9D0D-4E90-BB6D-6C2A19F5EF53}.Debug|ARM.ActiveCfg = Debug|ARM - {CAFDBD3E-9D0D-4E90-BB6D-6C2A19F5EF53}.Debug|ARM.Build.0 = Debug|ARM {CAFDBD3E-9D0D-4E90-BB6D-6C2A19F5EF53}.Debug|ARM64.ActiveCfg = Debug|ARM64 {CAFDBD3E-9D0D-4E90-BB6D-6C2A19F5EF53}.Debug|ARM64.Build.0 = Debug|ARM64 {CAFDBD3E-9D0D-4E90-BB6D-6C2A19F5EF53}.Debug|x64.ActiveCfg = Debug|x64 {CAFDBD3E-9D0D-4E90-BB6D-6C2A19F5EF53}.Debug|x64.Build.0 = Debug|x64 {CAFDBD3E-9D0D-4E90-BB6D-6C2A19F5EF53}.Debug|x86.ActiveCfg = Debug|Win32 {CAFDBD3E-9D0D-4E90-BB6D-6C2A19F5EF53}.Debug|x86.Build.0 = Debug|Win32 - {BF7D0638-AC4F-4206-B426-66CDDD468281}.Debug|ARM.ActiveCfg = Debug|ARM - {BF7D0638-AC4F-4206-B426-66CDDD468281}.Debug|ARM.Build.0 = Debug|ARM {BF7D0638-AC4F-4206-B426-66CDDD468281}.Debug|ARM64.ActiveCfg = Debug|ARM64 {BF7D0638-AC4F-4206-B426-66CDDD468281}.Debug|ARM64.Build.0 = Debug|ARM64 {BF7D0638-AC4F-4206-B426-66CDDD468281}.Debug|x64.ActiveCfg = Debug|x64 diff --git a/vs2022/stackman/stackman.vcxproj b/vs2022/stackman/stackman.vcxproj index 32dc9f0..9b37f4d 100644 --- a/vs2022/stackman/stackman.vcxproj +++ b/vs2022/stackman/stackman.vcxproj @@ -1,10 +1,6 @@ - - Debug - ARM - Debug ARM64 @@ -39,12 +35,6 @@ v143 Unicode - - StaticLibrary - true - v143 - Unicode - StaticLibrary true @@ -64,9 +54,6 @@ - - - @@ -80,10 +67,6 @@ true $(SolutionDir)\..\lib\win_x64\ - - true - $(SolutionDir)\..\lib\win_arm\ - true $(SolutionDir)\..\lib\win_arm64\ @@ -128,26 +111,6 @@ /Brepro %(AdditionalOptions) - - - Level3 - true - _DEBUG;_LIB;%(PreprocessorDefinitions) - true - /Zl %(AdditionalOptions) - NotUsing - pch.h - OldStyle - - - - - true - - - /Brepro %(AdditionalOptions) - - Level3 @@ -171,7 +134,6 @@ - @@ -185,44 +147,30 @@ AssemblyCode - AssemblyCode AssemblyCode false - false false Default - Default Default false - false false true - true false true - - false - true - true - true - Document - true true true true - true true true - true true diff --git a/vs2022/stackman/stackman.vcxproj.filters b/vs2022/stackman/stackman.vcxproj.filters index 9f57a1f..d4051af 100644 --- a/vs2022/stackman/stackman.vcxproj.filters +++ b/vs2022/stackman/stackman.vcxproj.filters @@ -33,9 +33,6 @@ Header Files - - Header Files - Header Files @@ -57,9 +54,6 @@ - - Source Files - Source Files diff --git a/vs2022/test/test.vcxproj b/vs2022/test/test.vcxproj index 266eb72..10cbf33 100644 --- a/vs2022/test/test.vcxproj +++ b/vs2022/test/test.vcxproj @@ -1,10 +1,6 @@ - - Debug - ARM - Debug ARM64 @@ -38,12 +34,6 @@ v143 Unicode - - Application - true - v143 - Unicode - Application true @@ -61,9 +51,6 @@ - - - @@ -76,9 +63,6 @@ true - - true - true @@ -113,21 +97,6 @@ ..\..\lib\win_x64;%(AdditionalLibraryDirectories) - - - Level3 - true - _DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - ..\..\stackman - - - Console - true - stackman.lib;%(AdditionalDependencies) - ..\..\lib\win_arm;%(AdditionalLibraryDirectories) - - Level3