nomen-lang 0.7.0 → 0.7.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.
Files changed (2) hide show
  1. package/dist/index.mjs +869 -54
  2. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -9989,10 +9989,13 @@ function is_compiler_temp_name$2(name) {
9989
9989
  * the generic call builder), since monomorphized tuple inits route through
9990
9990
  * each of them.
9991
9991
  */
9992
- function mark_tuple_literal_move_owners(node, status) {
9992
+ function mark_tuple_literal_move_owners(node, status, on_move) {
9993
9993
  if (!node.type?.name?.startsWith("_Tuple_")) return;
9994
9994
  if (!status.moved) status.moved = /* @__PURE__ */ new Set();
9995
- for (const p of node.params) if (p.node_type === "value" && p.is_moved && typeof p.value === "string") status.moved.add(p.value);
9995
+ for (const p of node.params) if (p.node_type === "value" && p.is_moved && typeof p.value === "string") {
9996
+ status.moved.add(p.value);
9997
+ on_move?.(p.value);
9998
+ }
9996
9999
  }
9997
10000
  //#endregion
9998
10001
  //#region ../src/build_common/string_escapes.ts
@@ -16045,6 +16048,28 @@ function emit_var_store(status, reg, name, size) {
16045
16048
  }
16046
16049
  }
16047
16050
  }
16051
+ /**
16052
+ * Save the fat (x0, x1) return pair at [x29, #offset]. The stp/ldp simm7
16053
+ * scaled range tops out at +504 — split into the 12-bit-scaled str/ldr
16054
+ * halves beyond it.
16055
+ */
16056
+ function emit_return_pair_save(status, offset) {
16057
+ if (offset + 8 > 504) {
16058
+ emit_asm(status, `str x0, [x29, #${offset}]\n`);
16059
+ emit_asm(status, `str x1, [x29, #${offset + 8}]\n`);
16060
+ return;
16061
+ }
16062
+ emit_asm(status, `stp x0, x1, [x29, #${offset}]\n`);
16063
+ }
16064
+ /** Restore the fat (x0, x1) return pair from [x29, #offset]. */
16065
+ function emit_return_pair_restore(status, offset) {
16066
+ if (offset + 8 > 504) {
16067
+ emit_asm(status, `ldr x0, [x29, #${offset}]\n`);
16068
+ emit_asm(status, `ldr x1, [x29, #${offset + 8}]\n`);
16069
+ return;
16070
+ }
16071
+ emit_asm(status, `ldp x0, x1, [x29, #${offset}]\n`);
16072
+ }
16048
16073
  //#endregion
16049
16074
  //#region ../src/build_aarch64/slp_pair.ts
16050
16075
  /**
@@ -16725,6 +16750,61 @@ function try_emit_slp_pair(index, ctx_ast, ctx_stmts, status) {
16725
16750
  return 2;
16726
16751
  }
16727
16752
  //#endregion
16753
+ //#region ../src/build_common/box_clone.ts
16754
+ /**
16755
+ * The load/transfer deep-copy story for `Box<T>` FIELDS on mv struct elements
16756
+ * of mv containers (2026-10-12, the Valen-comparison repro): a
16757
+ * `List<Ship>` whose `Ship` holds `var Box<Engine> engine` used to come back
16758
+ * from `load_T` with the element's box SHALLOW — the copy's scope-exit
16759
+ * destroy freed the slot's shared wrapper (SIGTRAP on the next use). `Box<T>`
16760
+ * joins the deep-copy/transfer set: `load_T` clones the wrapper
16761
+ * (`<Box_T>_box_clone` — malloc + byte copy + fresh copies of the pointee's
16762
+ * owned fields), and `store_T`/`replace_T` transfer it (the donor's field is
16763
+ * zeroed — the slot is the sole owner). The clone helper is emitted lazily
16764
+ * per backend (this module carries only the shared predicates).
16765
+ */
16766
+ /** The clone helper's symbol for a Box mono (`Box_Engine` → `Box_Engine_box_clone`). */
16767
+ function box_clone_fn_name(box_mono_name) {
16768
+ return `${box_mono_name}_box_clone`;
16769
+ }
16770
+ /**
16771
+ * The monomorphized `Box<T>` wrapper when `type` is a box field type, else
16772
+ * undefined. Shape-checked through `is_box_type` (single `__value` field,
16773
+ * compiler-stamped storage class), so a USER struct named `Box_*` is not
16774
+ * mistaken for the wrapper.
16775
+ */
16776
+ function box_field_mono(type, table) {
16777
+ if (!type.name || type.is_ref || type.is_view || type.is_array) return void 0;
16778
+ const mono = resolve_struct_type(type, table);
16779
+ if (!mono || !mono.is_class) return void 0;
16780
+ return is_box_type(mono.name, table.structs) ? mono : void 0;
16781
+ }
16782
+ /** The `__value` field's type (the pointee). The wrapper is shape-checked to
16783
+ * have exactly one field. */
16784
+ function box_pointee_type(box_mono) {
16785
+ return box_mono.fields[0]?.type;
16786
+ }
16787
+ /** Whether `<E>_copy` yields a fully independent value: no case payload is a
16788
+ * class/trait reference (those are shared pointers the copy cannot
16789
+ * duplicate — a cloned blob sharing one would free it twice at teardown). */
16790
+ function enum_copy_is_deep$1(elem, table) {
16791
+ for (const c of elem.cases) for (const p of c.params) if (table.structs.find((s) => s.name === p.type.name)?.is_class || table.traits?.some((t) => t.name === p.type.name)) return false;
16792
+ return true;
16793
+ }
16794
+ /**
16795
+ * Whether the pointee's plain `string` fields are heap-owned by the wrapper —
16796
+ * the same gate the box CONSTRUCTOR uses when normalizing the incoming value
16797
+ * (build_declaration_node's boxing path): an OWNING pointee's strings are
16798
+ * uniformly heap (transferred as-is), and a pointee without a user `#destroy`
16799
+ * that needs the auto destroy had its rodata/borrow fields strdup'd at
16800
+ * boxing. Anything else (a non-owning pointee with a user `#destroy`) may
16801
+ * still hold borrowed bytes the wrapper destroy never frees — the clone must
16802
+ * NOT strdup those (it would leak per clone).
16803
+ */
16804
+ function box_pointee_strings_are_heap(pointee, table) {
16805
+ return owns_strings_uniformly(pointee) || !has_destroy(pointee) && struct_needs_auto_destroy(pointee, table);
16806
+ }
16807
+ //#endregion
16728
16808
  //#region ../src/build_common/temp_anchor_consolidation.ts
16729
16809
  /**
16730
16810
  * Shared OWNERSHIP decision for hoisted call-temporary consolidation.
@@ -16804,6 +16884,20 @@ function emit_borrow_carrier_owned_free(status, elem, decl_name) {
16804
16884
  for (const field of elem.fields) {
16805
16885
  const size = get_type_size(field.type, status);
16806
16886
  if (!field.type.is_ref && !field.type.is_view && !field.type.is_array && !field.type.is_nullable && field.type.name !== "string") {
16887
+ const box_mono = box_field_mono(field.type, status);
16888
+ if (box_mono) {
16889
+ emit_var_address(status, "x0", decl_name);
16890
+ emit_asm(status, `ldr x0, [x0, #${offset}]\n`);
16891
+ const skip = `.Lskip_bcc_${status.label_counter = (status.label_counter ?? 0) + 1}`;
16892
+ emit_asm(status, `cbz x0, ${skip}\n`);
16893
+ emit_asm(status, `str x0, [sp, #-16]!\n`);
16894
+ emit_asm(status, `bl ${box_mono.name}_destroy\n`);
16895
+ emit_asm(status, `ldr x0, [sp], #16\n`);
16896
+ emit_free(status);
16897
+ emit_asm(status, `${skip}:\n`);
16898
+ offset += size;
16899
+ continue;
16900
+ }
16807
16901
  const field_struct = resolve_struct_type(field.type, status);
16808
16902
  if (field_struct && !field_struct.is_class && is_load_deep_copy_container(field_struct.name) && struct_needs_destroy(field_struct, status) && field_struct.functions.find((f) => f.name === "copy")) {
16809
16903
  emit_var_address(status, "x0", decl_name);
@@ -16906,6 +17000,38 @@ function exit_scope_frame(status, old) {
16906
17000
  if (saved_offsets) status.stack_offsets = saved_offsets;
16907
17001
  status.register_allocations = status.register_allocations_frames?.pop();
16908
17002
  }
17003
+ /**
17004
+ * Reclaim a freshly-built OWNED call result sitting in the result registers —
17005
+ * the `while` POST-STATEMENT (update) position, where the value is created and
17006
+ * discarded in the same expression and nothing else can have moved it:
17007
+ *
17008
+ * - `string` (non-view): the fat pair's ptr half is in x0 — free it.
17009
+ * - class instance: the heap pointer is in x0 — `<T>_destroy`, then free
17010
+ * (the pointer parks on the stack across the destroy call).
17011
+ * - owning value struct: the call landed the blob in its `_call_ret_N` sret
17012
+ * temp with the slot ADDRESS in x0 — `<T>_destroy` walks it in place; the
17013
+ * stack temp itself needs no free.
17014
+ *
17015
+ * Mirrors the C backend's statement-expression capture in
17016
+ * build_while_loop_node and the checker's bare-statement `_disc_N` hoist
17017
+ * (which cannot run in the update position — the update must re-evaluate per
17018
+ * iteration). Clears last_result_is_heap: the value was consumed here.
17019
+ */
17020
+ function emit_discarded_owned_result_free(status, type) {
17021
+ const resolved_name = resolve_struct_name(type.name, type.type_args, status);
17022
+ const struct_type = is_struct_type$4(resolved_name, status) || is_struct_type$4(type.name, status);
17023
+ if (type.name === "string") emit_free(status);
17024
+ else if (struct_type?.is_class) {
17025
+ const skip = `.Lskip_upd_${status.label_counter = (status.label_counter ?? 0) + 1}`;
17026
+ emit_asm(status, `cbz x0, ${skip}\n`);
17027
+ emit_asm(status, `str x0, [sp, #-16]!\n`);
17028
+ emit_asm(status, `bl ${resolved_name}_destroy\n`);
17029
+ emit_asm(status, `ldr x0, [sp], #16\n`);
17030
+ emit_free(status);
17031
+ emit_asm(status, `${skip}:\n`);
17032
+ } else if (struct_type) emit_asm(status, `bl ${resolved_name}_destroy\n`);
17033
+ status.last_result_is_heap = false;
17034
+ }
16909
17035
  /** Every declaration frame a `return` must clean: enclosing scopes first,
16910
17036
  * the current (innermost) frame last — matching fall-through cleanup order. */
