nomen-lang 0.5.0 → 0.6.1

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,8 @@ 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;
19245
+ const old_variable_types = status.variable_types;
19136
19246
  status.function_param_regs = /* @__PURE__ */ new Map();
19137
19247
  status.function_param_vars = /* @__PURE__ */ new Set();
19138
19248
  status.function_array_params = /* @__PURE__ */ new Set();
@@ -19140,6 +19250,8 @@ function build_function_node$1(node, status) {
19140
19250
  status.ref_class_slots = /* @__PURE__ */ new Map();
19141
19251
  status.function_struct_param_slots = /* @__PURE__ */ new Set();
19142
19252
  status.function_param_types = /* @__PURE__ */ new Map();
19253
+ status.function_local_types = /* @__PURE__ */ new Map();
19254
+ status.variable_types = /* @__PURE__ */ new Map();
19143
19255
  const old_variadic_params_aarch64 = status.function_variadic_params;
19144
19256
  const old_view_params = status.function_view_params;
19145
19257
  status.function_variadic_params = /* @__PURE__ */ new Set();
@@ -19387,6 +19499,8 @@ function build_function_node$1(node, status) {
19387
19499
  }
19388
19500
  const old_force_heap = status.force_heap_strings;
19389
19501
  status.force_heap_strings = scan_force_heap_strings(node.statements, status.structs);
19502
+ const old_reassigned_vars = status.reassigned_vars;
19503
+ status.reassigned_vars = scan_reassigned_vars(node.statements);
19390
19504
  const old_buffer_data_cache = status.buffer_data_cache;
19391
19505
  status.buffer_data_cache = void 0;
19392
19506
  const old_array_ptr_cache = status.array_ptr_cache;
@@ -19421,6 +19535,7 @@ function build_function_node$1(node, status) {
19421
19535
  status.buffer_data_cache = old_buffer_data_cache;
19422
19536
  status.array_ptr_cache = old_array_ptr_cache;
19423
19537
  status.force_heap_strings = old_force_heap;
19538
+ status.reassigned_vars = old_reassigned_vars;
19424
19539
  const loop_regs_used = status.callee_saved_regs_used ? [...status.callee_saved_regs_used].sort() : [];
19425
19540
  status.callee_saved_regs_used = old_callee_saved_regs;
19426
19541
  status.register_allocations = old_register_allocations;
@@ -19544,6 +19659,8 @@ function build_function_node$1(node, status) {
19544
19659
  status.ref_class_slots = old_ref_class_slots;
19545
19660
  status.function_struct_param_slots = old_struct_param_slots;
19546
19661
  status.function_param_types = old_function_param_types;
19662
+ status.function_local_types = old_function_local_types;
19663
+ status.variable_types = old_variable_types;
19547
19664
  status.function_variadic_params = old_variadic_params_aarch64;
19548
19665
  status.function_view_params = old_view_params;
19549
19666
  status.function_return_label = old_return_label;
@@ -19788,10 +19905,7 @@ function resolve_global_const_value(name, status) {
19788
19905
  function build_struct_node$1(node, status) {
19789
19906
  if (node.is_generic) return;
19790
19907
  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
- }
19908
+ for (const func of node.functions) ensure_runtime_for_method_a64(func, status);
19795
19909
  let old_code;
19796
19910
  if (is_nested) {
19797
19911
  old_code = status.code;
@@ -20172,14 +20286,18 @@ function build_init_function(node, status) {
20172
20286
  }
20173
20287
  emit_typed_store(status, "x1", "x19", offset, get_type_size(field.type, status));
20174
20288
  } else if (field.value.node_type === "func_call") {
20175
- if (status.structs.find((s) => s.name === field.type.name && !s.is_simple_type)) {
20289
+ const field_struct = status.structs.find((s) => s.name === field.type.name && !s.is_simple_type);
20290
+ if (field_struct) {
20176
20291
  build_node(field.value, status);
20177
20292
  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`);
20293
+ if (field_struct.is_class) emit_typed_store(status, "x0", "x19", offset, 8);
20294
+ else {
20295
+ const field_size = get_struct_size(field.type.name, status);
20296
+ const words = Math.ceil(field_size / 8);
20297
+ for (let w = 0; w < words; w++) {
20298
+ emit_asm(status, `ldr x9, [x0, #${w * 8}]\n`);
20299
+ emit_asm(status, `str x9, [x19, #${offset + w * 8}]\n`);
20300
+ }
20183
20301
  }
20184
20302
  }
20185
20303
  } else if (field.value.node_type === "func" && field.type.name === "func") {
@@ -20390,14 +20508,18 @@ function build_custom_init_function(node, func, status) {
20390
20508
  }
20391
20509
  emit_typed_store(status, "x1", "x19", offset, get_type_size(field.type, status));
20392
20510
  } else if (field.value.node_type === "func_call") {
20393
- if (status.structs.find((s) => s.name === field.type.name && !s.is_simple_type)) {
20511
+ const field_struct = status.structs.find((s) => s.name === field.type.name && !s.is_simple_type);
20512
+ if (field_struct) {
20394
20513
  build_node(field.value, status);
20395
20514
  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`);
20515
+ if (field_struct.is_class) emit_typed_store(status, "x0", "x19", offset, 8);
20516
+ else {
20517
+ const field_size = get_struct_size(field.type.name, status);
20518
+ const words = Math.ceil(field_size / 8);
20519
+ for (let w = 0; w < words; w++) {
20520
+ emit_asm(status, `ldr x9, [x0, #${w * 8}]\n`);
20521
+ emit_asm(status, `str x9, [x19, #${offset + w * 8}]\n`);
20522
+ }
20401
20523
  }
20402
20524
  }
20403
20525
  }
@@ -20451,6 +20573,7 @@ function build_custom_init_function(node, func, status) {
20451
20573
  }
20452
20574
  function build_struct_functions$1(node, status) {
20453
20575
  for (const func of node.functions) {
20576
+ if (func.name === "#spawn") continue;
20454
20577
  if (func.name === "#init" && !func.has_body) continue;
20455
20578
  if (func.name === "#init" && func.has_body) {
20456
20579
  if (!check_c_fallback(func, node.name, status)) build_custom_init_function(node, func, status);
@@ -20481,6 +20604,7 @@ function build_struct_functions$1(node, status) {
20481
20604
  const old_param_vars = status.function_param_vars;
20482
20605
  const old_return_label = status.function_return_label;
20483
20606
  const old_force_heap = status.force_heap_strings;
20607
+ const old_reassigned_vars = status.reassigned_vars;
20484
20608
  const old_function_name = status.current_function_name;
20485
20609
  const old_register_allocations = status.register_allocations;
20486
20610
  const old_nir_site_allocs = status.nir_site_allocs;
@@ -20572,7 +20696,7 @@ function build_struct_functions$1(node, status) {
20572
20696
  slot_idx += 2;
20573
20697
  continue;
20574
20698
  }
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) {
20699
+ 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
20700
  const saved_reg = callee_saved[callee_idx++];
20577
20701
  if (saved_reg !== "x19" || !needs_x19) emit_asm(status, `str ${saved_reg}, [sp, #-16]!\n`);
20578
20702
  if (slot_idx < 8) emit_asm(status, `mov ${saved_reg}, ${param_regs[slot_idx]}\n`);
@@ -20650,7 +20774,7 @@ function build_struct_functions$1(node, status) {
20650
20774
  second_slot_idx += 2;
20651
20775
  continue;
20652
20776
  }
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))) {
20777
+ 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
20778
  const is_ref = param.type.is_ref;
20655
20779
  const size = is_ref ? 8 : aarch64_size(param.type.name);
20656
20780
  const offset = allocate_stack_space(status, size, is_ref ? 8 : size);
@@ -20735,6 +20859,7 @@ function build_struct_functions$1(node, status) {
20735
20859
  }
20736
20860
  }
20737
20861
  status.force_heap_strings = scan_force_heap_strings(func.statements, status.structs);
20862
+ status.reassigned_vars = scan_reassigned_vars(func.statements);
20738
20863
  status.buffer_data_cache = void 0;
20739
20864
  status.array_ptr_cache = void 0;
20740
20865
  const moved_before = new Set(status.moved ?? []);
@@ -20820,6 +20945,7 @@ function build_struct_functions$1(node, status) {
20820
20945
  status.function_view_params = old_view_params;
20821
20946
  status.function_return_label = old_return_label;
20822
20947
  status.force_heap_strings = old_force_heap;
20948
+ status.reassigned_vars = old_reassigned_vars;
20823
20949
  status.struct_return_buffer = void 0;
20824
20950
  status.function_return_type = void 0;
20825
20951
  status.current_function_name = old_function_name;
@@ -22070,6 +22196,7 @@ function resolve_string_value(node, status) {
22070
22196
  if (node.node_type === "value") {
22071
22197
  const val = node.value;
22072
22198
  if (val.startsWith("\"") && val.endsWith("\"")) return val;
22199
+ if (status.reassigned_vars?.has(val)) return null;
22073
22200
  const decl = status.scoped_declarations.find((d) => d.name === val);
22074
22201
  if (decl && decl.value) return resolve_string_value(decl.value, status);
22075
22202
  }
@@ -22122,6 +22249,10 @@ function nir_array_element(nir, arr, i) {
22122
22249
  return nir?.kind === "call" && nir.node === arr ? nir.facts.args[i] : void 0;
22123
22250
  }
22124
22251
  function build_declaration_node$1(node, status, nir_init, nir_swap) {
22252
+ if (node.name && node.type?.name) {
22253
+ if (!status.function_local_types) status.function_local_types = /* @__PURE__ */ new Map();
22254
+ status.function_local_types.set(node.name, node.type.name);
22255
+ }
22125
22256
  hoist_field_overrides(node.value, build_node, status, "", node.name);
22126
22257
  status.last_result_is_heap = false;
22127
22258
  const prev_heap = status.last_result_is_heap;
@@ -23807,6 +23938,7 @@ function promote_loop_locals(status, scoped_declarations, sources, options) {
23807
23938
  if (!ptype.name || !SCALAR_TYPES.includes(ptype.name) || ptype.is_array || ptype.is_view || ptype.is_ref || ptype.is_nullable) continue;
23808
23939
  type_name = ptype.name;
23809
23940
  }
23941
+ if (status.function_local_types?.get(name) === "string") continue;
23810
23942
  }
23811
23943
  let offset = status.stack_offsets?.get(name);
23812
23944
  if (offset === void 0) {
@@ -24250,18 +24382,24 @@ function build_if_else_node$1(node, status, nir) {
24250
24382
  const pre_cache = status.buffer_data_cache;
24251
24383
  const pre_array_cache = status.array_ptr_cache;
24252
24384
  if (node.else_branch) {
24385
+ const then_frame = enter_scope_frame(status);
24253
24386
  status.buffer_data_cache = new Map(pre_cache);
24254
24387
  status.array_ptr_cache = new Map(pre_array_cache);
24255
24388
  build_block_with_cursor$1(node.if_branch, nir?.then_branch, status);
24389
+ exit_scope_frame(status, then_frame);
24256
24390
  emit_asm(status, `b end_${label}\n`);
24257
24391
  emit_asm(status, `else_${label}:\n`);
24392
+ const else_frame = enter_scope_frame(status);
24258
24393
  status.buffer_data_cache = new Map(pre_cache);
24259
24394
  status.array_ptr_cache = new Map(pre_array_cache);
24260
24395
  build_block_with_cursor$1(node.else_branch, nir?.else_branch, status);
24396
+ exit_scope_frame(status, else_frame);
24261
24397
  } else if (node.if_branch) {
24398
+ const then_frame = enter_scope_frame(status);
24262
24399
  status.buffer_data_cache = new Map(pre_cache);
24263
24400
  status.array_ptr_cache = new Map(pre_array_cache);
24264
24401
  build_block_with_cursor$1(node.if_branch, nir?.then_branch, status);
24402
+ exit_scope_frame(status, then_frame);
24265
24403
  }
24266
24404
  status.buffer_data_cache = pre_cache;
24267
24405
  status.array_ptr_cache = pre_array_cache;
@@ -24483,6 +24621,7 @@ function build_return_node$1(node, status, nir_value) {
24483
24621
  emit_asm(status, `ldr x8, [x29, #${status.return_buffer_stack_offset}]\n`);
24484
24622
  emit_asm(status, `str xzr, [x8, #${struct_size}]\n`);
24485
24623
  }
24624
+ emit_nursery_joins_on_return_a64(status);
24486
24625
  const finalized = status.moved ?? /* @__PURE__ */ new Set();
24487
24626
  for (const decl of all_scope_frames(status).flat()) {
24488
24627
  if (finalized.has(decl.name)) {
@@ -24502,6 +24641,7 @@ function build_return_node$1(node, status, nir_value) {
24502
24641
  emit_asm(status, `mov x0, #0\n`);
24503
24642
  emit_var_store(status, "x0", status.return_assign, size);
24504
24643
  } else if (status.function_return_label) {
24644
+ emit_nursery_joins_on_return_a64(status);
24505
24645
  const finalized = status.moved ?? /* @__PURE__ */ new Set();
24506
24646
  for (const decl of all_scope_frames(status).flat()) {
24507
24647
  if (finalized.has(decl.name)) {
@@ -24620,7 +24760,7 @@ function build_return_node$1(node, status, nir_value) {
24620
24760
  const raw_value = node.value.value;
24621
24761
  const is_str_lit = raw_value.length >= 2 && raw_value.startsWith("\"") && raw_value.endsWith("\"");
24622
24762
  const is_numeric = /^(\+|-)?\d+$/.test(raw_value);
24623
- if (!is_str_lit && !is_numeric && !status.heap_strings?.has(raw_value)) needs_borrow_strdup = true;
24763
+ if (!is_str_lit && !is_numeric && raw_value !== "null" && !status.heap_strings?.has(raw_value)) needs_borrow_strdup = true;
24624
24764
  } else if (is_container_borrow_access(node.value)) needs_borrow_strdup = true;
24625
24765
  else if (node.value.node_type === "access" && node.value.access.node_type === "access_field") needs_borrow_strdup = true;
24626
24766
  else if ((node.value.node_type === "match" || node.value.node_type === "switch") && !return_join_owned_string) needs_borrow_strdup = true;
@@ -24731,6 +24871,7 @@ function build_return_node$1(node, status, nir_value) {
24731
24871
  if (returns_fat_pair) emit_asm(status, `stp x0, x1, [sp, #-16]!\n`);
24732
24872
  else if (returns_float) emit_asm(status, `str d0, [sp, #-16]!\n`);
24733
24873
  else emit_asm(status, `str x0, [sp, #-16]!\n`);
24874
+ emit_nursery_joins_on_return_a64(status);
24734
24875
  for (const decl of all_scope_frames(status).flat()) {
24735
24876
  if (finalized.has(decl.name)) {
24736
24877
  release_heap_string_fields(status, decl.name, decl.type.name);
@@ -26485,7 +26626,7 @@ function emit_stmt_dispatch(child, index, statements, status) {
26485
26626
  const eval_node = nstmt.expr.node;
26486
26627
  if (eval_node.node_type === "access") {
26487
26628
  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;
26629
+ if (inner.node_type === "access_func" && inner.is_nursery_spawn) inner.is_statement = true;
26489
26630
  }
26490
26631
  const restore_eval = apply_forward_use(ctx.use_sites, nstmt.expr.node);
26491
26632
  try {
@@ -26891,7 +27032,7 @@ function emit_expr_from_nir$1(expr, status) {
26891
27032
  function build_async_block_node$1(node, status, nir) {
26892
27033
  const id = status.spawn_counter ?? 0;
26893
27034
  status.spawn_counter = id + 1;
26894
- if (node.mode === "race") ensure_concurrency_runtime_a64(status);
27035
+ ensure_concurrency_runtime_a64(status);
26895
27036
  const futures_off = allocate_stack_space(status, 8, 8);
26896
27037
  const count_off = allocate_stack_space(status, 8, 8);
26897
27038
  const cap_off = allocate_stack_space(status, 8, 8);
@@ -26913,28 +27054,6 @@ function build_async_block_node$1(node, status, nir) {
26913
27054
  let deadline_off;
26914
27055
  if (node.timeout) {
26915
27056
  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
27057
  emit_asm(status, `// Compute deadline: now + timeout_ms\n`);
26939
27058
  build_node(node.timeout, status);
26940
27059
  ensure_newline(status);
@@ -26955,11 +27074,57 @@ function build_async_block_node$1(node, status, nir) {
26955
27074
  emit_asm(status, `add x0, x1, x0\n`);
26956
27075
  emit_asm(status, `str x0, [x29, #${deadline_off}]\n`);
26957
27076
  }
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";
27077
+ if (!status.nursery_offsets) status.nursery_offsets = /* @__PURE__ */ new Map();
27078
+ status.nursery_offsets.set(id, {
27079
+ futures_off,
27080
+ count_off,
27081
+ cap_off,
27082
+ deadline_off
27083
+ });
27084
+ if (!status.nursery_meta) status.nursery_meta = /* @__PURE__ */ new Map();
27085
+ status.nursery_meta.set(id, {
27086
+ mode: node.mode ?? "all",
27087
+ has_deadline: !!node.timeout
27088
+ });
27089
+ status.nursery_stack ??= [];
27090
+ status.nursery_stack.push(id);
27091
+ const old_scoped_declarations = status.scoped_declarations;
27092
+ status.scoped_declarations = [];
27093
+ build_block_with_cursor$1(node, nir?.body, status);
27094
+ status.scoped_declarations = old_scoped_declarations;
27095
+ status.nursery_stack.pop();
27096
+ emit_asm(status, `// nursery ${id}: join all futures\n`);
27097
+ emit_nursery_join_a64(status, id, `__nursery_${id}`);
27098
+ status.nursery_offsets.delete(id);
27099
+ status.nursery_meta?.delete(id);
27100
+ }
27101
+ /**
27102
+ * Emit one nursery's join sequence in assembly: the race-mode wait (if any),
27103
+ * the wait+release loop over every registered future, and the futures-list
27104
+ * free. Shared by the block exit and the RETURN path — a `return` inside an
27105
+ * `async { }` block must route through the join (the structured-concurrency
27106
+ * contract). The nursery's per-invocation state lives at this function's
27107
+ * stack frame (offsets from x29), so a return site inside the block reaches
27108
+ * the same state. `label_prefix` must be unique per emission: the block exit
27109
+ * uses `__nursery_<id>`, a return site `__nursery_<id>_retjoin<N>` (the
27110
+ * block-exit labels would collide with a second loop in the same function).
27111
+ * Clobbers x0-x3, x20, x22, x23 (backend scratch — nothing live across a
27112
+ * join).
27113
+ */
27114
+ function emit_nursery_join_a64(status, id, label_prefix) {
27115
+ const offsets = status.nursery_offsets?.get(id);
27116
+ if (!offsets) return;
27117
+ const { futures_off, count_off, deadline_off } = offsets;
27118
+ const is_race = (status.nursery_meta?.get(id) ?? {
27119
+ mode: "all",
27120
+ has_deadline: false
27121
+ }).mode === "race";
27122
+ const loop_start = `${label_prefix}_join_start`;
27123
+ const loop_end = `${label_prefix}_join_end`;
27124
+ const loop_release = `${label_prefix}_release`;
27125
+ const no_list = `${label_prefix}_no_list`;
27126
+ emit_asm(status, `ldr x20, [x29, #${futures_off}]\n`);
27127
+ emit_asm(status, `ldr w22, [x29, #${count_off}]\n`);
26963
27128
  if (is_race) {
26964
27129
  emit_asm(status, `mov x0, x20\n`);
26965
27130
  emit_asm(status, `mov x1, x22\n`);
@@ -26967,6 +27132,7 @@ function build_async_block_node$1(node, status, nir) {
26967
27132
  else emit_asm(status, `mov x2, #0\n`);
26968
27133
  emit_asm(status, `bl ___nomen_nursery_race_wait\n`);
26969
27134
  }
27135
+ emit_asm(status, `mov x23, xzr\n`);
26970
27136
  emit_asm(status, `${loop_start}:\n`);
26971
27137
  emit_asm(status, `cmp x23, x22\n`);
26972
27138
  emit_asm(status, `b.ge ${loop_end}\n`);
@@ -26991,9 +27157,28 @@ function build_async_block_node$1(node, status, nir) {
26991
27157
  emit_asm(status, `b ${loop_start}\n`);
26992
27158
  emit_asm(status, `${loop_end}:\n`);
26993
27159
  emit_asm(status, `ldr x0, [x29, #${futures_off}]\n`);
26994
- emit_asm(status, `cbz x0, __nursery_${id}_no_list\n`);
27160
+ emit_asm(status, `cbz x0, ${no_list}\n`);
26995
27161
  emit_asm(status, `bl _free\n`);
26996
- emit_asm(status, `__nursery_${id}_no_list:\n`);
27162
+ emit_asm(status, `${no_list}:\n`);
27163
+ }
27164
+ let retjoin_counter = 0;
27165
+ /**
27166
+ * Emit the join sequence for every nursery the current `return` must route
27167
+ * through — innermost first. Only nurseries belonging to the CURRENT
27168
+ * function (stack depth >= the function's entry depth) apply: a lambda
27169
+ * defined inside an `async { }` block inherits the block's id on
27170
+ * nursery_stack (for lexical spawn captures), but its `return` exits the
27171
+ * lambda, whose frame has no nursery state. The return value must already
27172
+ * be parked (the join's `bl`s clobber x0/x1).
27173
+ */
27174
+ function emit_nursery_joins_on_return_a64(status) {
27175
+ if (!status.nursery_stack?.length) return;
27176
+ const base = status.function_nursery_depth ?? 0;
27177
+ for (let i = status.nursery_stack.length - 1; i >= base; i--) {
27178
+ const id = status.nursery_stack[i];
27179
+ emit_asm(status, `// return inside async block: join nursery ${id} before leaving\n`);
27180
+ emit_nursery_join_a64(status, id, `__nursery_${id}_retjoin${retjoin_counter++}`);
27181
+ }
26997
27182
  }
26998
27183
  //#endregion
26999
27184
  //#region ../src/build_aarch64/build_break_node.ts
@@ -27482,6 +27667,7 @@ function build_inline_method(struct_node, func, status) {
27482
27667
  const old_param_vars = status.function_param_vars;
27483
27668
  status.stack_offsets = /* @__PURE__ */ new Map();
27484
27669
  const old_param_types = status.function_param_types;
27670
+ const old_local_types = status.function_local_types;
27485
27671
  const old_return_label = status.function_return_label;
27486
27672
  const old_ref_params = status.function_ref_params;
27487
27673
  const old_struct_return_buffer = status.struct_return_buffer;
@@ -27548,6 +27734,7 @@ function build_inline_method(struct_node, func, status) {
27548
27734
  status.function_ref_params = /* @__PURE__ */ new Set();
27549
27735
  status.function_param_types = /* @__PURE__ */ new Map();
27550
27736
  for (const p of func.params) if (p.type) status.function_param_types.set(p.name, p.type);
27737
+ status.function_local_types = /* @__PURE__ */ new Map();
27551
27738
  if (needs_x19) status.function_param_regs.set("self", "x19");
27552
27739
  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
27740
  const saved_stack_slots = [];
@@ -27628,6 +27815,7 @@ function build_inline_method(struct_node, func, status) {
27628
27815
  status.function_param_regs = old_param_regs;
27629
27816
  status.function_param_vars = old_param_vars;
27630
27817
  status.function_param_types = old_param_types;
27818
+ status.function_local_types = old_local_types;
27631
27819
  status.function_ref_params = old_ref_params;
27632
27820
  status.function_return_label = old_return_label;
27633
27821
  status.stack_offsets = old_stack_offsets;
@@ -27658,6 +27846,7 @@ function build_inline_function(func, status) {
27658
27846
  const old_param_vars = status.function_param_vars;
27659
27847
  status.stack_offsets = /* @__PURE__ */ new Map();
27660
27848
  const old_param_types = status.function_param_types;
27849
+ const old_local_types = status.function_local_types;
27661
27850
  const old_return_label = status.function_return_label;
27662
27851
  const old_ref_params = status.function_ref_params;
27663
27852
  const old_struct_return_buffer = status.struct_return_buffer;
@@ -27716,6 +27905,7 @@ function build_inline_function(func, status) {
27716
27905
  status.function_ref_params = /* @__PURE__ */ new Set();
27717
27906
  status.function_param_types = /* @__PURE__ */ new Map();
27718
27907
  for (const p of func.params) if (p.type) status.function_param_types.set(p.name, p.type);
27908
+ status.function_local_types = /* @__PURE__ */ new Map();
27719
27909
  const saved_stack_slots = [];
27720
27910
  const inline_reloads = [];
27721
27911
  const inline_push_slots = [];
@@ -27785,6 +27975,7 @@ function build_inline_function(func, status) {
27785
27975
  status.function_param_regs = old_param_regs;
27786
27976
  status.function_param_vars = old_param_vars;
27787
27977
  status.function_param_types = old_param_types;
27978
+ status.function_local_types = old_local_types;
27788
27979
  status.function_ref_params = old_ref_params;
27789
27980
  status.function_return_label = old_return_label;
27790
27981
  status.stack_offsets = old_stack_offsets;
@@ -28217,7 +28408,7 @@ function build_function_call_node$1(node, status) {
28217
28408
  let arg_is_class = false;
28218
28409
  if (arg_name) {
28219
28410
  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);
28411
+ arg_is_class = !!tn && (!!status.structs.find((s) => s.name === tn && s.is_class) || !!status.traits.find((t) => t.name === tn));
28221
28412
  }
28222
28413
  const ref_param_slot = arg_name !== void 0 ? status.ref_class_slots?.get(arg_name) : void 0;
28223
28414
  if (ref_param_slot !== void 0) emit_ref_class_param_slot(status, ref_param_slot, arg_name, ref_class_param_reload);
@@ -28286,11 +28477,11 @@ function build_function_call_node$1(node, status) {
28286
28477
  }
28287
28478
  }
28288
28479
  }
28289
- if (is_struct && status.struct_return_buffer) {
28480
+ if (is_struct && is_struct.is_class) emit_asm(status, `ldr x0, [sp]\n`);
28481
+ else if (is_struct && status.struct_return_buffer) {
28290
28482
  if (status.return_buffer_stack_offset !== void 0) emit_asm(status, `ldr x8, [x29, #${status.return_buffer_stack_offset}]\n`);
28291
28483
  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) {
28484
+ } else if (is_struct && !is_struct.is_class) {
28294
28485
  const temp_addr = `_temp_${temp_counter - 1}`;
28295
28486
  emit_asm(status, `add x0, x29, #${status.stack_offsets.get(temp_addr)}\n`);
28296
28487
  } else if (!is_struct && node.type?.name && !status.call_x8_preset) {
@@ -28352,16 +28543,17 @@ function build_function_call_node$1(node, status) {
28352
28543
  }
28353
28544
  }
28354
28545
  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);
28546
+ if (is_struct && is_struct.is_class) emit_asm(status, `ldr x0, [sp], #16\n`);
28547
+ else if (is_struct && status.struct_return_buffer) {
28548
+ if (status.return_buffer_stack_offset !== void 0) emit_asm(status, `ldr x0, [x29, #${status.return_buffer_stack_offset}]\n`);
28549
+ else emit_asm(status, `mov x0, ${status.struct_return_buffer}\n`);
28550
+ } else if (is_struct && !status.struct_return_buffer) {
28551
+ const temp_addr = `_temp_${temp_counter - 1}`;
28552
+ const offset = status.stack_offsets.get(temp_addr);
28553
+ emit_asm(status, `add x0, x29, #${offset}\n`);
28554
+ if (node.field_overrides?.length) {
28555
+ emit_field_overrides(temp_addr, node, build_node, status);
28360
28556
  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
28557
  }
28366
28558
  }
28367
28559
  if (node.name.startsWith("_string_interpolate_")) {
@@ -28752,6 +28944,14 @@ struct nomen_future {
28752
28944
  // The fiber whose task this future is (set at fiber spawn). Cancellation
28753
28945
  // uses it to wake a fiber parked on a primitive waitq.
28754
28946
  struct nomen_fiber *owning_fiber;
28947
+ // 1 when the result slot holds a FAT string (set at construction, which
28948
+ // knows the wrapped call's return type). The last release frees an
28949
+ // unconsumed string buffer from the cell: result() zeroes the cell when
28950
+ // it moves the value out, and Task's #destroy zeroes after its own
28951
+ // free, so a non-NULL ptr at the last release means "never consumed".
28952
+ // Appended LAST: raw aarch64 bodies read refs/result_slot/owner_args at
28953
+ // fixed offsets (#116/#128/#136), which must not shift.
28954
+ int slot_fat;
28755
28955
  };
28756
28956
  // Fiber seam (defined in FIBER_HEADER, appended after this block). Declared
28757
28957
  // here — after struct nomen_future is complete — so the prototypes bind to
@@ -28877,6 +29077,19 @@ static void __nomen_future_release(struct nomen_future *f) {
28877
29077
  pthread_mutex_destroy(&f->mu);
28878
29078
  pthread_cond_destroy(&f->cv);
28879
29079
  free(f->cancel_flag);
29080
+ // An unconsumed fat-string result: free the buffer the trampoline
29081
+ // stored (race-mode and fire-and-forget tasks never hand it to a
29082
+ // consumer). Every consumer path zeroes the cell first — result()
29083
+ // on move-out, Task's #destroy after its own free — so a non-NULL
29084
+ // ptr here is unambiguous and the free happens exactly once.
29085
+ if (f->slot_fat && f->result_slot) {
29086
+ unsigned long long *_s = (unsigned long long *)f->result_slot;
29087
+ if (_s[0]) {
29088
+ free((void *)_s[0]);
29089
+ _s[0] = 0;
29090
+ _s[1] = 0;
29091
+ }
29092
+ }
28880
29093
  free(f->result_slot);
28881
29094
  // The task closure is owned by the future: disposing it here (the
28882
29095
  // last release, ordered after every use — trampoline, Task destroy,
@@ -29013,7 +29226,11 @@ static void __nomen_future_cancel(struct nomen_future *f) {
29013
29226
  // schedule() only queues PARKED fibers, so a double wake is harmless.
29014
29227
  if (f->owning_fiber) __nomen_fiber_schedule(f->owning_fiber);
29015
29228
  }
29229
+ // Non-blocking completion peek (Task.is_done): 1 once the future's task has
29230
+ // finished, 0 while it may still be running (and for a NULL — a consumed or
29231
+ // never-launched handle).
29016
29232
  static int __nomen_future_is_done(struct nomen_future *f) {
29233
+ if (!f) return 0;
29017
29234
  pthread_mutex_lock(&f->mu);
29018
29235
  int d = f->done;
29019
29236
  pthread_mutex_unlock(&f->mu);
@@ -29885,95 +30102,6 @@ static int __nomen_deadlock_check_worker(void) {
29885
30102
  }
29886
30103
  `;
29887
30104
  /**
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
30105
  * Resolve each spawn argument's C type. Classes/traits are pointers;
29978
30106
  * primitives and by-value structs use c_type's output directly. Generic
29979
30107
  * instantiations (e.g. Channel<uint64>) use the monomorphized C name
@@ -30058,98 +30186,41 @@ function find_spawn_callee(name, status) {
30058
30186
  visit(status.root);
30059
30187
  return found;
30060
30188
  }
30061
- //#endregion
30062
- //#region ../src/build_c/build_fiber_spawn.ts
30063
30189
  /**
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.
30190
+ * Whether any raw `#arch` body under this node
30191
+ * references the concurrency runtime (`__nomen_*` symbols — pool, futures,
30192
+ * fiber scheduler, nursery tracking, netpoller). The runtime is the
30193
+ * LIBRARY's dependency: a method whose raw body drives it pulls it in,
30194
+ * keyed on body CONTENT — never on a type name.
30086
30195
  */
30196
+ function raw_bodies_reference_runtime(node) {
30197
+ if (!node || typeof node !== "object") return false;
30198
+ if (node.node_type === "raw") {
30199
+ const raw = node.value;
30200
+ return typeof raw === "string" && raw.includes("__nomen_");
30201
+ }
30202
+ for (const key of Object.keys(node)) {
30203
+ if (key === "parent" || key === "scope") continue;
30204
+ const v = node[key];
30205
+ if (Array.isArray(v)) {
30206
+ for (const item of v) if (raw_bodies_reference_runtime(item)) return true;
30207
+ } else if (v && typeof v === "object" && "node_type" in v) {
30208
+ if (raw_bodies_reference_runtime(v)) return true;
30209
+ }
30210
+ }
30211
+ return false;
30212
+ }
30087
30213
  /**
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).
30214
+ * Pull the concurrency runtime in when this struct method's raw bodies
30215
+ * reference it (deduped by the ensure flags). Replaces the old per-type-name
30216
+ * gates (CONCURRENCY_TYPES / the aarch64 Fiber||Thread check): the runtime
30217
+ * dependency now belongs to the library code itself.
30097
30218
  */
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`;
30219
+ function ensure_runtime_for_method(func, status) {
30220
+ for (const stmt of func.statements ?? []) if (raw_bodies_reference_runtime(stmt)) {
30221
+ ensure_concurrency_runtime(status);
30222
+ return;
30146
30223
  }
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
30224
  }
30154
30225
  //#endregion
30155
30226
  //#region ../src/build_c/build_nursery_spawn.ts
@@ -30174,7 +30245,7 @@ function build_nursery_spawn$1(node, nursery_ptr, status) {
30174
30245
  const fire_and_forget = !!node.is_statement;
30175
30246
  const refs = fire_and_forget ? 2 : 3;
30176
30247
  ensure_concurrency_runtime(status);
30177
- const t_arg = spawn_result_type_arg$2(node.function_return_type);
30248
+ const t_arg = spawn_result_type_arg$1(node.function_return_type);
30178
30249
  const mono_thread = mono_type_name("Thread", [t_arg]);
30179
30250
  const mono_task_name = mono_type_name("Task", [t_arg]);
30180
30251
  const arg = node.params[0];
@@ -30211,7 +30282,7 @@ function build_nursery_spawn$1(node, nursery_ptr, status) {
30211
30282
  }
30212
30283
  /** The launch's T as a Type: the stamped wrapped-call return type, with
30213
30284
  * void coerced to uint64 (Task's result-slot convention). */
30214
- function spawn_result_type_arg$2(return_type) {
30285
+ function spawn_result_type_arg$1(return_type) {
30215
30286
  return return_type?.name && return_type.name !== "void" && return_type.name !== "?" ? new Type(return_type.name) : new Type("uint64");
30216
30287
  }
30217
30288
  //#endregion
@@ -31512,7 +31583,8 @@ function build_assignment_node$1(node, status, nir_rhs, nir_swap) {
31512
31583
  const lhs_decl = status.scoped_declarations.find((d) => d.name === lhs_name);
31513
31584
  const lhs_in_class_vars = !!status.class_vars?.has(lhs_name);
31514
31585
  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;
31586
+ const lhs_mono_name = lhs_type ? mono_type_name(lhs_type) : "";
31587
+ const lhs_struct = lhs_mono_name ? status.structs.find((s) => s.name === lhs_mono_name) : null;
31516
31588
  const lhs_is_string = lhs_type?.name === "string";
31517
31589
  const lhs_is_class = !!lhs_struct?.is_class || lhs_in_class_vars;
31518
31590
  const lhs_trait_class = lhs_scope_hit ? lhs_scope_hit.frame[lhs_scope_hit.index].trait_class_trait : void 0;
@@ -31580,11 +31652,12 @@ function build_assignment_node$1(node, status, nir_rhs, nir_swap) {
31580
31652
  }
31581
31653
  } else if (lhs_is_class && lhs_struct && !lhs_is_alias && (lhs_decl || lhs_in_class_vars)) {
31582
31654
  const temp = `_deferred_${status.label_counter = (status.label_counter ?? 0) + 1}`;
31583
- status.code += `struct ${lhs_struct.name}* ${temp} = ${lhs_name};\n`;
31655
+ const destroy_name = lhs_struct.name.startsWith(lhs_type.name) ? lhs_struct.name : lhs_type.name;
31656
+ status.code += `struct ${destroy_name}* ${temp} = ${lhs_name};\n`;
31584
31657
  if (!status.deferred_frees) status.deferred_frees = [];
31585
31658
  status.deferred_frees.push({
31586
31659
  temp,
31587
- struct_name: lhs_struct.name,
31660
+ struct_name: destroy_name,
31588
31661
  is_nullable: !!lhs_type?.is_nullable
31589
31662
  });
31590
31663
  } else if (lhs_is_class && lhs_is_alias && status.c_alias_owns_flags?.has(lhs_name)) {
@@ -31930,11 +32003,15 @@ function string_var_owns_heap(status, name) {
31930
32003
  function build_async_block_node(node, status, nir) {
31931
32004
  const id = status.spawn_counter ?? 0;
31932
32005
  status.spawn_counter = id + 1;
31933
- if (node.mode === "race") ensure_concurrency_runtime(status);
32006
+ ensure_concurrency_runtime(status);
31934
32007
  const futures_name = `__nomen_nursery_${id}_futures`;
31935
32008
  const count_name = `__nomen_nursery_${id}_count`;
31936
32009
  const cap_name = `__nomen_nursery_${id}_cap`;
31937
- const idx_name = `__nomen_nursery_${id}_i`;
32010
+ if (!status.nursery_meta) status.nursery_meta = /* @__PURE__ */ new Map();
32011
+ status.nursery_meta.set(id, {
32012
+ mode: node.mode ?? "all",
32013
+ has_deadline: !!node.timeout
32014
+ });
31938
32015
  status.code += `{\n`;
31939
32016
  status.code += `\tstruct nomen_future **${futures_name} = NULL;\n`;
31940
32017
  status.code += `\tint ${count_name} = 0;\n`;
@@ -31966,7 +32043,35 @@ function build_async_block_node(node, status, nir) {
31966
32043
  status.nursery_stack.push(id);
31967
32044
  build_block_with_cursor(node, nir?.body, status);
31968
32045
  status.nursery_stack.pop();
31969
- const is_race = node.mode === "race";
32046
+ emit_nursery_join_c(status, id);
32047
+ build_auto_free(status);
32048
+ status.code += `}\n`;
32049
+ leave_c_scope(status);
32050
+ status.scoped_declarations = old_scoped_declarations;
32051
+ status.deferred_frees = old_deferred_frees;
32052
+ }
32053
+ /**
32054
+ * Emit one nursery's join sequence: the race-mode wait (if any), the
32055
+ * wait+release loop over every registered future, and the futures-list
32056
+ * free. Shared by the block exit and the RETURN path — a `return` inside an
32057
+ * `async { }` block must route through the join (the structured-concurrency
32058
+ * contract: the block's scoped resources must not die under still-running
32059
+ * tasks, and the per-future release must not be skipped). The names are
32060
+ * deterministic per nursery id, so a return site inside the block can
32061
+ * re-emit the same sequence textually (the block's tracking locals are
32062
+ * still in C scope there).
32063
+ */
32064
+ function emit_nursery_join_c(status, id, is_return_path = false) {
32065
+ const meta = status.nursery_meta?.get(id) ?? {
32066
+ mode: "all",
32067
+ has_deadline: false
32068
+ };
32069
+ const futures_name = `__nomen_nursery_${id}_futures`;
32070
+ const count_name = `__nomen_nursery_${id}_count`;
32071
+ const idx_name = `__nomen_nursery_${id}_i`;
32072
+ const deadline_var = meta.has_deadline ? `_deadline_ms_${id}` : "";
32073
+ if (is_return_path) status.code += `\t// return inside async block: join nursery ${id} before leaving\n`;
32074
+ const is_race = meta.mode === "race";
31970
32075
  if (is_race) status.code += `\t__nomen_nursery_race_wait((struct nomen_future **)${futures_name}, ${count_name}, ${deadline_var ? deadline_var : "0"});\n`;
31971
32076
  status.code += `\tfor (int ${idx_name} = 0; ${idx_name} < ${count_name}; ${idx_name}++) {\n`;
31972
32077
  status.code += `\t\tstruct nomen_future *_f = (struct nomen_future *)${futures_name}[${idx_name}];\n`;
@@ -31983,11 +32088,19 @@ function build_async_block_node(node, status, nir) {
31983
32088
  status.code += `\t\t__nomen_future_release(_f);\n`;
31984
32089
  status.code += `\t}\n`;
31985
32090
  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;
32091
+ }
32092
+ /**
32093
+ * Emit the join sequence for every nursery the current `return` must route
32094
+ * through — innermost first. Only nurseries belonging to the CURRENT
32095
+ * function (stack depth >= the function's entry depth) apply: a lambda
32096
+ * defined inside an `async { }` block inherits the block's id on
32097
+ * nursery_stack (for lexical spawn captures), but its `return` exits the
32098
+ * lambda, whose frame has no nursery state.
32099
+ */
32100
+ function emit_nursery_joins_on_return_c(status) {
32101
+ if (!status.nursery_stack?.length) return;
32102
+ const base = status.function_nursery_depth ?? 0;
32103
+ for (let i = status.nursery_stack.length - 1; i >= base; i--) emit_nursery_join_c(status, status.nursery_stack[i], true);
31991
32104
  }
31992
32105
  //#endregion
31993
32106
  //#region ../src/build_c/build_array_values_node.ts
@@ -32945,6 +33058,7 @@ function build_return_node(node, status, nir_value) {
32945
33058
  const ret_is_null = returns_nullable_struct && (!node.value || node.value.node_type === "value" && node.value.value === "null");
32946
33059
  if (returns_nullable_struct) {
32947
33060
  if (ret_is_null) {
33061
+ emit_nursery_joins_on_return_c(status);
32948
33062
  reclaim_all_c_scopes(status);
32949
33063
  status.code += `*${ret_has} = 0;\n`;
32950
33064
  status.code += `return (struct ${status.function_return_type.name}){0};\n`;
@@ -32953,6 +33067,7 @@ function build_return_node(node, status, nir_value) {
32953
33067
  status.code += `*${ret_has} = 1;\n`;
32954
33068
  }
32955
33069
  if (!node.value) {
33070
+ emit_nursery_joins_on_return_c(status);
32956
33071
  reclaim_all_c_scopes(status);
32957
33072
  if (status.return_assign) status.code += `${status.return_assign} = 0;\n`;
32958
33073
  else if (status.current_function_name?.toLocaleLowerCase() === "main") status.code += `return 0;\n`;
@@ -33005,6 +33120,7 @@ function build_return_node(node, status, nir_value) {
33005
33120
  status.code += `_return_val->length = ${return_array_len};\n`;
33006
33121
  status.code += `${elem_c_type}* _return_data = (${elem_c_type}*)((char*)_return_val + sizeof(struct ${array_struct}));\n`;
33007
33122
  status.code += `for (long _i = 0; _i < ${return_array_len}; _i++) _return_data[_i] = ${return_array_var}[_i];\n`;
33123
+ emit_nursery_joins_on_return_c(status);
33008
33124
  reclaim_all_c_scopes(status);
33009
33125
  status.code += `return _return_val;\n`;
33010
33126
  return;
@@ -33020,6 +33136,7 @@ function build_return_node(node, status, nir_value) {
33020
33136
  emit_allocations(node.value, status);
33021
33137
  emit_return_value(node.value, nir_value, status);
33022
33138
  status.code += `;\n`;
33139
+ emit_nursery_joins_on_return_c(status);
33023
33140
  reclaim_all_c_scopes(status);
33024
33141
  status.code += `return;\n`;
33025
33142
  } else {
@@ -33042,6 +33159,7 @@ function build_return_node(node, status, nir_value) {
33042
33159
  build_node$1(node.value, status);
33043
33160
  status.join_needs_owned_string = old_join_owned;
33044
33161
  status.return_assign = old_return_assign;
33162
+ emit_nursery_joins_on_return_c(status);
33045
33163
  reclaim_all_c_scopes(status);
33046
33164
  if (string_join) status.code += any_branch_owned ? `return _return_val;\n` : `return nomen_str_dup(_return_val);\n`;
33047
33165
  else status.code += `return _return_val;\n`;
@@ -33100,6 +33218,7 @@ function build_return_node(node, status, nir_value) {
33100
33218
  status.code += `;\n`;
33101
33219
  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
33220
  if (has_field_overrides(node.value)) emit_field_overrides("_return_val", node.value, build_node$1, status, "", ";\n");
33221
+ emit_nursery_joins_on_return_c(status);
33103
33222
  reclaim_all_c_scopes(status);
33104
33223
  status.code += `return _return_val;\n`;
33105
33224
  }
@@ -33382,7 +33501,7 @@ function emit_stmt_from_nir(child, index, statements, status) {
33382
33501
  const eval_node = nstmt.expr.node;
33383
33502
  if (eval_node.node_type === "access") {
33384
33503
  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;
33504
+ if (inner.node_type === "access_func" && inner.is_nursery_spawn) inner.is_statement = true;
33386
33505
  }
33387
33506
  emit_expr_from_nir(nstmt.expr, status);
33388
33507
  with_semicolon_tail(status, eval_node);
@@ -33911,22 +34030,9 @@ function scan_borrow_only_strings(body) {
33911
34030
  }
33912
34031
  //#endregion
33913
34032
  //#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
34033
  function build_struct_node(node, status) {
33925
34034
  if (node.is_generic) return;
33926
- if (CONCURRENCY_TYPES.has(node.name)) {
33927
- ensure_concurrency_runtime(status);
33928
- status.used_fibers = true;
33929
- }
34035
+ for (const func of node.functions) ensure_runtime_for_method(func, status);
33930
34036
  if (node.is_simple_type) {
33931
34037
  status.code += `// Struct ${node.name}\n`;
33932
34038
  build_struct_functions(node, status);
@@ -34165,6 +34271,7 @@ function build_struct_functions(node, status, skip_init = false) {
34165
34271
  const old_current_struct = status.current_struct;
34166
34272
  status.current_struct = node;
34167
34273
  for (let func of node.functions) {
34274
+ if (func.name === "#spawn") continue;
34168
34275
  if (func.name === "#init" && !func.has_body) continue;
34169
34276
  if (func.name === "#init" && skip_init) continue;
34170
34277
  if (func.is_extern) {
@@ -35022,11 +35129,14 @@ function build_function_node(node, status) {
35022
35129
  status.function_view_params = /* @__PURE__ */ new Set();
35023
35130
  const old_return_type = status.function_return_type;
35024
35131
  status.function_return_type = node.return_type;
35132
+ const old_variable_types = status.variable_types;
35133
+ status.variable_types = /* @__PURE__ */ new Map();
35025
35134
  const old_nullable_ret_has = status.nullable_ret_has_param;
35026
35135
  if (is_nullable_struct_type(node.return_type, status)) status.nullable_ret_has_param = "_ret_has";
35027
35136
  else status.nullable_ret_has_param = void 0;
35028
35137
  const old_function_name = status.current_function_name;
35029
35138
  status.current_function_name = emission_label(node);
35139
+ status.function_nursery_depth = status.nursery_stack?.length ?? 0;
35030
35140
  const old_current_function = status.current_function;
35031
35141
  status.current_function = node;
35032
35142
  for (let param of node.params) {
@@ -35076,6 +35186,7 @@ function build_function_node(node, status) {
35076
35186
  status.function_variadic_params = old_variadic_params;
35077
35187
  status.function_view_params = old_view_params;
35078
35188
  status.function_return_type = old_return_type;
35189
+ status.variable_types = old_variable_types;
35079
35190
  status.nullable_ret_has_param = old_nullable_ret_has;
35080
35191
  status.current_function_name = old_function_name;
35081
35192
  status.current_function = old_current_function;
@@ -35645,22 +35756,6 @@ function build_access_node$1(node, status) {
35645
35756
  }
35646
35757
  case "access_func": {
35647
35758
  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
35759
  if (access_func.is_nursery_spawn) {
35665
35760
  build_nursery_spawn$1(access_func, nursery_pointer_expr(node.target, status), status);
35666
35761
  return;
@@ -35965,10 +36060,35 @@ function build_access_node$1(node, status) {
35965
36060
  const label = access_func.mangled_name || trait_default_label || `${mono_struct_name}_${access_func.name.replace(/#/g, "")}`;
35966
36061
  const array_wrap = array_receiver_wrap(node, method_type, target_method, mono_struct_name, status);
35967
36062
  if (array_wrap) status.code += array_wrap.prefix;
36063
+ const ctor_target_node = node.target.node_type === "func_call" ? node.target : void 0;
36064
+ 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));
36065
+ let ctor_temp_free;
36066
+ let ctor_temp_val;
36067
+ if (ctor_temp_receiver) {
36068
+ const id = status.label_counter = (status.label_counter ?? 0) + 1;
36069
+ ctor_temp_free = `_ctorr_${id}`;
36070
+ const ctor_mono = mono_type_name(type_from_value_node$1(node.target));
36071
+ status.code += `({ struct ${ctor_mono} *${ctor_temp_free} = `;
36072
+ {
36073
+ const saved_suppress = status.suppress_dereference;
36074
+ status.suppress_dereference = true;
36075
+ build_node$1(node.target, status);
36076
+ status.suppress_dereference = saved_suppress;
36077
+ }
36078
+ status.code += `; `;
36079
+ const ret = (target_method ?? trait_default_func)?.return_type;
36080
+ if (ret?.name && ret.name !== "void") {
36081
+ const mono_ret = mono_type_name(access_func.type);
36082
+ 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);
36083
+ ctor_temp_val = `_ctorv_${id}`;
36084
+ status.code += `${ret_c} ${ctor_temp_val} = `;
36085
+ }
36086
+ }
35968
36087
  if (lambda_ret_tmp) status.code += `${lambda_ret_c} ${lambda_ret_tmp} = `;
35969
36088
  status.code += `${label}(`;
35970
36089
  if (!access_func.is_static) {
35971
- if (array_wrap) status.code += array_wrap.self_expr;
36090
+ if (ctor_temp_free) status.code += ctor_temp_free;
36091
+ else if (array_wrap) status.code += array_wrap.self_expr;
35972
36092
  else {
35973
36093
  const method_self_is_ref = !!target_method?.params?.some((p) => p.is_self_param && (p.is_ref || p.type?.is_ref));
35974
36094
  const method_self = target_method?.params?.find((p) => p.is_self_param);
@@ -36037,6 +36157,11 @@ function build_access_node$1(node, status) {
36037
36157
  }
36038
36158
  }
36039
36159
  status.code += ")";
36160
+ if (ctor_temp_free) {
36161
+ status.code += `; free(${ctor_temp_free});`;
36162
+ if (ctor_temp_val) status.code += ` ${ctor_temp_val};`;
36163
+ status.code += ` })`;
36164
+ }
36040
36165
  if (lambda_arg_temps.size > 0) {
36041
36166
  for (const tmp of lambda_arg_temps.values()) status.code += `; ${closure_dispose_arm(tmp)}`;
36042
36167
  if (lambda_ret_tmp) status.code += `; ${lambda_ret_tmp}`;
@@ -36758,7 +36883,7 @@ function build_node$1(node, status, with_semicolon = false) {
36758
36883
  const access = node;
36759
36884
  if (with_semicolon && access.access.node_type === "access_func") {
36760
36885
  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;
36886
+ if (afn.is_nursery_spawn) afn.is_statement = true;
36762
36887
  }
36763
36888
  build_access_node$1(access, status);
36764
36889
  break;
@@ -36861,9 +36986,10 @@ function build_node$1(node, status, with_semicolon = false) {
36861
36986
  //#endregion
36862
36987
  //#region ../src/build_c/build_magic_ctor.ts
36863
36988
  /**
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
36989
+ * The `#spawn` construction (the deferred-call special form — the library
36990
+ * `Thread(fn(args))` / `Fiber(fn(args))`, or any class declaring its own
36991
+ * `#spawn` hook; docs/ASYNC.md, "The construction special form") — packs
36992
+ * the wrapped call's
36867
36993
  * arguments EAGERLY into a task environment, allocates the result slot,
36868
36994
  * cancel flag, and future, and builds the task closure — a heap descriptor
36869
36995
  * over that environment (the same ABI every lambda lowers to; the runtime's
@@ -37058,6 +37184,7 @@ function build_magic_ctor_node$1(node, status) {
37058
37184
  status.code += `\t_future->done = 0;\n`;
37059
37185
  status.code += `\t_future->cancel_flag = _cancel_ptr;\n`;
37060
37186
  status.code += `\t_future->result_slot = _result_ptr;\n`;
37187
+ status.code += `\t_future->slot_fat = ${slot_c_type === "nomen_string" ? 1 : 0};\n`;
37061
37188
  status.code += `\t_future->fiber_waiters = NULL;\n`;
37062
37189
  status.code += `\t_future->owning_fiber = NULL;\n`;
37063
37190
  status.code += `\t_future->refs = 1;\n`;
@@ -37075,10 +37202,29 @@ function build_magic_ctor_node$1(node, status) {
37075
37202
  status.code += `\t_self->cancel_flag = (unsigned long long)_cancel_ptr;\n`;
37076
37203
  status.code += `\t_self->future = (unsigned long long)_future;\n`;
37077
37204
  if (info.has_started) status.code += `\t_self->started = 0;\n`;
37205
+ emit_nursery_capture_c(status, kind, (field, expr) => {
37206
+ status.code += `\t_self->${field} = ${expr};\n`;
37207
+ });
37078
37208
  status.code += `\t_self;\n`;
37079
37209
  status.code += `})\n`;
37080
37210
  }
37081
37211
  /**
37212
+ * LEXICAL NURSERY CAPTURE (ASYNC.md): a construction lexically
37213
+ * inside an `async` block stores the block's tracking-slot pointers in the
37214
+ * instance, so `.start()` — an ordinary method on the spawn class —
37215
+ * registers the future with the scope that CREATED the deferred call rather
37216
+ * than wherever the launch happens. Library spawn classes only: Thread/Fiber
37217
+ * carry the three capture fields (the generalized Awaitable flavor's field
37218
+ * contract predates them and keeps its historical emission).
37219
+ */
37220
+ function emit_nursery_capture_c(status, kind, emit_field) {
37221
+ const nursery_id = status.nursery_stack?.at(-1);
37222
+ if (nursery_id === void 0 || kind !== "Thread" && kind !== "Fiber") return;
37223
+ emit_field("nursery_futures", `(unsigned long long)&__nomen_nursery_${nursery_id}_futures`);
37224
+ emit_field("nursery_count", `(unsigned long long)&__nomen_nursery_${nursery_id}_count`);
37225
+ emit_field("nursery_cap", `(unsigned long long)&__nomen_nursery_${nursery_id}_cap`);
37226
+ }
37227
+ /**
37082
37228
  * The Phase 3c function-value construction (`Thread(() => work(n))` /
37083
37229
  * `Thread(job)` with a zero-argument func-typed local): the task closure is
37084
37230
  * an ADAPTER over the given closure. The adapter env carries the user
@@ -37152,6 +37298,7 @@ function build_fn_value_ctor(node, status, id, kind, info) {
37152
37298
  status.code += `\t_future->done = 0;\n`;
37153
37299
  status.code += `\t_future->cancel_flag = _cancel_ptr;\n`;
37154
37300
  status.code += `\t_future->result_slot = _result_ptr;\n`;
37301
+ status.code += `\t_future->slot_fat = ${dup_result ? 1 : 0};\n`;
37155
37302
  status.code += `\t_future->fiber_waiters = NULL;\n`;
37156
37303
  status.code += `\t_future->owning_fiber = NULL;\n`;
37157
37304
  status.code += `\t_future->refs = 1;\n`;
@@ -37173,6 +37320,9 @@ function build_fn_value_ctor(node, status, id, kind, info) {
37173
37320
  status.code += `\t_self->cancel_flag = (unsigned long long)_cancel_ptr;\n`;
37174
37321
  status.code += `\t_self->future = (unsigned long long)_future;\n`;
37175
37322
  if (info.has_started) status.code += `\t_self->started = 0;\n`;
37323
+ emit_nursery_capture_c(status, kind, (field, expr) => {
37324
+ status.code += `\t_self->${field} = ${expr};\n`;
37325
+ });
37176
37326
  status.code += `\t_self;\n`;
37177
37327
  status.code += `})\n`;
37178
37328
  }
@@ -37193,10 +37343,10 @@ function is_struct_ret(node, status) {
37193
37343
  //#endregion
37194
37344
  //#region ../src/build_aarch64/build_magic_ctor.ts
37195
37345
  /**
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
37346
+ * The `#spawn` construction (the deferred-call special form — the library
37347
+ * `Thread(fn(args))` / `Fiber(fn(args))`, or any class declaring its own
37348
+ * `#spawn` hook; docs/ASYNC.md, "The construction special form"),
37349
+ * aarch64 backend: the per-site
37200
37350
  * trampoline and a constructor helper are emitted as C in the companion
37201
37351
  * file; the assembly stages the wrapped call's arguments and calls the
37202
37352
  * helper, which packs the env, allocates the future machinery, builds the
@@ -37218,10 +37368,14 @@ function build_magic_ctor_node(node, status) {
37218
37368
  const tramp_name = `__nomen_spawn_${id}_trampoline`;
37219
37369
  const desc_name = `__nomen_spawn_${id}_descriptor`;
37220
37370
  const struct_name = `__nomen_spawn_${id}_args`;
37371
+ const nursery_id = status.nursery_stack?.at(-1);
37372
+ const nursery_off = nursery_id !== void 0 ? status.nursery_offsets?.get(nursery_id) : void 0;
37373
+ const capture = nursery_capture_a64(status, kind);
37374
+ const arg_types = spawn_arg_types(call, status);
37221
37375
  const arg_c_types = [];
37222
37376
  const fat_string_args = [];
37223
37377
  for (let i = 0; i < call.params.length; i++) {
37224
- const mono_name = mono_type_name(type_from_value_node$1(call.params[i]));
37378
+ const mono_name = mono_type_name(arg_types[i]);
37225
37379
  const is_class = !!status.structs.find((s) => s.name === mono_name && s.is_class);
37226
37380
  const is_trait = !!status.traits.find((t) => t.name === mono_name);
37227
37381
  arg_c_types.push(is_class || is_trait ? `struct ${mono_name} *` : `${c_type(mono_name)}`);
@@ -37277,6 +37431,10 @@ function build_magic_ctor_node(node, status) {
37277
37431
  if (i > 0) c += ", ";
37278
37432
  c += `${arg_c_types[i]} arg${i}`;
37279
37433
  }
37434
+ if (capture) {
37435
+ if (arg_c_types.length > 0) c += ", ";
37436
+ c += capture.helper_params;
37437
+ }
37280
37438
  c += `) {\n`;
37281
37439
  c += `\tstruct ${struct_name} *a = (struct ${struct_name} *)malloc(sizeof(struct ${struct_name}));\n`;
37282
37440
  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 +37449,7 @@ function build_magic_ctor_node(node, status) {
37291
37449
  c += `\tf->refs = 1;\n`;
37292
37450
  c += `\tf->cancel_flag = a->cancel_flag;\n`;
37293
37451
  c += `\tf->result_slot = a->result_slot;\n`;
37452
+ c += `\tf->slot_fat = ${slot_c_type === "nomen_string" ? 1 : 0};\n`;
37294
37453
  c += `\tf->fiber_waiters = NULL;\n`;
37295
37454
  c += `\tf->owning_fiber = 0;\n`;
37296
37455
  c += `\ta->future = f;\n`;
@@ -37307,6 +37466,7 @@ function build_magic_ctor_node(node, status) {
37307
37466
  c += `\tself->cancel_flag = (unsigned long long)a->cancel_flag;\n`;
37308
37467
  c += `\tself->future = (unsigned long long)f;\n`;
37309
37468
  if (info.has_started) c += `\tself->started = 0;\n`;
37469
+ if (capture) c += capture.field_stores.join("");
37310
37470
  c += `\treturn self;\n`;
37311
37471
  c += `}\n`;
37312
37472
  if (!status.file_scope_c) status.file_scope_c = "";
@@ -37318,6 +37478,8 @@ function build_magic_ctor_node(node, status) {
37318
37478
  arg_slot.push(total_arg_slots);
37319
37479
  total_arg_slots += fat_string_args[i] ? 2 : 1;
37320
37480
  }
37481
+ const capture_base_slot = total_arg_slots;
37482
+ if (capture) total_arg_slots += capture.arg_count;
37321
37483
  if (total_arg_slots === 0) emit_asm(status, `bl _${helper_name}\n`);
37322
37484
  else {
37323
37485
  const args_base = allocate_stack_space(status, total_arg_slots * 8, 16);
@@ -37328,6 +37490,17 @@ function build_magic_ctor_node(node, status) {
37328
37490
  emit_asm(status, `str x0, [x29, #${args_base + arg_slot[i] * 8}]\n`);
37329
37491
  if (fat_string_args[i]) emit_asm(status, `str x1, [x29, #${args_base + (arg_slot[i] + 1) * 8}]\n`);
37330
37492
  }
37493
+ if (capture && nursery_off) {
37494
+ const addrs = [
37495
+ nursery_off.futures_off,
37496
+ nursery_off.count_off,
37497
+ nursery_off.cap_off
37498
+ ];
37499
+ for (let k = 0; k < addrs.length; k++) {
37500
+ emit_asm(status, `add x9, x29, #${addrs[k]}\n`);
37501
+ emit_asm(status, `str x9, [x29, #${args_base + (capture_base_slot + k) * 8}]\n`);
37502
+ }
37503
+ }
37331
37504
  const NUM_REG_ARGS = 8;
37332
37505
  const overflow_count = Math.max(0, total_arg_slots - NUM_REG_ARGS);
37333
37506
  if (overflow_count > 0) {
@@ -37402,7 +37575,8 @@ function build_fn_value_ctor_a64(node, status, id, kind, info) {
37402
37575
  c += `\t__nomen_future_release(a->future);\n`;
37403
37576
  c += `}\n`;
37404
37577
  c += `static struct nomen_closure ${desc_name} = { (void *)${tramp_name}, NULL, 0, NULL };\n`;
37405
- c += `void *${helper_name}(struct nomen_closure *fn) {\n`;
37578
+ const fnval_capture = nursery_capture_a64(status, kind);
37579
+ c += `void *${helper_name}(struct nomen_closure *fn${fnval_capture ? `, ${fnval_capture.helper_params}` : ""}) {\n`;
37406
37580
  c += `\tstruct ${struct_name} *a = (struct ${struct_name} *)malloc(sizeof(struct ${struct_name}));\n`;
37407
37581
  c += `\ta->fn = fn;\n`;
37408
37582
  c += `\ta->result_slot = (${slot_c_type} *)${returns_value ? `malloc(sizeof(${slot_c_type}))` : "malloc(16)"};\n`;
@@ -37416,6 +37590,7 @@ function build_fn_value_ctor_a64(node, status, id, kind, info) {
37416
37590
  c += `\tf->refs = 1;\n`;
37417
37591
  c += `\tf->cancel_flag = a->cancel_flag;\n`;
37418
37592
  c += `\tf->result_slot = a->result_slot;\n`;
37593
+ c += `\tf->slot_fat = ${dup_result ? 1 : 0};\n`;
37419
37594
  c += `\tf->fiber_waiters = NULL;\n`;
37420
37595
  c += `\tf->owning_fiber = 0;\n`;
37421
37596
  c += `\ta->future = f;\n`;
@@ -37432,6 +37607,7 @@ function build_fn_value_ctor_a64(node, status, id, kind, info) {
37432
37607
  c += `\tself->cancel_flag = (unsigned long long)a->cancel_flag;\n`;
37433
37608
  c += `\tself->future = (unsigned long long)f;\n`;
37434
37609
  if (info.has_started) c += `\tself->started = 0;\n`;
37610
+ if (fnval_capture) c += fnval_capture.field_stores.join("");
37435
37611
  c += `\treturn self;\n`;
37436
37612
  c += `}\n`;
37437
37613
  if (!status.file_scope_c) status.file_scope_c = "";
@@ -37447,8 +37623,41 @@ function build_fn_value_ctor_a64(node, status, id, kind, info) {
37447
37623
  if (!status.moved) status.moved = /* @__PURE__ */ new Set();
37448
37624
  status.moved.add(name);
37449
37625
  }
37626
+ if (fnval_capture) {
37627
+ const fnval_nursery_id = status.nursery_stack?.at(-1);
37628
+ const fnval_off = fnval_nursery_id !== void 0 ? status.nursery_offsets?.get(fnval_nursery_id) : void 0;
37629
+ if (fnval_off) {
37630
+ const fnval_park = allocate_stack_space(status, 8, 8);
37631
+ emit_asm(status, `str x0, [x29, #${fnval_park}]\n`);
37632
+ emit_asm(status, `add x1, x29, #${fnval_off.futures_off}\n`);
37633
+ emit_asm(status, `add x2, x29, #${fnval_off.count_off}\n`);
37634
+ emit_asm(status, `add x3, x29, #${fnval_off.cap_off}\n`);
37635
+ emit_asm(status, `ldr x0, [x29, #${fnval_park}]\n`);
37636
+ }
37637
+ }
37450
37638
  emit_asm(status, `bl _${helper_name}\n`);
37451
37639
  }
37640
+ /**
37641
+ * LEXICAL NURSERY CAPTURE (ASYNC.md), aarch64 backend. The
37642
+ * construction helper is companion C and cannot address the enclosing async
37643
+ * block's frame, so the assembly passes the three tracking-slot addresses as
37644
+ * trailing helper arguments and the helper stores them into the instance's
37645
+ * capture fields — the lexical-at-construction counterpart of the C
37646
+ * backend's emit_nursery_capture_c. Applies to Thread/Fiber only (they
37647
+ * carry the capture fields).
37648
+ */
37649
+ function nursery_capture_a64(status, kind) {
37650
+ if (status.nursery_stack?.at(-1) === void 0 || kind !== "Thread" && kind !== "Fiber") return void 0;
37651
+ return {
37652
+ helper_params: "unsigned long long nursery_futures, unsigned long long nursery_count, unsigned long long nursery_cap",
37653
+ field_stores: [
37654
+ `\tself->nursery_futures = nursery_futures;\n`,
37655
+ `\tself->nursery_count = nursery_count;\n`,
37656
+ `\tself->nursery_cap = nursery_cap;\n`
37657
+ ],
37658
+ arg_count: 3
37659
+ };
37660
+ }
37452
37661
  //#endregion
37453
37662
  //#region ../src/build_aarch64/build_panic_node.ts
37454
37663
  function build_panic_node(node, status) {
@@ -37576,7 +37785,7 @@ function build_value_node(node, status) {
37576
37785
  if (paramReg !== "x0") emit_asm(status, `mov x0, ${paramReg}`);
37577
37786
  } 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
37787
  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)) {
37788
+ 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
37789
  if (paramReg !== "x0") emit_asm(status, `mov x0, ${paramReg}`);
37581
37790
  } else emit_asm(status, deref_load_instr(paramReg, param_type_name));
37582
37791
  } else if (paramReg !== "x0") emit_asm(status, `mov x0, ${paramReg}`);
@@ -37628,7 +37837,7 @@ function build_value_node(node, status) {
37628
37837
  if (status.function_ref_params?.has(value) || status.function_ref_params?.has(original_value)) {
37629
37838
  if (alloc_reg !== "x0") emit_asm(status, `mov x0, ${alloc_reg}\n`);
37630
37839
  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));
37840
+ 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
37841
  } else if (alloc_reg !== "x0") {
37633
37842
  if (alloc_reg.startsWith("d")) emit_asm(status, `fmov x0, ${alloc_reg}`);
37634
37843
  else emit_asm(status, `mov x0, ${alloc_reg}`);
@@ -37836,7 +38045,7 @@ function build_node(node, status, with_semicolon = false) {
37836
38045
  const access = node;
37837
38046
  if (with_semicolon && access.access.node_type === "access_func") {
37838
38047
  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;
38048
+ if (afn.is_nursery_spawn) afn.is_statement = true;
37840
38049
  }
37841
38050
  build_access_node(access, status);
37842
38051
  break;
@@ -39757,7 +39966,7 @@ function build_operand(node, target_reg, status) {
39757
39966
  if (paramReg) {
39758
39967
  if (status.function_param_vars?.has(value) || status.function_ref_params?.has(value)) {
39759
39968
  const param_type_name = node.type?.name;
39760
- if (param_type_name && status.structs.find((s) => s.name === param_type_name && s.is_class)) {
39969
+ 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
39970
  if (paramReg !== target_reg) emit_asm(status, `mov ${target_reg}, ${paramReg}`);
39762
39971
  } else emit_asm(status, `ldr ${target_reg}, [${paramReg}]`);
39763
39972
  } else if (paramReg !== target_reg) emit_asm(status, `mov ${target_reg}, ${paramReg}`);
@@ -40990,113 +41199,6 @@ function array_licm_enabled() {
40990
41199
  return array_licm_on;
40991
41200
  }
40992
41201
  //#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
41202
  //#region ../src/build_aarch64/build_nursery_spawn.ts
41101
41203
  /**
41102
41204
  * Build a `name.start(Thread(fn(args)))` escape-hatch call for aarch64
@@ -41680,22 +41782,6 @@ function build_access_node(node, status) {
41680
41782
  break;
41681
41783
  case "access_func": {
41682
41784
  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
41785
  if (access_func.is_nursery_spawn) {
41700
41786
  build_nursery_spawn(node, access_func, status);
41701
41787
  return;
@@ -41741,6 +41827,46 @@ function get_base_target(node) {
41741
41827
  if (node.target.node_type === "access") return get_base_target(node.target);
41742
41828
  return node.target;
41743
41829
  }
41830
+ /**
41831
+ * Resolve the concrete type of an access target (a local/param/`self` value or
41832
+ * a nested access), consulting the containing struct's field declarations when
41833
+ * a cached type is absent — an inferred `move` class field
41834
+ * (`pub move rules = RuleSet()`) carries no cached AccessFieldNode.type.
41835
+ */
41836
+ function resolve_access_target_type(target, status) {
41837
+ if (target.node_type === "value") {
41838
+ const name = target.value;
41839
+ if (name === "self" && status.current_struct) return new Type(status.current_struct.name);
41840
+ if (status.variable_types?.has(name)) return status.variable_types.get(name);
41841
+ const decl = status.scoped_declarations.findLast((d) => d.name === name);
41842
+ if (decl?.type?.name) return decl.type;
41843
+ return type_from_value_node$1(target);
41844
+ }
41845
+ if (target.node_type === "access") return type_from_value_node$1(target) ?? resolve_access_type(target, status) ?? void 0;
41846
+ return type_from_value_node$1(target);
41847
+ }
41848
+ /**
41849
+ * Whether reading the final field of `node` must dereference an intermediate
41850
+ * pointer. The summed-offset-from-root load (`compute_field_offset` + the
41851
+ * base address) is only valid while every hop is an INLINE value struct. A
41852
+ * class-typed field — or a class-typed root value — stores a POINTER, so the
41853
+ * chain has to be built recursively (each pointer hop loaded) before the
41854
+ * final field is read. Without this, `state.rules.blocks.length` (State/
41855
+ * BlockParserState is a class) summed 8+8+8 and loaded from `state + 24`.
41856
+ */
41857
+ function access_chain_crosses_class(node, status) {
41858
+ const base_type = resolve_access_target_type(get_base_target(node), status);
41859
+ if (base_type?.name && status.structs.find((s) => s.name === base_type.name && s.is_class)) return true;
41860
+ let cursor = node;
41861
+ while (cursor.node_type === "access") {
41862
+ const acc = cursor;
41863
+ if (acc.access.node_type !== "access_field") return false;
41864
+ const ft = resolve_field_type(acc.access, resolve_access_target_type(acc.target, status)?.name, status);
41865
+ if (ft?.name && status.structs.find((s) => s.name === ft.name && s.is_class)) return true;
41866
+ cursor = acc.target;
41867
+ }
41868
+ return false;
41869
+ }
41744
41870
  function get_param_reg(name, status) {
41745
41871
  return status.function_param_regs?.get(name);
41746
41872
  }
@@ -42140,9 +42266,11 @@ function build_access_field(node, status) {
42140
42266
  emit_string_length(node.target, status);
42141
42267
  return;
42142
42268
  }
42143
- const offset = compute_field_offset(node, status);
42269
+ let offset = compute_field_offset(node, status);
42144
42270
  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);
42271
+ const target_access_field = node.target.node_type === "access" && node.target.access.node_type === "access_field" ? node.target.access : void 0;
42272
+ 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;
42273
+ 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
42274
  if (node.target.node_type === "access" && node.target.access.node_type === "access_func") {
42147
42275
  build_node(node.target, status);
42148
42276
  ensure_newline(status);
@@ -42173,8 +42301,13 @@ function build_access_field(node, status) {
42173
42301
  build_node(node.target, status);
42174
42302
  ensure_newline(status);
42175
42303
  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) {
42304
+ 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);
42305
+ 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 || "";
42306
+ if (!!field_type && !class_access_field_type?.is_ref && !class_access_field_type?.is_nullable && is_struct_type(field_type, status)) {
42307
+ if (final_offset > 0) emit_asm(status, `add x0, x0, #${final_offset}\n`);
42308
+ return;
42309
+ }
42310
+ if (field_type === "string" || class_access_field_type?.is_view) {
42178
42311
  emit_string_pair_load_at(status, "x0", final_offset);
42179
42312
  return;
42180
42313
  }
@@ -42204,14 +42337,14 @@ function build_access_field(node, status) {
42204
42337
  if (paramReg !== "x0") emit_asm(status, `mov x0, ${paramReg}\n`);
42205
42338
  } else emit_var_load(status, "x0", name, 8);
42206
42339
  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 || "";
42340
+ 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);
42341
+ const resolved_field_type = field_type_obj?.type_args?.length ? mono_type_name(field_type_obj) : field_type_obj?.name || "";
42209
42342
  if (!!resolved_field_type && !field_type_obj?.is_ref && !field_type_obj?.is_nullable && is_struct_type(resolved_field_type, status)) {
42210
42343
  if (final_offset > 0) emit_asm(status, `add x0, x0, #${final_offset}\n`);
42211
42344
  return;
42212
42345
  }
42213
- const field_type = access_field.type?.name || "";
42214
- if (field_type === "string" || access_field.type?.is_view) {
42346
+ const field_type = resolved_field_type;
42347
+ if (field_type === "string" || field_type_obj?.is_view) {
42215
42348
  emit_string_pair_load_at(status, "x0", final_offset);
42216
42349
  return;
42217
42350
  }
@@ -42222,16 +42355,25 @@ function build_access_field(node, status) {
42222
42355
  else emit_asm(status, `ldr x0, [x0, #${final_offset}]\n`);
42223
42356
  return;
42224
42357
  }
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);
42358
+ let target_built = false;
42359
+ if (node.target.node_type === "access" && access_chain_crosses_class(node, status)) {
42360
+ build_node(node.target, status);
42234
42361
  ensure_newline(status);
42362
+ offset = get_field_offset(target_type?.name || "", access_field.name, status);
42363
+ target_built = true;
42364
+ }
42365
+ if (!target_built) {
42366
+ if (base.node_type === "value") {
42367
+ const name = base.value;
42368
+ const paramReg = get_param_reg(name, status);
42369
+ if (paramReg) {
42370
+ if (paramReg !== "x0") emit_asm(status, `mov x0, ${paramReg}\n`);
42371
+ } else if (is_local_ref_var(name, status)) emit_deref_var_address(status, "x0", name);
42372
+ else emit_var_address(status, "x0", name);
42373
+ } else {
42374
+ build_node(base, status);
42375
+ ensure_newline(status);
42376
+ }
42235
42377
  }
42236
42378
  const field_type_obj = resolve_field_type(access_field, target_type?.name, status);
42237
42379
  const resolved_field_type = field_type_obj?.name || "";
@@ -42758,20 +42900,25 @@ function build_access_method(node, access_func, status) {
42758
42900
  } else if (!target_is_simple && node.target.node_type === "access") {
42759
42901
  const access_target = node.target;
42760
42902
  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);
42903
+ if (access_chain_crosses_class(access_target, status)) {
42904
+ build_node(access_target, status);
42772
42905
  ensure_newline(status);
42906
+ } else {
42907
+ const offset = compute_field_offset(access_target, status);
42908
+ const base = get_base_target(access_target);
42909
+ if (base.node_type === "value") {
42910
+ const name = base.value;
42911
+ const paramReg = get_param_reg(name, status);
42912
+ if (paramReg) {
42913
+ if (paramReg !== "x0") emit_asm(status, `mov x0, ${paramReg}\n`);
42914
+ } else if (is_local_ref_var(name, status)) emit_deref_var_address(status, "x0", name);
42915
+ else emit_var_address(status, "x0", name);
42916
+ } else {
42917
+ build_node(base, status);
42918
+ ensure_newline(status);
42919
+ }
42920
+ if (offset > 0) emit_asm(status, `add x0, x0, #${offset}\n`);
42773
42921
  }
42774
- if (offset > 0) emit_asm(status, `add x0, x0, #${offset}\n`);
42775
42922
  if (target_type.is_array && target_type.is_array_heap) {
42776
42923
  emit_asm(status, `ldr x0, [x0]\n`);
42777
42924
  emit_asm(status, `add x0, x0, #8\n`);
@@ -42793,6 +42940,13 @@ function build_access_method(node, access_func, status) {
42793
42940
  trait_recv_slot = allocate_stack_space(status, 8, 8);
42794
42941
  emit_asm(status, `str x0, [x29, #${trait_recv_slot}]\n`);
42795
42942
  }
42943
+ const ctor_temp_node = node.target.node_type === "func_call" ? node.target : void 0;
42944
+ 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));
42945
+ let ctor_recv_slot;
42946
+ if (ctor_temp_receiver && !access_func.is_static && !receiver_is_string) {
42947
+ ctor_recv_slot = allocate_stack_space(status, 8, 8);
42948
+ emit_asm(status, `str x0, [x29, #${ctor_recv_slot}]\n`);
42949
+ }
42796
42950
  const raw_needs_self = !access_func.is_static && access_func.params.length > 0 && !receiver_is_string;
42797
42951
  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
42952
  const elide_self_save = raw_needs_self && !!inline_func0 && naked_inline_skips_self(inline_func0, status.platform);
@@ -43010,12 +43164,20 @@ function build_access_method(node, access_func, status) {
43010
43164
  }
43011
43165
  if (outgoing_size > 0) emit_asm(status, `add sp, sp, #${outgoing_size}\n`);
43012
43166
  emit_dispose_lambda_args_a64(status, lambda_arg_slots);
43167
+ if (ctor_recv_slot !== void 0) {
43168
+ emit_asm(status, `str x0, [sp, #-16]!\n`);
43169
+ emit_asm(status, `str x1, [sp, #-16]!\n`);
43170
+ emit_asm(status, `ldr x0, [x29, #${ctor_recv_slot}]\n`);
43171
+ emit_free(status);
43172
+ emit_asm(status, `ldr x1, [sp], #16\n`);
43173
+ emit_asm(status, `ldr x0, [sp], #16\n`);
43174
+ }
43013
43175
  if (frees_string_receiver) {
43014
43176
  emit_asm(status, `ldr x9, [sp]\n`);
43015
43177
  emit_asm(status, `str x0, [sp, #-16]!\n`);
43016
43178
  emit_asm(status, `str x1, [sp, #-16]!\n`);
43017
43179
  emit_asm(status, `mov x0, x9\n`);
43018
- emit_asm(status, `bl _nomen_free_wrap\n`);
43180
+ emit_free(status);
43019
43181
  emit_asm(status, `ldr x1, [sp], #16\n`);
43020
43182
  emit_asm(status, `ldr x0, [sp], #16\n`);
43021
43183
  emit_asm(status, `add sp, sp, #16\n`);
@@ -43415,15 +43577,22 @@ function scan_spawn_callees(root, result) {
43415
43577
  if (node.node_type === "spawn") {
43416
43578
  spawn_call = node.call;
43417
43579
  spawn_ret = node.function_return_type;
43580
+ } else if (node.node_type === "func_call") {
43581
+ if (node.is_thread_ctor || node.is_fiber_ctor) {
43582
+ spawn_call = node.params?.[0];
43583
+ spawn_ret = node.function_return_type;
43584
+ }
43418
43585
  } 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) {
43586
+ if (node.access.is_nursery_spawn) {
43423
43587
  spawn_call = node.access.params?.[0]?.params?.[0];
43424
43588
  spawn_ret = node.access.function_return_type;
43425
43589
  }
43426
43590
  }
43591
+ if (spawn_call && spawn_call.node_type === "func") {
43592
+ mark_fn_value_callees(spawn_call, spawn_ret, result);
43593
+ spawn_call = void 0;
43594
+ spawn_ret = void 0;
43595
+ }
43427
43596
  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
43597
  if (name_and_ret && name_and_ret[0] && name_and_ret[1].name === "string") result.add(name_and_ret[0]);
43429
43598
  for (const key of Object.keys(node)) {
@@ -43435,6 +43604,34 @@ function scan_spawn_callees(root, result) {
43435
43604
  };
43436
43605
  visit(root);
43437
43606
  }
43607
+ /**
43608
+ * Heap-normalize a function-value spawn's lambda (spawn_ret is the
43609
+ * construction's function return type; the lambda's own signature is the
43610
+ * fallback). Two marks:
43611
+ *
43612
+ * 1. The LAMBDA's emission label — a literal or borrowed-variable return
43613
+ * inside the lambda is then strdup'd at the lambda's return site (the
43614
+ * return build classifies through the same label).
43615
+ * 2. Every string-returning call the lambda returns DIRECTLY
43616
+ * (`() => fn(args)`): the call's pair flows to the slot unchanged (a call
43617
+ * result is not re-strdup'd at return), so the callee itself must
43618
+ * normalize — the same mark the direct-call form puts on its callee.
43619
+ */
43620
+ function mark_fn_value_callees(lambda, spawn_ret, result) {
43621
+ if ((spawn_ret?.name ?? lambda.return_type?.name) !== "string") return;
43622
+ result.add(emission_label(lambda));
43623
+ const visit = (node) => {
43624
+ if (!node || typeof node !== "object") return;
43625
+ 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 }));
43626
+ for (const key of Object.keys(node)) {
43627
+ if (key === "parent" || key === "scope") continue;
43628
+ const val = node[key];
43629
+ if (Array.isArray(val)) for (const item of val) visit(item);
43630
+ else if (val && typeof val === "object") visit(val);
43631
+ }
43632
+ };
43633
+ for (const s of lambda.statements ?? []) visit(s);
43634
+ }
43438
43635
  function scan_statements(statements, result, struct_name) {
43439
43636
  for (const stmt of statements) {
43440
43637
  if (stmt.node_type === "struct") {
@@ -44072,6 +44269,8 @@ function build(root, options = {}) {
44072
44269
  status.code = reduce_pointer_walks(status.code);
44073
44270
  status.code = unroll_loops_x2(status.code);
44074
44271
  status.code = eliminate_dead_cycle_moves(status.code);
44272
+ status.code = expand_large_mov_immediates(status.code);
44273
+ status.code = expand_far_adr_addresses(status.code);
44075
44274
  if (options.audit) {
44076
44275
  status.code = status.code.replaceAll("bl _malloc\n", "bl _nomen_malloc_wrap\n");
44077
44276
  status.code = status.code.replaceAll("bl _calloc\n", "bl _nomen_calloc_wrap\n");
@@ -44235,7 +44434,9 @@ const CALL_KEYWORDS = /* @__PURE__ */ new Set([
44235
44434
  "trait",
44236
44435
  "enum",
44237
44436
  "bitset",
44238
- "cast"
44437
+ "cast",
44438
+ "panic",
44439
+ "todo"
44239
44440
  ]);
44240
44441
  const NO_SPACE_BEFORE = /* @__PURE__ */ new Set([
44241
44442
  ",",
@@ -44316,8 +44517,8 @@ function needs_space(pieces, i) {
44316
44517
  if (NO_SPACE_AFTER.has(previous.text)) return false;
44317
44518
  if (previous.text === "out" || previous.text === "in") return current.text !== ".";
44318
44519
  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);
44520
+ if (current.text === ".") return !is_value_end(pieces, i - 1);
44521
+ if (current.text === "(" || current.text === "[") return !is_value_end(pieces, i - 1) && !CALL_KEYWORDS.has(previous.text);
44321
44522
  if (current.kind === "comment") return current.space_before;
44322
44523
  return true;
44323
44524
  }
@@ -44330,13 +44531,20 @@ function is_sign(pieces, i) {
44330
44531
  if (PREFIX_POSITIONS.has(previous.text)) return true;
44331
44532
  return previous.kind === "word" && KEYWORDS.has(previous.text);
44332
44533
  }
44333
- /** Can the piece end a value, so that a following `(`, `[` or `.` binds to it? */
44334
- function is_value_end(piece) {
44534
+ /**
44535
+ * Can the piece at `i` end a value, so that a following `(`, `[` or `.` binds
44536
+ * to it? A word after `.` is a member name, so it binds even when it spells a
44537
+ * keyword (`old_keys.move(i)`).
44538
+ */
44539
+ function is_value_end(pieces, i) {
44540
+ const piece = pieces[i];
44335
44541
  switch (piece.kind) {
44336
44542
  case "number":
44337
44543
  case "string":
44338
44544
  case "char": return true;
44339
- case "word": return !KEYWORDS.has(piece.text);
44545
+ case "word":
44546
+ if (i > 0 && pieces[i - 1].text === ".") return true;
44547
+ return !KEYWORDS.has(piece.text);
44340
44548
  case "symbol": return piece.text === ")" || piece.text === "]" || piece.text === ">" || piece.text === "?";
44341
44549
  default: return false;
44342
44550
  }
@@ -45055,12 +45263,12 @@ function is_list_open(pieces, i) {
45055
45263
  const bracket = pieces[i].text;
45056
45264
  if (bracket === "(") {
45057
45265
  const previous = pieces[i - 1];
45058
- return !!previous && is_value_end(previous) && previous.text !== "=";
45266
+ return !!previous && is_value_end(pieces, i - 1) && previous.text !== "=";
45059
45267
  }
45060
45268
  if (bracket === "[") {
45061
45269
  const previous = pieces[i - 1];
45062
45270
  if (previous?.kind === "word" && KEYWORDS.has(previous.text)) return false;
45063
- return !previous || !is_value_end(previous);
45271
+ return !previous || !is_value_end(pieces, i - 1);
45064
45272
  }
45065
45273
  return false;
45066
45274
  }
@@ -45133,9 +45341,7 @@ function is_redundant(type, value) {
45133
45341
  if (value.length > type.length && value[type.length].text === "(") {
45134
45342
  if (match_bracket(value, type.length) === value.length - 1) {
45135
45343
  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
- }
45344
+ if (type.every((piece, index) => piece.text === constructor[index].text)) return true;
45139
45345
  }
45140
45346
  }
45141
45347
  if (value.length === 1 && type.length === 1) return literal_type(value[0]) === type[0].text && SAFE_LITERAL_TYPES.has(type[0].text);
@@ -48902,8 +49108,17 @@ function check_declaration_node(decl, status) {
48902
49108
  const src_sv = /^[A-Za-z_][A-Za-z0-9_]*$/.test(src_name ?? "") ? status.values.findLast((v) => v.name === src_name) : void 0;
48903
49109
  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
49110
  }
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") {
49111
+ if (!decl.type.name) {
49112
+ decl.type = type_from_value_node(decl.value, status);
49113
+ if (!decl.type.name && status.type_params.length > 0 && decl.value.node_type === "func_call") {
49114
+ const call = decl.value;
49115
+ if (call.type_args?.length) {
49116
+ const constructed = new Type(call.name);
49117
+ constructed.type_args = call.type_args;
49118
+ decl.type = constructed;
49119
+ }
49120
+ }
49121
+ } else if (decl.type.is_array && decl.value.node_type === "array") {
48907
49122
  const value_type = type_from_value_node(decl.value, status);
48908
49123
  if (value_type.is_array && value_type.length) decl.type.length = value_type.length;
48909
49124
  }
@@ -49473,20 +49688,25 @@ function infer_field_type_from_value(value, status) {
49473
49688
  * Resolving inferred field types upfront (right after gather_structs registers
49474
49689
  * every struct) makes them visible regardless of declaration order.
49475
49690
  *
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.
49691
+ * Only the field-type inference runs for generic structs too: a generic
49692
+ * field's initializer names the constructed type in terms of the struct's own
49693
+ * type params (`var items = Buffer<T>()` → `Buffer<T>`), which is exactly the
49694
+ * annotation it replaces, so recording it upfront is safe and keeps it visible
49695
+ * to the ownership classifier and to monomorphization. The buffer rewrite and
49696
+ * owning-param marking stay non-generic, mirroring check_struct_node. The
49697
+ * inference is structural to avoid re-running the full declaration check and
49698
+ * its side effects; check_struct_node later skips already-resolved fields, so
49699
+ * each field is resolved exactly once.
49480
49700
  */
49481
49701
  function resolve_struct_field_types(status) {
49482
49702
  for (const struct of status.structs) {
49483
- if (struct.is_generic) continue;
49484
49703
  for (const field of struct.fields) {
49485
49704
  if (field.type.name) continue;
49486
49705
  if (!field.value) continue;
49487
49706
  const t = infer_field_type_from_value(field.value, status);
49488
49707
  if (t?.name) field.type = t;
49489
49708
  }
49709
+ if (struct.is_generic) continue;
49490
49710
  rewrite_generic_buffer_fields(struct, status);
49491
49711
  mark_owning_auto_init_params(struct, status);
49492
49712
  }
@@ -49502,8 +49722,9 @@ function resolve_struct_field_types(status) {
49502
49722
  *
49503
49723
  * A trait method with a default body need not be overridden; if it isn't,
49504
49724
  * 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.
49725
+ * onto the struct by synthesize_generic_trait_defaults). `#init`,
49726
+ * `#destroy` and `#spawn` are lifecycle hooks, not contract methods, so
49727
+ * they're skipped.
49507
49728
  */
49508
49729
  function check_trait_conformance(struct, status) {
49509
49730
  for (let i = 0; i < struct.traits.length; i++) {
@@ -49514,7 +49735,7 @@ function check_trait_conformance(struct, status) {
49514
49735
  const substitution = /* @__PURE__ */ new Map();
49515
49736
  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
49737
  for (const trait_func of trait.functions) {
49517
- if (trait_func.name === "#init" || trait_func.name === "#destroy") continue;
49738
+ if (trait_func.name === "#init" || trait_func.name === "#destroy" || trait_func.name === "#spawn") continue;
49518
49739
  const overrides = struct.functions.filter((f) => f.name === trait_func.name);
49519
49740
  if (overrides.length === 0) {
49520
49741
  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 +50486,6 @@ function check_function_node(func, status) {
50265
50486
  }
50266
50487
  }
50267
50488
  //#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
50489
  //#region ../src/check/utils/enum_mono.ts
50317
50490
  /**
50318
50491
  * Monomorphize a generic enum (`enum Result<T, E>`) applied to concrete type
@@ -50496,39 +50669,37 @@ function synthesize_lambda_name(func, status) {
50496
50669
  //#endregion
50497
50670
  //#region ../src/check/utils/validate_spawn_args_sendable.ts
50498
50671
  /**
50499
- * Validate a spawn's packed arguments (CLOSURE.md Phase 3d).
50672
+ * Validate a spawn's packed arguments (CLOSURE.md Phase 3d; the shrunk
50673
+ * `Sendable` — docs/ASYNC.md, "Design decisions").
50500
50674
  *
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.
50675
+ * `Sendable` gates exactly the case that can race: a SHARED class/trait
50676
+ * reference crossing the task boundary (a plain argument — the packed env
50677
+ * aliases the instance). Everything else is exempt:
50678
+ *
50679
+ * - MOVED arguments: the callee's `move T` parameter takes exclusive
50680
+ * ownership, so no one else can touch the instance concurrently;
50681
+ * - scalars, strings, and everything `is_sendable_type` accepts (copied or
50682
+ * safe by value — strings are deep-copied at pack);
50683
+ * - owning VALUE structs whose fields are all safe (the pack deep-copies
50684
+ * them; `is_sendable_type`'s field recursion rejects a struct with an
50685
+ * unsafe class field).
50686
+ *
50687
+ * The nursery-borrow exception (a non-Sendable class aliased into a task
50688
+ * inside `async { }`) is RETIRED: mark the class `Sendable`, move it in
50689
+ * (a `move` parameter), or share it through a `Sendable` primitive
50690
+ * (`Mutex`/`Channel`).
50508
50691
  */
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) {
50692
+ function validate_spawn_args_sendable(call, status, reason) {
50693
+ const callee_params = call.resolved_function?.params?.filter((p) => !p.is_self_param);
50694
+ for (let i = 0; i < call.params.length; i++) {
50695
+ const param = call.params[i];
50512
50696
  let arg_type = type_from_value_node(param, status);
50513
50697
  const stamped = param.type;
50514
50698
  if (!arg_type.name && stamped?.name) arg_type = stamped;
50699
+ if (callee_params?.[i]?.is_moved) continue;
50515
50700
  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);
50701
+ 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
50702
  }
50531
- return result;
50532
50703
  }
50533
50704
  //#endregion
50534
50705
  //#region ../src/check/check_function_call_node.ts
@@ -50548,28 +50719,17 @@ function find_free_function(status, name) {
50548
50719
  return status.functions.findLast((f) => f.name === name && !is_method(f)) ?? status.functions.findLast((f) => f.name === name);
50549
50720
  }
50550
50721
  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);
50722
+ const magic_ctor = resolve_declared_struct(node.name, status)?.functions.some((f) => f.name === "#spawn") ? node.name : void 0;
50553
50723
  const first = node.params[0];
50554
50724
  const first_decl = first?.node_type === "value" && typeof first.value === "string" ? status.values.findLast((v) => v.name === first.value) : void 0;
50555
50725
  const first_fn = first?.node_type === "value" && typeof first.value === "string" ? status.functions.findLast((f) => f.name === first.value) : void 0;
50556
50726
  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
50727
  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
- }
50728
+ if (magic_ctor && !spawn_ctor_shape && !find_free_function(status, node.name)) {
50729
+ add_error(status, "`#spawn` expects a call or a zero-argument function value (bind arguments in the lambda's captures)", first?.start ?? node.start);
50730
+ return false;
50570
50731
  }
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);
50732
+ if (magic_ctor && spawn_ctor_shape) return check_magic_ctor(node, status, magic_ctor);
50573
50733
  let func = find_free_function(status, node.name);
50574
50734
  if (func?.is_closure && func.captures?.length) func = void 0;
50575
50735
  if (!func) {
@@ -50734,7 +50894,18 @@ function monomorphize(generic_struct, type_args, status) {
50734
50894
  const mono_fields = generic_struct.fields.map((field) => {
50735
50895
  const resolved_type = substitute_type(field.type, substitution);
50736
50896
  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);
50897
+ if (mono_field.value) {
50898
+ if (!mono_field.type.name && mono_field.value.node_type === "func_call") {
50899
+ const call = mono_field.value;
50900
+ const target = status.structs.find((s) => s.name === call.name);
50901
+ if (target?.type_params.length && call.type_args?.length === target.type_params.length) {
50902
+ const constructed = new Type(call.name);
50903
+ constructed.type_args = call.type_args.map((arg) => substitute_type(arg, substitution));
50904
+ mono_field.type = constructed;
50905
+ }
50906
+ }
50907
+ substitute_raw_in_node(mono_field.value, substitution, status.structs, void 0, status.traits, status.enums);
50908
+ }
50738
50909
  mono_field.is_library = field.is_library;
50739
50910
  mono_field.is_readonly = field.is_readonly;
50740
50911
  mono_field.is_view_keyword = field.is_view_keyword;
@@ -50761,6 +50932,7 @@ function monomorphize(generic_struct, type_args, status) {
50761
50932
  mono_struct.source_type_args = flat_args;
50762
50933
  mono_struct.is_class = generic_struct.is_class;
50763
50934
  mono_struct.is_library = generic_struct.is_library;
50935
+ mono_struct.trait_args = (generic_struct.trait_args ?? []).map((args) => args?.map((t) => substitute_type(t, substitution)));
50764
50936
  status.structs.push(mono_struct);
50765
50937
  status.types.push(mono_name);
50766
50938
  const cloned_methods = [];
@@ -51215,14 +51387,7 @@ function resolve_free_calls_in_node(node, status) {
51215
51387
  const any_node = node;
51216
51388
  if (node.node_type === "func_call" && !any_node.resolved_function) {
51217
51389
  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
- }
51390
+ if (resolve_declared_struct(name, status)?.functions.some((f) => f.name === "#spawn")) rederive_magic_ctor_annotations(node, status, name);
51226
51391
  const func = find_free_function(status, name);
51227
51392
  if (func) set_resolved_function(node, func);
51228
51393
  }
@@ -51281,18 +51446,6 @@ function derive_annotations_for_access_func(access_node, fc, status) {
51281
51446
  rederive_nursery_spawn_annotations(fc, status);
51282
51447
  return;
51283
51448
  }
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
51449
  const struct = status.structs.find((s) => s.name === receiver_type);
51297
51450
  if (!struct) return;
51298
51451
  const func = struct.functions.find((f) => f.name === fc.name);
@@ -51377,7 +51530,7 @@ function check_magic_ctor(node, status, name) {
51377
51530
  add_error(status, `Spawned call '${call.name}' did not resolve`, node.start);
51378
51531
  return false;
51379
51532
  }
51380
- validate_spawn_args_sendable(call, status, (status.nursery_depth ?? 0) > 0);
51533
+ validate_spawn_args_sendable(call, status);
51381
51534
  stamp_spawn_ctor(node, name);
51382
51535
  const return_type = call.type;
51383
51536
  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 +51555,8 @@ function stamp_spawn_ctor(node, name) {
51402
51555
  * nothing) — carries T on the type args, which the `.start()` /
51403
51556
  * nursery paths read. Also materializes Task<T>: the launch seam a user
51404
51557
  * primitive drives is Task's runtime (Task.pool_submit / future_* —
51405
- * ASYNC.md, "User-defined async primitives"), and statics on the bare
51558
+ * docs/ASYNC.md, "Extension: a class with its own `#spawn`"), and
51559
+ * statics on the bare
51406
51560
  * generic only link once a mono instantiation exists (the same
51407
51561
  * materialization a `.start()` launch performs). */
51408
51562
  function stamp_spawn_type_args(node, status, name, result_type_arg) {
@@ -51554,61 +51708,6 @@ function rederive_nursery_spawn_annotations(fc, status) {
51554
51708
  if (task_struct && task_struct.type_params.length > 0) monomorphize(task_struct, [result_type_arg], status);
51555
51709
  }
51556
51710
  /**
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
51711
  * Substitute type-param names through a function-typed parameter's nested
51613
51712
  * signature (`func (T, out T)` on a generic struct method or generic free
51614
51713
  * function). The signature hangs off the ParameterNode itself (`func_params`
@@ -51918,6 +52017,15 @@ function substitute_raw_in_node(node, substitution, structs, deref_params = /* @
51918
52017
  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
52018
  } 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
52019
  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));
52020
+ if (node.node_type === "declare" && !any_node.type?.name && any_node.value?.node_type === "func_call") {
52021
+ const call = any_node.value;
52022
+ const target = structs.find((s) => s.name === call.name);
52023
+ if (target?.type_params.length && call.type_args?.length === target.type_params.length) {
52024
+ const constructed = new Type(call.name);
52025
+ constructed.type_args = call.type_args;
52026
+ any_node.type = constructed;
52027
+ }
52028
+ }
51921
52029
  if (node.node_type === "declare" && any_node.type?.name) {
51922
52030
  any_node.type = substitute_type(any_node.type, substitution);
51923
52031
  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 +53392,11 @@ function check_access_node(node, status) {
53284
53392
  status.allocations.push(decl);
53285
53393
  node.target = new ValueNode(node.target.start, decl.name, target_type);
53286
53394
  }
53395
+ 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)) {
53396
+ const decl = new DeclarationNode(node.target.start, "private", "var", `_recv_${status.var_name_counter.value++}`, target_type, node.target);
53397
+ status.allocations.push(decl);
53398
+ node.target = new ValueNode(node.target.start, decl.name, target_type);
53399
+ }
53287
53400
  if (node.access.node_type === "access_field") {
53288
53401
  const af = node.access;
53289
53402
  if (af.is_destructure) {
@@ -53513,9 +53626,11 @@ function check_access_field_node(target_type, target, node, status) {
53513
53626
  }
53514
53627
  function check_access_function_node(target_type, target, node, status) {
53515
53628
  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");
53629
+ if (target_type.name === "Fiber" && node.name === "start_on") {
53630
+ if (!check_spawn_start_on(target, target_type, node, status)) return false;
53631
+ node.name = "start";
53632
+ node.params = [];
53633
+ }
53519
53634
  if (target_type.is_view) {
53520
53635
  const elem_name = target_type.name === "string" ? "char" : target_type.name;
53521
53636
  if (node.name === "at" && node.params.length === 1) {
@@ -53788,77 +53903,32 @@ function check_nursery_spawn(node, status) {
53788
53903
  return true;
53789
53904
  }
53790
53905
  /**
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);
53906
+ * Validate a `Fiber(fn(args)).start_on(buf)` call's stack buffer (see
53907
+ * ASYNC.md): a fixed-size array of at least 16 KB. The caller rewrites the
53908
+ * access to the ordinary Fiber.start afterwards — this function is
53909
+ * validation only.
53910
+ */
53911
+ function check_spawn_start_on(target, target_type, node, status) {
53912
+ if (node.params.length !== 1) {
53913
+ add_error(status, `start_on expects a stack buffer argument, e.g. Fiber(work(n)).start_on(buf)`, node.start);
53807
53914
  return false;
53808
53915
  }
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
- }
53916
+ const buf_type = type_from_value_node(node.params[0], status);
53917
+ if (!buf_type.is_array) {
53918
+ add_error(status, `start_on expects a fixed-size array stack buffer (T[N]), got ${buf_type.name || "<unknown>"}`, node.params[0].start);
53919
+ return false;
53920
+ }
53921
+ const elem_size = buf_type.name === "string" ? 16 : 8;
53922
+ const len_node = buf_type.length;
53923
+ const len = len_node && len_node.node_type === "value" ? parseInt(len_node.value, 10) : NaN;
53924
+ if (Number.isNaN(len)) {
53925
+ add_error(status, "start_on expects a fixed-size array (T[N]); the length must be known at compile time", node.params[0].start);
53926
+ return false;
53927
+ }
53928
+ if (len * elem_size < 16384) {
53929
+ add_error(status, `Fiber stack too small: ${len * elem_size} bytes, minimum is 16384`, node.params[0].start);
53930
+ return false;
53831
53931
  }
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
53932
  return true;
53863
53933
  }
53864
53934
  //#endregion
@@ -55483,7 +55553,13 @@ function check_return_node(ret, status) {
55483
55553
  } else if (!func.is_arrow_body) {
55484
55554
  add_error(status, `Function returns a value but has no 'out' return type`, ret.start);
55485
55555
  func.return_type = ret.type;
55486
- } else func.return_type = ret.type;
55556
+ } else {
55557
+ func.return_type = ret.type;
55558
+ if (!ret.type?.name) {
55559
+ func.return_type = new Type("void");
55560
+ ret.type = new Type("void");
55561
+ }
55562
+ }
55487
55563
  }
55488
55564
  }
55489
55565
  //#endregion
@@ -57545,7 +57621,7 @@ function parse_function(visibility, status, name_override, is_inline, is_extern,
57545
57621
  if (op_internal[name]) name = op_internal[name];
57546
57622
  const parent_for_type = status.stack.at(-1);
57547
57623
  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);
57624
+ 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
57625
  const func = new FunctionNode(start, visibility, name, return_type);
57550
57626
  if (is_inline) func.is_inline = true;
57551
57627
  if (is_unsafe) {
@@ -57565,7 +57641,7 @@ function parse_function(visibility, status, name_override, is_inline, is_extern,
57565
57641
  if (expect("=", status) && expect("(", status)) {
57566
57642
  const parent = status.stack.at(-1);
57567
57643
  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)) {
57644
+ if ((name === "#destroy" || name === "#spawn") && parent.node_type === "struct" && !func.params.some((p) => p.is_self_param)) {
57569
57645
  const self_param = new ParameterNode(start, "self", new Type(parent.name));
57570
57646
  self_param.is_self_param = true;
57571
57647
  self_param.declaration = "var";
@@ -57597,7 +57673,7 @@ function parse_function(visibility, status, name_override, is_inline, is_extern,
57597
57673
  parse_statement(status);
57598
57674
  expect("}", status);
57599
57675
  status.stack.pop();
57600
- if (name !== "#init" && name !== "#destroy") check_missing_return(func, status);
57676
+ if (name !== "#init" && name !== "#destroy" && name !== "#spawn") check_missing_return(func, status);
57601
57677
  }
57602
57678
  switch (parent.node_type) {
57603
57679
  case "root":
@@ -57782,7 +57858,7 @@ function parse_struct(visibility, status, is_class = false) {
57782
57858
  parse_statement(status);
57783
57859
  expect("}", status);
57784
57860
  status.stack.pop();
57785
- if (!struct.functions.some((f) => f.name === "#init")) {
57861
+ if (!struct.functions.some((f) => f.name === "#init" || f.name === "#spawn")) {
57786
57862
  const func = new FunctionNode(-1, visibility, "#init", new Type(struct.name));
57787
57863
  func.params = struct.fields.filter((f) => f.visibility !== "private" && !f.value).map((f) => {
57788
57864
  const param = new ParameterNode(-1, f.name, f.func_params ? new Type("func") : f.type);
@@ -58071,8 +58147,13 @@ function parse_visibility(visibility, status) {
58071
58147
  consume(status);
58072
58148
  consume(status);
58073
58149
  parse_function(visibility, status, "#destroy");
58150
+ } else if (next2 === "spawn") {
58151
+ consume(status);
58152
+ consume(status);
58153
+ consume(status);
58154
+ parse_function(visibility, status, "#spawn");
58074
58155
  } else {
58075
- add_error(status, `Expected #init or #destroy after pub`, get_index(status));
58156
+ add_error(status, `Expected #init, #destroy or #spawn after pub`, get_index(status));
58076
58157
  consume(status);
58077
58158
  }
58078
58159
  break;
@@ -58199,8 +58280,12 @@ function parse_statement(status) {
58199
58280
  consume(status);
58200
58281
  consume(status);
58201
58282
  parse_destroy(default_visibility(status), status, "#destroy");
58283
+ } else if (next === "spawn") {
58284
+ consume(status);
58285
+ consume(status);
58286
+ parse_destroy(default_visibility(status), status, "#spawn");
58202
58287
  } else {
58203
- add_error(status, `Expected #init or #destroy`, get_index(status));
58288
+ add_error(status, `Expected #init, #destroy or #spawn`, get_index(status));
58204
58289
  consume(status);
58205
58290
  }
58206
58291
  break;
@@ -58442,15 +58527,28 @@ function resolve_linked_types(source, library, file_path) {
58442
58527
  }
58443
58528
  const user_declared_funcs = /* @__PURE__ */ new Set();
58444
58529
  for (let i = 0; i < tokens.length - 1; i++) if (tokens[i].value === "func" && /^\w+$/.test(tokens[i + 1].value)) user_declared_funcs.add(tokens[i + 1].value);
58445
- for (const token of tokens) {
58530
+ const import_tokens = /* @__PURE__ */ new Set();
58531
+ for (let i = 0; i < tokens.length; i++) {
58532
+ if (tokens[i].value !== "import") continue;
58533
+ import_tokens.add(i);
58534
+ let j = i + 1;
58535
+ if (j < tokens.length && /^\w+$/.test(tokens[j].value)) {
58536
+ import_tokens.add(j);
58537
+ j += 1;
58538
+ while (j + 1 < tokens.length && tokens[j].value === "::" && /^\w+$/.test(tokens[j + 1].value)) {
58539
+ import_tokens.add(j);
58540
+ import_tokens.add(j + 1);
58541
+ j += 2;
58542
+ }
58543
+ }
58544
+ }
58545
+ for (let i = 0; i < tokens.length; i++) {
58546
+ if (import_tokens.has(i)) continue;
58547
+ const token = tokens[i];
58446
58548
  if (user_defined.has(token.value)) continue;
58447
58549
  if (library.types.has(token.value) && !BASE_TYPES.includes(token.value)) needed.add(token.value);
58448
58550
  else if (library.functions.has(token.value) && !user_declared_funcs.has(token.value)) needed.add(token.value);
58449
58551
  }
58450
- if (tokens.some((t) => t.value === "Thread" || t.value === "Fiber" || t.value === "async")) {
58451
- needed.add("Task");
58452
- needed.add("Sendable");
58453
- }
58454
58552
  if (tokens.some((t) => t.value === "async")) needed.add("Nursery");
58455
58553
  const resolved = resolve_types_with_deps(needed, library, is_library_file ? file_path : void 0);
58456
58554
  if (!resolved) return source;
@@ -59287,7 +59385,8 @@ function run_test_file(entry_path, lib_path, arch, audit = false, audit_runtime)
59287
59385
  audit_obj
59288
59386
  ]);
59289
59387
  }
59290
- const ext = arch === "aarch64" ? ".s" : ".c";
59388
+ const platform = default_platform();
59389
+ const ext = arch === "aarch64" ? ".s" : platform === "macos" || platform === "ios" ? ".m" : ".c";
59291
59390
  const codefile = path.join(buildDir, path.basename(entry_path, ".nm") + ext);
59292
59391
  const outfile = path.join(buildDir, path.basename(entry_path, ".nm"));
59293
59392
  fs.writeFileSync(path.join(buildDir, "main.h"), buildResult.headers ?? "");
@@ -59298,6 +59397,7 @@ function run_test_file(entry_path, lib_path, arch, audit = false, audit_runtime)
59298
59397
  codefile
59299
59398
  ];
59300
59399
  if (audit_obj) link_args.push(audit_obj);
59400
+ if (platform === "macos" || platform === "ios") link_args.push("-framework", "CoreGraphics", "-framework", "Foundation", "-framework", "AppKit", "-lobjc");
59301
59401
  try {
59302
59402
  execFileSync("clang", link_args, {
59303
59403
  encoding: "utf8",