nomen-lang 0.0.17 → 0.0.19

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
@@ -1777,6 +1777,11 @@ var AccessFunctionCallNode = class extends BaseNode {
1777
1777
  * FunctionCallNode.nullable_param_indices.
1778
1778
  */
1779
1779
  nullable_param_indices;
1780
+ /**
1781
+ * Indices of arguments whose corresponding callee parameter is a `view T`
1782
+ * (notably `view string`). See FunctionCallNode.view_param_indices.
1783
+ */
1784
+ view_param_indices;
1780
1785
  swap_params;
1781
1786
  variadic_param_name;
1782
1787
  variadic_param_index;
@@ -1828,6 +1833,12 @@ var AccessFunctionCallNode = class extends BaseNode {
1828
1833
  * the programmer is asserting the array is long enough positionally.
1829
1834
  */
1830
1835
  skip_bounds_check;
1836
+ /**
1837
+ * The concrete FunctionNode the checker resolved this method call to.
1838
+ * See FunctionCallNode.resolved_function — used by build-time passes for
1839
+ * callee-signature facts (notably hidden string-length companions).
1840
+ */
1841
+ resolved_function;
1831
1842
  constructor(start, name, type, params, is_static) {
1832
1843
  super("access_func", start);
1833
1844
  this.name = name;
@@ -1901,6 +1912,14 @@ var FunctionCallNode = class extends BaseNode {
1901
1912
  * struct pointer — see ROADBLOCKS "Nullable structs".
1902
1913
  */
1903
1914
  nullable_param_indices;
1915
+ /**
1916
+ * Indices of arguments whose corresponding callee parameter is a `view T`
1917
+ * (notably `view string`). Populated during the check pass when the
1918
+ * callee's signature is resolved. The build uses this to pass the (ptr,
1919
+ * len) pair at the call boundary: a view-typed argument passes through,
1920
+ * an owned `string` argument is wrapped with its strlen.
1921
+ */
1922
+ view_param_indices;
1904
1923
  swap_params;
1905
1924
  variadic_param_name;
1906
1925
  variadic_param_index;
@@ -1934,6 +1953,14 @@ var FunctionCallNode = class extends BaseNode {
1934
1953
  * tag+payload temp), mirroring `Enum.case(args)` access calls.
1935
1954
  */
1936
1955
  is_enum_shorthand;
1956
+ /**
1957
+ * The concrete FunctionNode the checker resolved this call to (plain
1958
+ * function, struct constructor, or monomorphized/specialized copy).
1959
+ * Build-time passes consult it for callee-signature facts that only the
1960
+ * checker can resolve robustly (overloads, monos) — notably the hidden
1961
+ * string-length companion params (`ParameterNode.hidden_len`).
1962
+ */
1963
+ resolved_function;
1937
1964
  constructor(start, name, type, params, is_static) {
1938
1965
  super("func_call", start);
1939
1966
  this.name = name;
@@ -2055,6 +2082,29 @@ function type_from_value_node$1(node) {
2055
2082
  return new Type("");
2056
2083
  }
2057
2084
  //#endregion
2085
+ //#region ../src/build_common/hidden_len.ts
2086
+ /**
2087
+ * Indices of a call's DECLARED arguments whose callee parameter carries the
2088
+ * hidden string-length companion (`ParameterNode.hidden_len`, stamped by the
2089
+ * post-check `stamp_hidden_string_lens` pass). The backends append one
2090
+ * companion argument right after each such argument — `long _<name>_len` on
2091
+ * C, the next AAPCS slot on aarch64 — and the hoist scan treats these args
2092
+ * like `.length` reads so a loop-invariant strlen temp covers them.
2093
+ *
2094
+ * Resolution comes from the checker's `resolved_function` stamp (exact for
2095
+ * overloads and monomorphized copies); an unstamped call site (synthesized
2096
+ * nodes, indirect func-value calls) yields no companions, which matches the
2097
+ * qualification pass — those callees are never stamped `hidden_len`.
2098
+ */
2099
+ function callee_hidden_len_indices(call) {
2100
+ const func = call.resolved_function;
2101
+ if (!func) return [];
2102
+ const self_offset = func.params[0]?.is_self_param ? 1 : 0;
2103
+ const out = [];
2104
+ for (let i = 0; i < call.params.length; i++) if (func.params[i + self_offset]?.hidden_len) out.push(i);
2105
+ return out;
2106
+ }
2107
+ //#endregion
2058
2108
  //#region ../src/build_common/mono_name.ts
2059
2109
  function mono_type_name(type_or_name, type_args) {
2060
2110
  const name = typeof type_or_name === "string" ? type_or_name : type_or_name.name;
@@ -2153,6 +2203,27 @@ var ParameterNode = class extends BaseNode {
2153
2203
  is_variadic_tuple;
2154
2204
  func_params;
2155
2205
  func_return_type;
2206
+ /**
2207
+ * Parallel-length equalities (`a.length == b.length` conjuncts) stripped
2208
+ * from `constraint` at signature-registration time — before any call site
2209
+ * can observe this function, so declaration order never matters (a
2210
+ * forward-referenced callee must not ask its caller to prove a clause no
2211
+ * call site could). The callee's own param check seeds
2212
+ * `status.equal_lengths` from these so its body still gets the assumption.
2213
+ */
2214
+ stripped_length_equalities;
2215
+ /**
2216
+ * True for a by-value `string` param whose function body reads
2217
+ * `<name>.length` (and never rebinds/mutates/escapes it) — stamped by the
2218
+ * post-check pass `stamp_hidden_string_lens`. The backends lower it as TWO
2219
+ * parameters: the `char*` itself plus a trailing companion length
2220
+ * (`long _<name>_len` on C; the next AAPCS slot on aarch64), so the body's
2221
+ * `.length` reads are register/stack loads instead of a per-call `strlen`.
2222
+ * Call sites append the companion argument (a hoisted loop-invariant
2223
+ * strlen temp when one exists — see scan_string_length_hoists). Mirrors
2224
+ * the nullable `_has` companion convention.
2225
+ */
2226
+ hidden_len;
2156
2227
  constructor(start, name, type, default_value, is_self_param, declaration) {
2157
2228
  super("param", start);
2158
2229
  this.name = name;
@@ -2217,6 +2288,12 @@ var FunctionNode = class extends BaseNode {
2217
2288
  type_params = [];
2218
2289
  scope;
2219
2290
  /**
2291
+ * Set once the post-check `stamp_hidden_string_lens` pass has considered
2292
+ * this function — guards against double-stamping when a check pass runs
2293
+ * twice over a shared tree.
2294
+ */
2295
+ hidden_len_stamped;
2296
+ /**
2220
2297
  * Optional contract on the return value, parsed from `out TYPE: constraint`.
2221
2298
  * The placeholder `out` refers to the return value; other identifiers refer
2222
2299
  * to the function's parameters. Used by check_function_call to propagate
@@ -2673,6 +2750,20 @@ function is_container_borrow_access(node) {
2673
2750
  return !!node && node.node_type === "access" && node.access?.node_type === "access_func" && !node.access.owned_return && is_container_borrow_accessor_name(node.access.name);
2674
2751
  }
2675
2752
  /**
2753
+ * Whether `target` (the receiver of a `.to_string()` call) is a `view string`
2754
+ * VALUE — a named view local/param (the checker stamps variable references
2755
+ * with the declared type, and substitute_type preserves `is_view` across
2756
+ * monomorphization) or an inline `.slice(...)` chain (a slice call always
2757
+ * produces a view, even when its cached type lost the modifier). Such a call
2758
+ * MATERIALIZES a fresh owned heap copy (malloc + memcpy), unlike
2759
+ * `to_string` on an owned string (the `string_to_string` identity).
2760
+ */
2761
+ function is_view_materialization_target(target) {
2762
+ if (!target || typeof target !== "object") return false;
2763
+ if (target.type?.is_view) return true;
2764
+ return target.node_type === "access" && target.access?.node_type === "access_func" && !target.access.owned_return && target.access.name === "slice";
2765
+ }
2766
+ /**
2676
2767
  * Whether a function has any `return <expr>` statement in its body (used by
2677
2768
  * the C backend's gather, which registers every string-returning function
2678
2769
  * that actually returns — the backend strdup's borrows at the return site,
@@ -2781,6 +2872,7 @@ function value_is_owned_string(v, table, visiting, borrow_names, enclosing_fn) {
2781
2872
  if (mangled.startsWith("_string_interpolate_")) return true;
2782
2873
  if (mangled.endsWith("_to_string") && mangled !== "string_to_string") return true;
2783
2874
  if (raw === "to_string" && v.target?.type?.name && v.target.type.name !== "string") return true;
2875
+ if (raw === "to_string" && is_view_materialization_target(v.target)) return true;
2784
2876
  if (!v.access.owned_return && is_container_borrow_accessor_name(raw)) return !is_call_site_borrow_accessor(enclosing_fn);
2785
2877
  const resolved = method_call_returns_owned(v, table, visiting);
2786
2878
  if (resolved !== void 0) return resolved;
@@ -4396,6 +4488,11 @@ function build_function_node$1(node, status) {
4396
4488
  first_pass_slot += 1;
4397
4489
  continue;
4398
4490
  }
4491
+ if (param.type.is_view) {
4492
+ first_pass_slot += 2;
4493
+ continue;
4494
+ }
4495
+ if (param.hidden_len) first_pass_slot++;
4399
4496
  const is_struct_type = !!status.structs.find((s) => s.name === param.type.name && !s.is_simple_type);
4400
4497
  const is_trait_type = !!status.traits.find((t) => t.name === param.type.name);
4401
4498
  const is_enum_with_data = !!status.enums.find((e) => e.name === param.type.name && e.has_associated_data);
@@ -4433,7 +4530,11 @@ function build_function_node$1(node, status) {
4433
4530
  status.ref_class_slots = /* @__PURE__ */ new Map();
4434
4531
  status.function_struct_param_slots = /* @__PURE__ */ new Set();
4435
4532
  const old_variadic_params_aarch64 = status.function_variadic_params;
4533
+ const old_view_params = status.function_view_params;
4436
4534
  status.function_variadic_params = /* @__PURE__ */ new Set();
4535
+ status.function_view_params = /* @__PURE__ */ new Set();
4536
+ const old_hidden_len_params = status.hidden_len_params;
4537
+ status.hidden_len_params = /* @__PURE__ */ new Set();
4437
4538
  status.moved_class_params = /* @__PURE__ */ new Map();
4438
4539
  let moved_param_save_slots = /* @__PURE__ */ new Map();
4439
4540
  if (has_body) {
@@ -4454,6 +4555,22 @@ function build_function_node$1(node, status) {
4454
4555
  }
4455
4556
  param_idx++;
4456
4557
  }
4558
+ if (param.type.is_view) {
4559
+ const offset = allocate_stack_space(status, 16, 16);
4560
+ status.stack_offsets.set(param.name, offset);
4561
+ status.function_view_params.add(param.name);
4562
+ for (const half of [0, 1]) {
4563
+ const p_slot = param_idx + half;
4564
+ if (p_slot < 8) status.code += `str ${param_regs[p_slot]}, [x29, #${offset + half * 8}]\n`;
4565
+ else {
4566
+ const k = p_slot - 8;
4567
+ status.code += `ldr x9, [x29, #${overflow_placeholder(node.name, k)}]\n`;
4568
+ status.code += `str x9, [x29, #${offset + half * 8}]\n`;
4569
+ }
4570
+ }
4571
+ param_idx += 2;
4572
+ continue;
4573
+ }
4457
4574
  if (callee_map.has(param.name)) {
4458
4575
  const reg = callee_map.get(param.name);
4459
4576
  status.function_param_regs.set(param.name, reg);
@@ -4487,6 +4604,19 @@ function build_function_node$1(node, status) {
4487
4604
  }
4488
4605
  }
4489
4606
  param_idx++;
4607
+ if (param.hidden_len) {
4608
+ const len_offset = allocate_stack_space(status, 8, 8);
4609
+ status.stack_offsets.set(`_${param.name}_len`, len_offset);
4610
+ if (!status.hidden_len_params) status.hidden_len_params = /* @__PURE__ */ new Set();
4611
+ status.hidden_len_params.add(param.name);
4612
+ if (param_idx < 8) status.code += `str ${param_regs[param_idx]}, [x29, #${len_offset}]\n`;
4613
+ else {
4614
+ const k = param_idx - 8;
4615
+ status.code += `ldr x9, [x29, #${overflow_placeholder(node.name, k)}]\n`;
4616
+ status.code += `str x9, [x29, #${len_offset}]\n`;
4617
+ }
4618
+ param_idx++;
4619
+ }
4490
4620
  if (param.declaration === "var") status.function_param_vars.add(param.name);
4491
4621
  if (param.type.is_array) {
4492
4622
  if (!param.is_variadic ? array_struct_name(param.type, status) : void 0) {
@@ -4502,6 +4632,7 @@ function build_function_node$1(node, status) {
4502
4632
  for (let i = 0; i < node.params.length; i++) {
4503
4633
  const param = node.params[i];
4504
4634
  if (param.is_variadic) pidx++;
4635
+ if (param.hidden_len) pidx++;
4505
4636
  if (param.is_moved) {
4506
4637
  if (!!status.structs.find((s) => s.name === param.type.name && s.is_class)) {
4507
4638
  const reg = callee_map.get(param.name) ?? param_regs[pidx];
@@ -4627,6 +4758,8 @@ function build_function_node$1(node, status) {
4627
4758
  status.ref_class_slots = old_ref_class_slots;
4628
4759
  status.function_struct_param_slots = old_struct_param_slots;
4629
4760
  status.function_variadic_params = old_variadic_params_aarch64;
4761
+ status.function_view_params = old_view_params;
4762
+ status.hidden_len_params = old_hidden_len_params;
4630
4763
  status.function_return_label = old_return_label;
4631
4764
  status.struct_return_buffer = void 0;
4632
4765
  status.return_buffer_stack_offset = void 0;
@@ -6314,6 +6447,108 @@ function build_swap_params(node, status) {
6314
6447
  }
6315
6448
  }
6316
6449
  //#endregion
6450
+ //#region ../src/build_aarch64/utils/view_value.ts
6451
+ /**
6452
+ * Resolve the struct name of an expression's value by walking the access
6453
+ * chain through the (monomorphized) struct table — bare names via
6454
+ * variable_types / scoped declarations / `self`, field accesses via the
6455
+ * base struct's field type, method calls via the method's return type.
6456
+ * Fallback for cached node types that lost their modifiers (generic bodies
6457
+ * re-checked after monomorphization can carry a stale plain `string`).
6458
+ */
6459
+ function value_struct_name$1(node, status) {
6460
+ if (node.node_type === "value") {
6461
+ const name = node.value;
6462
+ if (name === "self" && status.current_struct) return status.current_struct.name;
6463
+ const vt = status.variable_types?.get(name);
6464
+ if (vt?.name) return vt.name;
6465
+ const decl = status.scoped_declarations.findLast((d) => d.name === name);
6466
+ if (decl?.type?.name) return decl.type.name;
6467
+ return;
6468
+ }
6469
+ if (node.node_type === "access") {
6470
+ const inner = node.access;
6471
+ const base = value_struct_name$1(node.target, status);
6472
+ if (!base) return void 0;
6473
+ const struct = status.structs.find((s) => s.name === base && !s.is_generic);
6474
+ if (!struct) return void 0;
6475
+ if (inner.node_type === "access_field") return struct.fields.find((f) => f.name === inner.name)?.type?.name;
6476
+ if (inner.node_type === "access_func") return struct.functions.find((f) => f.name === inner.name)?.return_type?.name;
6477
+ }
6478
+ }
6479
+ /**
6480
+ * Whether an expression is a `view T` VALUE — a (ptr, len) pair that can be
6481
+ * passed to a `view T` parameter unchanged (loaded from the value's two
6482
+ * stack slots, or produced directly by a view-returning call). Recovers
6483
+ * `is_view` from the cached node type, the declared variable type, the
6484
+ * current function's view params, or (for method calls whose cached type
6485
+ * lost the modifier) the callee's declared return type.
6486
+ */
6487
+ function is_view_value$1(node, status) {
6488
+ if (type_from_value_node$1(node)?.is_view) return true;
6489
+ if (node.node_type === "value") {
6490
+ const name = node.value;
6491
+ if (status.variable_types?.get(name)?.is_view) return true;
6492
+ if (status.function_view_params?.has(name)) return true;
6493
+ }
6494
+ if (node.node_type === "access" && node.access.node_type === "access_func") {
6495
+ const access = node.access;
6496
+ const recv_struct = value_struct_name$1(node.target, status);
6497
+ if ((status.structs.find((s) => s.name === recv_struct && !s.is_generic)?.functions.find((f) => f.name === access.name || f.name === `#${access.name}`))?.return_type?.is_view) return true;
6498
+ }
6499
+ return false;
6500
+ }
6501
+ /**
6502
+ * Emit the (ptr, len) pair for a `view string` argument into x0/x1.
6503
+ * - A view VALUE (a view local/param's two stack slots, or a view-returning
6504
+ * call that leaves the pair in x0/x1) passes through unchanged.
6505
+ * - An owned string expression builds its pointer into x0, then measures it
6506
+ * with strlen — an implicit owned→view borrow; ownership stays with the
6507
+ * caller.
6508
+ */
6509
+ function emit_view_string_arg(arg, status) {
6510
+ if (is_view_value$1(arg, status)) {
6511
+ if (arg.node_type === "value") {
6512
+ const base = status.stack_offsets?.get(arg.value);
6513
+ if (base !== void 0) {
6514
+ status.code += `ldr x0, [x29, #${base}]\n`;
6515
+ status.code += `ldr x1, [x29, #${base + 8}]\n`;
6516
+ return;
6517
+ }
6518
+ }
6519
+ build_node$1(arg, status);
6520
+ if (!status.code.endsWith("\n")) status.code += "\n";
6521
+ return;
6522
+ }
6523
+ build_node$1(arg, status);
6524
+ if (!status.code.endsWith("\n")) status.code += "\n";
6525
+ status.code += `str x0, [sp, #-16]!\n`;
6526
+ status.code += `bl _strlen\n`;
6527
+ status.code += `mov x1, x0\n`;
6528
+ status.code += `ldr x0, [sp], #16\n`;
6529
+ }
6530
+ /**
6531
+ * Materialize a `view string` into an OWNED heap string. The (ptr, len)
6532
+ * pair must be in x0/x1 on entry; on exit x0 holds a malloc'd, null-
6533
+ * terminated copy of exactly len bytes (and the caller must reclaim it —
6534
+ * set last_result_is_heap / mark the target as a heap string). Mirrors the
6535
+ * view `.to_string` builtin sequence.
6536
+ */
6537
+ function emit_view_materialize_owned(status) {
6538
+ status.code += `stp x0, x1, [sp, #-16]!\n`;
6539
+ status.code += `add x0, x1, #1\n`;
6540
+ emit_malloc(status);
6541
+ status.code += `str x0, [sp, #-16]!\n`;
6542
+ status.code += `ldr x1, [sp, #16]\n`;
6543
+ status.code += `ldr x2, [sp, #24]\n`;
6544
+ status.code += `ldr x0, [sp]\n`;
6545
+ status.code += `bl _memcpy\n`;
6546
+ status.code += `ldr x0, [sp]\n`;
6547
+ status.code += `ldr x1, [sp, #24]\n`;
6548
+ status.code += `strb wzr, [x0, x1]\n`;
6549
+ status.code += `add sp, sp, #32\n`;
6550
+ }
6551
+ //#endregion
6317
6552
  //#region ../src/build_aarch64/build_declaration_node.ts
6318
6553
  /**
6319
6554
  * Pass a struct-typed param by address. For ValueNode (variable references)
@@ -6476,10 +6711,13 @@ function build_constructor_params(fc, param_regs, status) {
6476
6711
  const elem_struct = status.structs.find((s) => s.name === elem_type_name && !s.is_simple_type);
6477
6712
  const elem_size = elem_struct ? get_struct_size(elem_type_name, status) : 8;
6478
6713
  const count = arr.values.length;
6479
- const arr_offset = allocate_stack_space(status, Math.max(count, 1) * elem_size, 16);
6714
+ const arr_offset = allocate_stack_space(status, 8 + Math.max(count, 1) * elem_size, 16);
6715
+ const data_base = arr_offset + 8;
6716
+ status.code += `mov x0, #${count}\n`;
6717
+ status.code += `str x0, [x29, #${arr_offset}]\n`;
6480
6718
  for (let j = count - 1; j >= 0; j--) {
6481
6719
  const arg = arr.values[j];
6482
- const slot_offset = arr_offset + j * elem_size;
6720
+ const slot_offset = data_base + j * elem_size;
6483
6721
  if (elem_struct && arg.node_type === "func_call") {
6484
6722
  const tfc = arg;
6485
6723
  const fc_param_regs = [
@@ -6539,7 +6777,7 @@ function build_constructor_params(fc, param_regs, status) {
6539
6777
  emit_int_immediate(status, String(count));
6540
6778
  if (!status.code.endsWith("\n")) status.code += "\n";
6541
6779
  status.code += `str x0, [x29, #${count_slot}]\n`;
6542
- status.code += `add x0, x29, #${arr_offset}\n`;
6780
+ status.code += `add x0, x29, #${data_base}\n`;
6543
6781
  status.code += `str x0, [x29, #${ptr_slot}]\n`;
6544
6782
  const reg_slots = Math.min(total_slots, 7);
6545
6783
  for (let i = 0; i < reg_slots; i++) status.code += `ldr ${param_regs[i]}, [x29, #${slots_base + i * 8}]\n`;
@@ -7414,10 +7652,18 @@ function build_declaration_node$1(node, status) {
7414
7652
  const value_node = node.value;
7415
7653
  const raw = get_raw_value$1(value_node);
7416
7654
  const is_literal = /^(\+|-)?\d+(\.\d+)?$/.test(raw) || raw.startsWith("\"") || raw === "true" || raw === "false";
7417
- if (!!status.function_return_label) {
7655
+ const use_stack = !!status.function_return_label;
7656
+ const is_heap_alias = node.type.name === "string" && !is_literal && status.heap_strings?.has(raw);
7657
+ if (use_stack) {
7418
7658
  const offset = allocate_stack_space(status, size, size);
7419
7659
  status.stack_offsets.set(node.name, offset);
7420
- if (node.type.name === "string" && !is_literal && status.heap_strings?.has(raw)) {
7660
+ if (node.type.name === "string" && !node.type.is_view && is_view_value$1(node.value, status)) {
7661
+ emit_view_string_arg(node.value, status);
7662
+ emit_view_materialize_owned(status);
7663
+ status.code += `str x0, [x29, #${offset}]\n`;
7664
+ mark_heap_string(status, node.name);
7665
+ status.last_result_is_heap = false;
7666
+ } else if (is_heap_alias) {
7421
7667
  emit_var_load(status, "x0", raw, 8);
7422
7668
  emit_strdup(status);
7423
7669
  status.code += `str x0, [x29, #${offset}]\n`;
@@ -7570,6 +7816,14 @@ function build_declaration_node$1(node, status) {
7570
7816
  const offset = allocate_stack_space(status, size, size);
7571
7817
  status.stack_offsets.set(node.name, offset);
7572
7818
  } else emit_data(status, `${node.name}: .space ${size}\n`);
7819
+ if (node.type.name === "string" && !node.type.is_view && is_view_value$1(node.value, status)) {
7820
+ emit_view_string_arg(node.value, status);
7821
+ emit_view_materialize_owned(status);
7822
+ emit_var_store(status, "x0", node.name, size);
7823
+ mark_heap_string(status, node.name);
7824
+ status.last_result_is_heap = false;
7825
+ return;
7826
+ }
7573
7827
  build_node$1(node.value, status);
7574
7828
  emit_var_store(status, "x0", node.name, size);
7575
7829
  check_heap();
@@ -8292,10 +8546,13 @@ function build_function_call_node$1(node, status) {
8292
8546
  const elem_type_name = arr.type.name || "int";
8293
8547
  const elem_struct = status.structs.find((s) => s.name === elem_type_name && !s.is_simple_type);
8294
8548
  const elem_size = elem_struct ? get_struct_size(elem_type_name, status) : 8;
8295
- const arr_offset = allocate_stack_space(status, arr.values.length * elem_size, 16);
8549
+ const arr_offset = allocate_stack_space(status, 8 + Math.max(arr.values.length, 1) * elem_size, 16);
8550
+ const data_base = arr_offset + 8;
8551
+ status.code += `mov x0, #${arr.values.length}\n`;
8552
+ status.code += `str x0, [x29, #${arr_offset}]\n`;
8296
8553
  for (let j = arr.values.length - 1; j >= 0; j--) {
8297
8554
  const arg = arr.values[j];
8298
- const slot_offset = arr_offset + j * elem_size;
8555
+ const slot_offset = data_base + j * elem_size;
8299
8556
  if (elem_struct && arg.node_type === "func_call") {
8300
8557
  const fc = arg;
8301
8558
  const fc_param_regs = [
@@ -8338,16 +8595,29 @@ function build_function_call_node$1(node, status) {
8338
8595
  non_variadic.push({
8339
8596
  node: param,
8340
8597
  is_struct: is_struct_type$2(param_type, status) || is_enum_with_data_type$1(param_type, status),
8341
- is_ref: !!is_ref_param
8598
+ is_ref: !!is_ref_param,
8599
+ is_view: !!node.view_param_indices?.includes(i)
8342
8600
  });
8343
8601
  }
8344
8602
  const nv = non_variadic.length;
8345
- const total_slots = nv + 2;
8603
+ const nv_slot = [];
8604
+ let nv_total = 0;
8605
+ for (let i = 0; i < nv; i++) {
8606
+ nv_slot.push(nv_total);
8607
+ nv_total += non_variadic[i].is_view ? 2 : 1;
8608
+ }
8609
+ const total_slots = nv_total + 2;
8346
8610
  const slots_base = allocate_stack_space(status, total_slots * 8, 16);
8347
- const count_slot = slots_base + nv * 8;
8611
+ const count_slot = slots_base + nv_total * 8;
8348
8612
  const ptr_slot = count_slot + 8;
8349
8613
  for (let i = nv - 1; i >= 0; i--) {
8350
8614
  const ep = non_variadic[i];
8615
+ if (ep.is_view) {
8616
+ emit_view_string_arg(ep.node, status);
8617
+ status.code += `str x0, [x29, #${slots_base + nv_slot[i] * 8}]\n`;
8618
+ status.code += `str x1, [x29, #${slots_base + (nv_slot[i] + 1) * 8}]\n`;
8619
+ continue;
8620
+ }
8351
8621
  if (ep.is_struct) emit_struct_address(ep.node, status);
8352
8622
  else if (ep.is_ref) {
8353
8623
  const ep_name = ep.node.node_type === "value" ? ep.node.value : void 0;
@@ -8356,11 +8626,11 @@ function build_function_call_node$1(node, status) {
8356
8626
  else emit_address_of(ep.node, status);
8357
8627
  } else build_node$1(ep.node, status);
8358
8628
  if (!status.code.endsWith("\n")) status.code += "\n";
8359
- status.code += `str x0, [x29, #${slots_base + i * 8}]\n`;
8629
+ status.code += `str x0, [x29, #${slots_base + nv_slot[i] * 8}]\n`;
8360
8630
  }
8361
8631
  status.code += `mov x0, #${arr.values.length}\n`;
8362
8632
  status.code += `str x0, [x29, #${count_slot}]\n`;
8363
- status.code += `add x0, x29, #${arr_offset}\n`;
8633
+ status.code += `add x0, x29, #${data_base}\n`;
8364
8634
  status.code += `str x0, [x29, #${ptr_slot}]\n`;
8365
8635
  for (let s = 0; s < total_slots; s++) {
8366
8636
  const slot = start_reg + s;
@@ -8381,13 +8651,33 @@ function build_function_call_node$1(node, status) {
8381
8651
  }
8382
8652
  }
8383
8653
  } else {
8384
- const has_args = node.params.length > 0;
8654
+ const view_arg_set = new Set(node.view_param_indices ?? []);
8655
+ const hidden_len_indices = callee_hidden_len_indices(node);
8656
+ const hidden_len_set = new Set(hidden_len_indices);
8657
+ const hidden_len_slots = /* @__PURE__ */ new Map();
8658
+ const arg_slot = [];
8659
+ let total_slots = 0;
8660
+ for (let i = 0; i < node.params.length; i++) {
8661
+ arg_slot.push(total_slots);
8662
+ total_slots += view_arg_set.has(i) ? 2 : 1;
8663
+ if (hidden_len_set.has(i)) {
8664
+ hidden_len_slots.set(i, total_slots);
8665
+ total_slots += 1;
8666
+ }
8667
+ }
8668
+ const has_args = total_slots > 0;
8385
8669
  let args_base = 0;
8386
- if (has_args) args_base = allocate_stack_space(status, node.params.length * 8, 16);
8670
+ if (has_args) args_base = allocate_stack_space(status, total_slots * 8, 16);
8387
8671
  for (let i = node.params.length - 1; i >= 0; i--) {
8388
8672
  const param = node.params[i];
8389
8673
  const param_type = param.type?.name || "";
8390
8674
  const is_ref_param = node.ref_param_indices?.includes(i);
8675
+ if (view_arg_set.has(i)) {
8676
+ emit_view_string_arg(param, status);
8677
+ status.code += `str x0, [x29, #${args_base + arg_slot[i] * 8}]\n`;
8678
+ status.code += `str x1, [x29, #${args_base + (arg_slot[i] + 1) * 8}]\n`;
8679
+ continue;
8680
+ }
8391
8681
  const arr_arg_name = param.node_type === "value" ? param.value : void 0;
8392
8682
  if (!!arr_arg_name && !!status.heap_array_vars?.has(arr_arg_name)) build_node$1(param, status);
8393
8683
  else if (param.node_type === "array" && param_type) {
@@ -8445,19 +8735,35 @@ function build_function_call_node$1(node, status) {
8445
8735
  } else if (is_struct_type$2(param_type, status) || is_enum_with_data_type$1(param_type, status)) emit_struct_address(node.params[i], status);
8446
8736
  else build_node$1(node.params[i], status);
8447
8737
  if (!status.code.endsWith("\n")) status.code += "\n";
8448
- status.code += `str x0, [x29, #${args_base + i * 8}]\n`;
8738
+ status.code += `str x0, [x29, #${args_base + arg_slot[i] * 8}]\n`;
8739
+ }
8740
+ for (const i of hidden_len_indices) {
8741
+ const arg = node.params[i];
8742
+ let computed = false;
8743
+ if (arg.node_type === "value") {
8744
+ const hoisted = status.string_length_slots?.get(arg.value);
8745
+ if (hoisted !== void 0) {
8746
+ status.code += `ldr x0, [x29, #${hoisted}]\n`;
8747
+ computed = true;
8748
+ }
8749
+ }
8750
+ if (!computed) {
8751
+ status.code += `ldr x0, [x29, #${args_base + arg_slot[i] * 8}]\n`;
8752
+ status.code += `bl _strlen\n`;
8753
+ }
8754
+ status.code += `str x0, [x29, #${args_base + hidden_len_slots.get(i) * 8}]\n`;
8449
8755
  }
8450
8756
  if (has_args) {
8451
- for (let i = 0; i < node.params.length; i++) {
8452
- const slot = start_reg + i;
8757
+ for (let s = 0; s < total_slots; s++) {
8758
+ const slot = start_reg + s;
8453
8759
  if (slot >= 8) continue;
8454
8760
  const reg = param_regs[slot];
8455
8761
  if (reg === "x0") continue;
8456
- status.code += `ldr ${reg}, [x29, #${args_base + i * 8}]\n`;
8762
+ status.code += `ldr ${reg}, [x29, #${args_base + s * 8}]\n`;
8457
8763
  }
8458
8764
  if (!is_struct) status.code += `ldr x0, [x29, #${args_base}]\n`;
8459
8765
  }
8460
- overflow_count = Math.max(0, node.params.length - (8 - start_reg));
8766
+ overflow_count = Math.max(0, total_slots - (8 - start_reg));
8461
8767
  if (overflow_count > 0) {
8462
8768
  outgoing_size = Math.ceil(overflow_count * 8 / 16) * 16;
8463
8769
  status.code += `sub sp, sp, #${outgoing_size}\n`;
@@ -8769,9 +9075,13 @@ function static_enum_case(operand, enum_node, status) {
8769
9075
  //#region ../src/build_aarch64/build_operation_node.ts
8770
9076
  let string_counter$1 = 0;
8771
9077
  let coalesce_counter = 0;
9078
+ let view_cmp_counter = 0;
9079
+ let sc_counter = 0;
8772
9080
  function reset_string_counter$1() {
8773
9081
  string_counter$1 = 0;
8774
9082
  coalesce_counter = 0;
9083
+ view_cmp_counter = 0;
9084
+ sc_counter = 0;
8775
9085
  }
8776
9086
  function is_comparison(op) {
8777
9087
  return [
@@ -9052,6 +9362,29 @@ function build_operation_node$1(node, status) {
9052
9362
  status.code += `cset x0, ${node.op === "==" ? "eq" : "ne"}\n`;
9053
9363
  return;
9054
9364
  }
9365
+ if ((node.op === "==" || node.op === "!=") && node.operator_func?.struct_name === "string" && (is_view_value$1(node.left_value, status) || is_view_value$1(node.right_value, status))) {
9366
+ const id = view_cmp_counter++;
9367
+ const len_ne_label = `.Lview_cmp_len_ne_${id}`;
9368
+ const done_label = `.Lview_cmp_done_${id}`;
9369
+ emit_view_string_arg(node.right_value, status);
9370
+ status.code += `stp x0, x1, [sp, #-16]!\n`;
9371
+ emit_view_string_arg(node.left_value, status);
9372
+ status.code += `ldr x2, [sp, #8]\n`;
9373
+ status.code += `cmp x1, x2\n`;
9374
+ status.code += `b.ne ${len_ne_label}\n`;
9375
+ status.code += `mov x2, x1\n`;
9376
+ status.code += `ldr x1, [sp]\n`;
9377
+ status.code += `bl _memcmp\n`;
9378
+ status.code += `cmp x0, #0\n`;
9379
+ status.code += `cset x0, ${node.op === "==" ? "eq" : "ne"}\n`;
9380
+ status.code += `b ${done_label}\n`;
9381
+ status.code += `${len_ne_label}:\n`;
9382
+ status.code += `mov x0, #0\n`;
9383
+ if (node.op === "!=") status.code += `mov x0, #1\n`;
9384
+ status.code += `${done_label}:\n`;
9385
+ status.code += `add sp, sp, #16\n`;
9386
+ return;
9387
+ }
9055
9388
  if (node.operator_func) {
9056
9389
  const left_type = type_from_value_node$1(node.left_value);
9057
9390
  const is_array_op = node.operator_func.struct_name.startsWith("Array") && left_type.is_array;
@@ -9185,6 +9518,25 @@ function build_operation_node$1(node, status) {
9185
9518
  else status.code += `fmov x0, d0\n`;
9186
9519
  return;
9187
9520
  }
9521
+ if (node.op === "&&" || node.op === "||") {
9522
+ const id = sc_counter++;
9523
+ const skip_right = `.Lsc_skip_${id}`;
9524
+ const done = `.Lsc_done_${id}`;
9525
+ build_operand$1(node.left_value, "x0", status);
9526
+ if (!status.code.endsWith("\n")) status.code += "\n";
9527
+ status.code += `cmp x0, #0\n`;
9528
+ status.code += node.op === "&&" ? `b.eq ${skip_right}\n` : `b.ne ${skip_right}\n`;
9529
+ build_operand$1(node.right_value, "x0", status);
9530
+ if (!status.code.endsWith("\n")) status.code += "\n";
9531
+ status.code += `cmp x0, #0\n`;
9532
+ status.code += `cset x0, ne\n`;
9533
+ status.code += `b ${done}\n`;
9534
+ status.code += `${skip_right}:\n`;
9535
+ if (node.op === "&&") status.code += `mov x0, #0\n`;
9536
+ else status.code += `mov x0, #1\n`;
9537
+ status.code += `${done}:\n`;
9538
+ return;
9539
+ }
9188
9540
  const need_spill = !is_simple(node.left_value);
9189
9541
  build_operand$1(node.right_value, "x2", status);
9190
9542
  if (need_spill) {
@@ -9195,19 +9547,7 @@ function build_operation_node$1(node, status) {
9195
9547
  if (!status.code.endsWith("\n")) status.code += "\n";
9196
9548
  if (need_spill) status.code += `ldr x2, [sp], #16\n`;
9197
9549
  const unsigned = is_unsigned_type(node.left_value) || is_unsigned_type(node.right_value);
9198
- if (node.op === "&&") {
9199
- status.code += `cmp x1, #0\n`;
9200
- status.code += `cset x1, ne\n`;
9201
- status.code += `cmp x2, #0\n`;
9202
- status.code += `cset x2, ne\n`;
9203
- status.code += `and x0, x1, x2\n`;
9204
- } else if (node.op === "||") {
9205
- status.code += `cmp x1, #0\n`;
9206
- status.code += `cset x1, ne\n`;
9207
- status.code += `cmp x2, #0\n`;
9208
- status.code += `cset x2, ne\n`;
9209
- status.code += `orr x0, x1, x2\n`;
9210
- } else if (is_comparison(node.op)) {
9550
+ if (is_comparison(node.op)) {
9211
9551
  status.code += `cmp x1, x2\n`;
9212
9552
  status.code += `cset x0, ${map_cmp(node.op, unsigned)}\n`;
9213
9553
  } else {
@@ -9244,6 +9584,7 @@ function is_owned_heap_temp$1(node, status) {
9244
9584
  if (mangled.startsWith("_string_interpolate_")) return true;
9245
9585
  if (mangled.endsWith("_to_string") && mangled !== "string_to_string") return true;
9246
9586
  if (raw_name === "to_string" && target_type_name && target_type_name !== "string") return true;
9587
+ if (raw_name === "to_string" && status && node.node_type === "access" && is_view_value$1(node.target, status)) return true;
9247
9588
  const heap_set = status?.heap_returning_functions;
9248
9589
  if (heap_set?.has(mangled)) return true;
9249
9590
  if (heap_set && target_value && heap_set.has(`${target_value}_${raw_name}`)) return true;
@@ -9447,6 +9788,12 @@ function build_return_node$1(node, status) {
9447
9788
  if (override_ctor) status.struct_return_buffer = saved_buffer;
9448
9789
  }
9449
9790
  if (!status.code.endsWith("\n")) status.code += "\n";
9791
+ if (status.function_return_type?.is_view && status.function_return_type.name === "string" && node.value && !is_view_value$1(node.value, status)) {
9792
+ status.code += `str x0, [sp, #-16]!\n`;
9793
+ status.code += `bl _strlen\n`;
9794
+ status.code += `mov x1, x0\n`;
9795
+ status.code += `ldr x0, [sp], #16\n`;
9796
+ }
9450
9797
  if (status.function_return_type?.name === "string" && !status.function_return_type?.is_view && node.value.node_type === "value" && node.value.value.length >= 2 && node.value.value.startsWith("\"") && node.value.value.endsWith("\"")) {
9451
9798
  const fn_name = status.current_function_name;
9452
9799
  const mangled = status.current_struct ? `${status.current_struct.name}_${fn_name}` : fn_name;
@@ -9767,7 +10114,7 @@ function build_value_node$1(node, status) {
9767
10114
  }
9768
10115
  //#endregion
9769
10116
  //#region ../src/build_common/scan_string_length_hoists.ts
9770
- const NON_IDENTIFIERS = /* @__PURE__ */ new Set([
10117
+ const NON_IDENTIFIERS$1 = /* @__PURE__ */ new Set([
9771
10118
  "true",
9772
10119
  "false",
9773
10120
  "null",
@@ -9776,12 +10123,12 @@ const NON_IDENTIFIERS = /* @__PURE__ */ new Set([
9776
10123
  "default"
9777
10124
  ]);
9778
10125
  function is_identifier(value) {
9779
- return !!value && !NON_IDENTIFIERS.has(value) && !/^(\+|-)?\d+(\.\d+)?$/.test(value) && !value.startsWith("\"") && !value.startsWith("'");
10126
+ return !!value && !NON_IDENTIFIERS$1.has(value) && !/^(\+|-)?\d+(\.\d+)?$/.test(value) && !value.startsWith("\"") && !value.startsWith("'");
9780
10127
  }
9781
- function root_name(node) {
10128
+ function root_name$1(node) {
9782
10129
  if (!node) return void 0;
9783
10130
  if (node.node_type === "value") return node.value;
9784
- if (node.node_type === "access") return root_name(node.target);
10131
+ if (node.node_type === "access") return root_name$1(node.target);
9785
10132
  }
9786
10133
  /**
9787
10134
  * Find the bare `string` variables whose `.length` is read inside a while
@@ -9868,9 +10215,9 @@ function scan_string_length_hoists(condition, statements, update, status) {
9868
10215
  }
9869
10216
  case "assign": {
9870
10217
  const assign = n;
9871
- const lhs = root_name(assign.left_value);
10218
+ const lhs = root_name$1(assign.left_value);
9872
10219
  if (lhs) invalidated.add(lhs);
9873
- const swap = root_name(assign.swap);
10220
+ const swap = root_name$1(assign.swap);
9874
10221
  if (swap) invalidated.add(swap);
9875
10222
  walk_children(n, in_boundary);
9876
10223
  return;
@@ -9892,11 +10239,20 @@ function scan_string_length_hoists(condition, statements, update, status) {
9892
10239
  case "access_func": {
9893
10240
  const call = n;
9894
10241
  for (const indices of [call.ref_param_indices, call.mov_param_indices]) for (const i of indices ?? []) {
9895
- const arg = root_name(call.params?.[i]);
10242
+ const arg = root_name$1(call.params?.[i]);
9896
10243
  if (arg) invalidated.add(arg);
9897
10244
  }
9898
10245
  if (call.swap_params) {
9899
- for (const [i, swap] of call.swap_params) for (const arg of [root_name(call.params?.[i]), root_name(swap)]) if (arg) invalidated.add(arg);
10246
+ for (const [i, swap] of call.swap_params) for (const arg of [root_name$1(call.params?.[i]), root_name$1(swap)]) if (arg) invalidated.add(arg);
10247
+ }
10248
+ if (!in_boundary && call.resolved_function) {
10249
+ const self_offset = call.resolved_function.params[0]?.is_self_param ? 1 : 0;
10250
+ const params = call.params ?? [];
10251
+ for (let i = 0; i < params.length; i++) {
10252
+ if (!call.resolved_function.params[i + self_offset]?.hidden_len) continue;
10253
+ const arg = params[i];
10254
+ if (arg.node_type === "value" && is_identifier(arg.value) && is_string_target(arg)) candidates.set(arg.value, arg);
10255
+ }
9900
10256
  }
9901
10257
  walk_children(n, in_boundary);
9902
10258
  return;
@@ -10073,6 +10429,7 @@ function build_while_loop_node$1(node, status) {
10073
10429
  if (!status.string_length_slots) status.string_length_slots = /* @__PURE__ */ new Map();
10074
10430
  for (const [name, target] of hoists) {
10075
10431
  if (status.string_length_slots.has(name)) continue;
10432
+ if (status.hidden_len_params?.has(name)) continue;
10076
10433
  const offset = allocate_stack_space(status, 8);
10077
10434
  status.string_length_slots.set(name, offset);
10078
10435
  build_node$1(target, status);
@@ -10317,6 +10674,10 @@ function emit_owning_buffer_inline_aarch64(struct_node, func_name, status) {
10317
10674
  emit_owning_replace_T(elem, status);
10318
10675
  return true;
10319
10676
  }
10677
+ if (func_name === "shift_T") {
10678
+ emit_owning_shift_T(elem, status, "x0");
10679
+ return true;
10680
+ }
10320
10681
  return false;
10321
10682
  }
10322
10683
  if (owning_buffer_is_string_elem_aarch64(struct_node)) {
@@ -10328,6 +10689,10 @@ function emit_owning_buffer_inline_aarch64(struct_node, func_name, status) {
10328
10689
  emit_string_replace_T(status, "x0");
10329
10690
  return true;
10330
10691
  }
10692
+ if (func_name === "shift_T") {
10693
+ emit_string_shift_T(status, "x0");
10694
+ return true;
10695
+ }
10331
10696
  }
10332
10697
  return false;
10333
10698
  }
@@ -10341,7 +10706,7 @@ function emit_owning_buffer_inline_aarch64(struct_node, func_name, status) {
10341
10706
  function emit_owning_buffer_standalone_aarch64(node, func_name, status) {
10342
10707
  const elem = owning_buffer_element_aarch64(node, status);
10343
10708
  if (elem) {
10344
- if (func_name !== "store_T" && func_name !== "replace_T") return false;
10709
+ if (func_name !== "store_T" && func_name !== "replace_T" && func_name !== "shift_T") return false;
10345
10710
  emit_owning_standalone_struct(elem, func_name, status);
10346
10711
  return true;
10347
10712
  }
@@ -10354,12 +10719,20 @@ function emit_owning_buffer_standalone_aarch64(node, func_name, status) {
10354
10719
  emit_string_replace_T(status, "x19");
10355
10720
  return true;
10356
10721
  }
10722
+ if (func_name === "shift_T") {
10723
+ emit_string_shift_T(status, "x19");
10724
+ return true;
10725
+ }
10357
10726
  }
10358
10727
  return false;
10359
10728
  }
10360
10729
  function emit_owning_standalone_struct(elem, func_name, status) {
10361
10730
  const T_SIZE = get_struct_size(elem.name, status);
10362
10731
  const string_fields = collect_string_fields(elem, status);
10732
+ if (func_name === "shift_T") {
10733
+ emit_owning_shift_T(elem, status, "x19");
10734
+ return true;
10735
+ }
10363
10736
  status.code += `stp x20, x21, [sp, #-16]!\n`;
10364
10737
  status.code += `str x22, [sp, #-16]!\n`;
10365
10738
  status.code += `ldr x20, [x19, #8]\n`;
@@ -10459,6 +10832,59 @@ function emit_string_replace_T(status, self_reg) {
10459
10832
  status.code += `${done}:\n`;
10460
10833
  status.code += `ldp x20, x21, [sp], #16\n`;
10461
10834
  }
10835
+ /**
10836
+ * Specialized `Buffer<string>` shift_T. `self_reg` is the register holding
10837
+ * the Buffer pointer ("x0" inline, "x19" standalone); x1 = dst, x2 = src.
10838
+ * Move slot src into slot dst — free dst's heap copy, take over src's
10839
+ * pointer, zero src. Exactly one slot owns the string afterwards.
10840
+ */
10841
+ function emit_string_shift_T(status, self_reg) {
10842
+ const done = `.Lstr_sh_done_${status.label_counter = (status.label_counter ?? 0) + 1}`;
10843
+ status.code += `stp x20, x21, [sp, #-16]!\n`;
10844
+ status.code += `ldr x9, [${self_reg}, #8]\n`;
10845
+ status.code += `add x20, x9, x1, lsl #3\n`;
10846
+ status.code += `add x21, x9, x2, lsl #3\n`;
10847
+ status.code += `cmp x20, x21\n`;
10848
+ status.code += `b.eq ${done}\n`;
10849
+ status.code += `ldr x0, [x20]\n`;
10850
+ emit_free(status);
10851
+ status.code += `ldr x9, [x21]\n`;
10852
+ status.code += `str x9, [x20]\n`;
10853
+ status.code += `str xzr, [x21]\n`;
10854
+ status.code += `${done}:\n`;
10855
+ status.code += `ldp x20, x21, [sp], #16\n`;
10856
+ }
10857
+ /**
10858
+ * Specialized owning-value-struct shift_T. `self_reg` holds the Buffer
10859
+ * pointer ("x0" inline, "x19" standalone); x1 = dst, x2 = src (slot indices).
10860
+ * Destroy dst's old value, copy src's bytes over it wholesale (the moved
10861
+ * struct's owned pointers transfer — no deep copy), zero src.
10862
+ */
10863
+ function emit_owning_shift_T(elem, status, self_reg) {
10864
+ const T_SIZE = get_struct_size(elem.name, status);
10865
+ const done = `.Lown_sh_done_${status.label_counter = (status.label_counter ?? 0) + 1}`;
10866
+ status.code += `stp x20, x21, [sp, #-16]!\n`;
10867
+ status.code += `str x22, [sp, #-16]!\n`;
10868
+ status.code += `ldr x9, [${self_reg}, #8]\n`;
10869
+ status.code += `mov x22, #${T_SIZE}\n`;
10870
+ status.code += `madd x20, x1, x22, x9\n`;
10871
+ status.code += `madd x21, x2, x22, x9\n`;
10872
+ status.code += `cmp x20, x21\n`;
10873
+ status.code += `b.eq ${done}\n`;
10874
+ status.code += `mov x0, x20\n`;
10875
+ status.code += `bl ${elem.name}_destroy\n`;
10876
+ status.code += `mov x0, x20\n`;
10877
+ status.code += `mov x1, x21\n`;
10878
+ status.code += `mov x2, x22\n`;
10879
+ status.code += `bl _memcpy\n`;
10880
+ status.code += `mov x0, x21\n`;
10881
+ status.code += `mov x1, #0\n`;
10882
+ status.code += `mov x2, x22\n`;
10883
+ status.code += `bl _memset\n`;
10884
+ status.code += `${done}:\n`;
10885
+ status.code += `ldr x22, [sp], #16\n`;
10886
+ status.code += `ldp x20, x21, [sp], #16\n`;
10887
+ }
10462
10888
  function emit_owning_store_T(elem, status) {
10463
10889
  const T_SIZE = get_struct_size(elem.name, status);
10464
10890
  const string_fields = collect_string_fields(elem, status);
@@ -11192,10 +11618,14 @@ function build_struct_functions$1(node, status) {
11192
11618
  if (needs_x19) callee_idx = 1;
11193
11619
  const old_ref_params = status.function_ref_params;
11194
11620
  const old_ref_class_slots = status.ref_class_slots;
11621
+ const old_view_params = status.function_view_params;
11622
+ const old_hidden_len_params = status.hidden_len_params;
11195
11623
  status.function_param_regs = /* @__PURE__ */ new Map();
11196
11624
  status.function_param_vars = /* @__PURE__ */ new Set();
11197
11625
  status.function_ref_params = /* @__PURE__ */ new Set();
11198
11626
  status.ref_class_slots = /* @__PURE__ */ new Map();
11627
+ status.function_view_params = /* @__PURE__ */ new Set();
11628
+ status.hidden_len_params = /* @__PURE__ */ new Set();
11199
11629
  status.struct_return_buffer = void 0;
11200
11630
  const return_struct = !func.return_type?.is_array && !!status.structs.find((s) => s.name === func.return_type?.name && !s.is_simple_type && !s.is_class);
11201
11631
  let return_buffer_stack_offset;
@@ -11218,6 +11648,10 @@ function build_struct_functions$1(node, status) {
11218
11648
  if (param.type.is_ref) status.function_ref_params.add(param.name);
11219
11649
  continue;
11220
11650
  }
11651
+ if (param.type.is_view) {
11652
+ slot_idx += 2;
11653
+ continue;
11654
+ }
11221
11655
  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) {
11222
11656
  const saved_reg = callee_saved[callee_idx++];
11223
11657
  if (saved_reg !== "x19" || !needs_x19) status.code += `str ${saved_reg}, [sp, #-16]!\n`;
@@ -11235,6 +11669,7 @@ function build_struct_functions$1(node, status) {
11235
11669
  if (!status.class_vars) status.class_vars = /* @__PURE__ */ new Set();
11236
11670
  status.class_vars.add(param.name);
11237
11671
  }
11672
+ if (param.hidden_len) slot_idx++;
11238
11673
  slot_idx++;
11239
11674
  }
11240
11675
  status.code += `sub sp, sp, #${stack_placeholder}\n`;
@@ -11255,6 +11690,22 @@ function build_struct_functions$1(node, status) {
11255
11690
  second_slot_idx += 2;
11256
11691
  continue;
11257
11692
  }
11693
+ if (param.type.is_view) {
11694
+ const offset = allocate_stack_space(status, 16, 16);
11695
+ status.stack_offsets.set(param.name, offset);
11696
+ status.function_view_params.add(param.name);
11697
+ for (const half of [0, 1]) {
11698
+ const p_slot = second_slot_idx + half;
11699
+ if (p_slot < 8) status.code += `str ${param_regs[p_slot]}, [x29, #${offset + half * 8}]\n`;
11700
+ else {
11701
+ const k = p_slot - 8;
11702
+ status.code += `ldr x9, [x29, #${overflow_placeholder(func_label, k)}]\n`;
11703
+ status.code += `str x9, [x29, #${offset + half * 8}]\n`;
11704
+ }
11705
+ }
11706
+ second_slot_idx += 2;
11707
+ continue;
11708
+ }
11258
11709
  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))) {
11259
11710
  const size = aarch64_size(param.type.name);
11260
11711
  const offset = allocate_stack_space(status, size, size);
@@ -11285,6 +11736,20 @@ function build_struct_functions$1(node, status) {
11285
11736
  }
11286
11737
  }
11287
11738
  }
11739
+ if (param.hidden_len) {
11740
+ const len_offset = allocate_stack_space(status, 8, 8);
11741
+ status.stack_offsets.set(`_${param.name}_len`, len_offset);
11742
+ if (!status.hidden_len_params) status.hidden_len_params = /* @__PURE__ */ new Set();
11743
+ status.hidden_len_params.add(param.name);
11744
+ const len_slot = second_slot_idx + 1;
11745
+ if (len_slot < 8) status.code += `str ${param_regs[len_slot]}, [x29, #${len_offset}]\n`;
11746
+ else {
11747
+ const k = len_slot - 8;
11748
+ status.code += `ldr x9, [x29, #${overflow_placeholder(func_label, k)}]\n`;
11749
+ status.code += `str x9, [x29, #${len_offset}]\n`;
11750
+ }
11751
+ second_slot_idx++;
11752
+ }
11288
11753
  second_slot_idx++;
11289
11754
  }
11290
11755
  const moved_param_save_slots = /* @__PURE__ */ new Map();
@@ -11374,6 +11839,8 @@ function build_struct_functions$1(node, status) {
11374
11839
  status.function_param_vars = old_param_vars;
11375
11840
  status.function_ref_params = old_ref_params;
11376
11841
  status.ref_class_slots = old_ref_class_slots;
11842
+ status.function_view_params = old_view_params;
11843
+ status.hidden_len_params = old_hidden_len_params;
11377
11844
  status.function_return_label = old_return_label;
11378
11845
  status.force_heap_strings = old_force_heap;
11379
11846
  status.struct_return_buffer = void 0;
@@ -11786,10 +12253,17 @@ function build_naked_inline(struct_node, func, status) {
11786
12253
  let asm = extract_aarch64_asm(func, status.platform);
11787
12254
  const standalone_return_label = `.return_${struct_node.name}_${func.name.replace(/#/g, "")}`;
11788
12255
  asm = asm.replaceAll(`b ${standalone_return_label}`, "");
11789
- if (count_x19_reads(asm) === 1) asm = asm.replace(/\bx19\b/g, "x0");
12256
+ const x19_reads = count_x19_reads(asm);
12257
+ let prologue = "";
12258
+ let epilogue = "";
12259
+ if (x19_reads === 1) asm = asm.replace(/\bx19\b/g, "x0");
12260
+ else if (x19_reads > 1) {
12261
+ prologue = `str x19, [sp, #-16]!\nmov x19, x0\n`;
12262
+ epilogue = `ldr x19, [sp], #16\n`;
12263
+ }
11790
12264
  const site = inline_counter++;
11791
- asm = asm.replace(/(\.L[A-Za-z0-9_]+)/g, `$1_${site}`);
11792
- status.code += asm + "\n";
12265
+ asm = asm.replace(/(\.L[A-Za-z0-9_]+)/g, `$1__ni${site}`);
12266
+ status.code += prologue + asm + "\n" + epilogue;
11793
12267
  }
11794
12268
  function build_inline_method(struct_node, func, status) {
11795
12269
  if (emit_owning_buffer_inline_aarch64(struct_node, func.name, status)) return;
@@ -11814,6 +12288,11 @@ function build_inline_method(struct_node, func, status) {
11814
12288
  const old_heap_cleanup_stack = status.heap_cleanup_stack;
11815
12289
  const old_moved = status.moved;
11816
12290
  const old_outer_scope_declarations = status.outer_scope_declarations;
12291
+ const old_current_struct = status.current_struct;
12292
+ const old_current_function_name = status.current_function_name;
12293
+ const old_return_assign = status.return_assign;
12294
+ const old_join_needs_owned_string = status.join_needs_owned_string;
12295
+ const old_emitted_allocations = status.emitted_allocations;
11817
12296
  const return_label = `.inline_ret_${inline_counter++}`;
11818
12297
  status.function_return_label = return_label;
11819
12298
  status.scoped_declarations = [];
@@ -11824,6 +12303,11 @@ function build_inline_method(struct_node, func, status) {
11824
12303
  status.buffer_data_cache = void 0;
11825
12304
  status.heap_cleanup_stack = [];
11826
12305
  status.moved = /* @__PURE__ */ new Set();
12306
+ status.current_struct = struct_node;
12307
+ status.current_function_name = func.name;
12308
+ status.return_assign = void 0;
12309
+ status.join_needs_owned_string = void 0;
12310
+ status.emitted_allocations = /* @__PURE__ */ new Set();
11827
12311
  if (needs_x19) {
11828
12312
  status.code += `str x19, [sp, #-16]!\n`;
11829
12313
  status.code += `mov x19, x0\n`;
@@ -11881,9 +12365,11 @@ function build_inline_method(struct_node, func, status) {
11881
12365
  status.code += `str x8, [x29, #${return_buffer_stack_offset}]\n`;
11882
12366
  status.return_buffer_stack_offset = return_buffer_stack_offset;
11883
12367
  }
12368
+ const code_length_before_body = status.code.length;
11884
12369
  build_block_node$1(func, status);
11885
12370
  const standalone_return_label = `.return_${struct_node.name}_${func.name.replace(/#/g, "")}`;
11886
- status.code = status.code.replaceAll(`b ${standalone_return_label}`, `b ${return_label}`);
12371
+ const body = status.code.slice(code_length_before_body);
12372
+ status.code = status.code.slice(0, code_length_before_body) + body.replaceAll(`b ${standalone_return_label}\n`, `b ${return_label}\n`);
11887
12373
  status.code += `${return_label}:\n`;
11888
12374
  for (let i = saved_stack_slots.length - 1; i >= 0; i--) status.code += `add sp, sp, #16\n`;
11889
12375
  for (let ci = callee_idx - 1; ci >= 0; ci--) {
@@ -11905,6 +12391,11 @@ function build_inline_method(struct_node, func, status) {
11905
12391
  status.heap_cleanup_stack = old_heap_cleanup_stack;
11906
12392
  status.moved = old_moved;
11907
12393
  status.outer_scope_declarations = old_outer_scope_declarations;
12394
+ status.current_struct = old_current_struct;
12395
+ status.current_function_name = old_current_function_name;
12396
+ status.return_assign = old_return_assign;
12397
+ status.join_needs_owned_string = old_join_needs_owned_string;
12398
+ status.emitted_allocations = old_emitted_allocations;
11908
12399
  }
11909
12400
  let inline_fn_depth = 0;
11910
12401
  const MAX_INLINE_DEPTH = 2;
@@ -11925,6 +12416,11 @@ function build_inline_function(func, status) {
11925
12416
  const old_heap_cleanup_stack = status.heap_cleanup_stack;
11926
12417
  const old_moved = status.moved;
11927
12418
  const old_outer_scope_declarations = status.outer_scope_declarations;
12419
+ const old_current_struct = status.current_struct;
12420
+ const old_current_function_name = status.current_function_name;
12421
+ const old_return_assign = status.return_assign;
12422
+ const old_join_needs_owned_string = status.join_needs_owned_string;
12423
+ const old_emitted_allocations = status.emitted_allocations;
11928
12424
  const return_label = `.inline_fn_ret_${inline_counter++}`;
11929
12425
  status.function_return_label = return_label;
11930
12426
  status.scoped_declarations = [];
@@ -11935,6 +12431,11 @@ function build_inline_function(func, status) {
11935
12431
  status.buffer_data_cache = void 0;
11936
12432
  status.heap_cleanup_stack = [];
11937
12433
  status.moved = /* @__PURE__ */ new Set();
12434
+ status.current_struct = void 0;
12435
+ status.current_function_name = func.name;
12436
+ status.return_assign = void 0;
12437
+ status.join_needs_owned_string = void 0;
12438
+ status.emitted_allocations = /* @__PURE__ */ new Set();
11938
12439
  const param_regs = [
11939
12440
  "x0",
11940
12441
  "x1",
@@ -12004,6 +12505,11 @@ function build_inline_function(func, status) {
12004
12505
  status.heap_cleanup_stack = old_heap_cleanup_stack;
12005
12506
  status.moved = old_moved;
12006
12507
  status.outer_scope_declarations = old_outer_scope_declarations;
12508
+ status.current_struct = old_current_struct;
12509
+ status.current_function_name = old_current_function_name;
12510
+ status.return_assign = old_return_assign;
12511
+ status.join_needs_owned_string = old_join_needs_owned_string;
12512
+ status.emitted_allocations = old_emitted_allocations;
12007
12513
  inline_fn_depth--;
12008
12514
  return true;
12009
12515
  }
@@ -12158,8 +12664,25 @@ function load_nursery_struct_address(target, status) {
12158
12664
  }
12159
12665
  //#endregion
12160
12666
  //#region ../src/build_aarch64/build_access_node.ts
12667
+ /**
12668
+ * Whether `struct_node` is a monomorphized `Array<T>` (`Array_<T>`). The mono
12669
+ * name is unambiguous — the compiler derives it itself (`"Array_" + elem`)
12670
+ * for every `Array<T>` instantiation, so a name match is always an Array mono.
12671
+ */
12672
+ function is_array_mono_struct(struct_node, status) {
12673
+ return !!struct_node && !struct_node.is_generic && struct_node.name.startsWith("Array_") && !!status.structs.find((s) => s.name === struct_node.name);
12674
+ }
12161
12675
  function emit_string_length$1(target, status) {
12162
12676
  if (target.node_type === "value") {
12677
+ const name = target.value;
12678
+ if (name !== "self" && status.hidden_len_params?.has(name)) {
12679
+ const len_slot = status.stack_offsets?.get(`_${name}_len`);
12680
+ if (len_slot !== void 0) {
12681
+ status.last_result_is_heap = false;
12682
+ status.code += `ldr x0, [x29, #${len_slot}]\n`;
12683
+ return;
12684
+ }
12685
+ }
12163
12686
  const slot = status.string_length_slots?.get(target.value);
12164
12687
  if (slot !== void 0) {
12165
12688
  status.last_result_is_heap = false;
@@ -12188,6 +12711,14 @@ function emit_string_length$1(target, status) {
12188
12711
  * struct element, with x0 = temp address)
12189
12712
  * v.to_string() → malloc(len+1); memcpy; null-terminate; owned copy in x0
12190
12713
  * (string views only)
12714
+ * The receiver is either a NAMED view local/param (pair in its two stack
12715
+ * slots) or an INLINE view-producing expression such as
12716
+ * `text.slice(start, end).to_string()` — the expression leaves the pair in
12717
+ * x0/x1, which is spilled to scratch slots first. Without this, the inline
12718
+ * chain falls through to struct-method dispatch and resolves to
12719
+ * `string_to_string` (an identity `mov x0, x19`), handing consumers the raw
12720
+ * slice pointer — not NUL-terminated at len — and strlen-based readers run
12721
+ * past the slice.
12191
12722
  * Returns true if handled (caller skips struct-method dispatch).
12192
12723
  */
12193
12724
  function build_view_op(node, access_func, status) {
@@ -12196,11 +12727,19 @@ function build_view_op(node, access_func, status) {
12196
12727
  const vt = status.variable_types?.get(node.target.value);
12197
12728
  if (vt?.is_view) t = vt;
12198
12729
  }
12199
- if (!t?.is_view) return false;
12200
- if (node.target.node_type !== "value") return false;
12201
- const base = status.stack_offsets?.get(node.target.value);
12202
- if (base === void 0) return false;
12203
- const elem_name = t.name === "string" ? "char" : t.name;
12730
+ if (!t?.is_view && !is_view_value$1(node.target, status)) return false;
12731
+ let base;
12732
+ if (node.target.node_type === "value") base = status.stack_offsets?.get(node.target.value);
12733
+ if (base === void 0) {
12734
+ build_node$1(node.target, status);
12735
+ if (!status.code.endsWith("\n")) status.code += "\n";
12736
+ const temp_base = allocate_stack_space(status, 16, 16);
12737
+ status.code += `str x0, [x29, #${temp_base}]\n`;
12738
+ status.code += `str x1, [x29, #${temp_base + 8}]\n`;
12739
+ base = temp_base;
12740
+ }
12741
+ const view_elem = t?.name || "string";
12742
+ const elem_name = view_elem === "string" ? "char" : view_elem;
12204
12743
  if (access_func.name === "at" && access_func.params.length === 1) {
12205
12744
  build_node$1(access_func.params[0], status);
12206
12745
  if (!status.code.endsWith("\n")) status.code += "\n";
@@ -12226,21 +12765,10 @@ function build_view_op(node, access_func, status) {
12226
12765
  else status.code += `ldr x0, [x0, x1, lsl #3]\n`;
12227
12766
  return true;
12228
12767
  }
12229
- if (access_func.name === "to_string" && t.name === "string") {
12768
+ if (access_func.name === "to_string" && view_elem === "string") {
12230
12769
  status.code += `ldr x0, [x29, #${base}]\n`;
12231
12770
  status.code += `ldr x1, [x29, #${base + 8}]\n`;
12232
- status.code += `stp x0, x1, [sp, #-16]!\n`;
12233
- status.code += `add x0, x1, #1\n`;
12234
- emit_malloc(status);
12235
- status.code += `str x0, [sp, #-16]!\n`;
12236
- status.code += `ldr x1, [sp, #16]\n`;
12237
- status.code += `ldr x2, [sp, #24]\n`;
12238
- status.code += `ldr x0, [sp]\n`;
12239
- status.code += `bl _memcpy\n`;
12240
- status.code += `ldr x0, [sp]\n`;
12241
- status.code += `ldr x1, [sp, #24]\n`;
12242
- status.code += `strb wzr, [x0, x1]\n`;
12243
- status.code += `add sp, sp, #32\n`;
12771
+ emit_view_materialize_owned(status);
12244
12772
  status.last_result_is_heap = true;
12245
12773
  return true;
12246
12774
  }
@@ -12448,6 +12976,14 @@ function build_access_field(node, status) {
12448
12976
  }
12449
12977
  const target_name = node.target.node_type === "value" ? node.target.value : target_type?.name;
12450
12978
  const access_field = node.access;
12979
+ if (access_field.name === "length" && node.target.node_type === "value" && node.target.value === "self" && is_array_mono_struct(status.current_struct, status)) {
12980
+ const self_reg = get_param_reg("self", status);
12981
+ if (self_reg) {
12982
+ if (self_reg !== "x0") status.code += `mov x0, ${self_reg}\n`;
12983
+ status.code += `ldr x0, [x0, #-8]\n`;
12984
+ return;
12985
+ }
12986
+ }
12451
12987
  if (access_field.type?.name === "func") {
12452
12988
  status.code += `adr x0, ${target_type.name}_${access_field.name}\n`;
12453
12989
  return;
@@ -13086,14 +13622,41 @@ function build_access_method(node, access_func, status) {
13086
13622
  "x6",
13087
13623
  "x7"
13088
13624
  ];
13089
- const overflow_count = Math.max(0, access_func.params.length - (8 - start_reg));
13625
+ const view_arg_set = new Set(access_func.view_param_indices ?? []);
13626
+ const hidden_len_indices = callee_hidden_len_indices(access_func);
13627
+ const hidden_len_set = new Set(hidden_len_indices);
13628
+ const hidden_len_slots = /* @__PURE__ */ new Map();
13629
+ const arg_slot = [];
13630
+ let total_arg_slots = 0;
13631
+ for (let i = 0; i < access_func.params.length; i++) {
13632
+ arg_slot.push(total_arg_slots);
13633
+ total_arg_slots += view_arg_set.has(i) ? 2 : 1;
13634
+ if (hidden_len_set.has(i)) {
13635
+ hidden_len_slots.set(i, total_arg_slots);
13636
+ total_arg_slots += 1;
13637
+ }
13638
+ }
13639
+ const overflow_count = Math.max(0, total_arg_slots - (8 - start_reg));
13090
13640
  let overflow_base = 0;
13091
13641
  if (overflow_count > 0) overflow_base = allocate_stack_space(status, overflow_count * 8, 16);
13642
+ const has_view_args = view_arg_set.size > 0;
13643
+ let view_spill_base = 0;
13644
+ if (has_view_args) view_spill_base = allocate_stack_space(status, total_arg_slots * 8, 16);
13645
+ const view_half_store = (j, half) => {
13646
+ const half_slot = start_reg + arg_slot[j] + half;
13647
+ return half_slot >= 8 ? overflow_base + (half_slot - 8) * 8 : view_spill_base + (arg_slot[j] + half) * 8;
13648
+ };
13092
13649
  const ref_class_param_reload = [];
13093
13650
  for (let i = access_func.params.length - 1; i >= 0; i--) {
13094
13651
  const param = access_func.params[i];
13095
13652
  const is_ref_param = access_func.ref_param_indices?.includes(i);
13096
13653
  const param_type = param.type?.name || "";
13654
+ if (view_arg_set.has(i)) {
13655
+ emit_view_string_arg(param, status);
13656
+ status.code += `str x0, [x29, #${view_half_store(i, 0)}]\n`;
13657
+ status.code += `str x1, [x29, #${view_half_store(i, 1)}]\n`;
13658
+ continue;
13659
+ }
13097
13660
  const is_struct = is_struct_type(param_type, status) || is_enum_with_data_type(param_type, status);
13098
13661
  if (is_ref_param) {
13099
13662
  const rp_name = param.node_type === "value" ? param.value : void 0;
@@ -13115,14 +13678,66 @@ function build_access_method(node, access_func, status) {
13115
13678
  if (!status.code.endsWith("\n")) status.code += "\n";
13116
13679
  }
13117
13680
  } else build_node$1(param, status);
13118
- const slot = start_reg + i;
13681
+ const slot = start_reg + arg_slot[i];
13119
13682
  if (!status.code.endsWith("\n")) status.code += "\n";
13120
13683
  if (slot >= 8) status.code += `str x0, [x29, #${overflow_base + (slot - 8) * 8}]\n`;
13121
13684
  else {
13122
- const reg = param_regs[i];
13685
+ const reg = param_regs[arg_slot[i]];
13123
13686
  if (reg && reg !== "x0") status.code += `mov ${reg}, x0\n`;
13124
13687
  }
13125
13688
  }
13689
+ if (has_view_args) for (let j = 0; j < access_func.params.length; j++) {
13690
+ if (!view_arg_set.has(j)) continue;
13691
+ for (const half of [0, 1]) {
13692
+ const half_slot = start_reg + arg_slot[j] + half;
13693
+ if (half_slot >= 8) continue;
13694
+ status.code += `ldr x${half_slot}, [x29, #${view_spill_base + (arg_slot[j] + half) * 8}]\n`;
13695
+ }
13696
+ }
13697
+ if (hidden_len_indices.length > 0) {
13698
+ const needs_strlen = hidden_len_indices.some((i) => {
13699
+ const arg = access_func.params[i];
13700
+ if (arg.node_type !== "value") return true;
13701
+ return !status.string_length_slots?.has(arg.value);
13702
+ });
13703
+ const live_regs = [];
13704
+ if (needs_strlen) {
13705
+ const max_live = Math.min(7, start_reg + total_arg_slots - 1);
13706
+ for (let s = start_reg; s <= max_live; s++) live_regs.push(`x${s}`);
13707
+ }
13708
+ const reg_save_base = needs_strlen ? allocate_stack_space(status, Math.max(live_regs.length, 1) * 8, 16) : 0;
13709
+ const len_save_base = allocate_stack_space(status, hidden_len_indices.length * 8, 16);
13710
+ for (let k = 0; k < live_regs.length; k++) status.code += `str ${live_regs[k]}, [x29, #${reg_save_base + k * 8}]\n`;
13711
+ for (let h = 0; h < hidden_len_indices.length; h++) {
13712
+ const i = hidden_len_indices[h];
13713
+ const arg = access_func.params[i];
13714
+ let computed = false;
13715
+ if (arg.node_type === "value") {
13716
+ const hoisted = status.string_length_slots?.get(arg.value);
13717
+ if (hoisted !== void 0) {
13718
+ status.code += `ldr x9, [x29, #${hoisted}]\n`;
13719
+ status.code += `str x9, [x29, #${len_save_base + h * 8}]\n`;
13720
+ computed = true;
13721
+ }
13722
+ }
13723
+ if (!computed) {
13724
+ const src_slot = start_reg + arg_slot[i];
13725
+ if (src_slot < 8) {
13726
+ const save_index = src_slot - start_reg;
13727
+ status.code += `ldr x0, [x29, #${reg_save_base + save_index * 8}]\n`;
13728
+ } else status.code += `ldr x0, [x29, #${overflow_base + (src_slot - 8) * 8}]\n`;
13729
+ status.code += `bl _strlen\n`;
13730
+ status.code += `str x0, [x29, #${len_save_base + h * 8}]\n`;
13731
+ }
13732
+ }
13733
+ for (let k = live_regs.length - 1; k >= 0; k--) status.code += `ldr ${live_regs[k]}, [x29, #${reg_save_base + k * 8}]\n`;
13734
+ for (let h = 0; h < hidden_len_indices.length; h++) {
13735
+ const dest_slot = start_reg + hidden_len_slots.get(hidden_len_indices[h]);
13736
+ status.code += `ldr x9, [x29, #${len_save_base + h * 8}]\n`;
13737
+ if (dest_slot < 8) status.code += `mov x${dest_slot}, x9\n`;
13738
+ else status.code += `str x9, [x29, #${overflow_base + (dest_slot - 8) * 8}]\n`;
13739
+ }
13740
+ }
13126
13741
  if (!status.code.endsWith("\n")) status.code += "\n";
13127
13742
  if (needs_self_save) status.code += `ldr x0, [sp], #16\n`;
13128
13743
  let outgoing_size = 0;
@@ -14226,6 +14841,84 @@ function has_flag_name(name) {
14226
14841
  return `${name}_has`;
14227
14842
  }
14228
14843
  //#endregion
14844
+ //#region ../src/build_c/utils/view_value.ts
14845
+ /**
14846
+ * Resolve the struct name of an expression's value by walking the access
14847
+ * chain through the (monomorphized) struct table — bare names via
14848
+ * variable_types / scoped declarations / `self`, field accesses via the
14849
+ * base struct's field type, method calls via the method's return type.
14850
+ * Fallback for cached node types that lost their modifiers (generic bodies
14851
+ * re-checked after monomorphization can carry a stale plain `string`).
14852
+ */
14853
+ function value_struct_name(node, status) {
14854
+ if (node.node_type === "value") {
14855
+ const name = node.value;
14856
+ if (name === "self" && status.current_struct) return status.current_struct.name;
14857
+ const vt = status.variable_types?.get(name);
14858
+ if (vt?.name) return vt.name;
14859
+ const decl = status.scoped_declarations.findLast((d) => d.name === name);
14860
+ if (decl?.type?.name) return decl.type.name;
14861
+ return;
14862
+ }
14863
+ if (node.node_type === "access") {
14864
+ const inner = node.access;
14865
+ const base = value_struct_name(node.target, status);
14866
+ if (!base) return void 0;
14867
+ const struct = status.structs.find((s) => s.name === base && !s.is_generic);
14868
+ if (!struct) return void 0;
14869
+ if (inner.node_type === "access_field") return struct.fields.find((f) => f.name === inner.name)?.type?.name;
14870
+ if (inner.node_type === "access_func") return struct.functions.find((f) => f.name === inner.name)?.return_type?.name;
14871
+ }
14872
+ }
14873
+ /**
14874
+ * Whether an expression is a `view T` VALUE at the C level — a nomen_view
14875
+ * (ptr, len) struct expression that can pass through a `view T` parameter
14876
+ * unchanged. Recovers `is_view` from the cached node type, the declared
14877
+ * variable type, the current function's view params, or (for method calls
14878
+ * whose cached type lost the modifier) the callee's declared return type.
14879
+ */
14880
+ function is_view_value(node, status) {
14881
+ if (type_from_value_node$1(node)?.is_view) return true;
14882
+ if (node.node_type === "value") {
14883
+ const name = node.value;
14884
+ if (status.variable_types?.get(name)?.is_view) return true;
14885
+ if (status.function_view_params?.has(name)) return true;
14886
+ }
14887
+ if (node.node_type === "access" && node.access.node_type === "access_func") {
14888
+ const access = node.access;
14889
+ const recv_struct = value_struct_name(node.target, status);
14890
+ if ((status.structs.find((s) => s.name === recv_struct && !s.is_generic)?.functions.find((f) => f.name === access.name || f.name === `#${access.name}`))?.return_type?.is_view) return true;
14891
+ }
14892
+ return false;
14893
+ }
14894
+ /**
14895
+ * Emit a C expression for an argument passed to a `view string` parameter.
14896
+ * A view-typed argument is already a nomen_view — pass it through. An owned
14897
+ * `string` expression is wrapped into a nomen_view carrying its strlen (an
14898
+ * implicit owned→view borrow; the caller keeps ownership of the string).
14899
+ * Statement-expression form evaluates the operand exactly once.
14900
+ */
14901
+ function c_view_string_arg(arg, status) {
14902
+ if (is_view_value(arg, status)) {
14903
+ build_node(arg, status);
14904
+ return;
14905
+ }
14906
+ status.code += `({ char* _p = `;
14907
+ build_node(arg, status);
14908
+ status.code += `; nomen_view _v = { (void*)_p, (long)strlen(_p) }; _v; })`;
14909
+ }
14910
+ /**
14911
+ * Emit a C expression materializing a `view string` into an OWNED heap
14912
+ * string (malloc len+1 / memcpy / null-terminate). Used when a view value
14913
+ * initializes an owned `string` declaration (`const string s = v`) — the
14914
+ * resulting pointer is heap-owned and must be freed at scope exit.
14915
+ */
14916
+ function c_materialize_view_string(arg, status) {
14917
+ status.code += `({ nomen_view _t = `;
14918
+ build_node(arg, status);
14919
+ status.code += `; char* _r = malloc(_t.len + 1); memcpy(_r, _t.ptr, _t.len); _r[_t.len] = 0; _r; })`;
14920
+ }
14921
+ //#endregion
14229
14922
  //#region ../src/build_c/build_operation_node.ts
14230
14923
  function build_operation_node(node, status) {
14231
14924
  if (node.operator_func?.deferred) {
@@ -14332,8 +15025,16 @@ function build_operation_node(node, status) {
14332
15025
  status.code += `)`;
14333
15026
  } else if (node.operator_func) {
14334
15027
  const label = node.operator_func.mangled_name || `${node.operator_func.struct_name}_${node.operator_func.func_name}`;
14335
- const is_string_op = node.type?.name === "string";
14336
15028
  const is_string_cmp = node.op === "==" || node.op === "!=";
15029
+ if (is_string_cmp && node.operator_func.struct_name === "string" && (is_view_value(node.left_value, status) || is_view_value(node.right_value, status))) {
15030
+ status.code += `({ nomen_view _a = `;
15031
+ c_view_string_arg(node.left_value, status);
15032
+ status.code += `, _b = `;
15033
+ c_view_string_arg(node.right_value, status);
15034
+ status.code += `; ((_a.len == _b.len) && (memcmp(_a.ptr, _b.ptr, _a.len) == 0))${node.op === "!=" ? " == 0" : ""}; })`;
15035
+ return;
15036
+ }
15037
+ const is_string_op = node.type?.name === "string";
14337
15038
  const left_temp = (is_string_op || is_string_cmp) && is_owned_heap_temp(node.left_value, status);
14338
15039
  const right_temp = (is_string_op || is_string_cmp) && is_owned_heap_temp(node.right_value, status);
14339
15040
  if (left_temp || right_temp) {
@@ -14587,7 +15288,7 @@ function free_scoped_declarations(status, decls, persist_string_field_records =
14587
15288
  }
14588
15289
  const is_destructured_field_access = dec.value?.node_type === "access" && dec.value.access.node_type === "access_field" && !dec.value.is_moved;
14589
15290
  const is_borrowed_string = is_string_borrow(dec.value) || !!status.string_borrow_vars?.has(dec.name);
14590
- const value_is_heap_string = dec.type.name === "string" && (dec.value?.node_type === "access" && dec.value.access.node_type === "access_func" || dec.value?.node_type === "func_call");
15291
+ const value_is_heap_string = dec.type.name === "string" && (dec.value?.node_type === "access" && dec.value.access.node_type === "access_func" || dec.value?.node_type === "func_call" || !!dec.value && !dec.type.is_view && is_view_value(dec.value, status));
14591
15292
  const dec_value = dec.value;
14592
15293
  const dec_val_is_string_literal = dec.value?.node_type === "value" && dec_value.value.length >= 2 && dec_value.value.startsWith("\"") && dec_value.value.endsWith("\"");
14593
15294
  const dec_val_is_heap_string_var = dec.value?.node_type === "value" && !dec_val_is_string_literal && !!status.scoped_declarations.find((d) => d.name === dec_value.value);
@@ -14865,6 +15566,70 @@ function reclaim_to_loop_body(status) {
14865
15566
  return loopBodyIdx;
14866
15567
  }
14867
15568
  //#endregion
15569
+ //#region ../src/build_c/utils/hidden_len_call.ts
15570
+ function needs_len_materialize(arg) {
15571
+ if (arg.node_type === "value") return false;
15572
+ if (arg.node_type === "access" && arg.access.node_type === "access_field") return false;
15573
+ return true;
15574
+ }
15575
+ /**
15576
+ * Emit the statement-expression prologue materialising every non-lvalue
15577
+ * hidden-len argument once. Must be called immediately BEFORE the call's
15578
+ * label text is emitted; the args loop then substitutes the temps, and
15579
+ * close_hidden_len_wrap() terminates the expression after the call.
15580
+ */
15581
+ function open_hidden_len_wrap(indices, params, status) {
15582
+ const temps = /* @__PURE__ */ new Map();
15583
+ const to_materialize = indices.filter((i) => params[i] && needs_len_materialize(params[i]));
15584
+ if (to_materialize.length === 0) return {
15585
+ temps,
15586
+ open: false
15587
+ };
15588
+ status.code += `({ `;
15589
+ for (const i of to_materialize) {
15590
+ const tmp = `_hlm_${status.label_counter = (status.label_counter ?? 0) + 1}`;
15591
+ status.code += `char* ${tmp} = `;
15592
+ build_node(params[i], status);
15593
+ status.code += `; `;
15594
+ temps.set(i, tmp);
15595
+ }
15596
+ return {
15597
+ temps,
15598
+ open: true
15599
+ };
15600
+ }
15601
+ function close_hidden_len_wrap(wrap, status) {
15602
+ if (wrap.open) status.code += `; })`;
15603
+ }
15604
+ /**
15605
+ * Emit the companion length EXPRESSION for the argument at a hidden-len
15606
+ * index (the caller emits the separating `, `). `temp` is the materialised
15607
+ * pointer temp when one exists (open_hidden_len_wrap).
15608
+ */
15609
+ function emit_hidden_len_expr(arg, temp, status) {
15610
+ if (temp) {
15611
+ status.code += `strlen(${temp})`;
15612
+ return;
15613
+ }
15614
+ if (arg.node_type === "value") {
15615
+ const value = arg.value;
15616
+ if (value.startsWith("\"")) {
15617
+ status.code += `sizeof(${value}) - 1`;
15618
+ return;
15619
+ }
15620
+ const hoisted = status.string_length_temps?.get(value);
15621
+ if (hoisted) {
15622
+ status.code += hoisted;
15623
+ return;
15624
+ }
15625
+ status.code += `strlen(${value})`;
15626
+ return;
15627
+ }
15628
+ status.code += `strlen(`;
15629
+ build_node(arg, status);
15630
+ status.code += `)`;
15631
+ }
15632
+ //#endregion
14868
15633
  //#region ../src/build_c/build_access_node.ts
14869
15634
  /**
14870
15635
  * The C type of a single element of a `view T` slice, used to cast the
@@ -14926,6 +15691,66 @@ function build_vtable_target(node, status) {
14926
15691
  status.code = status.code.substring(0, before);
14927
15692
  status.code += "&" + expr;
14928
15693
  }
15694
+ /**
15695
+ * An Array method's `self` is a `struct Array_<T>*` — header `[0]=_vt`,
15696
+ * `[8]=length`, elements at `+16` — which is exactly what a heap receiver
15697
+ * (`struct Array_<T>*`) already is. A NON-heap receiver (a plain stack C
15698
+ * array `T w[N]`, or a variadic `...T` param lowered to `T *w`) would pass
15699
+ * the bare element pointer, so the callee's `self->length` and the raw
15700
+ * bodies' `(T*)((char*)self + sizeof(*self))` reads land on garbage
15701
+ * (`w[1]`, `w + 2`). When such a receiver with a usable length calls a
15702
+ * method on the mono `Array_<T>` struct, build a statement-expression
15703
+ * temporary carrying a proper header plus a copy of the elements — the
15704
+ * same wrap the binary-operator operand path uses
15705
+ * (build_array_operand_for_call). A `ref self` callee additionally gets
15706
+ * its element copy written back after the call, so mutations through
15707
+ * `self` reach the caller's array.
15708
+ */
15709
+ function array_receiver_wrap(node, target_type, target_method, mono_struct_name, status) {
15710
+ if (!target_type?.is_array || !mono_struct_name.startsWith("Array_")) return void 0;
15711
+ if (node.target.node_type !== "value") return void 0;
15712
+ const target_value = node.target.value;
15713
+ if (target_type.is_array_heap || status.heap_array_vars?.has(target_value)) return void 0;
15714
+ let length;
15715
+ if (status.stack_array_lengths?.has(target_value)) length = status.stack_array_lengths.get(target_value);
15716
+ else if (target_type.length) {
15717
+ const before = status.code.length;
15718
+ build_node(target_type.length, status);
15719
+ length = status.code.substring(before);
15720
+ status.code = status.code.substring(0, before);
15721
+ } else if (status.function_variadic_params?.has(target_value)) length = `_${target_value}_len`;
15722
+ if (!length) return void 0;
15723
+ const before = status.code.length;
15724
+ status.suppress_dereference = true;
15725
+ build_node(node.target, status);
15726
+ status.suppress_dereference = false;
15727
+ const recv = status.code.substring(before);
15728
+ status.code = status.code.substring(0, before);
15729
+ const elem_c = c_type(target_type.name);
15730
+ const id = status.label_counter = (status.label_counter ?? 0) + 1;
15731
+ const wrap = `_arrm_${id}`;
15732
+ const method_self_is_ref = !!target_method?.params?.some((p) => p.is_self_param && (p.is_ref || p.type?.is_ref));
15733
+ const ret = target_method?.return_type;
15734
+ const ret_c = !ret?.name ? "void" : ret.is_array ? "void*" : c_type(ret.name);
15735
+ const bytes = `${length} * sizeof(${elem_c})`;
15736
+ const length_is_literal = /^(\d+L?|0x[0-9a-fA-F]+)$/.test(length.trim());
15737
+ let prefix;
15738
+ let self_expr;
15739
+ if (length_is_literal) {
15740
+ prefix = `({ struct { struct ${mono_struct_name} _h; ${elem_c} _d[${length}]; } ${wrap}; ${wrap}._h._vt = 0; ${wrap}._h.length = ${length}; memcpy(${wrap}._d, ${recv}, ${bytes}); ` + (method_self_is_ref && ret_c !== "void" ? `${ret_c} _arrm_r_${id} = ` : "");
15741
+ self_expr = `(struct ${mono_struct_name}*)&${wrap}`;
15742
+ } else {
15743
+ prefix = `({ char ${wrap}[sizeof(struct ${mono_struct_name}) + ${bytes}]; struct ${mono_struct_name}* _arrm_p_${id} = (struct ${mono_struct_name}*)${wrap}; _arrm_p_${id}->_vt = 0; _arrm_p_${id}->length = ${length}; memcpy(${wrap} + sizeof(struct ${mono_struct_name}), ${recv}, ${bytes}); ` + (method_self_is_ref && ret_c !== "void" ? `${ret_c} _arrm_r_${id} = ` : "");
15744
+ self_expr = `_arrm_p_${id}`;
15745
+ }
15746
+ const copy_back = `memcpy(${recv}, ${length_is_literal ? `${wrap}._d` : `${wrap} + sizeof(struct ${mono_struct_name})`}, ${bytes}); `;
15747
+ const suffix = (method_self_is_ref ? `; ${copy_back}` + (ret_c !== "void" ? `_arrm_r_${id}; ` : `(void)0; `) : "") + `; })`;
15748
+ return {
15749
+ prefix,
15750
+ self_expr,
15751
+ suffix
15752
+ };
15753
+ }
14929
15754
  function build_access_node(node, status) {
14930
15755
  let target_type = type_from_value_node$1(node.target);
14931
15756
  if (!target_type?.name && node.target.node_type === "value") {
@@ -15233,20 +16058,36 @@ function build_access_node(node, status) {
15233
16058
  }
15234
16059
  }
15235
16060
  const label = access_func.mangled_name || trait_default_label || `${mono_struct_name}_${access_func.name.replace(/#/g, "")}`;
16061
+ const array_wrap = array_receiver_wrap(node, method_type, target_method, mono_struct_name, status);
16062
+ if (array_wrap) status.code += array_wrap.prefix;
16063
+ const hidden_len = callee_hidden_len_indices(access_func);
16064
+ const hidden_wrap = open_hidden_len_wrap(hidden_len, access_func.params, status);
15236
16065
  status.code += `${label}(`;
15237
16066
  if (!access_func.is_static) {
15238
- const method_self_is_ref = !!target_method?.params?.some((p) => p.is_self_param && (p.is_ref || p.type?.is_ref));
15239
- if (!built_in_types.includes(method_type?.name || "") || method_self_is_ref) {
15240
- const target_value = node.target.node_type === "value" ? node.target.value : "";
15241
- const target_is_ref_class_param = !!status.ref_class_params?.has(target_value);
15242
- if (!(!!status.function_ref_params?.has(target_value) || !!status.class_vars?.has(target_value) || !!status.heap_array_vars?.has(target_value))) status.code += "&";
15243
- else if (target_is_ref_class_param) {} else status.suppress_dereference = true;
16067
+ if (array_wrap) status.code += array_wrap.self_expr;
16068
+ else {
16069
+ const method_self_is_ref = !!target_method?.params?.some((p) => p.is_self_param && (p.is_ref || p.type?.is_ref));
16070
+ if (!built_in_types.includes(method_type?.name || "") || method_self_is_ref) {
16071
+ const target_value = node.target.node_type === "value" ? node.target.value : "";
16072
+ const target_is_ref_class_param = !!status.ref_class_params?.has(target_value);
16073
+ if (!(!!status.function_ref_params?.has(target_value) || !!status.class_vars?.has(target_value) || !!status.heap_array_vars?.has(target_value))) status.code += "&";
16074
+ else if (target_is_ref_class_param) {} else status.suppress_dereference = true;
16075
+ }
16076
+ build_node(node.target, status);
16077
+ status.suppress_dereference = false;
15244
16078
  }
15245
- build_node(node.target, status);
15246
- status.suppress_dereference = false;
15247
16079
  }
15248
16080
  for (let i = 0; i < access_func.params.length; i++) {
15249
16081
  if (!access_func.is_static || i > 0) status.code += ", ";
16082
+ const hidden_temp = hidden_wrap.temps.get(i);
16083
+ if (hidden_temp) {
16084
+ status.code += `${hidden_temp}, strlen(${hidden_temp})`;
16085
+ continue;
16086
+ }
16087
+ if (access_func.view_param_indices?.includes(i)) {
16088
+ c_view_string_arg(access_func.params[i], status);
16089
+ continue;
16090
+ }
15250
16091
  const param_type = type_from_value_node$1(access_func.params[i]);
15251
16092
  const param_value = access_func.params[i].node_type === "value" ? access_func.params[i].value : "";
15252
16093
  const arg_is_struct_or_trait = !!status.structs.find((s) => s.name === param_type.name && !s.is_simple_type) || !!status.traits.find((t) => t.name === param_type.name) || !!status.class_vars?.has(param_value);
@@ -15267,8 +16108,14 @@ function build_access_node(node, status) {
15267
16108
  build_node(access_func.params[i], status);
15268
16109
  status.suppress_dereference = false;
15269
16110
  } else build_node(access_func.params[i], status);
16111
+ if (hidden_len.includes(i)) {
16112
+ status.code += `, `;
16113
+ emit_hidden_len_expr(access_func.params[i], void 0, status);
16114
+ }
15270
16115
  }
15271
16116
  status.code += ")";
16117
+ close_hidden_len_wrap(hidden_wrap, status);
16118
+ if (array_wrap) status.code += array_wrap.suffix;
15272
16119
  }
15273
16120
  if (access_func.mov_param_indices) for (const idx of access_func.mov_param_indices) {
15274
16121
  const param = access_func.params[idx];
@@ -15310,6 +16157,11 @@ function resolve_access_field_type(node, status) {
15310
16157
  }
15311
16158
  function emit_string_length(target, status) {
15312
16159
  if (target.node_type === "value") {
16160
+ const name = target.value;
16161
+ if (name !== "self" && status.hidden_len_params?.has(c_function_name(name))) {
16162
+ status.code += `_${c_function_name(name)}_len`;
16163
+ return;
16164
+ }
15313
16165
  const temp = status.string_length_temps?.get(target.value);
15314
16166
  if (temp) {
15315
16167
  status.code += temp;
@@ -15834,6 +16686,10 @@ function build_parameter_node(node, status) {
15834
16686
  status.code += `)`;
15835
16687
  return;
15836
16688
  }
16689
+ if (!node.is_self_param && node.type.is_view) {
16690
+ status.code += `nomen_view ${c_function_name(node.name)}`;
16691
+ return;
16692
+ }
15837
16693
  const cls = classify_param(node.type, type_name, {
15838
16694
  is_ref: node.is_ref || node.type.is_ref,
15839
16695
  is_self: node.is_self_param,
@@ -15961,6 +16817,7 @@ function has_string_fields(node, status) {
15961
16817
  const OWNING_BUFFER_METHODS = /* @__PURE__ */ new Set([
15962
16818
  "store_T",
15963
16819
  "replace_T",
16820
+ "shift_T",
15964
16821
  "destroy",
15965
16822
  "#destroy"
15966
16823
  ]);
@@ -15995,6 +16852,15 @@ function emit_owning_buffer_string_body(func_name, status) {
15995
16852
  status.code += `if (val != _old) { free(_old); _slots[i] = val ? strdup(val) : 0; }\n`;
15996
16853
  return true;
15997
16854
  }
16855
+ if (func_name === "shift_T") {
16856
+ status.code += `char** _slots = (char**)self->data;\n`;
16857
+ status.code += `if (dst != src) {\n`;
16858
+ status.code += `free(_slots[dst]);\n`;
16859
+ status.code += `_slots[dst] = _slots[src];\n`;
16860
+ status.code += `_slots[src] = 0;\n`;
16861
+ status.code += `}\n`;
16862
+ return true;
16863
+ }
15998
16864
  if (func_name === "destroy") {
15999
16865
  status.code += `if (self->data) {\n`;
16000
16866
  status.code += `char** _slots = (char**)self->data;\n`;
@@ -16034,6 +16900,15 @@ function emit_owning_buffer_body(func_name, elem, status) {
16034
16900
  emit_deep_copy_fields(elem, "_slots[i]", "val", status);
16035
16901
  return true;
16036
16902
  }
16903
+ if (func_name === "shift_T") {
16904
+ status.code += `${Tptr}_slots = ${Tcast}(unsigned long long)self->data;\n`;
16905
+ status.code += `if (dst != src) {\n`;
16906
+ status.code += `${elem.name}_destroy(&_slots[dst]);\n`;
16907
+ status.code += `_slots[dst] = _slots[src];\n`;
16908
+ status.code += `memset((void*)&_slots[src], 0, sizeof(struct ${elem.name}));\n`;
16909
+ status.code += `}\n`;
16910
+ return true;
16911
+ }
16037
16912
  if (func_name === "destroy") {
16038
16913
  status.code += `if (self->data) {\n`;
16039
16914
  status.code += `${Tptr}_slots = ${Tcast}(unsigned long long)self->data;\n`;
@@ -16356,6 +17231,10 @@ function build_struct_functions(node, status, skip_init = false) {
16356
17231
  const old_borrow_only = status.c_borrow_only_strings;
16357
17232
  const old_return_type = status.function_return_type;
16358
17233
  const old_function_name = status.current_function_name;
17234
+ const old_view_params = status.function_view_params;
17235
+ status.function_view_params = /* @__PURE__ */ new Set();
17236
+ const old_hidden_len_params = status.hidden_len_params;
17237
+ status.hidden_len_params = /* @__PURE__ */ new Set();
16359
17238
  status.current_function_name = func.name;
16360
17239
  status.function_ref_params = /* @__PURE__ */ new Set();
16361
17240
  status.class_vars = /* @__PURE__ */ new Set();
@@ -16367,6 +17246,8 @@ function build_struct_functions(node, status, skip_init = false) {
16367
17246
  const self_param = func.params[0]?.is_self_param ? func.params[0] : null;
16368
17247
  status.self_is_ref = !!self_param?.is_ref || self_param?.declaration === "var";
16369
17248
  for (let param of func.params) {
17249
+ if (param.type.is_view && !param.is_self_param) status.function_view_params.add(c_function_name(param.name));
17250
+ if (param.hidden_len) status.hidden_len_params.add(c_function_name(param.name));
16370
17251
  const param_struct = status.structs.find((s) => s.name === param.type.name);
16371
17252
  const param_trait = status.traits.find((t) => t.name === param.type.name);
16372
17253
  if (param.is_self_param || param_struct && !param_struct.is_simple_type || param_trait || param.is_ref || param.type.is_ref || param.declaration === "var" && param_struct && !param_struct.is_simple_type) {
@@ -16417,6 +17298,7 @@ function build_struct_functions(node, status, skip_init = false) {
16417
17298
  for (let i = 0; i < func.params.length; i++) {
16418
17299
  if (i > 0) status.code += ", ";
16419
17300
  build_parameter_node(func.params[i], status);
17301
+ if (func.params[i].hidden_len) status.code += `, long _${c_function_name(func.params[i].name)}_len`;
16420
17302
  }
16421
17303
  status.code += `)`;
16422
17304
  forward_decl_referenced_types(func, status);
@@ -16446,6 +17328,8 @@ function build_struct_functions(node, status, skip_init = false) {
16446
17328
  status.c_borrow_only_strings = old_borrow_only;
16447
17329
  status.function_return_type = old_return_type;
16448
17330
  status.current_function_name = old_function_name;
17331
+ status.function_view_params = old_view_params;
17332
+ status.hidden_len_params = old_hidden_len_params;
16449
17333
  }
16450
17334
  status.current_struct = old_current_struct;
16451
17335
  for (let traitName of node.traits) {
@@ -16576,6 +17460,7 @@ function build_function_node(node, status) {
16576
17460
  else {
16577
17461
  if (node.return_type.name) {
16578
17462
  if (node.return_type.is_array) status.code += `struct Array_${node.return_type.name}* `;
17463
+ else if (node.return_type.is_view) status.code += `nomen_view `;
16579
17464
  else {
16580
17465
  const mono_return_name = mono_type_name(node.return_type);
16581
17466
  const return_is_class = !!status.structs.find((s) => s.name === mono_return_name && s.is_class);
@@ -16596,6 +17481,7 @@ function build_function_node(node, status) {
16596
17481
  if (node.params[i].is_variadic) status.code += `long _${node.params[i].name}_len, `;
16597
17482
  build_parameter_node(node.params[i], status);
16598
17483
  if (is_nullable_struct_type(node.params[i].type, status) && !node.params[i].is_self_param) status.code += `, unsigned char ${has_flag_name(c_function_name(node.params[i].name))}`;
17484
+ if (node.params[i].hidden_len) status.code += `, long _${c_function_name(node.params[i].name)}_len`;
16599
17485
  }
16600
17486
  if (is_nullable_struct_type(node.return_type, status)) {
16601
17487
  if (!first_param) status.code += ", ";
@@ -16624,6 +17510,10 @@ function build_function_node(node, status) {
16624
17510
  status.ref_class_param_types = /* @__PURE__ */ new Map();
16625
17511
  const old_variadic_params = status.function_variadic_params;
16626
17512
  status.function_variadic_params = /* @__PURE__ */ new Set();
17513
+ const old_hidden_len_params = status.hidden_len_params;
17514
+ status.hidden_len_params = /* @__PURE__ */ new Set();
17515
+ const old_view_params = status.function_view_params;
17516
+ status.function_view_params = /* @__PURE__ */ new Set();
16627
17517
  const old_return_type = status.function_return_type;
16628
17518
  status.function_return_type = node.return_type;
16629
17519
  const old_nullable_ret_has = status.nullable_ret_has_param;
@@ -16633,6 +17523,8 @@ function build_function_node(node, status) {
16633
17523
  status.current_function_name = node.name;
16634
17524
  for (let param of node.params) {
16635
17525
  if (param.is_variadic) status.function_variadic_params.add(c_function_name(param.name));
17526
+ if (param.hidden_len) status.hidden_len_params.add(c_function_name(param.name));
17527
+ if (param.type.is_view && !param.is_self_param) status.function_view_params.add(c_function_name(param.name));
16636
17528
  if (!param.is_variadic ? array_struct_name(param.type, status) : void 0) {
16637
17529
  const pname = c_function_name(param.name);
16638
17530
  if (!status.heap_array_vars) status.heap_array_vars = /* @__PURE__ */ new Set();
@@ -16665,6 +17557,8 @@ function build_function_node(node, status) {
16665
17557
  status.ref_class_params = old_ref_class_params;
16666
17558
  status.ref_class_param_types = old_ref_class_param_types;
16667
17559
  status.function_variadic_params = old_variadic_params;
17560
+ status.hidden_len_params = old_hidden_len_params;
17561
+ status.function_view_params = old_view_params;
16668
17562
  status.function_return_type = old_return_type;
16669
17563
  status.nullable_ret_has_param = old_nullable_ret_has;
16670
17564
  status.current_function_name = old_function_name;
@@ -17378,7 +18272,8 @@ function build_declaration_node(node, status) {
17378
18272
  const val_node = node.value;
17379
18273
  const val_is_string_literal = node.value.node_type === "value" && val_node.value.length >= 2 && val_node.value.startsWith("\"") && val_node.value.endsWith("\"");
17380
18274
  const val_is_heap_string_var = node.value.node_type === "value" && !val_is_string_literal && !!status.scoped_declarations.find((d) => d.name === val_node.value);
17381
- if (node.declaration === "var" && node.type.name === "string" && !node.type.is_view && (val_is_string_literal || val_is_heap_string_var) && !is_borrow_only_string) {
18275
+ if (node.type.name === "string" && !node.type.is_view && is_view_value(node.value, status)) c_materialize_view_string(node.value, status);
18276
+ else if (node.declaration === "var" && node.type.name === "string" && !node.type.is_view && (val_is_string_literal || val_is_heap_string_var) && !is_borrow_only_string) {
17382
18277
  status.code += `strdup(`;
17383
18278
  build_node(node.value, status);
17384
18279
  status.code += `)`;
@@ -17595,10 +18490,17 @@ function build_function_call_node(node, status) {
17595
18490
  }
17596
18491
  const is_struct = status.structs.find((s) => s.name === node.name && !s.is_simple_type);
17597
18492
  const func_name = is_struct ? `${node.name}_init` : c_function_name(node.name);
18493
+ const hidden_len = callee_hidden_len_indices(node);
18494
+ const hidden_wrap = open_hidden_len_wrap(hidden_len, node.params, status);
17598
18495
  status.code += `${func_name}(`;
17599
18496
  const variadic_idx = node.variadic_param_name ? node.params.findIndex((p) => p.node_type === "array") : -1;
17600
18497
  for (let i = 0; i < node.params.length; i++) {
17601
18498
  if (i > 0) status.code += ", ";
18499
+ const hidden_temp = hidden_wrap.temps.get(i);
18500
+ if (hidden_temp) {
18501
+ status.code += `${hidden_temp}, strlen(${hidden_temp})`;
18502
+ continue;
18503
+ }
17602
18504
  if (i === variadic_idx && node.params[i].node_type === "array") {
17603
18505
  const arr = node.params[i];
17604
18506
  status.code += `${arr.values.length}, (${c_type(arr.type.name)}[]){`;
@@ -17620,6 +18522,10 @@ function build_function_call_node(node, status) {
17620
18522
  status.suppress_dereference = false;
17621
18523
  continue;
17622
18524
  }
18525
+ if (node.view_param_indices?.includes(i)) {
18526
+ c_view_string_arg(node.params[i], status);
18527
+ continue;
18528
+ }
17623
18529
  const is_ref_param = node.ref_param_indices?.includes(i);
17624
18530
  const param_is_class = !!status.structs.find((s) => s.name === param_type.name && s.is_class);
17625
18531
  if (is_ref_param && param_is_class) {
@@ -17649,6 +18555,10 @@ function build_function_call_node(node, status) {
17649
18555
  status.code += `, `;
17650
18556
  emit_nullable_arg_flag(node.params[i], status);
17651
18557
  }
18558
+ if (hidden_len.includes(i)) {
18559
+ status.code += `, `;
18560
+ emit_hidden_len_expr(node.params[i], void 0, status);
18561
+ }
17652
18562
  }
17653
18563
  if (is_nullable_struct_type(node.type, status)) {
17654
18564
  const flag_name = status.current_nullable_call_flag;
@@ -17664,6 +18574,7 @@ function build_function_call_node(node, status) {
17664
18574
  }
17665
18575
  }
17666
18576
  status.code += ")";
18577
+ close_hidden_len_wrap(hidden_wrap, status);
17667
18578
  if (node.name.startsWith("_string_interpolate_")) status.interpolate_string_counts.add(node.params.length - 1);
17668
18579
  if (node.swap_params?.size) for (const [idx, swap_expr] of node.swap_params) {
17669
18580
  const source = node.params[idx];
@@ -18072,10 +18983,13 @@ function build_return_node(node, status) {
18072
18983
  }
18073
18984
  }
18074
18985
  if (returns_borrowed_string || returns_string_literal || returns_borrow_var) status.code += `strdup(`;
18986
+ const wrap_view_string_return = !!ret_type.is_view && ret_type.name === "string" && !is_view_value(node.value, status);
18075
18987
  const expr_type_name = node.type?.name || "";
18076
18988
  const expr_is_simple = !status.structs.find((s) => s.name === expr_type_name && !s.is_simple_type);
18077
18989
  if (is_struct && expr_is_simple && !returns_borrowed_string && !ret_type.is_view) status.code += return_is_class ? `(struct ${mono_ret_name}*)` : `(struct ${mono_ret_name})`;
18990
+ if (wrap_view_string_return) status.code += `({ char* _p = `;
18078
18991
  build_node(node.value, status);
18992
+ if (wrap_view_string_return) status.code += `; nomen_view _v = { (void*)_p, (long)strlen(_p) }; _v; })`;
18079
18993
  if (returns_borrowed_string || returns_string_literal || returns_borrow_var) status.code += `)`;
18080
18994
  status.code += `;\n`;
18081
18995
  if (node.value.node_type === "func_call" && node.value.field_overrides?.length) emit_field_overrides("_return_val", node.value, build_node, status, "", ";\n");
@@ -18289,6 +19203,7 @@ function build_while_loop_node(node, status) {
18289
19203
  if (!status.string_length_temps) status.string_length_temps = /* @__PURE__ */ new Map();
18290
19204
  for (const [name, target] of hoists) {
18291
19205
  if (status.string_length_temps.has(name)) continue;
19206
+ if (status.hidden_len_params?.has(c_function_name(name))) continue;
18292
19207
  const temp = `_slh_${status.label_counter = (status.label_counter ?? 0) + 1}`;
18293
19208
  status.string_length_temps.set(name, temp);
18294
19209
  status.code += `const long ${temp} = (long)strlen(`;
@@ -20131,6 +21046,16 @@ function add_error(status, message, start) {
20131
21046
  });
20132
21047
  }
20133
21048
  //#endregion
21049
+ //#region ../src/nodes/set_resolved_function.ts
21050
+ function set_resolved_function(node, func) {
21051
+ Object.defineProperty(node, "resolved_function", {
21052
+ value: func,
21053
+ enumerable: false,
21054
+ writable: true,
21055
+ configurable: true
21056
+ });
21057
+ }
21058
+ //#endregion
20134
21059
  //#region ../src/nodes/clone_node.ts
20135
21060
  function clone_type$2(type) {
20136
21061
  const t = new Type(type.name, type.is_static, type.is_array, type.length ? clone_node(type.length) : void 0);
@@ -20165,10 +21090,13 @@ function clone_node(node) {
20165
21090
  c.type_args = n.type_args?.map(clone_type$2);
20166
21091
  c.ref_param_indices = n.ref_param_indices?.slice();
20167
21092
  c.mov_param_indices = n.mov_param_indices?.slice();
21093
+ c.nullable_param_indices = n.nullable_param_indices?.slice();
21094
+ c.view_param_indices = n.view_param_indices?.slice();
20168
21095
  c.swap_params = n.swap_params ? new Map([...n.swap_params].map(([k, v]) => [k, clone_node(v)])) : void 0;
20169
21096
  c.variadic_param_name = n.variadic_param_name;
20170
21097
  c.variadic_param_index = n.variadic_param_index;
20171
21098
  c.is_enum_shorthand = n.is_enum_shorthand;
21099
+ set_resolved_function(c, n.resolved_function);
20172
21100
  c.allocations = n.allocations?.map(clone_node);
20173
21101
  return c;
20174
21102
  }
@@ -20196,6 +21124,7 @@ function clone_node(node) {
20196
21124
  c.mangled_name = n.mangled_name;
20197
21125
  c.owned_return = n.owned_return;
20198
21126
  c.nullable_param_indices = n.nullable_param_indices?.slice();
21127
+ c.view_param_indices = n.view_param_indices?.slice();
20199
21128
  c.return_bounds = n.return_bounds ? {
20200
21129
  upper: n.return_bounds.upper.slice(),
20201
21130
  lower: n.return_bounds.lower.slice(),
@@ -20207,6 +21136,7 @@ function clone_node(node) {
20207
21136
  c.function_return_type = n.function_return_type ? clone_type$2(n.function_return_type) : void 0;
20208
21137
  c.is_statement = n.is_statement;
20209
21138
  c.skip_bounds_check = n.skip_bounds_check;
21139
+ set_resolved_function(c, n.resolved_function);
20210
21140
  c.allocations = n.allocations?.map(clone_node);
20211
21141
  return c;
20212
21142
  }
@@ -20405,7 +21335,10 @@ function clone_node(node) {
20405
21335
  c.default_value_start = n.default_value_start;
20406
21336
  c.is_variadic = n.is_variadic;
20407
21337
  c.is_ref = n.is_ref;
21338
+ c.is_variadic_tuple = n.is_variadic_tuple;
21339
+ c.hidden_len = n.hidden_len;
20408
21340
  c.constraint = n.constraint ? clone_node(n.constraint) : void 0;
21341
+ c.stripped_length_equalities = n.stripped_length_equalities?.map((e) => ({ ...e }));
20409
21342
  c.func_params = n.func_params?.map((p) => clone_node(p));
20410
21343
  c.func_return_type = n.func_return_type ? clone_type$2(n.func_return_type) : void 0;
20411
21344
  c.allocations = n.allocations?.map(clone_node);
@@ -21111,6 +22044,66 @@ function strip_length_equalities(constraint, param_name, status) {
21111
22044
  });
21112
22045
  }
21113
22046
  /**
22047
+ * Registration-time variant of strip_length_equalities: removes
22048
+ * `<container>.<size> == <container>.<size>` conjuncts from a parameter
22049
+ * constraint and returns them alongside the remaining constraint. Used when a
22050
+ * function signature is REGISTERED — before any call site can observe it — so
22051
+ * a forward-referenced callee never hands its caller a clause no call site
22052
+ * could prove (declaration order must not matter). Bases are validated
22053
+ * against `known_names` (the function's own parameters) instead of
22054
+ * status.values, because at registration time nothing has been pushed into
22055
+ * any scope yet. Does NOT touch status.equal_lengths: the extracted
22056
+ * equalities are returned for the callee's own param check to seed.
22057
+ */
22058
+ function extract_length_equalities_at_registration(constraint, param_name, known_names) {
22059
+ const equalities = [];
22060
+ if (!constraint) return {
22061
+ constraint,
22062
+ equalities
22063
+ };
22064
+ const walk = (node) => {
22065
+ if (node.node_type !== "op") return node;
22066
+ const op = node;
22067
+ if (op.op === "&&") {
22068
+ const left = walk(op.left_value);
22069
+ const right = walk(op.right_value);
22070
+ if (left && right) {
22071
+ if (left === op.left_value && right === op.right_value) return node;
22072
+ return new OperationNode(op.start, "&&", left, right, op.type);
22073
+ }
22074
+ return left ?? right;
22075
+ }
22076
+ if (op.op !== "==") return node;
22077
+ const extract = (side) => {
22078
+ if (side.node_type !== "access") return void 0;
22079
+ const access = side;
22080
+ let field;
22081
+ if (access.access.node_type === "access_field") field = access.access.name;
22082
+ else if (access.access.node_type === "access_func") field = access.access.name;
22083
+ if (!field || !NON_NEGATIVE_FIELDS$1.has(field)) return void 0;
22084
+ if (access.target.node_type !== "value") return void 0;
22085
+ return {
22086
+ base: access.target.value,
22087
+ field
22088
+ };
22089
+ };
22090
+ const lhs = extract(op.left_value);
22091
+ const rhs = extract(op.right_value);
22092
+ if (!lhs || !rhs || lhs.field !== rhs.field) return node;
22093
+ if (lhs.base === rhs.base) return node;
22094
+ for (const base of [lhs.base, rhs.base]) if (base !== param_name && !known_names.has(base)) return node;
22095
+ equalities.push({
22096
+ a: lhs.base,
22097
+ b: rhs.base,
22098
+ field: lhs.field
22099
+ });
22100
+ };
22101
+ return {
22102
+ constraint: walk(constraint),
22103
+ equalities
22104
+ };
22105
+ }
22106
+ /**
21114
22107
  * Extract flow-sensitive bounds from a comparison condition.
21115
22108
  * E.g. `j < list.length` → { var: "j", op: "<", expr: "list.length" }
21116
22109
  * Returns undefined if the condition isn't a simple var-vs-expr comparison.
@@ -22433,6 +23426,7 @@ function is_inside_core_method(status) {
22433
23426
  return false;
22434
23427
  }
22435
23428
  function check_function_call(node, status, func, target_type, self_value, self_path) {
23429
+ set_resolved_function(node, func);
22436
23430
  const access_scope = status.stack.at(-1);
22437
23431
  if (func.name === "#init") {
22438
23432
  const struct_name = target_type?.name || func.return_type.name;
@@ -22498,6 +23492,10 @@ function check_function_call(node, status, func, target_type, self_value, self_p
22498
23492
  if (!node.nullable_param_indices.includes(i)) node.nullable_param_indices.push(i);
22499
23493
  }
22500
23494
  }
23495
+ if (func_param && func_param.type.is_view && !func_param.is_self_param) {
23496
+ if (!node.view_param_indices) node.view_param_indices = [];
23497
+ if (!node.view_param_indices.includes(i)) node.view_param_indices.push(i);
23498
+ }
22501
23499
  const old_expected_type = status.expected_type;
22502
23500
  const old_allow_null = status.allow_null_value;
22503
23501
  if (func_param) {
@@ -24075,6 +25073,29 @@ function clone_type(t) {
24075
25073
  c.type_args = t.type_args;
24076
25074
  return c;
24077
25075
  }
25076
+ /**
25077
+ * Strip parallel-length equality clauses (`a.length == b.length`) from every
25078
+ * parameter's constraint of `func` at SIGNATURE-GATHER time — before any call
25079
+ * site can observe the signature, regardless of declaration order. A
25080
+ * forward-referenced callee must not hand its caller a clause no call site
25081
+ * could ever prove; the clause becomes an assumed equality for the callee's
25082
+ * own body instead (stashed on the param, seeded into scope when its params
25083
+ * are checked). Idempotent: a constraint already stripped has no clauses
25084
+ * left to extract. `check_function_node` re-runs the same extraction at
25085
+ * registration for function clones that never pass through a gather pass
25086
+ * (generic monomorphizations).
25087
+ */
25088
+ function strip_param_length_equalities(func) {
25089
+ const param_names = new Set(func.params.map((p) => p.name));
25090
+ for (const param of func.params) {
25091
+ if (!param.constraint) continue;
25092
+ const { constraint, equalities } = extract_length_equalities_at_registration(param.constraint, param.name, param_names);
25093
+ if (equalities.length) {
25094
+ param.constraint = constraint;
25095
+ param.stripped_length_equalities = equalities;
25096
+ }
25097
+ }
25098
+ }
24078
25099
  function gather_structs(block, status) {
24079
25100
  const names_in_block = {
24080
25101
  structs: /* @__PURE__ */ new Set(),
@@ -24091,6 +25112,7 @@ function gather_structs(block, status) {
24091
25112
  names_in_block.structs.add(struct.name);
24092
25113
  struct.scope = status.stack.at(-1) || block;
24093
25114
  struct.is_generic = struct.type_params.length > 0;
25115
+ for (const func of struct.functions) strip_param_length_equalities(func);
24094
25116
  status.types.push(struct.name);
24095
25117
  status.structs.push(struct);
24096
25118
  }
@@ -24103,6 +25125,7 @@ function gather_structs(block, status) {
24103
25125
  names_in_block.traits.add(trait.name);
24104
25126
  status.types.push(trait.name);
24105
25127
  status.traits.push(trait);
25128
+ for (const func of trait.functions) strip_param_length_equalities(func);
24106
25129
  }
24107
25130
  break;
24108
25131
  }
@@ -24114,6 +25137,7 @@ function gather_structs(block, status) {
24114
25137
  func.scope = status.stack.at(-1) || block;
24115
25138
  func.return_type = materialize_type(func.return_type, status);
24116
25139
  instantiate_generic_type(func.return_type, status);
25140
+ strip_param_length_equalities(func);
24117
25141
  status.functions.push(func);
24118
25142
  }
24119
25143
  break;
@@ -24179,7 +25203,10 @@ function apply_extensions(block, status) {
24179
25203
  continue;
24180
25204
  }
24181
25205
  ext.scope = target;
24182
- for (const func of ext.functions) target.functions.push(func);
25206
+ for (const func of ext.functions) {
25207
+ strip_param_length_equalities(func);
25208
+ target.functions.push(func);
25209
+ }
24183
25210
  for (let i = 0; i < ext.traits.length; i++) {
24184
25211
  if (target.traits.includes(ext.traits[i])) {
24185
25212
  add_error(status, `Type '${ext.name}' already conforms to trait '${ext.traits[i]}'`, ext.start);
@@ -24227,6 +25254,10 @@ function check_function_parameter_node(param, status) {
24227
25254
  is_set: true,
24228
25255
  is_null: param.type.is_nullable ? true : void 0
24229
25256
  });
25257
+ if (param.stripped_length_equalities?.length) {
25258
+ if (!status.equal_lengths) status.equal_lengths = [];
25259
+ for (const eq of param.stripped_length_equalities) if (!status.equal_lengths.some((e) => e.field === eq.field && (e.a === eq.a && e.b === eq.b || e.a === eq.b && e.b === eq.a))) status.equal_lengths.push(eq);
25260
+ }
24230
25261
  if (param.constraint) {
24231
25262
  const stripped = strip_length_equalities(param.constraint, param.name, status);
24232
25263
  if (stripped !== param.constraint) {
@@ -24289,6 +25320,15 @@ function check_function_node(func, status) {
24289
25320
  if (func.checked) return;
24290
25321
  func.checked = true;
24291
25322
  status.functions.push(func);
25323
+ const param_names = new Set(func.params.map((p) => p.name));
25324
+ for (const param of func.params) {
25325
+ if (!param.constraint) continue;
25326
+ const { constraint, equalities } = extract_length_equalities_at_registration(param.constraint, param.name, param_names);
25327
+ if (equalities.length) {
25328
+ param.constraint = constraint;
25329
+ param.stripped_length_equalities = equalities;
25330
+ }
25331
+ }
24292
25332
  if (is_generic_func(func)) {
24293
25333
  func.is_generic = true;
24294
25334
  return;
@@ -26905,7 +27945,7 @@ function check_if_else_node(if_else, status) {
26905
27945
  const else_returns = if_else.else_branch && block_always_returns(if_else.else_branch);
26906
27946
  const if_falls_through = if_reachable && !if_returns;
26907
27947
  const else_falls_through = else_reachable && (!has_else || !else_returns);
26908
- if (!has_else && else_reachable) apply_negated_bounds(if_else.condition, status);
27948
+ if (!has_else && else_reachable && !if_returns) apply_negated_bounds(if_else.condition, status);
26909
27949
  for (let [i, value] of status.values.entries()) {
26910
27950
  const contributing_sets = [];
26911
27951
  if (if_falls_through) contributing_sets.push(!!if_status.values[i].is_set);
@@ -26959,6 +27999,7 @@ function check_if_else_node(if_else, status) {
26959
27999
  }
26960
28000
  }
26961
28001
  }
28002
+ if (!has_else && if_returns && else_reachable) apply_negated_bounds(if_else.condition, status);
26962
28003
  if (if_else.if_branch && !if_else.else_branch) {
26963
28004
  const parent = status.stack.at(-1);
26964
28005
  if (parent && (parent.node_type === "declare" || parent.node_type === "assign")) add_error(status, "If expression must have an else branch", if_else.start);
@@ -27711,6 +28752,251 @@ function promote_allocations(node, status) {
27711
28752
  }
27712
28753
  }
27713
28754
  //#endregion
28755
+ //#region ../src/check/stamp_hidden_string_lens.ts
28756
+ /**
28757
+ * Post-check pass implementing the PERF gap 2.4 fix: thread a string's
28758
+ * length through the call boundary so a per-character helper doesn't pay one
28759
+ * `strlen` per CALL.
28760
+ *
28761
+ * For every function whose Nomen-level body reads `<param>.length` on a
28762
+ * by-value `string` param (and never rebinds, mutates, or escapes it), the
28763
+ * param is stamped `hidden_len`. The backends then lower it as TWO
28764
+ * parameters — the `char*` plus a trailing `long _<name>_len` companion
28765
+ * (mirroring the nullable `_has` companion) — and the body's `.length` reads
28766
+ * become loads of that companion instead of `strlen`. Call sites append the
28767
+ * companion argument, preferring a loop-invariant hoisted strlen temp
28768
+ * (scan_string_length_hoists treats such args like `.length` reads), so a
28769
+ * helper called once per line over a large document costs ONE strlen, not
28770
+ * O(lines).
28771
+ *
28772
+ * Conservative exclusions (any of these keeps the plain one-pointer ABI):
28773
+ * - raw blocks anywhere in the body (opaque code may touch the param), or no
28774
+ * body at all
28775
+ * - `#init`/`#destroy`/`init`/`destroy`/`main` and any non-identifier or
28776
+ * `_`/`#`-prefixed name (operators, compiler-generated helpers) — their
28777
+ * call sites use bespoke emission paths
28778
+ * - variadic params anywhere (their call sites pack hidden count/ptr pairs)
28779
+ * - `is_inline` functions (the aarch64 inline splicer)
28780
+ * - trait method declarations/default bodies, and struct methods that
28781
+ * implement a trait method (vtable dispatch uses the trait's declared
28782
+ * signature)
28783
+ * - functions referenced as VALUES anywhere in the program (`var func f =
28784
+ * helper`, `obj.method` as a value, nursery.spawn targets) — indirect
28785
+ * calls use the declared ABI
28786
+ * - params that are ref/mov/view/nullable/variadic/self, or that the body
28787
+ * assigns to, passes ref/mov/swap, mutates through a `var self`/`ref self`
28788
+ * string method, or references across a nested func/async boundary, or
28789
+ * whose companion name `_<name>_len` collides with any declared name in
28790
+ * the body
28791
+ */
28792
+ const NON_IDENTIFIERS = /* @__PURE__ */ new Set([
28793
+ "true",
28794
+ "false",
28795
+ "null",
28796
+ "self",
28797
+ "as",
28798
+ "default"
28799
+ ]);
28800
+ function is_plain_identifier(value) {
28801
+ if (!value) return false;
28802
+ if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(value)) return false;
28803
+ if (value.startsWith("_")) return false;
28804
+ return !NON_IDENTIFIERS.has(value);
28805
+ }
28806
+ function root_name(node) {
28807
+ if (!node) return void 0;
28808
+ if (node.node_type === "value") {
28809
+ const v = node.value;
28810
+ return is_plain_identifier(v) ? v : void 0;
28811
+ }
28812
+ if (node.node_type === "access") return root_name(node.target);
28813
+ }
28814
+ function collect_functions(root, status) {
28815
+ const out = [];
28816
+ const seen = /* @__PURE__ */ new Set();
28817
+ const add = (func, struct, is_trait_func) => {
28818
+ if (seen.has(func)) return;
28819
+ seen.add(func);
28820
+ out.push({
28821
+ func,
28822
+ struct,
28823
+ is_trait_func
28824
+ });
28825
+ };
28826
+ for (const struct of status.structs) for (const func of struct.functions) add(func, struct);
28827
+ for (const trait of status.traits) for (const func of trait.functions) add(func, void 0, true);
28828
+ for (const func of status.functions) add(func);
28829
+ walk_tree(root, (n) => {
28830
+ if (n.node_type === "struct") {
28831
+ const s = n;
28832
+ for (const func of s.functions) add(func, s);
28833
+ } else if (n.node_type === "trait") {
28834
+ const t = n;
28835
+ for (const func of t.functions) add(func, void 0, true);
28836
+ } else if (n.node_type === "func") add(n);
28837
+ });
28838
+ return out;
28839
+ }
28840
+ /** Names of functions referenced as VALUES (func-typed, non-call nodes). */
28841
+ function collect_value_referenced_names(root, status) {
28842
+ const excluded = /* @__PURE__ */ new Set();
28843
+ const visit = (n) => {
28844
+ if (n.type?.name !== "func") return;
28845
+ if (n.node_type === "value") {
28846
+ const v = n.value;
28847
+ if (is_plain_identifier(v)) excluded.add(v);
28848
+ } else if (n.node_type === "access_field") {
28849
+ const name = n.name;
28850
+ if (is_plain_identifier(name)) excluded.add(name);
28851
+ }
28852
+ };
28853
+ walk_tree(root, visit);
28854
+ for (const struct of status.structs) walk_tree(struct, visit);
28855
+ for (const trait of status.traits) walk_tree(trait, visit);
28856
+ for (const func of status.functions) walk_tree(func, visit);
28857
+ return excluded;
28858
+ }
28859
+ function walk_tree(node, visit) {
28860
+ if (!node || typeof node !== "object") return;
28861
+ if (Array.isArray(node)) {
28862
+ for (const item of node) walk_tree(item, visit);
28863
+ return;
28864
+ }
28865
+ const n = node;
28866
+ if (typeof n.node_type !== "string") return;
28867
+ visit(n);
28868
+ const record = n;
28869
+ for (const key of Object.keys(record)) {
28870
+ if (key === "parent" || key === "scope" || key === "node_type") continue;
28871
+ walk_tree(record[key], visit);
28872
+ }
28873
+ }
28874
+ function stamp_hidden_string_lens(root, status) {
28875
+ const value_referenced = collect_value_referenced_names(root, status);
28876
+ const owners = collect_functions(root, status);
28877
+ const string_struct = status.structs.find((s) => s.name === "string");
28878
+ for (const { func, struct, is_trait_func } of owners) {
28879
+ if (func.hidden_len_stamped) continue;
28880
+ func.hidden_len_stamped = true;
28881
+ if (!func.has_body || func.statements.length === 0) continue;
28882
+ if (func.is_inline || is_trait_func) continue;
28883
+ const bare_name = func.name.replace(/^#/, "");
28884
+ if (!is_plain_identifier(func.name)) continue;
28885
+ if ([
28886
+ "main",
28887
+ "init",
28888
+ "destroy"
28889
+ ].includes(bare_name)) continue;
28890
+ if (value_referenced.has(func.name)) continue;
28891
+ if (func.params.some((p) => p.is_variadic || p.is_variadic_tuple)) continue;
28892
+ if (struct && trait_declares_method(struct, func.name, status)) continue;
28893
+ const candidates = /* @__PURE__ */ new Set();
28894
+ for (const param of func.params) if (param_is_hidden_len_candidate(param)) candidates.add(param.name);
28895
+ if (candidates.size === 0) continue;
28896
+ const reads = /* @__PURE__ */ new Set();
28897
+ const invalidated = /* @__PURE__ */ new Set();
28898
+ const declared_names = /* @__PURE__ */ new Set();
28899
+ let has_raw = false;
28900
+ const visit_children = (n, in_boundary) => {
28901
+ const record = n;
28902
+ for (const key of Object.keys(record)) {
28903
+ if (key === "parent" || key === "scope" || key === "node_type") continue;
28904
+ const child = record[key];
28905
+ if (Array.isArray(child)) {
28906
+ for (const item of child) if (item && typeof item === "object" && item.node_type) visit(item, in_boundary);
28907
+ } else if (child && typeof child === "object" && child.node_type) visit(child, in_boundary);
28908
+ }
28909
+ };
28910
+ const visit = (n, in_boundary) => {
28911
+ switch (n.node_type) {
28912
+ case "raw":
28913
+ has_raw = true;
28914
+ return;
28915
+ case "func":
28916
+ case "async_block":
28917
+ visit_children(n, true);
28918
+ return;
28919
+ case "value": {
28920
+ const v = n.value;
28921
+ if (in_boundary && is_plain_identifier(v)) invalidated.add(v);
28922
+ return;
28923
+ }
28924
+ case "access": {
28925
+ const access = n;
28926
+ if (!in_boundary && access.access.node_type === "access_field" && access.access.name === "length" && access.target.node_type === "value") {
28927
+ const name = access.target.value;
28928
+ if (name !== void 0 && candidates.has(name)) reads.add(name);
28929
+ }
28930
+ if (!in_boundary && access.access.node_type === "access_func" && access.target.node_type === "value") {
28931
+ const name = access.target.value;
28932
+ const call = access.access;
28933
+ if (name !== void 0 && candidates.has(name)) {
28934
+ const self_param = (string_struct?.functions.find((f) => f.name === call.name || f.name === `#${call.name}`))?.params?.[0];
28935
+ if (self_param?.is_self_param && (self_param.declaration === "var" || self_param.is_ref)) invalidated.add(name);
28936
+ }
28937
+ }
28938
+ visit_children(n, in_boundary);
28939
+ return;
28940
+ }
28941
+ case "assign": {
28942
+ const assign = n;
28943
+ const lhs = root_name(assign.left_value);
28944
+ if (lhs) invalidated.add(lhs);
28945
+ const swap = root_name(assign.swap);
28946
+ if (swap) invalidated.add(swap);
28947
+ visit_children(n, in_boundary);
28948
+ return;
28949
+ }
28950
+ case "declare":
28951
+ case "param": {
28952
+ const name = n.name;
28953
+ if (name) declared_names.add(name);
28954
+ visit_children(n, in_boundary);
28955
+ return;
28956
+ }
28957
+ case "for": {
28958
+ const item = n.item;
28959
+ if (item?.value) declared_names.add(item.value);
28960
+ visit_children(n, in_boundary);
28961
+ return;
28962
+ }
28963
+ case "func_call":
28964
+ case "access_func": {
28965
+ const call = n;
28966
+ for (const indices of [call.ref_param_indices, call.mov_param_indices]) for (const i of indices ?? []) {
28967
+ const arg = root_name(call.params?.[i]);
28968
+ if (arg) invalidated.add(arg);
28969
+ }
28970
+ if (call.swap_params) {
28971
+ for (const [i, swap] of call.swap_params) for (const arg of [root_name(call.params?.[i]), root_name(swap)]) if (arg) invalidated.add(arg);
28972
+ }
28973
+ visit_children(n, in_boundary);
28974
+ return;
28975
+ }
28976
+ default:
28977
+ visit_children(n, in_boundary);
28978
+ return;
28979
+ }
28980
+ };
28981
+ for (const stmt of func.statements) visit(stmt, false);
28982
+ if (has_raw) continue;
28983
+ for (const name of candidates) {
28984
+ if (!reads.has(name)) continue;
28985
+ if (invalidated.has(name)) continue;
28986
+ if (declared_names.has(`_${name}_len`)) continue;
28987
+ const param = func.params.find((p) => p.name === name);
28988
+ param.hidden_len = true;
28989
+ }
28990
+ }
28991
+ }
28992
+ function trait_declares_method(struct, method_name, status) {
28993
+ for (const trait_name of struct.traits) if (status.traits.find((t) => t.name === trait_name)?.functions.some((f) => f.name === method_name)) return true;
28994
+ return false;
28995
+ }
28996
+ function param_is_hidden_len_candidate(param) {
28997
+ return !param.is_self_param && !param.is_variadic && !param.is_variadic_tuple && !param.is_ref && !param.is_moved && param.type.name === "string" && !param.type.is_array && !param.type.is_ref && !param.type.is_view && !param.type.is_nullable;
28998
+ }
28999
+ //#endregion
27714
29000
  //#region ../src/check/warnings.ts
27715
29001
  /**
27716
29002
  * Lint pass run after a successful check. It walks the AST to surface three
@@ -27913,6 +29199,7 @@ function check(root) {
27913
29199
  mutated_local_names: /* @__PURE__ */ new Set()
27914
29200
  };
27915
29201
  check_node(root, status);
29202
+ stamp_hidden_string_lens(root, status);
27916
29203
  if (!status.errors.length) emit_warnings(root, status);
27917
29204
  return {
27918
29205
  ok: !status.errors.length,