16911
17037
  function all_scope_frames(status) {
@@ -16940,6 +17066,12 @@ function mark_heap_string(status, name) {
16940
17066
  if (!status.heap_strings) status.heap_strings = /* @__PURE__ */ new Set();
16941
17067
  status.heap_strings.add(name);
16942
17068
  if (status.heap_cleanup_stack?.length) status.heap_cleanup_stack[status.heap_cleanup_stack.length - 1].heap_strings.add(name);
17069
+ if (status.function_return_label && !status.mv_owns_flag?.has(name)) {
17070
+ const flag_offset = allocate_stack_space(status, 8, 8);
17071
+ emit_asm(status, `str xzr, [x29, #${flag_offset}]\n`);
17072
+ if (!status.mv_owns_flag) status.mv_owns_flag = /* @__PURE__ */ new Map();
17073
+ status.mv_owns_flag.set(name, flag_offset);
17074
+ }
16943
17075
  }
16944
17076
  function anchor_heap_pointer(status, var_name, frame_index, is_nullable) {
16945
17077
  const offset = allocate_stack_space(status, 8, 8);
@@ -17254,6 +17386,135 @@ function release_records_for_scope_exit(status, decl, moved) {
17254
17386
  }
17255
17387
  release_heap_string_fields(status, decl.name, decl.type.name, decl.type.is_nullable);
17256
17388
  }
17389
+ /**
17390
+ * Scope-exit / return cleanup for a declaration that the build-time `moved`
17391
+ * set says was moved out. For an owning `mv` value-struct local the move may
17392
+ * be CONDITIONAL (inside a branch/loop): the local still owns its value on the
17393
+ * path that skips the move, so the destroy is guarded on the local's runtime
17394
+ * "moved" flag (0 = owns, 1 = moved out). On the not-moved path the struct's
17395
+ * string fields (the `#init`-strdup'd default included) are freed; on the
17396
+ * moved path the callee already reclaimed them and the guard skips. Without a
17397
+ * flag (params, temporaries, non-owning structs) the records are released as
17398
+ * before.
17399
+ */
17400
+ function emit_moved_local_cleanup(status, decl) {
17401
+ const owns_flag = status.mv_owns_flag?.get(decl.name);
17402
+ if (owns_flag !== void 0 && decl.type.name === "string" && !decl.type.is_array && status.heap_strings?.has(decl.name)) {
17403
+ const skip = `.Lskip_mvowns_${status.label_counter = (status.label_counter ?? 0) + 1}`;
17404
+ emit_asm(status, `ldr x9, [x29, #${owns_flag}]\n`);
17405
+ emit_asm(status, `cbnz x9, ${skip}\n`);
17406
+ emit_var_load(status, "x0", decl.name, 8);
17407
+ emit_free(status);
17408
+ emit_asm(status, `${skip}:\n`);
17409
+ return;
17410
+ }
17411
+ const resolved = resolve_decl_struct(decl, status);
17412
+ if (owns_flag !== void 0 && resolved?.struct_type.is_owning === true && !resolved.struct_type.name.startsWith("_Tuple_") && !resolved.struct_type.name.startsWith("_Anon_")) {
17413
+ const skip = `.Lskip_mvowns_${status.label_counter = (status.label_counter ?? 0) + 1}`;
17414
+ emit_asm(status, `ldr x9, [x29, #${owns_flag}]\n`);
17415
+ emit_asm(status, `cbnz x9, ${skip}\n`);
17416
+ const had = !!status.moved?.has(decl.name);
17417
+ status.moved?.delete(decl.name);
17418
+ emit_destroy_for_decl(status, decl.name, resolved.type_name, void 0, resolved.type_args, decl.type.is_nullable);
17419
+ if (had) status.moved?.add(decl.name);
17420
+ emit_asm(status, `${skip}:\n`);
17421
+ return;
17422
+ }
17423
+ release_heap_string_fields(status, decl.name, decl.type.name, decl.type.is_nullable);
17424
+ }
17425
+ /**
17426
+ * Emit the reclaim for a heap-registered ARRAY declaration: an owned
17427
+ * fat-string array (`heap_string_arrays` — heap or inline storage), a
17428
+ * class-element array (`heap_class_arrays`), or a heap `Array<T>` buffer
17429
+ * (`heap_array_vars`, with `heap_owned_string_arrays` marking owned
17430
+ * fat-string elements freed slot-by-slot before the buffer). Returns true
17431
+ * when the name was registered and the reclaim was emitted.
17432
+ *
17433
+ * This is the RETURN-path variant of emit_destroy_for_scope's arms: a
17434
+ * `return` runs its own declaration cleanup and then jumps straight to the
17435
+ * function's return label, so the scope-exit walk's copy of the reclaim
17436
+ * never executes on that path — a hoisted `Array<T>` literal temp (or a
17437
+ * call-result buffer) leaked on every return that exits its scope. The
17438
+ * labels are counter-suffixed: the fall-through scope-exit walk (unreachable
17439
+ * after a `return`, but still emitted) may carry the same decl's reclaim,
17440
+ * and duplicating `.Lhosa_${name}` broke the assembler.
17441
+ */
17442
+ function emit_heap_array_decl_destroy(status, decl_name) {
17443
+ const label_id = status.label_counter = (status.label_counter ?? 0) + 1;
17444
+ if (status.heap_string_arrays?.has(decl_name)) {
17445
+ const registered = status.heap_string_arrays.get(decl_name);
17446
+ const len = Math.abs(registered);
17447
+ if (registered > 0) {
17448
+ for (let j = 0; j < len; j++) {
17449
+ emit_var_load(status, "x0", decl_name, 8);
17450
+ emit_asm(status, `ldr x0, [x0, #${8 + j * 16}]\n`);
17451
+ const skip = `.Lskip_hsa_${decl_name}_${label_id}_${j}`;
17452
+ emit_asm(status, `cbz x0, ${skip}\n`);
17453
+ emit_free(status);
17454
+ emit_asm(status, `${skip}:\n`);
17455
+ }
17456
+ emit_var_load(status, "x0", decl_name, 8);
17457
+ emit_free(status);
17458
+ } else for (let j = 0; j < len; j++) {
17459
+ emit_var_address(status, "x0", decl_name);
17460
+ emit_asm(status, `ldr x0, [x0, #${j * 16}]\n`);
17461
+ const skip = `.Lskip_hsa_${decl_name}_${label_id}_${j}`;
17462
+ emit_asm(status, `cbz x0, ${skip}\n`);
17463
+ emit_free(status);
17464
+ emit_asm(status, `${skip}:\n`);
17465
+ }
17466
+ return true;
17467
+ }
17468
+ if (status.heap_class_arrays?.has(decl_name)) {
17469
+ emit_asm(status, `str x19, [sp, #-16]!\n`);
17470
+ emit_asm(status, `str x20, [sp, #-16]!\n`);
17471
+ emit_var_load(status, "x0", decl_name, 8);
17472
+ emit_asm(status, `mov x19, x0\n`);
17473
+ emit_asm(status, `ldr x20, [x19]\n`);
17474
+ emit_asm(status, `add x19, x19, #8\n`);
17475
+ const skip = `.Lskip_cls_${decl_name}_${label_id}`;
17476
+ const label = `.Lcls_${decl_name}_${label_id}`;
17477
+ emit_asm(status, `${label}:\n`);
17478
+ emit_asm(status, `cbz x20, ${skip}\n`);
17479
+ emit_asm(status, `ldr x0, [x19]\n`);
17480
+ emit_free(status);
17481
+ emit_asm(status, `add x19, x19, #8\n`);
17482
+ emit_asm(status, `sub x20, x20, #1\n`);
17483
+ emit_asm(status, `cbnz x20, ${label}\n`);
17484
+ emit_asm(status, `${skip}:\n`);
17485
+ emit_var_load(status, "x0", decl_name, 8);
17486
+ emit_free(status);
17487
+ emit_asm(status, `ldr x20, [sp], #16\n`);
17488
+ emit_asm(status, `ldr x19, [sp], #16\n`);
17489
+ return true;
17490
+ }
17491
+ if (status.heap_array_vars?.has(decl_name)) {
17492
+ emit_asm(status, `str x19, [sp, #-16]!\n`);
17493
+ emit_asm(status, `str x20, [sp, #-16]!\n`);
17494
+ if (status.heap_owned_string_arrays?.has(decl_name)) {
17495
+ emit_var_load(status, "x0", decl_name, 8);
17496
+ emit_asm(status, `mov x19, x0\n`);
17497
+ emit_asm(status, `ldr x20, [x19]\n`);
17498
+ emit_asm(status, `add x19, x19, #8\n`);
17499
+ const loop = `.Lhosa_${decl_name}_${label_id}`;
17500
+ const done = `.Lhosa_done_${decl_name}_${label_id}`;
17501
+ emit_asm(status, `${loop}:\n`);
17502
+ emit_asm(status, `cbz x20, ${done}\n`);
17503
+ emit_asm(status, `ldr x0, [x19]\n`);
17504
+ emit_free(status);
17505
+ emit_asm(status, `add x19, x19, #16\n`);
17506
+ emit_asm(status, `sub x20, x20, #1\n`);
17507
+ emit_asm(status, `b ${loop}\n`);
17508
+ emit_asm(status, `${done}:\n`);
17509
+ }
17510
+ emit_var_load(status, "x0", decl_name, 8);
17511
+ emit_free(status);
17512
+ emit_asm(status, `ldr x20, [sp], #16\n`);
17513
+ emit_asm(status, `ldr x19, [sp], #16\n`);
17514
+ return true;
17515
+ }
17516
+ return false;
17517
+ }
17257
17518
  function emit_destroy_for_decl(status, decl_name, decl_type_name, addr_offset, type_args, is_nullable) {
17258
17519
  const moved = status.moved ?? /* @__PURE__ */ new Set();
17259
17520
  release_records_for_scope_exit(status, {
@@ -17265,6 +17526,7 @@ function emit_destroy_for_decl(status, decl_name, decl_type_name, addr_offset, t
17265
17526
  }
17266
17527
  }, moved);
17267
17528
  if (moved.has(decl_name)) return;
17529
+ if (addr_offset === void 0 && emit_heap_array_decl_destroy(status, decl_name)) return;
17268
17530
  if (decl_type_name === "func" && status.stack_offsets?.has(decl_name)) {
17269
17531
  const skip = `.Lclosure_free_done_${status.label_counter = (status.label_counter ?? 0) + 1}`;
17270
17532
  const no_destroy = `.Lclosure_no_destroy_${status.label_counter = (status.label_counter ?? 0) + 1}`;
@@ -17568,7 +17830,10 @@ function emit_destroy_for_scope(status, declarations_before) {
17568
17830
  for (let i = declarations_before; i < status.scoped_declarations.length; i++) {
17569
17831
  const decl = status.scoped_declarations[i];
17570
17832
  release_records_for_scope_exit(status, decl, moved);
17571
- if (moved.has(decl.name)) continue;
17833
+ if (moved.has(decl.name)) {
17834
+ emit_moved_local_cleanup(status, decl);
17835
+ continue;
17836
+ }
17572
17837
  const scope_enum = status.enums.find((e) => e.name === decl.type.name && e.has_associated_data);
17573
17838
  if (scope_enum && !decl.type.is_array) {
17574
17839
  emit_enum_payload_frees(status, scope_enum.name, decl.name);
@@ -17674,7 +17939,10 @@ function emit_destroy_for_scope(status, declarations_before) {
17674
17939
  for (let i = declarations_before; i < status.scoped_declarations.length; i++) {
17675
17940
  const decl = status.scoped_declarations[i];
17676
17941
  release_records_for_scope_exit(status, decl, moved);
17677
- if (moved.has(decl.name)) continue;
17942
+ if (moved.has(decl.name)) {
17943
+ emit_moved_local_cleanup(status, decl);
17944
+ continue;
17945
+ }
17678
17946
  const scope_enum = status.enums.find((e) => e.name === decl.type.name && e.has_associated_data);
17679
17947
  if (scope_enum && !decl.type.is_array) {
17680
17948
  emit_enum_payload_frees(status, scope_enum.name, decl.name);
@@ -17799,11 +18067,31 @@ function emit_cleanup_to_loop_depth(status) {
17799
18067
  }
17800
18068
  const moved = status.moved ?? /* @__PURE__ */ new Set();
17801
18069
  for (const entry of scope.struct_decls) {
17802
- if (moved.has(entry.name)) continue;
18070
+ if (moved.has(entry.name)) {
18071
+ if (status.mv_owns_flag?.get(entry.name) !== void 0) emit_moved_local_cleanup(status, {
18072
+ name: entry.name,
18073
+ type: {
18074
+ name: entry.type_name,
18075
+ type_args: entry.type_args,
18076
+ is_nullable: entry.is_nullable
18077
+ }
18078
+ });
18079
+ continue;
18080
+ }
17803
18081
  emit_destroy_for_decl(status, entry.name, entry.type_name, void 0, entry.type_args, entry.is_nullable);
17804
18082
  }
17805
18083
  for (const name of scope.heap_strings) {
17806
- if (moved.has(name)) continue;
18084
+ if (moved.has(name)) {
18085
+ const owns_flag = status.mv_owns_flag?.get(name);
18086
+ if (owns_flag === void 0) continue;
18087
+ const skip = `.Lskip_mvowns_${status.label_counter = (status.label_counter ?? 0) + 1}`;
18088
+ emit_asm(status, `ldr x9, [x29, #${owns_flag}]\n`);
18089
+ emit_asm(status, `cbnz x9, ${skip}\n`);
18090
+ emit_var_load(status, "x0", name, 8);
18091
+ emit_free(status);
18092
+ emit_asm(status, `${skip}:\n`);
18093
+ continue;
18094
+ }
17807
18095
  if (status.heap_string_arrays?.has(name)) {
17808
18096
  const registered = status.heap_string_arrays.get(name);
17809
18097
  const len = Math.abs(registered);
@@ -17838,6 +18126,22 @@ function consolidate_temp_anchors$1(status, call_node, result_type_name) {
17838
18126
  if (find_anchor_slot(status, pname) === void 0) continue;
17839
18127
  if (!status.moved) status.moved = /* @__PURE__ */ new Set();
17840
18128
  status.moved.add(pname);
18129
+ mark_local_moved_flag(status, pname);
18130
+ }
18131
+ }
18132
+ /**
18133
+ * Set an owning `mv` value-struct local's runtime moved flag (1 = moved out)
18134
+ * if it has one. Called from every move-marking path (call args, returns,
18135
+ * closure captures, tuple move owners) so the flag-guarded scope-exit destroy
18136
+ * skips the moved path and still reclaims the not-moved path.
18137
+ */
18138
+ function mark_local_moved_flag(status, name) {
18139
+ const flag = status.mv_owns_flag?.get(name);
18140
+ if (flag !== void 0) {
18141
+ emit_asm(status, `str x9, [sp, #-16]!\n`);
18142
+ emit_asm(status, `mov x9, #1\n`);
18143
+ emit_asm(status, `str x9, [x29, #${flag}]\n`);
18144
+ emit_asm(status, `ldr x9, [sp], #16\n`);
17841
18145
  }
17842
18146
  }
17843
18147
  function mark_moved_if_struct(value, status, opts) {
@@ -17856,10 +18160,12 @@ function mark_moved_if_struct(value, status, opts) {
17856
18160
  if (is_struct || is_enum_with_data || is_trait) {
17857
18161
  if (!status.moved) status.moved = /* @__PURE__ */ new Set();
17858
18162
  status.moved.add(var_name);
18163
+ mark_local_moved_flag(status, var_name);
17859
18164
  }
17860
18165
  if (opts?.for_return && status.heap_strings?.has(var_name)) {
17861
18166
  if (!status.moved) status.moved = /* @__PURE__ */ new Set();
17862
18167
  status.moved.add(var_name);
18168
+ mark_local_moved_flag(status, var_name);
17863
18169
  }
17864
18170
  }
17865
18171
  /**
@@ -20428,6 +20734,135 @@ function value_number_loops(body, ast_list, status, mutate_ast) {
20428
20734
  };
20429
20735
  }
20430
20736
  //#endregion
20737
+ //#region ../src/build_aarch64/utils/box_clone.ts
20738
+ /**
20739
+ * Emit the `<Box_T>_box_clone` helper for a `Box<T>` mono — the aarch64 twin
20740
+ * of the C backend's ensure_box_clone (see build_c/utils/box_clone.ts for the
20741
+ * rationale). x0 = src wrapper (NULL-safe), returns the clone in x0: malloc +
20742
+ * byte copy + fresh copies of every pointee field the wrapper's destroy would
20743
+ * reclaim (plain strings, deep-copyable containers, nested boxes,
20744
+ * payload-copyable enums). Class/trait-referencing state stays SHARED — the
20745
+ * pre-existing bounded gap recorded in FOLLOWUP.md.
20746
+ *
20747
+ * The DEFINITION buffers into `status.box_clone_definitions` (flushed at file
20748
+ * scope by build.ts after the statement walk — the call sites are
20749
+ * mid-function-body: the specialized load_T emitters), deduped per TU via
20750
+ * `status.emitted_box_clones`. Returns the helper's symbol.
20751
+ */
20752
+ function ensure_box_clone_aarch64(box_mono, status) {
20753
+ if (!status.emitted_box_clones) status.emitted_box_clones = /* @__PURE__ */ new Set();
20754
+ if (status.emitted_box_clones.has(box_mono.name)) return box_clone_fn_name(box_mono.name);
20755
+ status.emitted_box_clones.add(box_mono.name);
20756
+ const fn = box_clone_fn_name(box_mono.name);
20757
+ const T_SIZE = get_struct_size(box_mono.name, status);
20758
+ const scratch = { ...status };
20759
+ scratch.asm_code_state = void 0;
20760
+ scratch.code = "";
20761
+ scratch.box_clone_definitions = void 0;
20762
+ scratch.label_counter = status.label_counter ?? 0;
20763
+ const null_label = `.Lbox_clone_null_${scratch.label_counter = (scratch.label_counter ?? 0) + 1}`;
20764
+ emit_asm(scratch, `.p2align 2\n`);
20765
+ emit_asm(scratch, `${fn}:\n`);
20766
+ if (status.emit_mode === "system" && status.platform !== "windows") {
20767
+ emit_asm(scratch, `.globl _${fn}\n`);
20768
+ emit_asm(scratch, `_${fn} = ${fn}\n`);
20769
+ }
20770
+ emit_asm(scratch, `cbz x0, ${null_label}\n`);
20771
+ emit_asm(scratch, `stp x29, x30, [sp, #-16]!\n`);
20772
+ emit_asm(scratch, `stp x20, x21, [sp, #-16]!\n`);
20773
+ emit_asm(scratch, `mov x20, x0\n`);
20774
+ emit_asm(scratch, `mov x0, #${T_SIZE}\n`);
20775
+ emit_malloc(scratch);
20776
+ emit_asm(scratch, `mov x21, x0\n`);
20777
+ emit_asm(scratch, `mov x0, x21\n`);
20778
+ emit_asm(scratch, `mov x1, x20\n`);
20779
+ emit_asm(scratch, `mov x2, #${T_SIZE}\n`);
20780
+ emit_asm(scratch, `bl _memcpy\n`);
20781
+ const pointee_type = box_pointee_type(box_mono);
20782
+ if (pointee_type) emit_pointee_owned_copies_asm(scratch, pointee_type, "x21", get_field_offset(box_mono.name, "__value", status), /* @__PURE__ */ new Set());
20783
+ emit_asm(scratch, `mov x0, x21\n`);
20784
+ emit_asm(scratch, `ldp x20, x21, [sp], #16\n`);
20785
+ emit_asm(scratch, `ldp x29, x30, [sp], #16\n`);
20786
+ emit_asm(scratch, `ret\n`);
20787
+ emit_asm(scratch, `${null_label}:\n`);
20788
+ emit_asm(scratch, `ret\n`);
20789
+ status.label_counter = scratch.label_counter;
20790
+ status.box_clone_definitions = (status.box_clone_definitions ?? "") + (scratch.box_clone_definitions ?? "") + scratch.code;
20791
+ return fn;
20792
+ }
20793
+ /**
20794
+ * Give the wrapper clone at `[base + acc]` fresh copies of every field its
20795
+ * destroy would free (x21 must stay valid: every arm clobbers only
20796
+ * caller-saved registers). Recurses through nested value-struct fields; a
20797
+ * nested Box field clones through its own helper.
20798
+ */
20799
+ function emit_pointee_owned_copies_asm(scratch, type, base, acc, visiting) {
20800
+ if (!type.name || type.is_ref || type.is_view || type.is_array || type.is_nullable) return;
20801
+ if (type.name === "string") {
20802
+ emit_asm(scratch, `ldr x0, [${base}, #${acc}]\n`);
20803
+ const skip = `.Lbcs_${scratch.label_counter = (scratch.label_counter ?? 0) + 1}`;
20804
+ emit_asm(scratch, `cbz x0, ${skip}\n`);
20805
+ emit_strdup(scratch);
20806
+ emit_asm(scratch, `str x0, [${base}, #${acc}]\n`);
20807
+ emit_asm(scratch, `${skip}:\n`);
20808
+ return;
20809
+ }
20810
+ const field_enum = scratch.enums.find((e) => e.name === type.name && e.has_associated_data);
20811
+ if (field_enum) {
20812
+ if (enum_copy_is_deep$1(field_enum, scratch)) emit_enum_payload_strdups_at(scratch, field_enum.name, base, acc);
20813
+ return;
20814
+ }
20815
+ const pointee = resolve_struct_type(type, scratch);
20816
+ if (!pointee || pointee.is_class) return;
20817
+ if (visiting.has(pointee.name)) return;
20818
+ visiting.add(pointee.name);
20819
+ const strings_are_heap = box_pointee_strings_are_heap(pointee, scratch);
20820
+ for (const field of clone_fields$1(pointee, scratch)) {
20821
+ const offset = acc + get_field_offset_of_fields(clone_fields$1(pointee, scratch), field.name, scratch);
20822
+ if (field.type.is_ref || field.type.is_view || field.type.is_array || field.type.is_nullable) continue;
20823
+ const nested_box = box_field_mono(field.type, scratch);
20824
+ if (nested_box) {
20825
+ const nested_fn = ensure_box_clone_aarch64(nested_box, scratch);
20826
+ emit_asm(scratch, `ldr x0, [${base}, #${offset}]\n`);
20827
+ const skip = `.Lbcc_${scratch.label_counter = (scratch.label_counter ?? 0) + 1}`;
20828
+ emit_asm(scratch, `cbz x0, ${skip}\n`);
20829
+ emit_asm(scratch, `bl ${nested_fn}\n`);
20830
+ emit_asm(scratch, `str x0, [${base}, #${offset}]\n`);
20831
+ emit_asm(scratch, `${skip}:\n`);
20832
+ continue;
20833
+ }
20834
+ if (field.type.name === "string" && strings_are_heap) {
20835
+ emit_asm(scratch, `ldr x0, [${base}, #${offset}]\n`);
20836
+ const skip = `.Lbcs_${scratch.label_counter = (scratch.label_counter ?? 0) + 1}`;
20837
+ emit_asm(scratch, `cbz x0, ${skip}\n`);
20838
+ emit_strdup(scratch);
20839
+ emit_asm(scratch, `str x0, [${base}, #${offset}]\n`);
20840
+ emit_asm(scratch, `${skip}:\n`);
20841
+ continue;
20842
+ }
20843
+ const field_struct = resolve_struct_type(field.type, scratch);
20844
+ if (field_struct && !field_struct.is_class && is_load_deep_copy_container(field_struct.name) && struct_needs_destroy(field_struct, scratch) && field_struct.functions.find((f) => f.name === "copy")) {
20845
+ emit_asm(scratch, `add x8, ${base}, #${offset}\n`);
20846
+ emit_asm(scratch, `add x0, ${base}, #${offset}\n`);
20847
+ emit_asm(scratch, `bl ${field_struct.name}_copy\n`);
20848
+ continue;
20849
+ }
20850
+ const nested_enum = scratch.enums.find((e) => e.name === field.type.name && e.has_associated_data);
20851
+ if (nested_enum && enum_copy_is_deep$1(nested_enum, scratch)) {
20852
+ emit_enum_payload_strdups_at(scratch, nested_enum.name, base, offset);
20853
+ continue;
20854
+ }
20855
+ emit_pointee_owned_copies_asm(scratch, field.type, base, offset, visiting);
20856
+ }
20857
+ }
20858
+ /** The fields the pointee's destroy reclaims: an OWNING struct's
20859
+ * trait-declared fields are uniformly heap (the ctor strdup'd their
20860
+ * defaults) and join the walk, mirroring emit_field_destroys'
20861
+ * layout_fields merge. */
20862
+ function clone_fields$1(pointee, status) {
20863
+ return pointee.is_class || owns_strings_uniformly(pointee) ? layout_fields(pointee, status) : pointee.fields;
20864
+ }
20865
+ //#endregion
20431
20866
  //#region ../src/build_aarch64/utils/owning_buffer_specialize.ts
20432
20867
  /**
20433
20868
  * Detect whether a monomorphized struct is a `Buffer_<T>` whose element type
@@ -20635,6 +21070,14 @@ function collect_transfer_fields(elem, status, base_offset = 0) {
20635
21070
  let offset = 8;
20636
21071
  for (const field of elem.fields) {
20637
21072
  const size = get_type_size(field.type, status);
21073
+ if (box_field_mono(field.type, status)) {
21074
+ result.push({
21075
+ offset: base_offset + offset,
21076
+ size
21077
+ });
21078
+ offset += size;
21079
+ continue;
21080
+ }
20638
21081
  if (!field.type.is_ref && !field.type.is_view && !field.type.is_array && !field.type.is_nullable) {
20639
21082
  const field_struct = resolve_struct_type(field.type, status);
20640
21083
  if (field_struct && !field_struct.is_class) {
@@ -20680,6 +21123,16 @@ function emit_load_deep_copy_fields$1(elem, status, sret, slot) {
20680
21123
  continue;
20681
21124
  }
20682
21125
  if (!field.type.is_ref && !field.type.is_view && !field.type.is_array && !field.type.is_nullable && field.type.name !== "string") {
21126
+ const box_mono = box_field_mono(field.type, status);
21127
+ if (box_mono) {
21128
+ const clone_fn = ensure_box_clone_aarch64(box_mono, status);
21129
+ emit_asm(status, `add x0, ${sret}, #${offset}\n`);
21130
+ emit_asm(status, `ldr x0, [x0]\n`);
21131
+ emit_asm(status, `bl ${clone_fn}\n`);
21132
+ emit_asm(status, `str x0, [${sret}, #${offset}]\n`);
21133
+ offset += size;
21134
+ continue;
21135
+ }
20683
21136
  const field_struct = resolve_struct_type(field.type, status);
20684
21137
  if (field_struct && !field_struct.is_class && is_load_deep_copy_container(field_struct.name) && struct_needs_destroy(field_struct, status) && field_struct.functions.find((f) => f.name === "copy")) {
20685
21138
  emit_asm(status, `add x8, ${sret}, #${offset}\n`);
@@ -22886,6 +23339,8 @@ function build_function_node$1(node, status) {
22886
23339
  status.trait_class_frames = void 0;
22887
23340
  const old_moved = status.moved;
22888
23341
  status.moved = void 0;
23342
+ const old_mv_owns_flag = status.mv_owns_flag;
23343
+ status.mv_owns_flag = void 0;
22889
23344
  status.access_pins = void 0;
22890
23345
  status.forwarded_param_inits = void 0;
22891
23346
  const param_regs = [
@@ -23453,7 +23908,7 @@ function build_function_node$1(node, status) {
23453
23908
  let return_save;
23454
23909
  if (need_guard || need_save) {
23455
23910
  return_save = allocate_stack_space(status, returns_fat_pair ? 16 : 8);
23456
- if (returns_fat_pair) emit_asm(status, `stp x0, x1, [x29, #${return_save}]\n`);
23911
+ if (returns_fat_pair) emit_return_pair_save(status, return_save);
23457
23912
  else emit_asm(status, `str x0, [x29, #${return_save}]\n`);
23458
23913
  }
23459
23914
  for (const [name, info] of moved_param_save_slots) {
@@ -23481,7 +23936,7 @@ function build_function_node$1(node, status) {
23481
23936
  if (need_guard) emit_asm(status, `${keep_prefix}_${name}:\n`);
23482
23937
  }
23483
23938
  if (need_guard || need_save) {
23484
- if (returns_fat_pair) emit_asm(status, `ldp x0, x1, [x29, #${return_save}]\n`);
23939
+ if (returns_fat_pair) emit_return_pair_restore(status, return_save);
23485
23940
  else emit_asm(status, `ldr x0, [x29, #${return_save}]\n`);
23486
23941
  }
23487
23942
  }
@@ -23520,6 +23975,7 @@ function build_function_node$1(node, status) {
23520
23975
  }
23521
23976
  status.scoped_declarations = old_scoped_declarations;
23522
23977
  status.moved = old_moved;
23978
+ status.mv_owns_flag = old_mv_owns_flag;
23523
23979
  status.heap_string_fields = old_heap_string_fields;
23524
23980
  status.heap_strings = old_heap_strings;
23525
23981
  status.heap_string_arrays = old_heap_string_arrays;
@@ -23797,6 +24253,8 @@ function build_destroy_function(node, func, status) {
23797
24253
  status.trait_class_frames = void 0;
23798
24254
  const old_moved = status.moved;
23799
24255
  status.moved = void 0;
24256
+ const old_mv_owns_flag = status.mv_owns_flag;
24257
+ status.mv_owns_flag = void 0;
23800
24258
  const old_heap_string_arrays = status.heap_string_arrays;
23801
24259
  status.heap_string_arrays = void 0;
23802
24260
  const old_heap_class_arrays = status.heap_class_arrays;
@@ -23874,6 +24332,7 @@ function build_destroy_function(node, func, status) {
23874
24332
  status.heap_strings = old_heap_strings;
23875
24333
  status.trait_class_frames = old_trait_class_frames;
23876
24334
  status.moved = old_moved;
24335
+ status.mv_owns_flag = old_mv_owns_flag;
23877
24336
  status.heap_string_arrays = old_heap_string_arrays;
23878
24337
  status.heap_class_arrays = old_heap_class_arrays;
23879
24338
  status.heap_array_vars = old_heap_array_vars;
@@ -24230,6 +24689,8 @@ function build_custom_init_function(node, func, status) {
24230
24689
  status.trait_class_frames = void 0;
24231
24690
  const old_moved = status.moved;
24232
24691
  status.moved = void 0;
24692
+ const old_mv_owns_flag = status.mv_owns_flag;
24693
+ status.mv_owns_flag = void 0;
24233
24694
  const old_heap_string_arrays = status.heap_string_arrays;
24234
24695
  status.heap_string_arrays = void 0;
24235
24696
  const old_heap_class_arrays = status.heap_class_arrays;
@@ -24547,6 +25008,7 @@ function build_custom_init_function(node, func, status) {
24547
25008
  status.heap_strings = old_heap_strings;
24548
25009
  status.trait_class_frames = old_trait_class_frames;
24549
25010
  status.moved = old_moved;
25011
+ status.mv_owns_flag = old_mv_owns_flag;
24550
25012
  status.heap_string_arrays = old_heap_string_arrays;
24551
25013
  status.heap_class_arrays = old_heap_class_arrays;
24552
25014
  status.heap_array_vars = old_heap_array_vars;
@@ -24578,6 +25040,8 @@ function build_struct_functions$1(node, status) {
24578
25040
  status.trait_class_frames = void 0;
24579
25041
  const old_moved = status.moved;
24580
25042
  status.moved = void 0;
25043
+ const old_mv_owns_flag = status.mv_owns_flag;
25044
+ status.mv_owns_flag = void 0;
24581
25045
  const old_heap_string_arrays = status.heap_string_arrays;
24582
25046
  status.heap_string_arrays = void 0;
24583
25047
  const old_heap_class_arrays = status.heap_class_arrays;
@@ -24941,7 +25405,7 @@ function build_struct_functions$1(node, status) {
24941
25405
  let return_save;
24942
25406
  if (ret_is_class || need_save) {
24943
25407
  return_save = allocate_stack_space(status, returns_fat_pair ? 16 : 8);
24944
- if (returns_fat_pair) emit_asm(status, `stp x0, x1, [x29, #${return_save}]\n`);
25408
+ if (returns_fat_pair) emit_return_pair_save(status, return_save);
24945
25409
  else emit_asm(status, `str x0, [x29, #${return_save}]\n`);
24946
25410
  }
24947
25411
  for (const [name, info] of moved_param_save_slots) {
@@ -24969,7 +25433,7 @@ function build_struct_functions$1(node, status) {
24969
25433
  if (ret_is_class) emit_asm(status, `${keep_prefix}_${name}:\n`);
24970
25434
  }
24971
25435
  if (ret_is_class || need_save) {
24972
- if (returns_fat_pair) emit_asm(status, `ldp x0, x1, [x29, #${return_save}]\n`);
25436
+ if (returns_fat_pair) emit_return_pair_restore(status, return_save);
24973
25437
  else emit_asm(status, `ldr x0, [x29, #${return_save}]\n`);
24974
25438
  }
24975
25439
  }
@@ -24992,6 +25456,7 @@ function build_struct_functions$1(node, status) {
24992
25456
  status.heap_strings = old_heap_strings;
24993
25457
  status.trait_class_frames = old_trait_class_frames;
24994
25458
  status.moved = old_moved;
25459
+ status.mv_owns_flag = old_mv_owns_flag;
24995
25460
  status.function_return_type = old_function_return_type;
24996
25461
  status.heap_string_arrays = old_heap_string_arrays;
24997
25462
  status.heap_class_arrays = old_heap_class_arrays;
@@ -28159,7 +28624,7 @@ function build_function_call_node$1(node, status) {
28159
28624
  }
28160
28625
  }
28161
28626
  const is_struct = status.structs.find((s) => s.name === node.name && !s.is_simple_type);
28162
- mark_tuple_literal_move_owners(node, status);
28627
+ mark_tuple_literal_move_owners(node, status, (name) => mark_local_moved_flag(status, name));
28163
28628
  const func_name = is_struct ? node.mangled_name ?? `${node.name}_init` : emission_label(node.resolved_function ?? node);
28164
28629
  if (is_struct?.is_class) {
28165
28630
  const inner = box_ctor_direct_ctor_arg(node, status.structs);
@@ -28860,7 +29325,7 @@ function build_return_node$1(node, status, nir_value) {
28860
29325
  const finalized = status.moved ?? /* @__PURE__ */ new Set();
28861
29326
  for (const decl of all_scope_frames(status).flat()) {
28862
29327
  if (finalized.has(decl.name)) {
28863
- release_heap_string_fields(status, decl.name, decl.type.name, decl.type.is_nullable);
29328
+ emit_moved_local_cleanup(status, decl);
28864
29329
  continue;
28865
29330
  }
28866
29331
  if (is_field_struct_borrow(decl)) continue;
@@ -28880,7 +29345,7 @@ function build_return_node$1(node, status, nir_value) {
28880
29345
  const finalized = status.moved ?? /* @__PURE__ */ new Set();
28881
29346
  for (const decl of all_scope_frames(status).flat()) {
28882
29347
  if (finalized.has(decl.name)) {
28883
- release_heap_string_fields(status, decl.name, decl.type.name, decl.type.is_nullable);
29348
+ emit_moved_local_cleanup(status, decl);
28884
29349
  continue;
28885
29350
  }
28886
29351
  if (is_field_struct_borrow(decl)) continue;
@@ -29283,7 +29748,7 @@ function build_return_node$1(node, status, nir_value) {
29283
29748
  emit_nursery_joins_on_return_a64(status);
29284
29749
  for (const decl of all_scope_frames(status).flat()) {
29285
29750
  if (finalized.has(decl.name)) {
29286
- release_heap_string_fields(status, decl.name, decl.type.name, decl.type.is_nullable);
29751
+ emit_moved_local_cleanup(status, decl);
29287
29752
  continue;
29288
29753
  }
29289
29754
  if (is_field_struct_borrow(decl)) continue;
@@ -29342,6 +29807,42 @@ function build_switch_node$1(node, status, nir) {
29342
29807
  exit_scope_frame(status, old_scoped_declarations);
29343
29808
  }
29344
29809
  //#endregion
29810
+ //#region ../src/build_common/discarded_update_result.ts
29811
+ /**
29812
+ * The owned result type of a `while` POST-STATEMENT (update) expression whose
29813
+ * discarded value must be reclaimed once per iteration.
29814
+ *
29815
+ * This is the update-position twin of the checker's bare-statement `_disc_N`
29816
+ * hoist (`hoist_discarded_class_results`) — but the hoist itself cannot run
29817
+ * here: the update re-evaluates on every iteration, so wrapping it in a
29818
+ * one-shot declaration would run the call before the first condition check
29819
+ * and change loop semantics. Instead, each backend captures the fresh result
29820
+ * into a per-iteration temp and frees it in place.
29821
+ *
29822
+ * Predicate mirrors the hoist's: a free-function call (`func_call`) whose
29823
+ * result type is a class or an owning value struct (`string` included), or a
29824
+ * method call whose callee stamps `owned_return` (`mv out T`). A view / ref /
29825
+ * array result is a borrow — never an owner — and a nullable VALUE struct's
29826
+ * sret-plus-flag shape has no in-place free, so both stay out.
29827
+ */
29828
+ function discarded_owned_result_type(node, table) {
29829
+ let expr = node;
29830
+ while (expr.node_type === "grouped") expr = expr.value;
29831
+ const is_call = expr.node_type === "func_call";
29832
+ const is_method = expr.node_type === "access" && expr.access.node_type === "access_func";
29833
+ if (!is_call && !is_method) return void 0;
29834
+ const access_func = is_method ? expr.access : null;
29835
+ if (access_func && !access_func.owned_return) return void 0;
29836
+ const t = is_call ? expr.type : access_func.type;
29837
+ if (!t?.name || t.is_view || t.is_ref || t.is_array) return void 0;
29838
+ if (t.name === "string") return t;
29839
+ const struct = resolve_struct_type(t, table);
29840
+ if (!struct) return void 0;
29841
+ if (struct.is_class) return t;
29842
+ if (t.is_nullable) return void 0;
29843
+ if (struct_needs_destroy(struct, table)) return t;
29844
+ }
29845
+ //#endregion
29345
29846
  //#region ../src/build_aarch64/build_while_loop_node.ts
29346
29847
  let label_counter = 0;
29347
29848
  function reset_label_counter() {
@@ -29456,7 +29957,12 @@ function build_while_loop_node$1(node, status, nir, vector, unroll) {
29456
29957
  build_block_with_cursor$1(node, nir?.body, status);
29457
29958
  if (node.update) {
29458
29959
  emit_asm(status, `${continue_label}:\n`);
29960
+ const owned_update = discarded_owned_result_type(node.update, status);
29459
29961
  build_node(node.update, status);
29962
+ if (owned_update) {
29963
+ ensure_newline(status);
29964
+ emit_discarded_owned_result_free(status, owned_update);
29965
+ }
29460
29966
  ensure_newline(status);
29461
29967
  }
29462
29968
  emit_asm(status, `b ${start_label}\n`);
@@ -32136,6 +32642,102 @@ function end_code_scratch(status, saved) {
32136
32642
  return scratch;
32137
32643
  }
32138
32644
  //#endregion
32645
+ //#region ../src/build_c/utils/box_clone.ts
32646
+ /**
32647
+ * Emit the `<Box_T>_box_clone` helper for a `Box<T>` mono (STRUCT_PLAN_3
32648
+ * Box-in-container fix): malloc a fresh wrapper, byte-copy the pointee, then
32649
+ * give the clone independent copies of every field the wrapper's destroy
32650
+ * would reclaim (the same field set `build_auto_destroy` walks — plain
32651
+ * strings, deep-copyable containers, nested boxes, payload-copyable enums).
32652
+ * Class/trait-referencing state (a raw class field, an enum with reference
32653
+ * payloads) cannot be duplicated and stays SHARED — the pre-existing bounded
32654
+ * gap, recorded in FOLLOWUP.md.
32655
+ *
32656
+ * The DEFINITION buffers into `status.box_clone_definitions` (flushed at file
32657
+ * scope by build_root_node — C may not nest a function definition inside the
32658
+ * struct-method body being emitted) with the prototype in headers, so
32659
+ * definition order never matters. Deduped per TU via
32660
+ * `status.emitted_box_clones`. Returns the helper's symbol.
32661
+ */
32662
+ function ensure_box_clone(box_mono, status) {
32663
+ if (!status.emitted_box_clones) status.emitted_box_clones = /* @__PURE__ */ new Set();
32664
+ if (status.emitted_box_clones.has(box_mono.name)) return box_clone_fn_name(box_mono.name);
32665
+ status.emitted_box_clones.add(box_mono.name);
32666
+ const fn = box_clone_fn_name(box_mono.name);
32667
+ status.headers += `struct ${box_mono.name} *${fn}(struct ${box_mono.name} *);\n`;
32668
+ const out = [];
32669
+ out.push(`struct ${box_mono.name} *${fn}(struct ${box_mono.name} *src)\n{\n`);
32670
+ out.push(`if (!src) return 0;\n`);
32671
+ out.push(`struct ${box_mono.name} *_dst = (struct ${box_mono.name} *)malloc(sizeof(struct ${box_mono.name}));\n`);
32672
+ out.push(`*_dst = *src;\n`);
32673
+ const pointee_type = box_pointee_type(box_mono);
32674
+ if (pointee_type) emit_pointee_owned_copies("_dst->__value", pointee_type, status, /* @__PURE__ */ new Set(), out);
32675
+ out.push(`return _dst;\n`);
32676
+ out.push(`}\n`);
32677
+ status.box_clone_definitions = (status.box_clone_definitions ?? "") + out.join("");
32678
+ return fn;
32679
+ }
32680
+ /**
32681
+ * Give the wrapper clone at C lvalue `lv` fresh copies of every field its
32682
+ * destroy would free. Recurses through nested value-struct fields; a nested
32683
+ * Box field clones through its own helper (the same emit point, deduped).
32684
+ */
32685
+ function emit_pointee_owned_copies(lv, type, status, visiting, out) {
32686
+ if (!type.name || type.is_ref || type.is_view || type.is_array || type.is_nullable) return;
32687
+ if (type.name === "string") {
32688
+ out.push(`${lv} = ${lv}.ptr ? nomen_str_dup(${lv}) : ${lv};\n`);
32689
+ return;
32690
+ }
32691
+ const field_enum = status.enums.find((e) => e.name === type.name && e.has_associated_data);
32692
+ if (field_enum) {
32693
+ if (enum_copy_is_deep$1(field_enum, status)) out.push(`${lv} = ${field_enum.name}_copy(${lv});\n`);
32694
+ return;
32695
+ }
32696
+ const pointee = resolve_struct_type(type, status);
32697
+ if (!pointee || pointee.is_class) return;
32698
+ if (visiting.has(pointee.name)) return;
32699
+ visiting.add(pointee.name);
32700
+ const strings_are_heap = box_pointee_strings_are_heap(pointee, status);
32701
+ for (const field of clone_fields(pointee, status)) {
32702
+ if (field.type.is_ref || field.type.is_view || field.type.is_array || field.type.is_nullable) continue;
32703
+ const flv = `${lv}.${field.name}`;
32704
+ const nested_box = box_field_mono(field.type, status);
32705
+ if (nested_box) {
32706
+ const nested_fn = ensure_box_clone(nested_box, status);
32707
+ out.push(`${flv} = ${nested_fn}(${flv});\n`);
32708
+ continue;
32709
+ }
32710
+ if (field.type.name === "string" && !field.type.is_array && strings_are_heap) {
32711
+ out.push(`${flv} = ${flv}.ptr ? nomen_str_dup(${flv}) : ${flv};\n`);
32712
+ continue;
32713
+ }
32714
+ const field_struct = resolve_struct_type(field.type, status);
32715
+ if (field_struct && !field_struct.is_class && is_load_deep_copy_container(field_struct.name) && struct_needs_destroy(field_struct, status) && field_struct.functions.find((f) => f.name === "copy")) {
32716
+ out.push(`${flv} = ${field_struct.name}_copy(&${flv});\n`);
32717
+ continue;
32718
+ }
32719
+ const nested_enum = status.enums.find((e) => e.name === field.type.name && e.has_associated_data);
32720
+ if (nested_enum && enum_copy_is_deep$1(nested_enum, status)) {
32721
+ out.push(`${flv} = ${nested_enum.name}_copy(${flv});\n`);
32722
+ continue;
32723
+ }
32724
+ emit_pointee_owned_copies(flv, field.type, status, visiting, out);
32725
+ }
32726
+ }
32727
+ /** The fields the pointee's destroy reclaims: an OWNING struct's
32728
+ * trait-declared fields are uniformly heap (the ctor strdup'd their
32729
+ * defaults) and join the walk, mirroring `class_destroy_fields`. */
32730
+ function clone_fields(pointee, status) {
32731
+ if (!owns_strings_uniformly(pointee) || !pointee.traits.length) return pointee.fields;
32732
+ const extra = [];
32733
+ for (const trait_name of pointee.traits) {
32734
+ const trait = status.traits.find((t) => t.name === trait_name);
32735
+ if (!trait) continue;
32736
+ for (const f of trait.fields) if (!pointee.fields.find((nf) => nf.name === f.name)) extra.push(f);
32737
+ }
32738
+ return extra.length ? [...pointee.fields, ...extra] : pointee.fields;
32739
+ }
32740
+ //#endregion
32139
32741
  //#region ../src/build_c/utils/owning_buffer_specialize.ts
32140
32742
  /**
32141
32743
  * Detect whether a monomorphized struct is a `Buffer_<T>` whose element type
@@ -32214,10 +32816,14 @@ const OWNING_BUFFER_METHODS = /* @__PURE__ */ new Set([
32214
32816
  function emit_transfer_owning_fields(elem, src, status, prefix = "") {
32215
32817
  for (const field of elem.fields) {
32216
32818
  if (field.type.is_ref || field.type.is_view) continue;
32819
+ const src_field = `${prefix}${src}${arrow(src)}${field.name}`;
32820
+ if (box_field_mono(field.type, status)) {
32821
+ status.code += `memset((void*)&${src_field}, 0, sizeof(${src_field}));\n`;
32822
+ continue;
32823
+ }
32217
32824
  if (field.type.is_array || field.type.is_nullable) continue;
32218
32825
  const field_struct = resolve_struct_type(field.type, status);
32219
32826
  if (!field_struct || field_struct.is_class) continue;
32220
- const src_field = `${prefix}${src}${arrow(src)}${field.name}`;
32221
32827
  if (has_destroy(field_struct)) status.code += `memset((void*)&${src_field}, 0, sizeof(${src_field}));\n`;
32222
32828
  else if (struct_needs_destroy(field_struct, status)) emit_transfer_owning_fields(field_struct, src_field, status, "");
32223
32829
  }
@@ -32241,6 +32847,12 @@ function emit_load_deep_copy_fields(elem, dst, status) {
32241
32847
  if (field.type.is_ref || field.type.is_view || field.type.is_array) continue;
32242
32848
  if (field.type.is_nullable) continue;
32243
32849
  if (field.type.name === "string") continue;
32850
+ const box_mono = box_field_mono(field.type, status);
32851
+ if (box_mono) {
32852
+ const clone_fn = ensure_box_clone(box_mono, status);
32853
+ status.code += `${dst}.${field.name} = ${clone_fn}(${dst}.${field.name});\n`;
32854
+ continue;
32855
+ }
32244
32856
  const field_struct = resolve_struct_type(field.type, status);
32245
32857
  if (field_struct && !field_struct.is_class && is_load_deep_copy_container(field_struct.name) && struct_needs_destroy(field_struct, status) && field_struct.functions.find((f) => f.name === "copy")) {
32246
32858
  status.code += `${dst}.${field.name} = ${field_struct.name}_copy(&${dst}.${field.name});\n`;
@@ -32266,6 +32878,11 @@ function emit_load_owned_copies_destroy(status, elem, var_expr) {
32266
32878
  if (field.type.is_ref || field.type.is_view || field.type.is_array) continue;
32267
32879
  if (field.type.is_nullable) continue;
32268
32880
  if (field.type.name === "string") continue;
32881
+ const box_mono = box_field_mono(field.type, status);
32882
+ if (box_mono) {
32883
+ status.code += `if (${var_expr}.${field.name}) { ${box_mono.name}_destroy(${var_expr}.${field.name}); free(${var_expr}.${field.name}); }\n`;
32884
+ continue;
32885
+ }
32269
32886
  const field_struct = resolve_struct_type(field.type, status);
32270
32887
  if (field_struct && !field_struct.is_class && is_load_deep_copy_container(field_struct.name) && struct_needs_destroy(field_struct, status) && field_struct.functions.find((f) => f.name === "copy")) {
32271
32888
  status.code += `${field_struct.name}_destroy(&${var_expr}.${field.name});\n`;
@@ -32723,6 +33340,10 @@ function build_auto_free(status) {
32723
33340
  * (those are scope-exit-only concerns handled by build_auto_free).
32724
33341
  */
32725
33342
  function free_scoped_declarations(status, decls, persist_string_field_records = false) {
33343
+ if (status.pending_mv_owns_sets?.length) {
33344
+ status.code += status.pending_mv_owns_sets.join("\n") + "\n";
33345
+ status.pending_mv_owns_sets.length = 0;
33346
+ }
32726
33347
  let commented = false;
32727
33348
  if (status.heap_string_fields?.size) for (const dec of decls) {
32728
33349
  if (dec.moved_value_struct_param) {
@@ -32824,6 +33445,7 @@ function free_scoped_declarations(status, decls, persist_string_field_records =
32824
33445
  const is_heap_string_assign = !!status.heap_strings?.has(dec.name);
32825
33446
  const dec_struct = status.structs.find((s) => s.name === dec.type.name);
32826
33447
  const is_class_var = !!dec_struct?.is_class;
33448
+ const mv_owns_flag = status.c_mv_owns_flags?.get(dec.name);
32827
33449
  const trait_class_trait = dec.trait_class_trait;
32828
33450
  if (trait_class_trait !== void 0 && !is_destructured_field_access) {
32829
33451
  if (!commented) {
@@ -32840,7 +33462,8 @@ function free_scoped_declarations(status, decls, persist_string_field_records =
32840
33462
  status.code += "\n// Auto-free\n";
32841
33463
  commented = true;
32842
33464
  }
32843
- status.code += `free(${cname}.ptr);\n`;
33465
+ if (mv_owns_flag !== void 0) status.code += `if (!${mv_owns_flag}) { free(${cname}.ptr); }\n`;
33466
+ else status.code += `free(${cname}.ptr);\n`;
32844
33467
  }
32845
33468
  if (!is_destructured_field_access && !dec.type.is_array) {
32846
33469
  const enum_node = status.enums.find((e) => e.name === dec.type.name);
@@ -32866,7 +33489,8 @@ function free_scoped_declarations(status, decls, persist_string_field_records =
32866
33489
  if (cls) {
32867
33490
  const destroy_call = has_destroy_fn ? `${mono_cls_name}_destroy(${cname}); ` : "";
32868
33491
  const alias_flag = status.c_alias_owns_flags?.get(dec.name);
32869
- if (alias_flag !== void 0) status.code += `if (${alias_flag}) { ${destroy_call}free(${cname}); }\n`;
33492
+ if (mv_owns_flag !== void 0) status.code += `if (!${mv_owns_flag}) { ${destroy_call}free(${cname}); }\n`;
33493
+ else if (alias_flag !== void 0) status.code += `if (${alias_flag}) { ${destroy_call}free(${cname}); }\n`;
32870
33494
  else if (dec.type.is_nullable) status.code += `if (${cname}) { ${destroy_call}free(${cname}); }\n`;
32871
33495
  else status.code += `${destroy_call}free(${cname});\n`;
32872
33496
  } else status.code += `free(${cname});\n`;
@@ -32879,7 +33503,11 @@ function free_scoped_declarations(status, decls, persist_string_field_records =
32879
33503
  status.code += "\n// Auto-free\n";
32880
33504
  commented = true;
32881
33505
  }
32882
- emit_struct_destroys(status, struct_type, cname);
33506
+ if (mv_owns_flag !== void 0) {
33507
+ status.code += `if (!${mv_owns_flag}) { `;
33508
+ emit_struct_destroys(status, struct_type, cname);
33509
+ status.code += `}\n`;
33510
+ } else emit_struct_destroys(status, struct_type, cname);
32883
33511
  }
32884
33512
  }
32885
33513
  if (dec.trait_class_trait === void 0 && !!status.traits.find((t) => t.name === dec.type.name) && !is_destructured_field_access && !dec.type.is_array && dec.value) {
@@ -34441,7 +35069,12 @@ function build_assignment_node$1(node, status, nir_rhs, nir_swap) {
34441
35069
  release_recorded_string_fields(status, move_struct_type, lhs_name);
34442
35070
  if (struct_needs_destroy_by_name(move_struct_type.name, status)) emit_struct_destroys(status, move_struct_type, lhs_name);
34443
35071
  }
34444
- splice_decl_from_c_scopes(status, rhs.value);
35072
+ const mv_donor_name = rhs.value;
35073
+ const mv_donor_flag = status.c_mv_owns_flags?.get(mv_donor_name);
35074
+ if (!!mv_donor_flag && move_struct_type?.is_owning === true && !move_struct_type.name.startsWith("_Tuple_") && !move_struct_type.name.startsWith("_Anon_") && !node.swap) {
35075
+ if (!status.pending_mv_owns_sets) status.pending_mv_owns_sets = [];
35076
+ status.pending_mv_owns_sets.push(`${mv_donor_flag} = 1;`);
35077
+ } else splice_decl_from_c_scopes(status, mv_donor_name);
34445
35078
  } else if (rhs_is_base_literal) {
34446
35079
  const struct_type = lhs_mono_struct ?? lhs_struct;
34447
35080
  if (struct_type) {
@@ -35298,6 +35931,19 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
35298
35931
  if (node.type?.name === "string" && !node.type.is_array) {
35299
35932
  if (!status.owned_string_vars) status.owned_string_vars = /* @__PURE__ */ new Set();
35300
35933
  status.owned_string_vars.add(safe_name);
35934
+ if (!node.is_module_init) {
35935
+ const flag = `_mv_owns_${safe_name}`;
35936
+ status.code += `unsigned char ${flag} = 0;\n`;
35937
+ if (!status.c_mv_owns_flags) status.c_mv_owns_flags = /* @__PURE__ */ new Map();
35938
+ status.c_mv_owns_flags.set(node.name, flag);
35939
+ }
35940
+ }
35941
+ const mv_decl_struct = node.type?.name ? status.structs.find((s) => s.name === mono_type_name(node.type) && !s.is_simple_type && !s.is_generic) : void 0;
35942
+ if (mv_decl_struct?.is_owning === true && !mv_decl_struct.name.startsWith("_Tuple_") && !mv_decl_struct.name.startsWith("_Anon_") && !node.type.is_nullable && struct_needs_destroy_by_name(mv_decl_struct.name, status)) {
35943
+ const flag = `_mv_owns_${safe_name}`;
35944
+ status.code += `unsigned char ${flag} = 0;\n`;
35945
+ if (!status.c_mv_owns_flags) status.c_mv_owns_flags = /* @__PURE__ */ new Map();
35946
+ status.c_mv_owns_flags.set(node.name, flag);
35301
35947
  }
35302
35948
  } else {
35303
35949
  if (!status.class_alias_vars) status.class_alias_vars = /* @__PURE__ */ new Set();
@@ -36159,16 +36805,20 @@ function build_return_node(node, status, nir_value) {
36159
36805
  }
36160
36806
  }
36161
36807
  let returned_value_decl;
36808
+ let moved_string_flag;
36162
36809
  let returned_recorded_string_fields;
36163
36810
  if (node.value.node_type === "value") {
36164
36811
  const value = node.value.value;
36165
36812
  returned_value_decl = find_decl_across_scopes(value, status);
36166
- const frames = [status.scoped_declarations, ...status.c_scope_stack ?? []];
36167
- for (const frame of frames) {
36168
- const di = frame.indexOf(returned_value_decl);
36169
- if (di !== -1) {
36170
- frame.splice(di, 1);
36171
- break;
36813
+ moved_string_flag = returned_value_decl && returned_value_decl.type?.name === "string" && !returned_value_decl.type?.is_array && !returned_value_decl.type?.is_view ? status.c_mv_owns_flags?.get(value) : void 0;
36814
+ if (!moved_string_flag) {
36815
+ const frames = [status.scoped_declarations, ...status.c_scope_stack ?? []];
36816
+ for (const frame of frames) {
36817
+ const di = frame.indexOf(returned_value_decl);
36818
+ if (di !== -1) {
36819
+ frame.splice(di, 1);
36820
+ break;
36821
+ }
36172
36822
  }
36173
36823
  }
36174
36824
  if (status.heap_string_fields?.size) {
@@ -36232,6 +36882,7 @@ function build_return_node(node, status, nir_value) {
36232
36882
  status.code += `${old_return_assign} = `;
36233
36883
  emit_return_value(node.value, nir_value, status);
36234
36884
  status.code += `;\n`;
36885
+ if (moved_string_flag) status.code += `${moved_string_flag} = 1;\n`;
36235
36886
  reclaim_all_c_scopes(status);
36236
36887
  } else if ((status.function_return_type?.name ?? node.type?.name) === "void" && !node.type?.is_array) {
36237
36888
  emit_allocations(node.value, status);
@@ -36379,6 +37030,7 @@ function build_return_node(node, status, nir_value) {
36379
37030
  status.code += `_return_val.${field.name} = nomen_str_dup(_return_val.${field.name});\n`;
36380
37031
  }
36381
37032
  }
37033
+ if (moved_string_flag) status.code += `${moved_string_flag} = 1;\n`;
36382
37034
  emit_nursery_joins_on_return_c(status);
36383
37035
  reclaim_all_c_scopes(status);
36384
37036
  status.code += `return _return_val;\n`;
@@ -36545,13 +37197,41 @@ function build_while_loop_node(node, status, nir) {
36545
37197
  }
36546
37198
  return true;
36547
37199
  };
37200
+ const build_update_expr = () => {
37201
+ if (!node.update) return;
37202
+ const owned = discarded_owned_result_type(node.update, status);
37203
+ if (!owned) {
37204
+ build_node$1(node.update, status);
37205
+ return;
37206
+ }
37207
+ const mono = mono_type_name(owned);
37208
+ const struct = status.structs.find((s) => s.name === mono && !s.is_generic);
37209
+ const free_fn = status.audit ? "nomen_free_wrap" : "free";
37210
+ const tmp = `_upd_${status.label_counter = (status.label_counter ?? 0) + 1}`;
37211
+ status.code += `({ `;
37212
+ if (struct?.is_class) {
37213
+ status.code += `struct ${mono} *${tmp} = `;
37214
+ build_node$1(node.update, status);
37215
+ status.code += `; if (${tmp}) { ${mono}_destroy(${tmp}); ${free_fn}(${tmp}); }; `;
37216
+ } else if (mono === "string") {
37217
+ status.code += `nomen_string ${tmp} = `;
37218
+ build_node$1(node.update, status);
37219
+ status.code += `; ${free_fn}(${tmp}.ptr); `;
37220
+ } else if (struct) {
37221
+ status.code += `struct ${mono} ${tmp} = `;
37222
+ build_node$1(node.update, status);
37223
+ status.code += `; `;
37224
+ emit_struct_destroys(status, struct, tmp);
37225
+ }
37226
+ status.code += ` })`;
37227
+ };
36548
37228
  if (node.update) {
36549
37229
  status.code += `for (; `;
36550
37230
  const has_reeval = emit_condition_reeval();
36551
37231
  build_condition(node.condition, status);
36552
37232
  if (has_reeval) status.code += `; })`;
36553
37233
  status.code += `; `;
36554
- build_node$1(node.update, status);
37234
+ build_update_expr();
36555
37235
  status.code += `) {\n`;
36556
37236
  } else {
36557
37237
  status.code += `while (`;
@@ -36766,6 +37446,10 @@ function with_semicolon_tail(status, node) {
36766
37446
  status.code += status.pending_string_releases.join("\n") + "\n";
36767
37447
  status.pending_string_releases.length = 0;
36768
37448
  }
37449
+ if (status.pending_mv_owns_sets?.length) {
37450
+ status.code += status.pending_mv_owns_sets.join("\n") + "\n";
37451
+ status.pending_mv_owns_sets.length = 0;
37452
+ }
36769
37453
  }
36770
37454
  /**
36771
37455
  * NIR-level EXPRESSION emission (phase 4, canonical-IR stage 2+ — the C
@@ -37788,6 +38472,8 @@ function build_function_node(node, status) {
37788
38472
  status.deferred_frees = [];
37789
38473
  const old_alias_owns_flags = status.c_alias_owns_flags;
37790
38474
  status.c_alias_owns_flags = /* @__PURE__ */ new Map();
38475
+ const old_mv_owns_flags = status.c_mv_owns_flags;
38476
+ status.c_mv_owns_flags = /* @__PURE__ */ new Map();
37791
38477
  const old_alias_decl_frames = status.alias_decl_frames;
37792
38478
  status.alias_decl_frames = /* @__PURE__ */ new Map();
37793
38479
  const old_class_alias_vars = status.class_alias_vars;
@@ -37812,6 +38498,8 @@ function build_function_node(node, status) {
37812
38498
  status.stack_array_lengths = void 0;
37813
38499
  const old_moved = status.moved;
37814
38500
  status.moved = /* @__PURE__ */ new Set();
38501
+ const old_pending_mv_owns = status.pending_mv_owns_sets;
38502
+ status.pending_mv_owns_sets = void 0;
37815
38503
  const old_borrow_only = status.c_borrow_only_strings;
37816
38504
  status.c_borrow_only_strings = scan_borrow_only_strings(node);
37817
38505
  const old_force_heap = status.force_heap_strings;
@@ -38028,6 +38716,8 @@ function build_function_node(node, status) {
38028
38716
  status.c_borrow_only_strings = old_borrow_only;
38029
38717
  status.force_heap_strings = old_force_heap;
38030
38718
  status.c_alias_owns_flags = old_alias_owns_flags;
38719
+ status.c_mv_owns_flags = old_mv_owns_flags;
38720
+ status.pending_mv_owns_sets = old_pending_mv_owns;
38031
38721
  status.alias_decl_frames = old_alias_decl_frames;
38032
38722
  status.class_alias_vars = old_class_alias_vars;
38033
38723
  status.class_alias_source_map = old_class_alias_source_map;
@@ -38676,12 +39366,14 @@ function build_function_call_node(node, status) {
38676
39366
  if (tname && status.enums.find((e) => e.name === tname && e.has_associated_data)) continue;
38677
39367
  const decl_struct = decl_hit ? status.structs.find((s) => s.name === tname && !s.is_simple_type) : void 0;
38678
39368
  const is_value_struct = !!decl_struct && !decl_struct.is_class;
38679
- if (decl_hit) decl_hit.frame.splice(decl_hit.index, 1);
39369
+ const prefix = `${vname}.`;
39370
+ const box_ctor_transfer = is_box_type(node.name, status.structs);
39371
+ const callee_move_param = !!node.resolved_function?.params?.filter((p) => !p.is_self_param)[idx]?.is_moved;
39372
+ const transfer = box_ctor_transfer || (node.move_owned_param_indices?.includes(idx) ?? false) || callee_move_param;
39373
+ const owns_flag = status.c_mv_owns_flags?.get(vname);
39374
+ const use_flag = !!owns_flag && is_value_struct && transfer && decl_struct?.is_owning === true && !decl_struct.name.startsWith("_Tuple_") && !decl_struct.name.startsWith("_Anon_");
39375
+ if (decl_hit && !use_flag) decl_hit.frame.splice(decl_hit.index, 1);
38680
39376
  if (is_value_struct) {
38681
- const prefix = `${vname}.`;
38682
- const box_ctor_transfer = is_box_type(node.name, status.structs);
38683
- const callee_move_param = !!node.resolved_function?.params?.filter((p) => !p.is_self_param)[idx]?.is_moved;
38684
- const transfer = box_ctor_transfer || (node.move_owned_param_indices?.includes(idx) ?? false) || callee_move_param;
38685
39377
  for (const key of Array.from(status.heap_string_fields ?? [])) if (key.startsWith(prefix)) {
38686
39378
  if (!transfer) {
38687
39379
  if (!status.pending_string_releases) status.pending_string_releases = [];
@@ -38690,6 +39382,11 @@ function build_function_call_node(node, status) {
38690
39382
  status.heap_string_fields.delete(key);
38691
39383
  }
38692
39384
  }
39385
+ if (use_flag) {
39386
+ if (!status.pending_mv_owns_sets) status.pending_mv_owns_sets = [];
39387
+ status.pending_mv_owns_sets.push(`${owns_flag} = 1;`);
39388
+ continue;
39389
+ }
38693
39390
  if (!status.moved) status.moved = /* @__PURE__ */ new Set();
38694
39391
  status.moved.add(vname);
38695
39392
  }
@@ -39852,6 +40549,10 @@ function build_root_node(node, status) {
39852
40549
  status.code += status.closure_definitions;
39853
40550
  status.closure_definitions = void 0;
39854
40551
  }
40552
+ if (status.box_clone_definitions) {
40553
+ status.code += status.box_clone_definitions;
40554
+ status.box_clone_definitions = void 0;
40555
+ }
39855
40556
  if ((/\bobjc_msgSend\b|\bobjc_getClass\b|\bsel_registerName\b/.test(status.code) || /\bobjc_msgSend\b|\bobjc_getClass\b|\bsel_registerName\b/.test(status.headers)) && (status.platform === "macos" || status.platform === "ios")) status.headers = "#import <Foundation/Foundation.h>\n#include <objc/runtime.h>\n#include <objc/message.h>\n" + (status.platform === "macos" ? `#import <Cocoa/Cocoa.h>\n` : `#import <UIKit/UIKit.h>\n`) + status.headers;
39856
40557
  status.headers += "void **_get_trait_func(void **obj, int trait_index, int func_index);\n";
39857
40558
  if (status.emit_mode !== "user") status.code += `
@@ -40070,6 +40771,10 @@ function build_node$1(node, status, with_semicolon = false) {
40070
40771
  status.code += status.pending_string_releases.join("\n") + "\n";
40071
40772
  status.pending_string_releases.length = 0;
40072
40773
  }
40774
+ if (status.pending_mv_owns_sets?.length) {
40775
+ status.code += status.pending_mv_owns_sets.join("\n") + "\n";
40776
+ status.pending_mv_owns_sets.length = 0;
40777
+ }
40073
40778
  }
40074
40779
  }
40075
40780
  //#endregion
@@ -41248,6 +41953,7 @@ function emit_capturing_closure_value_a64(fn, status) {
41248
41953
  if (cap.is_move && !(cap.type.name === "string" && !cap.type.is_view && !cap.type.is_array)) {
41249
41954
  if (!status.moved) status.moved = /* @__PURE__ */ new Set();
41250
41955
  status.moved.add(cap.name);
41956
+ mark_local_moved_flag(status, cap.name);
41251
41957
  const prefix = `${cap.name}.`;
41252
41958
  for (const key of Array.from(status.heap_string_fields ?? [])) if (key.startsWith(prefix)) status.heap_string_fields.delete(key);
41253
41959
  }
@@ -41666,7 +42372,7 @@ function emit_struct_constructor_to_slot(fc, slot_addr, status) {
41666
42372
  * so register slots start at x1 — overflow begins at the 8th arg (slot 8).
41667
42373
  */
41668
42374
  function build_constructor_params(fc, param_regs, status) {
41669
- mark_tuple_literal_move_owners(fc, status);
42375
+ mark_tuple_literal_move_owners(fc, status, (name) => mark_local_moved_flag(status, name));
41670
42376
  const variadic_idx = fc.variadic_param_index;
41671
42377
  if (variadic_idx !== void 0 && fc.params[variadic_idx] && fc.params[variadic_idx].node_type === "array") {
41672
42378
  const arr = fc.params[variadic_idx];
@@ -42988,6 +43694,12 @@ function build_declaration_node(node, status, nir_init, nir_swap) {
42988
43694
  const offset = allocate_stack_space(status, total_size);
42989
43695
  status.stack_offsets.set(node.name, offset);
42990
43696
  if (nullable_struct) status.stack_offsets.set(has_flag_name(node.name), offset + struct_size);
43697
+ if (struct_type?.is_owning === true && !nullable_struct && !struct_type.name.startsWith("_Tuple_") && !struct_type.name.startsWith("_Anon_") && struct_needs_destroy(struct_type, status)) {
43698
+ const owns_flag_offset = allocate_stack_space(status, 8, 8);
43699
+ emit_asm(status, `str xzr, [x29, #${owns_flag_offset}]\n`);
43700
+ if (!status.mv_owns_flag) status.mv_owns_flag = /* @__PURE__ */ new Map();
43701
+ status.mv_owns_flag.set(node.name, owns_flag_offset);
43702
+ }
42991
43703
  } else if (node.is_module_init) {
42992
43704
  emit_data(status, ".data\n");
42993
43705
  emit_data(status, `.p2align 3\n${node.name}: .space ${total_size}\n`);
@@ -43731,10 +44443,27 @@ function emit_rhs_value(rhs, nir, status) {
43731
44443
  * `a = b swap <rep>` / `var X b = mv f swap <rep>`): the expression-seam
43732
44444
  * version of the historical `build_node(swap)`. Both callers normalize the
43733
44445
  * trailing newline immediately after, so the helper does it.
44446
+ *
44447
+ * The replacement is an ordinary expression COMPOSED INTO the enclosing
44448
+ * statement — never the enclosing function's return value — so a
44449
+ * value-struct-returning call in it (most commonly the fresh `T()` a swap
44450
+ * constructs) must build into a fresh stack temp, not the sret buffer.
44451
+ * `status.struct_return_buffer` is left set for the WHOLE body of an
44452
+ * sret-returning function, so without this it pointed the replacement's
44453
+ * destination at the caller's saved sret pointer and built the value through
44454
+ * it — corrupting the caller's frame (and, in the allmark parser's
44455
+ * `result.arena = mv inline_state.arena swap Arena<...>()`, the returned
44456
+ * structure's node arena, which later crashed in `#destroy`).
43734
44457
  */
43735
44458
  function emit_swap_value(swap, nir, status) {
44459
+ const old_buffer = status.struct_return_buffer;
44460
+ const old_offset = status.return_buffer_stack_offset;
44461
+ status.struct_return_buffer = void 0;
44462
+ status.return_buffer_stack_offset = void 0;
43736
44463
  if (nir && nir.node === swap) emit_expr_from_nir$1(nir, status);
43737
44464
  else build_node(swap, status);
44465
+ status.struct_return_buffer = old_buffer;
44466
+ status.return_buffer_stack_offset = old_offset;
43738
44467
  ensure_newline(status);
43739
44468
  }
43740
44469
  function build_nullable_struct_assignment(node, status, nir_rhs) {
@@ -44650,6 +45379,11 @@ function build_assignment_node(node, status, nir_rhs, nir_swap) {
44650
45379
  if (!status.heap_strings) status.heap_strings = /* @__PURE__ */ new Set();
44651
45380
  status.heap_strings.add(name);
44652
45381
  } else if (explicit_move_source !== void 0 && status.heap_strings?.delete(explicit_move_source)) status.heap_strings.add(name);
45382
+ if (lhs_is_heap && size === 16 && !status.heap_strings?.has(name)) {
45383
+ emit_strdup_string(status);
45384
+ if (!status.heap_strings) status.heap_strings = /* @__PURE__ */ new Set();
45385
+ status.heap_strings.add(name);
45386
+ }
44653
45387
  emit_asm(status, `\n`);
44654
45388
  emit_var_store(status, "x0", name, size);
44655
45389
  }
@@ -50388,6 +51122,10 @@ function build(root, options = {}) {
50388
51122
  status.borrow_returning_functions = scan_borrow_returning_functions(root);
50389
51123
  status.inline_functions = scan_inline_candidates(root);
50390
51124
  build_node(root, status);
51125
+ if (status.box_clone_definitions) {
51126
+ status.code += status.box_clone_definitions;
51127
+ status.box_clone_definitions = void 0;
51128
+ }
50391
51129
  const emitted_trait_destroys = /* @__PURE__ */ new Set();
50392
51130
  for (const trait of status.traits) {
50393
51131
  if (emitted_trait_destroys.has(trait.name)) continue;
@@ -52696,6 +53434,50 @@ function enum_owns_payloads(en, status, visited) {
52696
53434
  for (const c of en.cases) for (const p of c.params) if (type_owns_heap(p.type, status)) return true;
52697
53435
  return false;
52698
53436
  }
53437
+ /**
53438
+ * The parameter of the enclosing function named `name`, if any (a bare-name
53439
+ * donor lookup — mirrors the field-store rule's enclosing-func resolution).
53440
+ */
53441
+ function enclosing_function_param(status, name) {
53442
+ if (!name) return void 0;
53443
+ return status.stack.findLast((n) => n.node_type === "func")?.params.find((p) => p.name === name && !p.is_self_param);
53444
+ }
53445
+ /**
53446
+ * The B3 borrow contract for a BARE (non-`mv`) parameter of an owning mv
53447
+ * struct type: the call boundary does NOT materialize a private deep copy
53448
+ * (that is the pass-by-value convention's row, which declared-`mv` structs
53449
+ * are excluded from), so the parameter's storage aliases the caller's heap
53450
+ * fields and the caller keeps ownership. The callee may READ the parameter
53451
+ * but must never transfer ownership out of it — any transfer (`mv p` into a
53452
+ * declaration/assignment/owning parameter, with or without a swap: the swap
53453
+ * revalidates the callee's copy, not the caller's original; a by-value
53454
+ * `return p`; an implicit-mv boxing) hands the destination ownership the
53455
+ * caller's scope-exit destroy still reclaims (a double free). The fix at the
53456
+ * use site is to declare the parameter `mv` — which forces the call-site
53457
+ * `mv` keyword and invalidates the donor — or to `.copy()` it.
53458
+ *
53459
+ * Flagged only when the aliased bytes actually carry owned heap:
53460
+ * - a declared-`mv` struct whose fields transitively own heap (a string
53461
+ * field, a resource-releasing `#destroy`, a class/nested-owning field) —
53462
+ * a scalar-field `mv struct` aliases harmlessly and keeps the plain
53463
+ * by-value convention;
53464
+ * - an undeclared struct owning non-string heap (its locals are
53465
+ * auto-destroyed, and the boundary is an alias, not a copy).
53466
+ *
53467
+ * Exempt: `mv`/`ref`/`view`/`self` parameters, non-struct types, simple
53468
+ * types (`string` is a declared mv struct, but a string parameter's boundary
53469
+ * materializes a private deep copy, so moving one out never aliases), and
53470
+ * undeclared string-only structs (the pass-by-value deep copy).
53471
+ */
53472
+ function borrowed_param_transfer_error(param, status) {
53473
+ if (!param || param.is_moved || param.is_self_param) return void 0;
53474
+ if (param.type.is_ref || param.type.is_view) return void 0;
53475
+ if (!param.type.name) return void 0;
53476
+ const s = status.structs.find((st) => st.name === param.type.name && !st.is_simple_type && !st.is_class);
53477
+ if (!s) return void 0;
53478
+ if (!(s.is_owning ? struct_owns_heap(s, status, /* @__PURE__ */ new Set()) : struct_owns_non_string_heap(s, status, /* @__PURE__ */ new Set()))) return void 0;
53479
+ return `cannot transfer ownership out of parameter '${param.name}' — a bare parameter of mv struct type '${param.type.name}' is a borrow; declare it 'mv' (and pass it with 'mv' at the call site), or .copy() it`;
53480
+ }
52699
53481
  //#endregion
52700
53482
  //#region ../src/check/utils/captures.ts
52701
53483
  /**
@@ -55749,9 +56531,13 @@ function check_declaration_node(decl, status) {
55749
56531
  } else add_error(status, `cannot copy a trait handle — use 'mv ${src_name}' to transfer it, or box a fresh instance`, decl.value.start);
55750
56532
  } else if (src_sv && (src_sv.type.is_ref || src_sv.type.is_view)) add_error(status, boxing_owns_error(), decl.value.start);
55751
56533
  else if (src_sv && is_value_struct_conformer(src_sv.type.name, decl.type.name, status)) {
55752
- if (!decl.value.is_moved) decl.value.is_moved = true;
55753
- if (!status.moved_variables) status.moved_variables = /* @__PURE__ */ new Set();
55754
- status.moved_variables.add(src_name);
56534
+ const boxing_param_error = borrowed_param_transfer_error(enclosing_function_param(status, src_name), status);
56535
+ if (boxing_param_error) add_error(status, boxing_param_error, decl.value.start);
56536
+ else {
56537
+ if (!decl.value.is_moved) decl.value.is_moved = true;
56538
+ if (!status.moved_variables) status.moved_variables = /* @__PURE__ */ new Set();
56539
+ status.moved_variables.add(src_name);
56540
+ }
55755
56541
  }
55756
56542
  } else if (is_trait_type(decl.type.name, status) && !decl.type.is_array && !decl.type.is_ref && !decl.type.is_view && decl.value.node_type !== "func_call" && decl.value.node_type !== "value") {
55757
56543
  if (is_value_struct_conformer(type_from_value_node(decl.value, status).name, decl.type.name, status)) add_error(status, boxing_owns_error(), decl.value.start);
@@ -55814,12 +56600,16 @@ function check_declaration_node(decl, status) {
55814
56600
  } else if (check_node(decl.swap, status)) check_type_and_value_match(field_type, type_from_value_node(decl.swap, status), void 0, status, decl.swap.start, "swap");
55815
56601
  }
55816
56602
  }
55817
- if (decl.value?.node_type === "value" && decl.value.is_moved && !decl.swap) {
55818
- if (status.values.findLast((v) => v.name === decl.value.value)?.is_ref_binding) add_error(status, `'${decl.value.value}' is a ref — mv the owner instead`, decl.value.start);
55819
- else {
55820
- enforce_root_not_frozen(decl.value.value, status, decl.value.start);
55821
- if (!status.moved_variables) status.moved_variables = /* @__PURE__ */ new Set();
55822
- status.moved_variables.add(decl.value.value);
56603
+ if (decl.value?.node_type === "value" && decl.value.is_moved) {
56604
+ const transfer_error = borrowed_param_transfer_error(enclosing_function_param(status, decl.value.value), status);
56605
+ if (transfer_error) add_error(status, transfer_error, decl.value.start);
56606
+ else if (!decl.swap) {
56607
+ if (status.values.findLast((v) => v.name === decl.value.value)?.is_ref_binding) add_error(status, `'${decl.value.value}' is a ref — mv the owner instead`, decl.value.start);
56608
+ else {
56609
+ enforce_root_not_frozen(decl.value.value, status, decl.value.start);
56610
+ if (!status.moved_variables) status.moved_variables = /* @__PURE__ */ new Set();
56611
+ status.moved_variables.add(decl.value.value);
56612
+ }
55823
56613
  }
55824
56614
  }
55825
56615
  check_constraint(decl, status);
@@ -60350,9 +61140,13 @@ function check_function_call(node, status, func, target_type, self_value, self_p
60350
61140
  else if (!func_param.is_moved && has_mov_keyword) add_error(status, `Unexpected 'mv' keyword for non-mv parameter '${func_param.name}'`, param.start);
60351
61141
  if (has_mov_keyword && param_value && !node.swap_params?.has(i)) {
60352
61142
  if (func_param.type.name && (is_class_type$1(func_param.type.name, status) || !!status.traits.find((t) => t.name === func_param.type.name) || func_param.is_moved && is_owning_struct_type(func_param.type, status))) {
60353
- enforce_root_not_frozen(param_value, status, param.start);
60354
- if (!status.moved_variables) status.moved_variables = /* @__PURE__ */ new Set();
60355
- status.moved_variables.add(param_value);
61143
+ const transfer_error = borrowed_param_transfer_error(enclosing_function_param(status, param_value), status);
61144
+ if (transfer_error) add_error(status, transfer_error, param.start);
61145
+ else {
61146
+ enforce_root_not_frozen(param_value, status, param.start);
61147
+ if (!status.moved_variables) status.moved_variables = /* @__PURE__ */ new Set();
61148
+ status.moved_variables.add(param_value);
61149
+ }
60356
61150
  }
60357
61151
  }
60358
61152
  if (has_mov_keyword && !node.swap_params?.has(i) && param.node_type === "access" && param.access.node_type === "access_field") {
@@ -60381,6 +61175,8 @@ function check_function_call(node, status, func, target_type, self_value, self_p
60381
61175
  if (swap_expr) {
60382
61176
  if (!has_mov_keyword) add_error(status, `swap requires mv keyword`, swap_expr.start);
60383
61177
  else {
61178
+ const swap_param_error = borrowed_param_transfer_error(enclosing_function_param(status, param_value), status);
61179
+ if (swap_param_error) add_error(status, swap_param_error, param.start);
60384
61180
  check_node(swap_expr, status);
60385
61181
  check_type_and_value_match(param_type, type_from_value_node(swap_expr, status), void 0, status, swap_expr.start, "swap");
60386
61182
  }
@@ -61678,7 +62474,11 @@ function check_tuple_element_ownership(array, status) {
61678
62474
  const t = type_from_value_node(value, status);
61679
62475
  if (!t.name) continue;
61680
62476
  if (is_owning_struct_type(t, status)) {
61681
- if (vn.is_moved || vn.literal_last_use_move) {
62477
+ const transfer_error = borrowed_param_transfer_error(enclosing_function_param(status, vn.value), status);
62478
+ if (transfer_error) {
62479
+ add_error(status, transfer_error, value.start);
62480
+ rejected = true;
62481
+ } else if (vn.is_moved || vn.literal_last_use_move) {
61682
62482
  vn.is_moved = true;
61683
62483
  if (!status.moved_variables) status.moved_variables = /* @__PURE__ */ new Set();
61684
62484
  status.moved_variables.add(vn.value);
@@ -61953,9 +62753,13 @@ function check_assignment_node(assign, status) {
61953
62753
  return false;
61954
62754
  }
61955
62755
  if (src_sv && is_value_struct_conformer(src_sv.type.name, trait_name, status)) {
61956
- if (!assign.right_value.is_moved) assign.right_value.is_moved = true;
61957
- if (!status.moved_variables) status.moved_variables = /* @__PURE__ */ new Set();
61958
- status.moved_variables.add(src_name);
62756
+ const boxing_param_error = borrowed_param_transfer_error(enclosing_function_param(status, src_name), status);
62757
+ if (boxing_param_error) add_error(status, boxing_param_error, assign.right_value.start);
62758
+ else {
62759
+ if (!assign.right_value.is_moved) assign.right_value.is_moved = true;
62760
+ if (!status.moved_variables) status.moved_variables = /* @__PURE__ */ new Set();
62761
+ status.moved_variables.add(src_name);
62762
+ }
61959
62763
  }
61960
62764
  } else if (assign.right_value.node_type !== "func_call") {
61961
62765
  if (is_value_struct_conformer(type_from_value_node(assign.right_value, status).name, trait_name, status)) {
@@ -62033,12 +62837,16 @@ function check_assignment_node(assign, status) {
62033
62837
  const raw = assign.right_value.value;
62034
62838
  if (!(typeof raw === "string" && (raw.startsWith("\"") && raw.endsWith("\"") || raw === "null")) && rhs_type.name && is_owning_struct_type(rhs_type, status)) add_error(status, rhs_type.name === "string" ? `cannot copy a string by value — it owns its bytes; use 'mv' to transfer it, or .copy() for a deep copy` : `cannot copy '${rhs_type.name}' by value — it owns heap resources; use .copy() or mv`, assign.right_value.start);
62035
62839
  }
62036
- if (assign.right_value.node_type === "value" && assign.right_value.is_moved && !assign.swap) {
62037
- if (status.values.findLast((v) => v.name === assign.right_value.value)?.is_ref_binding) add_error(status, `'${assign.right_value.value}' is a ref — mv the owner instead`, assign.right_value.start);
62038
- else {
62039
- enforce_root_not_frozen(assign.right_value.value, status, assign.right_value.start);
62040
- if (!status.moved_variables) status.moved_variables = /* @__PURE__ */ new Set();
62041
- status.moved_variables.add(assign.right_value.value);
62840
+ if (assign.right_value.node_type === "value" && assign.right_value.is_moved) {
62841
+ const transfer_error = borrowed_param_transfer_error(enclosing_function_param(status, assign.right_value.value), status);
62842
+ if (transfer_error) add_error(status, transfer_error, assign.right_value.start);
62843
+ else if (!assign.swap) {
62844
+ if (status.values.findLast((v) => v.name === assign.right_value.value)?.is_ref_binding) add_error(status, `'${assign.right_value.value}' is a ref — mv the owner instead`, assign.right_value.start);
62845
+ else {
62846
+ enforce_root_not_frozen(assign.right_value.value, status, assign.right_value.start);
62847
+ if (!status.moved_variables) status.moved_variables = /* @__PURE__ */ new Set();
62848
+ status.moved_variables.add(assign.right_value.value);
62849
+ }
62042
62850
  }
62043
62851
  }
62044
62852
  if (assign.right_value.node_type === "value") {
@@ -63196,6 +64004,13 @@ function check_return_node(ret, status) {
63196
64004
  if (param && !param.is_self_param && (param.is_ref || param.type.is_ref) && param_struct) add_error(status, `cannot return '${param.name}' — a 'ref' borrow escapes implicitly; declare the parameter 'mv' to transfer it`, ret.value.start);
63197
64005
  }
63198
64006
  }
64007
+ if (func && ret.type.name && !func.return_type.is_view && !func.return_type.is_ref) {
64008
+ const value_node = get_inner_value_node(ret.value);
64009
+ if (value_node?.node_type === "value") {
64010
+ const transfer_error = borrowed_param_transfer_error(func.params.find((p) => p.name === value_node.value), status);
64011
+ if (transfer_error) add_error(status, transfer_error, ret.value.start);
64012
+ }
64013
+ }
63199
64014
  if (func && borrow_depth_of(ret.value, status) !== void 0) {
63200
64015
  const ret_value_name = value_from_value_node(ret.value);
63201
64016
  const sv = ret_value_name !== void 0 ? status.values.findLast((v) => v.name === ret_value_name) : void 0;