nomen-lang 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1702,6 +1702,7 @@ const MNEMONICS = {
1702
1702
  ]] },
1703
1703
  dup: { shapes: [["f", "f"], ["f", "r"]] },
1704
1704
  movz: { shapes: [["r", "i"]] },
1705
+ movk: { shapes: [["r", "i"]] },
1705
1706
  mvn: { shapes: [["r", "r"]] },
1706
1707
  fmov: { shapes: [
1707
1708
  ["f", "f"],
@@ -2108,7 +2109,8 @@ function try_parse_operands(parts) {
2108
2109
  continue;
2109
2110
  }
2110
2111
  if (SHIFT_TOKEN_RE.exec(tok) && operands.length > 0) {
2111
- if (operands[operands.length - 1].kind === "reg") continue;
2112
+ const prev = operands[operands.length - 1];
2113
+ if (prev.kind === "reg" || prev.kind === "imm") continue;
2112
2114
  }
2113
2115
  return null;
2114
2116
  }
@@ -2159,7 +2161,7 @@ function split_operands(s) {
2159
2161
  return parts;
2160
2162
  }
2161
2163
  const IMM_RE = /^#(-?)(0x[0-9a-fA-F]+|\d+)$/;
2162
- function parse_imm(tok) {
2164
+ function parse_imm$1(tok) {
2163
2165
  const m = IMM_RE.exec(tok);
2164
2166
  if (!m) return null;
2165
2167
  return (m[1] ? -1n : 1n) * (m[2].toLowerCase().startsWith("0x") ? BigInt(m[2]) : BigInt(m[2]));
@@ -2168,7 +2170,7 @@ function parse_imm(tok) {
2168
2170
  function parse_pseudo_imm(tok) {
2169
2171
  const t = tok.trim();
2170
2172
  if (!t.startsWith("=")) return null;
2171
- const v = parse_imm("#" + t.slice(1));
2173
+ const v = parse_imm$1("#" + t.slice(1));
2172
2174
  return v === null && /^=\d+$/.test(t) ? BigInt(t.slice(1)) : v;
2173
2175
  }
2174
2176
  function parse_reg(tok) {
@@ -2225,7 +2227,7 @@ function parse_mem(parts, start) {
2225
2227
  let next = start + 1;
2226
2228
  let postOffset;
2227
2229
  if (next < parts.length && !parts[next].startsWith("[")) {
2228
- const v = parse_imm(parts[next]);
2230
+ const v = parse_imm$1(parts[next]);
2229
2231
  if (v !== null && !parsed_inner.offset && !parsed_inner.scale) {
2230
2232
  postOffset = v;
2231
2233
  next++;
@@ -2248,7 +2250,7 @@ function parse_mem_inner(inner) {
2248
2250
  const mem = { base };
2249
2251
  for (let k = 1; k < comps.length; k++) {
2250
2252
  const comp = comps[k];
2251
- const imm = parse_imm(comp);
2253
+ const imm = parse_imm$1(comp);
2252
2254
  if (imm !== null) {
2253
2255
  mem.offset = {
2254
2256
  kind: "imm",
@@ -2287,7 +2289,7 @@ function parse_operand(tok) {
2287
2289
  width: !(cls === "fpr") ? 64 : reg.startsWith("d") ? 64 : /^[vs]\d+$/.test(reg) ? 32 : 128
2288
2290
  };
2289
2291
  }
2290
- const imm = parse_imm(trimmed);
2292
+ const imm = parse_imm$1(trimmed);
2291
2293
  if (imm !== null) return {
2292
2294
  kind: "imm",
2293
2295
  value: imm,
@@ -3734,6 +3736,110 @@ function eliminate_dead_cycle_moves(code) {
3734
3736
  }
3735
3737
  return code;
3736
3738
  }
3739
+ //#endregion
3740
+ //#region ../src/build_aarch64/asm_expand_adr.ts
3741
+ /**
3742
+ * Final-address expansion (runs last, after every optimizer that
3743
+ * pattern-matches the `adr` form). `adr` is PC-relative with a ±1 MB reach;
3744
+ * in a large program (allmark's generated spec tests assemble to 1.5-3 MB of
3745
+ * asm) the string and constant labels in `.data` sit more than 1 MB from the
3746
+ * code that uses them, and the assembler rejects every `adr xN, _str_KNN`
3747
+ * with "fixup value out of range".
3748
+ *
3749
+ * Rewrite `adr xN, SYM` into the page-relative pair the vtable install already
3750
+ * uses — `adrp xN, SYM@PAGE` + `add xN, xN, SYM@PAGEOFF` (adrp reaches ±4 GB).
3751
+ *
3752
+ * Only NON-LOCAL symbols are rewritten: a label beginning with `.` (or a
3753
+ * numeric `Nf`/`Nb` local) is intra-function and within reach, so it stays an
3754
+ * `adr` — keeping the emitted text of small programs unchanged.
3755
+ */
3756
+ const ADR_RE = /^(\s*)adr (x[0-9]+), ([A-Za-z_][A-Za-z0-9_]*)\s*$/;
3757
+ function expand_far_adr_addresses(code) {
3758
+ const lines = code.split("\n");
3759
+ for (let i = 0; i < lines.length; i++) {
3760
+ const m = ADR_RE.exec(lines[i]);
3761
+ if (!m) continue;
3762
+ const [, indent, reg, sym] = m;
3763
+ lines[i] = `${indent}adrp ${reg}, ${sym}@PAGE\n${indent}add ${reg}, ${reg}, ${sym}@PAGEOFF`;
3764
+ }
3765
+ return lines.join("\n");
3766
+ }
3767
+ //#endregion
3768
+ //#region ../src/build_aarch64/asm_expand_mov.ts
3769
+ /**
3770
+ * Large-immediate expansion (final pass, runs after every optimizer so the
3771
+ * passes see the canonical `mov xN, #imm` / `ldr xN, =K` forms).
3772
+ *
3773
+ * Two AArch64 hazards this pass removes:
3774
+ * - The `mov` alias only encodes a 16-bit immediate (movz; movn for small
3775
+ * negatives). An emitter site that hands a bigger constant to the plain
3776
+ * `mov` form — a long string literal's byte length, a big array count, a
3777
+ * large struct size — produces a line the assembler rejects outright.
3778
+ * - `ldr xN, =K` needs a literal pool the assembler must site within ±1 MB
3779
+ * of the load. In a multi-MB program (allmark's generated spec tests) the
3780
+ * pool falls out of range and the assembler reports "fixup value out of
3781
+ * range".
3782
+ *
3783
+ * Both are rewritten to a movz/movk chunk chain (positive) or the
3784
+ * movn + movk form (negative), exactly materializing the 64-bit constant. The
3785
+ * chunks are computed on BigInt so a full-width `uint64` (or a value written
3786
+ * as a hex literal) is preserved — a JS `number` rounds and can spill into a
3787
+ * fifth chunk (`movk …, lsl #64`, which the assembler rejects).
3788
+ */
3789
+ const MASK64 = (1n << 64n) - 1n;
3790
+ const MASK16 = 65535n;
3791
+ /** Materialize any 64-bit integer constant in `reg`, without a literal pool. */
3792
+ function materialize_immediate(reg, k) {
3793
+ if (k >= 0n && k <= 65535n) return [`mov ${reg}, #${k}`];
3794
+ if (k < 0n && k >= -65536n) return [`movn ${reg}, #${-k - 1n}`];
3795
+ const v = k & MASK64;
3796
+ const chunk = (i) => v >> BigInt(16 * i) & MASK16;
3797
+ if (k >= 0n) {
3798
+ const out = [`movz ${reg}, #${chunk(0)}`];
3799
+ for (let i = 1; i < 4; i++) {
3800
+ const c = chunk(i);
3801
+ if (c !== 0n) out.push(`movk ${reg}, #${c}, lsl #${i * 16}`);
3802
+ }
3803
+ return out;
3804
+ }
3805
+ return [
3806
+ `movn ${reg}, #${~chunk(0) & MASK16}`,
3807
+ `movk ${reg}, #${chunk(1)}, lsl #16`,
3808
+ `movk ${reg}, #${chunk(2)}, lsl #32`,
3809
+ `movk ${reg}, #${chunk(3)}, lsl #48`
3810
+ ];
3811
+ }
3812
+ function parse_imm(text) {
3813
+ try {
3814
+ return BigInt(text);
3815
+ } catch {
3816
+ return null;
3817
+ }
3818
+ }
3819
+ const MOV_IMM_RE = /^(\s*)mov (x|w)([0-9]+), #(-?\d+)$/;
3820
+ const LDR_POOL_RE = /^(\s*)ldr (x[0-9]+), =(-?\d+)$/;
3821
+ function expand_large_mov_immediates(code) {
3822
+ const lines = code.split("\n");
3823
+ for (let i = 0; i < lines.length; i++) {
3824
+ const m = MOV_IMM_RE.exec(lines[i]);
3825
+ if (m) {
3826
+ const [, indent, cls, num, imm_text] = m;
3827
+ const k = parse_imm(imm_text);
3828
+ if (k === null) continue;
3829
+ if (k >= -65536n && k <= 65535n) continue;
3830
+ const reg = `${cls}${num}`;
3831
+ lines[i] = materialize_immediate(reg, k).map((line) => `${indent}${line}`).join("\n");
3832
+ continue;
3833
+ }
3834
+ const p = LDR_POOL_RE.exec(lines[i]);
3835
+ if (!p) continue;
3836
+ const [, indent, reg, imm_text] = p;
3837
+ const k = parse_imm(imm_text);
3838
+ if (k === null) continue;
3839
+ lines[i] = materialize_immediate(reg, k).map((line) => `${indent}${line}`).join("\n");
3840
+ }
3841
+ return lines.join("\n");
3842
+ }
3737
3843
  const LABEL_LINE_RE$2 = /^([A-Za-z_.$][\w.$]*|\d+):$/;
3738
3844
  const NUMERIC_LABEL_RE = /^\d+$/;
3739
3845
  const B_ALIAS_RE = /^(eq|ne|lt|le|gt|ge|hs|lo|ls|hi|mi|pl)$/;
@@ -6309,36 +6415,24 @@ var AccessFunctionCallNode = class extends BaseNode {
6309
6415
  * escape-hatch call (target type is `Nursery`, method name is `start`).
6310
6416
  * The build phase reads `function_return_type` and emits the spawn
6311
6417
  * trampoline against the passed Nursery's runtime futures/count pointers.
6312
- * See ASYNC.md and ASYNC.md.
6418
+ * See ASYNC.md, "Nursery escape hatch".
6313
6419
  */
6314
6420
  is_nursery_spawn;
6315
6421
  /**
6316
- * Set during checking when this is a `Thread(fn(args)).start()` call —
6317
- * the surface form of a direct spawn (target type is `Thread`, method
6318
- * name is `start`). The build phase synthesizes a SpawnNode from the
6319
- * wrapped call and emits the standard spawn trampoline. See ASYNC.md.
6422
+ * RETIRED with the pre-migration launch dispatch: `.start()`/`.detach()`
6423
+ * are ordinary methods on the library `Thread` class now (docs/ASYNC.md).
6424
+ * The flags remain only because monomorphized clones can carry them from
6425
+ * older stamps; nothing sets or consumes them on fresh checks.
6320
6426
  */
6321
6427
  is_thread_start;
6322
- /**
6323
- * Set during checking when this is a `Thread(fn(args)).detach()` call —
6324
- * the daemon form. The build synthesizes a SpawnNode from the wrapped
6325
- * call and emits a detached-pthread trampoline: never a pool worker,
6326
- * nobody joins it, process exit kills it mid-execution by design (the
6327
- * std::thread::spawn contract). See ASYNC.md, "Daemon tasks".
6328
- */
6428
+ /** Retired — see is_thread_start. */
6329
6429
  is_thread_detach;
6330
- /**
6331
- * Set during checking when this is a `Fiber(fn(args)).start()` call —
6332
- * the fiber flavor of a direct spawn. The build synthesizes a SpawnNode
6333
- * from the wrapped call and emits the fiber trampoline (the launch goes
6334
- * to the fiber scheduler instead of the pool). See ASYNC.md.
6335
- */
6430
+ /** Retired — see is_thread_start. */
6336
6431
  is_fiber_start;
6337
6432
  /**
6338
- * Set during checking when this is a `Fiber(fn(args)).start_on(buf)`
6339
- * call — like is_fiber_start, but the fiber runs on the caller-provided
6340
- * fixed-size array stack. The single parameter is the buffer. See
6341
- * ASYNC.md.
6433
+ * RETIRED with the pre-migration launch dispatch: `.start_on(buf)` is
6434
+ * checker-validated and rewritten to the ordinary `Fiber.start` method
6435
+ * (docs/ASYNC.md). Kept only for old clone stamps.
6342
6436
  */
6343
6437
  is_fiber_start_on;
6344
6438
  /**
@@ -6520,16 +6614,17 @@ var FunctionCallNode = class extends BaseNode {
6520
6614
  /** Fiber flavor of the magic spawn constructor (see is_thread_ctor). */
6521
6615
  is_fiber_ctor;
6522
6616
  /**
6523
- * The GENERALIZED spawn-constructor flavor (ASYNC.md, "User-defined
6524
- * async primitives"): the call's name resolves to a user CLASS
6525
- * conforming to the core `Awaitable` trait and carrying the
6526
- * spawn-handle field contract (uint64 fields task / result_slot /
6527
- * cancel_flag / future — Thread/Fiber keep their own flags; they do not
6528
- * conform to Awaitable). `X(fn(args))` packs the wrapped call eagerly
6529
- * and yields a heap `X` instance (monomorphized `X<T>` when generic)
6530
- * whose fields carry the launch machinery; the class's own launch
6531
- * methods consume them. Typed/stamped by check_magic_ctor exactly like
6532
- * the Thread/Fiber flavors.
6617
+ * The catch-all spawn-constructor flavor: a `#spawn`-bearing class that
6618
+ * is neither Thread nor Fiber (a user class declaring its own
6619
+ * construction hook — ASYNC.md). The construction is keyed
6620
+ * on the `#spawn` member, never on Awaitable conformance (the old
6621
+ * field-contract sugar retired; see docs/ASYNC.md, "Design decisions"). `X(fn(args))` packs the
6622
+ * wrapped call eagerly and yields a heap `X` instance (monomorphized
6623
+ * `X<T>` when generic) whose fields carry the launch machinery (the
6624
+ * uint64 task / result_slot / cancel_flag / future contract the
6625
+ * Task.* seam drives); the class's own launch methods consume them.
6626
+ * Typed/stamped by check_magic_ctor exactly like the Thread/Fiber
6627
+ * flavors.
6533
6628
  */
6534
6629
  is_awaitable_ctor;
6535
6630
  /**
@@ -7351,7 +7446,7 @@ function scan_self_string_field_writes(struct, method) {
7351
7446
  const scan_method = (func) => {
7352
7447
  if (visited.has(func.name)) return;
7353
7448
  visited.add(func.name);
7354
- walk$2(func.statements ?? [], (n) => {
7449
+ walk$3(func.statements ?? [], (n) => {
7355
7450
  if (n.node_type === "assign") {
7356
7451
  const lhs = n.left_value;
7357
7452
  if (lhs?.node_type !== "access") return;
@@ -7395,10 +7490,10 @@ function drop_self_written_string_field_records(status, receiver_name, fields) {
7395
7490
  * statement arrays) — skipping `parent`/`scope` back-references and NOT
7396
7491
  * descending INTO nested `func`/`struct`/`trait` declarations (a nested
7397
7492
  * function's body is a separate function, not part of this one's writes). */
7398
- function walk$2(value, cb) {
7493
+ function walk$3(value, cb) {
7399
7494
  if (!value || typeof value !== "object") return;
7400
7495
  if (Array.isArray(value)) {
7401
- for (const item of value) walk$2(item, cb);
7496
+ for (const item of value) walk$3(item, cb);
7402
7497
  return;
7403
7498
  }
7404
7499
  const n = value;
@@ -7407,7 +7502,7 @@ function walk$2(value, cb) {
7407
7502
  if (is_boundary) return;
7408
7503
  for (const key of Object.keys(value)) {
7409
7504
  if (key === "parent" || key === "scope" || key === "node_type") continue;
7410
- walk$2(value[key], cb);
7505
+ walk$3(value[key], cb);
7411
7506
  }
7412
7507
  }
7413
7508
  //#endregion
@@ -8983,186 +9078,6 @@ var AsyncBlockNode = class extends BaseNode {
8983
9078
  }
8984
9079
  };
8985
9080
  //#endregion
8986
- //#region ../src/build_aarch64/utils/aarch64_size.ts
8987
- /**
8988
- * Storage width of a value of `type` in bytes, derived from the
8989
- * built-in type table (see BuiltInTypeInfo.bytes). Non-built-in names are
8990
- * structs/enums/generics — 8 unless overridden by struct layout. `string`
8991
- * is the fat 16-byte { char* ptr; long len; } pair; in registers it rides
8992
- * as a consecutive (ptr, len) pair — the same ABI `view T` uses.
8993
- */
8994
- function aarch64_size(type) {
8995
- if (type === "string") return 16;
8996
- return get_built_in_type(type)?.bytes ?? 8;
8997
- }
8998
- //#endregion
8999
- //#region ../src/build_aarch64/utils/stack_var.ts
9000
- function allocate_stack_space(status, size, alignment = 8) {
9001
- if (!status.stack_size) status.stack_size = 0;
9002
- const aligned_size = Math.ceil(status.stack_size / alignment) * alignment;
9003
- status.stack_size = aligned_size + size;
9004
- return aligned_size;
9005
- }
9006
- /**
9007
- * Load a stack slot into a promoted (callee-saved) register with the slot's
9008
- * width. A sub-word slot (bool/char/int8 = strb, int16 = strh, int32 = str w)
9009
- * must be loaded with the matching zero-extending ldrb/ldrh/ldr w — a
9010
- * full-width `ldr` picks up dirty stack bytes above the slot and a bool can
9011
- * read as true regardless of its stored value.
9012
- */
9013
- function emit_promoted_load(status, reg, offset, type_name) {
9014
- const size = aarch64_size(type_name);
9015
- if (size === 1) emit_asm(status, `ldrb ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
9016
- else if (size === 2) emit_asm(status, `ldrh ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
9017
- else if (size === 4) emit_asm(status, `ldr ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
9018
- else emit_asm(status, `ldr ${reg}, [x29, #${offset}]\n`);
9019
- }
9020
- /**
9021
- * Write a promoted (callee-saved) register back to its stack slot with the
9022
- * slot's width. A full-width `str` into a sub-word slot clobbers the adjacent
9023
- * stack bytes — whatever variable lives there gets corrupted.
9024
- */
9025
- function emit_promoted_store(status, reg, offset, type_name) {
9026
- const size = aarch64_size(type_name);
9027
- if (size === 1) emit_asm(status, `strb ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
9028
- else if (size === 2) emit_asm(status, `strh ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
9029
- else if (size === 4) emit_asm(status, `str ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
9030
- else emit_asm(status, `str ${reg}, [x29, #${offset}]\n`);
9031
- }
9032
- function is_local_ref_var(name, status) {
9033
- return !!status.function_ref_params?.has(name) && !status.function_param_regs?.has(name);
9034
- }
9035
- function emit_var_address(status, reg, name) {
9036
- if (status.closure_env_offsets?.has(name) && status.closure_env_slot !== void 0) {
9037
- emit_asm(status, `ldr ${reg}, [x29, #${status.closure_env_slot}]\n`);
9038
- const off = status.closure_env_offsets.get(name);
9039
- if (off !== 0) emit_asm(status, `add ${reg}, ${reg}, #${off}\n`);
9040
- return;
9041
- }
9042
- const alloc_reg = status.register_allocations?.get(name);
9043
- if (alloc_reg) {
9044
- const offset = status.stack_offsets?.get(name);
9045
- if (offset !== void 0) {
9046
- const type_name = status.scoped_declarations.find((d) => d.name === name)?.type?.name || status.variable_types?.get(name)?.name || "";
9047
- if (type_name) emit_promoted_store(status, alloc_reg, offset, type_name);
9048
- else emit_asm(status, `str ${alloc_reg}, [x29, #${offset}]\n`);
9049
- }
9050
- }
9051
- const offset = status.stack_offsets?.get(name);
9052
- if (offset !== void 0) {
9053
- if (status.function_struct_param_slots?.has(name)) emit_asm(status, `ldr ${reg}, [x29, #${offset}]\n`);
9054
- else emit_asm(status, `add ${reg}, x29, #${offset}\n`);
9055
- } else {
9056
- const param_reg = status.function_param_regs?.get(name);
9057
- if (param_reg) emit_asm(status, `mov ${reg}, ${param_reg}\n`);
9058
- else emit_asm(status, `adr ${reg}, ${name}\n`);
9059
- }
9060
- }
9061
- function emit_deref_var_address(status, reg, name) {
9062
- emit_var_address(status, reg, name);
9063
- if (is_local_ref_var(name, status)) emit_asm(status, `ldr ${reg}, [${reg}]\n`);
9064
- }
9065
- function emit_var_load(status, reg, name, size) {
9066
- if (name === "null") {
9067
- emit_asm(status, `mov ${reg}, #0\n`);
9068
- return;
9069
- }
9070
- if (status.closure_env_offsets?.has(name) && status.closure_env_slot !== void 0) {
9071
- const off = status.closure_env_offsets.get(name);
9072
- emit_asm(status, `ldr ${reg}, [x29, #${status.closure_env_slot}]\n`);
9073
- if (size === 16 && reg.startsWith("x")) {
9074
- emit_asm(status, `ldr x${parseInt(reg.substring(1), 10) + 1}, [${reg}, #${off + 8}]\n`);
9075
- emit_asm(status, `ldr ${reg}, [${reg}, #${off}]\n`);
9076
- } else if (size === 1) emit_asm(status, `ldrb ${reg.replace("x", "w")}, [${reg}, #${off}]\n`);
9077
- else if (size === 2) emit_asm(status, `ldrh ${reg.replace("x", "w")}, [${reg}, #${off}]\n`);
9078
- else if (size === 4) emit_asm(status, `ldr ${reg.replace("x", "w")}, [${reg}, #${off}]\n`);
9079
- else emit_asm(status, `ldr ${reg}, [${reg}, #${off}]\n`);
9080
- return;
9081
- }
9082
- const alloc_reg = status.register_allocations?.get(name);
9083
- if (alloc_reg) {
9084
- if (alloc_reg.startsWith("d") && reg.startsWith("d")) {
9085
- if (reg !== alloc_reg) emit_asm(status, `fmov ${reg}, ${alloc_reg}\n`);
9086
- } else if (alloc_reg.startsWith("d")) emit_asm(status, `fmov ${reg}, ${alloc_reg}\n`);
9087
- else if (reg !== alloc_reg) emit_asm(status, `mov ${reg}, ${alloc_reg}\n`);
9088
- return;
9089
- }
9090
- const offset = status.stack_offsets?.get(name);
9091
- if (offset !== void 0) {
9092
- if (size === 16 && reg.startsWith("x")) {
9093
- const n = parseInt(reg.substring(1), 10);
9094
- if (offset + 8 > 504) {
9095
- emit_asm(status, `ldr ${reg}, [x29, #${offset}]\n`);
9096
- emit_asm(status, `ldr x${n + 1}, [x29, #${offset + 8}]\n`);
9097
- } else emit_asm(status, `ldp ${reg}, x${n + 1}, [x29, #${offset}]\n`);
9098
- return;
9099
- }
9100
- if (size === 1) emit_asm(status, `ldrb ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
9101
- else if (size === 2) emit_asm(status, `ldrh ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
9102
- else if (size === 4) emit_asm(status, `ldr ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
9103
- else emit_asm(status, `ldr ${reg}, [x29, #${offset}]\n`);
9104
- } else {
9105
- const param_reg = status.function_param_regs?.get(name);
9106
- if (param_reg) {
9107
- if (reg !== param_reg) emit_asm(status, `mov ${reg}, ${param_reg}\n`);
9108
- } else {
9109
- emit_asm(status, `adr ${reg}, ${name}\n`);
9110
- if (size === 1) emit_asm(status, `ldrb ${reg.replace("x", "w")}, [${reg}]\n`);
9111
- else if (size === 2) emit_asm(status, `ldrh ${reg.replace("x", "w")}, [${reg}]\n`);
9112
- else if (size === 4) emit_asm(status, `ldr ${reg.replace("x", "w")}, [${reg}]\n`);
9113
- else emit_asm(status, `ldr ${reg}, [${reg}]\n`);
9114
- }
9115
- }
9116
- }
9117
- function emit_var_store(status, reg, name, size) {
9118
- if (status.closure_env_offsets?.has(name) && status.closure_env_slot !== void 0) {
9119
- const off = status.closure_env_offsets.get(name);
9120
- emit_asm(status, `ldr x9, [x29, #${status.closure_env_slot}]\n`);
9121
- if (size === 16 && reg.startsWith("x")) {
9122
- const n = parseInt(reg.substring(1), 10);
9123
- emit_asm(status, `str ${reg}, [x9, #${off}]\n`);
9124
- emit_asm(status, `str x${n + 1}, [x9, #${off + 8}]\n`);
9125
- } else if (size === 1) emit_asm(status, `strb ${reg.replace("x", "w")}, [x9, #${off}]\n`);
9126
- else if (size === 2) emit_asm(status, `strh ${reg.replace("x", "w")}, [x9, #${off}]\n`);
9127
- else if (size === 4) emit_asm(status, `str ${reg.replace("x", "w")}, [x9, #${off}]\n`);
9128
- else emit_asm(status, `str ${reg}, [x9, #${off}]\n`);
9129
- return;
9130
- }
9131
- const alloc_reg = status.register_allocations?.get(name);
9132
- if (alloc_reg) {
9133
- if (alloc_reg.startsWith("d") && reg.startsWith("d")) {
9134
- if (reg !== alloc_reg) emit_asm(status, `fmov ${alloc_reg}, ${reg}\n`);
9135
- } else if (alloc_reg.startsWith("d")) emit_asm(status, `fmov ${alloc_reg}, ${reg}\n`);
9136
- else if (reg !== alloc_reg) emit_asm(status, `mov ${alloc_reg}, ${reg}\n`);
9137
- return;
9138
- }
9139
- const offset = status.stack_offsets?.get(name);
9140
- if (offset !== void 0) {
9141
- if (size === 16 && reg.startsWith("x")) {
9142
- const n = parseInt(reg.substring(1), 10);
9143
- if (offset + 8 > 504) {
9144
- emit_asm(status, `str ${reg}, [x29, #${offset}]\n`);
9145
- emit_asm(status, `str x${n + 1}, [x29, #${offset + 8}]\n`);
9146
- } else emit_asm(status, `stp ${reg}, x${n + 1}, [x29, #${offset}]\n`);
9147
- return;
9148
- }
9149
- if (size === 1) emit_asm(status, `strb ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
9150
- else if (size === 2) emit_asm(status, `strh ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
9151
- else if (size === 4) emit_asm(status, `str ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
9152
- else emit_asm(status, `str ${reg}, [x29, #${offset}]\n`);
9153
- } else {
9154
- const param_reg = status.function_param_regs?.get(name);
9155
- if (param_reg) emit_asm(status, `mov ${param_reg}, ${reg}\n`);
9156
- else {
9157
- emit_asm(status, `adr x1, ${name}\n`);
9158
- if (size === 1) emit_asm(status, `strb ${reg.replace("x", "w")}, [x1]\n`);
9159
- else if (size === 2) emit_asm(status, `strh ${reg.replace("x", "w")}, [x1]\n`);
9160
- else if (size === 4) emit_asm(status, `str ${reg.replace("x", "w")}, [x1]\n`);
9161
- else emit_asm(status, `str ${reg}, [x1]\n`);
9162
- }
9163
- }
9164
- }
9165
- //#endregion
9166
9081
  //#region ../src/build_aarch64/build_spawn_node.ts
9167
9082
  /**
9168
9083
  * Pool infrastructure emitted as file-scope C on the first spawn.
@@ -9234,6 +9149,14 @@ struct nomen_future {
9234
9149
  // The fiber whose task this future is (set at fiber spawn). Cancellation
9235
9150
  // uses it to wake a fiber parked on a primitive waitq.
9236
9151
  struct nomen_fiber *owning_fiber;
9152
+ // 1 when the result slot holds a FAT string (set at construction, which
9153
+ // knows the wrapped call's return type). The last release frees an
9154
+ // unconsumed string buffer from the cell: result() zeroes the cell when
9155
+ // it moves the value out, and Task's #destroy zeroes after its own
9156
+ // free, so a non-NULL ptr at the last release means "never consumed".
9157
+ // Appended LAST: raw bodies read refs/result_slot/owner_args at fixed
9158
+ // offsets (#116/#128/#136), which must not shift.
9159
+ int slot_fat;
9237
9160
  };
9238
9161
  // Fiber seam (defined in FIBER_HEADER_C, appended after this block).
9239
9162
  // Declared here — after struct nomen_future is complete — so the
@@ -9343,6 +9266,15 @@ int __nomen_future_timedwait(struct nomen_future *f, long long deadline_ms) {
9343
9266
  pthread_mutex_unlock(&f->mu);
9344
9267
  return 1;
9345
9268
  }
9269
+ // Non-blocking completion peek (Task.is_done) — non-static: raw asm
9270
+ // branches into it.
9271
+ int __nomen_future_is_done(struct nomen_future *f) {
9272
+ if (!f) return 0;
9273
+ pthread_mutex_lock(&f->mu);
9274
+ int done = f->done;
9275
+ pthread_mutex_unlock(&f->mu);
9276
+ return done;
9277
+ }
9346
9278
  void __nomen_future_release(struct nomen_future *f) {
9347
9279
  pthread_mutex_lock(&f->mu);
9348
9280
  int last = --f->refs == 0;
@@ -9351,6 +9283,19 @@ void __nomen_future_release(struct nomen_future *f) {
9351
9283
  pthread_mutex_destroy(&f->mu);
9352
9284
  pthread_cond_destroy(&f->cv);
9353
9285
  free(f->cancel_flag);
9286
+ // An unconsumed fat-string result: free the buffer the trampoline
9287
+ // stored (race-mode and fire-and-forget tasks never hand it to a
9288
+ // consumer). Every consumer path zeroes the cell first — result()
9289
+ // on move-out, Task's #destroy after its own free — so a non-NULL
9290
+ // ptr here is unambiguous and the free happens exactly once.
9291
+ if (f->slot_fat && f->result_slot) {
9292
+ unsigned long long *_s = (unsigned long long *)f->result_slot;
9293
+ if (_s[0]) {
9294
+ free((void *)_s[0]);
9295
+ _s[0] = 0;
9296
+ _s[1] = 0;
9297
+ }
9298
+ }
9354
9299
  free(f->result_slot);
9355
9300
  // The task closure is owned by the future — disposed at the last
9356
9301
  // release (see __nomen_closure_dispose above for the lifetime note).
@@ -9474,13 +9419,9 @@ void __nomen_future_cancel(struct nomen_future *f) {
9474
9419
  // schedule it so it resumes and observes the flag (idempotent).
9475
9420
  if (f->owning_fiber) __nomen_fiber_schedule(f->owning_fiber);
9476
9421
  }
9477
- // Race-mode helpers — see build_c POOL_HEADER for semantics.
9478
- int __nomen_future_is_done(struct nomen_future *f) {
9479
- pthread_mutex_lock(&f->mu);
9480
- int d = f->done;
9481
- pthread_mutex_unlock(&f->mu);
9482
- return d;
9483
- }
9422
+ // Race-mode helpers — see build_c POOL_HEADER for semantics. (The
9423
+ // non-blocking peek itself, __nomen_future_is_done, lives with the future
9424
+ // machinery above — non-static: Task.is_done's raw asm branches into it.)
9484
9425
  int __nomen_nursery_race_wait(struct nomen_future **futures, int count, long long deadline_ms) {
9485
9426
  if (count <= 0) return 0;
9486
9427
  struct timespec sleep_ts = {0, 1000000};
@@ -9524,7 +9465,8 @@ static void *__nomen_detached_run(void *p) {
9524
9465
  free(d);
9525
9466
  return NULL;
9526
9467
  }
9527
- static void __nomen_task_detach(struct nomen_closure *task) {
9468
+ // Non-static: raw asm (Thread.nm's #destroy/detach bodies) branches into it.
9469
+ void __nomen_task_detach(struct nomen_closure *task) {
9528
9470
  struct nomen_detached_start *d =
9529
9471
  (struct nomen_detached_start *)malloc(sizeof(struct nomen_detached_start));
9530
9472
  d->task = task;
@@ -9548,6 +9490,7 @@ static void __nomen_task_detach(struct nomen_closure *task) {
9548
9490
  * linked program must define them.
9549
9491
  */
9550
9492
  function ensure_concurrency_runtime_a64(status) {
9493
+ status.used_fibers = true;
9551
9494
  if (status.emit_mode === "system") return;
9552
9495
  if (status.pool_runtime_emitted && status.fiber_runtime_emitted) return;
9553
9496
  if (!status.pool_runtime_emitted) {
@@ -10299,116 +10242,6 @@ static int __nomen_deadlock_check_worker(void) {
10299
10242
  }
10300
10243
  `;
10301
10244
  /**
10302
- * Build a `.start()` launch on a `Thread(fn(args))` construction (or a
10303
- * stored Thread binding) — aarch64 backend (CLOSURE.md Phase 3b).
10304
- * A companion-C helper reads the packed handles from the receiver's fields,
10305
- * submits the task closure to the pool, registers the future with the
10306
- * enclosing nursery (if any), transfers the handles out of the instance,
10307
- * and returns the Task<T> handle (or NULL, fire-and-forget); the assembly
10308
- * builds the receiver pointer and calls it.
10309
- */
10310
- function build_thread_start$1(access_func, target, status) {
10311
- ensure_concurrency_runtime_a64(status);
10312
- const id = status.spawn_counter ?? 0;
10313
- status.spawn_counter = id + 1;
10314
- const helper_name = `nomen_thread_${id}_start`;
10315
- const t_arg = spawn_result_type_arg$5(access_func.function_return_type);
10316
- const mono_thread = mono_type_name("Thread", [t_arg]);
10317
- const mono_task_name = mono_type_name("Task", [t_arg]);
10318
- const nursery_id = status.nursery_stack?.at(-1);
10319
- const nursery_off = nursery_id !== void 0 ? status.nursery_offsets?.get(nursery_id) : void 0;
10320
- const fire_and_forget = !!access_func.is_statement;
10321
- const target_is_temp = target.node_type === "func_call" && !!(target.is_thread_ctor || target.is_fiber_ctor);
10322
- const refs = fire_and_forget ? nursery_id !== void 0 ? 2 : 1 : nursery_id !== void 0 ? 3 : 2;
10323
- let c = `// --- thread start site ${id} ---\n`;
10324
- c += `void *${helper_name}(void *self`;
10325
- if (nursery_off) c += `, void **__nomen_nursery_futures, int *__nomen_nursery_count, int *__nomen_nursery_cap`;
10326
- c += `) {\n`;
10327
- c += `\textern void *${mono_task_name}_traits[];\n\tstruct ${mono_thread} *t = (struct ${mono_thread} *)self;\n`;
10328
- c += `\tstruct nomen_future *f = (struct nomen_future *)t->future;\n`;
10329
- c += `\tvoid *result_ptr = (void *)t->result_slot;\n`;
10330
- c += `\tunsigned long long *cancel_ptr = (unsigned long long *)t->cancel_flag;\n`;
10331
- c += `\tstruct nomen_closure *cl = (struct nomen_closure *)t->task;\n`;
10332
- c += `\tf->refs = ${refs};\n`;
10333
- c += `\t__nomen_pool_submit(cl);\n`;
10334
- if (nursery_off) c += `\t__nomen_nursery_track(__nomen_nursery_futures, __nomen_nursery_count, __nomen_nursery_cap, f);\n`;
10335
- c += `\tt->task = 0;\n`;
10336
- c += `\tt->future = 0;\n`;
10337
- c += `\tt->result_slot = 0;\n`;
10338
- c += `\tt->cancel_flag = 0;\n`;
10339
- c += `\tt->started = 1;\n`;
10340
- if (target_is_temp) c += `\tfree(t);\n`;
10341
- if (fire_and_forget) c += `\treturn (void *)0;\n`;
10342
- else {
10343
- c += `\tstruct ${mono_task_name} *task = (struct ${mono_task_name} *)malloc(sizeof(struct ${mono_task_name}));\n`;
10344
- c += `\ttask->_vt = (void **)${mono_task_name}_traits;\n`;
10345
- c += `\ttask->handle = 0;\n`;
10346
- c += `\ttask->done = 0;\n`;
10347
- c += `\ttask->result_slot = (unsigned long long)result_ptr;\n`;
10348
- c += `\ttask->cancel_flag = (unsigned long long)cancel_ptr;\n`;
10349
- c += `\ttask->future = (unsigned long long)f;\n`;
10350
- c += `\treturn task;\n`;
10351
- }
10352
- c += `}\n`;
10353
- if (!status.file_scope_c) status.file_scope_c = "";
10354
- status.file_scope_c += c;
10355
- emit_asm(status, `// thread start site ${id}\n`);
10356
- build_node(target, status);
10357
- ensure_newline(status);
10358
- if (nursery_off) {
10359
- const park = allocate_stack_space(status, 8, 8);
10360
- emit_asm(status, `str x0, [x29, #${park}]\n`);
10361
- emit_asm(status, `add x1, x29, #${nursery_off.futures_off}\n`);
10362
- emit_asm(status, `add x2, x29, #${nursery_off.count_off}\n`);
10363
- emit_asm(status, `add x3, x29, #${nursery_off.cap_off}\n`);
10364
- emit_asm(status, `ldr x0, [x29, #${park}]\n`);
10365
- }
10366
- emit_asm(status, `bl _${helper_name}\n`);
10367
- }
10368
- /**
10369
- * Build a `.detach()` launch on a Thread construction — the daemon form
10370
- * (ASYNC.md, "Daemon tasks"), aarch64 backend. The companion helper
10371
- * releases the construction's unused future (detaching the closure from
10372
- * it — the daemon runner owns the closure), hands the task to the detached
10373
- * launcher, and marks the instance started; the assembly calls it.
10374
- */
10375
- function build_thread_detach$1(access_func, target, status) {
10376
- ensure_concurrency_runtime_a64(status);
10377
- const id = status.spawn_counter ?? 0;
10378
- status.spawn_counter = id + 1;
10379
- const helper_name = `nomen_detach_${id}_start`;
10380
- const mono_thread = mono_type_name("Thread", [spawn_result_type_arg$5(access_func.function_return_type)]);
10381
- const target_is_temp = target.node_type === "func_call" && !!(target.is_thread_ctor || target.is_fiber_ctor);
10382
- let c = `// --- thread detach site ${id} ---\n`;
10383
- c += `void *${helper_name}(void *self) {\n`;
10384
- c += `\tstruct ${mono_thread} *t = (struct ${mono_thread} *)self;\n`;
10385
- c += `\tstruct nomen_future *unused = (struct nomen_future *)t->future;\n`;
10386
- c += `\tif (unused) {\n`;
10387
- c += `\t\tunused->owner_args = NULL;\n`;
10388
- c += `\t\t__nomen_future_release(unused);\n`;
10389
- c += `\t}\n`;
10390
- c += `\t__nomen_task_detach((struct nomen_closure *)t->task);\n`;
10391
- c += `\tt->task = 0;\n`;
10392
- c += `\tt->future = 0;\n`;
10393
- c += `\tt->result_slot = 0;\n`;
10394
- c += `\tt->cancel_flag = 0;\n`;
10395
- c += `\tt->started = 1;\n`;
10396
- if (target_is_temp) c += `\tfree(t);\n`;
10397
- c += `\treturn (void *)0;\n`;
10398
- c += `}\n`;
10399
- if (!status.file_scope_c) status.file_scope_c = "";
10400
- status.file_scope_c += c;
10401
- emit_asm(status, `// thread detach site ${id}\n`);
10402
- build_node(target, status);
10403
- ensure_newline(status);
10404
- emit_asm(status, `bl _${helper_name}\n`);
10405
- }
10406
- /** The launch's T as a Type: the stamped wrapped-call return type, with
10407
- * void coerced to uint64 (Task's result-slot convention). */
10408
- function spawn_result_type_arg$5(return_type) {
10409
- return return_type?.name && return_type.name !== "void" && return_type.name !== "?" ? new Type(return_type.name) : new Type("uint64");
10410
- }
10411
- /**
10412
10245
  * Whether a spawn argument rides the fat-string (ptr, len) pair ABI: its
10413
10246
  * static type names `string`, or it is a string literal (whose ValueNode.type
10414
10247
  * may be unset). Mirrors arg_is_string in build_function_call_node.
@@ -10419,6 +10252,35 @@ function spawn_arg_is_string(node) {
10419
10252
  const t = type_from_value_node$1(node);
10420
10253
  return t?.name === "string" && !t.is_view && !t.is_array;
10421
10254
  }
10255
+ /**
10256
+ * Aarch64 backend: pull the concurrency runtime in
10257
+ * when this struct method's raw bodies reference it — keyed on body
10258
+ * content, never on a type name. See the C backend's
10259
+ * ensure_runtime_for_method.
10260
+ */
10261
+ function ensure_runtime_for_method_a64(func, status) {
10262
+ for (const stmt of func.statements ?? []) if (raw_bodies_reference_runtime$1(stmt)) {
10263
+ ensure_concurrency_runtime_a64(status);
10264
+ return;
10265
+ }
10266
+ }
10267
+ function raw_bodies_reference_runtime$1(node) {
10268
+ if (!node || typeof node !== "object") return false;
10269
+ if (node.node_type === "raw") {
10270
+ const raw = node.value;
10271
+ return typeof raw === "string" && raw.includes("__nomen_");
10272
+ }
10273
+ for (const key of Object.keys(node)) {
10274
+ if (key === "parent" || key === "scope") continue;
10275
+ const v = node[key];
10276
+ if (Array.isArray(v)) {
10277
+ for (const item of v) if (raw_bodies_reference_runtime$1(item)) return true;
10278
+ } else if (v && typeof v === "object" && "node_type" in v) {
10279
+ if (raw_bodies_reference_runtime$1(v)) return true;
10280
+ }
10281
+ }
10282
+ return false;
10283
+ }
10422
10284
  //#endregion
10423
10285
  //#region ../src/nir/from_ast.ts
10424
10286
  /**
@@ -11054,14 +10916,14 @@ function array_struct_name(type, status) {
11054
10916
  //#region ../src/build_common/scan_force_heap_strings.ts
11055
10917
  function scan_force_heap_strings(statements, structs) {
11056
10918
  const result = /* @__PURE__ */ new Set();
11057
- walk$1(statements, result, structs);
10919
+ walk$2(statements, result, structs);
11058
10920
  return result;
11059
10921
  }
11060
- function walk$1(statements, result, structs) {
10922
+ function walk$2(statements, result, structs) {
11061
10923
  if (!statements) return;
11062
- for (const stmt of statements) visit(stmt, result, structs);
10924
+ for (const stmt of statements) visit$1(stmt, result, structs);
11063
10925
  }
11064
- function visit(node, result, structs) {
10926
+ function visit$1(node, result, structs) {
11065
10927
  if (!node) return;
11066
10928
  switch (node.node_type) {
11067
10929
  case "assign": {
@@ -11070,7 +10932,7 @@ function visit(node, result, structs) {
11070
10932
  if (is_fresh_heap_string(a.right_value)) result.add(a.left_value.value);
11071
10933
  else if (!a.operator && is_owned_string_var_rhs(a.right_value)) result.add(a.left_value.value);
11072
10934
  }
11073
- visit(a.right_value, result, structs);
10935
+ visit$1(a.right_value, result, structs);
11074
10936
  break;
11075
10937
  }
11076
10938
  case "access": {
@@ -11083,30 +10945,37 @@ function visit(node, result, structs) {
11083
10945
  }
11084
10946
  case "while":
11085
10947
  case "for":
11086
- walk$1(node.statements, result, structs);
10948
+ walk$2(node.statements, result, structs);
10949
+ break;
10950
+ case "async_block":
10951
+ walk$2(node.statements, result, structs);
11087
10952
  break;
11088
10953
  case "if": {
11089
10954
  const n = node;
11090
- walk$1(n.if_branch?.statements, result, structs);
11091
- walk$1(n.else_branch?.statements, result, structs);
10955
+ walk$2(n.if_branch?.statements, result, structs);
10956
+ walk$2(n.else_branch?.statements, result, structs);
11092
10957
  break;
11093
10958
  }
11094
10959
  case "switch": {
11095
10960
  const n = node;
11096
- for (const c of n.cases) walk$1(c.branch?.statements, result, structs);
11097
- walk$1(n.else_branch?.statements, result, structs);
10961
+ for (const c of n.cases) walk$2(c.branch?.statements, result, structs);
10962
+ walk$2(n.else_branch?.statements, result, structs);
11098
10963
  break;
11099
10964
  }
11100
10965
  case "match": {
11101
10966
  const m = node;
11102
- for (const c of m.cases ?? []) walk$1(c.branch?.statements, result, structs);
11103
- walk$1(m.else_branch?.statements, result, structs);
10967
+ for (const c of m.cases ?? []) walk$2(c.branch?.statements, result, structs);
10968
+ walk$2(m.else_branch?.statements, result, structs);
11104
10969
  break;
11105
10970
  }
11106
10971
  }
11107
10972
  }
11108
10973
  function is_fresh_heap_string(node) {
11109
10974
  if (!node) return false;
10975
+ if (node.node_type === "access") {
10976
+ const access = node.access;
10977
+ if (access.node_type === "access_func" && access.name === "to_string") return true;
10978
+ }
11110
10979
  if (node.type?.name !== "string") return false;
11111
10980
  if (node.node_type === "op") return true;
11112
10981
  if (node.node_type === "func_call") return true;
@@ -11249,6 +11118,61 @@ function receiver_escapes(func, owner_struct_name, state) {
11249
11118
  return escapes;
11250
11119
  }
11251
11120
  //#endregion
11121
+ //#region ../src/build_common/scan_reassigned_vars.ts
11122
+ /**
11123
+ * Collect the names reassigned anywhere in a function body (any nested
11124
+ * scope). The aarch64 string-concat constant folder (`resolve_string_value`)
11125
+ * resolves a variable operand to its DECLARATION initializer; that is only
11126
+ * sound while the variable still holds it. Once the name is reassigned, the
11127
+ * fold must be suppressed (`var tag = ""; tag = f(); "</" + tag + ">"` folded
11128
+ * to `"</>"` using the stale `""`).
11129
+ */
11130
+ function scan_reassigned_vars(statements) {
11131
+ const result = /* @__PURE__ */ new Set();
11132
+ walk$1(statements, result);
11133
+ return result;
11134
+ }
11135
+ function walk$1(statements, result) {
11136
+ if (!statements) return;
11137
+ for (const stmt of statements) visit(stmt, result);
11138
+ }
11139
+ function visit(node, result) {
11140
+ if (!node) return;
11141
+ switch (node.node_type) {
11142
+ case "assign": {
11143
+ const a = node;
11144
+ if (a.left_value.node_type === "value") {
11145
+ const name = a.left_value.value;
11146
+ if (typeof name === "string") result.add(name);
11147
+ } else if (a.left_value.node_type === "access") a.left_value;
11148
+ visit(a.right_value, result);
11149
+ break;
11150
+ }
11151
+ case "while":
11152
+ case "for":
11153
+ walk$1(node.statements, result);
11154
+ break;
11155
+ case "if": {
11156
+ const n = node;
11157
+ walk$1(n.if_branch?.statements, result);
11158
+ walk$1(n.else_branch?.statements, result);
11159
+ break;
11160
+ }
11161
+ case "switch": {
11162
+ const n = node;
11163
+ for (const c of n.cases) walk$1(c.branch?.statements, result);
11164
+ walk$1(n.else_branch?.statements, result);
11165
+ break;
11166
+ }
11167
+ case "match": {
11168
+ const m = node;
11169
+ for (const c of m.cases ?? []) walk$1(c.branch?.statements, result);
11170
+ walk$1(m.else_branch?.statements, result);
11171
+ break;
11172
+ }
11173
+ }
11174
+ }
11175
+ //#endregion
11252
11176
  //#region ../src/build_aarch64/build_extern.ts
11253
11177
  /**
11254
11178
  * Emit an `extern func` for the aarch64 backend: a fixed-shape adapter that
@@ -11371,6 +11295,19 @@ function raw_reload_lines_for(code, plan, current_length) {
11371
11295
  return out;
11372
11296
  }
11373
11297
  //#endregion
11298
+ //#region ../src/build_aarch64/utils/aarch64_size.ts
11299
+ /**
11300
+ * Storage width of a value of `type` in bytes, derived from the
11301
+ * built-in type table (see BuiltInTypeInfo.bytes). Non-built-in names are
11302
+ * structs/enums/generics — 8 unless overridden by struct layout. `string`
11303
+ * is the fat 16-byte { char* ptr; long len; } pair; in registers it rides
11304
+ * as a consecutive (ptr, len) pair — the same ABI `view T` uses.
11305
+ */
11306
+ function aarch64_size(type) {
11307
+ if (type === "string") return 16;
11308
+ return get_built_in_type(type)?.bytes ?? 8;
11309
+ }
11310
+ //#endregion
11374
11311
  //#region ../src/build_aarch64/utils/struct_layout.ts
11375
11312
  const VT_SIZE = 8;
11376
11313
  /**
@@ -12595,6 +12532,173 @@ function cset_flag_is_write_only(ctx, name) {
12595
12532
  return ctx.write_only?.has(name) ?? false;
12596
12533
  }
12597
12534
  //#endregion
12535
+ //#region ../src/build_aarch64/utils/stack_var.ts
12536
+ function allocate_stack_space(status, size, alignment = 8) {
12537
+ if (!status.stack_size) status.stack_size = 0;
12538
+ const aligned_size = Math.ceil(status.stack_size / alignment) * alignment;
12539
+ status.stack_size = aligned_size + size;
12540
+ return aligned_size;
12541
+ }
12542
+ /**
12543
+ * Load a stack slot into a promoted (callee-saved) register with the slot's
12544
+ * width. A sub-word slot (bool/char/int8 = strb, int16 = strh, int32 = str w)
12545
+ * must be loaded with the matching zero-extending ldrb/ldrh/ldr w — a
12546
+ * full-width `ldr` picks up dirty stack bytes above the slot and a bool can
12547
+ * read as true regardless of its stored value.
12548
+ */
12549
+ function emit_promoted_load(status, reg, offset, type_name) {
12550
+ const size = aarch64_size(type_name);
12551
+ if (size === 1) emit_asm(status, `ldrb ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
12552
+ else if (size === 2) emit_asm(status, `ldrh ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
12553
+ else if (size === 4) emit_asm(status, `ldr ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
12554
+ else emit_asm(status, `ldr ${reg}, [x29, #${offset}]\n`);
12555
+ }
12556
+ /**
12557
+ * Write a promoted (callee-saved) register back to its stack slot with the
12558
+ * slot's width. A full-width `str` into a sub-word slot clobbers the adjacent
12559
+ * stack bytes — whatever variable lives there gets corrupted.
12560
+ */
12561
+ function emit_promoted_store(status, reg, offset, type_name) {
12562
+ const size = aarch64_size(type_name);
12563
+ if (size === 1) emit_asm(status, `strb ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
12564
+ else if (size === 2) emit_asm(status, `strh ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
12565
+ else if (size === 4) emit_asm(status, `str ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
12566
+ else emit_asm(status, `str ${reg}, [x29, #${offset}]\n`);
12567
+ }
12568
+ function is_local_ref_var(name, status) {
12569
+ return !!status.function_ref_params?.has(name) && !status.function_param_regs?.has(name);
12570
+ }
12571
+ function emit_var_address(status, reg, name) {
12572
+ if (status.closure_env_offsets?.has(name) && status.closure_env_slot !== void 0) {
12573
+ emit_asm(status, `ldr ${reg}, [x29, #${status.closure_env_slot}]\n`);
12574
+ const off = status.closure_env_offsets.get(name);
12575
+ if (off !== 0) emit_asm(status, `add ${reg}, ${reg}, #${off}\n`);
12576
+ return;
12577
+ }
12578
+ const alloc_reg = status.register_allocations?.get(name);
12579
+ if (alloc_reg) {
12580
+ const offset = status.stack_offsets?.get(name);
12581
+ if (offset !== void 0) {
12582
+ const type_name = status.scoped_declarations.find((d) => d.name === name)?.type?.name || status.variable_types?.get(name)?.name || "";
12583
+ if (type_name) emit_promoted_store(status, alloc_reg, offset, type_name);
12584
+ else emit_asm(status, `str ${alloc_reg}, [x29, #${offset}]\n`);
12585
+ }
12586
+ }
12587
+ const offset = status.stack_offsets?.get(name);
12588
+ if (offset !== void 0) {
12589
+ if (status.function_struct_param_slots?.has(name)) emit_asm(status, `ldr ${reg}, [x29, #${offset}]\n`);
12590
+ else emit_asm(status, `add ${reg}, x29, #${offset}\n`);
12591
+ } else {
12592
+ const param_reg = status.function_param_regs?.get(name);
12593
+ if (param_reg) emit_asm(status, `mov ${reg}, ${param_reg}\n`);
12594
+ else emit_asm(status, `adr ${reg}, ${name}\n`);
12595
+ }
12596
+ }
12597
+ function emit_deref_var_address(status, reg, name) {
12598
+ emit_var_address(status, reg, name);
12599
+ if (is_local_ref_var(name, status)) emit_asm(status, `ldr ${reg}, [${reg}]\n`);
12600
+ }
12601
+ function emit_var_load(status, reg, name, size) {
12602
+ if (name === "null") {
12603
+ emit_asm(status, `mov ${reg}, #0\n`);
12604
+ return;
12605
+ }
12606
+ if (status.closure_env_offsets?.has(name) && status.closure_env_slot !== void 0) {
12607
+ const off = status.closure_env_offsets.get(name);
12608
+ emit_asm(status, `ldr ${reg}, [x29, #${status.closure_env_slot}]\n`);
12609
+ if (size === 16 && reg.startsWith("x")) {
12610
+ emit_asm(status, `ldr x${parseInt(reg.substring(1), 10) + 1}, [${reg}, #${off + 8}]\n`);
12611
+ emit_asm(status, `ldr ${reg}, [${reg}, #${off}]\n`);
12612
+ } else if (size === 1) emit_asm(status, `ldrb ${reg.replace("x", "w")}, [${reg}, #${off}]\n`);
12613
+ else if (size === 2) emit_asm(status, `ldrh ${reg.replace("x", "w")}, [${reg}, #${off}]\n`);
12614
+ else if (size === 4) emit_asm(status, `ldr ${reg.replace("x", "w")}, [${reg}, #${off}]\n`);
12615
+ else emit_asm(status, `ldr ${reg}, [${reg}, #${off}]\n`);
12616
+ return;
12617
+ }
12618
+ const alloc_reg = status.register_allocations?.get(name);
12619
+ if (alloc_reg) {
12620
+ if (alloc_reg.startsWith("d") && reg.startsWith("d")) {
12621
+ if (reg !== alloc_reg) emit_asm(status, `fmov ${reg}, ${alloc_reg}\n`);
12622
+ } else if (alloc_reg.startsWith("d")) emit_asm(status, `fmov ${reg}, ${alloc_reg}\n`);
12623
+ else if (reg !== alloc_reg) emit_asm(status, `mov ${reg}, ${alloc_reg}\n`);
12624
+ return;
12625
+ }
12626
+ const offset = status.stack_offsets?.get(name);
12627
+ if (offset !== void 0) {
12628
+ if (size === 16 && reg.startsWith("x")) {
12629
+ const n = parseInt(reg.substring(1), 10);
12630
+ if (offset + 8 > 504) {
12631
+ emit_asm(status, `ldr ${reg}, [x29, #${offset}]\n`);
12632
+ emit_asm(status, `ldr x${n + 1}, [x29, #${offset + 8}]\n`);
12633
+ } else emit_asm(status, `ldp ${reg}, x${n + 1}, [x29, #${offset}]\n`);
12634
+ return;
12635
+ }
12636
+ if (size === 1) emit_asm(status, `ldrb ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
12637
+ else if (size === 2) emit_asm(status, `ldrh ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
12638
+ else if (size === 4) emit_asm(status, `ldr ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
12639
+ else emit_asm(status, `ldr ${reg}, [x29, #${offset}]\n`);
12640
+ } else {
12641
+ const param_reg = status.function_param_regs?.get(name);
12642
+ if (param_reg) {
12643
+ if (reg !== param_reg) emit_asm(status, `mov ${reg}, ${param_reg}\n`);
12644
+ } else {
12645
+ emit_asm(status, `adr ${reg}, ${name}\n`);
12646
+ if (size === 1) emit_asm(status, `ldrb ${reg.replace("x", "w")}, [${reg}]\n`);
12647
+ else if (size === 2) emit_asm(status, `ldrh ${reg.replace("x", "w")}, [${reg}]\n`);
12648
+ else if (size === 4) emit_asm(status, `ldr ${reg.replace("x", "w")}, [${reg}]\n`);
12649
+ else emit_asm(status, `ldr ${reg}, [${reg}]\n`);
12650
+ }
12651
+ }
12652
+ }
12653
+ function emit_var_store(status, reg, name, size) {
12654
+ if (status.closure_env_offsets?.has(name) && status.closure_env_slot !== void 0) {
12655
+ const off = status.closure_env_offsets.get(name);
12656
+ emit_asm(status, `ldr x9, [x29, #${status.closure_env_slot}]\n`);
12657
+ if (size === 16 && reg.startsWith("x")) {
12658
+ const n = parseInt(reg.substring(1), 10);
12659
+ emit_asm(status, `str ${reg}, [x9, #${off}]\n`);
12660
+ emit_asm(status, `str x${n + 1}, [x9, #${off + 8}]\n`);
12661
+ } else if (size === 1) emit_asm(status, `strb ${reg.replace("x", "w")}, [x9, #${off}]\n`);
12662
+ else if (size === 2) emit_asm(status, `strh ${reg.replace("x", "w")}, [x9, #${off}]\n`);
12663
+ else if (size === 4) emit_asm(status, `str ${reg.replace("x", "w")}, [x9, #${off}]\n`);
12664
+ else emit_asm(status, `str ${reg}, [x9, #${off}]\n`);
12665
+ return;
12666
+ }
12667
+ const alloc_reg = status.register_allocations?.get(name);
12668
+ if (alloc_reg) {
12669
+ if (alloc_reg.startsWith("d") && reg.startsWith("d")) {
12670
+ if (reg !== alloc_reg) emit_asm(status, `fmov ${alloc_reg}, ${reg}\n`);
12671
+ } else if (alloc_reg.startsWith("d")) emit_asm(status, `fmov ${alloc_reg}, ${reg}\n`);
12672
+ else if (reg !== alloc_reg) emit_asm(status, `mov ${alloc_reg}, ${reg}\n`);
12673
+ return;
12674
+ }
12675
+ const offset = status.stack_offsets?.get(name);
12676
+ if (offset !== void 0) {
12677
+ if (size === 16 && reg.startsWith("x")) {
12678
+ const n = parseInt(reg.substring(1), 10);
12679
+ if (offset + 8 > 504) {
12680
+ emit_asm(status, `str ${reg}, [x29, #${offset}]\n`);
12681
+ emit_asm(status, `str x${n + 1}, [x29, #${offset + 8}]\n`);
12682
+ } else emit_asm(status, `stp ${reg}, x${n + 1}, [x29, #${offset}]\n`);
12683
+ return;
12684
+ }
12685
+ if (size === 1) emit_asm(status, `strb ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
12686
+ else if (size === 2) emit_asm(status, `strh ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
12687
+ else if (size === 4) emit_asm(status, `str ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
12688
+ else emit_asm(status, `str ${reg}, [x29, #${offset}]\n`);
12689
+ } else {
12690
+ const param_reg = status.function_param_regs?.get(name);
12691
+ if (param_reg) emit_asm(status, `mov ${param_reg}, ${reg}\n`);
12692
+ else {
12693
+ emit_asm(status, `adr x1, ${name}\n`);
12694
+ if (size === 1) emit_asm(status, `strb ${reg.replace("x", "w")}, [x1]\n`);
12695
+ else if (size === 2) emit_asm(status, `strh ${reg.replace("x", "w")}, [x1]\n`);
12696
+ else if (size === 4) emit_asm(status, `str ${reg.replace("x", "w")}, [x1]\n`);
12697
+ else emit_asm(status, `str ${reg}, [x1]\n`);
12698
+ }
12699
+ }
12700
+ }
12701
+ //#endregion
12598
12702
  //#region ../src/build_aarch64/slp_pair.ts
12599
12703
  /**
12600
12704
  * Field-pair SLP (ASM_PLAN_4 remaining step 1 — the PLAN_3 D-step-2
@@ -13641,14 +13745,32 @@ function emit_destroy_for_anchor_slot(status, offset, type_name, type_args, is_n
13641
13745
  emit_field_destroys_from_slot(status, struct_type, offset);
13642
13746
  if (skip_label) emit_asm(status, `${skip_label}:\n`);
13643
13747
  }
13748
+ /**
13749
+ * Load the address of the sub-object at accumulated offset `acc` from the
13750
+ * object rooted at frame slot `root_slot` into x0. A CLASS root is a pointer
13751
+ * stored in the slot; a VALUE-struct root is inline AT the slot. Nested
13752
+ * value-struct fields keep accumulating offsets from that single base — they
13753
+ * are never re-addressed as if each nested field had its own frame slot. (The
13754
+ * old bug loaded `[x29, root_slot + field_offset]` for a heap class's inline
13755
+ * `List` field, feeding a garbage buffer header to the destroy loop.)
13756
+ */
13757
+ function emit_slot_base(status, root_slot, root_is_class, acc) {
13758
+ if (root_is_class) emit_asm(status, `ldr x0, [x29, #${root_slot}]\n`);
13759
+ else emit_asm(status, `add x0, x29, #${root_slot}\n`);
13760
+ if (acc !== 0) emit_asm(status, `add x0, x0, #${acc}\n`);
13761
+ }
13644
13762
  function emit_field_destroys_from_slot(status, struct_type, base_offset) {
13763
+ emit_field_destroys_at(status, struct_type, base_offset, 0, !!struct_type.is_class);
13764
+ }
13765
+ function emit_field_destroys_at(status, struct_type, root_slot, acc, root_is_class) {
13645
13766
  for (const field of struct_type.fields) {
13646
13767
  const offset = get_field_offset_of_fields(struct_type.fields, field.name, status);
13647
13768
  const field_struct = is_struct_type$4(resolve_struct_name(field.type.name, field.type.type_args, status), status) || is_struct_type$4(field.type.name, status);
13769
+ const field_acc = acc + offset;
13648
13770
  if (field_struct) {
13649
13771
  if (field_struct.is_class && !field.type.is_ref) {
13650
- emit_asm(status, `ldr x0, [x29, #${base_offset}]\n`);
13651
- emit_asm(status, `ldr x0, [x0, #${offset}]\n`);
13772
+ emit_slot_base(status, root_slot, root_is_class, field_acc);
13773
+ emit_asm(status, `ldr x0, [x0]\n`);
13652
13774
  emit_asm(status, `str x0, [sp, #-16]!\n`);
13653
13775
  const skip_label = `.Lskip_defer_${status.label_counter = (status.label_counter ?? 0) + 1}`;
13654
13776
  emit_asm(status, `cbz x0, ${skip_label}\n`);
@@ -13657,32 +13779,17 @@ function emit_field_destroys_from_slot(status, struct_type, base_offset) {
13657
13779
  emit_asm(status, `ldr x0, [sp], #16\n`);
13658
13780
  emit_free(status);
13659
13781
  } else if (has_destroy(field_struct)) {
13660
- emit_asm(status, `ldr x0, [x29, #${base_offset}]\n`);
13661
- emit_asm(status, `add x0, x0, #${offset}\n`);
13782
+ emit_slot_base(status, root_slot, root_is_class, field_acc);
13662
13783
  emit_asm(status, `bl ${resolve_struct_name(field_struct.name, field.type.type_args, status)}_destroy\n`);
13663
13784
  }
13664
- emit_nested_field_destroys_from_slot(status, field_struct, base_offset + offset);
13785
+ emit_field_destroys_at(status, field_struct, root_slot, field_acc, root_is_class);
13665
13786
  } else if (field.type.name === "string" && !field.type.is_array && !field.type.is_ref && !field.type.is_view) {
13666
- emit_asm(status, `ldr x0, [x29, #${base_offset}]\n`);
13667
- emit_asm(status, `ldr x0, [x0, #${offset}]\n`);
13787
+ emit_slot_base(status, root_slot, root_is_class, field_acc);
13788
+ emit_asm(status, `ldr x0, [x0]\n`);
13668
13789
  emit_free(status);
13669
13790
  }
13670
13791
  }
13671
13792
  }
13672
- function emit_nested_field_destroys_from_slot(status, struct_type, base_offset) {
13673
- for (const field of struct_type.fields) {
13674
- const offset = get_field_offset_of_fields(struct_type.fields, field.name, status);
13675
- const field_struct = is_struct_type$4(resolve_struct_name(field.type.name, field.type.type_args, status), status) || is_struct_type$4(field.type.name, status);
13676
- if (field_struct) {
13677
- if (has_destroy(field_struct)) {
13678
- emit_asm(status, `ldr x0, [x29, #${base_offset}]\n`);
13679
- emit_asm(status, `add x0, x0, #${offset}\n`);
13680
- emit_asm(status, `bl ${resolve_struct_name(field_struct.name, field.type.type_args, status)}_destroy\n`);
13681
- }
13682
- emit_nested_field_destroys_from_slot(status, field_struct, base_offset + offset);
13683
- }
13684
- }
13685
- }
13686
13793
  /**
13687
13794
  * Free an anchor slot, running its deferred destroy (if any) first.
13688
13795
  */
@@ -18963,6 +19070,7 @@ function build_function_node$1(node, status) {
18963
19070
  if (check_c_fallback(node, void 0, status)) return;
18964
19071
  const old_function_name = status.current_function_name;
18965
19072
  status.current_function_name = emission_label(node);
19073
+ status.function_nursery_depth = status.nursery_stack?.length ?? 0;
18966
19074
  const old_scoped_declarations = status.scoped_declarations;
18967
19075
  status.scoped_declarations = [];
18968
19076
  status.last_result_is_heap = false;
@@ -19133,6 +19241,7 @@ function build_function_node$1(node, status) {
19133
19241
  const old_ref_class_slots = status.ref_class_slots;
19134
19242
  const old_struct_param_slots = status.function_struct_param_slots;
19135
19243
  const old_function_param_types = status.function_param_types;
19244
+ const old_function_local_types = status.function_local_types;
19136
19245
  status.function_param_regs = /* @__PURE__ */ new Map();
19137
19246
  status.function_param_vars = /* @__PURE__ */ new Set();
19138
19247
  status.function_array_params = /* @__PURE__ */ new Set();
@@ -19140,6 +19249,7 @@ function build_function_node$1(node, status) {
19140
19249
  status.ref_class_slots = /* @__PURE__ */ new Map();
19141
19250
  status.function_struct_param_slots = /* @__PURE__ */ new Set();
19142
19251
  status.function_param_types = /* @__PURE__ */ new Map();
19252
+ status.function_local_types = /* @__PURE__ */ new Map();
19143
19253
  const old_variadic_params_aarch64 = status.function_variadic_params;
19144
19254
  const old_view_params = status.function_view_params;
19145
19255
  status.function_variadic_params = /* @__PURE__ */ new Set();
@@ -19387,6 +19497,8 @@ function build_function_node$1(node, status) {
19387
19497
  }
19388
19498
  const old_force_heap = status.force_heap_strings;
19389
19499
  status.force_heap_strings = scan_force_heap_strings(node.statements, status.structs);
19500
+ const old_reassigned_vars = status.reassigned_vars;
19501
+ status.reassigned_vars = scan_reassigned_vars(node.statements);
19390
19502
  const old_buffer_data_cache = status.buffer_data_cache;
19391
19503
  status.buffer_data_cache = void 0;
19392
19504
  const old_array_ptr_cache = status.array_ptr_cache;
@@ -19421,6 +19533,7 @@ function build_function_node$1(node, status) {
19421
19533
  status.buffer_data_cache = old_buffer_data_cache;
19422
19534
  status.array_ptr_cache = old_array_ptr_cache;
19423
19535
  status.force_heap_strings = old_force_heap;
19536
+ status.reassigned_vars = old_reassigned_vars;
19424
19537
  const loop_regs_used = status.callee_saved_regs_used ? [...status.callee_saved_regs_used].sort() : [];
19425
19538
  status.callee_saved_regs_used = old_callee_saved_regs;
19426
19539
  status.register_allocations = old_register_allocations;
@@ -19544,6 +19657,7 @@ function build_function_node$1(node, status) {
19544
19657
  status.ref_class_slots = old_ref_class_slots;
19545
19658
  status.function_struct_param_slots = old_struct_param_slots;
19546
19659
  status.function_param_types = old_function_param_types;
19660
+ status.function_local_types = old_function_local_types;
19547
19661
  status.function_variadic_params = old_variadic_params_aarch64;
19548
19662
  status.function_view_params = old_view_params;
19549
19663
  status.function_return_label = old_return_label;
@@ -19788,10 +19902,7 @@ function resolve_global_const_value(name, status) {
19788
19902
  function build_struct_node$1(node, status) {
19789
19903
  if (node.is_generic) return;
19790
19904
  const is_nested = !!status.function_return_label;
19791
- if (node.name === "Fiber" || node.name === "Thread") {
19792
- ensure_concurrency_runtime_a64(status);
19793
- status.used_fibers = true;
19794
- }
19905
+ for (const func of node.functions) ensure_runtime_for_method_a64(func, status);
19795
19906
  let old_code;
19796
19907
  if (is_nested) {
19797
19908
  old_code = status.code;
@@ -20172,14 +20283,18 @@ function build_init_function(node, status) {
20172
20283
  }
20173
20284
  emit_typed_store(status, "x1", "x19", offset, get_type_size(field.type, status));
20174
20285
  } else if (field.value.node_type === "func_call") {
20175
- if (status.structs.find((s) => s.name === field.type.name && !s.is_simple_type)) {
20286
+ const field_struct = status.structs.find((s) => s.name === field.type.name && !s.is_simple_type);
20287
+ if (field_struct) {
20176
20288
  build_node(field.value, status);
20177
20289
  ensure_newline(status);
20178
- const field_size = get_struct_size(field.type.name, status);
20179
- const words = Math.ceil(field_size / 8);
20180
- for (let w = 0; w < words; w++) {
20181
- emit_asm(status, `ldr x9, [x0, #${w * 8}]\n`);
20182
- emit_asm(status, `str x9, [x19, #${offset + w * 8}]\n`);
20290
+ if (field_struct.is_class) emit_typed_store(status, "x0", "x19", offset, 8);
20291
+ else {
20292
+ const field_size = get_struct_size(field.type.name, status);
20293
+ const words = Math.ceil(field_size / 8);
20294
+ for (let w = 0; w < words; w++) {
20295
+ emit_asm(status, `ldr x9, [x0, #${w * 8}]\n`);
20296
+ emit_asm(status, `str x9, [x19, #${offset + w * 8}]\n`);
20297
+ }
20183
20298
  }
20184
20299
  }
20185
20300
  } else if (field.value.node_type === "func" && field.type.name === "func") {
@@ -20390,14 +20505,18 @@ function build_custom_init_function(node, func, status) {
20390
20505
  }
20391
20506
  emit_typed_store(status, "x1", "x19", offset, get_type_size(field.type, status));
20392
20507
  } else if (field.value.node_type === "func_call") {
20393
- if (status.structs.find((s) => s.name === field.type.name && !s.is_simple_type)) {
20508
+ const field_struct = status.structs.find((s) => s.name === field.type.name && !s.is_simple_type);
20509
+ if (field_struct) {
20394
20510
  build_node(field.value, status);
20395
20511
  ensure_newline(status);
20396
- const field_size = get_struct_size(field.type.name, status);
20397
- const words = Math.ceil(field_size / 8);
20398
- for (let w = 0; w < words; w++) {
20399
- emit_asm(status, `ldr x9, [x0, #${w * 8}]\n`);
20400
- emit_asm(status, `str x9, [x19, #${offset + w * 8}]\n`);
20512
+ if (field_struct.is_class) emit_typed_store(status, "x0", "x19", offset, 8);
20513
+ else {
20514
+ const field_size = get_struct_size(field.type.name, status);
20515
+ const words = Math.ceil(field_size / 8);
20516
+ for (let w = 0; w < words; w++) {
20517
+ emit_asm(status, `ldr x9, [x0, #${w * 8}]\n`);
20518
+ emit_asm(status, `str x9, [x19, #${offset + w * 8}]\n`);
20519
+ }
20401
20520
  }
20402
20521
  }
20403
20522
  }
@@ -20451,6 +20570,7 @@ function build_custom_init_function(node, func, status) {
20451
20570
  }
20452
20571
  function build_struct_functions$1(node, status) {
20453
20572
  for (const func of node.functions) {
20573
+ if (func.name === "#spawn") continue;
20454
20574
  if (func.name === "#init" && !func.has_body) continue;
20455
20575
  if (func.name === "#init" && func.has_body) {
20456
20576
  if (!check_c_fallback(func, node.name, status)) build_custom_init_function(node, func, status);
@@ -20481,6 +20601,7 @@ function build_struct_functions$1(node, status) {
20481
20601
  const old_param_vars = status.function_param_vars;
20482
20602
  const old_return_label = status.function_return_label;
20483
20603
  const old_force_heap = status.force_heap_strings;
20604
+ const old_reassigned_vars = status.reassigned_vars;
20484
20605
  const old_function_name = status.current_function_name;
20485
20606
  const old_register_allocations = status.register_allocations;
20486
20607
  const old_nir_site_allocs = status.nir_site_allocs;
@@ -20572,7 +20693,7 @@ function build_struct_functions$1(node, status) {
20572
20693
  slot_idx += 2;
20573
20694
  continue;
20574
20695
  }
20575
- if ((!!status.structs.find((s) => s.name === param.type.name && !s.is_simple_type) || !!status.enums.find((e) => e.name === param.type.name && e.has_associated_data)) && callee_idx < callee_saved.length) {
20696
+ if ((!!status.structs.find((s) => s.name === param.type.name && !s.is_simple_type) || !!status.enums.find((e) => e.name === param.type.name && e.has_associated_data) || !!status.traits.find((t) => t.name === param.type.name)) && callee_idx < callee_saved.length) {
20576
20697
  const saved_reg = callee_saved[callee_idx++];
20577
20698
  if (saved_reg !== "x19" || !needs_x19) emit_asm(status, `str ${saved_reg}, [sp, #-16]!\n`);
20578
20699
  if (slot_idx < 8) emit_asm(status, `mov ${saved_reg}, ${param_regs[slot_idx]}\n`);
@@ -20650,7 +20771,7 @@ function build_struct_functions$1(node, status) {
20650
20771
  second_slot_idx += 2;
20651
20772
  continue;
20652
20773
  }
20653
- if (!(!!status.structs.find((s) => s.name === param.type.name && !s.is_simple_type) || !!status.enums.find((e) => e.name === param.type.name && e.has_associated_data))) {
20774
+ if (!(!!status.structs.find((s) => s.name === param.type.name && !s.is_simple_type) || !!status.enums.find((e) => e.name === param.type.name && e.has_associated_data) || !!status.traits.find((t) => t.name === param.type.name))) {
20654
20775
  const is_ref = param.type.is_ref;
20655
20776
  const size = is_ref ? 8 : aarch64_size(param.type.name);
20656
20777
  const offset = allocate_stack_space(status, size, is_ref ? 8 : size);
@@ -20735,6 +20856,7 @@ function build_struct_functions$1(node, status) {
20735
20856
  }
20736
20857
  }
20737
20858
  status.force_heap_strings = scan_force_heap_strings(func.statements, status.structs);
20859
+ status.reassigned_vars = scan_reassigned_vars(func.statements);
20738
20860
  status.buffer_data_cache = void 0;
20739
20861
  status.array_ptr_cache = void 0;
20740
20862
  const moved_before = new Set(status.moved ?? []);
@@ -20820,6 +20942,7 @@ function build_struct_functions$1(node, status) {
20820
20942
  status.function_view_params = old_view_params;
20821
20943
  status.function_return_label = old_return_label;
20822
20944
  status.force_heap_strings = old_force_heap;
20945
+ status.reassigned_vars = old_reassigned_vars;
20823
20946
  status.struct_return_buffer = void 0;
20824
20947
  status.function_return_type = void 0;
20825
20948
  status.current_function_name = old_function_name;
@@ -22070,6 +22193,7 @@ function resolve_string_value(node, status) {
22070
22193
  if (node.node_type === "value") {
22071
22194
  const val = node.value;
22072
22195
  if (val.startsWith("\"") && val.endsWith("\"")) return val;
22196
+ if (status.reassigned_vars?.has(val)) return null;
22073
22197
  const decl = status.scoped_declarations.find((d) => d.name === val);
22074
22198
  if (decl && decl.value) return resolve_string_value(decl.value, status);
22075
22199
  }
@@ -22122,6 +22246,10 @@ function nir_array_element(nir, arr, i) {
22122
22246
  return nir?.kind === "call" && nir.node === arr ? nir.facts.args[i] : void 0;
22123
22247
  }
22124
22248
  function build_declaration_node$1(node, status, nir_init, nir_swap) {
22249
+ if (node.name && node.type?.name) {
22250
+ if (!status.function_local_types) status.function_local_types = /* @__PURE__ */ new Map();
22251
+ status.function_local_types.set(node.name, node.type.name);
22252
+ }
22125
22253
  hoist_field_overrides(node.value, build_node, status, "", node.name);
22126
22254
  status.last_result_is_heap = false;
22127
22255
  const prev_heap = status.last_result_is_heap;
@@ -23807,6 +23935,7 @@ function promote_loop_locals(status, scoped_declarations, sources, options) {
23807
23935
  if (!ptype.name || !SCALAR_TYPES.includes(ptype.name) || ptype.is_array || ptype.is_view || ptype.is_ref || ptype.is_nullable) continue;
23808
23936
  type_name = ptype.name;
23809
23937
  }
23938
+ if (status.function_local_types?.get(name) === "string") continue;
23810
23939
  }
23811
23940
  let offset = status.stack_offsets?.get(name);
23812
23941
  if (offset === void 0) {
@@ -24250,18 +24379,24 @@ function build_if_else_node$1(node, status, nir) {
24250
24379
  const pre_cache = status.buffer_data_cache;
24251
24380
  const pre_array_cache = status.array_ptr_cache;
24252
24381
  if (node.else_branch) {
24382
+ const then_frame = enter_scope_frame(status);
24253
24383
  status.buffer_data_cache = new Map(pre_cache);
24254
24384
  status.array_ptr_cache = new Map(pre_array_cache);
24255
24385
  build_block_with_cursor$1(node.if_branch, nir?.then_branch, status);
24386
+ exit_scope_frame(status, then_frame);
24256
24387
  emit_asm(status, `b end_${label}\n`);
24257
24388
  emit_asm(status, `else_${label}:\n`);
24389
+ const else_frame = enter_scope_frame(status);
24258
24390
  status.buffer_data_cache = new Map(pre_cache);
24259
24391
  status.array_ptr_cache = new Map(pre_array_cache);
24260
24392
  build_block_with_cursor$1(node.else_branch, nir?.else_branch, status);
24393
+ exit_scope_frame(status, else_frame);
24261
24394
  } else if (node.if_branch) {
24395
+ const then_frame = enter_scope_frame(status);
24262
24396
  status.buffer_data_cache = new Map(pre_cache);
24263
24397
  status.array_ptr_cache = new Map(pre_array_cache);
24264
24398
  build_block_with_cursor$1(node.if_branch, nir?.then_branch, status);
24399
+ exit_scope_frame(status, then_frame);
24265
24400
  }
24266
24401
  status.buffer_data_cache = pre_cache;
24267
24402
  status.array_ptr_cache = pre_array_cache;
@@ -24483,6 +24618,7 @@ function build_return_node$1(node, status, nir_value) {
24483
24618
  emit_asm(status, `ldr x8, [x29, #${status.return_buffer_stack_offset}]\n`);
24484
24619
  emit_asm(status, `str xzr, [x8, #${struct_size}]\n`);
24485
24620
  }
24621
+ emit_nursery_joins_on_return_a64(status);
24486
24622
  const finalized = status.moved ?? /* @__PURE__ */ new Set();
24487
24623
  for (const decl of all_scope_frames(status).flat()) {
24488
24624
  if (finalized.has(decl.name)) {
@@ -24502,6 +24638,7 @@ function build_return_node$1(node, status, nir_value) {
24502
24638
  emit_asm(status, `mov x0, #0\n`);
24503
24639
  emit_var_store(status, "x0", status.return_assign, size);
24504
24640
  } else if (status.function_return_label) {
24641
+ emit_nursery_joins_on_return_a64(status);
24505
24642
  const finalized = status.moved ?? /* @__PURE__ */ new Set();
24506
24643
  for (const decl of all_scope_frames(status).flat()) {
24507
24644
  if (finalized.has(decl.name)) {
@@ -24620,7 +24757,7 @@ function build_return_node$1(node, status, nir_value) {
24620
24757
  const raw_value = node.value.value;
24621
24758
  const is_str_lit = raw_value.length >= 2 && raw_value.startsWith("\"") && raw_value.endsWith("\"");
24622
24759
  const is_numeric = /^(\+|-)?\d+$/.test(raw_value);
24623
- if (!is_str_lit && !is_numeric && !status.heap_strings?.has(raw_value)) needs_borrow_strdup = true;
24760
+ if (!is_str_lit && !is_numeric && raw_value !== "null" && !status.heap_strings?.has(raw_value)) needs_borrow_strdup = true;
24624
24761
  } else if (is_container_borrow_access(node.value)) needs_borrow_strdup = true;
24625
24762
  else if (node.value.node_type === "access" && node.value.access.node_type === "access_field") needs_borrow_strdup = true;
24626
24763
  else if ((node.value.node_type === "match" || node.value.node_type === "switch") && !return_join_owned_string) needs_borrow_strdup = true;
@@ -24731,6 +24868,7 @@ function build_return_node$1(node, status, nir_value) {
24731
24868
  if (returns_fat_pair) emit_asm(status, `stp x0, x1, [sp, #-16]!\n`);
24732
24869
  else if (returns_float) emit_asm(status, `str d0, [sp, #-16]!\n`);
24733
24870
  else emit_asm(status, `str x0, [sp, #-16]!\n`);
24871
+ emit_nursery_joins_on_return_a64(status);
24734
24872
  for (const decl of all_scope_frames(status).flat()) {
24735
24873
  if (finalized.has(decl.name)) {
24736
24874
  release_heap_string_fields(status, decl.name, decl.type.name);
@@ -26485,7 +26623,7 @@ function emit_stmt_dispatch(child, index, statements, status) {
26485
26623
  const eval_node = nstmt.expr.node;
26486
26624
  if (eval_node.node_type === "access") {
26487
26625
  const inner = eval_node.access;
26488
- if (inner.node_type === "access_func" && (inner.is_nursery_spawn || inner.is_thread_start || inner.is_thread_detach || inner.is_fiber_start)) inner.is_statement = true;
26626
+ if (inner.node_type === "access_func" && inner.is_nursery_spawn) inner.is_statement = true;
26489
26627
  }
26490
26628
  const restore_eval = apply_forward_use(ctx.use_sites, nstmt.expr.node);
26491
26629
  try {
@@ -26891,7 +27029,7 @@ function emit_expr_from_nir$1(expr, status) {
26891
27029
  function build_async_block_node$1(node, status, nir) {
26892
27030
  const id = status.spawn_counter ?? 0;
26893
27031
  status.spawn_counter = id + 1;
26894
- if (node.mode === "race") ensure_concurrency_runtime_a64(status);
27032
+ ensure_concurrency_runtime_a64(status);
26895
27033
  const futures_off = allocate_stack_space(status, 8, 8);
26896
27034
  const count_off = allocate_stack_space(status, 8, 8);
26897
27035
  const cap_off = allocate_stack_space(status, 8, 8);
@@ -26913,28 +27051,6 @@ function build_async_block_node$1(node, status, nir) {
26913
27051
  let deadline_off;
26914
27052
  if (node.timeout) {
26915
27053
  deadline_off = allocate_stack_space(status, 8, 8);
26916
- emit_asm(status, `mov x0, #-1\n`);
26917
- emit_asm(status, `str x0, [x29, #${deadline_off}]\n`);
26918
- }
26919
- if (!status.nursery_offsets) status.nursery_offsets = /* @__PURE__ */ new Map();
26920
- status.nursery_offsets.set(id, {
26921
- futures_off,
26922
- count_off,
26923
- cap_off,
26924
- deadline_off
26925
- });
26926
- status.nursery_stack ??= [];
26927
- status.nursery_stack.push(id);
26928
- const old_scoped_declarations = status.scoped_declarations;
26929
- status.scoped_declarations = [];
26930
- build_block_with_cursor$1(node, nir?.body, status);
26931
- status.scoped_declarations = old_scoped_declarations;
26932
- status.nursery_stack.pop();
26933
- status.nursery_offsets.delete(id);
26934
- emit_asm(status, `// nursery ${id}: join all futures\n`);
26935
- emit_asm(status, `ldr x20, [x29, #${futures_off}]\n`);
26936
- emit_asm(status, `ldr w22, [x29, #${count_off}]\n`);
26937
- if (deadline_off !== void 0) {
26938
27054
  emit_asm(status, `// Compute deadline: now + timeout_ms\n`);
26939
27055
  build_node(node.timeout, status);
26940
27056
  ensure_newline(status);
@@ -26955,11 +27071,57 @@ function build_async_block_node$1(node, status, nir) {
26955
27071
  emit_asm(status, `add x0, x1, x0\n`);
26956
27072
  emit_asm(status, `str x0, [x29, #${deadline_off}]\n`);
26957
27073
  }
26958
- emit_asm(status, `mov x23, #0\n`);
26959
- const loop_start = `__nursery_${id}_join_start`;
26960
- const loop_end = `__nursery_${id}_join_end`;
26961
- const loop_release = `__nursery_${id}_release`;
26962
- const is_race = node.mode === "race";
27074
+ if (!status.nursery_offsets) status.nursery_offsets = /* @__PURE__ */ new Map();
27075
+ status.nursery_offsets.set(id, {
27076
+ futures_off,
27077
+ count_off,
27078
+ cap_off,
27079
+ deadline_off
27080
+ });
27081
+ if (!status.nursery_meta) status.nursery_meta = /* @__PURE__ */ new Map();
27082
+ status.nursery_meta.set(id, {
27083
+ mode: node.mode ?? "all",
27084
+ has_deadline: !!node.timeout
27085
+ });
27086
+ status.nursery_stack ??= [];
27087
+ status.nursery_stack.push(id);
27088
+ const old_scoped_declarations = status.scoped_declarations;
27089
+ status.scoped_declarations = [];
27090
+ build_block_with_cursor$1(node, nir?.body, status);
27091
+ status.scoped_declarations = old_scoped_declarations;
27092
+ status.nursery_stack.pop();
27093
+ emit_asm(status, `// nursery ${id}: join all futures\n`);
27094
+ emit_nursery_join_a64(status, id, `__nursery_${id}`);
27095
+ status.nursery_offsets.delete(id);
27096
+ status.nursery_meta?.delete(id);
27097
+ }
27098
+ /**
27099
+ * Emit one nursery's join sequence in assembly: the race-mode wait (if any),
27100
+ * the wait+release loop over every registered future, and the futures-list
27101
+ * free. Shared by the block exit and the RETURN path — a `return` inside an
27102
+ * `async { }` block must route through the join (the structured-concurrency
27103
+ * contract). The nursery's per-invocation state lives at this function's
27104
+ * stack frame (offsets from x29), so a return site inside the block reaches
27105
+ * the same state. `label_prefix` must be unique per emission: the block exit
27106
+ * uses `__nursery_<id>`, a return site `__nursery_<id>_retjoin<N>` (the
27107
+ * block-exit labels would collide with a second loop in the same function).
27108
+ * Clobbers x0-x3, x20, x22, x23 (backend scratch — nothing live across a
27109
+ * join).
27110
+ */
27111
+ function emit_nursery_join_a64(status, id, label_prefix) {
27112
+ const offsets = status.nursery_offsets?.get(id);
27113
+ if (!offsets) return;
27114
+ const { futures_off, count_off, deadline_off } = offsets;
27115
+ const is_race = (status.nursery_meta?.get(id) ?? {
27116
+ mode: "all",
27117
+ has_deadline: false
27118
+ }).mode === "race";
27119
+ const loop_start = `${label_prefix}_join_start`;
27120
+ const loop_end = `${label_prefix}_join_end`;
27121
+ const loop_release = `${label_prefix}_release`;
27122
+ const no_list = `${label_prefix}_no_list`;
27123
+ emit_asm(status, `ldr x20, [x29, #${futures_off}]\n`);
27124
+ emit_asm(status, `ldr w22, [x29, #${count_off}]\n`);
26963
27125
  if (is_race) {
26964
27126
  emit_asm(status, `mov x0, x20\n`);
26965
27127
  emit_asm(status, `mov x1, x22\n`);
@@ -26967,6 +27129,7 @@ function build_async_block_node$1(node, status, nir) {
26967
27129
  else emit_asm(status, `mov x2, #0\n`);
26968
27130
  emit_asm(status, `bl ___nomen_nursery_race_wait\n`);
26969
27131
  }
27132
+ emit_asm(status, `mov x23, xzr\n`);
26970
27133
  emit_asm(status, `${loop_start}:\n`);
26971
27134
  emit_asm(status, `cmp x23, x22\n`);
26972
27135
  emit_asm(status, `b.ge ${loop_end}\n`);
@@ -26991,9 +27154,28 @@ function build_async_block_node$1(node, status, nir) {
26991
27154
  emit_asm(status, `b ${loop_start}\n`);
26992
27155
  emit_asm(status, `${loop_end}:\n`);
26993
27156
  emit_asm(status, `ldr x0, [x29, #${futures_off}]\n`);
26994
- emit_asm(status, `cbz x0, __nursery_${id}_no_list\n`);
27157
+ emit_asm(status, `cbz x0, ${no_list}\n`);
26995
27158
  emit_asm(status, `bl _free\n`);
26996
- emit_asm(status, `__nursery_${id}_no_list:\n`);
27159
+ emit_asm(status, `${no_list}:\n`);
27160
+ }
27161
+ let retjoin_counter = 0;
27162
+ /**
27163
+ * Emit the join sequence for every nursery the current `return` must route
27164
+ * through — innermost first. Only nurseries belonging to the CURRENT
27165
+ * function (stack depth >= the function's entry depth) apply: a lambda
27166
+ * defined inside an `async { }` block inherits the block's id on
27167
+ * nursery_stack (for lexical spawn captures), but its `return` exits the
27168
+ * lambda, whose frame has no nursery state. The return value must already
27169
+ * be parked (the join's `bl`s clobber x0/x1).
27170
+ */
27171
+ function emit_nursery_joins_on_return_a64(status) {
27172
+ if (!status.nursery_stack?.length) return;
27173
+ const base = status.function_nursery_depth ?? 0;
27174
+ for (let i = status.nursery_stack.length - 1; i >= base; i--) {
27175
+ const id = status.nursery_stack[i];
27176
+ emit_asm(status, `// return inside async block: join nursery ${id} before leaving\n`);
27177
+ emit_nursery_join_a64(status, id, `__nursery_${id}_retjoin${retjoin_counter++}`);
27178
+ }
26997
27179
  }
26998
27180
  //#endregion
26999
27181
  //#region ../src/build_aarch64/build_break_node.ts
@@ -27482,6 +27664,7 @@ function build_inline_method(struct_node, func, status) {
27482
27664
  const old_param_vars = status.function_param_vars;
27483
27665
  status.stack_offsets = /* @__PURE__ */ new Map();
27484
27666
  const old_param_types = status.function_param_types;
27667
+ const old_local_types = status.function_local_types;
27485
27668
  const old_return_label = status.function_return_label;
27486
27669
  const old_ref_params = status.function_ref_params;
27487
27670
  const old_struct_return_buffer = status.struct_return_buffer;
@@ -27548,6 +27731,7 @@ function build_inline_method(struct_node, func, status) {
27548
27731
  status.function_ref_params = /* @__PURE__ */ new Set();
27549
27732
  status.function_param_types = /* @__PURE__ */ new Map();
27550
27733
  for (const p of func.params) if (p.type) status.function_param_types.set(p.name, p.type);
27734
+ status.function_local_types = /* @__PURE__ */ new Map();
27551
27735
  if (needs_x19) status.function_param_regs.set("self", "x19");
27552
27736
  if (process.env.AMI_HOMES && func.name === "get") console.log(`HOMES-DBG get: needs_x19=${needs_x19} self_is_var=${self_is_var} code_tail=${JSON.stringify(status.code.slice(-120))}`);
27553
27737
  const saved_stack_slots = [];
@@ -27628,6 +27812,7 @@ function build_inline_method(struct_node, func, status) {
27628
27812
  status.function_param_regs = old_param_regs;
27629
27813
  status.function_param_vars = old_param_vars;
27630
27814
  status.function_param_types = old_param_types;
27815
+ status.function_local_types = old_local_types;
27631
27816
  status.function_ref_params = old_ref_params;
27632
27817
  status.function_return_label = old_return_label;
27633
27818
  status.stack_offsets = old_stack_offsets;
@@ -27658,6 +27843,7 @@ function build_inline_function(func, status) {
27658
27843
  const old_param_vars = status.function_param_vars;
27659
27844
  status.stack_offsets = /* @__PURE__ */ new Map();
27660
27845
  const old_param_types = status.function_param_types;
27846
+ const old_local_types = status.function_local_types;
27661
27847
  const old_return_label = status.function_return_label;
27662
27848
  const old_ref_params = status.function_ref_params;
27663
27849
  const old_struct_return_buffer = status.struct_return_buffer;
@@ -27716,6 +27902,7 @@ function build_inline_function(func, status) {
27716
27902
  status.function_ref_params = /* @__PURE__ */ new Set();
27717
27903
  status.function_param_types = /* @__PURE__ */ new Map();
27718
27904
  for (const p of func.params) if (p.type) status.function_param_types.set(p.name, p.type);
27905
+ status.function_local_types = /* @__PURE__ */ new Map();
27719
27906
  const saved_stack_slots = [];
27720
27907
  const inline_reloads = [];
27721
27908
  const inline_push_slots = [];
@@ -27785,6 +27972,7 @@ function build_inline_function(func, status) {
27785
27972
  status.function_param_regs = old_param_regs;
27786
27973
  status.function_param_vars = old_param_vars;
27787
27974
  status.function_param_types = old_param_types;
27975
+ status.function_local_types = old_local_types;
27788
27976
  status.function_ref_params = old_ref_params;
27789
27977
  status.function_return_label = old_return_label;
27790
27978
  status.stack_offsets = old_stack_offsets;
@@ -28217,7 +28405,7 @@ function build_function_call_node$1(node, status) {
28217
28405
  let arg_is_class = false;
28218
28406
  if (arg_name) {
28219
28407
  const tn = arg.type?.name ?? status.variable_types?.get(arg_name)?.name;
28220
- arg_is_class = !!tn && !!status.structs.find((s) => s.name === tn && s.is_class);
28408
+ arg_is_class = !!tn && (!!status.structs.find((s) => s.name === tn && s.is_class) || !!status.traits.find((t) => t.name === tn));
28221
28409
  }
28222
28410
  const ref_param_slot = arg_name !== void 0 ? status.ref_class_slots?.get(arg_name) : void 0;
28223
28411
  if (ref_param_slot !== void 0) emit_ref_class_param_slot(status, ref_param_slot, arg_name, ref_class_param_reload);
@@ -28286,11 +28474,11 @@ function build_function_call_node$1(node, status) {
28286
28474
  }
28287
28475
  }
28288
28476
  }
28289
- if (is_struct && status.struct_return_buffer) {
28477
+ if (is_struct && is_struct.is_class) emit_asm(status, `ldr x0, [sp]\n`);
28478
+ else if (is_struct && status.struct_return_buffer) {
28290
28479
  if (status.return_buffer_stack_offset !== void 0) emit_asm(status, `ldr x8, [x29, #${status.return_buffer_stack_offset}]\n`);
28291
28480
  emit_asm(status, `mov x0, ${status.struct_return_buffer}\n`);
28292
- } else if (is_struct && is_struct.is_class) emit_asm(status, `ldr x0, [sp]\n`);
28293
- else if (is_struct && !is_struct.is_class) {
28481
+ } else if (is_struct && !is_struct.is_class) {
28294
28482
  const temp_addr = `_temp_${temp_counter - 1}`;
28295
28483
  emit_asm(status, `add x0, x29, #${status.stack_offsets.get(temp_addr)}\n`);
28296
28484
  } else if (!is_struct && node.type?.name && !status.call_x8_preset) {
@@ -28352,16 +28540,17 @@ function build_function_call_node$1(node, status) {
28352
28540
  }
28353
28541
  }
28354
28542
  build_swap_params(node, status);
28355
- if (is_struct && !status.struct_return_buffer) {
28356
- if (is_struct.is_class) emit_asm(status, `ldr x0, [sp], #16\n`);
28357
- else {
28358
- const temp_addr = `_temp_${temp_counter - 1}`;
28359
- const offset = status.stack_offsets.get(temp_addr);
28543
+ if (is_struct && is_struct.is_class) emit_asm(status, `ldr x0, [sp], #16\n`);
28544
+ else if (is_struct && status.struct_return_buffer) {
28545
+ if (status.return_buffer_stack_offset !== void 0) emit_asm(status, `ldr x0, [x29, #${status.return_buffer_stack_offset}]\n`);
28546
+ else emit_asm(status, `mov x0, ${status.struct_return_buffer}\n`);
28547
+ } else if (is_struct && !status.struct_return_buffer) {
28548
+ const temp_addr = `_temp_${temp_counter - 1}`;
28549
+ const offset = status.stack_offsets.get(temp_addr);
28550
+ emit_asm(status, `add x0, x29, #${offset}\n`);
28551
+ if (node.field_overrides?.length) {
28552
+ emit_field_overrides(temp_addr, node, build_node, status);
28360
28553
  emit_asm(status, `add x0, x29, #${offset}\n`);
28361
- if (node.field_overrides?.length) {
28362
- emit_field_overrides(temp_addr, node, build_node, status);
28363
- emit_asm(status, `add x0, x29, #${offset}\n`);
28364
- }
28365
28554
  }
28366
28555
  }
28367
28556
  if (node.name.startsWith("_string_interpolate_")) {
@@ -28752,6 +28941,14 @@ struct nomen_future {
28752
28941
  // The fiber whose task this future is (set at fiber spawn). Cancellation
28753
28942
  // uses it to wake a fiber parked on a primitive waitq.
28754
28943
  struct nomen_fiber *owning_fiber;
28944
+ // 1 when the result slot holds a FAT string (set at construction, which
28945
+ // knows the wrapped call's return type). The last release frees an
28946
+ // unconsumed string buffer from the cell: result() zeroes the cell when
28947
+ // it moves the value out, and Task's #destroy zeroes after its own
28948
+ // free, so a non-NULL ptr at the last release means "never consumed".
28949
+ // Appended LAST: raw aarch64 bodies read refs/result_slot/owner_args at
28950
+ // fixed offsets (#116/#128/#136), which must not shift.
28951
+ int slot_fat;
28755
28952
  };
28756
28953
  // Fiber seam (defined in FIBER_HEADER, appended after this block). Declared
28757
28954
  // here — after struct nomen_future is complete — so the prototypes bind to
@@ -28877,6 +29074,19 @@ static void __nomen_future_release(struct nomen_future *f) {
28877
29074
  pthread_mutex_destroy(&f->mu);
28878
29075
  pthread_cond_destroy(&f->cv);
28879
29076
  free(f->cancel_flag);
29077
+ // An unconsumed fat-string result: free the buffer the trampoline
29078
+ // stored (race-mode and fire-and-forget tasks never hand it to a
29079
+ // consumer). Every consumer path zeroes the cell first — result()
29080
+ // on move-out, Task's #destroy after its own free — so a non-NULL
29081
+ // ptr here is unambiguous and the free happens exactly once.
29082
+ if (f->slot_fat && f->result_slot) {
29083
+ unsigned long long *_s = (unsigned long long *)f->result_slot;
29084
+ if (_s[0]) {
29085
+ free((void *)_s[0]);
29086
+ _s[0] = 0;
29087
+ _s[1] = 0;
29088
+ }
29089
+ }
28880
29090
  free(f->result_slot);
28881
29091
  // The task closure is owned by the future: disposing it here (the
28882
29092
  // last release, ordered after every use — trampoline, Task destroy,
@@ -29013,7 +29223,11 @@ static void __nomen_future_cancel(struct nomen_future *f) {
29013
29223
  // schedule() only queues PARKED fibers, so a double wake is harmless.
29014
29224
  if (f->owning_fiber) __nomen_fiber_schedule(f->owning_fiber);
29015
29225
  }
29226
+ // Non-blocking completion peek (Task.is_done): 1 once the future's task has
29227
+ // finished, 0 while it may still be running (and for a NULL — a consumed or
29228
+ // never-launched handle).
29016
29229
  static int __nomen_future_is_done(struct nomen_future *f) {
29230
+ if (!f) return 0;
29017
29231
  pthread_mutex_lock(&f->mu);
29018
29232
  int d = f->done;
29019
29233
  pthread_mutex_unlock(&f->mu);
@@ -29885,95 +30099,6 @@ static int __nomen_deadlock_check_worker(void) {
29885
30099
  }
29886
30100
  `;
29887
30101
  /**
29888
- * Build a `.start()` launch on a `Thread(fn(args))` construction — the
29889
- * surface form of a direct spawn (CLOSURE.md Phase 3b). The
29890
- * receiver is the construction expression itself or any Thread-typed
29891
- * expression (a stored `var t = Thread(...)` binding); its fields carry the
29892
- * task closure packed eagerly at the construction site, plus the result
29893
- * slot, cancel flag, and future. This submits the closure to the worker
29894
- * pool, registers the future with the enclosing nursery (if any), transfers
29895
- * the handles out of the instance (so its #destroy becomes a no-op), and
29896
- * yields a Task<T> handle — or nothing, for a fire-and-forget statement.
29897
- */
29898
- function build_thread_start(access_func, target, status) {
29899
- ensure_concurrency_runtime(status);
29900
- status.spawn_counter = (status.spawn_counter ?? 0) + 1;
29901
- const t_arg = spawn_result_type_arg$4(access_func.function_return_type);
29902
- const mono_thread = mono_type_name("Thread", [t_arg]);
29903
- const mono_task_name = mono_type_name("Task", [t_arg]);
29904
- const nursery_id = status.nursery_stack?.at(-1);
29905
- const fire_and_forget = !!access_func.is_statement;
29906
- const refs = fire_and_forget ? nursery_id !== void 0 ? 2 : 1 : nursery_id !== void 0 ? 3 : 2;
29907
- const target_is_temp = target.node_type === "func_call" && !!(target.is_thread_ctor || target.is_fiber_ctor);
29908
- status.code += `({\n`;
29909
- status.code += `\tstruct ${mono_thread} *_self = `;
29910
- build_node$1(target, status);
29911
- status.code += `;\n`;
29912
- status.code += `\tstruct nomen_future *_future = (struct nomen_future *)_self->future;\n`;
29913
- status.code += `\tvoid *_result_ptr = (void *)_self->result_slot;\n`;
29914
- status.code += `\tunsigned long long *_cancel_ptr = (unsigned long long *)_self->cancel_flag;\n`;
29915
- status.code += `\tstruct nomen_closure *_closure = (struct nomen_closure *)_self->task;\n`;
29916
- status.code += `\t_future->refs = ${refs};\n`;
29917
- status.code += `\t__nomen_pool_submit(_closure);\n`;
29918
- if (nursery_id !== void 0) status.code += `\t__nomen_nursery_track((void **)&__nomen_nursery_${nursery_id}_futures, &__nomen_nursery_${nursery_id}_count, &__nomen_nursery_${nursery_id}_cap, _future);\n`;
29919
- status.code += `\t_self->task = 0;\n`;
29920
- status.code += `\t_self->future = 0;\n`;
29921
- status.code += `\t_self->result_slot = 0;\n`;
29922
- status.code += `\t_self->cancel_flag = 0;\n`;
29923
- status.code += `\t_self->started = 1;\n`;
29924
- if (target_is_temp) status.code += `\tfree(_self);\n`;
29925
- if (fire_and_forget) status.code += `\t(void)0;\n`;
29926
- else {
29927
- status.code += `\tstruct ${mono_task_name} *_task = (struct ${mono_task_name} *)malloc(sizeof(struct ${mono_task_name}));\n`;
29928
- status.code += `\t_task->_vt = &_${mono_task_name}_traits;\n`;
29929
- status.code += `\t_task->handle = 0;\n`;
29930
- status.code += `\t_task->done = 0;\n`;
29931
- status.code += `\t_task->result_slot = (unsigned long long)_result_ptr;\n`;
29932
- status.code += `\t_task->cancel_flag = (unsigned long long)_cancel_ptr;\n`;
29933
- status.code += `\t_task->future = (unsigned long long)_future;\n`;
29934
- status.code += `\t_task;\n`;
29935
- }
29936
- status.code += `})\n`;
29937
- }
29938
- /**
29939
- * Build a `.detach()` launch on a `Thread(fn(args))` construction — the
29940
- * daemon form (ASYNC.md, "Daemon tasks"). The packed task closure runs on a
29941
- * dedicated detached pthread: never a pool worker, never joined, killed by
29942
- * process exit by design. No future is used: the construction allocated one
29943
- * (a Thread always carries the full launch machinery), so detach releases
29944
- * that unused future (first detaching the closure from it — the daemon
29945
- * runner owns the closure now) and marks the instance started.
29946
- */
29947
- function build_thread_detach(access_func, target, status) {
29948
- ensure_concurrency_runtime(status);
29949
- status.spawn_counter = (status.spawn_counter ?? 0) + 1;
29950
- const mono_thread = mono_type_name("Thread", [spawn_result_type_arg$4(access_func.function_return_type)]);
29951
- const target_is_temp = target.node_type === "func_call" && !!(target.is_thread_ctor || target.is_fiber_ctor);
29952
- status.code += `({\n`;
29953
- status.code += `\tstruct ${mono_thread} *_self = `;
29954
- build_node$1(target, status);
29955
- status.code += `;\n`;
29956
- status.code += `\tstruct nomen_future *_unused_future = (struct nomen_future *)_self->future;\n`;
29957
- status.code += `\tif (_unused_future) {\n`;
29958
- status.code += `\t\t_unused_future->owner_args = NULL;\n`;
29959
- status.code += `\t\t__nomen_future_release(_unused_future);\n`;
29960
- status.code += `\t}\n`;
29961
- status.code += `\t__nomen_task_detach((struct nomen_closure *)_self->task);\n`;
29962
- status.code += `\t_self->task = 0;\n`;
29963
- status.code += `\t_self->future = 0;\n`;
29964
- status.code += `\t_self->result_slot = 0;\n`;
29965
- status.code += `\t_self->cancel_flag = 0;\n`;
29966
- status.code += `\t_self->started = 1;\n`;
29967
- if (target_is_temp) status.code += `\tfree(_self);\n`;
29968
- status.code += `\t(void)0;\n`;
29969
- status.code += `})\n`;
29970
- }
29971
- /** The launch's T as a Type: the stamped wrapped-call return type, with
29972
- * void coerced to uint64 (Task's result-slot convention). */
29973
- function spawn_result_type_arg$4(return_type) {
29974
- return return_type?.name && return_type.name !== "void" && return_type.name !== "?" ? new Type(return_type.name) : new Type("uint64");
29975
- }
29976
- /**
29977
30102
  * Resolve each spawn argument's C type. Classes/traits are pointers;
29978
30103
  * primitives and by-value structs use c_type's output directly. Generic
29979
30104
  * instantiations (e.g. Channel<uint64>) use the monomorphized C name
@@ -30058,98 +30183,41 @@ function find_spawn_callee(name, status) {
30058
30183
  visit(status.root);
30059
30184
  return found;
30060
30185
  }
30061
- //#endregion
30062
- //#region ../src/build_c/build_fiber_spawn.ts
30063
30186
  /**
30064
- * Fiber scheduler runtime (ASYNC.md Phase 1), C backend.
30065
- *
30066
- * Appended immediately after POOL_HEADER (which forward-declares the fiber
30067
- * seam, owns `__nomen_current_fiber` — Runtime.current() — and owns the
30068
- * worker loop that drains the fiber queue between pool tasks). The context
30069
- * switch is ucontext-based here: portable across every target the C backend
30070
- * serves. The aarch64 backend's FIBER_HEADER_C is the same scheduler with a
30071
- * naked-asm switch instead.
30072
- *
30073
- * Execution model: a fiber is a heap (or caller-provided) stack plus a
30074
- * context. `__nomen_fiber_spawn` enqueues it; pool workers drain the fiber
30075
- * queue between pool tasks, so a parked fiber frees its worker. In
30076
- * cooperative mode (Fiber.set_cooperative) the spawning thread drains the
30077
- * queue itself — no worker threads are ever started, which is the
30078
- * no-thread/embedded path.
30079
- *
30080
- * Swap protocol: the runner captures its continuation into a stack-local
30081
- * `ret` (its address rides the per-thread `__nomen_fiber_ret` pointer) and
30082
- * switches into `f->ctx`. A suspending fiber saves ITS continuation into
30083
- * `f->ctx` (overwriting the entry state after the first suspension) and
30084
- * switches into `__nomen_fiber_ret`. Resume therefore always continues where
30085
- * the fiber last suspended.
30187
+ * Whether any raw `#arch` body under this node
30188
+ * references the concurrency runtime (`__nomen_*` symbols — pool, futures,
30189
+ * fiber scheduler, nursery tracking, netpoller). The runtime is the
30190
+ * LIBRARY's dependency: a method whose raw body drives it pulls it in,
30191
+ * keyed on body CONTENT — never on a type name.
30086
30192
  */
30193
+ function raw_bodies_reference_runtime(node) {
30194
+ if (!node || typeof node !== "object") return false;
30195
+ if (node.node_type === "raw") {
30196
+ const raw = node.value;
30197
+ return typeof raw === "string" && raw.includes("__nomen_");
30198
+ }
30199
+ for (const key of Object.keys(node)) {
30200
+ if (key === "parent" || key === "scope") continue;
30201
+ const v = node[key];
30202
+ if (Array.isArray(v)) {
30203
+ for (const item of v) if (raw_bodies_reference_runtime(item)) return true;
30204
+ } else if (v && typeof v === "object" && "node_type" in v) {
30205
+ if (raw_bodies_reference_runtime(v)) return true;
30206
+ }
30207
+ }
30208
+ return false;
30209
+ }
30087
30210
  /**
30088
- * Build a `.start()` / `.start_on(buf)` launch on a `Fiber(fn(args))`
30089
- * construction (or a stored Fiber binding) — C backend
30090
- * (CLOSURE.md Phase 3b). The receiver's fields carry the task
30091
- * closure packed eagerly at the construction site, plus the result slot,
30092
- * cancel flag, and future. This launches the closure on the fiber
30093
- * scheduler (a heap stack, or the caller's fixed-size array buffer for
30094
- * start_on — validated at check time, >= 16 KB), registers the future with
30095
- * the enclosing nursery (if any), transfers the handles out of the
30096
- * instance, and yields Task<T> (or nothing, fire-and-forget).
30211
+ * Pull the concurrency runtime in when this struct method's raw bodies
30212
+ * reference it (deduped by the ensure flags). Replaces the old per-type-name
30213
+ * gates (CONCURRENCY_TYPES / the aarch64 Fiber||Thread check): the runtime
30214
+ * dependency now belongs to the library code itself.
30097
30215
  */
30098
- function build_fiber_spawn_node$1(access_func, target, status, start_on) {
30099
- ensure_concurrency_runtime(status);
30100
- status.used_fibers = true;
30101
- const t_arg = spawn_result_type_arg$3(access_func.function_return_type);
30102
- const mono_fiber = mono_type_name("Fiber", [t_arg]);
30103
- const mono_task_name = mono_type_name("Task", [t_arg]);
30104
- const nursery_id = status.nursery_stack?.at(-1);
30105
- const fire_and_forget = !!access_func.is_statement;
30106
- const target_is_temp = target.node_type === "func_call" && !!(target.is_thread_ctor || target.is_fiber_ctor);
30107
- const refs = fire_and_forget ? nursery_id !== void 0 ? 2 : 1 : nursery_id !== void 0 ? 3 : 2;
30108
- status.code += `({\n`;
30109
- status.code += `\tstruct ${mono_fiber} *_self = `;
30110
- build_node$1(target, status);
30111
- status.code += `;\n`;
30112
- status.code += `\tstruct nomen_future *_future = (struct nomen_future *)_self->future;\n`;
30113
- status.code += `\tvoid *_result_ptr = (void *)_self->result_slot;\n`;
30114
- status.code += `\tunsigned long long *_cancel_ptr = (unsigned long long *)_self->cancel_flag;\n`;
30115
- status.code += `\tstruct nomen_closure *_closure = (struct nomen_closure *)_self->task;\n`;
30116
- status.code += `\t_future->refs = ${refs};\n`;
30117
- status.code += `\t__nomen_fiber_spawn${start_on ? "_on" : ""}(_closure, _future`;
30118
- if (start_on) {
30119
- const buf_type = type_from_value_node$1(start_on);
30120
- const elem_size = buf_type.name === "string" ? 16 : 8;
30121
- const len_node = buf_type.length;
30122
- const len = len_node && len_node.node_type === "value" ? parseInt(len_node.value, 10) : NaN;
30123
- const stack_bytes = Number.isNaN(len) ? 0 : len * elem_size;
30124
- status.code += `, (void *)`;
30125
- build_node$1(start_on, status);
30126
- status.code += `, (size_t)${stack_bytes}`;
30127
- }
30128
- status.code += `);\n`;
30129
- if (nursery_id !== void 0) status.code += `\t__nomen_nursery_track((void **)&__nomen_nursery_${nursery_id}_futures, &__nomen_nursery_${nursery_id}_count, &__nomen_nursery_${nursery_id}_cap, _future);\n`;
30130
- status.code += `\t_self->task = 0;\n`;
30131
- status.code += `\t_self->future = 0;\n`;
30132
- status.code += `\t_self->result_slot = 0;\n`;
30133
- status.code += `\t_self->cancel_flag = 0;\n`;
30134
- status.code += `\t_self->started = 1;\n`;
30135
- if (target_is_temp) status.code += `\tfree(_self);\n`;
30136
- if (fire_and_forget) status.code += `\t(void)0;\n`;
30137
- else {
30138
- status.code += `\tstruct ${mono_task_name} *_task = (struct ${mono_task_name} *)malloc(sizeof(struct ${mono_task_name}));\n`;
30139
- status.code += `\t_task->_vt = &_${mono_task_name}_traits;\n`;
30140
- status.code += `\t_task->handle = 0;\n`;
30141
- status.code += `\t_task->done = 0;\n`;
30142
- status.code += `\t_task->result_slot = (unsigned long long)_result_ptr;\n`;
30143
- status.code += `\t_task->cancel_flag = (unsigned long long)_cancel_ptr;\n`;
30144
- status.code += `\t_task->future = (unsigned long long)_future;\n`;
30145
- status.code += `\t_task;\n`;
30216
+ function ensure_runtime_for_method(func, status) {
30217
+ for (const stmt of func.statements ?? []) if (raw_bodies_reference_runtime(stmt)) {
30218
+ ensure_concurrency_runtime(status);
30219
+ return;
30146
30220
  }
30147
- status.code += `})\n`;
30148
- }
30149
- /** The launch's T as a Type: the stamped wrapped-call return type, with
30150
- * void coerced to uint64 (Task's result-slot convention). */
30151
- function spawn_result_type_arg$3(return_type) {
30152
- return return_type?.name && return_type.name !== "void" && return_type.name !== "?" ? new Type(return_type.name) : new Type("uint64");
30153
30221
  }
30154
30222
  //#endregion
30155
30223
  //#region ../src/build_c/build_nursery_spawn.ts
@@ -30174,7 +30242,7 @@ function build_nursery_spawn$1(node, nursery_ptr, status) {
30174
30242
  const fire_and_forget = !!node.is_statement;
30175
30243
  const refs = fire_and_forget ? 2 : 3;
30176
30244
  ensure_concurrency_runtime(status);
30177
- const t_arg = spawn_result_type_arg$2(node.function_return_type);
30245
+ const t_arg = spawn_result_type_arg$1(node.function_return_type);
30178
30246
  const mono_thread = mono_type_name("Thread", [t_arg]);
30179
30247
  const mono_task_name = mono_type_name("Task", [t_arg]);
30180
30248
  const arg = node.params[0];
@@ -30211,7 +30279,7 @@ function build_nursery_spawn$1(node, nursery_ptr, status) {
30211
30279
  }
30212
30280
  /** The launch's T as a Type: the stamped wrapped-call return type, with
30213
30281
  * void coerced to uint64 (Task's result-slot convention). */
30214
- function spawn_result_type_arg$2(return_type) {
30282
+ function spawn_result_type_arg$1(return_type) {
30215
30283
  return return_type?.name && return_type.name !== "void" && return_type.name !== "?" ? new Type(return_type.name) : new Type("uint64");
30216
30284
  }
30217
30285
  //#endregion
@@ -31512,7 +31580,8 @@ function build_assignment_node$1(node, status, nir_rhs, nir_swap) {
31512
31580
  const lhs_decl = status.scoped_declarations.find((d) => d.name === lhs_name);
31513
31581
  const lhs_in_class_vars = !!status.class_vars?.has(lhs_name);
31514
31582
  const lhs_type = lhs_decl?.type || status.variable_types?.get(lhs_name);
31515
- const lhs_struct = lhs_type ? status.structs.find((s) => s.name === lhs_type.name) : null;
31583
+ const lhs_mono_name = lhs_type ? mono_type_name(lhs_type) : "";
31584
+ const lhs_struct = lhs_mono_name ? status.structs.find((s) => s.name === lhs_mono_name) : null;
31516
31585
  const lhs_is_string = lhs_type?.name === "string";
31517
31586
  const lhs_is_class = !!lhs_struct?.is_class || lhs_in_class_vars;
31518
31587
  const lhs_trait_class = lhs_scope_hit ? lhs_scope_hit.frame[lhs_scope_hit.index].trait_class_trait : void 0;
@@ -31580,11 +31649,12 @@ function build_assignment_node$1(node, status, nir_rhs, nir_swap) {
31580
31649
  }
31581
31650
  } else if (lhs_is_class && lhs_struct && !lhs_is_alias && (lhs_decl || lhs_in_class_vars)) {
31582
31651
  const temp = `_deferred_${status.label_counter = (status.label_counter ?? 0) + 1}`;
31583
- status.code += `struct ${lhs_struct.name}* ${temp} = ${lhs_name};\n`;
31652
+ const destroy_name = lhs_struct.name.startsWith(lhs_type.name) ? lhs_struct.name : lhs_type.name;
31653
+ status.code += `struct ${destroy_name}* ${temp} = ${lhs_name};\n`;
31584
31654
  if (!status.deferred_frees) status.deferred_frees = [];
31585
31655
  status.deferred_frees.push({
31586
31656
  temp,
31587
- struct_name: lhs_struct.name,
31657
+ struct_name: destroy_name,
31588
31658
  is_nullable: !!lhs_type?.is_nullable
31589
31659
  });
31590
31660
  } else if (lhs_is_class && lhs_is_alias && status.c_alias_owns_flags?.has(lhs_name)) {
@@ -31930,11 +32000,15 @@ function string_var_owns_heap(status, name) {
31930
32000
  function build_async_block_node(node, status, nir) {
31931
32001
  const id = status.spawn_counter ?? 0;
31932
32002
  status.spawn_counter = id + 1;
31933
- if (node.mode === "race") ensure_concurrency_runtime(status);
32003
+ ensure_concurrency_runtime(status);
31934
32004
  const futures_name = `__nomen_nursery_${id}_futures`;
31935
32005
  const count_name = `__nomen_nursery_${id}_count`;
31936
32006
  const cap_name = `__nomen_nursery_${id}_cap`;
31937
- const idx_name = `__nomen_nursery_${id}_i`;
32007
+ if (!status.nursery_meta) status.nursery_meta = /* @__PURE__ */ new Map();
32008
+ status.nursery_meta.set(id, {
32009
+ mode: node.mode ?? "all",
32010
+ has_deadline: !!node.timeout
32011
+ });
31938
32012
  status.code += `{\n`;
31939
32013
  status.code += `\tstruct nomen_future **${futures_name} = NULL;\n`;
31940
32014
  status.code += `\tint ${count_name} = 0;\n`;
@@ -31966,7 +32040,35 @@ function build_async_block_node(node, status, nir) {
31966
32040
  status.nursery_stack.push(id);
31967
32041
  build_block_with_cursor(node, nir?.body, status);
31968
32042
  status.nursery_stack.pop();
31969
- const is_race = node.mode === "race";
32043
+ emit_nursery_join_c(status, id);
32044
+ build_auto_free(status);
32045
+ status.code += `}\n`;
32046
+ leave_c_scope(status);
32047
+ status.scoped_declarations = old_scoped_declarations;
32048
+ status.deferred_frees = old_deferred_frees;
32049
+ }
32050
+ /**
32051
+ * Emit one nursery's join sequence: the race-mode wait (if any), the
32052
+ * wait+release loop over every registered future, and the futures-list
32053
+ * free. Shared by the block exit and the RETURN path — a `return` inside an
32054
+ * `async { }` block must route through the join (the structured-concurrency
32055
+ * contract: the block's scoped resources must not die under still-running
32056
+ * tasks, and the per-future release must not be skipped). The names are
32057
+ * deterministic per nursery id, so a return site inside the block can
32058
+ * re-emit the same sequence textually (the block's tracking locals are
32059
+ * still in C scope there).
32060
+ */
32061
+ function emit_nursery_join_c(status, id, is_return_path = false) {
32062
+ const meta = status.nursery_meta?.get(id) ?? {
32063
+ mode: "all",
32064
+ has_deadline: false
32065
+ };
32066
+ const futures_name = `__nomen_nursery_${id}_futures`;
32067
+ const count_name = `__nomen_nursery_${id}_count`;
32068
+ const idx_name = `__nomen_nursery_${id}_i`;
32069
+ const deadline_var = meta.has_deadline ? `_deadline_ms_${id}` : "";
32070
+ if (is_return_path) status.code += `\t// return inside async block: join nursery ${id} before leaving\n`;
32071
+ const is_race = meta.mode === "race";
31970
32072
  if (is_race) status.code += `\t__nomen_nursery_race_wait((struct nomen_future **)${futures_name}, ${count_name}, ${deadline_var ? deadline_var : "0"});\n`;
31971
32073
  status.code += `\tfor (int ${idx_name} = 0; ${idx_name} < ${count_name}; ${idx_name}++) {\n`;
31972
32074
  status.code += `\t\tstruct nomen_future *_f = (struct nomen_future *)${futures_name}[${idx_name}];\n`;
@@ -31983,11 +32085,19 @@ function build_async_block_node(node, status, nir) {
31983
32085
  status.code += `\t\t__nomen_future_release(_f);\n`;
31984
32086
  status.code += `\t}\n`;
31985
32087
  status.code += `\tfree(${futures_name});\n`;
31986
- build_auto_free(status);
31987
- status.code += `}\n`;
31988
- leave_c_scope(status);
31989
- status.scoped_declarations = old_scoped_declarations;
31990
- status.deferred_frees = old_deferred_frees;
32088
+ }
32089
+ /**
32090
+ * Emit the join sequence for every nursery the current `return` must route
32091
+ * through — innermost first. Only nurseries belonging to the CURRENT
32092
+ * function (stack depth >= the function's entry depth) apply: a lambda
32093
+ * defined inside an `async { }` block inherits the block's id on
32094
+ * nursery_stack (for lexical spawn captures), but its `return` exits the
32095
+ * lambda, whose frame has no nursery state.
32096
+ */
32097
+ function emit_nursery_joins_on_return_c(status) {
32098
+ if (!status.nursery_stack?.length) return;
32099
+ const base = status.function_nursery_depth ?? 0;
32100
+ for (let i = status.nursery_stack.length - 1; i >= base; i--) emit_nursery_join_c(status, status.nursery_stack[i], true);
31991
32101
  }
31992
32102
  //#endregion
31993
32103
  //#region ../src/build_c/build_array_values_node.ts
@@ -32945,6 +33055,7 @@ function build_return_node(node, status, nir_value) {
32945
33055
  const ret_is_null = returns_nullable_struct && (!node.value || node.value.node_type === "value" && node.value.value === "null");
32946
33056
  if (returns_nullable_struct) {
32947
33057
  if (ret_is_null) {
33058
+ emit_nursery_joins_on_return_c(status);
32948
33059
  reclaim_all_c_scopes(status);
32949
33060
  status.code += `*${ret_has} = 0;\n`;
32950
33061
  status.code += `return (struct ${status.function_return_type.name}){0};\n`;
@@ -32953,6 +33064,7 @@ function build_return_node(node, status, nir_value) {
32953
33064
  status.code += `*${ret_has} = 1;\n`;
32954
33065
  }
32955
33066
  if (!node.value) {
33067
+ emit_nursery_joins_on_return_c(status);
32956
33068
  reclaim_all_c_scopes(status);
32957
33069
  if (status.return_assign) status.code += `${status.return_assign} = 0;\n`;
32958
33070
  else if (status.current_function_name?.toLocaleLowerCase() === "main") status.code += `return 0;\n`;
@@ -33005,6 +33117,7 @@ function build_return_node(node, status, nir_value) {
33005
33117
  status.code += `_return_val->length = ${return_array_len};\n`;
33006
33118
  status.code += `${elem_c_type}* _return_data = (${elem_c_type}*)((char*)_return_val + sizeof(struct ${array_struct}));\n`;
33007
33119
  status.code += `for (long _i = 0; _i < ${return_array_len}; _i++) _return_data[_i] = ${return_array_var}[_i];\n`;
33120
+ emit_nursery_joins_on_return_c(status);
33008
33121
  reclaim_all_c_scopes(status);
33009
33122
  status.code += `return _return_val;\n`;
33010
33123
  return;
@@ -33020,6 +33133,7 @@ function build_return_node(node, status, nir_value) {
33020
33133
  emit_allocations(node.value, status);
33021
33134
  emit_return_value(node.value, nir_value, status);
33022
33135
  status.code += `;\n`;
33136
+ emit_nursery_joins_on_return_c(status);
33023
33137
  reclaim_all_c_scopes(status);
33024
33138
  status.code += `return;\n`;
33025
33139
  } else {
@@ -33042,6 +33156,7 @@ function build_return_node(node, status, nir_value) {
33042
33156
  build_node$1(node.value, status);
33043
33157
  status.join_needs_owned_string = old_join_owned;
33044
33158
  status.return_assign = old_return_assign;
33159
+ emit_nursery_joins_on_return_c(status);
33045
33160
  reclaim_all_c_scopes(status);
33046
33161
  if (string_join) status.code += any_branch_owned ? `return _return_val;\n` : `return nomen_str_dup(_return_val);\n`;
33047
33162
  else status.code += `return _return_val;\n`;
@@ -33100,6 +33215,7 @@ function build_return_node(node, status, nir_value) {
33100
33215
  status.code += `;\n`;
33101
33216
  if (node.value.node_type === "access" && node.value.access?.node_type === "access_field" && !returns_string_zero && !returns_struct_zero && !returns_view_value && ret_type.name !== "string" && !ret_type.is_view && !ret_type.is_array && status.enums.find((e) => e.name === ret_type.name && e.has_associated_data)) status.code += `_return_val = ${ret_type.name}_copy(_return_val);\n`;
33102
33217
  if (has_field_overrides(node.value)) emit_field_overrides("_return_val", node.value, build_node$1, status, "", ";\n");
33218
+ emit_nursery_joins_on_return_c(status);
33103
33219
  reclaim_all_c_scopes(status);
33104
33220
  status.code += `return _return_val;\n`;
33105
33221
  }
@@ -33382,7 +33498,7 @@ function emit_stmt_from_nir(child, index, statements, status) {
33382
33498
  const eval_node = nstmt.expr.node;
33383
33499
  if (eval_node.node_type === "access") {
33384
33500
  const inner = eval_node.access;
33385
- if (inner.node_type === "access_func" && (inner.is_nursery_spawn || inner.is_thread_start || inner.is_thread_detach || inner.is_fiber_start)) inner.is_statement = true;
33501
+ if (inner.node_type === "access_func" && inner.is_nursery_spawn) inner.is_statement = true;
33386
33502
  }
33387
33503
  emit_expr_from_nir(nstmt.expr, status);
33388
33504
  with_semicolon_tail(status, eval_node);
@@ -33911,22 +34027,9 @@ function scan_borrow_only_strings(body) {
33911
34027
  }
33912
34028
  //#endregion
33913
34029
  //#region ../src/build_c/build_struct_node.ts
33914
- /** System types whose method bodies call into the concurrency runtime. */
33915
- const CONCURRENCY_TYPES = /* @__PURE__ */ new Set([
33916
- "Task",
33917
- "Thread",
33918
- "Fiber",
33919
- "Channel",
33920
- "Mutex",
33921
- "Nursery",
33922
- "Tcp"
33923
- ]);
33924
34030
  function build_struct_node(node, status) {
33925
34031
  if (node.is_generic) return;
33926
- if (CONCURRENCY_TYPES.has(node.name)) {
33927
- ensure_concurrency_runtime(status);
33928
- status.used_fibers = true;
33929
- }
34032
+ for (const func of node.functions) ensure_runtime_for_method(func, status);
33930
34033
  if (node.is_simple_type) {
33931
34034
  status.code += `// Struct ${node.name}\n`;
33932
34035
  build_struct_functions(node, status);
@@ -34165,6 +34268,7 @@ function build_struct_functions(node, status, skip_init = false) {
34165
34268
  const old_current_struct = status.current_struct;
34166
34269
  status.current_struct = node;
34167
34270
  for (let func of node.functions) {
34271
+ if (func.name === "#spawn") continue;
34168
34272
  if (func.name === "#init" && !func.has_body) continue;
34169
34273
  if (func.name === "#init" && skip_init) continue;
34170
34274
  if (func.is_extern) {
@@ -35027,6 +35131,7 @@ function build_function_node(node, status) {
35027
35131
  else status.nullable_ret_has_param = void 0;
35028
35132
  const old_function_name = status.current_function_name;
35029
35133
  status.current_function_name = emission_label(node);
35134
+ status.function_nursery_depth = status.nursery_stack?.length ?? 0;
35030
35135
  const old_current_function = status.current_function;
35031
35136
  status.current_function = node;
35032
35137
  for (let param of node.params) {
@@ -35645,22 +35750,6 @@ function build_access_node$1(node, status) {
35645
35750
  }
35646
35751
  case "access_func": {
35647
35752
  const access_func = node.access;
35648
- if (node.target.node_type === "value" && node.target.value === "Fiber") {
35649
- ensure_concurrency_runtime(status);
35650
- status.used_fibers = true;
35651
- }
35652
- if (access_func.is_thread_start) {
35653
- build_thread_start(access_func, node.target, status);
35654
- return;
35655
- }
35656
- if (access_func.is_thread_detach) {
35657
- build_thread_detach(access_func, node.target, status);
35658
- return;
35659
- }
35660
- if (access_func.is_fiber_start) {
35661
- build_fiber_spawn_node$1(access_func, node.target, status, access_func.is_fiber_start_on ? access_func.params[0] : void 0);
35662
- return;
35663
- }
35664
35753
  if (access_func.is_nursery_spawn) {
35665
35754
  build_nursery_spawn$1(access_func, nursery_pointer_expr(node.target, status), status);
35666
35755
  return;
@@ -35965,10 +36054,35 @@ function build_access_node$1(node, status) {
35965
36054
  const label = access_func.mangled_name || trait_default_label || `${mono_struct_name}_${access_func.name.replace(/#/g, "")}`;
35966
36055
  const array_wrap = array_receiver_wrap(node, method_type, target_method, mono_struct_name, status);
35967
36056
  if (array_wrap) status.code += array_wrap.prefix;
36057
+ const ctor_target_node = node.target.node_type === "func_call" ? node.target : void 0;
36058
+ const ctor_temp_receiver = !!(ctor_target_node && (ctor_target_node.is_thread_ctor || ctor_target_node.is_fiber_ctor || ctor_target_node.is_awaitable_ctor));
36059
+ let ctor_temp_free;
36060
+ let ctor_temp_val;
36061
+ if (ctor_temp_receiver) {
36062
+ const id = status.label_counter = (status.label_counter ?? 0) + 1;
36063
+ ctor_temp_free = `_ctorr_${id}`;
36064
+ const ctor_mono = mono_type_name(type_from_value_node$1(node.target));
36065
+ status.code += `({ struct ${ctor_mono} *${ctor_temp_free} = `;
36066
+ {
36067
+ const saved_suppress = status.suppress_dereference;
36068
+ status.suppress_dereference = true;
36069
+ build_node$1(node.target, status);
36070
+ status.suppress_dereference = saved_suppress;
36071
+ }
36072
+ status.code += `; `;
36073
+ const ret = (target_method ?? trait_default_func)?.return_type;
36074
+ if (ret?.name && ret.name !== "void") {
36075
+ const mono_ret = mono_type_name(access_func.type);
36076
+ const ret_c = !!status.structs.find((s) => s.name === mono_ret && s.is_class) || !!status.traits.find((t) => t.name === mono_ret) ? `struct ${mono_ret} *` : c_type(mono_ret);
36077
+ ctor_temp_val = `_ctorv_${id}`;
36078
+ status.code += `${ret_c} ${ctor_temp_val} = `;
36079
+ }
36080
+ }
35968
36081
  if (lambda_ret_tmp) status.code += `${lambda_ret_c} ${lambda_ret_tmp} = `;
35969
36082
  status.code += `${label}(`;
35970
36083
  if (!access_func.is_static) {
35971
- if (array_wrap) status.code += array_wrap.self_expr;
36084
+ if (ctor_temp_free) status.code += ctor_temp_free;
36085
+ else if (array_wrap) status.code += array_wrap.self_expr;
35972
36086
  else {
35973
36087
  const method_self_is_ref = !!target_method?.params?.some((p) => p.is_self_param && (p.is_ref || p.type?.is_ref));
35974
36088
  const method_self = target_method?.params?.find((p) => p.is_self_param);
@@ -36037,6 +36151,11 @@ function build_access_node$1(node, status) {
36037
36151
  }
36038
36152
  }
36039
36153
  status.code += ")";
36154
+ if (ctor_temp_free) {
36155
+ status.code += `; free(${ctor_temp_free});`;
36156
+ if (ctor_temp_val) status.code += ` ${ctor_temp_val};`;
36157
+ status.code += ` })`;
36158
+ }
36040
36159
  if (lambda_arg_temps.size > 0) {
36041
36160
  for (const tmp of lambda_arg_temps.values()) status.code += `; ${closure_dispose_arm(tmp)}`;
36042
36161
  if (lambda_ret_tmp) status.code += `; ${lambda_ret_tmp}`;
@@ -36758,7 +36877,7 @@ function build_node$1(node, status, with_semicolon = false) {
36758
36877
  const access = node;
36759
36878
  if (with_semicolon && access.access.node_type === "access_func") {
36760
36879
  const afn = access.access;
36761
- if (afn.is_nursery_spawn || afn.is_thread_start || afn.is_thread_detach || afn.is_fiber_start) afn.is_statement = true;
36880
+ if (afn.is_nursery_spawn) afn.is_statement = true;
36762
36881
  }
36763
36882
  build_access_node$1(access, status);
36764
36883
  break;
@@ -36861,9 +36980,10 @@ function build_node$1(node, status, with_semicolon = false) {
36861
36980
  //#endregion
36862
36981
  //#region ../src/build_c/build_magic_ctor.ts
36863
36982
  /**
36864
- * The compiler-special `Thread(fn(args))` / `Fiber(fn(args))` constructor —
36865
- * and its generalized flavor, a user Awaitable class's `MyThing(fn(args))`
36866
- * (ASYNC.md, "User-defined async primitives") — packs the wrapped call's
36983
+ * The `#spawn` construction (the deferred-call special form — the library
36984
+ * `Thread(fn(args))` / `Fiber(fn(args))`, or any class declaring its own
36985
+ * `#spawn` hook; docs/ASYNC.md, "The construction special form") — packs
36986
+ * the wrapped call's
36867
36987
  * arguments EAGERLY into a task environment, allocates the result slot,
36868
36988
  * cancel flag, and future, and builds the task closure — a heap descriptor
36869
36989
  * over that environment (the same ABI every lambda lowers to; the runtime's
@@ -37058,6 +37178,7 @@ function build_magic_ctor_node$1(node, status) {
37058
37178
  status.code += `\t_future->done = 0;\n`;
37059
37179
  status.code += `\t_future->cancel_flag = _cancel_ptr;\n`;
37060
37180
  status.code += `\t_future->result_slot = _result_ptr;\n`;
37181
+ status.code += `\t_future->slot_fat = ${slot_c_type === "nomen_string" ? 1 : 0};\n`;
37061
37182
  status.code += `\t_future->fiber_waiters = NULL;\n`;
37062
37183
  status.code += `\t_future->owning_fiber = NULL;\n`;
37063
37184
  status.code += `\t_future->refs = 1;\n`;
@@ -37075,10 +37196,29 @@ function build_magic_ctor_node$1(node, status) {
37075
37196
  status.code += `\t_self->cancel_flag = (unsigned long long)_cancel_ptr;\n`;
37076
37197
  status.code += `\t_self->future = (unsigned long long)_future;\n`;
37077
37198
  if (info.has_started) status.code += `\t_self->started = 0;\n`;
37199
+ emit_nursery_capture_c(status, kind, (field, expr) => {
37200
+ status.code += `\t_self->${field} = ${expr};\n`;
37201
+ });
37078
37202
  status.code += `\t_self;\n`;
37079
37203
  status.code += `})\n`;
37080
37204
  }
37081
37205
  /**
37206
+ * LEXICAL NURSERY CAPTURE (ASYNC.md): a construction lexically
37207
+ * inside an `async` block stores the block's tracking-slot pointers in the
37208
+ * instance, so `.start()` — an ordinary method on the spawn class —
37209
+ * registers the future with the scope that CREATED the deferred call rather
37210
+ * than wherever the launch happens. Library spawn classes only: Thread/Fiber
37211
+ * carry the three capture fields (the generalized Awaitable flavor's field
37212
+ * contract predates them and keeps its historical emission).
37213
+ */
37214
+ function emit_nursery_capture_c(status, kind, emit_field) {
37215
+ const nursery_id = status.nursery_stack?.at(-1);
37216
+ if (nursery_id === void 0 || kind !== "Thread" && kind !== "Fiber") return;
37217
+ emit_field("nursery_futures", `(unsigned long long)&__nomen_nursery_${nursery_id}_futures`);
37218
+ emit_field("nursery_count", `(unsigned long long)&__nomen_nursery_${nursery_id}_count`);
37219
+ emit_field("nursery_cap", `(unsigned long long)&__nomen_nursery_${nursery_id}_cap`);
37220
+ }
37221
+ /**
37082
37222
  * The Phase 3c function-value construction (`Thread(() => work(n))` /
37083
37223
  * `Thread(job)` with a zero-argument func-typed local): the task closure is
37084
37224
  * an ADAPTER over the given closure. The adapter env carries the user
@@ -37152,6 +37292,7 @@ function build_fn_value_ctor(node, status, id, kind, info) {
37152
37292
  status.code += `\t_future->done = 0;\n`;
37153
37293
  status.code += `\t_future->cancel_flag = _cancel_ptr;\n`;
37154
37294
  status.code += `\t_future->result_slot = _result_ptr;\n`;
37295
+ status.code += `\t_future->slot_fat = ${dup_result ? 1 : 0};\n`;
37155
37296
  status.code += `\t_future->fiber_waiters = NULL;\n`;
37156
37297
  status.code += `\t_future->owning_fiber = NULL;\n`;
37157
37298
  status.code += `\t_future->refs = 1;\n`;
@@ -37173,6 +37314,9 @@ function build_fn_value_ctor(node, status, id, kind, info) {
37173
37314
  status.code += `\t_self->cancel_flag = (unsigned long long)_cancel_ptr;\n`;
37174
37315
  status.code += `\t_self->future = (unsigned long long)_future;\n`;
37175
37316
  if (info.has_started) status.code += `\t_self->started = 0;\n`;
37317
+ emit_nursery_capture_c(status, kind, (field, expr) => {
37318
+ status.code += `\t_self->${field} = ${expr};\n`;
37319
+ });
37176
37320
  status.code += `\t_self;\n`;
37177
37321
  status.code += `})\n`;
37178
37322
  }
@@ -37193,10 +37337,10 @@ function is_struct_ret(node, status) {
37193
37337
  //#endregion
37194
37338
  //#region ../src/build_aarch64/build_magic_ctor.ts
37195
37339
  /**
37196
- * The spawn-sugar construction (the compiler-special
37197
- * `Thread(fn(args))` / `Fiber(fn(args))`, or the generalized user
37198
- * Awaitable class's `MyThing(fn(args))` — CLOSURE.md Phase 3b, ASYNC.md
37199
- * "User-defined async primitives"), aarch64 backend: the per-site
37340
+ * The `#spawn` construction (the deferred-call special form — the library
37341
+ * `Thread(fn(args))` / `Fiber(fn(args))`, or any class declaring its own
37342
+ * `#spawn` hook; docs/ASYNC.md, "The construction special form"),
37343
+ * aarch64 backend: the per-site
37200
37344
  * trampoline and a constructor helper are emitted as C in the companion
37201
37345
  * file; the assembly stages the wrapped call's arguments and calls the
37202
37346
  * helper, which packs the env, allocates the future machinery, builds the
@@ -37218,10 +37362,14 @@ function build_magic_ctor_node(node, status) {
37218
37362
  const tramp_name = `__nomen_spawn_${id}_trampoline`;
37219
37363
  const desc_name = `__nomen_spawn_${id}_descriptor`;
37220
37364
  const struct_name = `__nomen_spawn_${id}_args`;
37365
+ const nursery_id = status.nursery_stack?.at(-1);
37366
+ const nursery_off = nursery_id !== void 0 ? status.nursery_offsets?.get(nursery_id) : void 0;
37367
+ const capture = nursery_capture_a64(status, kind);
37368
+ const arg_types = spawn_arg_types(call, status);
37221
37369
  const arg_c_types = [];
37222
37370
  const fat_string_args = [];
37223
37371
  for (let i = 0; i < call.params.length; i++) {
37224
- const mono_name = mono_type_name(type_from_value_node$1(call.params[i]));
37372
+ const mono_name = mono_type_name(arg_types[i]);
37225
37373
  const is_class = !!status.structs.find((s) => s.name === mono_name && s.is_class);
37226
37374
  const is_trait = !!status.traits.find((t) => t.name === mono_name);
37227
37375
  arg_c_types.push(is_class || is_trait ? `struct ${mono_name} *` : `${c_type(mono_name)}`);
@@ -37277,6 +37425,10 @@ function build_magic_ctor_node(node, status) {
37277
37425
  if (i > 0) c += ", ";
37278
37426
  c += `${arg_c_types[i]} arg${i}`;
37279
37427
  }
37428
+ if (capture) {
37429
+ if (arg_c_types.length > 0) c += ", ";
37430
+ c += capture.helper_params;
37431
+ }
37280
37432
  c += `) {\n`;
37281
37433
  c += `\tstruct ${struct_name} *a = (struct ${struct_name} *)malloc(sizeof(struct ${struct_name}));\n`;
37282
37434
  for (let i = 0; i < arg_c_types.length; i++) c += `\ta->arg${i} = ${fat_string_args[i] ? "nomen_str_dup(arg" + i + ")" : `arg${i}`};\n`;
@@ -37291,6 +37443,7 @@ function build_magic_ctor_node(node, status) {
37291
37443
  c += `\tf->refs = 1;\n`;
37292
37444
  c += `\tf->cancel_flag = a->cancel_flag;\n`;
37293
37445
  c += `\tf->result_slot = a->result_slot;\n`;
37446
+ c += `\tf->slot_fat = ${slot_c_type === "nomen_string" ? 1 : 0};\n`;
37294
37447
  c += `\tf->fiber_waiters = NULL;\n`;
37295
37448
  c += `\tf->owning_fiber = 0;\n`;
37296
37449
  c += `\ta->future = f;\n`;
@@ -37307,6 +37460,7 @@ function build_magic_ctor_node(node, status) {
37307
37460
  c += `\tself->cancel_flag = (unsigned long long)a->cancel_flag;\n`;
37308
37461
  c += `\tself->future = (unsigned long long)f;\n`;
37309
37462
  if (info.has_started) c += `\tself->started = 0;\n`;
37463
+ if (capture) c += capture.field_stores.join("");
37310
37464
  c += `\treturn self;\n`;
37311
37465
  c += `}\n`;
37312
37466
  if (!status.file_scope_c) status.file_scope_c = "";
@@ -37318,6 +37472,8 @@ function build_magic_ctor_node(node, status) {
37318
37472
  arg_slot.push(total_arg_slots);
37319
37473
  total_arg_slots += fat_string_args[i] ? 2 : 1;
37320
37474
  }
37475
+ const capture_base_slot = total_arg_slots;
37476
+ if (capture) total_arg_slots += capture.arg_count;
37321
37477
  if (total_arg_slots === 0) emit_asm(status, `bl _${helper_name}\n`);
37322
37478
  else {
37323
37479
  const args_base = allocate_stack_space(status, total_arg_slots * 8, 16);
@@ -37328,6 +37484,17 @@ function build_magic_ctor_node(node, status) {
37328
37484
  emit_asm(status, `str x0, [x29, #${args_base + arg_slot[i] * 8}]\n`);
37329
37485
  if (fat_string_args[i]) emit_asm(status, `str x1, [x29, #${args_base + (arg_slot[i] + 1) * 8}]\n`);
37330
37486
  }
37487
+ if (capture && nursery_off) {
37488
+ const addrs = [
37489
+ nursery_off.futures_off,
37490
+ nursery_off.count_off,
37491
+ nursery_off.cap_off
37492
+ ];
37493
+ for (let k = 0; k < addrs.length; k++) {
37494
+ emit_asm(status, `add x9, x29, #${addrs[k]}\n`);
37495
+ emit_asm(status, `str x9, [x29, #${args_base + (capture_base_slot + k) * 8}]\n`);
37496
+ }
37497
+ }
37331
37498
  const NUM_REG_ARGS = 8;
37332
37499
  const overflow_count = Math.max(0, total_arg_slots - NUM_REG_ARGS);
37333
37500
  if (overflow_count > 0) {
@@ -37402,7 +37569,8 @@ function build_fn_value_ctor_a64(node, status, id, kind, info) {
37402
37569
  c += `\t__nomen_future_release(a->future);\n`;
37403
37570
  c += `}\n`;
37404
37571
  c += `static struct nomen_closure ${desc_name} = { (void *)${tramp_name}, NULL, 0, NULL };\n`;
37405
- c += `void *${helper_name}(struct nomen_closure *fn) {\n`;
37572
+ const fnval_capture = nursery_capture_a64(status, kind);
37573
+ c += `void *${helper_name}(struct nomen_closure *fn${fnval_capture ? `, ${fnval_capture.helper_params}` : ""}) {\n`;
37406
37574
  c += `\tstruct ${struct_name} *a = (struct ${struct_name} *)malloc(sizeof(struct ${struct_name}));\n`;
37407
37575
  c += `\ta->fn = fn;\n`;
37408
37576
  c += `\ta->result_slot = (${slot_c_type} *)${returns_value ? `malloc(sizeof(${slot_c_type}))` : "malloc(16)"};\n`;
@@ -37416,6 +37584,7 @@ function build_fn_value_ctor_a64(node, status, id, kind, info) {
37416
37584
  c += `\tf->refs = 1;\n`;
37417
37585
  c += `\tf->cancel_flag = a->cancel_flag;\n`;
37418
37586
  c += `\tf->result_slot = a->result_slot;\n`;
37587
+ c += `\tf->slot_fat = ${dup_result ? 1 : 0};\n`;
37419
37588
  c += `\tf->fiber_waiters = NULL;\n`;
37420
37589
  c += `\tf->owning_fiber = 0;\n`;
37421
37590
  c += `\ta->future = f;\n`;
@@ -37432,6 +37601,7 @@ function build_fn_value_ctor_a64(node, status, id, kind, info) {
37432
37601
  c += `\tself->cancel_flag = (unsigned long long)a->cancel_flag;\n`;
37433
37602
  c += `\tself->future = (unsigned long long)f;\n`;
37434
37603
  if (info.has_started) c += `\tself->started = 0;\n`;
37604
+ if (fnval_capture) c += fnval_capture.field_stores.join("");
37435
37605
  c += `\treturn self;\n`;
37436
37606
  c += `}\n`;
37437
37607
  if (!status.file_scope_c) status.file_scope_c = "";
@@ -37447,8 +37617,41 @@ function build_fn_value_ctor_a64(node, status, id, kind, info) {
37447
37617
  if (!status.moved) status.moved = /* @__PURE__ */ new Set();
37448
37618
  status.moved.add(name);
37449
37619
  }
37620
+ if (fnval_capture) {
37621
+ const fnval_nursery_id = status.nursery_stack?.at(-1);
37622
+ const fnval_off = fnval_nursery_id !== void 0 ? status.nursery_offsets?.get(fnval_nursery_id) : void 0;
37623
+ if (fnval_off) {
37624
+ const fnval_park = allocate_stack_space(status, 8, 8);
37625
+ emit_asm(status, `str x0, [x29, #${fnval_park}]\n`);
37626
+ emit_asm(status, `add x1, x29, #${fnval_off.futures_off}\n`);
37627
+ emit_asm(status, `add x2, x29, #${fnval_off.count_off}\n`);
37628
+ emit_asm(status, `add x3, x29, #${fnval_off.cap_off}\n`);
37629
+ emit_asm(status, `ldr x0, [x29, #${fnval_park}]\n`);
37630
+ }
37631
+ }
37450
37632
  emit_asm(status, `bl _${helper_name}\n`);
37451
37633
  }
37634
+ /**
37635
+ * LEXICAL NURSERY CAPTURE (ASYNC.md), aarch64 backend. The
37636
+ * construction helper is companion C and cannot address the enclosing async
37637
+ * block's frame, so the assembly passes the three tracking-slot addresses as
37638
+ * trailing helper arguments and the helper stores them into the instance's
37639
+ * capture fields — the lexical-at-construction counterpart of the C
37640
+ * backend's emit_nursery_capture_c. Applies to Thread/Fiber only (they
37641
+ * carry the capture fields).
37642
+ */
37643
+ function nursery_capture_a64(status, kind) {
37644
+ if (status.nursery_stack?.at(-1) === void 0 || kind !== "Thread" && kind !== "Fiber") return void 0;
37645
+ return {
37646
+ helper_params: "unsigned long long nursery_futures, unsigned long long nursery_count, unsigned long long nursery_cap",
37647
+ field_stores: [
37648
+ `\tself->nursery_futures = nursery_futures;\n`,
37649
+ `\tself->nursery_count = nursery_count;\n`,
37650
+ `\tself->nursery_cap = nursery_cap;\n`
37651
+ ],
37652
+ arg_count: 3
37653
+ };
37654
+ }
37452
37655
  //#endregion
37453
37656
  //#region ../src/build_aarch64/build_panic_node.ts
37454
37657
  function build_panic_node(node, status) {
@@ -37576,7 +37779,7 @@ function build_value_node(node, status) {
37576
37779
  if (paramReg !== "x0") emit_asm(status, `mov x0, ${paramReg}`);
37577
37780
  } else if (status.function_param_vars?.has(original_value) || status.function_param_vars?.has(value) || status.function_ref_params?.has(original_value) || status.function_ref_params?.has(value)) {
37578
37781
  const param_type_name = node.type?.name;
37579
- if (param_type_name && status.structs.find((s) => s.name === param_type_name && s.is_class) || !!status.class_vars?.has(value) || !!status.class_vars?.has(original_value)) {
37782
+ if (param_type_name && status.structs.find((s) => s.name === param_type_name && s.is_class) || !!param_type_name && !!status.traits.find((t) => t.name === param_type_name) || !!status.class_vars?.has(value) || !!status.class_vars?.has(original_value)) {
37580
37783
  if (paramReg !== "x0") emit_asm(status, `mov x0, ${paramReg}`);
37581
37784
  } else emit_asm(status, deref_load_instr(paramReg, param_type_name));
37582
37785
  } else if (paramReg !== "x0") emit_asm(status, `mov x0, ${paramReg}`);
@@ -37628,7 +37831,7 @@ function build_value_node(node, status) {
37628
37831
  if (status.function_ref_params?.has(value) || status.function_ref_params?.has(original_value)) {
37629
37832
  if (alloc_reg !== "x0") emit_asm(status, `mov x0, ${alloc_reg}\n`);
37630
37833
  const param_type_name = node.type?.name;
37631
- if (!(param_type_name && status.structs.find((s) => s.name === param_type_name && s.is_class) || !!status.class_vars?.has(value) || !!status.class_vars?.has(original_value))) emit_asm(status, deref_load_instr("x0", param_type_name));
37834
+ if (!(param_type_name && status.structs.find((s) => s.name === param_type_name && s.is_class) || !!param_type_name && !!status.traits.find((t) => t.name === param_type_name) || !!status.class_vars?.has(value) || !!status.class_vars?.has(original_value))) emit_asm(status, deref_load_instr("x0", param_type_name));
37632
37835
  } else if (alloc_reg !== "x0") {
37633
37836
  if (alloc_reg.startsWith("d")) emit_asm(status, `fmov x0, ${alloc_reg}`);
37634
37837
  else emit_asm(status, `mov x0, ${alloc_reg}`);
@@ -37836,7 +38039,7 @@ function build_node(node, status, with_semicolon = false) {
37836
38039
  const access = node;
37837
38040
  if (with_semicolon && access.access.node_type === "access_func") {
37838
38041
  const afn = access.access;
37839
- if (afn.is_nursery_spawn || afn.is_thread_start || afn.is_thread_detach || afn.is_fiber_start) afn.is_statement = true;
38042
+ if (afn.is_nursery_spawn) afn.is_statement = true;
37840
38043
  }
37841
38044
  build_access_node(access, status);
37842
38045
  break;
@@ -39757,7 +39960,7 @@ function build_operand(node, target_reg, status) {
39757
39960
  if (paramReg) {
39758
39961
  if (status.function_param_vars?.has(value) || status.function_ref_params?.has(value)) {
39759
39962
  const param_type_name = node.type?.name;
39760
- if (param_type_name && status.structs.find((s) => s.name === param_type_name && s.is_class)) {
39963
+ if (param_type_name && status.structs.find((s) => s.name === param_type_name && s.is_class) || !!param_type_name && !!status.traits.find((t) => t.name === param_type_name)) {
39761
39964
  if (paramReg !== target_reg) emit_asm(status, `mov ${target_reg}, ${paramReg}`);
39762
39965
  } else emit_asm(status, `ldr ${target_reg}, [${paramReg}]`);
39763
39966
  } else if (paramReg !== target_reg) emit_asm(status, `mov ${target_reg}, ${paramReg}`);
@@ -40990,113 +41193,6 @@ function array_licm_enabled() {
40990
41193
  return array_licm_on;
40991
41194
  }
40992
41195
  //#endregion
40993
- //#region ../src/build_aarch64/build_fiber_spawn.ts
40994
- /**
40995
- * Fiber scheduler runtime (ASYNC.md Phase 1), aarch64 backend.
40996
- *
40997
- * Same scheduler as the C backend's FIBER_HEADER (which see), but the
40998
- * context switch is a naked-asm function compiled into the companion C —
40999
- * 13 words (x19–x28, FP, LR, SP) per context, ~30 instructions per switch,
41000
- * no ucontext. The raw-asm-called helpers (__nomen_fiber_yield,
41001
- * __nomen_fiber_is_active, __nomen_fiber_set_cooperative — reached from
41002
- * Fiber.nm's `#arch: aarch64` bodies as ___nomen_fiber_*) are non-static.
41003
- */
41004
- /**
41005
- * Build a `.start()` / `.start_on(buf)` launch on a `Fiber(fn(args))`
41006
- * construction (or a stored Fiber binding) — aarch64 backend
41007
- * (CLOSURE.md Phase 3b). A companion-C helper reads the packed
41008
- * handles from the receiver's fields and launches the task closure on the
41009
- * fiber scheduler (a heap stack, or the caller's fixed-size array buffer
41010
- * for start_on — validated at check time, >= 16 KB), registers the future
41011
- * with the enclosing nursery (if any), transfers the handles out of the
41012
- * instance, and returns Task<T> (or NULL, fire-and-forget); the assembly
41013
- * builds the receiver pointer and calls it.
41014
- */
41015
- function build_fiber_spawn_node(access_func, target, status, start_on) {
41016
- ensure_concurrency_runtime_a64(status);
41017
- status.used_fibers = true;
41018
- const id = status.spawn_counter ?? 0;
41019
- status.spawn_counter = id + 1;
41020
- const helper_name = `nomen_fiber_${id}_start`;
41021
- const t_arg = spawn_result_type_arg$1(access_func.function_return_type);
41022
- const mono_fiber = mono_type_name("Fiber", [t_arg]);
41023
- const mono_task_name = mono_type_name("Task", [t_arg]);
41024
- const nursery_id = status.nursery_stack?.at(-1);
41025
- const nursery_off = nursery_id !== void 0 ? status.nursery_offsets?.get(nursery_id) : void 0;
41026
- const fire_and_forget = !!access_func.is_statement;
41027
- const target_is_temp = target.node_type === "func_call" && !!(target.is_thread_ctor || target.is_fiber_ctor);
41028
- const refs = fire_and_forget ? nursery_id !== void 0 ? 2 : 1 : nursery_id !== void 0 ? 3 : 2;
41029
- let c = `// --- fiber start site ${id} ---\n`;
41030
- c += `void *${helper_name}(void *self`;
41031
- let trailing = "";
41032
- if (nursery_off) trailing += `void **__nomen_nursery_futures, int *__nomen_nursery_count, int *__nomen_nursery_cap`;
41033
- if (start_on) trailing += `${trailing ? ", " : ""}void *__nomen_stack, unsigned long long __nomen_stack_size`;
41034
- if (trailing) c += `, ${trailing}`;
41035
- c += `) {\n`;
41036
- c += `\textern void *${mono_task_name}_traits[];\n\tstruct ${mono_fiber} *t = (struct ${mono_fiber} *)self;\n`;
41037
- c += `\tstruct nomen_future *f = (struct nomen_future *)t->future;\n`;
41038
- c += `\tvoid *result_ptr = (void *)t->result_slot;\n`;
41039
- c += `\tunsigned long long *cancel_ptr = (unsigned long long *)t->cancel_flag;\n`;
41040
- c += `\tstruct nomen_closure *cl = (struct nomen_closure *)t->task;\n`;
41041
- c += `\tf->refs = ${refs};\n`;
41042
- c += `\t__nomen_fiber_spawn${start_on ? "_on" : ""}(cl, f${start_on ? ", __nomen_stack, (size_t)__nomen_stack_size" : ""});\n`;
41043
- if (nursery_off) c += `\t__nomen_nursery_track(__nomen_nursery_futures, __nomen_nursery_count, __nomen_nursery_cap, f);\n`;
41044
- c += `\tt->task = 0;\n`;
41045
- c += `\tt->future = 0;\n`;
41046
- c += `\tt->result_slot = 0;\n`;
41047
- c += `\tt->cancel_flag = 0;\n`;
41048
- c += `\tt->started = 1;\n`;
41049
- if (target_is_temp) c += `\tfree(t);\n`;
41050
- if (fire_and_forget) c += `\treturn (void *)0;\n`;
41051
- else {
41052
- c += `\tstruct ${mono_task_name} *task = (struct ${mono_task_name} *)malloc(sizeof(struct ${mono_task_name}));\n`;
41053
- c += `\ttask->_vt = (void **)${mono_task_name}_traits;\n`;
41054
- c += `\ttask->handle = 0;\n`;
41055
- c += `\ttask->done = 0;\n`;
41056
- c += `\ttask->result_slot = (unsigned long long)result_ptr;\n`;
41057
- c += `\ttask->cancel_flag = (unsigned long long)cancel_ptr;\n`;
41058
- c += `\ttask->future = (unsigned long long)f;\n`;
41059
- c += `\treturn task;\n`;
41060
- }
41061
- c += `}\n`;
41062
- if (!status.file_scope_c) status.file_scope_c = "";
41063
- status.file_scope_c += c;
41064
- emit_asm(status, `// fiber start site ${id}\n`);
41065
- build_node(target, status);
41066
- ensure_newline(status);
41067
- let extra_slots = 0;
41068
- if (nursery_off) extra_slots += 3;
41069
- if (start_on) extra_slots += 2;
41070
- if (extra_slots > 0) {
41071
- const park = allocate_stack_space(status, 8, 8);
41072
- emit_asm(status, `str x0, [x29, #${park}]\n`);
41073
- if (nursery_off) {
41074
- emit_asm(status, `add x1, x29, #${nursery_off.futures_off}\n`);
41075
- emit_asm(status, `add x2, x29, #${nursery_off.count_off}\n`);
41076
- emit_asm(status, `add x3, x29, #${nursery_off.cap_off}\n`);
41077
- }
41078
- if (start_on) {
41079
- emit_asm(status, `// Build stack buffer address\n`);
41080
- emit_address_of(start_on, status);
41081
- ensure_newline(status);
41082
- emit_asm(status, `mov x${nursery_off ? 4 : 1}, x0\n`);
41083
- const buf_type = type_from_value_node$1(start_on);
41084
- const elem_size = buf_type.name === "string" ? 16 : 8;
41085
- const len_node = buf_type.length;
41086
- const len = len_node && len_node.node_type === "value" ? parseInt(len_node.value, 10) : NaN;
41087
- emit_asm(status, `// stack size = ${Number.isNaN(len) ? 0 : len} * ${elem_size}\n`);
41088
- emit_asm(status, `mov x${nursery_off ? 5 : 2}, #${Number.isNaN(len) ? 0 : len * elem_size}\n`);
41089
- }
41090
- emit_asm(status, `ldr x0, [x29, #${park}]\n`);
41091
- }
41092
- emit_asm(status, `bl _${helper_name}\n`);
41093
- }
41094
- /** The launch's T as a Type: the stamped wrapped-call return type, with
41095
- * void coerced to uint64 (Task's result-slot convention). */
41096
- function spawn_result_type_arg$1(return_type) {
41097
- return return_type?.name && return_type.name !== "void" && return_type.name !== "?" ? new Type(return_type.name) : new Type("uint64");
41098
- }
41099
- //#endregion
41100
41196
  //#region ../src/build_aarch64/build_nursery_spawn.ts
41101
41197
  /**
41102
41198
  * Build a `name.start(Thread(fn(args)))` escape-hatch call for aarch64
@@ -41680,22 +41776,6 @@ function build_access_node(node, status) {
41680
41776
  break;
41681
41777
  case "access_func": {
41682
41778
  const access_func = node.access;
41683
- if (node.target.node_type === "value" && node.target.value === "Fiber" && !status.used_fibers) {
41684
- ensure_concurrency_runtime_a64(status);
41685
- status.used_fibers = true;
41686
- }
41687
- if (access_func.is_thread_start) {
41688
- build_thread_start$1(access_func, node.target, status);
41689
- return;
41690
- }
41691
- if (access_func.is_thread_detach) {
41692
- build_thread_detach$1(access_func, node.target, status);
41693
- return;
41694
- }
41695
- if (access_func.is_fiber_start) {
41696
- build_fiber_spawn_node(access_func, node.target, status, access_func.is_fiber_start_on ? access_func.params[0] : void 0);
41697
- return;
41698
- }
41699
41779
  if (access_func.is_nursery_spawn) {
41700
41780
  build_nursery_spawn(node, access_func, status);
41701
41781
  return;
@@ -41741,6 +41821,46 @@ function get_base_target(node) {
41741
41821
  if (node.target.node_type === "access") return get_base_target(node.target);
41742
41822
  return node.target;
41743
41823
  }
41824
+ /**
41825
+ * Resolve the concrete type of an access target (a local/param/`self` value or
41826
+ * a nested access), consulting the containing struct's field declarations when
41827
+ * a cached type is absent — an inferred `move` class field
41828
+ * (`pub move rules = RuleSet()`) carries no cached AccessFieldNode.type.
41829
+ */
41830
+ function resolve_access_target_type(target, status) {
41831
+ if (target.node_type === "value") {
41832
+ const name = target.value;
41833
+ if (name === "self" && status.current_struct) return new Type(status.current_struct.name);
41834
+ if (status.variable_types?.has(name)) return status.variable_types.get(name);
41835
+ const decl = status.scoped_declarations.findLast((d) => d.name === name);
41836
+ if (decl?.type?.name) return decl.type;
41837
+ return type_from_value_node$1(target);
41838
+ }
41839
+ if (target.node_type === "access") return type_from_value_node$1(target) ?? resolve_access_type(target, status) ?? void 0;
41840
+ return type_from_value_node$1(target);
41841
+ }
41842
+ /**
41843
+ * Whether reading the final field of `node` must dereference an intermediate
41844
+ * pointer. The summed-offset-from-root load (`compute_field_offset` + the
41845
+ * base address) is only valid while every hop is an INLINE value struct. A
41846
+ * class-typed field — or a class-typed root value — stores a POINTER, so the
41847
+ * chain has to be built recursively (each pointer hop loaded) before the
41848
+ * final field is read. Without this, `state.rules.blocks.length` (State/
41849
+ * BlockParserState is a class) summed 8+8+8 and loaded from `state + 24`.
41850
+ */
41851
+ function access_chain_crosses_class(node, status) {
41852
+ const base_type = resolve_access_target_type(get_base_target(node), status);
41853
+ if (base_type?.name && status.structs.find((s) => s.name === base_type.name && s.is_class)) return true;
41854
+ let cursor = node;
41855
+ while (cursor.node_type === "access") {
41856
+ const acc = cursor;
41857
+ if (acc.access.node_type !== "access_field") return false;
41858
+ const ft = resolve_field_type(acc.access, resolve_access_target_type(acc.target, status)?.name, status);
41859
+ if (ft?.name && status.structs.find((s) => s.name === ft.name && s.is_class)) return true;
41860
+ cursor = acc.target;
41861
+ }
41862
+ return false;
41863
+ }
41744
41864
  function get_param_reg(name, status) {
41745
41865
  return status.function_param_regs?.get(name);
41746
41866
  }
@@ -42140,9 +42260,11 @@ function build_access_field(node, status) {
42140
42260
  emit_string_length(node.target, status);
42141
42261
  return;
42142
42262
  }
42143
- const offset = compute_field_offset(node, status);
42263
+ let offset = compute_field_offset(node, status);
42144
42264
  const base = get_base_target(node);
42145
- const target_is_class_access = node.target.node_type === "access" && node.target.access.node_type === "access_field" && !!status.structs.find((s) => s.name === node.target.access.type?.name && s.is_class);
42265
+ const target_access_field = node.target.node_type === "access" && node.target.access.node_type === "access_field" ? node.target.access : void 0;
42266
+ const target_class_field_type = target_access_field ? resolve_field_type(target_access_field, resolve_access_target_type(node.target.target, status)?.name, status) : void 0;
42267
+ const target_is_class_access = !!target_class_field_type?.name && !!status.structs.find((s) => s.name === target_class_field_type.name && s.is_class);
42146
42268
  if (node.target.node_type === "access" && node.target.access.node_type === "access_func") {
42147
42269
  build_node(node.target, status);
42148
42270
  ensure_newline(status);
@@ -42173,8 +42295,13 @@ function build_access_field(node, status) {
42173
42295
  build_node(node.target, status);
42174
42296
  ensure_newline(status);
42175
42297
  const final_offset = get_field_offset(target_type?.name || "", access_field.name, status);
42176
- const field_type = access_field.type?.name || "";
42177
- if (field_type === "string" || access_field.type?.is_view) {
42298
+ const class_access_field_type = status.structs.find((s) => s.name === (target_type?.name || "") && !s.is_simple_type)?.fields.find((f) => f.name === access_field.name)?.type ?? resolve_field_type(access_field, target_type?.name, status);
42299
+ const field_type = class_access_field_type?.type_args?.length ? mono_type_name(class_access_field_type) : class_access_field_type?.name || access_field.type?.name || "";
42300
+ if (!!field_type && !class_access_field_type?.is_ref && !class_access_field_type?.is_nullable && is_struct_type(field_type, status)) {
42301
+ if (final_offset > 0) emit_asm(status, `add x0, x0, #${final_offset}\n`);
42302
+ return;
42303
+ }
42304
+ if (field_type === "string" || class_access_field_type?.is_view) {
42178
42305
  emit_string_pair_load_at(status, "x0", final_offset);
42179
42306
  return;
42180
42307
  }
@@ -42204,14 +42331,14 @@ function build_access_field(node, status) {
42204
42331
  if (paramReg !== "x0") emit_asm(status, `mov x0, ${paramReg}\n`);
42205
42332
  } else emit_var_load(status, "x0", name, 8);
42206
42333
  const final_offset = get_field_offset(target_type?.name || "", access_field.name, status);
42207
- const field_type_obj = resolve_field_type(access_field, target_type?.name, status);
42208
- const resolved_field_type = field_type_obj?.name || "";
42334
+ const field_type_obj = status.structs.find((s) => s.name === (target_type?.name || "") && !s.is_simple_type)?.fields.find((f) => f.name === access_field.name)?.type ?? resolve_field_type(access_field, target_type?.name, status);
42335
+ const resolved_field_type = field_type_obj?.type_args?.length ? mono_type_name(field_type_obj) : field_type_obj?.name || "";
42209
42336
  if (!!resolved_field_type && !field_type_obj?.is_ref && !field_type_obj?.is_nullable && is_struct_type(resolved_field_type, status)) {
42210
42337
  if (final_offset > 0) emit_asm(status, `add x0, x0, #${final_offset}\n`);
42211
42338
  return;
42212
42339
  }
42213
- const field_type = access_field.type?.name || "";
42214
- if (field_type === "string" || access_field.type?.is_view) {
42340
+ const field_type = resolved_field_type;
42341
+ if (field_type === "string" || field_type_obj?.is_view) {
42215
42342
  emit_string_pair_load_at(status, "x0", final_offset);
42216
42343
  return;
42217
42344
  }
@@ -42222,16 +42349,25 @@ function build_access_field(node, status) {
42222
42349
  else emit_asm(status, `ldr x0, [x0, #${final_offset}]\n`);
42223
42350
  return;
42224
42351
  }
42225
- if (base.node_type === "value") {
42226
- const name = base.value;
42227
- const paramReg = get_param_reg(name, status);
42228
- if (paramReg) {
42229
- if (paramReg !== "x0") emit_asm(status, `mov x0, ${paramReg}\n`);
42230
- } else if (is_local_ref_var(name, status)) emit_deref_var_address(status, "x0", name);
42231
- else emit_var_address(status, "x0", name);
42232
- } else {
42233
- build_node(base, status);
42352
+ let target_built = false;
42353
+ if (node.target.node_type === "access" && access_chain_crosses_class(node, status)) {
42354
+ build_node(node.target, status);
42234
42355
  ensure_newline(status);
42356
+ offset = get_field_offset(target_type?.name || "", access_field.name, status);
42357
+ target_built = true;
42358
+ }
42359
+ if (!target_built) {
42360
+ if (base.node_type === "value") {
42361
+ const name = base.value;
42362
+ const paramReg = get_param_reg(name, status);
42363
+ if (paramReg) {
42364
+ if (paramReg !== "x0") emit_asm(status, `mov x0, ${paramReg}\n`);
42365
+ } else if (is_local_ref_var(name, status)) emit_deref_var_address(status, "x0", name);
42366
+ else emit_var_address(status, "x0", name);
42367
+ } else {
42368
+ build_node(base, status);
42369
+ ensure_newline(status);
42370
+ }
42235
42371
  }
42236
42372
  const field_type_obj = resolve_field_type(access_field, target_type?.name, status);
42237
42373
  const resolved_field_type = field_type_obj?.name || "";
@@ -42758,20 +42894,25 @@ function build_access_method(node, access_func, status) {
42758
42894
  } else if (!target_is_simple && node.target.node_type === "access") {
42759
42895
  const access_target = node.target;
42760
42896
  if (access_target.access.node_type === "access_field") {
42761
- const offset = compute_field_offset(access_target, status);
42762
- const base = get_base_target(access_target);
42763
- if (base.node_type === "value") {
42764
- const name = base.value;
42765
- const paramReg = get_param_reg(name, status);
42766
- if (paramReg) {
42767
- if (paramReg !== "x0") emit_asm(status, `mov x0, ${paramReg}\n`);
42768
- } else if (is_local_ref_var(name, status)) emit_deref_var_address(status, "x0", name);
42769
- else emit_var_address(status, "x0", name);
42770
- } else {
42771
- build_node(base, status);
42897
+ if (access_chain_crosses_class(access_target, status)) {
42898
+ build_node(access_target, status);
42772
42899
  ensure_newline(status);
42900
+ } else {
42901
+ const offset = compute_field_offset(access_target, status);
42902
+ const base = get_base_target(access_target);
42903
+ if (base.node_type === "value") {
42904
+ const name = base.value;
42905
+ const paramReg = get_param_reg(name, status);
42906
+ if (paramReg) {
42907
+ if (paramReg !== "x0") emit_asm(status, `mov x0, ${paramReg}\n`);
42908
+ } else if (is_local_ref_var(name, status)) emit_deref_var_address(status, "x0", name);
42909
+ else emit_var_address(status, "x0", name);
42910
+ } else {
42911
+ build_node(base, status);
42912
+ ensure_newline(status);
42913
+ }
42914
+ if (offset > 0) emit_asm(status, `add x0, x0, #${offset}\n`);
42773
42915
  }
42774
- if (offset > 0) emit_asm(status, `add x0, x0, #${offset}\n`);
42775
42916
  if (target_type.is_array && target_type.is_array_heap) {
42776
42917
  emit_asm(status, `ldr x0, [x0]\n`);
42777
42918
  emit_asm(status, `add x0, x0, #8\n`);
@@ -42793,6 +42934,13 @@ function build_access_method(node, access_func, status) {
42793
42934
  trait_recv_slot = allocate_stack_space(status, 8, 8);
42794
42935
  emit_asm(status, `str x0, [x29, #${trait_recv_slot}]\n`);
42795
42936
  }
42937
+ const ctor_temp_node = node.target.node_type === "func_call" ? node.target : void 0;
42938
+ const ctor_temp_receiver = !!(ctor_temp_node && (ctor_temp_node.is_thread_ctor || ctor_temp_node.is_fiber_ctor || ctor_temp_node.is_awaitable_ctor));
42939
+ let ctor_recv_slot;
42940
+ if (ctor_temp_receiver && !access_func.is_static && !receiver_is_string) {
42941
+ ctor_recv_slot = allocate_stack_space(status, 8, 8);
42942
+ emit_asm(status, `str x0, [x29, #${ctor_recv_slot}]\n`);
42943
+ }
42796
42944
  const raw_needs_self = !access_func.is_static && access_func.params.length > 0 && !receiver_is_string;
42797
42945
  const inline_func0 = status.structs.find((s) => s.name === mono_struct_name)?.functions.find((f) => f.is_inline && f.name === access_func.name && (access_func.mangled_name ? mangled_label(f, mono_struct_name) === access_func.mangled_name : true));
42798
42946
  const elide_self_save = raw_needs_self && !!inline_func0 && naked_inline_skips_self(inline_func0, status.platform);
@@ -43010,6 +43158,14 @@ function build_access_method(node, access_func, status) {
43010
43158
  }
43011
43159
  if (outgoing_size > 0) emit_asm(status, `add sp, sp, #${outgoing_size}\n`);
43012
43160
  emit_dispose_lambda_args_a64(status, lambda_arg_slots);
43161
+ if (ctor_recv_slot !== void 0) {
43162
+ emit_asm(status, `str x0, [sp, #-16]!\n`);
43163
+ emit_asm(status, `str x1, [sp, #-16]!\n`);
43164
+ emit_asm(status, `ldr x0, [x29, #${ctor_recv_slot}]\n`);
43165
+ emit_free(status);
43166
+ emit_asm(status, `ldr x1, [sp], #16\n`);
43167
+ emit_asm(status, `ldr x0, [sp], #16\n`);
43168
+ }
43013
43169
  if (frees_string_receiver) {
43014
43170
  emit_asm(status, `ldr x9, [sp]\n`);
43015
43171
  emit_asm(status, `str x0, [sp, #-16]!\n`);
@@ -43415,15 +43571,22 @@ function scan_spawn_callees(root, result) {
43415
43571
  if (node.node_type === "spawn") {
43416
43572
  spawn_call = node.call;
43417
43573
  spawn_ret = node.function_return_type;
43574
+ } else if (node.node_type === "func_call") {
43575
+ if (node.is_thread_ctor || node.is_fiber_ctor) {
43576
+ spawn_call = node.params?.[0];
43577
+ spawn_ret = node.function_return_type;
43578
+ }
43418
43579
  } else if (node.node_type === "access" && node.access?.node_type === "access_func") {
43419
- if (node.access.is_thread_start || node.access.is_fiber_start) {
43420
- spawn_call = node.target?.params?.[0];
43421
- spawn_ret = node.access.function_return_type;
43422
- } else if (node.access.is_nursery_spawn) {
43580
+ if (node.access.is_nursery_spawn) {
43423
43581
  spawn_call = node.access.params?.[0]?.params?.[0];
43424
43582
  spawn_ret = node.access.function_return_type;
43425
43583
  }
43426
43584
  }
43585
+ if (spawn_call && spawn_call.node_type === "func") {
43586
+ mark_fn_value_callees(spawn_call, spawn_ret, result);
43587
+ spawn_call = void 0;
43588
+ spawn_ret = void 0;
43589
+ }
43427
43590
  const name_and_ret = spawn_call && spawn_call.node_type === "func_call" ? [emission_label(spawn_call.resolved_function ?? { name: spawn_call.name }), spawn_ret ?? {}] : void 0;
43428
43591
  if (name_and_ret && name_and_ret[0] && name_and_ret[1].name === "string") result.add(name_and_ret[0]);
43429
43592
  for (const key of Object.keys(node)) {
@@ -43435,6 +43598,34 @@ function scan_spawn_callees(root, result) {
43435
43598
  };
43436
43599
  visit(root);
43437
43600
  }
43601
+ /**
43602
+ * Heap-normalize a function-value spawn's lambda (spawn_ret is the
43603
+ * construction's function return type; the lambda's own signature is the
43604
+ * fallback). Two marks:
43605
+ *
43606
+ * 1. The LAMBDA's emission label — a literal or borrowed-variable return
43607
+ * inside the lambda is then strdup'd at the lambda's return site (the
43608
+ * return build classifies through the same label).
43609
+ * 2. Every string-returning call the lambda returns DIRECTLY
43610
+ * (`() => fn(args)`): the call's pair flows to the slot unchanged (a call
43611
+ * result is not re-strdup'd at return), so the callee itself must
43612
+ * normalize — the same mark the direct-call form puts on its callee.
43613
+ */
43614
+ function mark_fn_value_callees(lambda, spawn_ret, result) {
43615
+ if ((spawn_ret?.name ?? lambda.return_type?.name) !== "string") return;
43616
+ result.add(emission_label(lambda));
43617
+ const visit = (node) => {
43618
+ if (!node || typeof node !== "object") return;
43619
+ if (node.node_type === "return" && node.value?.node_type === "func_call" && node.value.type?.name === "string") result.add(emission_label(node.value.resolved_function ?? { name: node.value.name }));
43620
+ for (const key of Object.keys(node)) {
43621
+ if (key === "parent" || key === "scope") continue;
43622
+ const val = node[key];
43623
+ if (Array.isArray(val)) for (const item of val) visit(item);
43624
+ else if (val && typeof val === "object") visit(val);
43625
+ }
43626
+ };
43627
+ for (const s of lambda.statements ?? []) visit(s);
43628
+ }
43438
43629
  function scan_statements(statements, result, struct_name) {
43439
43630
  for (const stmt of statements) {
43440
43631
  if (stmt.node_type === "struct") {
@@ -44072,6 +44263,8 @@ function build(root, options = {}) {
44072
44263
  status.code = reduce_pointer_walks(status.code);
44073
44264
  status.code = unroll_loops_x2(status.code);
44074
44265
  status.code = eliminate_dead_cycle_moves(status.code);
44266
+ status.code = expand_large_mov_immediates(status.code);
44267
+ status.code = expand_far_adr_addresses(status.code);
44075
44268
  if (options.audit) {
44076
44269
  status.code = status.code.replaceAll("bl _malloc\n", "bl _nomen_malloc_wrap\n");
44077
44270
  status.code = status.code.replaceAll("bl _calloc\n", "bl _nomen_calloc_wrap\n");
@@ -44235,7 +44428,9 @@ const CALL_KEYWORDS = /* @__PURE__ */ new Set([
44235
44428
  "trait",
44236
44429
  "enum",
44237
44430
  "bitset",
44238
- "cast"
44431
+ "cast",
44432
+ "panic",
44433
+ "todo"
44239
44434
  ]);
44240
44435
  const NO_SPACE_BEFORE = /* @__PURE__ */ new Set([
44241
44436
  ",",
@@ -44316,8 +44511,8 @@ function needs_space(pieces, i) {
44316
44511
  if (NO_SPACE_AFTER.has(previous.text)) return false;
44317
44512
  if (previous.text === "out" || previous.text === "in") return current.text !== ".";
44318
44513
  if (is_sign(pieces, i - 1)) return false;
44319
- if (current.text === ".") return !is_value_end(previous);
44320
- if (current.text === "(" || current.text === "[") return !is_value_end(previous) && !CALL_KEYWORDS.has(previous.text);
44514
+ if (current.text === ".") return !is_value_end(pieces, i - 1);
44515
+ if (current.text === "(" || current.text === "[") return !is_value_end(pieces, i - 1) && !CALL_KEYWORDS.has(previous.text);
44321
44516
  if (current.kind === "comment") return current.space_before;
44322
44517
  return true;
44323
44518
  }
@@ -44330,13 +44525,20 @@ function is_sign(pieces, i) {
44330
44525
  if (PREFIX_POSITIONS.has(previous.text)) return true;
44331
44526
  return previous.kind === "word" && KEYWORDS.has(previous.text);
44332
44527
  }
44333
- /** Can the piece end a value, so that a following `(`, `[` or `.` binds to it? */
44334
- function is_value_end(piece) {
44528
+ /**
44529
+ * Can the piece at `i` end a value, so that a following `(`, `[` or `.` binds
44530
+ * to it? A word after `.` is a member name, so it binds even when it spells a
44531
+ * keyword (`old_keys.move(i)`).
44532
+ */
44533
+ function is_value_end(pieces, i) {
44534
+ const piece = pieces[i];
44335
44535
  switch (piece.kind) {
44336
44536
  case "number":
44337
44537
  case "string":
44338
44538
  case "char": return true;
44339
- case "word": return !KEYWORDS.has(piece.text);
44539
+ case "word":
44540
+ if (i > 0 && pieces[i - 1].text === ".") return true;
44541
+ return !KEYWORDS.has(piece.text);
44340
44542
  case "symbol": return piece.text === ")" || piece.text === "]" || piece.text === ">" || piece.text === "?";
44341
44543
  default: return false;
44342
44544
  }
@@ -45055,12 +45257,12 @@ function is_list_open(pieces, i) {
45055
45257
  const bracket = pieces[i].text;
45056
45258
  if (bracket === "(") {
45057
45259
  const previous = pieces[i - 1];
45058
- return !!previous && is_value_end(previous) && previous.text !== "=";
45260
+ return !!previous && is_value_end(pieces, i - 1) && previous.text !== "=";
45059
45261
  }
45060
45262
  if (bracket === "[") {
45061
45263
  const previous = pieces[i - 1];
45062
45264
  if (previous?.kind === "word" && KEYWORDS.has(previous.text)) return false;
45063
- return !previous || !is_value_end(previous);
45265
+ return !previous || !is_value_end(pieces, i - 1);
45064
45266
  }
45065
45267
  return false;
45066
45268
  }
@@ -45133,9 +45335,7 @@ function is_redundant(type, value) {
45133
45335
  if (value.length > type.length && value[type.length].text === "(") {
45134
45336
  if (match_bracket(value, type.length) === value.length - 1) {
45135
45337
  const constructor = value.slice(0, type.length);
45136
- if (!constructor.some((piece) => piece.text === "<" || piece.text === ">")) {
45137
- if (type.every((piece, index) => piece.text === constructor[index].text)) return true;
45138
- }
45338
+ if (type.every((piece, index) => piece.text === constructor[index].text)) return true;
45139
45339
  }
45140
45340
  }
45141
45341
  if (value.length === 1 && type.length === 1) return literal_type(value[0]) === type[0].text && SAFE_LITERAL_TYPES.has(type[0].text);
@@ -48902,8 +49102,17 @@ function check_declaration_node(decl, status) {
48902
49102
  const src_sv = /^[A-Za-z_][A-Za-z0-9_]*$/.test(src_name ?? "") ? status.values.findLast((v) => v.name === src_name) : void 0;
48903
49103
  if (src_sv && !src_sv.trait_slot_conformer && src_sv.type.name === decl.type.name) add_error(status, `a class-backed trait local cannot be copied — reassign through the original binding or declare a new class instance`, decl.value.start);
48904
49104
  }
48905
- if (!decl.type.name) decl.type = type_from_value_node(decl.value, status);
48906
- else if (decl.type.is_array && decl.value.node_type === "array") {
49105
+ if (!decl.type.name) {
49106
+ decl.type = type_from_value_node(decl.value, status);
49107
+ if (!decl.type.name && status.type_params.length > 0 && decl.value.node_type === "func_call") {
49108
+ const call = decl.value;
49109
+ if (call.type_args?.length) {
49110
+ const constructed = new Type(call.name);
49111
+ constructed.type_args = call.type_args;
49112
+ decl.type = constructed;
49113
+ }
49114
+ }
49115
+ } else if (decl.type.is_array && decl.value.node_type === "array") {
48907
49116
  const value_type = type_from_value_node(decl.value, status);
48908
49117
  if (value_type.is_array && value_type.length) decl.type.length = value_type.length;
48909
49118
  }
@@ -49473,20 +49682,25 @@ function infer_field_type_from_value(value, status) {
49473
49682
  * Resolving inferred field types upfront (right after gather_structs registers
49474
49683
  * every struct) makes them visible regardless of declaration order.
49475
49684
  *
49476
- * Only non-generic structs are handled here, mirroring check_struct_node's own
49477
- * field-type rewrite. The inference is structural to avoid re-running the full
49478
- * declaration check and its side effects; check_struct_node later skips
49479
- * already-resolved fields, so each field is resolved exactly once.
49685
+ * Only the field-type inference runs for generic structs too: a generic
49686
+ * field's initializer names the constructed type in terms of the struct's own
49687
+ * type params (`var items = Buffer<T>()` → `Buffer<T>`), which is exactly the
49688
+ * annotation it replaces, so recording it upfront is safe and keeps it visible
49689
+ * to the ownership classifier and to monomorphization. The buffer rewrite and
49690
+ * owning-param marking stay non-generic, mirroring check_struct_node. The
49691
+ * inference is structural to avoid re-running the full declaration check and
49692
+ * its side effects; check_struct_node later skips already-resolved fields, so
49693
+ * each field is resolved exactly once.
49480
49694
  */
49481
49695
  function resolve_struct_field_types(status) {
49482
49696
  for (const struct of status.structs) {
49483
- if (struct.is_generic) continue;
49484
49697
  for (const field of struct.fields) {
49485
49698
  if (field.type.name) continue;
49486
49699
  if (!field.value) continue;
49487
49700
  const t = infer_field_type_from_value(field.value, status);
49488
49701
  if (t?.name) field.type = t;
49489
49702
  }
49703
+ if (struct.is_generic) continue;
49490
49704
  rewrite_generic_buffer_fields(struct, status);
49491
49705
  mark_owning_auto_init_params(struct, status);
49492
49706
  }
@@ -49502,8 +49716,9 @@ function resolve_struct_field_types(status) {
49502
49716
  *
49503
49717
  * A trait method with a default body need not be overridden; if it isn't,
49504
49718
  * the default is inherited (and, for generic traits, already synthesized
49505
- * onto the struct by synthesize_generic_trait_defaults). `#init` and
49506
- * `#destroy` are lifecycle hooks, not contract methods, so they're skipped.
49719
+ * onto the struct by synthesize_generic_trait_defaults). `#init`,
49720
+ * `#destroy` and `#spawn` are lifecycle hooks, not contract methods, so
49721
+ * they're skipped.
49507
49722
  */
49508
49723
  function check_trait_conformance(struct, status) {
49509
49724
  for (let i = 0; i < struct.traits.length; i++) {
@@ -49514,7 +49729,7 @@ function check_trait_conformance(struct, status) {
49514
49729
  const substitution = /* @__PURE__ */ new Map();
49515
49730
  if (args && trait.type_params.length === args.length) for (let j = 0; j < trait.type_params.length; j++) substitution.set(trait.type_params[j], args[j].name);
49516
49731
  for (const trait_func of trait.functions) {
49517
- if (trait_func.name === "#init" || trait_func.name === "#destroy") continue;
49732
+ if (trait_func.name === "#init" || trait_func.name === "#destroy" || trait_func.name === "#spawn") continue;
49518
49733
  const overrides = struct.functions.filter((f) => f.name === trait_func.name);
49519
49734
  if (overrides.length === 0) {
49520
49735
  if (!trait_func.has_body) add_error(status, `Type '${struct.name}' does not conform to trait '${trait.name}': missing required method '${trait_func.name}'`, struct.start);
@@ -50265,54 +50480,6 @@ function check_function_node(func, status) {
50265
50480
  }
50266
50481
  }
50267
50482
  //#endregion
50268
- //#region ../src/check/utils/awaitable_spawn_class.ts
50269
- /**
50270
- * The spawn-handle field contract an Awaitable class must declare for the
50271
- * `X(fn(args))` construction sugar (ASYNC.md, "User-defined async
50272
- * primitives"). The sugar packs the wrapped call eagerly and stores the
50273
- * launch machinery in these fields — the same layout Thread/Fiber carry and
50274
- * their own launch methods consume; a user primitive's launch methods read
50275
- * the same contract.
50276
- */
50277
- const SPAWN_FIELD_CONTRACT = [
50278
- "task",
50279
- "result_slot",
50280
- "cancel_flag",
50281
- "future"
50282
- ];
50283
- /**
50284
- * Resolve the Awaitable-conforming CLASS a generalized spawn-sugar call
50285
- * (`X(fn(args))` / `X(() => work(n))`) targets. Undefined for everything
50286
- * else: non-classes, classes not conforming to the core `Awaitable` trait,
50287
- * and the reserved library names Thread/Fiber (whose own fast path runs
50288
- * first and whose user-named shadows mean a plain struct).
50289
- *
50290
- * The class must additionally carry the spawn-handle field contract
50291
- * (`spawn_field_contract_gaps`) and at most one type parameter before the
50292
- * sugar fires — the callers validate those and report dedicated errors.
50293
- */
50294
- function resolve_awaitable_spawn_class(name, status) {
50295
- if (name === "Thread" || name === "Fiber") return void 0;
50296
- const struct = resolve_declared_struct(name, status);
50297
- if (!struct || !struct.is_class) return void 0;
50298
- if (!struct.traits.includes("Awaitable")) return void 0;
50299
- return struct;
50300
- }
50301
- /**
50302
- * The class's spawn-field contract gaps: one human-readable entry per
50303
- * missing or mistyped handle field. Empty means the class can carry the
50304
- * packed task machinery.
50305
- */
50306
- function spawn_field_contract_gaps(struct) {
50307
- const gaps = [];
50308
- for (const field_name of SPAWN_FIELD_CONTRACT) {
50309
- const field = struct.fields.find((f) => f.name === field_name);
50310
- if (!field) gaps.push(`missing field '${field_name}'`);
50311
- else if (field.type?.name !== "uint64") gaps.push(`field '${field_name}' must be uint64, got '${field.type?.name || "<none>"}'`);
50312
- }
50313
- return gaps;
50314
- }
50315
- //#endregion
50316
50483
  //#region ../src/check/utils/enum_mono.ts
50317
50484
  /**
50318
50485
  * Monomorphize a generic enum (`enum Result<T, E>`) applied to concrete type
@@ -50496,39 +50663,37 @@ function synthesize_lambda_name(func, status) {
50496
50663
  //#endregion
50497
50664
  //#region ../src/check/utils/validate_spawn_args_sendable.ts
50498
50665
  /**
50499
- * Validate a spawn's packed arguments (CLOSURE.md Phase 3d).
50666
+ * Validate a spawn's packed arguments (CLOSURE.md Phase 3d; the shrunk
50667
+ * `Sendable` — docs/ASYNC.md, "Design decisions").
50668
+ *
50669
+ * `Sendable` gates exactly the case that can race: a SHARED class/trait
50670
+ * reference crossing the task boundary (a plain argument — the packed env
50671
+ * aliases the instance). Everything else is exempt:
50500
50672
  *
50501
- * Every argument must be Sendable — EXCEPT, inside a nursery
50502
- * (`allow_borrows`), a non-Sendable CLASS or TRAIT argument that is a named
50503
- * local or parameter: it is a BORROW capture (the env aliases the instance
50504
- * for the task's lifetime, which the nursery's join-at-exit bounds by the
50505
- * donors' lifetimes). Temps and field accesses are not borrowable — a temp
50506
- * dies at the statement, and a field's owner may be shallower than the
50507
- * task.
50673
+ * - MOVED arguments: the callee's `move T` parameter takes exclusive
50674
+ * ownership, so no one else can touch the instance concurrently;
50675
+ * - scalars, strings, and everything `is_sendable_type` accepts (copied or
50676
+ * safe by value — strings are deep-copied at pack);
50677
+ * - owning VALUE structs whose fields are all safe (the pack deep-copies
50678
+ * them; `is_sendable_type`'s field recursion rejects a struct with an
50679
+ * unsafe class field).
50680
+ *
50681
+ * The nursery-borrow exception (a non-Sendable class aliased into a task
50682
+ * inside `async { }`) is RETIRED: mark the class `Sendable`, move it in
50683
+ * (a `move` parameter), or share it through a `Sendable` primitive
50684
+ * (`Mutex`/`Channel`).
50508
50685
  */
50509
- function validate_spawn_args_sendable(call, status, allow_borrows = false, reason) {
50510
- const result = { borrow_args: false };
50511
- for (const param of call.params) {
50686
+ function validate_spawn_args_sendable(call, status, reason) {
50687
+ const callee_params = call.resolved_function?.params?.filter((p) => !p.is_self_param);
50688
+ for (let i = 0; i < call.params.length; i++) {
50689
+ const param = call.params[i];
50512
50690
  let arg_type = type_from_value_node(param, status);
50513
50691
  const stamped = param.type;
50514
50692
  if (!arg_type.name && stamped?.name) arg_type = stamped;
50693
+ if (callee_params?.[i]?.is_moved) continue;
50515
50694
  if (is_sendable_type(arg_type.name, status)) continue;
50516
- const donor = param.node_type === "value" ? status.values.findLast((v) => v.name === param.value) : void 0;
50517
- const is_pointer_arg = (() => {
50518
- const struct = status.structs.find((s) => s.name === arg_type.name);
50519
- return !!struct && (struct.is_class || struct.traits.length > 0);
50520
- })();
50521
- if (allow_borrows && is_pointer_arg && donor) {
50522
- result.borrow_args = true;
50523
- continue;
50524
- }
50525
- if (allow_borrows && is_pointer_arg && !donor) {
50526
- add_error(status, `Spawn argument '${arg_type.name}' is not Sendable and may only be borrowed from a named local or parameter (a temporary would die before the task runs)`, param.start);
50527
- continue;
50528
- }
50529
- add_error(status, `Spawn argument of type ${arg_type.name || "<unknown>"} is not Sendable${is_pointer_arg && !allow_borrows ? reason ?? " — a non-Sendable class may only be passed inside a nursery (async { }), where the join bounds the borrow" : ""}`, param.start);
50695
+ add_error(status, `Spawn argument of type ${arg_type.name || "<unknown>"} is not Sendable${reason ?? " — a shared class/trait reference crossing a task boundary must be marked Sendable; move it in (a `move` parameter) or share it through a Sendable primitive (Mutex/Channel)"}`, param.start);
50530
50696
  }
50531
- return result;
50532
50697
  }
50533
50698
  //#endregion
50534
50699
  //#region ../src/check/check_function_call_node.ts
@@ -50548,28 +50713,17 @@ function find_free_function(status, name) {
50548
50713
  return status.functions.findLast((f) => f.name === name && !is_method(f)) ?? status.functions.findLast((f) => f.name === name);
50549
50714
  }
50550
50715
  function check_function_call_node(node, status) {
50551
- const magic_ctor = node.name === "Thread" ? "Thread" : node.name === "Fiber" && !!resolve_declared_struct("Fiber", status)?.is_library ? "Fiber" : void 0;
50552
- const thread_shadow_struct = resolve_declared_struct("Thread", status);
50716
+ const magic_ctor = resolve_declared_struct(node.name, status)?.functions.some((f) => f.name === "#spawn") ? node.name : void 0;
50553
50717
  const first = node.params[0];
50554
50718
  const first_decl = first?.node_type === "value" && typeof first.value === "string" ? status.values.findLast((v) => v.name === first.value) : void 0;
50555
50719
  const first_fn = first?.node_type === "value" && typeof first.value === "string" ? status.functions.findLast((f) => f.name === first.value) : void 0;
50556
50720
  const func_value_arg = first?.node_type === "func" || first?.node_type === "value" && typeof first.value === "string" && (first_decl?.func_params !== void 0 || !!first_fn);
50557
50721
  const spawn_ctor_shape = node.params.length === 1 && (node.params[0].node_type === "func_call" || func_value_arg) && !find_free_function(status, node.name);
50558
- let awaitable_ctor;
50559
- if (!magic_ctor && spawn_ctor_shape) {
50560
- const candidate = resolve_awaitable_spawn_class(node.name, status);
50561
- if (candidate) {
50562
- const gaps = spawn_field_contract_gaps(candidate);
50563
- if (gaps.length === 0 && candidate.type_params.length <= 1) awaitable_ctor = candidate;
50564
- else {
50565
- const why = gaps.length ? `it lacks the spawn-field contract (${gaps.join("; ")})` : `it has ${candidate.type_params.length} type parameters (the sugar types the class as C<T> with T the wrapped call's return type)`;
50566
- add_error(status, `'${node.name}' conforms to Awaitable but cannot take the ${node.name}(fn(args)) construction sugar: ${why}`, node.start);
50567
- return false;
50568
- }
50569
- }
50722
+ if (magic_ctor && !spawn_ctor_shape && !find_free_function(status, node.name)) {
50723
+ add_error(status, "`#spawn` expects a call or a zero-argument function value (bind arguments in the lambda's captures)", first?.start ?? node.start);
50724
+ return false;
50570
50725
  }
50571
- if (magic_ctor && spawn_ctor_shape && !(magic_ctor === "Thread" && thread_shadow_struct && !thread_shadow_struct.is_library)) return check_magic_ctor(node, status, magic_ctor);
50572
- if (awaitable_ctor) return check_magic_ctor(node, status, awaitable_ctor.name);
50726
+ if (magic_ctor && spawn_ctor_shape) return check_magic_ctor(node, status, magic_ctor);
50573
50727
  let func = find_free_function(status, node.name);
50574
50728
  if (func?.is_closure && func.captures?.length) func = void 0;
50575
50729
  if (!func) {
@@ -50734,7 +50888,18 @@ function monomorphize(generic_struct, type_args, status) {
50734
50888
  const mono_fields = generic_struct.fields.map((field) => {
50735
50889
  const resolved_type = substitute_type(field.type, substitution);
50736
50890
  const mono_field = new DeclarationNode(field.start, field.visibility, field.declaration, field.name, resolved_type, field.value ? clone_node(field.value) : void 0);
50737
- if (mono_field.value) substitute_raw_in_node(mono_field.value, substitution, status.structs, void 0, status.traits, status.enums);
50891
+ if (mono_field.value) {
50892
+ if (!mono_field.type.name && mono_field.value.node_type === "func_call") {
50893
+ const call = mono_field.value;
50894
+ const target = status.structs.find((s) => s.name === call.name);
50895
+ if (target?.type_params.length && call.type_args?.length === target.type_params.length) {
50896
+ const constructed = new Type(call.name);
50897
+ constructed.type_args = call.type_args.map((arg) => substitute_type(arg, substitution));
50898
+ mono_field.type = constructed;
50899
+ }
50900
+ }
50901
+ substitute_raw_in_node(mono_field.value, substitution, status.structs, void 0, status.traits, status.enums);
50902
+ }
50738
50903
  mono_field.is_library = field.is_library;
50739
50904
  mono_field.is_readonly = field.is_readonly;
50740
50905
  mono_field.is_view_keyword = field.is_view_keyword;
@@ -50761,6 +50926,7 @@ function monomorphize(generic_struct, type_args, status) {
50761
50926
  mono_struct.source_type_args = flat_args;
50762
50927
  mono_struct.is_class = generic_struct.is_class;
50763
50928
  mono_struct.is_library = generic_struct.is_library;
50929
+ mono_struct.trait_args = (generic_struct.trait_args ?? []).map((args) => args?.map((t) => substitute_type(t, substitution)));
50764
50930
  status.structs.push(mono_struct);
50765
50931
  status.types.push(mono_name);
50766
50932
  const cloned_methods = [];
@@ -51215,14 +51381,7 @@ function resolve_free_calls_in_node(node, status) {
51215
51381
  const any_node = node;
51216
51382
  if (node.node_type === "func_call" && !any_node.resolved_function) {
51217
51383
  const name = node.name;
51218
- if (name === "Thread" || name === "Fiber") {
51219
- const fiber_struct = resolve_declared_struct("Fiber", status);
51220
- const magic = name === "Thread" ? "Thread" : fiber_struct?.is_library === true ? "Fiber" : void 0;
51221
- if (magic) rederive_magic_ctor_annotations(node, status, magic);
51222
- } else {
51223
- const candidate = resolve_awaitable_spawn_class(name, status);
51224
- if (candidate && spawn_field_contract_gaps(candidate).length === 0 && candidate.type_params.length <= 1) rederive_magic_ctor_annotations(node, status, candidate.name);
51225
- }
51384
+ if (resolve_declared_struct(name, status)?.functions.some((f) => f.name === "#spawn")) rederive_magic_ctor_annotations(node, status, name);
51226
51385
  const func = find_free_function(status, name);
51227
51386
  if (func) set_resolved_function(node, func);
51228
51387
  }
@@ -51281,18 +51440,6 @@ function derive_annotations_for_access_func(access_node, fc, status) {
51281
51440
  rederive_nursery_spawn_annotations(fc, status);
51282
51441
  return;
51283
51442
  }
51284
- if (receiver_type === "Thread" && fc.name === "start") {
51285
- rederive_spawn_start_annotations(access_node, fc, status, "Thread", false);
51286
- return;
51287
- }
51288
- if (receiver_type === "Thread" && fc.name === "detach") {
51289
- rederive_spawn_detach_annotations(access_node, fc, status);
51290
- return;
51291
- }
51292
- if (receiver_type === "Fiber" && (fc.name === "start" || fc.name === "start_on")) {
51293
- rederive_spawn_start_annotations(access_node, fc, status, "Fiber", fc.name === "start_on");
51294
- return;
51295
- }
51296
51443
  const struct = status.structs.find((s) => s.name === receiver_type);
51297
51444
  if (!struct) return;
51298
51445
  const func = struct.functions.find((f) => f.name === fc.name);
@@ -51377,7 +51524,7 @@ function check_magic_ctor(node, status, name) {
51377
51524
  add_error(status, `Spawned call '${call.name}' did not resolve`, node.start);
51378
51525
  return false;
51379
51526
  }
51380
- validate_spawn_args_sendable(call, status, (status.nursery_depth ?? 0) > 0);
51527
+ validate_spawn_args_sendable(call, status);
51381
51528
  stamp_spawn_ctor(node, name);
51382
51529
  const return_type = call.type;
51383
51530
  const result_type_arg = return_type && return_type.name && return_type.name !== "void" && return_type.name !== "?" ? new Type(return_type.name) : new Type("uint64");
@@ -51402,7 +51549,8 @@ function stamp_spawn_ctor(node, name) {
51402
51549
  * nothing) — carries T on the type args, which the `.start()` /
51403
51550
  * nursery paths read. Also materializes Task<T>: the launch seam a user
51404
51551
  * primitive drives is Task's runtime (Task.pool_submit / future_* —
51405
- * ASYNC.md, "User-defined async primitives"), and statics on the bare
51552
+ * docs/ASYNC.md, "Extension: a class with its own `#spawn`"), and
51553
+ * statics on the bare
51406
51554
  * generic only link once a mono instantiation exists (the same
51407
51555
  * materialization a `.start()` launch performs). */
51408
51556
  function stamp_spawn_type_args(node, status, name, result_type_arg) {
@@ -51554,61 +51702,6 @@ function rederive_nursery_spawn_annotations(fc, status) {
51554
51702
  if (task_struct && task_struct.type_params.length > 0) monomorphize(task_struct, [result_type_arg], status);
51555
51703
  }
51556
51704
  /**
51557
- * Re-derive the annotations for a `Thread(fn(args)).start()` /
51558
- * `Fiber(fn(args)).start[_on](...)` call inside a monomorphised body —
51559
- * mirroring check_spawn_start (check_access_node). The build gates its
51560
- * spawn/fiber-trampoline emission on is_thread_start / is_fiber_start
51561
- * (without them the call falls through to method resolution, which finds no
51562
- * `start`) and the declaration-ownership analysis on owned_return (the Task
51563
- * a spawn yields is a fresh heap allocation, not a borrow).
51564
- */
51565
- function rederive_spawn_start_annotations(access_node, fc, status, name, _start_on) {
51566
- const ctor = access_node?.target;
51567
- const ctor_flagged = ctor?.node_type === "func_call" && (name === "Thread" ? ctor.is_thread_ctor : ctor.is_fiber_ctor);
51568
- if (name === "Thread") fc.is_thread_start = true;
51569
- else fc.is_fiber_start = true;
51570
- fc.owned_return = true;
51571
- let return_type;
51572
- if (ctor_flagged) {
51573
- const call = ctor.params[0];
51574
- return_type = ctor.function_return_type ?? call.type;
51575
- if (!return_type?.name) {
51576
- const func = find_free_function(status, call.name);
51577
- if (func) return_type = func.return_type;
51578
- }
51579
- } else return_type = (access_node?.target?.type)?.type_args?.[0];
51580
- if (!return_type?.name) return_type = new Type("uint64");
51581
- const is_void = !return_type.name || return_type.name === "void" || return_type.name === "?";
51582
- fc.function_return_type = is_void ? new Type("uint64") : return_type;
51583
- const result_type_arg = is_void ? new Type("uint64") : return_type;
51584
- if (!fc.type?.name) {
51585
- const task_type = new Type("Task");
51586
- task_type.type_args = [result_type_arg];
51587
- fc.type = task_type;
51588
- }
51589
- const task_struct = status.structs.find((s) => s.name === "Task");
51590
- if (task_struct && task_struct.type_params.length > 0) monomorphize(task_struct, [result_type_arg], status);
51591
- }
51592
- /**
51593
- * Re-derive the annotations for a `Thread(fn(args)).detach()` call inside a
51594
- * monomorphised body — mirroring check_spawn_detach (check_access_node).
51595
- * The build gates its detached-pthread emission on is_thread_detach.
51596
- */
51597
- function rederive_spawn_detach_annotations(access_node, fc, status) {
51598
- fc.is_thread_detach = true;
51599
- const ctor = access_node?.target;
51600
- if (ctor?.node_type === "func_call" && ctor.is_thread_ctor && ctor.params[0]) {
51601
- const call = ctor.params[0];
51602
- let return_type = ctor.function_return_type ?? call.type;
51603
- if (!return_type?.name) {
51604
- const func = find_free_function(status, call.name);
51605
- if (func) return_type = func.return_type;
51606
- }
51607
- fc.function_return_type = return_type;
51608
- } else fc.function_return_type = (access_node?.target?.type)?.type_args?.[0] ?? new Type("uint64");
51609
- if (!fc.type?.name) fc.type = new Type("void");
51610
- }
51611
- /**
51612
51705
  * Substitute type-param names through a function-typed parameter's nested
51613
51706
  * signature (`func (T, out T)` on a generic struct method or generic free
51614
51707
  * function). The signature hangs off the ParameterNode itself (`func_params`
@@ -51918,6 +52011,15 @@ function substitute_raw_in_node(node, substitution, structs, deref_params = /* @
51918
52011
  any_node.name = mono_type_name(route_reference_elem(any_node.name.endsWith(old_suffix) ? any_node.name.slice(0, any_node.name.length - old_suffix.length) : any_node.name, any_node.type_args), any_node.type_args);
51919
52012
  } else if (node.node_type === "access_func" && any_node.type_args?.length) any_node.type_args = any_node.type_args.map((t) => substitute_type(t, substitution));
51920
52013
  if (node.node_type === "access" && any_node.access?.type_args?.length) any_node.access.type_args = any_node.access.type_args.map((t) => substitute_type(t, substitution));
52014
+ if (node.node_type === "declare" && !any_node.type?.name && any_node.value?.node_type === "func_call") {
52015
+ const call = any_node.value;
52016
+ const target = structs.find((s) => s.name === call.name);
52017
+ if (target?.type_params.length && call.type_args?.length === target.type_params.length) {
52018
+ const constructed = new Type(call.name);
52019
+ constructed.type_args = call.type_args;
52020
+ any_node.type = constructed;
52021
+ }
52022
+ }
51921
52023
  if (node.node_type === "declare" && any_node.type?.name) {
51922
52024
  any_node.type = substitute_type(any_node.type, substitution);
51923
52025
  if (any_node.type.name === "Buffer" && any_node.type.type_args?.length === 1 && is_reference_type_arg(any_node.type.type_args[0], structs, traits)) any_node.type.name = "ClassBuffer";
@@ -53284,6 +53386,11 @@ function check_access_node(node, status) {
53284
53386
  status.allocations.push(decl);
53285
53387
  node.target = new ValueNode(node.target.start, decl.name, target_type);
53286
53388
  }
53389
+ if (node.target.node_type === "access" && node.target.access.node_type === "access_func" && node.target.access.owned_return && !!status.structs.find((s) => (s.name === target_type.name || target_type.type_args?.length && s.name === mono_type_name(target_type)) && s.is_class)) {
53390
+ const decl = new DeclarationNode(node.target.start, "private", "var", `_recv_${status.var_name_counter.value++}`, target_type, node.target);
53391
+ status.allocations.push(decl);
53392
+ node.target = new ValueNode(node.target.start, decl.name, target_type);
53393
+ }
53287
53394
  if (node.access.node_type === "access_field") {
53288
53395
  const af = node.access;
53289
53396
  if (af.is_destructure) {
@@ -53513,9 +53620,11 @@ function check_access_field_node(target_type, target, node, status) {
53513
53620
  }
53514
53621
  function check_access_function_node(target_type, target, node, status) {
53515
53622
  if (target_type.name === "Nursery" && node.name === "start") return check_nursery_spawn(node, status);
53516
- if (target_type.name === "Thread" && node.name === "start") return check_spawn_start(target, target_type, node, status, "Thread", false);
53517
- if (target_type.name === "Thread" && node.name === "detach") return check_spawn_detach(target, target_type, node, status);
53518
- if (target_type.name === "Fiber" && (node.name === "start" || node.name === "start_on")) return check_spawn_start(target, target_type, node, status, "Fiber", node.name === "start_on");
53623
+ if (target_type.name === "Fiber" && node.name === "start_on") {
53624
+ if (!check_spawn_start_on(target, target_type, node, status)) return false;
53625
+ node.name = "start";
53626
+ node.params = [];
53627
+ }
53519
53628
  if (target_type.is_view) {
53520
53629
  const elem_name = target_type.name === "string" ? "char" : target_type.name;
53521
53630
  if (node.name === "at" && node.params.length === 1) {
@@ -53788,77 +53897,32 @@ function check_nursery_spawn(node, status) {
53788
53897
  return true;
53789
53898
  }
53790
53899
  /**
53791
- * Check a `Thread(fn(args)).start()` / `Fiber(fn(args)).start[_on](buf)`
53792
- * call — the surface forms of a direct spawn (see ASYNC.md,
53793
- * CLOSURE.md Phase 3b). The receiver is the magic constructor
53794
- * (already checked — its args were packed eagerly) or any expression of
53795
- * the spawn class's type; this types the expression as `Task<T>`. For
53796
- * `start_on`, also validates the stack buffer: a fixed-size array of at
53797
- * least 16 KB. The build launches the receiver's packed task closure
53798
- * (submitting it to the pool or fiber scheduler) and yields Task<T>.
53799
- */
53800
- function check_spawn_start(target, target_type, node, status, kind, start_on) {
53801
- const ctor = target;
53802
- const wrapped_call = target.node_type === "func_call" && (kind === "Thread" ? ctor.is_thread_ctor : ctor.is_fiber_ctor) ? ctor.params[0] : void 0;
53803
- let return_type = target_type.type_args?.[0];
53804
- if (!return_type?.name && wrapped_call) return_type = wrapped_call.type;
53805
- if (!return_type?.name) {
53806
- add_error(status, `${kind}(fn(args)).start expects a spawned call, e.g. ${kind}(work(n)).start()`, node.start);
53900
+ * Validate a `Fiber(fn(args)).start_on(buf)` call's stack buffer (see
53901
+ * ASYNC.md): a fixed-size array of at least 16 KB. The caller rewrites the
53902
+ * access to the ordinary Fiber.start afterwards — this function is
53903
+ * validation only.
53904
+ */
53905
+ function check_spawn_start_on(target, target_type, node, status) {
53906
+ if (node.params.length !== 1) {
53907
+ add_error(status, `start_on expects a stack buffer argument, e.g. Fiber(work(n)).start_on(buf)`, node.start);
53807
53908
  return false;
53808
53909
  }
53809
- const call = wrapped_call;
53810
- if (start_on) {
53811
- if (node.params.length !== 1) {
53812
- add_error(status, `start_on expects a stack buffer argument, e.g. ${kind}(work(n)).start_on(buf)`, node.start);
53813
- return false;
53814
- }
53815
- const buf_type = type_from_value_node(node.params[0], status);
53816
- if (!buf_type.is_array) {
53817
- add_error(status, `start_on expects a fixed-size array stack buffer (T[N]), got ${buf_type.name || "<unknown>"}`, node.params[0].start);
53818
- return false;
53819
- }
53820
- const elem_size = buf_type.name === "string" ? 16 : 8;
53821
- const len_node = buf_type.length;
53822
- const len = len_node && len_node.node_type === "value" ? parseInt(len_node.value, 10) : NaN;
53823
- if (Number.isNaN(len)) {
53824
- add_error(status, "start_on expects a fixed-size array (T[N]); the length must be known at compile time", node.params[0].start);
53825
- return false;
53826
- }
53827
- if (len * elem_size < 16384) {
53828
- add_error(status, `Fiber stack too small: ${len * elem_size} bytes, minimum is 16384`, node.params[0].start);
53829
- return false;
53830
- }
53910
+ const buf_type = type_from_value_node(node.params[0], status);
53911
+ if (!buf_type.is_array) {
53912
+ add_error(status, `start_on expects a fixed-size array stack buffer (T[N]), got ${buf_type.name || "<unknown>"}`, node.params[0].start);
53913
+ return false;
53914
+ }
53915
+ const elem_size = buf_type.name === "string" ? 16 : 8;
53916
+ const len_node = buf_type.length;
53917
+ const len = len_node && len_node.node_type === "value" ? parseInt(len_node.value, 10) : NaN;
53918
+ if (Number.isNaN(len)) {
53919
+ add_error(status, "start_on expects a fixed-size array (T[N]); the length must be known at compile time", node.params[0].start);
53920
+ return false;
53921
+ }
53922
+ if (len * elem_size < 16384) {
53923
+ add_error(status, `Fiber stack too small: ${len * elem_size} bytes, minimum is 16384`, node.params[0].start);
53924
+ return false;
53831
53925
  }
53832
- const result_type_arg = return_type.name && return_type.name !== "void" && return_type.name !== "?" ? return_type : new Type("uint64");
53833
- const task_type = new Type("Task");
53834
- task_type.type_args = [result_type_arg];
53835
- node.function_return_type = return_type;
53836
- node.type = task_type;
53837
- if (call) call.type = task_type;
53838
- if (kind === "Thread") node.is_thread_start = true;
53839
- else node.is_fiber_start = true;
53840
- node.owned_return = true;
53841
- const task_struct = status.structs.find((s) => s.name === "Task");
53842
- if (task_struct && task_struct.type_params.length > 0) monomorphize(task_struct, [result_type_arg], status);
53843
- return true;
53844
- }
53845
- /**
53846
- * Check a `Thread(fn(args)).detach()` call — the daemon form (see
53847
- * ASYNC.md, "Daemon tasks"). The wrapped call runs on a dedicated detached
53848
- * pthread (never a pool worker); nobody joins it, and process exit kills it
53849
- * mid-execution BY DESIGN — the std::thread::spawn contract. The daemon
53850
- * owns its own shutdown (a stop channel or flag), not process exit. There
53851
- * is no handle, no future, and no cancellation: statement form only, the
53852
- * expression types as void. The receiver is the magic constructor or any
53853
- * Thread-typed expression (a stored daemon handle).
53854
- */
53855
- function check_spawn_detach(target, target_type, node, status) {
53856
- const ctor = target;
53857
- const wrapped_call = target.node_type === "func_call" && ctor.is_thread_ctor ? ctor.params[0] : void 0;
53858
- if (wrapped_call) validate_spawn_args_sendable(wrapped_call, status, false, " — a detached task must own its arguments (it outlives every scope; only a nursery's join bounds a borrow)");
53859
- node.function_return_type = wrapped_call?.type ?? target_type.type_args?.[0] ?? new Type("uint64");
53860
- node.type = new Type("void");
53861
- node.is_thread_detach = true;
53862
53926
  return true;
53863
53927
  }
53864
53928
  //#endregion
@@ -57545,7 +57609,7 @@ function parse_function(visibility, status, name_override, is_inline, is_extern,
57545
57609
  if (op_internal[name]) name = op_internal[name];
57546
57610
  const parent_for_type = status.stack.at(-1);
57547
57611
  let return_type = new Type("");
57548
- if (name === "#init" && (parent_for_type?.node_type === "struct" || parent_for_type?.node_type === "extend")) return_type = new Type(parent_for_type.name);
57612
+ if ((name === "#init" || name === "#spawn") && (parent_for_type?.node_type === "struct" || parent_for_type?.node_type === "extend")) return_type = new Type(parent_for_type.name);
57549
57613
  const func = new FunctionNode(start, visibility, name, return_type);
57550
57614
  if (is_inline) func.is_inline = true;
57551
57615
  if (is_unsafe) {
@@ -57565,7 +57629,7 @@ function parse_function(visibility, status, name_override, is_inline, is_extern,
57565
57629
  if (expect("=", status) && expect("(", status)) {
57566
57630
  const parent = status.stack.at(-1);
57567
57631
  if (peek_current(status) !== ")") parse_function_parameter(parent, func, status);
57568
- if (name === "#destroy" && parent.node_type === "struct" && !func.params.some((p) => p.is_self_param)) {
57632
+ if ((name === "#destroy" || name === "#spawn") && parent.node_type === "struct" && !func.params.some((p) => p.is_self_param)) {
57569
57633
  const self_param = new ParameterNode(start, "self", new Type(parent.name));
57570
57634
  self_param.is_self_param = true;
57571
57635
  self_param.declaration = "var";
@@ -57597,7 +57661,7 @@ function parse_function(visibility, status, name_override, is_inline, is_extern,
57597
57661
  parse_statement(status);
57598
57662
  expect("}", status);
57599
57663
  status.stack.pop();
57600
- if (name !== "#init" && name !== "#destroy") check_missing_return(func, status);
57664
+ if (name !== "#init" && name !== "#destroy" && name !== "#spawn") check_missing_return(func, status);
57601
57665
  }
57602
57666
  switch (parent.node_type) {
57603
57667
  case "root":
@@ -57782,7 +57846,7 @@ function parse_struct(visibility, status, is_class = false) {
57782
57846
  parse_statement(status);
57783
57847
  expect("}", status);
57784
57848
  status.stack.pop();
57785
- if (!struct.functions.some((f) => f.name === "#init")) {
57849
+ if (!struct.functions.some((f) => f.name === "#init" || f.name === "#spawn")) {
57786
57850
  const func = new FunctionNode(-1, visibility, "#init", new Type(struct.name));
57787
57851
  func.params = struct.fields.filter((f) => f.visibility !== "private" && !f.value).map((f) => {
57788
57852
  const param = new ParameterNode(-1, f.name, f.func_params ? new Type("func") : f.type);
@@ -58071,8 +58135,13 @@ function parse_visibility(visibility, status) {
58071
58135
  consume(status);
58072
58136
  consume(status);
58073
58137
  parse_function(visibility, status, "#destroy");
58138
+ } else if (next2 === "spawn") {
58139
+ consume(status);
58140
+ consume(status);
58141
+ consume(status);
58142
+ parse_function(visibility, status, "#spawn");
58074
58143
  } else {
58075
- add_error(status, `Expected #init or #destroy after pub`, get_index(status));
58144
+ add_error(status, `Expected #init, #destroy or #spawn after pub`, get_index(status));
58076
58145
  consume(status);
58077
58146
  }
58078
58147
  break;
@@ -58199,8 +58268,12 @@ function parse_statement(status) {
58199
58268
  consume(status);
58200
58269
  consume(status);
58201
58270
  parse_destroy(default_visibility(status), status, "#destroy");
58271
+ } else if (next === "spawn") {
58272
+ consume(status);
58273
+ consume(status);
58274
+ parse_destroy(default_visibility(status), status, "#spawn");
58202
58275
  } else {
58203
- add_error(status, `Expected #init or #destroy`, get_index(status));
58276
+ add_error(status, `Expected #init, #destroy or #spawn`, get_index(status));
58204
58277
  consume(status);
58205
58278
  }
58206
58279
  break;
@@ -58447,10 +58520,6 @@ function resolve_linked_types(source, library, file_path) {
58447
58520
  if (library.types.has(token.value) && !BASE_TYPES.includes(token.value)) needed.add(token.value);
58448
58521
  else if (library.functions.has(token.value) && !user_declared_funcs.has(token.value)) needed.add(token.value);
58449
58522
  }
58450
- if (tokens.some((t) => t.value === "Thread" || t.value === "Fiber" || t.value === "async")) {
58451
- needed.add("Task");
58452
- needed.add("Sendable");
58453
- }
58454
58523
  if (tokens.some((t) => t.value === "async")) needed.add("Nursery");
58455
58524
  const resolved = resolve_types_with_deps(needed, library, is_library_file ? file_path : void 0);
58456
58525
  if (!resolved) return source;