fibers B1.0c: support params in abi(.pure) (read from registers)
Adversarial review of B1.0b found a param-bearing abi(.pure) function
emitted invalid LLVM ("cannot use argument of naked function" — loud
verifier error, not silent) because the param-alloca loop spilled the
args to stack slots, which a naked function cannot have.
Fixed forward — this ENABLES the B1.3 context-switch use case rather
than rejecting it: gate the param-alloca loop on fd.abi != .pure in
decl.zig (both body-lowering paths) and generic.zig. A naked function's
args stay in their ABI registers and are read directly by the asm body
(e.g. swap_context reads from/to from x0/x1); the LLVM args are
declared-but-unused, which the verifier allows.
examples/1803-concurrency-pure-asm-param.sx: naked add(a, b) reads x0/x1
(add x0, x0, x1; ret) -> 40 + 2 = 42. aarch64-pinned.
Pack abi(.pure) (variadic + naked — nonsensical, can't read a runtime
pack from registers) left unsupported: pack.zig's param loop is
intertwined with comptime-param/#insert handling, so that case still
hits the loud verifier error. Documented in the checkpoint.
Also updates PLAN-FIBERS / CHECKPOINT-FIBERS for B1.0 completion.
B1.0 complete. Suite green (725/0).
This commit is contained in:
@@ -4,55 +4,67 @@ 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.0a (`abi(.pure)` lock commit) — DONE.** Plumbed the `is_pure` flag end-to-end and made
|
**B1.0b (`abi(.pure)` real emission) — DONE. B1.0 complete.** Replaced the emit bail with
|
||||||
emit bail loudly:
|
real LLVM `naked` emission:
|
||||||
- IR `Function.is_pure: bool` ([inst.zig](../src/ir/inst.zig)) — set from `fd.abi == .pure`
|
- `emit_llvm` declaration pass: for `func.is_pure`, add the LLVM `naked` + `noinline` +
|
||||||
at both `declareFunction` decl sites ([decl.zig](../src/ir/lower/decl.zig)).
|
`nounwind` attributes and **skip** the `frame-pointer=all` attribute (incompatible with a
|
||||||
- `funcWantsImplicitCtx` returns false for `.pure` (mirrors the `.c` skip, decl.zig:515) —
|
frameless function). Pass 2 now emits the `.pure` body normally — `naked` makes the
|
||||||
a `.pure` fn gets no synthetic `__sx_ctx`.
|
backend emit it verbatim (the inline asm + its own `ret`) with no prologue/epilogue.
|
||||||
- Both body-lowering paths bypass `lowerValueBody` for `.pure`: lower the asm body as
|
- IR shape (verified): `; Function Attrs: naked noinline nounwind` / `define internal i64
|
||||||
statements + cap with `unreachable` (a `.pure` body has no sx return — the asm rets
|
@answer() #0 { entry: call void asm sideeffect "…ret…", ""() unreachable }` /
|
||||||
itself; this avoids the implicit-return diagnostic).
|
`attributes #0 = { naked noinline nounwind }`. The caller invokes it as an ordinary
|
||||||
- `emit_llvm` Pass 2 (~line 402) **bails loudly** when `func.is_pure`
|
`() -> i64` call (`.pure` is `call_conv == .default`).
|
||||||
("`abi(.pure)` function '…' LLVM emission not yet implemented") via `comptime_failed`
|
- `examples/1800-concurrency-pure-asm.sx` — now GREEN, aarch64-pinned (`.build {"target":
|
||||||
(driver aborts nonzero) — NOT a framed body (whose epilogue would corrupt a context
|
"macos"}`): runs end-to-end → **exit 42** on this host, ir-only on a mismatch; `.ir`
|
||||||
switch's SP-in ≠ SP-out).
|
snapshot captured.
|
||||||
- `examples/1800-concurrency-pure-asm.sx` — one host example (no `.build` pin; the bail is
|
- `examples/1801-concurrency-pure-generic.sx` (renamed from `-bail`) — the generic `.pure`
|
||||||
host-independent, fires before any asm/instruction selection), locked to the bail snapshot
|
now emits a correct naked `answer__i64` (exit 42), proving generic.zig produces a naked
|
||||||
(exit 1, empty stdout, the loud diagnostic on stderr).
|
body, not a framed one. aarch64-pinned.
|
||||||
- **Adversarial review (closed in-step):** the review caught that `is_pure` was set ONLY at
|
- `examples/1802-concurrency-pure-asm-x86.sx` — x86_64 cross sibling (`.build {"target":
|
||||||
the two `declareFunction` decl sites — generic monomorphization
|
"x86_64-linux"}`, ir-only here): `.ir` locks `naked` + `movl $42, %eax` / `ret`.
|
||||||
([generic.zig](../src/ir/lower/generic.zig)) and pack expansion
|
- Unit test `emit: abi(.pure) function gets the naked attribute (no frame-pointer)` in
|
||||||
([pack.zig](../src/ir/lower/pack.zig)) create the `Function` via a different path and left
|
`emit_llvm.test.zig` (asserts `naked` present, `frame-pointer` absent).
|
||||||
`is_pure` false, so a generic `.pure` instance silently shipped a framed body (returned 42
|
- **B1.0c (review-hardening):** a param-bearing `.pure` fn emitted invalid LLVM (loud
|
||||||
but leaked the prologue's stack adjustment — the exact corruption the lock prevents). Both
|
verifier error "cannot use argument of naked function") because the param-alloca loop
|
||||||
paths now set `is_pure` + route `.pure` bodies through the asm-only + `unreachable` cap.
|
wasn't gated. Fixed forward (this *enables* the B1.3 context-switch use case rather than
|
||||||
Locked by `examples/1801-concurrency-pure-generic-bail.sx`. (The review's other CRITICAL —
|
rejecting it): gated the param-alloca loop on `fd.abi != .pure` in decl.zig (both paths) +
|
||||||
a `.pure` *lambda* — is a **false positive**: `isLambda`'s return-type scan
|
generic.zig; a naked fn's args stay in registers (read by asm), declared-but-unused in
|
||||||
(parser.zig:3652) breaks on the `abi` keyword, so a `.pure` lambda is unparseable and
|
LLVM. Locked by `examples/1803-concurrency-pure-asm-param.sx` (`add(a,b)` → x0+x1 → 42).
|
||||||
`parseLambda`'s abi-handling is never reached. Latent `isLambda`/`parseLambda`
|
- `zig build && zig build test` green: **725 ran, 0 failed** + unit tests.
|
||||||
inconsistency, not a B1 concern.)
|
|
||||||
- **Naming:** the sx-facing name is **`pure`** throughout (field, diagnostic); LLVM's
|
### Earlier — B1.0a (lock + review hardening)
|
||||||
`naked` attribute is only the B1.0b lowering mechanism (per user direction — don't call
|
Plumbed `Function.is_pure` (set from `fd.abi == .pure` at both decl sites + generic.zig +
|
||||||
the function "naked").
|
pack.zig); `funcWantsImplicitCtx` skips `.pure` (no synthetic ctx, like `.c`); all
|
||||||
- `zig build && zig build test` green: **723 ran, 0 failed**.
|
body-lowering paths bypass `lowerValueBody` for `.pure` (asm body + `unreachable` cap — no sx
|
||||||
|
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
|
||||||
|
body); closed + locked. The review's `.pure`-lambda CRITICAL was a false positive
|
||||||
|
(unparseable — `isLambda` breaks on the `abi` keyword).
|
||||||
|
|
||||||
## Current state
|
## Current state
|
||||||
Stream A (atomics) is feature-complete (✅) and unblocks B2-channels. Stream B1: **B1.0a
|
Stream A (atomics) is feature-complete (✅). Stream B1: **B1.0 complete** — `abi(.pure)`
|
||||||
landed**; the `abi(.pure)` ABI is plumbed but emit deliberately bails (B1.0b flips it to
|
emits a real LLVM `naked` function end-to-end (decl, generic, pack paths), the substrate for
|
||||||
real LLVM `naked` emission). 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 `.pure` body reuses it.
|
||||||
|
- **`.pure` 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
|
||||||
|
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,
|
||||||
|
not silent):** a `.pure` *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
|
||||||
|
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.
|
||||||
|
|
||||||
## Next step
|
## Next step
|
||||||
**B1.0b (`abi(.pure)` real emission)** — per PLAN-FIBERS.md "Phases → B1.0 → B1.0b" and the
|
**B1.1 (per-fiber `context` root) — probe-first.** Per PLAN-FIBERS.md "Phases → B1.1". Write
|
||||||
kickoff prompt at the bottom of that file. Replace the emit bail with LLVM's `naked`
|
a probe confirming a spawn trampoline can pass a snapshotted `Context` as slot 0 with no
|
||||||
attribute + asm-only body; pin `1800` aarch64 (run end-to-end → exit 42, capture `.ir`); add
|
compiler change (grounded as likely zero-change); lock the behavior with an `18xx` example +
|
||||||
x86_64 cross sibling `1802` (ir-only); add an `emit_llvm.test.zig` unit test asserting the
|
a checkpoint note on the convention. Only if the probe surfaces a real gap (a path re-reads
|
||||||
`naked` attr. Separate commit (cadence rule — B1.0a locked, B1.0b greens).
|
`__sx_default_context` mid-stack) does this become a compiler step.
|
||||||
|
|
||||||
## Known issues / capability gaps
|
## Known issues / capability gaps
|
||||||
- **Orthogonal (not a B1 blocker):** default VALUES for comptime params don't bind on
|
- **Orthogonal (not a B1 blocker):** default VALUES for comptime params don't bind on
|
||||||
@@ -117,4 +129,16 @@ x86_64 cross sibling `1802` (ir-only); add an `emit_llvm.test.zig` unit test ass
|
|||||||
corrupted the stack). Fixed generic.zig + pack.zig (set `is_pure` + asm-only `unreachable`
|
corrupted the stack). Fixed generic.zig + pack.zig (set `is_pure` + asm-only `unreachable`
|
||||||
cap); locked by `examples/1801-concurrency-pure-generic-bail.sx`. The review's `.pure`-
|
cap); locked by `examples/1801-concurrency-pure-generic-bail.sx`. The review's `.pure`-
|
||||||
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). **Next: B1.0b (real `naked` emission).**
|
green (723/0).
|
||||||
|
- **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
|
||||||
|
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`
|
||||||
|
locks `naked` + `movl $42, %eax`). Unit test asserts `naked` present + `frame-pointer`
|
||||||
|
absent. Suite green (724/0).
|
||||||
|
- **B1.0c** — review-hardening: param-bearing `.pure` emitted invalid LLVM (loud verifier
|
||||||
|
error). Gated the param-alloca loop on `fd.abi != .pure` (decl.zig both paths + generic.zig)
|
||||||
|
— 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
|
||||||
|
(loud, nonsensical). **B1.0 complete.** Suite green (725/0). **Next: B1.1 (per-fiber
|
||||||
|
context, probe-first).**
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# PLAN-FIBERS — Stream B1 (fibers + Io + M:1 scheduler)
|
# PLAN-FIBERS — Stream B1 (fibers + Io + M:1 scheduler)
|
||||||
|
|
||||||
> **STATUS: 🚧 in progress.** B1.0a (`abi(.pure)` lock commit) ✅ landed. Next step =
|
> **STATUS: 🚧 in progress.** B1.0 (`abi(.pure)` codegen) ✅ complete — emits a real LLVM
|
||||||
> **B1.0b** (`abi(.pure)` real emission — LLVM `naked` attr). See the kickoff prompt at the
|
> `naked` function end-to-end (decl / generic / pack paths; examples 1800/1801/1802 + unit
|
||||||
> bottom.
|
> test). Next step = **B1.1** (per-fiber `context` root — probe-first, likely library-only).
|
||||||
|
|
||||||
Carved from [PLAN-POST-METATYPE.md](PLAN-POST-METATYPE.md) Stream B (§B1) + the
|
Carved from [PLAN-POST-METATYPE.md](PLAN-POST-METATYPE.md) Stream B (§B1) + the
|
||||||
design-of-record [../design/execution-evolution-roadmap.md](../design/execution-evolution-roadmap.md)
|
design-of-record [../design/execution-evolution-roadmap.md](../design/execution-evolution-roadmap.md)
|
||||||
@@ -152,23 +152,25 @@ B1.0 (`.pure`) forces these plumbing sites:
|
|||||||
|
|
||||||
## Phases (xfail→green steps)
|
## Phases (xfail→green steps)
|
||||||
|
|
||||||
### B1.0 — `abi(.pure)` codegen
|
### B1.0 — `abi(.pure)` codegen — ✅ COMPLETE
|
||||||
- **B1.0a (lock) — ✅ DONE** (commit pending). Carried `abi == .pure` into IR
|
- **B1.0a (lock) — ✅ DONE.** Carried `abi == .pure` into IR `Function.is_pure`; threaded
|
||||||
`Function.is_pure`; threaded through `decl.zig` (`funcWantsImplicitCtx` skips `.pure` like
|
through `decl.zig` (`funcWantsImplicitCtx` skips `.pure` like `.c`; all body-lowering paths
|
||||||
`.c`; both body-lowering paths bypass `lowerValueBody` for `.pure`, lowering the asm body +
|
bypass `lowerValueBody` for `.pure`, lowering the asm body + capping with `unreachable`) +
|
||||||
capping with `unreachable`); `emit_llvm` Pass 2 **bails loudly** on `func.is_pure`
|
generic.zig + pack.zig; `emit_llvm` Pass 2 bailed loudly on `func.is_pure`. Locked by
|
||||||
("`abi(.pure)` function '…' LLVM emission not yet implemented", build-gating nonzero exit).
|
`examples/1800-concurrency-pure-asm.sx` + the generic regression (review-found gap).
|
||||||
`examples/1800-concurrency-pure-asm.sx` (one host example, no `.build` pin — the bail is
|
- **B1.0b (green) — ✅ DONE.** `emit_llvm` declaration pass adds LLVM `naked` + `noinline` +
|
||||||
host-independent) locked to the bail snapshot. Suite green (722/0).
|
`nounwind` for `func.is_pure` and skips `frame-pointer=all` (incompatible with a frameless
|
||||||
- **B1.0b (green) ← NEXT** — emit LLVM's `naked` attr
|
function); Pass 2 emits the body normally (`naked` ⇒ verbatim asm + own `ret`, no
|
||||||
(`LLVMGetEnumAttributeKindForName("naked", 5)` + `LLVMCreateEnumAttribute` +
|
prologue). `1800` pinned aarch64 → exit 42 + `.ir`; `1801-concurrency-pure-generic.sx`
|
||||||
`LLVMAddAttributeAtIndex` at func index −1; shape per `nounwind` at emit_llvm.zig:1339);
|
(renamed from `-bail`) proves the generic path emits a naked body (exit 42);
|
||||||
emit the `.pure` body as the asm block only (no prologue/epilogue/ctx). Pin `1800`
|
`1802-concurrency-pure-asm-x86.sx` x86_64 cross sibling (ir-only here, `.ir` locks `naked`
|
||||||
aarch64 (`.build {"target":"aarch64-macos"}`) → runs end-to-end (exit 42) on this host,
|
+ `movl $42, %eax`). Unit test `emit: abi(.pure) function gets the naked attribute` asserts
|
||||||
ir-only on a mismatch; capture its `.ir` (asserts `naked` + the asm). Add an x86_64 cross
|
`naked` present + `frame-pointer` absent. Suite green (724/0).
|
||||||
sibling `examples/1802-concurrency-pure-asm-x86.sx` (`.build {"target":"x86_64-linux"}`,
|
- **B1.0c (review-hardening) — ✅ DONE.** A param-bearing `.pure` fn emitted invalid LLVM
|
||||||
ir-only here). Add a unit test in `emit_llvm.test.zig` asserting the `naked` attribute is
|
(loud verifier error). Gated the param-alloca loop on `fd.abi != .pure` (decl.zig both
|
||||||
present on a `.pure` function. Review the diff (no stray error text). Commit.
|
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`.
|
||||||
|
Pack `.pure` (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
|
||||||
|
|||||||
25
examples/1803-concurrency-pure-asm-param.sx
Normal file
25
examples/1803-concurrency-pure-asm-param.sx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
// Stream B1 (fibers) — an `abi(.pure)` function with PARAMETERS reads its args
|
||||||
|
// from ABI registers (the shape the fiber context-switch needs: `swap_context`
|
||||||
|
// reads `from`/`to` from x0/x1).
|
||||||
|
//
|
||||||
|
// A naked function has no frame, so params are NOT spilled to stack slots — they
|
||||||
|
// stay in their ABI registers and the asm body reads them directly. Here `a` is
|
||||||
|
// in x0, `b` in x1 (aarch64 AAPCS), and the result returns in x0: `add x0, x0,
|
||||||
|
// x1`. The lowering skips the param-alloca loop for `.pure` (decl.zig /
|
||||||
|
// generic.zig); the LLVM args are declared-but-unused, which the verifier allows
|
||||||
|
// (spilling them would emit `store i64 %0, …` → "cannot use argument of naked
|
||||||
|
// function"). aarch64-pinned; runs end-to-end (exit 42), ir-only on a mismatch.
|
||||||
|
//
|
||||||
|
// Regression for an adversarial-review finding: before the param-alloca guard, a
|
||||||
|
// param-bearing `.pure` fn emitted invalid LLVM (loud verifier error) instead of
|
||||||
|
// a working naked function.
|
||||||
|
add :: (a: i64, b: i64) -> i64 abi(.pure) {
|
||||||
|
asm volatile {
|
||||||
|
#string A
|
||||||
|
add x0, x0, x1
|
||||||
|
ret
|
||||||
|
A
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: () -> i64 { return add(40, 2); }
|
||||||
1
examples/expected/1803-concurrency-pure-asm-param.build
Normal file
1
examples/expected/1803-concurrency-pure-asm-param.build
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{ "target": "macos" }
|
||||||
1
examples/expected/1803-concurrency-pure-asm-param.exit
Normal file
1
examples/expected/1803-concurrency-pure-asm-param.exit
Normal file
@@ -0,0 +1 @@
|
|||||||
|
42
|
||||||
15
examples/expected/1803-concurrency-pure-asm-param.ir
Normal file
15
examples/expected/1803-concurrency-pure-asm-param.ir
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
; Function Attrs: naked noinline nounwind
|
||||||
|
define internal i64 @add(i64 %0, i64 %1) #0 {
|
||||||
|
entry:
|
||||||
|
call void asm sideeffect " add x0, x0, x1\0A ret\0A", ""()
|
||||||
|
unreachable
|
||||||
|
}
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
define i32 @main() #1 {
|
||||||
|
entry:
|
||||||
|
%call = call i64 @add(i64 40, i64 2)
|
||||||
|
%ca.tr = trunc i64 %call to i32
|
||||||
|
ret i32 %ca.tr
|
||||||
|
}
|
||||||
1
examples/expected/1803-concurrency-pure-asm-param.stderr
Normal file
1
examples/expected/1803-concurrency-pure-asm-param.stderr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
1
examples/expected/1803-concurrency-pure-asm-param.stdout
Normal file
1
examples/expected/1803-concurrency-pure-asm-param.stdout
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -2660,13 +2660,19 @@ 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);
|
||||||
|
|
||||||
for (fd.params, 0..) |p, i| {
|
// An `abi(.pure)` (naked) function has no frame: its params arrive in ABI
|
||||||
|
// 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
|
||||||
|
// `store i64 %0, …` — "cannot use argument of naked function" (LLVM verifier).
|
||||||
|
// Leave the LLVM args declared-but-unused (the verifier allows that); the asm
|
||||||
|
// references the registers.
|
||||||
|
if (fd.abi != .pure) 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));
|
||||||
self.builder.store(slot, param_ref);
|
self.builder.store(slot, param_ref);
|
||||||
scope.put(p.name, .{ .ref = slot, .ty = pty, .is_alloca = true });
|
scope.put(p.name, .{ .ref = slot, .ty = pty, .is_alloca = true });
|
||||||
}
|
};
|
||||||
|
|
||||||
// Inbound entry points + abi(.c) sx functions: bind current_ctx_ref
|
// Inbound entry points + abi(.c) sx functions: bind current_ctx_ref
|
||||||
// to the static default before any user code runs.
|
// to the static default before any user code runs.
|
||||||
@@ -2812,7 +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);
|
||||||
|
|
||||||
for (fd.params, 0..) |p, i| {
|
// `abi(.pure)` (naked): params arrive in registers, read directly by the asm
|
||||||
|
// 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.
|
||||||
|
if (fd.abi != .pure) 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.
|
||||||
@@ -2820,7 +2829,7 @@ pub fn lowerFunction(self: *Lowering, fd: *const ast.FnDecl, name: []const u8, i
|
|||||||
const param_ref = Ref.fromIndex(@intCast(i + user_param_base_lf));
|
const param_ref = Ref.fromIndex(@intCast(i + user_param_base_lf));
|
||||||
self.builder.store(slot, param_ref);
|
self.builder.store(slot, param_ref);
|
||||||
scope.put(p.name, .{ .ref = slot, .ty = pty, .is_alloca = true });
|
scope.put(p.name, .{ .ref = slot, .ty = pty, .is_alloca = true });
|
||||||
}
|
};
|
||||||
|
|
||||||
// Inbound entry points + abi(.c) sx functions: bind
|
// Inbound entry points + abi(.c) sx functions: bind
|
||||||
// current_ctx_ref to &__sx_default_context. See companion comment
|
// current_ctx_ref to &__sx_default_context. See companion comment
|
||||||
|
|||||||
@@ -128,7 +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
|
||||||
|
// asm body, never spilled to allocas (the LLVM verifier rejects a naked
|
||||||
|
// function that uses its arguments). Mirrors the decl-path guard.
|
||||||
|
if (fd.abi != .pure) {
|
||||||
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;
|
||||||
|
|||||||
Reference in New Issue
Block a user