Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 3 additions & 138 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -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.
143 changes: 143 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -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_<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.

## 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`).
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
Loading
Loading