fibers: rename ABI variant .pure -> .naked

"pure" universally means side-effect-free (GCC __attribute__((pure)),
FP purity, D's pure) — the opposite of a register-clobbering context
switch. The concept is "naked": no compiler-generated prologue/epilogue,
body is raw asm that emits its own ret. That is the established term
everywhere (LLVM's naked function attribute — which we literally emit —
plus Zig callconv(.naked), Rust #[naked], GCC/Clang __attribute__
((naked))). Rename the keyword + everything keyed off it so concept,
surface, field, and the emitted LLVM attribute all agree.

- ast.zig: ABI enum variant pure -> naked (+ doc).
- parser: accept abi(.naked); error text updated.
- IR Function.is_pure -> is_naked; type_resolver/decl/generic/pack/
  emit_llvm references updated; diagnostics say abi(.naked).
- examples 1800-1803 renamed *-pure-* -> *-naked-* (source + expected/
  snapshots; .ir/.exit/.stdout/.stderr are byte-identical — the emitted
  IR is unchanged, only the keyword spelling differs).
- docs (PLAN-FIBERS, CHECKPOINT-FIBERS, PLAN-POST-METATYPE, the design
  roadmap, the compiler-API checkpoint/design) updated; the naming
  rationale now records why .naked over .pure.

No semantic change — pure cosmetics. Suite green (725/0).
This commit is contained in:
agra
2026-06-20 17:01:09 +03:00
parent b631590574
commit a7fe165684
40 changed files with 175 additions and 171 deletions

View File

@@ -351,7 +351,7 @@ linkage keyword are two orthogonal annotations.
- `abi(.x)` — ABI / calling-convention annotation in the slot **before** - `abi(.x)` — ABI / calling-convention annotation in the slot **before**
`extern`/`export`. **Unified replacement for `callconv(...)`, which is removed.** `extern`/`export`. **Unified replacement for `callconv(...)`, which is removed.**
`ABI = { default, c, zig, pure }`: `.c` (C ABI), `.zig` (Zig-layout weld → the `ABI = { default, c, zig, pure }`: `.c` (C ABI), `.zig` (Zig-layout weld → the
`compiler` library), `.pure` (naked asm), `.default` (unannotated). Can appear `compiler` library), `.naked` (naked asm), `.default` (unannotated). Can appear
standalone (no extern) on any fn / fn-type / lambda. standalone (no extern) on any fn / fn-type / lambda.
- `extern <lib>` — linkage keyword + binding source (named library). - `extern <lib>` — linkage keyword + binding source (named library).
@@ -363,12 +363,12 @@ What landed:
- **Lexer/token** (`src/token.zig`, `src/lexer.zig`): `kw_callconv` → `kw_abi`, - **Lexer/token** (`src/token.zig`, `src/lexer.zig`): `kw_callconv` → `kw_abi`,
keyword string `"callconv"` → `"abi"`. keyword string `"callconv"` → `"abi"`.
- **Parser** (`src/parser.zig`): `parseOptionalCallConv` → `parseOptionalAbi` - **Parser** (`src/parser.zig`): `parseOptionalCallConv` → `parseOptionalAbi`
(parses `abi(.c|.zig|.pure)`); wired in the fn-decl postfix slot (before (parses `abi(.c|.zig|.naked)`); wired in the fn-decl postfix slot (before
`extern`/`export`), the function-type-expr slot, and the lambda slot; `extern`/`export`), the function-type-expr slot, and the lambda slot;
`isFunctionDef`/`hasFnBodyAfterArrow` recognise `kw_abi`. `isFunctionDef`/`hasFnBodyAfterArrow` recognise `kw_abi`.
- **AST→IR map** (`src/ir/type_resolver.zig`, `src/ir/lower/decl.zig`, `sema.zig`, - **AST→IR map** (`src/ir/type_resolver.zig`, `src/ir/lower/decl.zig`, `sema.zig`,
`closure.zig`): the AST `.abi == .c` reads kept their C-ABI meaning; the `closure.zig`): the AST `.abi == .c` reads kept their C-ABI meaning; the
function-type resolver maps `.zig`/`.pure` → IR `.default` (no fn-pointer-type function-type resolver maps `.zig`/`.naked` → IR `.default` (no fn-pointer-type
CC for those decl-level ABIs; neither occurs in a function-TYPE position yet). CC for those decl-level ABIs; neither occurs in a function-TYPE position yet).
- **CC-mismatch diagnostic** (`src/ir/lower/expr.zig`, `src/sema.zig`): the - **CC-mismatch diagnostic** (`src/ir/lower/expr.zig`, `src/sema.zig`): the
user-facing text `callconv(.c)` → `abi(.c)`. user-facing text `callconv(.c)` → `abi(.c)`.
@@ -379,7 +379,7 @@ What landed:
- **Tests**: parser unit tests in `src/parser.test.zig` — `abi(.zig) extern <lib>` - **Tests**: parser unit tests in `src/parser.test.zig` — `abi(.zig) extern <lib>`
on a fn decl (asserts `abi == .zig`, `extern_export == .extern_`, `extern_lib == on a fn decl (asserts `abi == .zig`, `extern_export == .extern_`, `extern_lib ==
"compiler"`); bare `extern` leaves `abi == .default`; standalone `abi(.c)` / "compiler"`); bare `extern` leaves `abi == .default`; standalone `abi(.c)` /
`abi(.pure)`. lexer/sema tests updated. `abi(.naked)`. lexer/sema tests updated.
`zig build` + `zig build test` green (450/450 unit + 685 corpus). `zig build` + `zig build test` green (450/450 unit + 685 corpus).
@@ -1656,7 +1656,7 @@ when reached (sentinels or accessor fns; see the design doc Risks).
after `struct`. Parser unit tests (welded `Field` + plain struct), break-verified. after `struct`. Parser unit tests (welded `Field` + plain struct), break-verified.
Build + suite green. Parse-only sub-step (fns + structs) of Phase 1.1 complete. Build + suite green. Parse-only sub-step (fns + structs) of Phase 1.1 complete.
- **Phase 1.1 first sub-step + `callconv`→`abi` unification.** Parsed `abi(.zig) - **Phase 1.1 first sub-step + `callconv`→`abi` unification.** Parsed `abi(.zig)
extern <lib>` on fn decls; unified `callconv` into `abi(.c|.zig|.pure)` (removed extern <lib>` on fn decls; unified `callconv` into `abi(.c|.zig|.naked)` (removed
the `callconv` keyword), migrated 52 sx files + compiler diagnostics + docs + the `callconv` keyword), migrated 52 sx files + compiler diagnostics + docs +
snapshots. Build + suite green. The original design's `extern(.zig)` single snapshots. Build + suite green. The original design's `extern(.zig)` single
qualifier was split into `abi(.zig)` (ABI/layout, before extern) + `extern qualifier was split into `abi(.zig)` (ABI/layout, before extern) + `extern

View File

