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:
@@ -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
|
||||||
|
|||||||
@@ -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).**
|
||||||
|
|||||||
@@ -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.1–B1.5 are library + tests)
|
### Files the compiler floor touches (B1.0 only; B1.1–B1.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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user