@@ -4,57 +4,57 @@ Companion to [PLAN-FIBERS.md](PLAN-FIBERS.md). Update after every step (one step
per the cadence rule). New corpus category: `18xx` concurrency. per the cadence rule). New corpus category: `18xx` concurrency.
## Last completed step ## Last completed step
**B1.0b (`abi(.pure)` real emission) — DONE. B1.0 complete.** Replaced the emit bail with **B1.0b (`abi(.naked)` real emission) — DONE. B1.0 complete.** Replaced the emit bail with
real LLVM `naked` emission: real LLVM `naked` emission:
- `emit_llvm` declaration pass: for `func.is_pure`, add the LLVM `naked` + `noinline` + - `emit_llvm` declaration pass: for `func.is_naked`, add the LLVM `naked` + `noinline` +
`nounwind` attributes and **skip** the `frame-pointer=all` attribute (incompatible with a `nounwind` attributes and **skip** the `frame-pointer=all` attribute (incompatible with a
frameless function). Pass 2 now emits the `.pure` body normally — `naked` makes the frameless function). Pass 2 now emits the `.naked` body normally — `naked` makes the
backend emit it verbatim (the inline asm + its own `ret`) with no prologue/epilogue. backend emit it verbatim (the inline asm + its own `ret`) with no prologue/epilogue.
- IR shape (verified): `; Function Attrs: naked noinline nounwind` / `define internal i64 - IR shape (verified): `; Function Attrs: naked noinline nounwind` / `define internal i64
@answer() #0 { entry: call void asm sideeffect "…ret…", ""() unreachable }` / @answer() #0 { entry: call void asm sideeffect "…ret…", ""() unreachable }` /
`attributes #0 = { naked noinline nounwind }`. The caller invokes it as an ordinary `attributes #0 = { naked noinline nounwind }`. The caller invokes it as an ordinary
`() -> i64` call (`.pure` is `call_conv == .default`). `() -> i64` call (`.naked` is `call_conv == .default`).
- `examples/1800-concurrency-pure-asm.sx` — now GREEN, aarch64-pinned (`.build {"target": - `examples/1800-concurrency-naked-asm.sx` — now GREEN, aarch64-pinned (`.build {"target":
"macos"}`): runs end-to-end → **exit 42** on this host, ir-only on a mismatch; `.ir` "macos"}`): runs end-to-end → **exit 42** on this host, ir-only on a mismatch; `.ir`
snapshot captured. snapshot captured.
- `examples/1801-concurrency-pure-generic.sx` (renamed from `-bail`) — the generic `.pure` - `examples/1801-concurrency-naked-generic.sx` (renamed from `-bail`) — the generic `.naked`
now emits a correct naked `answer__i64` (exit 42), proving generic.zig produces a naked now emits a correct naked `answer__i64` (exit 42), proving generic.zig produces a naked
body, not a framed one. aarch64-pinned. body, not a framed one. aarch64-pinned.
- `examples/1802-concurrency-pure-asm-x86.sx` — x86_64 cross sibling (`.build {"target": - `examples/1802-concurrency-naked-asm-x86.sx` — x86_64 cross sibling (`.build {"target":
"x86_64-linux"}`, ir-only here): `.ir` locks `naked` + `movl $42, %eax` / `ret`. "x86_64-linux"}`, ir-only here): `.ir` locks `naked` + `movl $42, %eax` / `ret`.
- Unit test `emit: abi(.pure) function gets the naked attribute (no frame-pointer)` in - Unit test `emit: abi(.naked) function gets the naked attribute (no frame-pointer)` in
`emit_llvm.test.zig` (asserts `naked` present, `frame-pointer` absent). `emit_llvm.test.zig` (asserts `naked` present, `frame-pointer` absent).
- **B1.0c (review-hardening):** a param-bearing `.pure` fn emitted invalid LLVM (loud - **B1.0c (review-hardening):** a param-bearing `.naked` fn emitted invalid LLVM (loud
verifier error "cannot use argument of naked function") because the param-alloca loop verifier error "cannot use argument of naked function") because the param-alloca loop
wasn't gated. Fixed forward (this *enables* the B1.3 context-switch use case rather than wasn't gated. Fixed forward (this *enables* the B1.3 context-switch use case rather than
rejecting it): gated the param-alloca loop on `fd.abi != .pure` in decl.zig (both paths) + rejecting it): gated the param-alloca loop on `fd.abi != .naked` in decl.zig (both paths) +
generic.zig; a naked fn's args stay in registers (read by asm), declared-but-unused in generic.zig; a naked fn's args stay in registers (read by asm), declared-but-unused in
LLVM. Locked by `examples/1803-concurrency-pure-asm-param.sx` (`add(a,b)` → x0+x1 → 42). LLVM. Locked by `examples/1803-concurrency-naked-asm-param.sx` (`add(a,b)` → x0+x1 → 42).
- `zig build && zig build test` green: **725 ran, 0 failed** + unit tests. - `zig build && zig build test` green: **725 ran, 0 failed** + unit tests.
### Earlier — B1.0a (lock + review hardening) ### Earlier — B1.0a (lock + review hardening)
Plumbed `Function.is_pure` (set from `fd.abi == .pure` at both decl sites + generic.zig + Plumbed `Function.is_naked` (set from `fd.abi == .naked` at both decl sites + generic.zig +
pack.zig); `funcWantsImplicitCtx` skips `.pure` (no synthetic ctx, like `.c`); all pack.zig); `funcWantsImplicitCtx` skips `.naked` (no synthetic ctx, like `.c`); all
body-lowering paths bypass `lowerValueBody` for `.pure` (asm body + `unreachable` cap — no sx body-lowering paths bypass `lowerValueBody` for `.naked` (asm body + `unreachable` cap — no sx
return); `emit_llvm` Pass 2 bailed loudly (since flipped to real emission). Adversarial return); `emit_llvm` Pass 2 bailed loudly (since flipped to real emission). Adversarial
review caught the generic/pack `is_pure` gap (a generic `.pure` silently shipped a framed review caught the generic/pack `is_naked` gap (a generic `.naked` silently shipped a framed
body); closed + locked. The review's `.pure`-lambda CRITICAL was a false positive body); closed + locked. The review's `.naked`-lambda CRITICAL was a false positive
(unparseable — `isLambda` breaks on the `abi` keyword). (unparseable — `isLambda` breaks on the `abi` keyword).
## Current state ## Current state
Stream A (atomics) is feature-complete (✅). Stream B1: **B1.0 complete** — `abi(.pure)` Stream A (atomics) is feature-complete (✅). Stream B1: **B1.0 complete** — `abi(.naked)`
emits a real LLVM `naked` function end-to-end (decl, generic, pack paths), the substrate for emits a real LLVM `naked` function end-to-end (decl, generic, pack paths), the substrate for
the fiber context-switch. No fibers/Io/scheduler code yet. Grounded floor facts: the fiber context-switch. No fibers/Io/scheduler code yet. Grounded floor facts:
- `context` is already an implicit `*Context` param (slot 0) + `push Context` is a stack - `context` is already an implicit `*Context` param (slot 0) + `push Context` is a stack
`alloca` ⇒ **fiber-local for free**. Only shared root = `__sx_default_context` global `alloca` ⇒ **fiber-local for free**. Only shared root = `__sx_default_context` global
(entry-point bind). B1.1 expected to be a **library convention** (spawn trampoline (entry-point bind). B1.1 expected to be a **library convention** (spawn trampoline
snapshots the spawner's ctx into slot 0), **likely zero compiler change** — probe first. snapshots the spawner's ctx into slot 0), **likely zero compiler change** — probe first.
- Inline asm works end-to-end (lower→emit→JIT, aarch64 + x86_64) — the `.pure` body reuses it. - Inline asm works end-to-end (lower→emit→JIT, aarch64 + x86_64) — the `.naked` body reuses it.
- **`.pure` with PARAMS works** (B1.0c, the B1.3 substrate): the param-alloca loop is gated - **`.naked` with PARAMS works** (B1.0c, the B1.3 substrate): the param-alloca loop is gated
on `fd.abi != .pure` in decl.zig (both paths) + generic.zig — a naked fn's args stay in on `fd.abi != .naked` in decl.zig (both paths) + generic.zig — a naked fn's args stay in
ABI registers (read by the asm body), declared-but-unused in LLVM (verifier-legal). ABI registers (read by the asm body), declared-but-unused in LLVM (verifier-legal).
Example `1803-concurrency-pure-asm-param.sx` (`add(a,b)` reads x0/x1). **Unsupported (loud, Example `1803-concurrency-naked-asm-param.sx` (`add(a,b)` reads x0/x1). **Unsupported (loud,
not silent):** a `.pure` *variadic-pack* fn (pack.zig's param loop is intertwined with not silent):** a `.naked` *variadic-pack* fn (pack.zig's param loop is intertwined with
comptime-param/`#insert` handling, and a naked fn can't read a runtime-sized pack from comptime-param/`#insert` handling, and a naked fn can't read a runtime-sized pack from
registers anyway) → loud LLVM-verifier error for that nonsensical construct. Acceptable registers anyway) → loud LLVM-verifier error for that nonsensical construct. Acceptable
boundary; a sharper sx diagnostic for it is a candidate polish, not a blocker. boundary; a sharper sx diagnostic for it is a candidate polish, not a blocker.
@@ -80,20 +80,21 @@ a checkpoint note on the convention. Only if the probe surfaces a real gap (a pa
## Decisions (Stream B1 specifics; surface locked in design §4 / §4.6) ## Decisions (Stream B1 specifics; surface locked in design §4 / §4.6)
- **The async runtime is sx LIBRARY code.** The compiler provides only: the general - **The async runtime is sx LIBRARY code.** The compiler provides only: the general
primitives (inline asm ✅, `abi(.pure)` naked [B1.0], atomics ✅) + fiber-safe codegen primitives (inline asm ✅, `abi(.naked)` naked [B1.0], atomics ✅) + fiber-safe codegen
(`context` already fiber-local — B1.1). Schedulers, fibers, channels, futures, `Io` (`context` already fiber-local — B1.1). Schedulers, fibers, channels, futures, `Io`
vtables, `mmap` stacks are all sx. vtables, `mmap` stacks are all sx.
- **`abi(.pure)` is the real spelling of the design's `callconv(.naked)`** — postfix slot, - **`abi(.naked)` is the real spelling of the design's `callconv(.naked)`** — postfix slot,
`name :: (sig) -> Ret abi(.pure) { asm { … }; }`. B1.0 = carry it into IR + emit LLVM `name :: (sig) -> Ret abi(.naked) { asm { … }; }`. B1.0 = carry it into IR + emit LLVM
`naked` + skip prologue/ctx (mirror the existing `.c` skip), NOT extend the enum (it's `naked` + skip prologue/ctx (mirror the existing `.c` skip), NOT extend the enum (it's
already there, just inert). already there, just inert).
- **`.pure` ≠ `.c`:** a `.c` epilogue would restore SP from the wrong stack across a context - **`.naked` ≠ `.c`:** a `.c` epilogue would restore SP from the wrong stack across a context
switch (SP-in ≠ SP-out by design). `.pure` = no prologue/epilogue/frame; the asm emits its switch (SP-in ≠ SP-out by design). `.naked` = no prologue/epilogue/frame; the asm emits its
own `ret`. This is why the switch must be `.pure`. own `ret`. This is why the switch must be `.naked`.
- **Naming:** sx-facing name is **`pure`** (field `is_pure`, the diagnostic). LLVM's `naked` - **Naming:** sx-facing name is **`naked`** (keyword `abi(.naked)`, field `is_naked`, the
function attribute is only the lowering mechanism (B1.0b) — do not call the function diagnostic), matching LLVM's `naked` attribute and the industry term (Zig/Rust/GCC/Clang).
"naked" (user direction). The ABI variant was renamed `.pure → .naked` (user direction): "pure" universally means
- **B1.0 snapshot scope:** a `.pure` body is raw per-arch asm; LLVM's `naked` attr text is *side-effect-free*, the opposite of a register-clobbering context switch.
- **B1.0 snapshot scope:** a `.naked` body is raw per-arch asm; LLVM's `naked` attr text is
arch-invariant. **B1.0a** = one host example locked to the emit bail (host-independent — arch-invariant. **B1.0a** = one host example locked to the emit bail (host-independent —
fires before instruction selection; no `.build` pin). **B1.0b** = pin aarch64 + add an fires before instruction selection; no `.build` pin). **B1.0b** = pin aarch64 + add an
x86_64 cross sibling (`.build` target-gated, ir-only on mismatch), like the asm corpus x86_64 cross sibling (`.build` target-gated, ir-only on mismatch), like the asm corpus
@@ -111,34 +112,34 @@ a checkpoint note on the convention. Only if the probe surfaces a real gap (a pa
## Log ## Log
- **carve** — wrote PLAN-FIBERS.md + CHECKPOINT-FIBERS.md. Grounded the B1 compiler floor: - **carve** — wrote PLAN-FIBERS.md + CHECKPOINT-FIBERS.md. Grounded the B1 compiler floor:
`ABI.pure` inert (type_resolver.zig:237), IR `Function` has no naked flag (inst.zig:605), `ABI.naked` inert (type_resolver.zig:237), IR `Function` has no naked flag (inst.zig:605),
attribute API pattern (emit_llvm.zig:1339 nounwind), `.c` ctx-skip precedent attribute API pattern (emit_llvm.zig:1339 nounwind), `.c` ctx-skip precedent
(decl.zig:515), `push Context` stack-alloca + slot-0 implicit ctx (stmt.zig:1263, (decl.zig:515), `push Context` stack-alloca + slot-0 implicit ctx (stmt.zig:1263,
lower.zig:259), `__sx_default_context` root (decl.zig:2667/2815), inline-asm corpus lower.zig:259), `__sx_default_context` root (decl.zig:2667/2815), inline-asm corpus
(1645/1651). Corrected the design's `callconv(.naked)` → real `abi(.pure)` spelling and (1645/1651). Corrected the design's `callconv(.naked)` → real `abi(.naked)` spelling and
the B1.0 snapshot story. B1.1 grounded as likely library-only. Baseline green (721/0). the B1.0 snapshot story. B1.1 grounded as likely library-only. Baseline green (721/0).
- **B1.0a** — plumbed `Function.is_pure` (set from `fd.abi == .pure` at both decl sites); - **B1.0a** — plumbed `Function.is_naked` (set from `fd.abi == .naked` at both decl sites);
`funcWantsImplicitCtx` skips `.pure` (no implicit ctx, like `.c`); both body-lowering `funcWantsImplicitCtx` skips `.naked` (no implicit ctx, like `.c`); both body-lowering
paths bypass `lowerValueBody` for `.pure` (asm body + `unreachable` cap — no sx return); paths bypass `lowerValueBody` for `.naked` (asm body + `unreachable` cap — no sx return);
`emit_llvm` Pass 2 bails loudly on `func.is_pure`. `examples/1800-concurrency-pure-asm.sx` `emit_llvm` Pass 2 bails loudly on `func.is_naked`. `examples/1800-concurrency-naked-asm.sx`
locked to the bail (exit 1 + diagnostic). Renamed `is_naked`→`is_pure` per user direction locked to the bail (exit 1 + diagnostic). Suite green (722/0). (ABI variant later renamed
(sx says `pure`, not "naked"; LLVM `naked` attr is only the B1.0b mechanism). Suite green `.pure → .naked` — see the Naming decision above — so all `is_*`/`abi(.*)`/example names
(722/0). here read `naked`.)
- **B1.0a review-hardening** — adversarial review found generic/pack Function-creation paths - **B1.0a review-hardening** — adversarial review found generic/pack Function-creation paths
left `is_pure` false (silent framed body for a generic `.pure` instance — returned 42 but left `is_naked` false (silent framed body for a generic `.naked` instance — returned 42 but
corrupted the stack). Fixed generic.zig + pack.zig (set `is_pure` + asm-only `unreachable` corrupted the stack). Fixed generic.zig + pack.zig (set `is_naked` + asm-only `unreachable`
cap); locked by `examples/1801-concurrency-pure-generic-bail.sx`. The review's `.pure`- cap); locked by `examples/1801-concurrency-naked-generic-bail.sx`. The review's `.naked`-
lambda CRITICAL was a false positive (unparseable — `isLambda` breaks on `abi`). Suite lambda CRITICAL was a false positive (unparseable — `isLambda` breaks on `abi`). Suite
green (723/0). green (723/0).
- **B1.0b** — real `naked` emission: emit_llvm declaration pass adds LLVM `naked`/`noinline`/ - **B1.0b** — real `naked` emission: emit_llvm declaration pass adds LLVM `naked`/`noinline`/
`nounwind` + skips `frame-pointer` for `func.is_pure`; Pass 2 emits the body verbatim (no `nounwind` + skips `frame-pointer` for `func.is_naked`; Pass 2 emits the body verbatim (no
prologue). `1800` green aarch64-pinned (exit 42 + `.ir`); renamed `1801` → `-generic` prologue). `1800` green aarch64-pinned (exit 42 + `.ir`); renamed `1801` → `-generic`
(generic `.pure` emits a naked body, exit 42); added x86_64 sibling `1802` (ir-only, `.ir` (generic `.naked` emits a naked body, exit 42); added x86_64 sibling `1802` (ir-only, `.ir`
locks `naked` + `movl $42, %eax`). Unit test asserts `naked` present + `frame-pointer` locks `naked` + `movl $42, %eax`). Unit test asserts `naked` present + `frame-pointer`
absent. Suite green (724/0). absent. Suite green (724/0).
- **B1.0c** — review-hardening: param-bearing `.pure` emitted invalid LLVM (loud verifier - **B1.0c** — review-hardening: param-bearing `.naked` emitted invalid LLVM (loud verifier
error). Gated the param-alloca loop on `fd.abi != .pure` (decl.zig both paths + generic.zig) error). Gated the param-alloca loop on `fd.abi != .naked` (decl.zig both paths + generic.zig)
— naked args stay in registers, read by the asm body (the B1.3 context-switch shape). — naked args stay in registers, read by the asm body (the B1.3 context-switch shape).
Locked by `examples/1803-concurrency-pure-asm-param.sx`. Pack `.pure` left unsupported Locked by `examples/1803-concurrency-naked-asm-param.sx`. Pack `.naked` left unsupported
(loud, nonsensical). **B1.0 complete.** Suite green (725/0). **Next: B1.1 (per-fiber (loud, nonsensical). **B1.0 complete.** Suite green (725/0). **Next: B1.1 (per-fiber
context, probe-first).** context, probe-first).**

View File

@@ -1,6 +1,6 @@
# PLAN-FIBERS — Stream B1 (fibers + Io + M:1 scheduler) # PLAN-FIBERS — Stream B1 (fibers + Io + M:1 scheduler)
> **STATUS: 🚧 in progress.** B1.0 (`abi(.pure)` codegen) ✅ complete — emits a real LLVM > **STATUS: 🚧 in progress.** B1.0 (`abi(.naked)` codegen) ✅ complete — emits a real LLVM
> `naked` function end-to-end (decl / generic / pack paths; examples 1800/1801/1802 + unit > `naked` function end-to-end (decl / generic / pack paths; examples 1800/1801/1802 + unit
> test). Next step = **B1.1** (per-fiber `context` root — probe-first, likely library-only). > test). Next step = **B1.1** (per-fiber `context` root — probe-first, likely library-only).
@@ -12,7 +12,7 @@ separate carve ([PLAN-CHANNELS.md], when reached) and depends on this + atomics
**Goal:** the colorblind, stackful, **pure-sx** async runtime — fibers behind an `Io` **Goal:** the colorblind, stackful, **pure-sx** async runtime — fibers behind an `Io`
interface, an M:1 scheduler, blocking + deterministic-sim + event-loop `Io` impls. The interface, an M:1 scheduler, blocking + deterministic-sim + event-loop `Io` impls. The
**compiler floor is small and net-new**: make `abi(.pure)` actually emit an LLVM `naked` **compiler floor is small and net-new**: make `abi(.naked)` actually emit an LLVM `naked`
function (B1.0), and confirm/close the per-fiber `context` root (B1.1). **Everything function (B1.0), and confirm/close the per-fiber `context` root (B1.1). **Everything
else — the context-switch asm, fiber bootstrap, `mmap` stacks, the scheduler, futures, else — the context-switch asm, fiber bootstrap, `mmap` stacks, the scheduler, futures,
the `Io` vtables — is ordinary sx library code** (design §4, §4.4). The irreducible FFI the `Io` vtables — is ordinary sx library code** (design §4, §4.4). The irreducible FFI
@@ -30,25 +30,27 @@ authorization.
## Design (grounded against the tree) ## Design (grounded against the tree)
### B1.0 — `abi(.pure)` codegen (the one genuinely net-new compiler piece in B1) ### B1.0 — `abi(.naked)` codegen (the one genuinely net-new compiler piece in B1)
The design doc spells this `callconv(.naked)`; the **real sx surface is `abi(.pure)`** The design doc spells this `callconv(.naked)`; the **real sx surface is `abi(.naked)`**
written in the postfix slot, `name :: (sig) -> Ret abi(.pure) { asm { … }; }` (cf. written in the postfix slot, `name :: (sig) -> Ret abi(.naked) { asm { … }; }` (cf.
`build_options :: () -> BuildOptions abi(.compiler);` in [build.sx:28](../library/modules/build.sx#L28)). `build_options :: () -> BuildOptions abi(.compiler);` in [build.sx:28](../library/modules/build.sx#L28)).
The sx-facing name is **pure** throughout (field, flag, diagnostics); LLVM's `naked` The sx-facing name is **`naked`** throughout (keyword, field `is_naked`, diagnostics) —
function attribute is only the *lowering mechanism* (B1.0b), not what we call the function. matching LLVM's `naked` attribute (the lowering mechanism) and the industry term
(Zig/Rust/GCC/Clang). The ABI variant was renamed `.pure → .naked`: "pure" universally
means *side-effect-free*, the opposite of a register-clobbering context switch.
**Grounding (verified — do not re-derive):** **Grounding (verified — do not re-derive):**
- The `ABI` enum **already carries `.pure`**`ABI = enum { default, c, compiler, pure }` - The `ABI` enum **already carries `.naked`**`ABI = enum { default, c, compiler, naked }`
([ast.zig:142](../src/ast.zig#L142)), documented "pure / naked function (inline asm ([ast.zig:142](../src/ast.zig#L142)), documented "naked function (inline asm
body), no calling-convention prologue/epilogue." So B1.0 is **NOT** "extend the enum." body), no calling-convention prologue/epilogue." So B1.0 is **NOT** "extend the enum."
- `.pure` is **inert today**: [type_resolver.zig:237](../src/ir/type_resolver.zig#L237) - `.naked` is **inert today**: [type_resolver.zig:237](../src/ir/type_resolver.zig#L237)
maps `.compiler, .pure → .default` CC, and `emit_llvm` emits **no LLVM `naked` maps `.compiler, .naked → .default` CC, and `emit_llvm` emits **no LLVM `naked`
attribute**. So the net-new work is exactly: **carry `abi == .pure` into the IR attribute**. So the net-new work is exactly: **carry `abi == .naked` into the IR
`Function`, emit LLVM's `naked` attr, and skip the implicit-`Context` / prologue `Function`, emit LLVM's `naked` attr, and skip the implicit-`Context` / prologue
lowering** so the body is just the asm block + its own `ret`. lowering** so the body is just the asm block + its own `ret`.
- The IR `Function` struct ([inst.zig:605](../src/ir/inst.zig#L605)) carries `call_conv` - The IR `Function` struct ([inst.zig:605](../src/ir/inst.zig#L605)) carries `call_conv`
(default/c) + `is_compiler_domain`, but **no pure flag** — add one (`is_pure: bool`). (default/c) + `is_compiler_domain`, but **no naked flag** — add one (`is_naked: bool`).
- Attribute API is in-tree: `nounwind` is set at - Attribute API is in-tree: `nounwind` is set at
[emit_llvm.zig:1339](../src/ir/emit_llvm.zig#L1339) via [emit_llvm.zig:1339](../src/ir/emit_llvm.zig#L1339) via
`LLVMGetEnumAttributeKindForName("nounwind", 8)``LLVMCreateEnumAttribute(ctx, id, 0)` `LLVMGetEnumAttributeKindForName("nounwind", 8)``LLVMCreateEnumAttribute(ctx, id, 0)`
@@ -56,24 +58,24 @@ function attribute is only the *lowering mechanism* (B1.0b), not what we call th
is the same shape: `LLVMGetEnumAttributeKindForName("naked", 5)`. is the same shape: `LLVMGetEnumAttributeKindForName("naked", 5)`.
- The `.c` ABI **already skips the implicit ctx** at lowering — `lam.abi == .c` / - The `.c` ABI **already skips the implicit ctx** at lowering — `lam.abi == .c` /
`fd.abi == .c` gates (closure.zig:171, [decl.zig:515](../src/ir/lower/decl.zig#L515)). `fd.abi == .c` gates (closure.zig:171, [decl.zig:515](../src/ir/lower/decl.zig#L515)).
`.pure` must skip it **too** (a `.pure` fn gets no synthetic `__sx_ctx`, no stack frame, `.naked` must skip it **too** (a `.naked` fn gets no synthetic `__sx_ctx`, no stack frame,
no prologue — args arrive in ABI registers and are read directly from asm). The no prologue — args arrive in ABI registers and are read directly from asm). The
implicit-return machinery (`lowerValueBody`) must also be bypassed: a `.pure` body has no implicit-return machinery (`lowerValueBody`) must also be bypassed: a `.naked` body has no
sx return (the asm rets itself), so lower its statements and cap the block with sx return (the asm rets itself), so lower its statements and cap the block with
`unreachable`. `unreachable`.
- **Inline asm already works end-to-end** (lower→emit→JIT): aarch64 - **Inline asm already works end-to-end** (lower→emit→JIT): aarch64
([examples/1645](../examples/1645-platform-asm-aarch64-add.sx)), x86_64 ([examples/1645](../examples/1645-platform-asm-aarch64-add.sx)), x86_64
([examples/1651](../examples/1651-platform-asm-x86-syscall-write.sx)), global asm, JIT ([examples/1651](../examples/1651-platform-asm-x86-syscall-write.sx)), global asm, JIT
([1653](../examples/1653-platform-asm-global-jit.sx)). `emitInlineAsm` / ([1653](../examples/1653-platform-asm-global-jit.sx)). `emitInlineAsm` /
`LLVMGetInlineAsm` at [ops.zig:915](../src/backend/llvm/ops.zig#L915). The `.pure` body `LLVMGetInlineAsm` at [ops.zig:915](../src/backend/llvm/ops.zig#L915). The `.naked` body
is a single asm block reusing this path. is a single asm block reusing this path.
**`.pure``.c` (design §4.6 context-switch note):** a `.c` epilogue restores SP from the **`.naked``.c` (design §4.6 context-switch note):** a `.c` epilogue restores SP from the
frame; a context switch deliberately makes SP-in ≠ SP-out, so the `.c` epilogue would frame; a context switch deliberately makes SP-in ≠ SP-out, so the `.c` epilogue would
restore from the *wrong* stack. `.pure` = no prologue/epilogue/frame — the asm emits its restore from the *wrong* stack. `.naked` = no prologue/epilogue/frame — the asm emits its
own `ret`. This is *why* the switch must be `.pure`, not `.c`. own `ret`. This is *why* the switch must be `.naked`, not `.c`.
**Snapshot story (per the atomics precedent):** a `.pure` fn's *body is raw per-arch asm* **Snapshot story (per the atomics precedent):** a `.naked` fn's *body is raw per-arch asm*
(it can't be portable — that's the point), while LLVM's `naked` attribute text is (it can't be portable — that's the point), while LLVM's `naked` attribute text is
arch-invariant. **B1.0a** (lock) needs only **one host example** locked to the emit bail — arch-invariant. **B1.0a** (lock) needs only **one host example** locked to the emit bail —
the bail fires at the function level *before* any asm/instruction selection, so it is the bail fires at the function level *before* any asm/instruction selection, so it is
@@ -133,18 +135,18 @@ compiler work.** Prerequisite of B1.3 (a fiber needs a valid root before it swit
don't churn every snapshot. don't churn every snapshot.
### Files the compiler floor touches (B1.0 only; B1.1B1.5 are library + tests) ### Files the compiler floor touches (B1.0 only; B1.1B1.5 are library + tests)
B1.0 (`.pure`) forces these plumbing sites: B1.0 (`.naked`) forces these plumbing sites:
- [ast.zig:142](../src/ast.zig#L142) — `ABI.pure` (exists; reference only). - [ast.zig:142](../src/ast.zig#L142) — `ABI.naked` (exists; reference only).
- [inst.zig:605](../src/ir/inst.zig#L605) — add `is_pure: bool = false` to `Function`. - [inst.zig:605](../src/ir/inst.zig#L605) — add `is_naked: bool = false` to `Function`.
- [decl.zig](../src/ir/lower/decl.zig) — set `is_pure` from `fd.abi == .pure`; gate the - [decl.zig](../src/ir/lower/decl.zig) — set `is_naked` from `fd.abi == .naked`; gate the
implicit-ctx off for `.pure` in `funcWantsImplicitCtx` (mirror the `.c` skip at implicit-ctx off for `.naked` in `funcWantsImplicitCtx` (mirror the `.c` skip at
decl.zig:515) and bypass `lowerValueBody` for `.pure` bodies (lower statements + cap with decl.zig:515) and bypass `lowerValueBody` for `.naked` bodies (lower statements + cap with
`unreachable`, in both body-lowering paths) — a `.pure` fn binds no ctx and has no sx `unreachable`, in both body-lowering paths) — a `.naked` fn binds no ctx and has no sx
return. return.
- [type_resolver.zig:237](../src/ir/type_resolver.zig#L237) — leave CC `.default` (a `.pure` - [type_resolver.zig:237](../src/ir/type_resolver.zig#L237) — leave CC `.default` (a `.naked`
fn-pointer type has no CC of its own; pureness is a decl-level emit attribute). fn-pointer type has no CC of its own; nakedness is a decl-level emit attribute).
- [emit_llvm.zig:402](../src/ir/emit_llvm.zig#L402) Pass 2 — **B1.0a:** bail loudly when - [emit_llvm.zig:402](../src/ir/emit_llvm.zig#L402) Pass 2 — **B1.0a:** bail loudly when
`func.is_pure` (build-gating). **B1.0b:** instead emit LLVM's `naked` attr (shape per `func.is_naked` (build-gating). **B1.0b:** instead emit LLVM's `naked` attr (shape per
`nounwind` at emit_llvm.zig:1339) + the asm-only body (no prologue). `nounwind` at emit_llvm.zig:1339) + the asm-only body (no prologue).
- Any `.op`/`Function`-field switch the Zig build flags — let the build tell you. - Any `.op`/`Function`-field switch the Zig build flags — let the build tell you.
@@ -152,25 +154,25 @@ B1.0 (`.pure`) forces these plumbing sites:
## Phases (xfail→green steps) ## Phases (xfail→green steps)
### B1.0 — `abi(.pure)` codegen — ✅ COMPLETE ### B1.0 — `abi(.naked)` codegen — ✅ COMPLETE
- **B1.0a (lock) — ✅ DONE.** Carried `abi == .pure` into IR `Function.is_pure`; threaded - **B1.0a (lock) — ✅ DONE.** Carried `abi == .naked` into IR `Function.is_naked`; threaded
through `decl.zig` (`funcWantsImplicitCtx` skips `.pure` like `.c`; all body-lowering paths through `decl.zig` (`funcWantsImplicitCtx` skips `.naked` like `.c`; all body-lowering paths
bypass `lowerValueBody` for `.pure`, lowering the asm body + capping with `unreachable`) + bypass `lowerValueBody` for `.naked`, lowering the asm body + capping with `unreachable`) +
generic.zig + pack.zig; `emit_llvm` Pass 2 bailed loudly on `func.is_pure`. Locked by generic.zig + pack.zig; `emit_llvm` Pass 2 bailed loudly on `func.is_naked`. Locked by
`examples/1800-concurrency-pure-asm.sx` + the generic regression (review-found gap). `examples/1800-concurrency-naked-asm.sx` + the generic regression (review-found gap).
- **B1.0b (green) — ✅ DONE.** `emit_llvm` declaration pass adds LLVM `naked` + `noinline` + - **B1.0b (green) — ✅ DONE.** `emit_llvm` declaration pass adds LLVM `naked` + `noinline` +
`nounwind` for `func.is_pure` and skips `frame-pointer=all` (incompatible with a frameless `nounwind` for `func.is_naked` and skips `frame-pointer=all` (incompatible with a frameless
function); Pass 2 emits the body normally (`naked` ⇒ verbatim asm + own `ret`, no function); Pass 2 emits the body normally (`naked` ⇒ verbatim asm + own `ret`, no
prologue). `1800` pinned aarch64 → exit 42 + `.ir`; `1801-concurrency-pure-generic.sx` prologue). `1800` pinned aarch64 → exit 42 + `.ir`; `1801-concurrency-naked-generic.sx`
(renamed from `-bail`) proves the generic path emits a naked body (exit 42); (renamed from `-bail`) proves the generic path emits a naked body (exit 42);
`1802-concurrency-pure-asm-x86.sx` x86_64 cross sibling (ir-only here, `.ir` locks `naked` `1802-concurrency-naked-asm-x86.sx` x86_64 cross sibling (ir-only here, `.ir` locks `naked`
+ `movl $42, %eax`). Unit test `emit: abi(.pure) function gets the naked attribute` asserts + `movl $42, %eax`). Unit test `emit: abi(.naked) function gets the naked attribute` asserts
`naked` present + `frame-pointer` absent. Suite green (724/0). `naked` present + `frame-pointer` absent. Suite green (724/0).
- **B1.0c (review-hardening) — ✅ DONE.** A param-bearing `.pure` fn emitted invalid LLVM - **B1.0c (review-hardening) — ✅ DONE.** A param-bearing `.naked` fn emitted invalid LLVM
(loud verifier error). Gated the param-alloca loop on `fd.abi != .pure` (decl.zig both (loud verifier error). Gated the param-alloca loop on `fd.abi != .naked` (decl.zig both
paths + generic.zig) so a naked fn's args stay in registers (read by the asm body) — this paths + generic.zig) so a naked fn's args stay in registers (read by the asm body) — this
*enables* B1.3's `swap_context(from, to)`. Locked by `1803-concurrency-pure-asm-param.sx`. *enables* B1.3's `swap_context(from, to)`. Locked by `1803-concurrency-naked-asm-param.sx`.
Pack `.pure` (variadic + naked, nonsensical) left unsupported → loud verifier error. Pack `.naked` (variadic + naked, nonsensical) left unsupported → loud verifier error.
### B1.1 — per-fiber `context` root (probe-first; likely zero compiler change) ### B1.1 — per-fiber `context` root (probe-first; likely zero compiler change)
- **B1.1a (probe + lock)** — write a probe (`.sx-tmp/`) + an `18xx` example that snapshots a - **B1.1a (probe + lock)** — write a probe (`.sx-tmp/`) + an `18xx` example that snapshots a
@@ -207,7 +209,7 @@ asserting program-emitted ordering contracts.
--- ---
## Gates ## Gates
- **B1.0:** unit `emit_llvm.test.zig` (the `naked` attr present on a `.pure` fn); two - **B1.0:** unit `emit_llvm.test.zig` (the `naked` attr present on a `.naked` fn); two
arch-gated examples (aarch64 + x86_64) run end-to-end on a matching host, ir-only on a arch-gated examples (aarch64 + x86_64) run end-to-end on a matching host, ir-only on a
mismatch (assert `naked` + asm in `.ir`). **OUT of corpus scope, stated loudly:** the mismatch (assert `naked` + asm in `.ir`). **OUT of corpus scope, stated loudly:** the
*correctness* of any hand-written register save/restore — that's the B1.3 stress harness. *correctness* of any hand-written register save/restore — that's the B1.3 stress harness.
@@ -220,24 +222,24 @@ asserting program-emitted ordering contracts.
- **B1.5:** `18xx` ordering-contract snapshots under the deterministic `Io`. - **B1.5:** `18xx` ordering-contract snapshots under the deterministic `Io`.
## Kickoff prompt (B1.0b — paste into a fresh session) ## Kickoff prompt (B1.0b — paste into a fresh session)
> Implement Stream B1 step **B1.0b** (`abi(.pure)` real emission) per > Implement Stream B1 step **B1.0b** (`abi(.naked)` real emission) per
> `current/PLAN-FIBERS.md`. Verify `zig build && zig build test` is green first (B1.0a is > `current/PLAN-FIBERS.md`. Verify `zig build && zig build test` is green first (B1.0a is
> already landed: `Function.is_pure` plumbed, `decl.zig` skips ctx + bypasses implicit-return > already landed: `Function.is_naked` plumbed, `decl.zig` skips ctx + bypasses implicit-return
> for `.pure`, `emit_llvm` Pass 2 bails loudly, `examples/1800-concurrency-pure-asm.sx` > for `.naked`, `emit_llvm` Pass 2 bails loudly, `examples/1800-concurrency-naked-asm.sx`
> locked to the bail). Then: (1) in `src/ir/emit_llvm.zig` Pass 2 (~line 402), REPLACE the > locked to the bail). Then: (1) in `src/ir/emit_llvm.zig` Pass 2 (~line 402), REPLACE the
> `func.is_pure` bail with real emission — set LLVM's `naked` attribute on the function > `func.is_naked` bail with real emission — set LLVM's `naked` attribute on the function
> (`LLVMGetEnumAttributeKindForName("naked", 5)` → `LLVMCreateEnumAttribute(ctx, id, 0)` → > (`LLVMGetEnumAttributeKindForName("naked", 5)` → `LLVMCreateEnumAttribute(ctx, id, 0)` →
> `LLVMAddAttributeAtIndex(llvm_func, -1, attr)`; shape per the `nounwind` set at > `LLVMAddAttributeAtIndex(llvm_func, -1, attr)`; shape per the `nounwind` set at
> emit_llvm.zig:1339) and emit the `.pure` body as its asm block only, no prologue/epilogue > emit_llvm.zig:1339) and emit the `.naked` body as its asm block only, no prologue/epilogue
> (the body already lowers to the inline-asm op + an `unreachable` terminator). (2) Pin > (the body already lowers to the inline-asm op + an `unreachable` terminator). (2) Pin
> `examples/1800-concurrency-pure-asm.sx` aarch64 with a `.build` sidecar > `examples/1800-concurrency-naked-asm.sx` aarch64 with a `.build` sidecar
> `{"target":"aarch64-macos"}`; on this aarch64 host it runs end-to-end (exit 42), capture > `{"target":"aarch64-macos"}`; on this aarch64 host it runs end-to-end (exit 42), capture
> `.ir` + regen (`-Dname=examples/1800-concurrency-pure-asm.sx -Dupdate-goldens`), review the > `.ir` + regen (`-Dname=examples/1800-concurrency-naked-asm.sx -Dupdate-goldens`), review the
> diff (assert the `.ir` shows the `naked` attr + `mov x0, #42` / `ret`, NO stray error > diff (assert the `.ir` shows the `naked` attr + `mov x0, #42` / `ret`, NO stray error
> text). (3) Add `examples/1802-concurrency-pure-asm-x86.sx` (x86_64 body, `.build > text). (3) Add `examples/1802-concurrency-naked-asm-x86.sx` (x86_64 body, `.build
> {"target":"x86_64-linux"}`, ir-only on this host — requires its `.ir`, now producible). > {"target":"x86_64-linux"}`, ir-only on this host — requires its `.ir`, now producible).
> (4) Add a unit test in `src/ir/emit_llvm.test.zig` asserting the `naked` attribute is > (4) Add a unit test in `src/ir/emit_llvm.test.zig` asserting the `naked` attribute is
> present on an `abi(.pure)` function. Confirm `zig build test` green, commit. NOTE: the > present on an `abi(.naked)` function. Confirm `zig build test` green, commit. NOTE: the
> `.ir` proves the keyword + asm emitted, NOT register-save correctness (that's the B1.3 > `.ir` proves the keyword + asm emitted, NOT register-save correctness (that's the B1.3
> switch-stress harness). If you hit an UNRELATED compiler bug, file `issues/NNNN`, mark > switch-stress harness). If you hit an UNRELATED compiler bug, file `issues/NNNN`, mark
> `CHECKPOINT-FIBERS.md` BLOCKED, and STOP. > `CHECKPOINT-FIBERS.md` BLOCKED, and STOP.

View File

@@ -61,14 +61,14 @@ The colorblind, stackful, pure-sx async runtime (design §4). Compiler floor is
the runtime is sx lib. Likely carved as two PLANs: the runtime is sx lib. Likely carved as two PLANs:
### B1 — Fibers + Io + M:1 (the runtime; `PLAN-FIBERS.md`) · 🚧 **CARVED** (not started; first step B1.0a) ### B1 — Fibers + Io + M:1 (the runtime; `PLAN-FIBERS.md`) · 🚧 **CARVED** (not started; first step B1.0a)
- B1.0 **`abi(.naked)` — make the EXISTING `.pure` ABI actually naked.** The enum - B1.0 **`abi(.naked)` — make the EXISTING `.naked` ABI actually naked.** The enum
already carries `.pure` (ast.zig:142, documented "pure/naked, no prologue/epilogue"), already carries `.naked` (ast.zig:142, documented "naked, no prologue/epilogue"),
but it is an **inert label today**: `type_resolver.zig:237` maps `.pure → .default` but it is an **inert label today**: `type_resolver.zig:237` maps `.naked → .default`
CC and there is **zero naked-attribute emission in emit_llvm**. So B1.0 is NOT CC and there is **zero naked-attribute emission in emit_llvm**. So B1.0 is NOT
"extend the enum" (done) — it is "emit the LLVM `naked` attr + skip prologue/epilogue "extend the enum" (done) — it is "emit the LLVM `naked` attr + skip prologue/epilogue
lowering for `.pure`," genuinely net-new. (Roadmap §7-step-4's "extend lowering for `.naked`," genuinely net-new. (Roadmap §7-step-4's "extend
`CallConv {default, c}`" is stale — CallConv was renamed ABI and already gained `CallConv {default, c}`" is stale — CallConv was renamed ABI and already gained
`compiler`/`pure` in the compiler-API stream.) Gates the context-switch. `compiler`/`naked` in the compiler-API stream.) Gates the context-switch.
- B1.1 **Per-fiber `context` root + `push Context`-stack storage.** Grounding correction: - B1.1 **Per-fiber `context` root + `push Context`-stack storage.** Grounding correction:
`context` is **already an implicit `*Context` parameter** (comptime_vm.zig:392, `context` is **already an implicit `*Context` parameter** (comptime_vm.zig:392,
lower.zig:257 "Implicit Context parameter machinery"), **not raw TLS** — so it already lower.zig:257 "Implicit Context parameter machinery"), **not raw TLS** — so it already
@@ -192,7 +192,7 @@ module hazard; S2 TLS + C-constructor JIT test per host OS (the exact prior-spik
metatype stream), deterministic-`Io` oracle calibration, `context`-fiber-local/errno metatype stream), deterministic-`Io` oracle calibration, `context`-fiber-local/errno
(C — gated by the named stress harness), S2 (E), C1 args-buffer layout (D). (C — gated by the named stress harness), S2 (E), C1 args-buffer layout (D).
- **The compiler floor stays small, but deep — net-new pieces, grounded:** atomics - **The compiler floor stays small, but deep — net-new pieces, grounded:** atomics
(100% net-new, no scaffolding), making `abi(.pure)` actually naked (the enum variant (100% net-new, no scaffolding), making `abi(.naked)` actually naked (the enum variant
exists but is inert today), per-fiber `context` root + push-stack storage (`context` exists but is inert today), per-fiber `context` root + push-stack storage (`context`
is already an implicit param, NOT TLS — so this is smaller/different than "repointable is already an implicit param, NOT TLS — so this is smaller/different than "repointable
codegen" implied), `declare`/`define`/`type_info` (metatype stream — **done**), the codegen" implied), `declare`/`define`/`type_info` (metatype stream — **done**), the

View File

@@ -71,7 +71,7 @@ internal surface (Zig types + functions). Two defining properties:
> **before** `extern`/`export`. It is the unified replacement for the old > **before** `extern`/`export`. It is the unified replacement for the old
> `callconv(...)` (which is removed): `ABI = { default, c, zig, pure }` — > `callconv(...)` (which is removed): `ABI = { default, c, zig, pure }` —
> `.c` (C ABI / cdecl), `.zig` (Zig-layout weld → the `compiler` library), > `.c` (C ABI / cdecl), `.zig` (Zig-layout weld → the `compiler` library),
> `.pure` (naked asm). `.default` = unannotated (ordinary sx convention). > `.naked` (naked asm). `.default` = unannotated (ordinary sx convention).
> - `extern <lib>` — the linkage keyword + binding source (the named library). > - `extern <lib>` — the linkage keyword + binding source (the named library).
`abi(...)` sits where `callconv(...)` went (after the return type for fns); the `abi(...)` sits where `callconv(...)` went (after the return type for fns); the

View File

@@ -396,10 +396,10 @@ grounding) are explicit steps, not buried.
capability:** name minted results by the instantiation's mangled name + input capability:** name minted results by the instantiation's mangled name + input
validation. validation.
4. **`abi(.naked)`** — *correction:* `CallConv` was renamed `ABI` and **already carries 4. **`abi(.naked)`** — *correction:* `CallConv` was renamed `ABI` and **already carries
`.pure`** (ast.zig:142, "pure/naked, no prologue/epilogue") during the compiler-API `.naked`** (ast.zig:142, "naked, no prologue/epilogue") during the compiler-API
stream — so this is NOT "extend the enum." `.pure` is an **inert label today**: stream — so this is NOT "extend the enum." `.naked` is an **inert label today**:
`type_resolver.zig:237` maps it to `.default` CC and emit_llvm emits **no** naked `type_resolver.zig:237` maps it to `.default` CC and emit_llvm emits **no** naked
attribute. The net-new work is making `.pure` actually emit LLVM `naked` + skip attribute. The net-new work is making `.naked` actually emit LLVM `naked` + skip
prologue/epilogue lowering. Gates A2. prologue/epilogue lowering. Gates A2.
5. **Per-fiber `context` root + push-stack storage***correction:* `context` is 5. **Per-fiber `context` root + push-stack storage***correction:* `context` is
**already an implicit `*Context` parameter** (comptime_vm.zig:392, lower.zig:257 **already an implicit `*Context` parameter** (comptime_vm.zig:392, lower.zig:257

View File

@@ -137,9 +137,10 @@ pub const Root = struct {
/// bodiless decls whose Zig/VM handler is the impl) AND user compiler-domain /// bodiless decls whose Zig/VM handler is the impl) AND user compiler-domain
/// functions like post-link callbacks (bodied, but emit-skipped). The ABI alone /// functions like post-link callbacks (bodied, but emit-skipped). The ABI alone
/// marks it — there is no `extern <lib>` and no fake `#library "compiler"`. /// marks it — there is no `extern <lib>` and no fake `#library "compiler"`.
/// - `.pure` — a pure / naked function (inline asm body), no calling-convention /// - `.naked` — a naked function (inline asm body), no calling-convention
/// prologue/epilogue. /// prologue/epilogue. The body is responsible for its own `ret`; args arrive
pub const ABI = enum { default, c, compiler, pure }; /// in ABI registers (no frame, no implicit `__sx_ctx`).
pub const ABI = enum { default, c, compiler, naked };
/// Linkage modifier written in the postfix slot before `abi(...)`: /// Linkage modifier written in the postfix slot before `abi(...)`:
/// `name :: (sig) -> Ret [extern | export] [abi(.x)] [lib] [;|{…}];` /// `name :: (sig) -> Ret [extern | export] [abi(.x)] [lib] [;|{…}];`
@@ -156,7 +157,7 @@ pub const FnDecl = struct {
body: *Node, body: *Node,
type_params: []const StructTypeParam = &.{}, type_params: []const StructTypeParam = &.{},
is_arrow: bool = false, is_arrow: bool = false,
/// ABI / calling-convention annotation (`abi(.c)` / `abi(.zig)` / `abi(.pure)`) /// ABI / calling-convention annotation (`abi(.c)` / `abi(.zig)` / `abi(.naked)`)
/// in the postfix slot after `extern`/`export`. `.default` = unannotated. /// in the postfix slot after `extern`/`export`. `.default` = unannotated.
/// `.zig` marks a function bound to the comptime `compiler` library — its /// `.zig` marks a function bound to the comptime `compiler` library — its
/// signature is welded to the real internal Zig fn and it dispatches over the /// signature is welded to the real internal Zig fn and it dispatches over the

View File

@@ -1325,18 +1325,18 @@ test "emit: reflectArgRepr surfaces .unresolved for an unresolvable reflection a
try std.testing.expect(emitter.reflectArgRepr(bogus) != .bare); try std.testing.expect(emitter.reflectArgRepr(bogus) != .bare);
} }
test "emit: abi(.pure) function gets the naked attribute (no frame-pointer)" { test "emit: abi(.naked) function gets the naked attribute (no frame-pointer)" {
const alloc = std.testing.allocator; const alloc = std.testing.allocator;
var module = Module.init(alloc); var module = Module.init(alloc);
defer module.deinit(); defer module.deinit();
var b = Builder.init(&module); var b = Builder.init(&module);
// func answer() -> i64 abi(.pure) { asm volatile { "ret" }; unreachable } // func answer() -> i64 abi(.naked) { asm volatile { "ret" }; unreachable }
// The naked attribute is keyed off Function.is_pure in the declaration pass, // The naked attribute is keyed off Function.is_naked in the declaration pass,
// independent of the body — a minimal asm + unreachable body suffices. // independent of the body — a minimal asm + unreachable body suffices.
_ = b.beginFunction(str(&module, "answer"), &.{}, .i64); _ = b.beginFunction(str(&module, "answer"), &.{}, .i64);
b.currentFunc().is_pure = true; b.currentFunc().is_naked = true;
const entry = b.appendBlock(str(&module, "entry"), &.{}); const entry = b.appendBlock(str(&module, "entry"), &.{});
b.switchToBlock(entry); b.switchToBlock(entry);

View File

@@ -408,9 +408,9 @@ pub const LLVMEmitter = struct {
// its only references are in comptime code, so DCE drops the leftover // its only references are in comptime code, so DCE drops the leftover
// declaration. See current/PLAN-COMPILER-VM.md (S3). // declaration. See current/PLAN-COMPILER-VM.md (S3).
if (func.is_compiler_domain) continue; if (func.is_compiler_domain) continue;
// `abi(.pure)` functions emit normally — the `naked` attribute (set // `abi(.naked)` functions emit normally — the `naked` attribute (set
// in the declaration pass) makes the backend emit the body (inline // in the declaration pass) makes the backend emit the body (inline
// asm + its own `ret`) with no prologue/epilogue. See Function.is_pure. // asm + its own `ret`) with no prologue/epilogue. See Function.is_naked.
self.emitFunction(&func, @intCast(i)); self.emitFunction(&func, @intCast(i));
} }
@@ -1327,13 +1327,13 @@ pub const LLVMEmitter = struct {
// Add frame-pointer and nounwind attributes for correct ARM64 codegen // Add frame-pointer and nounwind attributes for correct ARM64 codegen
{ {
const func_idx_attr: c.LLVMAttributeIndex = @bitCast(@as(i32, -1)); const func_idx_attr: c.LLVMAttributeIndex = @bitCast(@as(i32, -1));
if (func.is_pure) { if (func.is_naked) {
// `abi(.pure)`: emit via LLVM's `naked` attribute — the backend // `abi(.naked)`: emit via LLVM's `naked` attribute — the backend
// emits the body verbatim (our inline asm + its own `ret`) with // emits the body verbatim (our inline asm + its own `ret`) with
// NO prologue/epilogue/frame. Do NOT request `frame-pointer` // NO prologue/epilogue/frame. Do NOT request `frame-pointer`
// (incompatible with a frameless function). `noinline` keeps the // (incompatible with a frameless function). `noinline` keeps the
// asm body out of a framed caller; `nounwind` — naked asm never // asm body out of a framed caller; `nounwind` — naked asm never
// unwinds. See Function.is_pure / current/PLAN-FIBERS.md. // unwinds. See Function.is_naked / current/PLAN-FIBERS.md.
const naked_id = c.LLVMGetEnumAttributeKindForName("naked", 5); const naked_id = c.LLVMGetEnumAttributeKindForName("naked", 5);
c.LLVMAddAttributeAtIndex(llvm_func, func_idx_attr, c.LLVMCreateEnumAttribute(self.context, naked_id, 0)); c.LLVMAddAttributeAtIndex(llvm_func, func_idx_attr, c.LLVMCreateEnumAttribute(self.context, naked_id, 0));
const noinline_id = c.LLVMGetEnumAttributeKindForName("noinline", 8); const noinline_id = c.LLVMGetEnumAttributeKindForName("noinline", 8);

View File

@@ -640,15 +640,15 @@ pub const Function = struct {
/// drops the leftover declaration. See current/PLAN-COMPILER-VM.md (S3). /// drops the leftover declaration. See current/PLAN-COMPILER-VM.md (S3).
is_compiler_domain: bool = false, is_compiler_domain: bool = false,
/// True for an `abi(.pure)` function — no calling-convention /// True for an `abi(.naked)` function — no calling-convention
/// prologue/epilogue/frame, no implicit `__sx_ctx`. Its body is a single /// prologue/epilogue/frame, no implicit `__sx_ctx`. Its body is a single
/// inline-asm block that reads args from ABI registers and emits its own /// inline-asm block that reads args from ABI registers and emits its own
/// `ret` (the context-switch primitive; design §4.6). emit_llvm lowers this /// `ret` (the context-switch primitive; design §4.6). emit_llvm lowers this
/// via LLVM's `naked` function attribute and generates no frame setup. A /// via LLVM's `naked` function attribute and generates no frame setup. A
/// `.c` epilogue would restore SP from the wrong stack across a context /// `.c` epilogue would restore SP from the wrong stack across a context
/// switch (SP-in ≠ SP-out by design), which is why `.pure` is distinct /// switch (SP-in ≠ SP-out by design), which is why `.naked` is distinct
/// from `.c`. /// from `.c`.
is_pure: bool = false, is_naked: bool = false,
pub const Param = struct { pub const Param = struct {
name: StringId, name: StringId,

View File

@@ -513,11 +513,11 @@ pub fn detectContextDecl(decls: []const *const Node) bool {
pub fn funcWantsImplicitCtx(self: *const Lowering, fd: *const ast.FnDecl) bool { pub fn funcWantsImplicitCtx(self: *const Lowering, fd: *const ast.FnDecl) bool {
if (!self.implicit_ctx_enabled) return false; if (!self.implicit_ctx_enabled) return false;
if (fd.abi == .c) return false; if (fd.abi == .c) return false;
// An `abi(.pure)` function has no frame and no synthetic params — its body // An `abi(.naked)` function has no frame and no synthetic params — its body
// is a single asm block reading args from ABI registers. No implicit // is a single asm block reading args from ABI registers. No implicit
// `__sx_ctx` (it would occupy a register slot the asm doesn't expect). // `__sx_ctx` (it would occupy a register slot the asm doesn't expect).
// See Function.is_pure. // See Function.is_naked.
if (fd.abi == .pure) return false; if (fd.abi == .naked) return false;
// A BODILESS `abi(.compiler)` decl (compiler-API surface) is dispatched by name // A BODILESS `abi(.compiler)` decl (compiler-API surface) is dispatched by name
// to a Zig/VM handler with exactly the declared args; an implicit `__sx_ctx` // to a Zig/VM handler with exactly the declared args; an implicit `__sx_ctx`
// prepend would shift every arg (breaking the handler's arity check). No sx // prepend would shift every arg (breaking the handler's arity check). No sx
@@ -2315,7 +2315,7 @@ pub fn declareFunction(self: *Lowering, fd: *const ast.FnDecl, name: []const u8)
func.source_file = self.current_source_file; func.source_file = self.current_source_file;
func.is_variadic = is_variadic; func.is_variadic = is_variadic;
func.has_implicit_ctx = wants_ctx; func.has_implicit_ctx = wants_ctx;
func.is_pure = (fd.abi == .pure); func.is_naked = (fd.abi == .naked);
self.extern_name_map.put(name, c_name) catch {}; self.extern_name_map.put(name, c_name) catch {};
self.fn_decl_fids.put(fd, fid) catch {}; self.fn_decl_fids.put(fd, fid) catch {};
return; return;
@@ -2329,7 +2329,7 @@ pub fn declareFunction(self: *Lowering, fd: *const ast.FnDecl, name: []const u8)
func.source_file = self.current_source_file; func.source_file = self.current_source_file;
func.is_variadic = is_variadic; func.is_variadic = is_variadic;
func.has_implicit_ctx = wants_ctx; func.has_implicit_ctx = wants_ctx;
func.is_pure = (fd.abi == .pure); func.is_naked = (fd.abi == .naked);
if (weldedCompilerFn(self, fd, name)) func.compiler_welded = true; if (weldedCompilerFn(self, fd, name)) func.compiler_welded = true;
// A BODIED `abi(.compiler)` function is a user compiler-domain function (e.g. a // A BODIED `abi(.compiler)` function is a user compiler-domain function (e.g. a
// post-link callback): the VM runs its sx body, but it NEVER runs in the binary // post-link callback): the VM runs its sx body, but it NEVER runs in the binary
@@ -2660,13 +2660,13 @@ pub fn lowerFunctionBodyInto(self: *Lowering, fd: *const ast.FnDecl, fid: FuncId
const user_param_base: u32 = if (wants_ctx) 1 else 0; const user_param_base: u32 = if (wants_ctx) 1 else 0;
if (wants_ctx) self.current_ctx_ref = Ref.fromIndex(0); if (wants_ctx) self.current_ctx_ref = Ref.fromIndex(0);
// An `abi(.pure)` (naked) function has no frame: its params arrive in ABI // An `abi(.naked)` (naked) function has no frame: its params arrive in ABI
// registers and are read directly by the asm body (e.g. `swap_context`'s // registers and are read directly by the asm body (e.g. `swap_context`'s
// `from`/`to`). Spilling them to allocas would (a) need a frame and (b) emit // `from`/`to`). Spilling them to allocas would (a) need a frame and (b) emit
// `store i64 %0, …` — "cannot use argument of naked function" (LLVM verifier). // `store i64 %0, …` — "cannot use argument of naked function" (LLVM verifier).
// Leave the LLVM args declared-but-unused (the verifier allows that); the asm // Leave the LLVM args declared-but-unused (the verifier allows that); the asm
// references the registers. // references the registers.
if (fd.abi != .pure) for (fd.params, 0..) |p, i| { if (fd.abi != .naked) for (fd.params, 0..) |p, i| {
const pty = self.resolveParamType(&p); const pty = self.resolveParamType(&p);
const slot = self.builder.alloca(pty); const slot = self.builder.alloca(pty);
const param_ref = Ref.fromIndex(@intCast(i + user_param_base)); const param_ref = Ref.fromIndex(@intCast(i + user_param_base));
@@ -2685,8 +2685,8 @@ pub fn lowerFunctionBodyInto(self: *Lowering, fd: *const ast.FnDecl, fid: FuncId
// Lower the function body (set target_type to return type for implicit returns) // Lower the function body (set target_type to return type for implicit returns)
const saved_target = self.target_type; const saved_target = self.target_type;
self.target_type = if (ret_ty != .void and ret_ty != .noreturn) ret_ty else null; self.target_type = if (ret_ty != .void and ret_ty != .noreturn) ret_ty else null;
if (self.builder.currentFunc().is_pure) { if (self.builder.currentFunc().is_naked) {
// `abi(.pure)`: the body is a single asm block that emits its own `ret`. // `abi(.naked)`: the body is a single asm block that emits its own `ret`.
// There is no sx-level value return — lower the statements and cap the // There is no sx-level value return — lower the statements and cap the
// block with `unreachable` (control never falls back into sx). This // block with `unreachable` (control never falls back into sx). This
// bypasses the implicit-return machinery, which would otherwise reject // bypasses the implicit-return machinery, which would otherwise reject
@@ -2818,10 +2818,10 @@ pub fn lowerFunction(self: *Lowering, fd: *const ast.FnDecl, name: []const u8, i
const user_param_base_lf: u32 = if (wants_ctx_lf) 1 else 0; const user_param_base_lf: u32 = if (wants_ctx_lf) 1 else 0;
if (wants_ctx_lf) self.current_ctx_ref = Ref.fromIndex(0); if (wants_ctx_lf) self.current_ctx_ref = Ref.fromIndex(0);
// `abi(.pure)` (naked): params arrive in registers, read directly by the asm // `abi(.naked)` (naked): params arrive in registers, read directly by the asm
// body — no frame, no alloca/store (which the LLVM verifier rejects on a // body — no frame, no alloca/store (which the LLVM verifier rejects on a
// naked function). See the sibling guard in the other body-lowering path. // naked function). See the sibling guard in the other body-lowering path.
if (fd.abi != .pure) for (fd.params, 0..) |p, i| { if (fd.abi != .naked) for (fd.params, 0..) |p, i| {
const pty = self.resolveParamType(&p); const pty = self.resolveParamType(&p);
// Allocate stack slot for param, store initial value. // Allocate stack slot for param, store initial value.
// Refs 0..N-1 are reserved for function parameters by beginFunction. // Refs 0..N-1 are reserved for function parameters by beginFunction.
@@ -2843,8 +2843,8 @@ pub fn lowerFunction(self: *Lowering, fd: *const ast.FnDecl, name: []const u8, i
// Lower the function body, capturing the last expression's value for implicit return // Lower the function body, capturing the last expression's value for implicit return
const saved_target = self.target_type; const saved_target = self.target_type;
self.target_type = if (ret_ty != .void and ret_ty != .noreturn) ret_ty else null; self.target_type = if (ret_ty != .void and ret_ty != .noreturn) ret_ty else null;
if (self.builder.currentFunc().is_pure) { if (self.builder.currentFunc().is_naked) {
// `abi(.pure)`: asm-only body that rets itself — see the sibling path // `abi(.naked)`: asm-only body that rets itself — see the sibling path
// above. Lower statements, cap with `unreachable`; emission is B1.0b. // above. Lower statements, cap with `unreachable`; emission is B1.0b.
self.lowerBlock(fd.body); self.lowerBlock(fd.body);
if (!self.currentBlockHasTerminator()) self.builder.emitUnreachable(); if (!self.currentBlockHasTerminator()) self.builder.emitUnreachable();

View File

@@ -115,7 +115,7 @@ pub fn monomorphizeFunction(self: *Lowering, fd: *const ast.FnDecl, mangled_name
const func_id = self.builder.beginFunction(name_id, params.items, ret_ty); const func_id = self.builder.beginFunction(name_id, params.items, ret_ty);
_ = func_id; _ = func_id;
self.builder.currentFunc().has_implicit_ctx = wants_ctx; self.builder.currentFunc().has_implicit_ctx = wants_ctx;
self.builder.currentFunc().is_pure = (fd.abi == .pure); self.builder.currentFunc().is_naked = (fd.abi == .naked);
// Create entry block // Create entry block
const entry_name = self.module.types.internString("entry"); const entry_name = self.module.types.internString("entry");
@@ -128,10 +128,10 @@ pub fn monomorphizeFunction(self: *Lowering, fd: *const ast.FnDecl, mangled_name
defer scope.deinit(); defer scope.deinit();
self.scope = &scope; self.scope = &scope;
// `abi(.pure)` (naked): no frame — params arrive in registers, read by the // `abi(.naked)` (naked): no frame — params arrive in registers, read by the
// asm body, never spilled to allocas (the LLVM verifier rejects a naked // asm body, never spilled to allocas (the LLVM verifier rejects a naked
// function that uses its arguments). Mirrors the decl-path guard. // function that uses its arguments). Mirrors the decl-path guard.
if (fd.abi != .pure) { if (fd.abi != .naked) {
var param_idx: u32 = if (wants_ctx) 1 else 0; var param_idx: u32 = if (wants_ctx) 1 else 0;
for (fd.params) |p| { for (fd.params) |p| {
if (isTypeParamDecl(&p, fd.type_params)) continue; if (isTypeParamDecl(&p, fd.type_params)) continue;
@@ -155,10 +155,10 @@ pub fn monomorphizeFunction(self: *Lowering, fd: *const ast.FnDecl, mangled_name
self.ensureTerminator(ret_ty); self.ensureTerminator(ret_ty);
} }
self.builder.finalize(); self.builder.finalize();
} else if (self.builder.currentFunc().is_pure) { } else if (self.builder.currentFunc().is_naked) {
// `abi(.pure)`: asm-only body that rets itself — no sx value return. // `abi(.naked)`: asm-only body that rets itself — no sx value return.
// Lower the statements + cap with `unreachable` (mirrors the decl path). // Lower the statements + cap with `unreachable` (mirrors the decl path).
// emit_llvm bails on `is_pure` until B1.0b implements `naked` emission. // emit_llvm bails on `is_naked` until B1.0b implements `naked` emission.
self.lowerBlock(fd.body); self.lowerBlock(fd.body);
if (!self.currentBlockHasTerminator()) self.builder.emitUnreachable(); if (!self.currentBlockHasTerminator()) self.builder.emitUnreachable();
self.builder.finalize(); self.builder.finalize();

View File

@@ -949,7 +949,7 @@ pub fn monomorphizePackFn(
const name_id = self.module.types.internString(owned_name); const name_id = self.module.types.internString(owned_name);
_ = self.builder.beginFunction(name_id, params.items, ret_ty); _ = self.builder.beginFunction(name_id, params.items, ret_ty);
self.builder.currentFunc().has_implicit_ctx = wants_ctx; self.builder.currentFunc().has_implicit_ctx = wants_ctx;
self.builder.currentFunc().is_pure = (fd.abi == .pure); self.builder.currentFunc().is_naked = (fd.abi == .naked);
const entry_name = self.module.types.internString("entry"); const entry_name = self.module.types.internString("entry");
const entry = self.builder.appendBlock(entry_name, &.{}); const entry = self.builder.appendBlock(entry_name, &.{});
@@ -1039,10 +1039,10 @@ pub fn monomorphizePackFn(
defer self.setCurrentSourceFile(saved_source); defer self.setCurrentSourceFile(saved_source);
if (fd.body.source_file) |src| self.setCurrentSourceFile(src); if (fd.body.source_file) |src| self.setCurrentSourceFile(src);
if (self.builder.currentFunc().is_pure) { if (self.builder.currentFunc().is_naked) {
// `abi(.pure)`: asm-only body that rets itself — no sx value return. // `abi(.naked)`: asm-only body that rets itself — no sx value return.
// Lower statements + cap with `unreachable` (mirrors the decl path). // Lower statements + cap with `unreachable` (mirrors the decl path).
// emit_llvm bails on `is_pure` until B1.0b implements `naked` emission. // emit_llvm bails on `is_naked` until B1.0b implements `naked` emission.
self.lowerBlock(fd.body); self.lowerBlock(fd.body);
if (!self.currentBlockHasTerminator()) self.builder.emitUnreachable(); if (!self.currentBlockHasTerminator()) self.builder.emitUnreachable();
} else if (ret_ty != .void) { } else if (ret_ty != .void) {

View File

@@ -228,13 +228,13 @@ pub const TypeResolver = struct {
const cc: types.TypeInfo.CallConv = switch (ft.abi) { const cc: types.TypeInfo.CallConv = switch (ft.abi) {
.default => .default, .default => .default,
.c => .c, .c => .c,
// `.compiler` (compiler-domain fn) and `.pure` (naked asm) are // `.compiler` (compiler-domain fn) and `.naked` (naked asm) are
// decl-level ABIs with no function-pointer-type calling // decl-level ABIs with no function-pointer-type calling
// convention of their own; the IR function-type CC models only // convention of their own; the IR function-type CC models only
// sx-default vs C. An `abi(.compiler)` function-TYPE param marks // sx-default vs C. An `abi(.compiler)` function-TYPE param marks
// the bound function compiler-domain (handled at the call/bind // the bound function compiler-domain (handled at the call/bind
// site, not here) — its CC is still sx-default. // site, not here) — its CC is still sx-default.
.compiler, .pure => .default, .compiler, .naked => .default,
}; };
break :blk table.functionTypeCC(param_ids.items, ret_ty, cc); break :blk table.functionTypeCC(param_ids.items, ret_ty, cc);
}, },

View File

@@ -144,15 +144,15 @@ test "parser: bare extern leaves abi == .default" {
// Lock: `abi(.c)` parses standalone (no extern/export) in the postfix slot — the // Lock: `abi(.c)` parses standalone (no extern/export) in the postfix slot — the
// migrated spelling of the old `callconv(.c)` on an ordinary function pointer / // migrated spelling of the old `callconv(.c)` on an ordinary function pointer /
// fn decl. And `abi(.pure)` parses (naked-asm ABI). // fn decl. And `abi(.naked)` parses (naked-asm ABI).
test "parser: abi(.c) and abi(.pure) parse standalone" { test "parser: abi(.c) and abi(.naked) parse standalone" {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator); var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit(); defer arena.deinit();
const alloc = arena.allocator(); const alloc = arena.allocator();
const src = const src =
\\cb :: () -> i64 abi(.c) { 0; } \\cb :: () -> i64 abi(.c) { 0; }
\\nk :: () -> i64 abi(.pure) { 0; } \\nk :: () -> i64 abi(.naked) { 0; }
\\ \\
; ;
var parser = Parser.init(alloc, src); var parser = Parser.init(alloc, src);
@@ -163,7 +163,7 @@ test "parser: abi(.c) and abi(.pure) parse standalone" {
try std.testing.expectEqual(ast.ABI.c, decls[0].data.fn_decl.abi); try std.testing.expectEqual(ast.ABI.c, decls[0].data.fn_decl.abi);
try std.testing.expectEqual(ast.ExternExportModifier.none, decls[0].data.fn_decl.extern_export); try std.testing.expectEqual(ast.ExternExportModifier.none, decls[0].data.fn_decl.extern_export);
try std.testing.expect(decls[1].data == .fn_decl); try std.testing.expect(decls[1].data == .fn_decl);
try std.testing.expectEqual(ast.ABI.pure, decls[1].data.fn_decl.abi); try std.testing.expectEqual(ast.ABI.naked, decls[1].data.fn_decl.abi);
} }
// Lock: the postfix `abi(...)` slot PARSES on a STRUCT decl — `Name :: struct // Lock: the postfix `abi(...)` slot PARSES on a STRUCT decl — `Name :: struct

View File

@@ -1931,7 +1931,7 @@ pub const Parser = struct {
} }
// Optional ABI / calling-convention annotation: `abi(.c)` / `abi(.zig)` / // Optional ABI / calling-convention annotation: `abi(.c)` / `abi(.zig)` /
// `abi(.pure)`. Sits in the postfix slot BEFORE the `extern`/`export` // `abi(.naked)`. Sits in the postfix slot BEFORE the `extern`/`export`
// linkage keyword (it is part of the function declaration). `abi(.zig)` // linkage keyword (it is part of the function declaration). `abi(.zig)`
// marks a binding to the comptime `compiler` library. // marks a binding to the comptime `compiler` library.
const abi = try self.parseOptionalAbi(); const abi = try self.parseOptionalAbi();
@@ -3684,7 +3684,7 @@ pub const Parser = struct {
return_type = try self.parseTypeExpr(); return_type = try self.parseTypeExpr();
} }
// Optional ABI annotation: abi(.c) / abi(.zig) / abi(.pure) // Optional ABI annotation: abi(.c) / abi(.zig) / abi(.naked)
const abi = try self.parseOptionalAbi(); const abi = try self.parseOptionalAbi();
// A closure is its own function boundary: clear the cleanup-body flags // A closure is its own function boundary: clear the cleanup-body flags
@@ -3793,7 +3793,7 @@ pub const Parser = struct {
} }
/// Optional ABI / calling-convention annotation `abi(.c)` / `abi(.zig)` / /// Optional ABI / calling-convention annotation `abi(.c)` / `abi(.zig)` /
/// `abi(.pure)` in the postfix slot before `extern`/`export`. `.default` when /// `abi(.naked)` in the postfix slot before `extern`/`export`. `.default` when
/// absent. Subsumes the old `callconv(...)` spelling. /// absent. Subsumes the old `callconv(...)` spelling.
fn parseOptionalAbi(self: *Parser) anyerror!ast.ABI { fn parseOptionalAbi(self: *Parser) anyerror!ast.ABI {
if (self.current.tag != .kw_abi) return .default; if (self.current.tag != .kw_abi) return .default;
@@ -3801,16 +3801,16 @@ pub const Parser = struct {
try self.expect(.l_paren); try self.expect(.l_paren);
try self.expect(.dot); try self.expect(.dot);
if (self.current.tag != .identifier) if (self.current.tag != .identifier)
return self.fail("expected ABI name ('.c', '.compiler', or '.pure') after '.'"); return self.fail("expected ABI name ('.c', '.compiler', or '.naked') after '.'");
const abi_name = self.tokenSlice(self.current); const abi_name = self.tokenSlice(self.current);
const abi: ast.ABI = if (std.mem.eql(u8, abi_name, "c")) const abi: ast.ABI = if (std.mem.eql(u8, abi_name, "c"))
.c .c
else if (std.mem.eql(u8, abi_name, "compiler")) else if (std.mem.eql(u8, abi_name, "compiler"))
.compiler .compiler
else if (std.mem.eql(u8, abi_name, "pure")) else if (std.mem.eql(u8, abi_name, "naked"))
.pure .naked
else else
return self.fail("unknown ABI (expected '.c', '.compiler', or '.pure')"); return self.fail("unknown ABI (expected '.c', '.compiler', or '.naked')");
self.advance(); self.advance();
try self.expect(.r_paren); try self.expect(.r_paren);
return abi; return abi;

View File

@@ -42,7 +42,7 @@ pub const Tag = enum {
kw_impl, // impl kw_impl, // impl
kw_Self, // Self (in protocol declarations) kw_Self, // Self (in protocol declarations)
kw_inline, // inline (compile-time if/for/while) kw_inline, // inline (compile-time if/for/while)
kw_abi, // abi (ABI / calling-convention annotation: abi(.c)/abi(.zig)/abi(.pure)) kw_abi, // abi (ABI / calling-convention annotation: abi(.c)/abi(.zig)/abi(.naked))
kw_extern, // extern (import: external linkage, C ABI, no body) kw_extern, // extern (import: external linkage, C ABI, no body)
kw_export, // export (define + expose: external linkage, C ABI) kw_export, // export (define + expose: external linkage, C ABI)
kw_asm, // asm (inline assembly expression / global asm decl) kw_asm, // asm (inline assembly expression / global asm decl)