nomen-lang 0.0.16 → 0.0.18

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
@@ -4396,6 +4473,11 @@ function build_function_node$1(node, status) {
4396
4473
  first_pass_slot += 1;
4397
4474
  continue;
4398
4475
  }
4476
+ if (param.type.is_view) {
4477
+ first_pass_slot += 2;
4478
+ continue;
4479
+ }
4480
+ if (param.hidden_len) first_pass_slot++;
4399
4481
  const is_struct_type = !!status.structs.find((s) => s.name === param.type.name && !s.is_simple_type);
4400
4482
  const is_trait_type = !!status.traits.find((t) => t.name === param.type.name);
4401
4483
  const is_enum_with_data = !!status.enums.find((e) => e.name === param.type.name && e.has_associated_data);
@@ -4433,7 +4515,11 @@ function build_function_node$1(node, status) {
4433
4515
  status.ref_class_slots = /* @__PURE__ */ new Map();
4434
4516
  status.function_struct_param_slots = /* @__PURE__ */ new Set();
4435
4517
  const old_variadic_params_aarch64 = status.function_variadic_params;
4518
+ const old_view_params = status.function_view_params;
4436
4519
  status.function_variadic_params = /* @__PURE__ */ new Set();
4520
+ status.function_view_params = /* @__PURE__ */ new Set();
4521
+ const old_hidden_len_params = status.hidden_len_params;
4522
+ status.hidden_len_params = /* @__PURE__ */ new Set();
4437
4523
  status.moved_class_params = /* @__PURE__ */ new Map();
4438
4524
  let moved_param_save_slots = /* @__PURE__ */ new Map();
4439
4525
  if (has_body) {
@@ -4454,6 +4540,22 @@ function build_function_node$1(node, status) {
4454
4540
  }
4455
4541
  param_idx++;
4456
4542
  }
4543
+ if (param.type.is_view) {
4544
+ const offset = allocate_stack_space(status, 16, 16);
4545
+ status.stack_offsets.set(param.name, offset);
4546
+ status.function_view_params.add(param.name);
4547
+ for (const half of [0, 1]) {
4548
+ const p_slot = param_idx + half;
4549
+ if (p_slot < 8) status.code += `str ${param_regs[p_slot]}, [x29, #${offset + half * 8}]\n`;
4550
+ else {
4551
+ const k = p_slot - 8;
4552
+ status.code += `ldr x9, [x29, #${overflow_placeholder(node.name, k)}]\n`;
4553
+ status.code += `str x9, [x29, #${offset + half * 8}]\n`;
4554
+ }
4555
+ }
4556
+ param_idx += 2;
4557
+ continue;
4558
+ }
4457
4559
  if (callee_map.has(param.name)) {
4458
4560
  const reg = callee_map.get(param.name);
4459
4561
  status.function_param_regs.set(param.name, reg);
@@ -4487,6 +4589,19 @@ function build_function_node$1(node, status) {
4487
4589
  }
4488
4590
  }
4489
4591
  param_idx++;
4592
+ if (param.hidden_len) {
4593
+ const len_offset = allocate_stack_space(status, 8, 8);
4594
+ status.stack_offsets.set(`_${param.name}_len`, len_offset);
4595
+ if (!status.hidden_len_params) status.hidden_len_params = /* @__PURE__ */ new Set();
4596
+ status.hidden_len_params.add(param.name);
4597
+ if (param_idx < 8) status.code += `str ${param_regs[param_idx]}, [x29, #${len_offset}]\n`;
4598
+ else {
4599
+ const k = param_idx - 8;
4600
+ status.code += `ldr x9, [x29, #${overflow_placeholder(node.name, k)}]\n`;
4601
+ status.code += `str x9, [x29, #${len_offset}]\n`;
4602
+ }
4603
+ param_idx++;
4604
+ }
4490
4605
  if (param.declaration === "var") status.function_param_vars.add(param.name);
4491
4606
  if (param.type.is_array) {
4492
4607
  if (!param.is_variadic ? array_struct_name(param.type, status) : void 0) {
@@ -4502,6 +4617,7 @@ function build_function_node$1(node, status) {
4502
4617
  for (let i = 0; i < node.params.length; i++) {
4503
4618
  const param = node.params[i];
4504
4619
  if (param.is_variadic) pidx++;
4620
+ if (param.hidden_len) pidx++;
4505
4621
  if (param.is_moved) {
4506
4622
  if (!!status.structs.find((s) => s.name === param.type.name && s.is_class)) {
4507
4623
  const reg = callee_map.get(param.name) ?? param_regs[pidx];
@@ -4627,6 +4743,8 @@ function build_function_node$1(node, status) {
4627
4743
  status.ref_class_slots = old_ref_class_slots;
4628
4744
  status.function_struct_param_slots = old_struct_param_slots;
4629
4745
  status.function_variadic_params = old_variadic_params_aarch64;
4746
+ status.function_view_params = old_view_params;
4747
+ status.hidden_len_params = old_hidden_len_params;
4630
4748
  status.function_return_label = old_return_label;
4631
4749
  status.struct_return_buffer = void 0;
4632
4750
  status.return_buffer_stack_offset = void 0;
@@ -6314,6 +6432,108 @@ function build_swap_params(node, status) {
6314
6432
  }
6315
6433
  }
6316
6434
  //#endregion
6435
+ //#region ../src/build_aarch64/utils/view_value.ts
6436
+ /**
6437
+ * Resolve the struct name of an expression's value by walking the access
6438
+ * chain through the (monomorphized) struct table — bare names via
6439
+ * variable_types / scoped declarations / `self`, field accesses via the
6440
+ * base struct's field type, method calls via the method's return type.
6441
+ * Fallback for cached node types that lost their modifiers (generic bodies
6442
+ * re-checked after monomorphization can carry a stale plain `string`).
6443
+ */
6444
+ function value_struct_name$1(node, status) {
6445
+ if (node.node_type === "value") {
6446
+ const name = node.value;
6447
+ if (name === "self" && status.current_struct) return status.current_struct.name;
6448
+ const vt = status.variable_types?.get(name);
6449
+ if (vt?.name) return vt.name;
6450
+ const decl = status.scoped_declarations.findLast((d) => d.name === name);
6451
+ if (decl?.type?.name) return decl.type.name;
6452
+ return;
6453
+ }
6454
+ if (node.node_type === "access") {
6455
+ const inner = node.access;
6456
+ const base = value_struct_name$1(node.target, status);
6457
+ if (!base) return void 0;
6458
+ const struct = status.structs.find((s) => s.name === base && !s.is_generic);
6459
+ if (!struct) return void 0;
6460
+ if (inner.node_type === "access_field") return struct.fields.find((f) => f.name === inner.name)?.type?.name;
6461
+ if (inner.node_type === "access_func") return struct.functions.find((f) => f.name === inner.name)?.return_type?.name;
6462
+ }
6463
+ }
6464
+ /**
6465
+ * Whether an expression is a `view T` VALUE — a (ptr, len) pair that can be
6466
+ * passed to a `view T` parameter unchanged (loaded from the value's two
6467
+ * stack slots, or produced directly by a view-returning call). Recovers
6468
+ * `is_view` from the cached node type, the declared variable type, the
6469
+ * current function's view params, or (for method calls whose cached type
6470
+ * lost the modifier) the callee's declared return type.
6471
+ */
6472
+ function is_view_value$1(node, status) {
6473
+ if (type_from_value_node$1(node)?.is_view) return true;
6474
+ if (node.node_type === "value") {
6475
+ const name = node.value;
6476
+ if (status.variable_types?.get(name)?.is_view) return true;
6477
+ if (status.function_view_params?.has(name)) return true;
6478
+ }
6479
+ if (node.node_type === "access" && node.access.node_type === "access_func") {
6480
+ const access = node.access;
6481
+ const recv_struct = value_struct_name$1(node.target, status);
6482
+ 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;
6483
+ }
6484
+ return false;
6485
+ }
6486
+ /**
6487
+ * Emit the (ptr, len) pair for a `view string` argument into x0/x1.
6488
+ * - A view VALUE (a view local/param's two stack slots, or a view-returning
6489
+ * call that leaves the pair in x0/x1) passes through unchanged.
6490
+ * - An owned string expression builds its pointer into x0, then measures it
6491
+ * with strlen — an implicit owned→view borrow; ownership stays with the
6492
+ * caller.
6493
+ */
6494
+ function emit_view_string_arg(arg, status) {
6495
+ if (is_view_value$1(arg, status)) {
6496
+ if (arg.node_type === "value") {
6497
+ const base = status.stack_offsets?.get(arg.value);
6498
+ if (base !== void 0) {
6499
+ status.code += `ldr x0, [x29, #${base}]\n`;
6500
+ status.code += `ldr x1, [x29, #${base + 8}]\n`;
6501
+ return;
6502
+ }
6503
+ }
6504
+ build_node$1(arg, status);
6505
+ if (!status.code.endsWith("\n")) status.code += "\n";
6506
+ return;
6507
+ }
6508
+ build_node$1(arg, status);
6509
+ if (!status.code.endsWith("\n")) status.code += "\n";
6510
+ status.code += `str x0, [sp, #-16]!\n`;
6511
+ status.code += `bl _strlen\n`;
6512
+ status.code += `mov x1, x0\n`;
6513
+ status.code += `ldr x0, [sp], #16\n`;
6514
+ }
6515
+ /**
6516
+ * Materialize a `view string` into an OWNED heap string. The (ptr, len)
6517
+ * pair must be in x0/x1 on entry; on exit x0 holds a malloc'd, null-
6518
+ * terminated copy of exactly len bytes (and the caller must reclaim it —
6519
+ * set last_result_is_heap / mark the target as a heap string). Mirrors the
6520
+ * view `.to_string` builtin sequence.
6521
+ */
6522
+ function emit_view_materialize_owned(status) {
6523
+ status.code += `stp x0, x1, [sp, #-16]!\n`;
6524
+ status.code += `add x0, x1, #1\n`;
6525
+ emit_malloc(status);
6526
+ status.code += `str x0, [sp, #-16]!\n`;
6527
+ status.code += `ldr x1, [sp, #16]\n`;
6528
+ status.code += `ldr x2, [sp, #24]\n`;
6529
+ status.code += `ldr x0, [sp]\n`;
6530
+ status.code += `bl _memcpy\n`;
6531
+ status.code += `ldr x0, [sp]\n`;
6532
+ status.code += `ldr x1, [sp, #24]\n`;
6533
+ status.code += `strb wzr, [x0, x1]\n`;
6534
+ status.code += `add sp, sp, #32\n`;
6535
+ }
6536
+ //#endregion
6317
6537
  //#region ../src/build_aarch64/build_declaration_node.ts
6318
6538
  /**
6319
6539
  * Pass a struct-typed param by address. For ValueNode (variable references)
@@ -6476,10 +6696,13 @@ function build_constructor_params(fc, param_regs, status) {
6476
6696
  const elem_struct = status.structs.find((s) => s.name === elem_type_name && !s.is_simple_type);
6477
6697
  const elem_size = elem_struct ? get_struct_size(elem_type_name, status) : 8;
6478
6698
  const count = arr.values.length;
6479
- const arr_offset = allocate_stack_space(status, Math.max(count, 1) * elem_size, 16);
6699
+ const arr_offset = allocate_stack_space(status, 8 + Math.max(count, 1) * elem_size, 16);
6700
+ const data_base = arr_offset + 8;
6701
+ status.code += `mov x0, #${count}\n`;
6702
+ status.code += `str x0, [x29, #${arr_offset}]\n`;
6480
6703
  for (let j = count - 1; j >= 0; j--) {
6481
6704
  const arg = arr.values[j];
6482
- const slot_offset = arr_offset + j * elem_size;
6705
+ const slot_offset = data_base + j * elem_size;
6483
6706
  if (elem_struct && arg.node_type === "func_call") {
6484
6707
  const tfc = arg;
6485
6708
  const fc_param_regs = [
@@ -6539,7 +6762,7 @@ function build_constructor_params(fc, param_regs, status) {
6539
6762
  emit_int_immediate(status, String(count));
6540
6763
  if (!status.code.endsWith("\n")) status.code += "\n";
6541
6764
  status.code += `str x0, [x29, #${count_slot}]\n`;
6542
- status.code += `add x0, x29, #${arr_offset}\n`;
6765
+ status.code += `add x0, x29, #${data_base}\n`;
6543
6766
  status.code += `str x0, [x29, #${ptr_slot}]\n`;
6544
6767
  const reg_slots = Math.min(total_slots, 7);
6545
6768
  for (let i = 0; i < reg_slots; i++) status.code += `ldr ${param_regs[i]}, [x29, #${slots_base + i * 8}]\n`;
@@ -7414,10 +7637,18 @@ function build_declaration_node$1(node, status) {
7414
7637
  const value_node = node.value;
7415
7638
  const raw = get_raw_value$1(value_node);
7416
7639
  const is_literal = /^(\+|-)?\d+(\.\d+)?$/.test(raw) || raw.startsWith("\"") || raw === "true" || raw === "false";
7417
- if (!!status.function_return_label) {
7640
+ const use_stack = !!status.function_return_label;
7641
+ const is_heap_alias = node.type.name === "string" && !is_literal && status.heap_strings?.has(raw);
7642
+ if (use_stack) {
7418
7643
  const offset = allocate_stack_space(status, size, size);
7419
7644
  status.stack_offsets.set(node.name, offset);
7420
- if (node.type.name === "string" && !is_literal && status.heap_strings?.has(raw)) {
7645
+ if (node.type.name === "string" && !node.type.is_view && is_view_value$1(node.value, status)) {
7646
+ emit_view_string_arg(node.value, status);
7647
+ emit_view_materialize_owned(status);
7648
+ status.code += `str x0, [x29, #${offset}]\n`;
7649
+ mark_heap_string(status, node.name);
7650
+ status.last_result_is_heap = false;
7651
+ } else if (is_heap_alias) {
7421
7652
  emit_var_load(status, "x0", raw, 8);
7422
7653
  emit_strdup(status);
7423
7654
  status.code += `str x0, [x29, #${offset}]\n`;
@@ -7570,6 +7801,14 @@ function build_declaration_node$1(node, status) {
7570
7801
  const offset = allocate_stack_space(status, size, size);
7571
7802
  status.stack_offsets.set(node.name, offset);
7572
7803
  } else emit_data(status, `${node.name}: .space ${size}\n`);
7804
+ if (node.type.name === "string" && !node.type.is_view && is_view_value$1(node.value, status)) {
7805
+ emit_view_string_arg(node.value, status);
7806
+ emit_view_materialize_owned(status);
7807
+ emit_var_store(status, "x0", node.name, size);
7808
+ mark_heap_string(status, node.name);
7809
+ status.last_result_is_heap = false;
7810
+ return;
7811
+ }
7573
7812
  build_node$1(node.value, status);
7574
7813
  emit_var_store(status, "x0", node.name, size);
7575
7814
  check_heap();
@@ -8292,10 +8531,13 @@ function build_function_call_node$1(node, status) {
8292
8531
  const elem_type_name = arr.type.name || "int";
8293
8532
  const elem_struct = status.structs.find((s) => s.name === elem_type_name && !s.is_simple_type);
8294
8533
  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);
8534
+ const arr_offset = allocate_stack_space(status, 8 + Math.max(arr.values.length, 1) * elem_size, 16);
8535
+ const data_base = arr_offset + 8;
8536
+ status.code += `mov x0, #${arr.values.length}\n`;
8537
+ status.code += `str x0, [x29, #${arr_offset}]\n`;
8296
8538
  for (let j = arr.values.length - 1; j >= 0; j--) {
8297
8539
  const arg = arr.values[j];
8298
- const slot_offset = arr_offset + j * elem_size;
8540
+ const slot_offset = data_base + j * elem_size;
8299
8541
  if (elem_struct && arg.node_type === "func_call") {
8300
8542
  const fc = arg;
8301
8543
  const fc_param_regs = [
@@ -8338,16 +8580,29 @@ function build_function_call_node$1(node, status) {
8338
8580
  non_variadic.push({
8339
8581
  node: param,
8340
8582
  is_struct: is_struct_type$2(param_type, status) || is_enum_with_data_type$1(param_type, status),
8341
- is_ref: !!is_ref_param
8583
+ is_ref: !!is_ref_param,
8584
+ is_view: !!node.view_param_indices?.includes(i)
8342
8585
  });
8343
8586
  }
8344
8587
  const nv = non_variadic.length;
8345
- const total_slots = nv + 2;
8588
+ const nv_slot = [];
8589
+ let nv_total = 0;
8590
+ for (let i = 0; i < nv; i++) {
8591
+ nv_slot.push(nv_total);
8592
+ nv_total += non_variadic[i].is_view ? 2 : 1;
8593
+ }
8594
+ const total_slots = nv_total + 2;
8346
8595
  const slots_base = allocate_stack_space(status, total_slots * 8, 16);
8347
- const count_slot = slots_base + nv * 8;
8596
+ const count_slot = slots_base + nv_total * 8;
8348
8597
  const ptr_slot = count_slot + 8;
8349
8598
  for (let i = nv - 1; i >= 0; i--) {
8350
8599
  const ep = non_variadic[i];
8600
+ if (ep.is_view) {
8601
+ emit_view_string_arg(ep.node, status);
8602
+ status.code += `str x0, [x29, #${slots_base + nv_slot[i] * 8}]\n`;
8603
+ status.code += `str x1, [x29, #${slots_base + (nv_slot[i] + 1) * 8}]\n`;
8604
+ continue;
8605
+ }
8351
8606
  if (ep.is_struct) emit_struct_address(ep.node, status);
8352
8607
  else if (ep.is_ref) {
8353
8608
  const ep_name = ep.node.node_type === "value" ? ep.node.value : void 0;
@@ -8356,11 +8611,11 @@ function build_function_call_node$1(node, status) {
8356
8611
  else emit_address_of(ep.node, status);
8357
8612
  } else build_node$1(ep.node, status);
8358
8613
  if (!status.code.endsWith("\n")) status.code += "\n";
8359
- status.code += `str x0, [x29, #${slots_base + i * 8}]\n`;
8614
+ status.code += `str x0, [x29, #${slots_base + nv_slot[i] * 8}]\n`;
8360
8615
  }
8361
8616
  status.code += `mov x0, #${arr.values.length}\n`;
8362
8617
  status.code += `str x0, [x29, #${count_slot}]\n`;
8363
- status.code += `add x0, x29, #${arr_offset}\n`;
8618
+ status.code += `add x0, x29, #${data_base}\n`;
8364
8619
  status.code += `str x0, [x29, #${ptr_slot}]\n`;
8365
8620
  for (let s = 0; s < total_slots; s++) {
8366
8621
  const slot = start_reg + s;
@@ -8381,13 +8636,33 @@ function build_function_call_node$1(node, status) {
8381
8636
  }
8382
8637
  }
8383
8638
  } else {
8384
- const has_args = node.params.length > 0;
8639
+ const view_arg_set = new Set(node.view_param_indices ?? []);
8640
+ const hidden_len_indices = callee_hidden_len_indices(node);
8641
+ const hidden_len_set = new Set(hidden_len_indices);
8642
+ const hidden_len_slots = /* @__PURE__ */ new Map();
8643
+ const arg_slot = [];
8644
+ let total_slots = 0;
8645
+ for (let i = 0; i < node.params.length; i++) {
8646
+ arg_slot.push(total_slots);
8647
+ total_slots += view_arg_set.has(i) ? 2 : 1;
8648
+ if (hidden_len_set.has(i)) {
8649
+ hidden_len_slots.set(i, total_slots);
8650
+ total_slots += 1;
8651
+ }
8652
+ }
8653
+ const has_args = total_slots > 0;
8385
8654
  let args_base = 0;
8386
- if (has_args) args_base = allocate_stack_space(status, node.params.length * 8, 16);
8655
+ if (has_args) args_base = allocate_stack_space(status, total_slots * 8, 16);
8387
8656
  for (let i = node.params.length - 1; i >= 0; i--) {
8388
8657
  const param = node.params[i];
8389
8658
  const param_type = param.type?.name || "";
8390
8659
  const is_ref_param = node.ref_param_indices?.includes(i);
8660
+ if (view_arg_set.has(i)) {
8661
+ emit_view_string_arg(param, status);
8662
+ status.code += `str x0, [x29, #${args_base + arg_slot[i] * 8}]\n`;
8663
+ status.code += `str x1, [x29, #${args_base + (arg_slot[i] + 1) * 8}]\n`;
8664
+ continue;
8665
+ }
8391
8666
  const arr_arg_name = param.node_type === "value" ? param.value : void 0;
8392
8667
  if (!!arr_arg_name && !!status.heap_array_vars?.has(arr_arg_name)) build_node$1(param, status);
8393
8668
  else if (param.node_type === "array" && param_type) {
@@ -8445,19 +8720,35 @@ function build_function_call_node$1(node, status) {
8445
8720
  } 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
8721
  else build_node$1(node.params[i], status);
8447
8722
  if (!status.code.endsWith("\n")) status.code += "\n";
8448
- status.code += `str x0, [x29, #${args_base + i * 8}]\n`;
8723
+ status.code += `str x0, [x29, #${args_base + arg_slot[i] * 8}]\n`;
8724
+ }
8725
+ for (const i of hidden_len_indices) {
8726
+ const arg = node.params[i];
8727
+ let computed = false;
8728
+ if (arg.node_type === "value") {
8729
+ const hoisted = status.string_length_slots?.get(arg.value);
8730
+ if (hoisted !== void 0) {
8731
+ status.code += `ldr x0, [x29, #${hoisted}]\n`;
8732
+ computed = true;
8733
+ }
8734
+ }
8735
+ if (!computed) {
8736
+ status.code += `ldr x0, [x29, #${args_base + arg_slot[i] * 8}]\n`;
8737
+ status.code += `bl _strlen\n`;
8738
+ }
8739
+ status.code += `str x0, [x29, #${args_base + hidden_len_slots.get(i) * 8}]\n`;
8449
8740
  }
8450
8741
  if (has_args) {
8451
- for (let i = 0; i < node.params.length; i++) {
8452
- const slot = start_reg + i;
8742
+ for (let s = 0; s < total_slots; s++) {
8743
+ const slot = start_reg + s;
8453
8744
  if (slot >= 8) continue;
8454
8745
  const reg = param_regs[slot];
8455
8746
  if (reg === "x0") continue;
8456
- status.code += `ldr ${reg}, [x29, #${args_base + i * 8}]\n`;
8747
+ status.code += `ldr ${reg}, [x29, #${args_base + s * 8}]\n`;
8457
8748
  }
8458
8749
  if (!is_struct) status.code += `ldr x0, [x29, #${args_base}]\n`;
8459
8750
  }
8460
- overflow_count = Math.max(0, node.params.length - (8 - start_reg));
8751
+ overflow_count = Math.max(0, total_slots - (8 - start_reg));
8461
8752
  if (overflow_count > 0) {
8462
8753
  outgoing_size = Math.ceil(overflow_count * 8 / 16) * 16;
8463
8754
  status.code += `sub sp, sp, #${outgoing_size}\n`;
@@ -8769,9 +9060,13 @@ function static_enum_case(operand, enum_node, status) {
8769
9060
  //#region ../src/build_aarch64/build_operation_node.ts
8770
9061
  let string_counter$1 = 0;
8771
9062
  let coalesce_counter = 0;
9063
+ let view_cmp_counter = 0;
9064
+ let sc_counter = 0;
8772
9065
  function reset_string_counter$1() {
8773
9066
  string_counter$1 = 0;
8774
9067
  coalesce_counter = 0;
9068
+ view_cmp_counter = 0;
9069
+ sc_counter = 0;
8775
9070
  }
8776
9071
  function is_comparison(op) {
8777
9072
  return [
@@ -9052,6 +9347,29 @@ function build_operation_node$1(node, status) {
9052
9347
  status.code += `cset x0, ${node.op === "==" ? "eq" : "ne"}\n`;
9053
9348
  return;
9054
9349
  }
9350
+ 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))) {
9351
+ const id = view_cmp_counter++;
9352
+ const len_ne_label = `.Lview_cmp_len_ne_${id}`;
9353
+ const done_label = `.Lview_cmp_done_${id}`;
9354
+ emit_view_string_arg(node.right_value, status);
9355
+ status.code += `stp x0, x1, [sp, #-16]!\n`;
9356
+ emit_view_string_arg(node.left_value, status);
9357
+ status.code += `ldr x2, [sp, #8]\n`;
9358
+ status.code += `cmp x1, x2\n`;
9359
+ status.code += `b.ne ${len_ne_label}\n`;
9360
+ status.code += `mov x2, x1\n`;
9361
+ status.code += `ldr x1, [sp]\n`;
9362
+ status.code += `bl _memcmp\n`;
9363
+ status.code += `cmp x0, #0\n`;
9364
+ status.code += `cset x0, ${node.op === "==" ? "eq" : "ne"}\n`;
9365
+ status.code += `b ${done_label}\n`;
9366
+ status.code += `${len_ne_label}:\n`;
9367
+ status.code += `mov x0, #0\n`;
9368
+ if (node.op === "!=") status.code += `mov x0, #1\n`;
9369
+ status.code += `${done_label}:\n`;
9370
+ status.code += `add sp, sp, #16\n`;
9371
+ return;
9372
+ }
9055
9373
  if (node.operator_func) {
9056
9374
  const left_type = type_from_value_node$1(node.left_value);
9057
9375
  const is_array_op = node.operator_func.struct_name.startsWith("Array") && left_type.is_array;
@@ -9185,6 +9503,25 @@ function build_operation_node$1(node, status) {
9185
9503
  else status.code += `fmov x0, d0\n`;
9186
9504
  return;
9187
9505
  }
9506
+ if (node.op === "&&" || node.op === "||") {
9507
+ const id = sc_counter++;
9508
+ const skip_right = `.Lsc_skip_${id}`;
9509
+ const done = `.Lsc_done_${id}`;
9510
+ build_operand$1(node.left_value, "x0", status);
9511
+ if (!status.code.endsWith("\n")) status.code += "\n";
9512
+ status.code += `cmp x0, #0\n`;
9513
+ status.code += node.op === "&&" ? `b.eq ${skip_right}\n` : `b.ne ${skip_right}\n`;
9514
+ build_operand$1(node.right_value, "x0", status);
9515
+ if (!status.code.endsWith("\n")) status.code += "\n";
9516
+ status.code += `cmp x0, #0\n`;
9517
+ status.code += `cset x0, ne\n`;
9518
+ status.code += `b ${done}\n`;
9519
+ status.code += `${skip_right}:\n`;
9520
+ if (node.op === "&&") status.code += `mov x0, #0\n`;
9521
+ else status.code += `mov x0, #1\n`;
9522
+ status.code += `${done}:\n`;
9523
+ return;
9524
+ }
9188
9525
  const need_spill = !is_simple(node.left_value);
9189
9526
  build_operand$1(node.right_value, "x2", status);
9190
9527
  if (need_spill) {
@@ -9195,19 +9532,7 @@ function build_operation_node$1(node, status) {
9195
9532
  if (!status.code.endsWith("\n")) status.code += "\n";
9196
9533
  if (need_spill) status.code += `ldr x2, [sp], #16\n`;
9197
9534
  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)) {
9535
+ if (is_comparison(node.op)) {
9211
9536
  status.code += `cmp x1, x2\n`;
9212
9537
  status.code += `cset x0, ${map_cmp(node.op, unsigned)}\n`;
9213
9538
  } else {
@@ -9447,6 +9772,12 @@ function build_return_node$1(node, status) {
9447
9772
  if (override_ctor) status.struct_return_buffer = saved_buffer;
9448
9773
  }
9449
9774
  if (!status.code.endsWith("\n")) status.code += "\n";
9775
+ if (status.function_return_type?.is_view && status.function_return_type.name === "string" && node.value && !is_view_value$1(node.value, status)) {
9776
+ status.code += `str x0, [sp, #-16]!\n`;
9777
+ status.code += `bl _strlen\n`;
9778
+ status.code += `mov x1, x0\n`;
9779
+ status.code += `ldr x0, [sp], #16\n`;
9780
+ }
9450
9781
  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
9782
  const fn_name = status.current_function_name;
9452
9783
  const mangled = status.current_struct ? `${status.current_struct.name}_${fn_name}` : fn_name;
@@ -9767,7 +10098,7 @@ function build_value_node$1(node, status) {
9767
10098
  }
9768
10099
  //#endregion
9769
10100
  //#region ../src/build_common/scan_string_length_hoists.ts
9770
- const NON_IDENTIFIERS = /* @__PURE__ */ new Set([
10101
+ const NON_IDENTIFIERS$1 = /* @__PURE__ */ new Set([
9771
10102
  "true",
9772
10103
  "false",
9773
10104
  "null",
@@ -9776,12 +10107,12 @@ const NON_IDENTIFIERS = /* @__PURE__ */ new Set([
9776
10107
  "default"
9777
10108
  ]);
9778
10109
  function is_identifier(value) {
9779
- return !!value && !NON_IDENTIFIERS.has(value) && !/^(\+|-)?\d+(\.\d+)?$/.test(value) && !value.startsWith("\"") && !value.startsWith("'");
10110
+ return !!value && !NON_IDENTIFIERS$1.has(value) && !/^(\+|-)?\d+(\.\d+)?$/.test(value) && !value.startsWith("\"") && !value.startsWith("'");
9780
10111
  }
9781
- function root_name(node) {
10112
+ function root_name$1(node) {
9782
10113
  if (!node) return void 0;
9783
10114
  if (node.node_type === "value") return node.value;
9784
- if (node.node_type === "access") return root_name(node.target);
10115
+ if (node.node_type === "access") return root_name$1(node.target);
9785
10116
  }
9786
10117
  /**
9787
10118
  * Find the bare `string` variables whose `.length` is read inside a while
@@ -9868,9 +10199,9 @@ function scan_string_length_hoists(condition, statements, update, status) {
9868
10199
  }
9869
10200
  case "assign": {
9870
10201
  const assign = n;
9871
- const lhs = root_name(assign.left_value);
10202
+ const lhs = root_name$1(assign.left_value);
9872
10203
  if (lhs) invalidated.add(lhs);
9873
- const swap = root_name(assign.swap);
10204
+ const swap = root_name$1(assign.swap);
9874
10205
  if (swap) invalidated.add(swap);
9875
10206
  walk_children(n, in_boundary);
9876
10207
  return;
@@ -9892,11 +10223,20 @@ function scan_string_length_hoists(condition, statements, update, status) {
9892
10223
  case "access_func": {
9893
10224
  const call = n;
9894
10225
  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]);
10226
+ const arg = root_name$1(call.params?.[i]);
9896
10227
  if (arg) invalidated.add(arg);
9897
10228
  }
9898
10229
  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);
10230
+ 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);
10231
+ }
10232
+ if (!in_boundary && call.resolved_function) {
10233
+ const self_offset = call.resolved_function.params[0]?.is_self_param ? 1 : 0;
10234
+ const params = call.params ?? [];
10235
+ for (let i = 0; i < params.length; i++) {
10236
+ if (!call.resolved_function.params[i + self_offset]?.hidden_len) continue;
10237
+ const arg = params[i];
10238
+ if (arg.node_type === "value" && is_identifier(arg.value) && is_string_target(arg)) candidates.set(arg.value, arg);
10239
+ }
9900
10240
  }
9901
10241
  walk_children(n, in_boundary);
9902
10242
  return;
@@ -10073,6 +10413,7 @@ function build_while_loop_node$1(node, status) {
10073
10413
  if (!status.string_length_slots) status.string_length_slots = /* @__PURE__ */ new Map();
10074
10414
  for (const [name, target] of hoists) {
10075
10415
  if (status.string_length_slots.has(name)) continue;
10416
+ if (status.hidden_len_params?.has(name)) continue;
10076
10417
  const offset = allocate_stack_space(status, 8);
10077
10418
  status.string_length_slots.set(name, offset);
10078
10419
  build_node$1(target, status);
@@ -10317,6 +10658,10 @@ function emit_owning_buffer_inline_aarch64(struct_node, func_name, status) {
10317
10658
  emit_owning_replace_T(elem, status);
10318
10659
  return true;
10319
10660
  }
10661
+ if (func_name === "shift_T") {
10662
+ emit_owning_shift_T(elem, status, "x0");
10663
+ return true;
10664
+ }
10320
10665
  return false;
10321
10666
  }
10322
10667
  if (owning_buffer_is_string_elem_aarch64(struct_node)) {
@@ -10328,6 +10673,10 @@ function emit_owning_buffer_inline_aarch64(struct_node, func_name, status) {
10328
10673
  emit_string_replace_T(status, "x0");
10329
10674
  return true;
10330
10675
  }
10676
+ if (func_name === "shift_T") {
10677
+ emit_string_shift_T(status, "x0");
10678
+ return true;
10679
+ }
10331
10680
  }
10332
10681
  return false;
10333
10682
  }
@@ -10341,7 +10690,7 @@ function emit_owning_buffer_inline_aarch64(struct_node, func_name, status) {
10341
10690
  function emit_owning_buffer_standalone_aarch64(node, func_name, status) {
10342
10691
  const elem = owning_buffer_element_aarch64(node, status);
10343
10692
  if (elem) {
10344
- if (func_name !== "store_T" && func_name !== "replace_T") return false;
10693
+ if (func_name !== "store_T" && func_name !== "replace_T" && func_name !== "shift_T") return false;
10345
10694
  emit_owning_standalone_struct(elem, func_name, status);
10346
10695
  return true;
10347
10696
  }
@@ -10354,12 +10703,20 @@ function emit_owning_buffer_standalone_aarch64(node, func_name, status) {
10354
10703
  emit_string_replace_T(status, "x19");
10355
10704
  return true;
10356
10705
  }
10706
+ if (func_name === "shift_T") {
10707
+ emit_string_shift_T(status, "x19");
10708
+ return true;
10709
+ }
10357
10710
  }
10358
10711
  return false;
10359
10712
  }
10360
10713
  function emit_owning_standalone_struct(elem, func_name, status) {
10361
10714
  const T_SIZE = get_struct_size(elem.name, status);
10362
10715
  const string_fields = collect_string_fields(elem, status);
10716
+ if (func_name === "shift_T") {
10717
+ emit_owning_shift_T(elem, status, "x19");
10718
+ return true;
10719
+ }
10363
10720
  status.code += `stp x20, x21, [sp, #-16]!\n`;
10364
10721
  status.code += `str x22, [sp, #-16]!\n`;
10365
10722
  status.code += `ldr x20, [x19, #8]\n`;
@@ -10459,6 +10816,59 @@ function emit_string_replace_T(status, self_reg) {
10459
10816
  status.code += `${done}:\n`;
10460
10817
  status.code += `ldp x20, x21, [sp], #16\n`;
10461
10818
  }
10819
+ /**
10820
+ * Specialized `Buffer<string>` shift_T. `self_reg` is the register holding
10821
+ * the Buffer pointer ("x0" inline, "x19" standalone); x1 = dst, x2 = src.
10822
+ * Move slot src into slot dst — free dst's heap copy, take over src's
10823
+ * pointer, zero src. Exactly one slot owns the string afterwards.
10824
+ */
10825
+ function emit_string_shift_T(status, self_reg) {
10826
+ const done = `.Lstr_sh_done_${status.label_counter = (status.label_counter ?? 0) + 1}`;
10827
+ status.code += `stp x20, x21, [sp, #-16]!\n`;
10828
+ status.code += `ldr x9, [${self_reg}, #8]\n`;
10829
+ status.code += `add x20, x9, x1, lsl #3\n`;
10830
+ status.code += `add x21, x9, x2, lsl #3\n`;
10831
+ status.code += `cmp x20, x21\n`;
10832
+ status.code += `b.eq ${done}\n`;
10833
+ status.code += `ldr x0, [x20]\n`;
10834
+ emit_free(status);
10835
+ status.code += `ldr x9, [x21]\n`;
10836
+ status.code += `str x9, [x20]\n`;
10837
+ status.code += `str xzr, [x21]\n`;
10838
+ status.code += `${done}:\n`;
10839
+ status.code += `ldp x20, x21, [sp], #16\n`;
10840
+ }
10841
+ /**
10842
+ * Specialized owning-value-struct shift_T. `self_reg` holds the Buffer
10843
+ * pointer ("x0" inline, "x19" standalone); x1 = dst, x2 = src (slot indices).
10844
+ * Destroy dst's old value, copy src's bytes over it wholesale (the moved
10845
+ * struct's owned pointers transfer — no deep copy), zero src.
10846
+ */
10847
+ function emit_owning_shift_T(elem, status, self_reg) {
10848
+ const T_SIZE = get_struct_size(elem.name, status);
10849
+ const done = `.Lown_sh_done_${status.label_counter = (status.label_counter ?? 0) + 1}`;
10850
+ status.code += `stp x20, x21, [sp, #-16]!\n`;
10851
+ status.code += `str x22, [sp, #-16]!\n`;
10852
+ status.code += `ldr x9, [${self_reg}, #8]\n`;
10853
+ status.code += `mov x22, #${T_SIZE}\n`;
10854
+ status.code += `madd x20, x1, x22, x9\n`;
10855
+ status.code += `madd x21, x2, x22, x9\n`;
10856
+ status.code += `cmp x20, x21\n`;
10857
+ status.code += `b.eq ${done}\n`;
10858
+ status.code += `mov x0, x20\n`;
10859
+ status.code += `bl ${elem.name}_destroy\n`;
10860
+ status.code += `mov x0, x20\n`;
10861
+ status.code += `mov x1, x21\n`;
10862
+ status.code += `mov x2, x22\n`;
10863
+ status.code += `bl _memcpy\n`;
10864
+ status.code += `mov x0, x21\n`;
10865
+ status.code += `mov x1, #0\n`;
10866
+ status.code += `mov x2, x22\n`;
10867
+ status.code += `bl _memset\n`;
10868
+ status.code += `${done}:\n`;
10869
+ status.code += `ldr x22, [sp], #16\n`;
10870
+ status.code += `ldp x20, x21, [sp], #16\n`;
10871
+ }
10462
10872
  function emit_owning_store_T(elem, status) {
10463
10873
  const T_SIZE = get_struct_size(elem.name, status);
10464
10874
  const string_fields = collect_string_fields(elem, status);
@@ -11192,10 +11602,14 @@ function build_struct_functions$1(node, status) {
11192
11602
  if (needs_x19) callee_idx = 1;
11193
11603
  const old_ref_params = status.function_ref_params;
11194
11604
  const old_ref_class_slots = status.ref_class_slots;
11605
+ const old_view_params = status.function_view_params;
11606
+ const old_hidden_len_params = status.hidden_len_params;
11195
11607
  status.function_param_regs = /* @__PURE__ */ new Map();
11196
11608
  status.function_param_vars = /* @__PURE__ */ new Set();
11197
11609
  status.function_ref_params = /* @__PURE__ */ new Set();
11198
11610
  status.ref_class_slots = /* @__PURE__ */ new Map();
11611
+ status.function_view_params = /* @__PURE__ */ new Set();
11612
+ status.hidden_len_params = /* @__PURE__ */ new Set();
11199
11613
  status.struct_return_buffer = void 0;
11200
11614
  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
11615
  let return_buffer_stack_offset;
@@ -11218,6 +11632,10 @@ function build_struct_functions$1(node, status) {
11218
11632
  if (param.type.is_ref) status.function_ref_params.add(param.name);
11219
11633
  continue;
11220
11634
  }
11635
+ if (param.type.is_view) {
11636
+ slot_idx += 2;
11637
+ continue;
11638
+ }
11221
11639
  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
11640
  const saved_reg = callee_saved[callee_idx++];
11223
11641
  if (saved_reg !== "x19" || !needs_x19) status.code += `str ${saved_reg}, [sp, #-16]!\n`;
@@ -11235,6 +11653,7 @@ function build_struct_functions$1(node, status) {
11235
11653
  if (!status.class_vars) status.class_vars = /* @__PURE__ */ new Set();
11236
11654
  status.class_vars.add(param.name);
11237
11655
  }
11656
+ if (param.hidden_len) slot_idx++;
11238
11657
  slot_idx++;
11239
11658
  }
11240
11659
  status.code += `sub sp, sp, #${stack_placeholder}\n`;
@@ -11255,6 +11674,22 @@ function build_struct_functions$1(node, status) {
11255
11674
  second_slot_idx += 2;
11256
11675
  continue;
11257
11676
  }
11677
+ if (param.type.is_view) {
11678
+ const offset = allocate_stack_space(status, 16, 16);
11679
+ status.stack_offsets.set(param.name, offset);
11680
+ status.function_view_params.add(param.name);
11681
+ for (const half of [0, 1]) {
11682
+ const p_slot = second_slot_idx + half;
11683
+ if (p_slot < 8) status.code += `str ${param_regs[p_slot]}, [x29, #${offset + half * 8}]\n`;
11684
+ else {
11685
+ const k = p_slot - 8;
11686
+ status.code += `ldr x9, [x29, #${overflow_placeholder(func_label, k)}]\n`;
11687
+ status.code += `str x9, [x29, #${offset + half * 8}]\n`;
11688
+ }
11689
+ }
11690
+ second_slot_idx += 2;
11691
+ continue;
11692
+ }
11258
11693
  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
11694
  const size = aarch64_size(param.type.name);
11260
11695
  const offset = allocate_stack_space(status, size, size);
@@ -11285,6 +11720,20 @@ function build_struct_functions$1(node, status) {
11285
11720
  }
11286
11721
  }
11287
11722
  }
11723
+ if (param.hidden_len) {
11724
+ const len_offset = allocate_stack_space(status, 8, 8);
11725
+ status.stack_offsets.set(`_${param.name}_len`, len_offset);
11726
+ if (!status.hidden_len_params) status.hidden_len_params = /* @__PURE__ */ new Set();
11727
+ status.hidden_len_params.add(param.name);
11728
+ const len_slot = second_slot_idx + 1;
11729
+ if (len_slot < 8) status.code += `str ${param_regs[len_slot]}, [x29, #${len_offset}]\n`;
11730
+ else {
11731
+ const k = len_slot - 8;
11732
+ status.code += `ldr x9, [x29, #${overflow_placeholder(func_label, k)}]\n`;
11733
+ status.code += `str x9, [x29, #${len_offset}]\n`;
11734
+ }
11735
+ second_slot_idx++;
11736
+ }
11288
11737
  second_slot_idx++;
11289
11738
  }
11290
11739
  const moved_param_save_slots = /* @__PURE__ */ new Map();
@@ -11374,6 +11823,8 @@ function build_struct_functions$1(node, status) {
11374
11823
  status.function_param_vars = old_param_vars;
11375
11824
  status.function_ref_params = old_ref_params;
11376
11825
  status.ref_class_slots = old_ref_class_slots;
11826
+ status.function_view_params = old_view_params;
11827
+ status.hidden_len_params = old_hidden_len_params;
11377
11828
  status.function_return_label = old_return_label;
11378
11829
  status.force_heap_strings = old_force_heap;
11379
11830
  status.struct_return_buffer = void 0;
@@ -11779,7 +12230,7 @@ function extract_aarch64_asm(func, platform) {
11779
12230
  return asm;
11780
12231
  }
11781
12232
  function count_x19_reads(asm) {
11782
- const matches = asm.match(/\bx19\b/g);
12233
+ const matches = asm.split("\n").map((l) => l.replace(/\/\/.*$/, "")).join("\n").match(/\bx19\b/g);
11783
12234
  return matches ? matches.length : 0;
11784
12235
  }
11785
12236
  function build_naked_inline(struct_node, func, status) {
@@ -11787,6 +12238,8 @@ function build_naked_inline(struct_node, func, status) {
11787
12238
  const standalone_return_label = `.return_${struct_node.name}_${func.name.replace(/#/g, "")}`;
11788
12239
  asm = asm.replaceAll(`b ${standalone_return_label}`, "");
11789
12240
  if (count_x19_reads(asm) === 1) asm = asm.replace(/\bx19\b/g, "x0");
12241
+ const site = inline_counter++;
12242
+ asm = asm.replace(/(\.L[A-Za-z0-9_]+)/g, `$1_${site}`);
11790
12243
  status.code += asm + "\n";
11791
12244
  }
11792
12245
  function build_inline_method(struct_node, func, status) {
@@ -11811,15 +12264,27 @@ function build_inline_method(struct_node, func, status) {
11811
12264
  const old_buffer_data_cache = status.buffer_data_cache;
11812
12265
  const old_heap_cleanup_stack = status.heap_cleanup_stack;
11813
12266
  const old_moved = status.moved;
12267
+ const old_outer_scope_declarations = status.outer_scope_declarations;
12268
+ const old_current_struct = status.current_struct;
12269
+ const old_current_function_name = status.current_function_name;
12270
+ const old_return_assign = status.return_assign;
12271
+ const old_join_needs_owned_string = status.join_needs_owned_string;
12272
+ const old_emitted_allocations = status.emitted_allocations;
11814
12273
  const return_label = `.inline_ret_${inline_counter++}`;
11815
12274
  status.function_return_label = return_label;
11816
12275
  status.scoped_declarations = [];
12276
+ status.outer_scope_declarations = [];
11817
12277
  status.function_return_type = void 0;
11818
12278
  status.struct_return_buffer = void 0;
11819
12279
  status.return_buffer_stack_offset = void 0;
11820
12280
  status.buffer_data_cache = void 0;
11821
12281
  status.heap_cleanup_stack = [];
11822
12282
  status.moved = /* @__PURE__ */ new Set();
12283
+ status.current_struct = struct_node;
12284
+ status.current_function_name = func.name;
12285
+ status.return_assign = void 0;
12286
+ status.join_needs_owned_string = void 0;
12287
+ status.emitted_allocations = /* @__PURE__ */ new Set();
11823
12288
  if (needs_x19) {
11824
12289
  status.code += `str x19, [sp, #-16]!\n`;
11825
12290
  status.code += `mov x19, x0\n`;
@@ -11877,9 +12342,11 @@ function build_inline_method(struct_node, func, status) {
11877
12342
  status.code += `str x8, [x29, #${return_buffer_stack_offset}]\n`;
11878
12343
  status.return_buffer_stack_offset = return_buffer_stack_offset;
11879
12344
  }
12345
+ const code_length_before_body = status.code.length;
11880
12346
  build_block_node$1(func, status);
11881
12347
  const standalone_return_label = `.return_${struct_node.name}_${func.name.replace(/#/g, "")}`;
11882
- status.code = status.code.replaceAll(`b ${standalone_return_label}`, `b ${return_label}`);
12348
+ const body = status.code.slice(code_length_before_body);
12349
+ status.code = status.code.slice(0, code_length_before_body) + body.replaceAll(`b ${standalone_return_label}\n`, `b ${return_label}\n`);
11883
12350
  status.code += `${return_label}:\n`;
11884
12351
  for (let i = saved_stack_slots.length - 1; i >= 0; i--) status.code += `add sp, sp, #16\n`;
11885
12352
  for (let ci = callee_idx - 1; ci >= 0; ci--) {
@@ -11900,6 +12367,12 @@ function build_inline_method(struct_node, func, status) {
11900
12367
  status.buffer_data_cache = old_buffer_data_cache;
11901
12368
  status.heap_cleanup_stack = old_heap_cleanup_stack;
11902
12369
  status.moved = old_moved;
12370
+ status.outer_scope_declarations = old_outer_scope_declarations;
12371
+ status.current_struct = old_current_struct;
12372
+ status.current_function_name = old_current_function_name;
12373
+ status.return_assign = old_return_assign;
12374
+ status.join_needs_owned_string = old_join_needs_owned_string;
12375
+ status.emitted_allocations = old_emitted_allocations;
11903
12376
  }
11904
12377
  let inline_fn_depth = 0;
11905
12378
  const MAX_INLINE_DEPTH = 2;
@@ -11919,15 +12392,27 @@ function build_inline_function(func, status) {
11919
12392
  const old_buffer_data_cache = status.buffer_data_cache;
11920
12393
  const old_heap_cleanup_stack = status.heap_cleanup_stack;
11921
12394
  const old_moved = status.moved;
12395
+ const old_outer_scope_declarations = status.outer_scope_declarations;
12396
+ const old_current_struct = status.current_struct;
12397
+ const old_current_function_name = status.current_function_name;
12398
+ const old_return_assign = status.return_assign;
12399
+ const old_join_needs_owned_string = status.join_needs_owned_string;
12400
+ const old_emitted_allocations = status.emitted_allocations;
11922
12401
  const return_label = `.inline_fn_ret_${inline_counter++}`;
11923
12402
  status.function_return_label = return_label;
11924
12403
  status.scoped_declarations = [];
12404
+ status.outer_scope_declarations = [];
11925
12405
  status.function_return_type = void 0;
11926
12406
  status.struct_return_buffer = void 0;
11927
12407
  status.return_buffer_stack_offset = void 0;
11928
12408
  status.buffer_data_cache = void 0;
11929
12409
  status.heap_cleanup_stack = [];
11930
12410
  status.moved = /* @__PURE__ */ new Set();
12411
+ status.current_struct = void 0;
12412
+ status.current_function_name = func.name;
12413
+ status.return_assign = void 0;
12414
+ status.join_needs_owned_string = void 0;
12415
+ status.emitted_allocations = /* @__PURE__ */ new Set();
11931
12416
  const param_regs = [
11932
12417
  "x0",
11933
12418
  "x1",
@@ -11996,6 +12481,12 @@ function build_inline_function(func, status) {
11996
12481
  status.buffer_data_cache = old_buffer_data_cache;
11997
12482
  status.heap_cleanup_stack = old_heap_cleanup_stack;
11998
12483
  status.moved = old_moved;
12484
+ status.outer_scope_declarations = old_outer_scope_declarations;
12485
+ status.current_struct = old_current_struct;
12486
+ status.current_function_name = old_current_function_name;
12487
+ status.return_assign = old_return_assign;
12488
+ status.join_needs_owned_string = old_join_needs_owned_string;
12489
+ status.emitted_allocations = old_emitted_allocations;
11999
12490
  inline_fn_depth--;
12000
12491
  return true;
12001
12492
  }
@@ -12150,8 +12641,25 @@ function load_nursery_struct_address(target, status) {
12150
12641
  }
12151
12642
  //#endregion
12152
12643
  //#region ../src/build_aarch64/build_access_node.ts
12644
+ /**
12645
+ * Whether `struct_node` is a monomorphized `Array<T>` (`Array_<T>`). The mono
12646
+ * name is unambiguous — the compiler derives it itself (`"Array_" + elem`)
12647
+ * for every `Array<T>` instantiation, so a name match is always an Array mono.
12648
+ */
12649
+ function is_array_mono_struct(struct_node, status) {
12650
+ return !!struct_node && !struct_node.is_generic && struct_node.name.startsWith("Array_") && !!status.structs.find((s) => s.name === struct_node.name);
12651
+ }
12153
12652
  function emit_string_length$1(target, status) {
12154
12653
  if (target.node_type === "value") {
12654
+ const name = target.value;
12655
+ if (name !== "self" && status.hidden_len_params?.has(name)) {
12656
+ const len_slot = status.stack_offsets?.get(`_${name}_len`);
12657
+ if (len_slot !== void 0) {
12658
+ status.last_result_is_heap = false;
12659
+ status.code += `ldr x0, [x29, #${len_slot}]\n`;
12660
+ return;
12661
+ }
12662
+ }
12155
12663
  const slot = status.string_length_slots?.get(target.value);
12156
12664
  if (slot !== void 0) {
12157
12665
  status.last_result_is_heap = false;
@@ -12221,18 +12729,7 @@ function build_view_op(node, access_func, status) {
12221
12729
  if (access_func.name === "to_string" && t.name === "string") {
12222
12730
  status.code += `ldr x0, [x29, #${base}]\n`;
12223
12731
  status.code += `ldr x1, [x29, #${base + 8}]\n`;
12224
- status.code += `stp x0, x1, [sp, #-16]!\n`;
12225
- status.code += `add x0, x1, #1\n`;
12226
- emit_malloc(status);
12227
- status.code += `str x0, [sp, #-16]!\n`;
12228
- status.code += `ldr x1, [sp, #16]\n`;
12229
- status.code += `ldr x2, [sp, #24]\n`;
12230
- status.code += `ldr x0, [sp]\n`;
12231
- status.code += `bl _memcpy\n`;
12232
- status.code += `ldr x0, [sp]\n`;
12233
- status.code += `ldr x1, [sp, #24]\n`;
12234
- status.code += `strb wzr, [x0, x1]\n`;
12235
- status.code += `add sp, sp, #32\n`;
12732
+ emit_view_materialize_owned(status);
12236
12733
  status.last_result_is_heap = true;
12237
12734
  return true;
12238
12735
  }
@@ -12440,6 +12937,14 @@ function build_access_field(node, status) {
12440
12937
  }
12441
12938
  const target_name = node.target.node_type === "value" ? node.target.value : target_type?.name;
12442
12939
  const access_field = node.access;
12940
+ if (access_field.name === "length" && node.target.node_type === "value" && node.target.value === "self" && is_array_mono_struct(status.current_struct, status)) {
12941
+ const self_reg = get_param_reg("self", status);
12942
+ if (self_reg) {
12943
+ if (self_reg !== "x0") status.code += `mov x0, ${self_reg}\n`;
12944
+ status.code += `ldr x0, [x0, #-8]\n`;
12945
+ return;
12946
+ }
12947
+ }
12443
12948
  if (access_field.type?.name === "func") {
12444
12949
  status.code += `adr x0, ${target_type.name}_${access_field.name}\n`;
12445
12950
  return;
@@ -13078,14 +13583,41 @@ function build_access_method(node, access_func, status) {
13078
13583
  "x6",
13079
13584
  "x7"
13080
13585
  ];
13081
- const overflow_count = Math.max(0, access_func.params.length - (8 - start_reg));
13586
+ const view_arg_set = new Set(access_func.view_param_indices ?? []);
13587
+ const hidden_len_indices = callee_hidden_len_indices(access_func);
13588
+ const hidden_len_set = new Set(hidden_len_indices);
13589
+ const hidden_len_slots = /* @__PURE__ */ new Map();
13590
+ const arg_slot = [];
13591
+ let total_arg_slots = 0;
13592
+ for (let i = 0; i < access_func.params.length; i++) {
13593
+ arg_slot.push(total_arg_slots);
13594
+ total_arg_slots += view_arg_set.has(i) ? 2 : 1;
13595
+ if (hidden_len_set.has(i)) {
13596
+ hidden_len_slots.set(i, total_arg_slots);
13597
+ total_arg_slots += 1;
13598
+ }
13599
+ }
13600
+ const overflow_count = Math.max(0, total_arg_slots - (8 - start_reg));
13082
13601
  let overflow_base = 0;
13083
13602
  if (overflow_count > 0) overflow_base = allocate_stack_space(status, overflow_count * 8, 16);
13603
+ const has_view_args = view_arg_set.size > 0;
13604
+ let view_spill_base = 0;
13605
+ if (has_view_args) view_spill_base = allocate_stack_space(status, total_arg_slots * 8, 16);
13606
+ const view_half_store = (j, half) => {
13607
+ const half_slot = start_reg + arg_slot[j] + half;
13608
+ return half_slot >= 8 ? overflow_base + (half_slot - 8) * 8 : view_spill_base + (arg_slot[j] + half) * 8;
13609
+ };
13084
13610
  const ref_class_param_reload = [];
13085
13611
  for (let i = access_func.params.length - 1; i >= 0; i--) {
13086
13612
  const param = access_func.params[i];
13087
13613
  const is_ref_param = access_func.ref_param_indices?.includes(i);
13088
13614
  const param_type = param.type?.name || "";
13615
+ if (view_arg_set.has(i)) {
13616
+ emit_view_string_arg(param, status);
13617
+ status.code += `str x0, [x29, #${view_half_store(i, 0)}]\n`;
13618
+ status.code += `str x1, [x29, #${view_half_store(i, 1)}]\n`;
13619
+ continue;
13620
+ }
13089
13621
  const is_struct = is_struct_type(param_type, status) || is_enum_with_data_type(param_type, status);
13090
13622
  if (is_ref_param) {
13091
13623
  const rp_name = param.node_type === "value" ? param.value : void 0;
@@ -13107,14 +13639,66 @@ function build_access_method(node, access_func, status) {
13107
13639
  if (!status.code.endsWith("\n")) status.code += "\n";
13108
13640
  }
13109
13641
  } else build_node$1(param, status);
13110
- const slot = start_reg + i;
13642
+ const slot = start_reg + arg_slot[i];
13111
13643
  if (!status.code.endsWith("\n")) status.code += "\n";
13112
13644
  if (slot >= 8) status.code += `str x0, [x29, #${overflow_base + (slot - 8) * 8}]\n`;
13113
13645
  else {
13114
- const reg = param_regs[i];
13646
+ const reg = param_regs[arg_slot[i]];
13115
13647
  if (reg && reg !== "x0") status.code += `mov ${reg}, x0\n`;
13116
13648
  }
13117
13649
  }
13650
+ if (has_view_args) for (let j = 0; j < access_func.params.length; j++) {
13651
+ if (!view_arg_set.has(j)) continue;
13652
+ for (const half of [0, 1]) {
13653
+ const half_slot = start_reg + arg_slot[j] + half;
13654
+ if (half_slot >= 8) continue;
13655
+ status.code += `ldr x${half_slot}, [x29, #${view_spill_base + (arg_slot[j] + half) * 8}]\n`;
13656
+ }
13657
+ }
13658
+ if (hidden_len_indices.length > 0) {
13659
+ const needs_strlen = hidden_len_indices.some((i) => {
13660
+ const arg = access_func.params[i];
13661
+ if (arg.node_type !== "value") return true;
13662
+ return !status.string_length_slots?.has(arg.value);
13663
+ });
13664
+ const live_regs = [];
13665
+ if (needs_strlen) {
13666
+ const max_live = Math.min(7, start_reg + total_arg_slots - 1);
13667
+ for (let s = start_reg; s <= max_live; s++) live_regs.push(`x${s}`);
13668
+ }
13669
+ const reg_save_base = needs_strlen ? allocate_stack_space(status, Math.max(live_regs.length, 1) * 8, 16) : 0;
13670
+ const len_save_base = allocate_stack_space(status, hidden_len_indices.length * 8, 16);
13671
+ for (let k = 0; k < live_regs.length; k++) status.code += `str ${live_regs[k]}, [x29, #${reg_save_base + k * 8}]\n`;
13672
+ for (let h = 0; h < hidden_len_indices.length; h++) {
13673
+ const i = hidden_len_indices[h];
13674
+ const arg = access_func.params[i];
13675
+ let computed = false;
13676
+ if (arg.node_type === "value") {
13677
+ const hoisted = status.string_length_slots?.get(arg.value);
13678
+ if (hoisted !== void 0) {
13679
+ status.code += `ldr x9, [x29, #${hoisted}]\n`;
13680
+ status.code += `str x9, [x29, #${len_save_base + h * 8}]\n`;
13681
+ computed = true;
13682
+ }
13683
+ }
13684
+ if (!computed) {
13685
+ const src_slot = start_reg + arg_slot[i];
13686
+ if (src_slot < 8) {
13687
+ const save_index = src_slot - start_reg;
13688
+ status.code += `ldr x0, [x29, #${reg_save_base + save_index * 8}]\n`;
13689
+ } else status.code += `ldr x0, [x29, #${overflow_base + (src_slot - 8) * 8}]\n`;
13690
+ status.code += `bl _strlen\n`;
13691
+ status.code += `str x0, [x29, #${len_save_base + h * 8}]\n`;
13692
+ }
13693
+ }
13694
+ for (let k = live_regs.length - 1; k >= 0; k--) status.code += `ldr ${live_regs[k]}, [x29, #${reg_save_base + k * 8}]\n`;
13695
+ for (let h = 0; h < hidden_len_indices.length; h++) {
13696
+ const dest_slot = start_reg + hidden_len_slots.get(hidden_len_indices[h]);
13697
+ status.code += `ldr x9, [x29, #${len_save_base + h * 8}]\n`;
13698
+ if (dest_slot < 8) status.code += `mov x${dest_slot}, x9\n`;
13699
+ else status.code += `str x9, [x29, #${overflow_base + (dest_slot - 8) * 8}]\n`;
13700
+ }
13701
+ }
13118
13702
  if (!status.code.endsWith("\n")) status.code += "\n";
13119
13703
  if (needs_self_save) status.code += `ldr x0, [sp], #16\n`;
13120
13704
  let outgoing_size = 0;
@@ -14218,6 +14802,84 @@ function has_flag_name(name) {
14218
14802
  return `${name}_has`;
14219
14803
  }
14220
14804
  //#endregion
14805
+ //#region ../src/build_c/utils/view_value.ts
14806
+ /**
14807
+ * Resolve the struct name of an expression's value by walking the access
14808
+ * chain through the (monomorphized) struct table — bare names via
14809
+ * variable_types / scoped declarations / `self`, field accesses via the
14810
+ * base struct's field type, method calls via the method's return type.
14811
+ * Fallback for cached node types that lost their modifiers (generic bodies
14812
+ * re-checked after monomorphization can carry a stale plain `string`).
14813
+ */
14814
+ function value_struct_name(node, status) {
14815
+ if (node.node_type === "value") {
14816
+ const name = node.value;
14817
+ if (name === "self" && status.current_struct) return status.current_struct.name;
14818
+ const vt = status.variable_types?.get(name);
14819
+ if (vt?.name) return vt.name;
14820
+ const decl = status.scoped_declarations.findLast((d) => d.name === name);
14821
+ if (decl?.type?.name) return decl.type.name;
14822
+ return;
14823
+ }
14824
+ if (node.node_type === "access") {
14825
+ const inner = node.access;
14826
+ const base = value_struct_name(node.target, status);
14827
+ if (!base) return void 0;
14828
+ const struct = status.structs.find((s) => s.name === base && !s.is_generic);
14829
+ if (!struct) return void 0;
14830
+ if (inner.node_type === "access_field") return struct.fields.find((f) => f.name === inner.name)?.type?.name;
14831
+ if (inner.node_type === "access_func") return struct.functions.find((f) => f.name === inner.name)?.return_type?.name;
14832
+ }
14833
+ }
14834
+ /**
14835
+ * Whether an expression is a `view T` VALUE at the C level — a nomen_view
14836
+ * (ptr, len) struct expression that can pass through a `view T` parameter
14837
+ * unchanged. Recovers `is_view` from the cached node type, the declared
14838
+ * variable type, the current function's view params, or (for method calls
14839
+ * whose cached type lost the modifier) the callee's declared return type.
14840
+ */
14841
+ function is_view_value(node, status) {
14842
+ if (type_from_value_node$1(node)?.is_view) return true;
14843
+ if (node.node_type === "value") {
14844
+ const name = node.value;
14845
+ if (status.variable_types?.get(name)?.is_view) return true;
14846
+ if (status.function_view_params?.has(name)) return true;
14847
+ }
14848
+ if (node.node_type === "access" && node.access.node_type === "access_func") {
14849
+ const access = node.access;
14850
+ const recv_struct = value_struct_name(node.target, status);
14851
+ 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;
14852
+ }
14853
+ return false;
14854
+ }
14855
+ /**
14856
+ * Emit a C expression for an argument passed to a `view string` parameter.
14857
+ * A view-typed argument is already a nomen_view — pass it through. An owned
14858
+ * `string` expression is wrapped into a nomen_view carrying its strlen (an
14859
+ * implicit owned→view borrow; the caller keeps ownership of the string).
14860
+ * Statement-expression form evaluates the operand exactly once.
14861
+ */
14862
+ function c_view_string_arg(arg, status) {
14863
+ if (is_view_value(arg, status)) {
14864
+ build_node(arg, status);
14865
+ return;
14866
+ }
14867
+ status.code += `({ char* _p = `;
14868
+ build_node(arg, status);
14869
+ status.code += `; nomen_view _v = { (void*)_p, (long)strlen(_p) }; _v; })`;
14870
+ }
14871
+ /**
14872
+ * Emit a C expression materializing a `view string` into an OWNED heap
14873
+ * string (malloc len+1 / memcpy / null-terminate). Used when a view value
14874
+ * initializes an owned `string` declaration (`const string s = v`) — the
14875
+ * resulting pointer is heap-owned and must be freed at scope exit.
14876
+ */
14877
+ function c_materialize_view_string(arg, status) {
14878
+ status.code += `({ nomen_view _t = `;
14879
+ build_node(arg, status);
14880
+ status.code += `; char* _r = malloc(_t.len + 1); memcpy(_r, _t.ptr, _t.len); _r[_t.len] = 0; _r; })`;
14881
+ }
14882
+ //#endregion
14221
14883
  //#region ../src/build_c/build_operation_node.ts
14222
14884
  function build_operation_node(node, status) {
14223
14885
  if (node.operator_func?.deferred) {
@@ -14324,8 +14986,16 @@ function build_operation_node(node, status) {
14324
14986
  status.code += `)`;
14325
14987
  } else if (node.operator_func) {
14326
14988
  const label = node.operator_func.mangled_name || `${node.operator_func.struct_name}_${node.operator_func.func_name}`;
14327
- const is_string_op = node.type?.name === "string";
14328
14989
  const is_string_cmp = node.op === "==" || node.op === "!=";
14990
+ if (is_string_cmp && node.operator_func.struct_name === "string" && (is_view_value(node.left_value, status) || is_view_value(node.right_value, status))) {
14991
+ status.code += `({ nomen_view _a = `;
14992
+ c_view_string_arg(node.left_value, status);
14993
+ status.code += `, _b = `;
14994
+ c_view_string_arg(node.right_value, status);
14995
+ status.code += `; ((_a.len == _b.len) && (memcmp(_a.ptr, _b.ptr, _a.len) == 0))${node.op === "!=" ? " == 0" : ""}; })`;
14996
+ return;
14997
+ }
14998
+ const is_string_op = node.type?.name === "string";
14329
14999
  const left_temp = (is_string_op || is_string_cmp) && is_owned_heap_temp(node.left_value, status);
14330
15000
  const right_temp = (is_string_op || is_string_cmp) && is_owned_heap_temp(node.right_value, status);
14331
15001
  if (left_temp || right_temp) {
@@ -14579,7 +15249,7 @@ function free_scoped_declarations(status, decls, persist_string_field_records =
14579
15249
  }
14580
15250
  const is_destructured_field_access = dec.value?.node_type === "access" && dec.value.access.node_type === "access_field" && !dec.value.is_moved;
14581
15251
  const is_borrowed_string = is_string_borrow(dec.value) || !!status.string_borrow_vars?.has(dec.name);
14582
- 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");
15252
+ 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));
14583
15253
  const dec_value = dec.value;
14584
15254
  const dec_val_is_string_literal = dec.value?.node_type === "value" && dec_value.value.length >= 2 && dec_value.value.startsWith("\"") && dec_value.value.endsWith("\"");
14585
15255
  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);
@@ -14857,6 +15527,70 @@ function reclaim_to_loop_body(status) {
14857
15527
  return loopBodyIdx;
14858
15528
  }
14859
15529
  //#endregion
15530
+ //#region ../src/build_c/utils/hidden_len_call.ts
15531
+ function needs_len_materialize(arg) {
15532
+ if (arg.node_type === "value") return false;
15533
+ if (arg.node_type === "access" && arg.access.node_type === "access_field") return false;
15534
+ return true;
15535
+ }
15536
+ /**
15537
+ * Emit the statement-expression prologue materialising every non-lvalue
15538
+ * hidden-len argument once. Must be called immediately BEFORE the call's
15539
+ * label text is emitted; the args loop then substitutes the temps, and
15540
+ * close_hidden_len_wrap() terminates the expression after the call.
15541
+ */
15542
+ function open_hidden_len_wrap(indices, params, status) {
15543
+ const temps = /* @__PURE__ */ new Map();
15544
+ const to_materialize = indices.filter((i) => params[i] && needs_len_materialize(params[i]));
15545
+ if (to_materialize.length === 0) return {
15546
+ temps,
15547
+ open: false
15548
+ };
15549
+ status.code += `({ `;
15550
+ for (const i of to_materialize) {
15551
+ const tmp = `_hlm_${status.label_counter = (status.label_counter ?? 0) + 1}`;
15552
+ status.code += `char* ${tmp} = `;
15553
+ build_node(params[i], status);
15554
+ status.code += `; `;
15555
+ temps.set(i, tmp);
15556
+ }
15557
+ return {
15558
+ temps,
15559
+ open: true
15560
+ };
15561
+ }
15562
+ function close_hidden_len_wrap(wrap, status) {
15563
+ if (wrap.open) status.code += `; })`;
15564
+ }
15565
+ /**
15566
+ * Emit the companion length EXPRESSION for the argument at a hidden-len
15567
+ * index (the caller emits the separating `, `). `temp` is the materialised
15568
+ * pointer temp when one exists (open_hidden_len_wrap).
15569
+ */
15570
+ function emit_hidden_len_expr(arg, temp, status) {
15571
+ if (temp) {
15572
+ status.code += `strlen(${temp})`;
15573
+ return;
15574
+ }
15575
+ if (arg.node_type === "value") {
15576
+ const value = arg.value;
15577
+ if (value.startsWith("\"")) {
15578
+ status.code += `sizeof(${value}) - 1`;
15579
+ return;
15580
+ }
15581
+ const hoisted = status.string_length_temps?.get(value);
15582
+ if (hoisted) {
15583
+ status.code += hoisted;
15584
+ return;
15585
+ }
15586
+ status.code += `strlen(${value})`;
15587
+ return;
15588
+ }
15589
+ status.code += `strlen(`;
15590
+ build_node(arg, status);
15591
+ status.code += `)`;
15592
+ }
15593
+ //#endregion
14860
15594
  //#region ../src/build_c/build_access_node.ts
14861
15595
  /**
14862
15596
  * The C type of a single element of a `view T` slice, used to cast the
@@ -14918,6 +15652,66 @@ function build_vtable_target(node, status) {
14918
15652
  status.code = status.code.substring(0, before);
14919
15653
  status.code += "&" + expr;
14920
15654
  }
15655
+ /**
15656
+ * An Array method's `self` is a `struct Array_<T>*` — header `[0]=_vt`,
15657
+ * `[8]=length`, elements at `+16` — which is exactly what a heap receiver
15658
+ * (`struct Array_<T>*`) already is. A NON-heap receiver (a plain stack C
15659
+ * array `T w[N]`, or a variadic `...T` param lowered to `T *w`) would pass
15660
+ * the bare element pointer, so the callee's `self->length` and the raw
15661
+ * bodies' `(T*)((char*)self + sizeof(*self))` reads land on garbage
15662
+ * (`w[1]`, `w + 2`). When such a receiver with a usable length calls a
15663
+ * method on the mono `Array_<T>` struct, build a statement-expression
15664
+ * temporary carrying a proper header plus a copy of the elements — the
15665
+ * same wrap the binary-operator operand path uses
15666
+ * (build_array_operand_for_call). A `ref self` callee additionally gets
15667
+ * its element copy written back after the call, so mutations through
15668
+ * `self` reach the caller's array.
15669
+ */
15670
+ function array_receiver_wrap(node, target_type, target_method, mono_struct_name, status) {
15671
+ if (!target_type?.is_array || !mono_struct_name.startsWith("Array_")) return void 0;
15672
+ if (node.target.node_type !== "value") return void 0;
15673
+ const target_value = node.target.value;
15674
+ if (target_type.is_array_heap || status.heap_array_vars?.has(target_value)) return void 0;
15675
+ let length;
15676
+ if (status.stack_array_lengths?.has(target_value)) length = status.stack_array_lengths.get(target_value);
15677
+ else if (target_type.length) {
15678
+ const before = status.code.length;
15679
+ build_node(target_type.length, status);
15680
+ length = status.code.substring(before);
15681
+ status.code = status.code.substring(0, before);
15682
+ } else if (status.function_variadic_params?.has(target_value)) length = `_${target_value}_len`;
15683
+ if (!length) return void 0;
15684
+ const before = status.code.length;
15685
+ status.suppress_dereference = true;
15686
+ build_node(node.target, status);
15687
+ status.suppress_dereference = false;
15688
+ const recv = status.code.substring(before);
15689
+ status.code = status.code.substring(0, before);
15690
+ const elem_c = c_type(target_type.name);
15691
+ const id = status.label_counter = (status.label_counter ?? 0) + 1;
15692
+ const wrap = `_arrm_${id}`;
15693
+ const method_self_is_ref = !!target_method?.params?.some((p) => p.is_self_param && (p.is_ref || p.type?.is_ref));
15694
+ const ret = target_method?.return_type;
15695
+ const ret_c = !ret?.name ? "void" : ret.is_array ? "void*" : c_type(ret.name);
15696
+ const bytes = `${length} * sizeof(${elem_c})`;
15697
+ const length_is_literal = /^(\d+L?|0x[0-9a-fA-F]+)$/.test(length.trim());
15698
+ let prefix;
15699
+ let self_expr;
15700
+ if (length_is_literal) {
15701
+ 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} = ` : "");
15702
+ self_expr = `(struct ${mono_struct_name}*)&${wrap}`;
15703
+ } else {
15704
+ 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} = ` : "");
15705
+ self_expr = `_arrm_p_${id}`;
15706
+ }
15707
+ const copy_back = `memcpy(${recv}, ${length_is_literal ? `${wrap}._d` : `${wrap} + sizeof(struct ${mono_struct_name})`}, ${bytes}); `;
15708
+ const suffix = (method_self_is_ref ? `; ${copy_back}` + (ret_c !== "void" ? `_arrm_r_${id}; ` : `(void)0; `) : "") + `; })`;
15709
+ return {
15710
+ prefix,
15711
+ self_expr,
15712
+ suffix
15713
+ };
15714
+ }
14921
15715
  function build_access_node(node, status) {
14922
15716
  let target_type = type_from_value_node$1(node.target);
14923
15717
  if (!target_type?.name && node.target.node_type === "value") {
@@ -15225,20 +16019,36 @@ function build_access_node(node, status) {
15225
16019
  }
15226
16020
  }
15227
16021
  const label = access_func.mangled_name || trait_default_label || `${mono_struct_name}_${access_func.name.replace(/#/g, "")}`;
16022
+ const array_wrap = array_receiver_wrap(node, method_type, target_method, mono_struct_name, status);
16023
+ if (array_wrap) status.code += array_wrap.prefix;
16024
+ const hidden_len = callee_hidden_len_indices(access_func);
16025
+ const hidden_wrap = open_hidden_len_wrap(hidden_len, access_func.params, status);
15228
16026
  status.code += `${label}(`;
15229
16027
  if (!access_func.is_static) {
15230
- const method_self_is_ref = !!target_method?.params?.some((p) => p.is_self_param && (p.is_ref || p.type?.is_ref));
15231
- if (!built_in_types.includes(method_type?.name || "") || method_self_is_ref) {
15232
- const target_value = node.target.node_type === "value" ? node.target.value : "";
15233
- const target_is_ref_class_param = !!status.ref_class_params?.has(target_value);
15234
- if (!(!!status.function_ref_params?.has(target_value) || !!status.class_vars?.has(target_value) || !!status.heap_array_vars?.has(target_value))) status.code += "&";
15235
- else if (target_is_ref_class_param) {} else status.suppress_dereference = true;
16028
+ if (array_wrap) status.code += array_wrap.self_expr;
16029
+ else {
16030
+ const method_self_is_ref = !!target_method?.params?.some((p) => p.is_self_param && (p.is_ref || p.type?.is_ref));
16031
+ if (!built_in_types.includes(method_type?.name || "") || method_self_is_ref) {
16032
+ const target_value = node.target.node_type === "value" ? node.target.value : "";
16033
+ const target_is_ref_class_param = !!status.ref_class_params?.has(target_value);
16034
+ if (!(!!status.function_ref_params?.has(target_value) || !!status.class_vars?.has(target_value) || !!status.heap_array_vars?.has(target_value))) status.code += "&";
16035
+ else if (target_is_ref_class_param) {} else status.suppress_dereference = true;
16036
+ }
16037
+ build_node(node.target, status);
16038
+ status.suppress_dereference = false;
15236
16039
  }
15237
- build_node(node.target, status);
15238
- status.suppress_dereference = false;
15239
16040
  }
15240
16041
  for (let i = 0; i < access_func.params.length; i++) {
15241
16042
  if (!access_func.is_static || i > 0) status.code += ", ";
16043
+ const hidden_temp = hidden_wrap.temps.get(i);
16044
+ if (hidden_temp) {
16045
+ status.code += `${hidden_temp}, strlen(${hidden_temp})`;
16046
+ continue;
16047
+ }
16048
+ if (access_func.view_param_indices?.includes(i)) {
16049
+ c_view_string_arg(access_func.params[i], status);
16050
+ continue;
16051
+ }
15242
16052
  const param_type = type_from_value_node$1(access_func.params[i]);
15243
16053
  const param_value = access_func.params[i].node_type === "value" ? access_func.params[i].value : "";
15244
16054
  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);
@@ -15259,8 +16069,14 @@ function build_access_node(node, status) {
15259
16069
  build_node(access_func.params[i], status);
15260
16070
  status.suppress_dereference = false;
15261
16071
  } else build_node(access_func.params[i], status);
16072
+ if (hidden_len.includes(i)) {
16073
+ status.code += `, `;
16074
+ emit_hidden_len_expr(access_func.params[i], void 0, status);
16075
+ }
15262
16076
  }
15263
16077
  status.code += ")";
16078
+ close_hidden_len_wrap(hidden_wrap, status);
16079
+ if (array_wrap) status.code += array_wrap.suffix;
15264
16080
  }
15265
16081
  if (access_func.mov_param_indices) for (const idx of access_func.mov_param_indices) {
15266
16082
  const param = access_func.params[idx];
@@ -15302,6 +16118,11 @@ function resolve_access_field_type(node, status) {
15302
16118
  }
15303
16119
  function emit_string_length(target, status) {
15304
16120
  if (target.node_type === "value") {
16121
+ const name = target.value;
16122
+ if (name !== "self" && status.hidden_len_params?.has(c_function_name(name))) {
16123
+ status.code += `_${c_function_name(name)}_len`;
16124
+ return;
16125
+ }
15305
16126
  const temp = status.string_length_temps?.get(target.value);
15306
16127
  if (temp) {
15307
16128
  status.code += temp;
@@ -15826,6 +16647,10 @@ function build_parameter_node(node, status) {
15826
16647
  status.code += `)`;
15827
16648
  return;
15828
16649
  }
16650
+ if (!node.is_self_param && node.type.is_view) {
16651
+ status.code += `nomen_view ${c_function_name(node.name)}`;
16652
+ return;
16653
+ }
15829
16654
  const cls = classify_param(node.type, type_name, {
15830
16655
  is_ref: node.is_ref || node.type.is_ref,
15831
16656
  is_self: node.is_self_param,
@@ -15953,6 +16778,7 @@ function has_string_fields(node, status) {
15953
16778
  const OWNING_BUFFER_METHODS = /* @__PURE__ */ new Set([
15954
16779
  "store_T",
15955
16780
  "replace_T",
16781
+ "shift_T",
15956
16782
  "destroy",
15957
16783
  "#destroy"
15958
16784
  ]);
@@ -15987,6 +16813,15 @@ function emit_owning_buffer_string_body(func_name, status) {
15987
16813
  status.code += `if (val != _old) { free(_old); _slots[i] = val ? strdup(val) : 0; }\n`;
15988
16814
  return true;
15989
16815
  }
16816
+ if (func_name === "shift_T") {
16817
+ status.code += `char** _slots = (char**)self->data;\n`;
16818
+ status.code += `if (dst != src) {\n`;
16819
+ status.code += `free(_slots[dst]);\n`;
16820
+ status.code += `_slots[dst] = _slots[src];\n`;
16821
+ status.code += `_slots[src] = 0;\n`;
16822
+ status.code += `}\n`;
16823
+ return true;
16824
+ }
15990
16825
  if (func_name === "destroy") {
15991
16826
  status.code += `if (self->data) {\n`;
15992
16827
  status.code += `char** _slots = (char**)self->data;\n`;
@@ -16026,6 +16861,15 @@ function emit_owning_buffer_body(func_name, elem, status) {
16026
16861
  emit_deep_copy_fields(elem, "_slots[i]", "val", status);
16027
16862
  return true;
16028
16863
  }
16864
+ if (func_name === "shift_T") {
16865
+ status.code += `${Tptr}_slots = ${Tcast}(unsigned long long)self->data;\n`;
16866
+ status.code += `if (dst != src) {\n`;
16867
+ status.code += `${elem.name}_destroy(&_slots[dst]);\n`;
16868
+ status.code += `_slots[dst] = _slots[src];\n`;
16869
+ status.code += `memset((void*)&_slots[src], 0, sizeof(struct ${elem.name}));\n`;
16870
+ status.code += `}\n`;
16871
+ return true;
16872
+ }
16029
16873
  if (func_name === "destroy") {
16030
16874
  status.code += `if (self->data) {\n`;
16031
16875
  status.code += `${Tptr}_slots = ${Tcast}(unsigned long long)self->data;\n`;
@@ -16348,6 +17192,10 @@ function build_struct_functions(node, status, skip_init = false) {
16348
17192
  const old_borrow_only = status.c_borrow_only_strings;
16349
17193
  const old_return_type = status.function_return_type;
16350
17194
  const old_function_name = status.current_function_name;
17195
+ const old_view_params = status.function_view_params;
17196
+ status.function_view_params = /* @__PURE__ */ new Set();
17197
+ const old_hidden_len_params = status.hidden_len_params;
17198
+ status.hidden_len_params = /* @__PURE__ */ new Set();
16351
17199
  status.current_function_name = func.name;
16352
17200
  status.function_ref_params = /* @__PURE__ */ new Set();
16353
17201
  status.class_vars = /* @__PURE__ */ new Set();
@@ -16359,6 +17207,8 @@ function build_struct_functions(node, status, skip_init = false) {
16359
17207
  const self_param = func.params[0]?.is_self_param ? func.params[0] : null;
16360
17208
  status.self_is_ref = !!self_param?.is_ref || self_param?.declaration === "var";
16361
17209
  for (let param of func.params) {
17210
+ if (param.type.is_view && !param.is_self_param) status.function_view_params.add(c_function_name(param.name));
17211
+ if (param.hidden_len) status.hidden_len_params.add(c_function_name(param.name));
16362
17212
  const param_struct = status.structs.find((s) => s.name === param.type.name);
16363
17213
  const param_trait = status.traits.find((t) => t.name === param.type.name);
16364
17214
  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) {
@@ -16409,6 +17259,7 @@ function build_struct_functions(node, status, skip_init = false) {
16409
17259
  for (let i = 0; i < func.params.length; i++) {
16410
17260
  if (i > 0) status.code += ", ";
16411
17261
  build_parameter_node(func.params[i], status);
17262
+ if (func.params[i].hidden_len) status.code += `, long _${c_function_name(func.params[i].name)}_len`;
16412
17263
  }
16413
17264
  status.code += `)`;
16414
17265
  forward_decl_referenced_types(func, status);
@@ -16438,6 +17289,8 @@ function build_struct_functions(node, status, skip_init = false) {
16438
17289
  status.c_borrow_only_strings = old_borrow_only;
16439
17290
  status.function_return_type = old_return_type;
16440
17291
  status.current_function_name = old_function_name;
17292
+ status.function_view_params = old_view_params;
17293
+ status.hidden_len_params = old_hidden_len_params;
16441
17294
  }
16442
17295
  status.current_struct = old_current_struct;
16443
17296
  for (let traitName of node.traits) {
@@ -16568,6 +17421,7 @@ function build_function_node(node, status) {
16568
17421
  else {
16569
17422
  if (node.return_type.name) {
16570
17423
  if (node.return_type.is_array) status.code += `struct Array_${node.return_type.name}* `;
17424
+ else if (node.return_type.is_view) status.code += `nomen_view `;
16571
17425
  else {
16572
17426
  const mono_return_name = mono_type_name(node.return_type);
16573
17427
  const return_is_class = !!status.structs.find((s) => s.name === mono_return_name && s.is_class);
@@ -16588,6 +17442,7 @@ function build_function_node(node, status) {
16588
17442
  if (node.params[i].is_variadic) status.code += `long _${node.params[i].name}_len, `;
16589
17443
  build_parameter_node(node.params[i], status);
16590
17444
  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))}`;
17445
+ if (node.params[i].hidden_len) status.code += `, long _${c_function_name(node.params[i].name)}_len`;
16591
17446
  }
16592
17447
  if (is_nullable_struct_type(node.return_type, status)) {
16593
17448
  if (!first_param) status.code += ", ";
@@ -16616,6 +17471,10 @@ function build_function_node(node, status) {
16616
17471
  status.ref_class_param_types = /* @__PURE__ */ new Map();
16617
17472
  const old_variadic_params = status.function_variadic_params;
16618
17473
  status.function_variadic_params = /* @__PURE__ */ new Set();
17474
+ const old_hidden_len_params = status.hidden_len_params;
17475
+ status.hidden_len_params = /* @__PURE__ */ new Set();
17476
+ const old_view_params = status.function_view_params;
17477
+ status.function_view_params = /* @__PURE__ */ new Set();
16619
17478
  const old_return_type = status.function_return_type;
16620
17479
  status.function_return_type = node.return_type;
16621
17480
  const old_nullable_ret_has = status.nullable_ret_has_param;
@@ -16625,6 +17484,8 @@ function build_function_node(node, status) {
16625
17484
  status.current_function_name = node.name;
16626
17485
  for (let param of node.params) {
16627
17486
  if (param.is_variadic) status.function_variadic_params.add(c_function_name(param.name));
17487
+ if (param.hidden_len) status.hidden_len_params.add(c_function_name(param.name));
17488
+ if (param.type.is_view && !param.is_self_param) status.function_view_params.add(c_function_name(param.name));
16628
17489
  if (!param.is_variadic ? array_struct_name(param.type, status) : void 0) {
16629
17490
  const pname = c_function_name(param.name);
16630
17491
  if (!status.heap_array_vars) status.heap_array_vars = /* @__PURE__ */ new Set();
@@ -16657,6 +17518,8 @@ function build_function_node(node, status) {
16657
17518
  status.ref_class_params = old_ref_class_params;
16658
17519
  status.ref_class_param_types = old_ref_class_param_types;
16659
17520
  status.function_variadic_params = old_variadic_params;
17521
+ status.hidden_len_params = old_hidden_len_params;
17522
+ status.function_view_params = old_view_params;
16660
17523
  status.function_return_type = old_return_type;
16661
17524
  status.nullable_ret_has_param = old_nullable_ret_has;
16662
17525
  status.current_function_name = old_function_name;
@@ -17370,7 +18233,8 @@ function build_declaration_node(node, status) {
17370
18233
  const val_node = node.value;
17371
18234
  const val_is_string_literal = node.value.node_type === "value" && val_node.value.length >= 2 && val_node.value.startsWith("\"") && val_node.value.endsWith("\"");
17372
18235
  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);
17373
- 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) {
18236
+ if (node.type.name === "string" && !node.type.is_view && is_view_value(node.value, status)) c_materialize_view_string(node.value, status);
18237
+ 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) {
17374
18238
  status.code += `strdup(`;
17375
18239
  build_node(node.value, status);
17376
18240
  status.code += `)`;
@@ -17587,10 +18451,17 @@ function build_function_call_node(node, status) {
17587
18451
  }
17588
18452
  const is_struct = status.structs.find((s) => s.name === node.name && !s.is_simple_type);
17589
18453
  const func_name = is_struct ? `${node.name}_init` : c_function_name(node.name);
18454
+ const hidden_len = callee_hidden_len_indices(node);
18455
+ const hidden_wrap = open_hidden_len_wrap(hidden_len, node.params, status);
17590
18456
  status.code += `${func_name}(`;
17591
18457
  const variadic_idx = node.variadic_param_name ? node.params.findIndex((p) => p.node_type === "array") : -1;
17592
18458
  for (let i = 0; i < node.params.length; i++) {
17593
18459
  if (i > 0) status.code += ", ";
18460
+ const hidden_temp = hidden_wrap.temps.get(i);
18461
+ if (hidden_temp) {
18462
+ status.code += `${hidden_temp}, strlen(${hidden_temp})`;
18463
+ continue;
18464
+ }
17594
18465
  if (i === variadic_idx && node.params[i].node_type === "array") {
17595
18466
  const arr = node.params[i];
17596
18467
  status.code += `${arr.values.length}, (${c_type(arr.type.name)}[]){`;
@@ -17612,6 +18483,10 @@ function build_function_call_node(node, status) {
17612
18483
  status.suppress_dereference = false;
17613
18484
  continue;
17614
18485
  }
18486
+ if (node.view_param_indices?.includes(i)) {
18487
+ c_view_string_arg(node.params[i], status);
18488
+ continue;
18489
+ }
17615
18490
  const is_ref_param = node.ref_param_indices?.includes(i);
17616
18491
  const param_is_class = !!status.structs.find((s) => s.name === param_type.name && s.is_class);
17617
18492
  if (is_ref_param && param_is_class) {
@@ -17641,6 +18516,10 @@ function build_function_call_node(node, status) {
17641
18516
  status.code += `, `;
17642
18517
  emit_nullable_arg_flag(node.params[i], status);
17643
18518
  }
18519
+ if (hidden_len.includes(i)) {
18520
+ status.code += `, `;
18521
+ emit_hidden_len_expr(node.params[i], void 0, status);
18522
+ }
17644
18523
  }
17645
18524
  if (is_nullable_struct_type(node.type, status)) {
17646
18525
  const flag_name = status.current_nullable_call_flag;
@@ -17656,6 +18535,7 @@ function build_function_call_node(node, status) {
17656
18535
  }
17657
18536
  }
17658
18537
  status.code += ")";
18538
+ close_hidden_len_wrap(hidden_wrap, status);
17659
18539
  if (node.name.startsWith("_string_interpolate_")) status.interpolate_string_counts.add(node.params.length - 1);
17660
18540
  if (node.swap_params?.size) for (const [idx, swap_expr] of node.swap_params) {
17661
18541
  const source = node.params[idx];
@@ -18064,10 +18944,13 @@ function build_return_node(node, status) {
18064
18944
  }
18065
18945
  }
18066
18946
  if (returns_borrowed_string || returns_string_literal || returns_borrow_var) status.code += `strdup(`;
18947
+ const wrap_view_string_return = !!ret_type.is_view && ret_type.name === "string" && !is_view_value(node.value, status);
18067
18948
  const expr_type_name = node.type?.name || "";
18068
18949
  const expr_is_simple = !status.structs.find((s) => s.name === expr_type_name && !s.is_simple_type);
18069
18950
  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})`;
18951
+ if (wrap_view_string_return) status.code += `({ char* _p = `;
18070
18952
  build_node(node.value, status);
18953
+ if (wrap_view_string_return) status.code += `; nomen_view _v = { (void*)_p, (long)strlen(_p) }; _v; })`;
18071
18954
  if (returns_borrowed_string || returns_string_literal || returns_borrow_var) status.code += `)`;
18072
18955
  status.code += `;\n`;
18073
18956
  if (node.value.node_type === "func_call" && node.value.field_overrides?.length) emit_field_overrides("_return_val", node.value, build_node, status, "", ";\n");
@@ -18257,7 +19140,7 @@ function build_value_node(node, status) {
18257
19140
  else value = c_function_name(value);
18258
19141
  if (value.startsWith("\"")) value = escape_c_string(value);
18259
19142
  if (value !== "self" && status.function_ref_params?.has(value) && !status.suppress_dereference) status.code += `*`;
18260
- if (value === "self" && status.function_ref_params?.has("self") && !status.suppress_dereference) status.code += `*`;
19143
+ if (value === "self" && status.function_ref_params?.has("self") && !status.suppress_dereference && status.current_struct?.name !== "string") status.code += `*`;
18261
19144
  if (value !== "self" && status.ref_class_params?.has(value) && !status.suppress_dereference) {
18262
19145
  status.code += `(*${value})`;
18263
19146
  return;
@@ -18281,6 +19164,7 @@ function build_while_loop_node(node, status) {
18281
19164
  if (!status.string_length_temps) status.string_length_temps = /* @__PURE__ */ new Map();
18282
19165
  for (const [name, target] of hoists) {
18283
19166
  if (status.string_length_temps.has(name)) continue;
19167
+ if (status.hidden_len_params?.has(c_function_name(name))) continue;
18284
19168
  const temp = `_slh_${status.label_counter = (status.label_counter ?? 0) + 1}`;
18285
19169
  status.string_length_temps.set(name, temp);
18286
19170
  status.code += `const long ${temp} = (long)strlen(`;
@@ -20123,6 +21007,16 @@ function add_error(status, message, start) {
20123
21007
  });
20124
21008
  }
20125
21009
  //#endregion
21010
+ //#region ../src/nodes/set_resolved_function.ts
21011
+ function set_resolved_function(node, func) {
21012
+ Object.defineProperty(node, "resolved_function", {
21013
+ value: func,
21014
+ enumerable: false,
21015
+ writable: true,
21016
+ configurable: true
21017
+ });
21018
+ }
21019
+ //#endregion
20126
21020
  //#region ../src/nodes/clone_node.ts
20127
21021
  function clone_type$2(type) {
20128
21022
  const t = new Type(type.name, type.is_static, type.is_array, type.length ? clone_node(type.length) : void 0);
@@ -20157,10 +21051,13 @@ function clone_node(node) {
20157
21051
  c.type_args = n.type_args?.map(clone_type$2);
20158
21052
  c.ref_param_indices = n.ref_param_indices?.slice();
20159
21053
  c.mov_param_indices = n.mov_param_indices?.slice();
21054
+ c.nullable_param_indices = n.nullable_param_indices?.slice();
21055
+ c.view_param_indices = n.view_param_indices?.slice();
20160
21056
  c.swap_params = n.swap_params ? new Map([...n.swap_params].map(([k, v]) => [k, clone_node(v)])) : void 0;
20161
21057
  c.variadic_param_name = n.variadic_param_name;
20162
21058
  c.variadic_param_index = n.variadic_param_index;
20163
21059
  c.is_enum_shorthand = n.is_enum_shorthand;
21060
+ set_resolved_function(c, n.resolved_function);
20164
21061
  c.allocations = n.allocations?.map(clone_node);
20165
21062
  return c;
20166
21063
  }
@@ -20188,6 +21085,7 @@ function clone_node(node) {
20188
21085
  c.mangled_name = n.mangled_name;
20189
21086
  c.owned_return = n.owned_return;
20190
21087
  c.nullable_param_indices = n.nullable_param_indices?.slice();
21088
+ c.view_param_indices = n.view_param_indices?.slice();
20191
21089
  c.return_bounds = n.return_bounds ? {
20192
21090
  upper: n.return_bounds.upper.slice(),
20193
21091
  lower: n.return_bounds.lower.slice(),
@@ -20199,6 +21097,7 @@ function clone_node(node) {
20199
21097
  c.function_return_type = n.function_return_type ? clone_type$2(n.function_return_type) : void 0;
20200
21098
  c.is_statement = n.is_statement;
20201
21099
  c.skip_bounds_check = n.skip_bounds_check;
21100
+ set_resolved_function(c, n.resolved_function);
20202
21101
  c.allocations = n.allocations?.map(clone_node);
20203
21102
  return c;
20204
21103
  }
@@ -20386,6 +21285,7 @@ function clone_node(node) {
20386
21285
  c.return_constraint = n.return_constraint ? clone_node(n.return_constraint) : void 0;
20387
21286
  c.returns_mov = n.returns_mov;
20388
21287
  c.is_library = n.is_library;
21288
+ c.is_inline = n.is_inline;
20389
21289
  return c;
20390
21290
  }
20391
21291
  case "param": {
@@ -20396,7 +21296,10 @@ function clone_node(node) {
20396
21296
  c.default_value_start = n.default_value_start;
20397
21297
  c.is_variadic = n.is_variadic;
20398
21298
  c.is_ref = n.is_ref;
21299
+ c.is_variadic_tuple = n.is_variadic_tuple;
21300
+ c.hidden_len = n.hidden_len;
20399
21301
  c.constraint = n.constraint ? clone_node(n.constraint) : void 0;
21302
+ c.stripped_length_equalities = n.stripped_length_equalities?.map((e) => ({ ...e }));
20400
21303
  c.func_params = n.func_params?.map((p) => clone_node(p));
20401
21304
  c.func_return_type = n.func_return_type ? clone_type$2(n.func_return_type) : void 0;
20402
21305
  c.allocations = n.allocations?.map(clone_node);
@@ -20954,6 +21857,40 @@ function snapshot_bounds(name, status) {
20954
21857
  lower_bound_inclusive_exprs: decl.lower_bound_inclusive_exprs?.slice()
20955
21858
  };
20956
21859
  }
21860
+ function snapshot_all_bounds(status) {
21861
+ const snap = /* @__PURE__ */ new Map();
21862
+ for (const v of status.values) snap.set(v, {
21863
+ range_lower: v.range_lower,
21864
+ range_upper: v.range_upper,
21865
+ upper_bound_exprs: v.upper_bound_exprs?.slice(),
21866
+ lower_bound_exprs: v.lower_bound_exprs?.slice(),
21867
+ upper_bound_inclusive_exprs: v.upper_bound_inclusive_exprs?.slice(),
21868
+ lower_bound_inclusive_exprs: v.lower_bound_inclusive_exprs?.slice(),
21869
+ upper_bound_expr: v.upper_bound_expr,
21870
+ lower_bound_expr: v.lower_bound_expr,
21871
+ alias_of: v.alias_of,
21872
+ known_length: v.known_length,
21873
+ path_bounds: v.path_bounds ? new Map(v.path_bounds) : void 0
21874
+ });
21875
+ return snap;
21876
+ }
21877
+ function restore_all_bounds(status, snap) {
21878
+ for (const v of status.values) {
21879
+ const s = snap.get(v);
21880
+ if (!s) continue;
21881
+ v.range_lower = s.range_lower;
21882
+ v.range_upper = s.range_upper;
21883
+ v.upper_bound_exprs = s.upper_bound_exprs;
21884
+ v.lower_bound_exprs = s.lower_bound_exprs;
21885
+ v.upper_bound_inclusive_exprs = s.upper_bound_inclusive_exprs;
21886
+ v.lower_bound_inclusive_exprs = s.lower_bound_inclusive_exprs;
21887
+ v.upper_bound_expr = s.upper_bound_expr;
21888
+ v.lower_bound_expr = s.lower_bound_expr;
21889
+ v.alias_of = s.alias_of;
21890
+ v.known_length = s.known_length;
21891
+ v.path_bounds = s.path_bounds;
21892
+ }
21893
+ }
20957
21894
  /**
20958
21895
  * Serialize an AST expression to a canonical string for comparison.
20959
21896
  * E.g. `list.length` → "list.length", `self.length` → resolves alias to actual var.
@@ -20963,7 +21900,7 @@ function expr_to_string(node, status) {
20963
21900
  const vn = node;
20964
21901
  if (status) {
20965
21902
  const decl = status.values.findLast((v) => v.name === vn.value);
20966
- if (decl?.alias_of) return decl.alias_of;
21903
+ if (decl?.alias_of) return canonicalize_length_path(decl.alias_of, status);
20967
21904
  }
20968
21905
  return vn.value;
20969
21906
  }
@@ -20972,7 +21909,10 @@ function expr_to_string(node, status) {
20972
21909
  if (access.access.node_type === "access_field") {
20973
21910
  const target = expr_to_string(access.target, status);
20974
21911
  const field = access.access.name;
20975
- if (target) return `${target}.${field}`;
21912
+ if (target) {
21913
+ const path = `${target}.${field}`;
21914
+ return status ? canonicalize_length_path(path, status) : path;
21915
+ }
20976
21916
  } else if (access.access.node_type === "access_func") {
20977
21917
  const target = expr_to_string(access.target, status);
20978
21918
  const method = access.access.name;
@@ -20981,9 +21921,148 @@ function expr_to_string(node, status) {
20981
21921
  "size",
20982
21922
  "count",
20983
21923
  "cap"
20984
- ].includes(method)) return `${target}.${method}`;
20985
- }
20986
- }
21924
+ ].includes(method)) {
21925
+ const path = `${target}.${method}`;
21926
+ return status ? canonicalize_length_path(path, status) : path;
21927
+ }
21928
+ }
21929
+ }
21930
+ }
21931
+ /**
21932
+ * Rewrite a `<base>.<field>` path through the assumed parallel-length
21933
+ * equalities (`a.length == b.length` param contracts) to a canonical
21934
+ * representative, so a bound phrased against either container's length
21935
+ * (`i < xs.length`) verifies a constraint on the other (`hash.length`).
21936
+ * Each equation's `a` side is the representative; following `b → a` edges
21937
+ * to a fixed point makes the rewrite idempotent and confluent for chains
21938
+ * (a==b, b==c all canonicalize to the chain head). Only touches paths whose
21939
+ * field has a recorded equation.
21940
+ */
21941
+ function canonicalize_length_path(path, status) {
21942
+ const eqs = status.equal_lengths;
21943
+ if (!eqs?.length) return path;
21944
+ const m = /^(\w+)\.(length|count|size|cap)$/.exec(path);
21945
+ if (!m) return path;
21946
+ const field = m[2];
21947
+ let base = m[1];
21948
+ for (let step = 0; step < eqs.length; step++) {
21949
+ const eq = eqs.find((e) => e.field === field && (e.a === base || e.b === base));
21950
+ if (!eq || eq.a === base) break;
21951
+ base = eq.a;
21952
+ }
21953
+ return `${base}.${field}`;
21954
+ }
21955
+ /**
21956
+ * Remove `X.<f> == Y.<f>` clauses (same container-size field on two params,
21957
+ * one of them `param_name`) from a parameter constraint, recording each as an
21958
+ * ASSUMED parallel-length equality on `status.equal_lengths`. Such a clause
21959
+ * relates two runtime lengths, which the verifier can never prove at a call
21960
+ * site — instead it is trusted there (stripped from the contract) and made
21961
+ * available to the body's bounds machinery via expr_to_string
21962
+ * canonicalization. Returns the rewritten constraint, or undefined when
21963
+ * everything was stripped. Nodes are only rebuilt when something changed.
21964
+ */
21965
+ function strip_length_equalities(constraint, param_name, status) {
21966
+ if (!constraint) return void 0;
21967
+ if (constraint.node_type !== "op") return constraint;
21968
+ const op = constraint;
21969
+ if (op.op === "&&") {
21970
+ const left = strip_length_equalities(op.left_value, param_name, status);
21971
+ const right = strip_length_equalities(op.right_value, param_name, status);
21972
+ if (left && right) {
21973
+ if (left === op.left_value && right === op.right_value) return constraint;
21974
+ return new OperationNode(op.start, "&&", left, right, op.type);
21975
+ }
21976
+ return left ?? right;
21977
+ }
21978
+ if (op.op !== "==") return constraint;
21979
+ const extract = (side) => {
21980
+ if (side.node_type !== "access") return void 0;
21981
+ const access = side;
21982
+ let field;
21983
+ if (access.access.node_type === "access_field") field = access.access.name;
21984
+ else if (access.access.node_type === "access_func") field = access.access.name;
21985
+ if (!field || !NON_NEGATIVE_FIELDS$1.has(field)) return void 0;
21986
+ if (access.target.node_type !== "value") return void 0;
21987
+ const base = access.target.value;
21988
+ if (base !== param_name && !status.values.some((v) => v.name === base)) return void 0;
21989
+ return {
21990
+ base,
21991
+ field
21992
+ };
21993
+ };
21994
+ const lhs = extract(op.left_value);
21995
+ const rhs = extract(op.right_value);
21996
+ if (!lhs || !rhs || lhs.field !== rhs.field) return constraint;
21997
+ if (lhs.base === rhs.base) return constraint;
21998
+ if (!status.equal_lengths) status.equal_lengths = [];
21999
+ const other = lhs.base;
22000
+ const paired = rhs.base;
22001
+ if (!status.equal_lengths.some((e) => e.field === lhs.field && (e.a === other && e.b === paired || e.a === paired && e.b === other))) status.equal_lengths.push({
22002
+ a: other,
22003
+ b: paired,
22004
+ field: lhs.field
22005
+ });
22006
+ }
22007
+ /**
22008
+ * Registration-time variant of strip_length_equalities: removes
22009
+ * `<container>.<size> == <container>.<size>` conjuncts from a parameter
22010
+ * constraint and returns them alongside the remaining constraint. Used when a
22011
+ * function signature is REGISTERED — before any call site can observe it — so
22012
+ * a forward-referenced callee never hands its caller a clause no call site
22013
+ * could prove (declaration order must not matter). Bases are validated
22014
+ * against `known_names` (the function's own parameters) instead of
22015
+ * status.values, because at registration time nothing has been pushed into
22016
+ * any scope yet. Does NOT touch status.equal_lengths: the extracted
22017
+ * equalities are returned for the callee's own param check to seed.
22018
+ */
22019
+ function extract_length_equalities_at_registration(constraint, param_name, known_names) {
22020
+ const equalities = [];
22021
+ if (!constraint) return {
22022
+ constraint,
22023
+ equalities
22024
+ };
22025
+ const walk = (node) => {
22026
+ if (node.node_type !== "op") return node;
22027
+ const op = node;
22028
+ if (op.op === "&&") {
22029
+ const left = walk(op.left_value);
22030
+ const right = walk(op.right_value);
22031
+ if (left && right) {
22032
+ if (left === op.left_value && right === op.right_value) return node;
22033
+ return new OperationNode(op.start, "&&", left, right, op.type);
22034
+ }
22035
+ return left ?? right;
22036
+ }
22037
+ if (op.op !== "==") return node;
22038
+ const extract = (side) => {
22039
+ if (side.node_type !== "access") return void 0;
22040
+ const access = side;
22041
+ let field;
22042
+ if (access.access.node_type === "access_field") field = access.access.name;
22043
+ else if (access.access.node_type === "access_func") field = access.access.name;
22044
+ if (!field || !NON_NEGATIVE_FIELDS$1.has(field)) return void 0;
22045
+ if (access.target.node_type !== "value") return void 0;
22046
+ return {
22047
+ base: access.target.value,
22048
+ field
22049
+ };
22050
+ };
22051
+ const lhs = extract(op.left_value);
22052
+ const rhs = extract(op.right_value);
22053
+ if (!lhs || !rhs || lhs.field !== rhs.field) return node;
22054
+ if (lhs.base === rhs.base) return node;
22055
+ for (const base of [lhs.base, rhs.base]) if (base !== param_name && !known_names.has(base)) return node;
22056
+ equalities.push({
22057
+ a: lhs.base,
22058
+ b: rhs.base,
22059
+ field: lhs.field
22060
+ });
22061
+ };
22062
+ return {
22063
+ constraint: walk(constraint),
22064
+ equalities
22065
+ };
20987
22066
  }
20988
22067
  /**
20989
22068
  * Extract flow-sensitive bounds from a comparison condition.
@@ -21079,32 +22158,59 @@ function apply_bounds(condition, status, is_loop = false) {
21079
22158
  var_decl = status.values.findLast((v) => v.name === base);
21080
22159
  }
21081
22160
  if (!var_decl) return;
22161
+ let path_entry;
22162
+ if (bound.var_name.includes(".") && bound.var_name !== var_decl.name) {
22163
+ if (!var_decl.path_bounds) var_decl.path_bounds = /* @__PURE__ */ new Map();
22164
+ path_entry = var_decl.path_bounds.get(bound.var_name);
22165
+ if (!path_entry) {
22166
+ path_entry = {};
22167
+ var_decl.path_bounds.set(bound.var_name, path_entry);
22168
+ }
22169
+ }
22170
+ const push_unique = (arr, expr) => {
22171
+ const out = arr ?? [];
22172
+ if (!out.includes(expr)) out.push(expr);
22173
+ return out;
22174
+ };
21082
22175
  if (bound.op === "<" || bound.op === "<=") {
21083
22176
  const inclusive = bound.op === "<=";
21084
22177
  if (!var_decl.upper_bound_exprs) var_decl.upper_bound_exprs = [];
21085
22178
  if (!var_decl.upper_bound_inclusive_exprs) var_decl.upper_bound_inclusive_exprs = [];
21086
22179
  if (inclusive) {
21087
- if (!var_decl.upper_bound_inclusive_exprs.includes(bound.expr)) var_decl.upper_bound_inclusive_exprs.push(bound.expr);
21088
- } else if (!var_decl.upper_bound_exprs.includes(bound.expr)) var_decl.upper_bound_exprs.push(bound.expr);
22180
+ var_decl.upper_bound_inclusive_exprs = push_unique(var_decl.upper_bound_inclusive_exprs, bound.expr);
22181
+ if (path_entry) path_entry.upper_inclusive = push_unique(path_entry.upper_inclusive, bound.expr);
22182
+ } else {
22183
+ var_decl.upper_bound_exprs = push_unique(var_decl.upper_bound_exprs, bound.expr);
22184
+ if (path_entry) path_entry.upper = push_unique(path_entry.upper, bound.expr);
22185
+ }
21089
22186
  var_decl.upper_bound_expr = bound.expr;
21090
22187
  const num = numeric_interval(string_to_node(bound.expr), status);
21091
22188
  if (num) {
21092
22189
  const hi = bound.op === "<=" ? num.upper : num.lower;
21093
22190
  if (is_loop) var_decl.range_upper = hi;
21094
22191
  else if (var_decl.range_upper === void 0 || hi < var_decl.range_upper) var_decl.range_upper = hi;
22192
+ if (path_entry) {
22193
+ if (is_loop) path_entry.range_upper = hi;
22194
+ else if (path_entry.range_upper === void 0 || hi < path_entry.range_upper) path_entry.range_upper = hi;
22195
+ }
21095
22196
  }
21096
22197
  } else if (bound.op === ">" || bound.op === ">=") {
21097
22198
  const inclusive = bound.op === ">=";
21098
22199
  if (!var_decl.lower_bound_exprs) var_decl.lower_bound_exprs = [];
21099
22200
  if (!var_decl.lower_bound_inclusive_exprs) var_decl.lower_bound_inclusive_exprs = [];
21100
22201
  if (inclusive) {
21101
- if (!var_decl.lower_bound_inclusive_exprs.includes(bound.expr)) var_decl.lower_bound_inclusive_exprs.push(bound.expr);
21102
- } else if (!var_decl.lower_bound_exprs.includes(bound.expr)) var_decl.lower_bound_exprs.push(bound.expr);
22202
+ var_decl.lower_bound_inclusive_exprs = push_unique(var_decl.lower_bound_inclusive_exprs, bound.expr);
22203
+ if (path_entry) path_entry.lower_inclusive = push_unique(path_entry.lower_inclusive, bound.expr);
22204
+ } else {
22205
+ var_decl.lower_bound_exprs = push_unique(var_decl.lower_bound_exprs, bound.expr);
22206
+ if (path_entry) path_entry.lower = push_unique(path_entry.lower, bound.expr);
22207
+ }
21103
22208
  var_decl.lower_bound_expr = bound.expr;
21104
22209
  const num = numeric_interval(string_to_node(bound.expr), status);
21105
22210
  if (num) {
21106
22211
  const lo = bound.op === ">=" ? num.lower : num.upper;
21107
22212
  if (var_decl.range_lower === void 0 || lo > var_decl.range_lower) var_decl.range_lower = lo;
22213
+ if (path_entry && (path_entry.range_lower === void 0 || lo > path_entry.range_lower)) path_entry.range_lower = lo;
21108
22214
  }
21109
22215
  }
21110
22216
  apply_inverse_numeric_bound(bound, var_decl, status);
@@ -21259,6 +22365,31 @@ function substitute_constraint(node, lhs_name, param_to_arg, visited = /* @__PUR
21259
22365
  }
21260
22366
  return node;
21261
22367
  }
22368
+ /** Extract `return_bounds` from a value node that is a call (or wraps one). */
22369
+ function call_return_bounds(value) {
22370
+ if (!value) return void 0;
22371
+ if (value.node_type === "func_call" || value.node_type === "access_func") return value.return_bounds;
22372
+ if (value.node_type === "access" && value.access.node_type === "access_func") return value.access.return_bounds;
22373
+ }
22374
+ /**
22375
+ * Transfer a call's return-contract bounds onto the variable the call result
22376
+ * is bound to (`const int m = mid(xs)` / `m = mid(xs)`). Without this, only
22377
+ * the nested form `xs.at(mid(xs))` carried the contract (via the call node's
22378
+ * `return_bounds` decoration); binding to a variable dropped it and forced
22379
+ * unreadable nesting. Uses the same funneled semantics as the nested path
22380
+ * (collect_return_bounds: `<=` lands in the strict `upper` array), so both
22381
+ * forms verify identically.
22382
+ */
22383
+ function apply_return_bounds_to_var(name, rb, status) {
22384
+ if (!rb) return;
22385
+ const decl = status.values.findLast((v) => v.name === name);
22386
+ if (!decl) return;
22387
+ const push = (arr, add) => arr ? (arr.push(...add.filter((e) => !arr.includes(e))), arr) : add.slice();
22388
+ if (rb.upper.length) decl.upper_bound_exprs = push(decl.upper_bound_exprs, rb.upper);
22389
+ if (rb.lower.length) decl.lower_bound_exprs = push(decl.lower_bound_exprs, rb.lower);
22390
+ if (rb.upper_inclusive.length) decl.upper_bound_inclusive_exprs = push(decl.upper_bound_inclusive_exprs, rb.upper_inclusive);
22391
+ if (rb.lower_inclusive.length) decl.lower_bound_inclusive_exprs = push(decl.lower_bound_inclusive_exprs, rb.lower_inclusive);
22392
+ }
21262
22393
  /**
21263
22394
  * Build an expression node tree from a dotted path string (e.g. "list",
21264
22395
  * "self.items") for use in return-contract substitution. Used to map `self`
@@ -22256,6 +23387,7 @@ function is_inside_core_method(status) {
22256
23387
  return false;
22257
23388
  }
22258
23389
  function check_function_call(node, status, func, target_type, self_value, self_path) {
23390
+ set_resolved_function(node, func);
22259
23391
  const access_scope = status.stack.at(-1);
22260
23392
  if (func.name === "#init") {
22261
23393
  const struct_name = target_type?.name || func.return_type.name;
@@ -22321,6 +23453,10 @@ function check_function_call(node, status, func, target_type, self_value, self_p
22321
23453
  if (!node.nullable_param_indices.includes(i)) node.nullable_param_indices.push(i);
22322
23454
  }
22323
23455
  }
23456
+ if (func_param && func_param.type.is_view && !func_param.is_self_param) {
23457
+ if (!node.view_param_indices) node.view_param_indices = [];
23458
+ if (!node.view_param_indices.includes(i)) node.view_param_indices.push(i);
23459
+ }
22324
23460
  const old_expected_type = status.expected_type;
22325
23461
  const old_allow_null = status.allow_null_value;
22326
23462
  if (func_param) {
@@ -22474,6 +23610,18 @@ function check_function_call(node, status, func, target_type, self_value, self_p
22474
23610
  if (access.access.node_type === "access_field") field_name = access.access.name;
22475
23611
  else if (access.access.node_type === "access_func") field_name = access.access.name;
22476
23612
  if (field_name && NON_NEGATIVE_FIELDS.has(field_name)) alias_of = expr_to_string(param, status);
23613
+ const access_path = expr_to_string(param, status);
23614
+ if (access_path && access_path.includes(".")) {
23615
+ const path_bounds = status.values.findLast((v) => v.name === access_path.split(".")[0])?.path_bounds?.get(access_path);
23616
+ if (path_bounds) {
23617
+ upper_bound_exprs = path_bounds.upper?.slice();
23618
+ lower_bound_exprs = path_bounds.lower?.slice();
23619
+ upper_bound_inclusive_exprs = path_bounds.upper_inclusive?.slice();
23620
+ lower_bound_inclusive_exprs = path_bounds.lower_inclusive?.slice();
23621
+ range_lower = path_bounds.range_lower;
23622
+ range_upper = path_bounds.range_upper;
23623
+ }
23624
+ }
22477
23625
  }
22478
23626
  let nested_rb;
22479
23627
  if (param.node_type === "func_call" || param.node_type === "access_func") nested_rb = param.return_bounds;
@@ -22692,6 +23840,222 @@ function receiver_is_const(target_type, self_value, status) {
22692
23840
  return !!binding && binding.declaration === "const" && !binding.type.is_ref;
22693
23841
  }
22694
23842
  //#endregion
23843
+ //#region ../src/build_common/fold_asm_constants.ts
23844
+ /**
23845
+ * A tiny constant-propagation peephole over monomorphized raw aarch64
23846
+ * assembly (`#arch: aarch64` blocks after T → concrete-type substitution).
23847
+ *
23848
+ * Raw library code is written generically: `mov x3, #T_SIZE` followed by a
23849
+ * runtime width dispatch (`cmp x3, #8 / b.gt … / cmp x3, #1 / b.eq …`).
23850
+ * After substitution T_SIZE is a literal, so every dispatched branch has a
23851
+ * compile-time outcome — but as TEXT, so nothing folds it. This pass tracks
23852
+ * `mov <reg>, #<imm>` definitions per basic block and:
23853
+ *
23854
+ * - folds `cmp <reg>, #<imm>` + following `b.<cond> <label>` when the
23855
+ * comparison's outcome is known: never-taken branches are dropped,
23856
+ * always-taken ones become unconditional `b <label>`,
23857
+ * - rewrites `madd <d>, <n>, <m>, xzr` / `mul <d>, <n>, <m>` as
23858
+ * `lsl <d>, <n>, #<log2>` when `<m>` is a known power-of-two constant
23859
+ * (the element-stride multiply in every load_T/store_T body).
23860
+ *
23861
+ * Soundness: a register's definition is only tracked from its immediate
23862
+ * `mov` to its next write; the map is cleared at labels (jump targets may
23863
+ * arrive with different values), at unconditional branches, at calls
23864
+ * (`bl`/`blr` clobber the caller-saved set), and at any instruction form we
23865
+ * do not model. Dead code left behind dropped branches (e.g. the
23866
+ * width-matched tails and the memcpy copy path) stays in place — it is
23867
+ * unreachable, and removing it would require a full reachability pass.
23868
+ * Comments (`// …`) are ignored.
23869
+ */
23870
+ const REG = /^[wx][0-9]+$/;
23871
+ const COND_OPS = /* @__PURE__ */ new Set([
23872
+ "eq",
23873
+ "ne",
23874
+ "gt",
23875
+ "ge",
23876
+ "lt",
23877
+ "le",
23878
+ "hi",
23879
+ "hs",
23880
+ "lo",
23881
+ "ls",
23882
+ "al"
23883
+ ]);
23884
+ function split_comment(line) {
23885
+ return line.replace(/\/\/.*$/, "");
23886
+ }
23887
+ /** Does `a <cond> b` hold? (signed semantics; immediates here are small.) */
23888
+ function cond_holds(cond, a, b) {
23889
+ switch (cond) {
23890
+ case "eq": return a === b;
23891
+ case "ne": return a !== b;
23892
+ case "gt": return a > b;
23893
+ case "ge": return a >= b;
23894
+ case "lt": return a < b;
23895
+ case "le": return a <= b;
23896
+ case "hi": return a > b;
23897
+ case "hs": return a >= b;
23898
+ case "lo": return a < b;
23899
+ case "ls": return a <= b;
23900
+ case "al": return true;
23901
+ default: return false;
23902
+ }
23903
+ }
23904
+ function is_pow2(n) {
23905
+ if (n > 0 && (n & n - 1) === 0) return Math.log2(n);
23906
+ }
23907
+ function fold_asm_constants(asm) {
23908
+ const lines = asm.split("\n");
23909
+ const out = [];
23910
+ const defs = /* @__PURE__ */ new Map();
23911
+ const clear = () => defs.clear();
23912
+ /** Invalidate the destination register of a general instruction. */
23913
+ const invalidate_dest = (ops) => {
23914
+ const first = ops.split(",")[0]?.trim();
23915
+ if (first && REG.test(first)) defs.delete(first);
23916
+ };
23917
+ let i = 0;
23918
+ while (i < lines.length) {
23919
+ const raw_line = lines[i];
23920
+ const line = split_comment(raw_line).trim();
23921
+ if (!line) {
23922
+ out.push(raw_line);
23923
+ i++;
23924
+ continue;
23925
+ }
23926
+ if (/^\.[\w$]+:$/.test(line) || line.startsWith(".")) {
23927
+ clear();
23928
+ out.push(raw_line);
23929
+ i++;
23930
+ continue;
23931
+ }
23932
+ const m = /^(\w+)\s*(.*)$/.exec(line);
23933
+ if (!m) {
23934
+ clear();
23935
+ out.push(raw_line);
23936
+ i++;
23937
+ continue;
23938
+ }
23939
+ const mn = m[1];
23940
+ const ops = m[2].trim();
23941
+ if (mn === "bl" || mn === "blr" || mn === "b" || mn === "br" || mn === "ret") {
23942
+ clear();
23943
+ out.push(raw_line);
23944
+ i++;
23945
+ continue;
23946
+ }
23947
+ if (mn === "mov") {
23948
+ const mm = /^([wx][0-9]+)\s*,\s*#(-?\d+)$/.exec(ops);
23949
+ if (mm) {
23950
+ defs.set(mm[1], parseInt(mm[2], 10));
23951
+ out.push(raw_line);
23952
+ i++;
23953
+ continue;
23954
+ }
23955
+ invalidate_dest(ops);
23956
+ out.push(raw_line);
23957
+ i++;
23958
+ continue;
23959
+ }
23960
+ if (mn === "cmp") {
23961
+ const cm = /^([wx][0-9]+)\s*,\s*#(-?\d+)$/.exec(ops);
23962
+ if (cm && defs.has(cm[1])) {
23963
+ const a = defs.get(cm[1]);
23964
+ const b = parseInt(cm[2], 10);
23965
+ const run = [];
23966
+ let j = i + 1;
23967
+ while (j < lines.length) {
23968
+ const t = split_comment(lines[j]).trim();
23969
+ if (!t) {
23970
+ j++;
23971
+ continue;
23972
+ }
23973
+ const bm = /^b\.(\w+)\s+(\S+)$/.exec(t);
23974
+ if (bm && COND_OPS.has(bm[1])) {
23975
+ run.push({
23976
+ cond: bm[1],
23977
+ label: bm[2],
23978
+ line_idx: j
23979
+ });
23980
+ j++;
23981
+ continue;
23982
+ }
23983
+ break;
23984
+ }
23985
+ if (run.length > 0) {
23986
+ let follower = "";
23987
+ for (let k = run[run.length - 1].line_idx + 1; k < lines.length; k++) {
23988
+ const t = split_comment(lines[k]).trim();
23989
+ if (!t) continue;
23990
+ follower = t;
23991
+ break;
23992
+ }
23993
+ const follower_mn = /^(\S+)/.exec(follower)?.[1] ?? "";
23994
+ if (follower === "" || follower.startsWith(".") || [
23995
+ "cmp",
23996
+ "tst",
23997
+ "cmn",
23998
+ "b",
23999
+ "bl",
24000
+ "ret",
24001
+ "cbz",
24002
+ "cbnz"
24003
+ ].includes(follower_mn)) {
24004
+ for (const r of run) if (cond_holds(r.cond, a, b)) {
24005
+ out.push(`b ${r.label}`);
24006
+ break;
24007
+ }
24008
+ i = run[run.length - 1].line_idx + 1;
24009
+ continue;
24010
+ }
24011
+ }
24012
+ }
24013
+ out.push(raw_line);
24014
+ i++;
24015
+ continue;
24016
+ }
24017
+ if (mn === "madd" || mn === "mul") {
24018
+ const parts = ops.split(",").map((p) => p.trim());
24019
+ if (parts.length === (mn === "madd" ? 4 : 3)) {
24020
+ const dest = parts[0];
24021
+ const srcs = [parts[1], parts[2]];
24022
+ const addend = mn === "madd" ? parts[3] : void 0;
24023
+ const addend_zero = mn === "madd" ? /^(x|w)zr$/.test(addend ?? "") : true;
24024
+ let folded = false;
24025
+ if (addend_zero && REG.test(dest)) for (const [n_reg, m_reg] of [[srcs[0], srcs[1]], [srcs[1], srcs[0]]]) {
24026
+ const c = m_reg !== void 0 && REG.test(m_reg) ? defs.get(m_reg) : void 0;
24027
+ const shift = c !== void 0 ? is_pow2(c) : void 0;
24028
+ if (shift !== void 0 && REG.test(n_reg) && !defs.has(n_reg)) {
24029
+ out.push(`lsl ${dest}, ${n_reg}, #${shift}`);
24030
+ defs.delete(dest);
24031
+ i++;
24032
+ folded = true;
24033
+ break;
24034
+ }
24035
+ }
24036
+ if (folded) continue;
24037
+ }
24038
+ invalidate_dest(ops);
24039
+ out.push(raw_line);
24040
+ i++;
24041
+ continue;
24042
+ }
24043
+ if (![
24044
+ "str",
24045
+ "stp",
24046
+ "cmp",
24047
+ "tst",
24048
+ "cbz",
24049
+ "cbnz",
24050
+ "tbz",
24051
+ "tbnz"
24052
+ ].includes(mn)) invalidate_dest(ops);
24053
+ out.push(raw_line);
24054
+ i++;
24055
+ }
24056
+ return out.join("\n");
24057
+ }
24058
+ //#endregion
22695
24059
  //#region ../src/nodes/ExtendNode.ts
22696
24060
  /**
22697
24061
  * An `extend struct Name { ... }` / `extend class Name { ... }` declaration.
@@ -23089,6 +24453,7 @@ function check_declaration_node(decl, status) {
23089
24453
  status.pending_return_bounds.delete(decl.name);
23090
24454
  }
23091
24455
  }
24456
+ if (decl.value) apply_return_bounds_to_var(decl.name, call_return_bounds(decl.value), status);
23092
24457
  }
23093
24458
  }
23094
24459
  /**
@@ -23708,6 +25073,15 @@ function gather_structs(block, status) {
23708
25073
  func.scope = status.stack.at(-1) || block;
23709
25074
  func.return_type = materialize_type(func.return_type, status);
23710
25075
  instantiate_generic_type(func.return_type, status);
25076
+ const param_names = new Set(func.params.map((p) => p.name));
25077
+ for (const param of func.params) {
25078
+ if (!param.constraint) continue;
25079
+ const { constraint, equalities } = extract_length_equalities_at_registration(param.constraint, param.name, param_names);
25080
+ if (equalities.length) {
25081
+ param.constraint = constraint;
25082
+ param.stripped_length_equalities = equalities;
25083
+ }
25084
+ }
23711
25085
  status.functions.push(func);
23712
25086
  }
23713
25087
  break;
@@ -23821,7 +25195,16 @@ function check_function_parameter_node(param, status) {
23821
25195
  is_set: true,
23822
25196
  is_null: param.type.is_nullable ? true : void 0
23823
25197
  });
25198
+ if (param.stripped_length_equalities?.length) {
25199
+ if (!status.equal_lengths) status.equal_lengths = [];
25200
+ 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);
25201
+ }
23824
25202
  if (param.constraint) {
25203
+ const stripped = strip_length_equalities(param.constraint, param.name, status);
25204
+ if (stripped !== param.constraint) {
25205
+ param.constraint = stripped;
25206
+ if (!param.constraint) return;
25207
+ }
23825
25208
  check_node(param.constraint, status);
23826
25209
  const constraint_type = type_from_value_node(param.constraint, status);
23827
25210
  if (constraint_type.name && constraint_type.name !== "bool") add_error(status, `Constraint must be a boolean expression, got ${constraint_type.name}`, param.constraint.start);
@@ -23844,7 +25227,15 @@ function clone_status(status) {
23844
25227
  upper_bound_exprs: v.upper_bound_exprs?.slice(),
23845
25228
  lower_bound_exprs: v.lower_bound_exprs?.slice(),
23846
25229
  upper_bound_inclusive_exprs: v.upper_bound_inclusive_exprs?.slice(),
23847
- lower_bound_inclusive_exprs: v.lower_bound_inclusive_exprs?.slice()
25230
+ lower_bound_inclusive_exprs: v.lower_bound_inclusive_exprs?.slice(),
25231
+ path_bounds: v.path_bounds ? new Map([...v.path_bounds].map(([k, b]) => [k, {
25232
+ upper: b.upper?.slice(),
25233
+ lower: b.lower?.slice(),
25234
+ upper_inclusive: b.upper_inclusive?.slice(),
25235
+ lower_inclusive: b.lower_inclusive?.slice(),
25236
+ range_lower: b.range_lower,
25237
+ range_upper: b.range_upper
25238
+ }])) : void 0
23848
25239
  })),
23849
25240
  function_value_base: status.function_value_base,
23850
25241
  structs: status.structs.slice(),
@@ -23857,6 +25248,7 @@ function clone_status(status) {
23857
25248
  type_params: status.type_params,
23858
25249
  errors: status.errors,
23859
25250
  buffer_caps: status.buffer_caps,
25251
+ equal_lengths: status.equal_lengths?.slice(),
23860
25252
  mutated_local_names: status.mutated_local_names
23861
25253
  };
23862
25254
  }
@@ -23869,12 +25261,22 @@ function check_function_node(func, status) {
23869
25261
  if (func.checked) return;
23870
25262
  func.checked = true;
23871
25263
  status.functions.push(func);
25264
+ const param_names = new Set(func.params.map((p) => p.name));
25265
+ for (const param of func.params) {
25266
+ if (!param.constraint) continue;
25267
+ const { constraint, equalities } = extract_length_equalities_at_registration(param.constraint, param.name, param_names);
25268
+ if (equalities.length) {
25269
+ param.constraint = constraint;
25270
+ param.stripped_length_equalities = equalities;
25271
+ }
25272
+ }
23872
25273
  if (is_generic_func(func)) {
23873
25274
  func.is_generic = true;
23874
25275
  return;
23875
25276
  }
23876
25277
  let function_status = clone_status(status);
23877
25278
  function_status.function_value_base = function_status.values.length;
25279
+ function_status.equal_lengths = [];
23878
25280
  const structs_before = function_status.structs.length;
23879
25281
  const enums_before = function_status.enums.length;
23880
25282
  const types_before = function_status.types.length;
@@ -24812,6 +26214,7 @@ function substitute_raw_in_node(node, substitution, structs, deref_params = /* @
24812
26214
  value = value.replace(new RegExp(`\\b${param}_NEEDS_STRDUP\\b`, "g"), type === "string" ? "1" : "0");
24813
26215
  }
24814
26216
  for (const pname of deref_params) value = value.replace(new RegExp(`(?<![&*.>\\w])\\b${pname}\\b(?![\\w])`, "g"), `(*${pname})`);
26217
+ if (raw_block_is_pure_asm(value)) value = fold_asm_constants(value);
24815
26218
  raw.value = value;
24816
26219
  return;
24817
26220
  }
@@ -26037,13 +27440,17 @@ function check_assignment_node(assign, status) {
26037
27440
  left_value.lower_bound_exprs = void 0;
26038
27441
  left_value.upper_bound_inclusive_exprs = void 0;
26039
27442
  left_value.lower_bound_inclusive_exprs = void 0;
27443
+ left_value.path_bounds = void 0;
26040
27444
  left_value.alias_of = void 0;
26041
27445
  left_value.class_alias_of = void 0;
26042
27446
  left_value.type.length = void 0;
26043
27447
  if (assign.left_value.node_type === "value") invalidate_view_borrows_of(status, left_value.name);
26044
27448
  let synthetic_rhs;
26045
27449
  if (is_trackable_compound) synthetic_rhs = new OperationNode(assign.right_value.start, compound_op, assign.left_value, assign.right_value, left_value.type);
26046
- if ((!is_compound || synthetic_rhs) && left_value_name === left_value.name) track_assignment_bounds(left_value.name, synthetic_rhs ?? assign.right_value, status, self_snapshot);
27450
+ if ((!is_compound || synthetic_rhs) && left_value_name === left_value.name) {
27451
+ track_assignment_bounds(left_value.name, synthetic_rhs ?? assign.right_value, status, self_snapshot);
27452
+ if (!is_compound) apply_return_bounds_to_var(left_value.name, call_return_bounds(assign.right_value), status);
27453
+ }
26047
27454
  if (!is_compound && left_value.type.name === "string" && assign.right_value.node_type === "value" && assign.right_value.value.startsWith("\"") && assign.right_value.value.endsWith("\"")) {
26048
27455
  const len = assign.right_value.value.length - 2;
26049
27456
  left_value.type.length = new ValueNode(assign.right_value.start, len.toString(), new Type("int"));
@@ -26701,7 +28108,11 @@ function check_operation_node(op, status) {
26701
28108
  var_obj.is_null = false;
26702
28109
  }
26703
28110
  }
26704
- if (!check_node(op.right_value, status)) return false;
28111
+ const saved_bounds = snapshot_all_bounds(status);
28112
+ apply_bounds(op.left_value, status);
28113
+ const right_ok = check_node(op.right_value, status);
28114
+ restore_all_bounds(status, saved_bounds);
28115
+ if (!right_ok) return false;
26705
28116
  if (saved_null !== void 0 && left_check) {
26706
28117
  const var_obj = status.values.findLast((v) => v.name === left_check.name);
26707
28118
  if (var_obj) var_obj.is_null = saved_null;
@@ -26709,6 +28120,16 @@ function check_operation_node(op, status) {
26709
28120
  op.type = new Type("bool");
26710
28121
  return true;
26711
28122
  }
28123
+ if (op.op === "||") {
28124
+ if (!check_node(op.left_value, status)) return false;
28125
+ const saved_bounds = snapshot_all_bounds(status);
28126
+ apply_negated_bounds(op.left_value, status);
28127
+ const right_ok = check_node(op.right_value, status);
28128
+ restore_all_bounds(status, saved_bounds);
28129
+ if (!right_ok) return false;
28130
+ op.type = new Type("bool");
28131
+ return true;
28132
+ }
26712
28133
  const is_equality = op.op === "==" || op.op === "!=";
26713
28134
  const is_null_coalesce = op.op === "??";
26714
28135
  const old_allow_null = status.allow_null_value;
@@ -26786,7 +28207,6 @@ function check_operation_node(op, status) {
26786
28207
  case ">=":
26787
28208
  case "<":
26788
28209
  case "<=":
26789
- case "||":
26790
28210
  op.type = new Type("bool");
26791
28211
  break;
26792
28212
  case "??": {
@@ -27272,6 +28692,251 @@ function promote_allocations(node, status) {
27272
28692
  }
27273
28693
  }
27274
28694
  //#endregion
28695
+ //#region ../src/check/stamp_hidden_string_lens.ts
28696
+ /**
28697
+ * Post-check pass implementing the PERF gap 2.4 fix: thread a string's
28698
+ * length through the call boundary so a per-character helper doesn't pay one
28699
+ * `strlen` per CALL.
28700
+ *
28701
+ * For every function whose Nomen-level body reads `<param>.length` on a
28702
+ * by-value `string` param (and never rebinds, mutates, or escapes it), the
28703
+ * param is stamped `hidden_len`. The backends then lower it as TWO
28704
+ * parameters — the `char*` plus a trailing `long _<name>_len` companion
28705
+ * (mirroring the nullable `_has` companion) — and the body's `.length` reads
28706
+ * become loads of that companion instead of `strlen`. Call sites append the
28707
+ * companion argument, preferring a loop-invariant hoisted strlen temp
28708
+ * (scan_string_length_hoists treats such args like `.length` reads), so a
28709
+ * helper called once per line over a large document costs ONE strlen, not
28710
+ * O(lines).
28711
+ *
28712
+ * Conservative exclusions (any of these keeps the plain one-pointer ABI):
28713
+ * - raw blocks anywhere in the body (opaque code may touch the param), or no
28714
+ * body at all
28715
+ * - `#init`/`#destroy`/`init`/`destroy`/`main` and any non-identifier or
28716
+ * `_`/`#`-prefixed name (operators, compiler-generated helpers) — their
28717
+ * call sites use bespoke emission paths
28718
+ * - variadic params anywhere (their call sites pack hidden count/ptr pairs)
28719
+ * - `is_inline` functions (the aarch64 inline splicer)
28720
+ * - trait method declarations/default bodies, and struct methods that
28721
+ * implement a trait method (vtable dispatch uses the trait's declared
28722
+ * signature)
28723
+ * - functions referenced as VALUES anywhere in the program (`var func f =
28724
+ * helper`, `obj.method` as a value, nursery.spawn targets) — indirect
28725
+ * calls use the declared ABI
28726
+ * - params that are ref/mov/view/nullable/variadic/self, or that the body
28727
+ * assigns to, passes ref/mov/swap, mutates through a `var self`/`ref self`
28728
+ * string method, or references across a nested func/async boundary, or
28729
+ * whose companion name `_<name>_len` collides with any declared name in
28730
+ * the body
28731
+ */
28732
+ const NON_IDENTIFIERS = /* @__PURE__ */ new Set([
28733
+ "true",
28734
+ "false",
28735
+ "null",
28736
+ "self",
28737
+ "as",
28738
+ "default"
28739
+ ]);
28740
+ function is_plain_identifier(value) {
28741
+ if (!value) return false;
28742
+ if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(value)) return false;
28743
+ if (value.startsWith("_")) return false;
28744
+ return !NON_IDENTIFIERS.has(value);
28745
+ }
28746
+ function root_name(node) {
28747
+ if (!node) return void 0;
28748
+ if (node.node_type === "value") {
28749
+ const v = node.value;
28750
+ return is_plain_identifier(v) ? v : void 0;
28751
+ }
28752
+ if (node.node_type === "access") return root_name(node.target);
28753
+ }
28754
+ function collect_functions(root, status) {
28755
+ const out = [];
28756
+ const seen = /* @__PURE__ */ new Set();
28757
+ const add = (func, struct, is_trait_func) => {
28758
+ if (seen.has(func)) return;
28759
+ seen.add(func);
28760
+ out.push({
28761
+ func,
28762
+ struct,
28763
+ is_trait_func
28764
+ });
28765
+ };
28766
+ for (const struct of status.structs) for (const func of struct.functions) add(func, struct);
28767
+ for (const trait of status.traits) for (const func of trait.functions) add(func, void 0, true);
28768
+ for (const func of status.functions) add(func);
28769
+ walk_tree(root, (n) => {
28770
+ if (n.node_type === "struct") {
28771
+ const s = n;
28772
+ for (const func of s.functions) add(func, s);
28773
+ } else if (n.node_type === "trait") {
28774
+ const t = n;
28775
+ for (const func of t.functions) add(func, void 0, true);
28776
+ } else if (n.node_type === "func") add(n);
28777
+ });
28778
+ return out;
28779
+ }
28780
+ /** Names of functions referenced as VALUES (func-typed, non-call nodes). */
28781
+ function collect_value_referenced_names(root, status) {
28782
+ const excluded = /* @__PURE__ */ new Set();
28783
+ const visit = (n) => {
28784
+ if (n.type?.name !== "func") return;
28785
+ if (n.node_type === "value") {
28786
+ const v = n.value;
28787
+ if (is_plain_identifier(v)) excluded.add(v);
28788
+ } else if (n.node_type === "access_field") {
28789
+ const name = n.name;
28790
+ if (is_plain_identifier(name)) excluded.add(name);
28791
+ }
28792
+ };
28793
+ walk_tree(root, visit);
28794
+ for (const struct of status.structs) walk_tree(struct, visit);
28795
+ for (const trait of status.traits) walk_tree(trait, visit);
28796
+ for (const func of status.functions) walk_tree(func, visit);
28797
+ return excluded;
28798
+ }
28799
+ function walk_tree(node, visit) {
28800
+ if (!node || typeof node !== "object") return;
28801
+ if (Array.isArray(node)) {
28802
+ for (const item of node) walk_tree(item, visit);
28803
+ return;
28804
+ }
28805
+ const n = node;
28806
+ if (typeof n.node_type !== "string") return;
28807
+ visit(n);
28808
+ const record = n;
28809
+ for (const key of Object.keys(record)) {
28810
+ if (key === "parent" || key === "scope" || key === "node_type") continue;
28811
+ walk_tree(record[key], visit);
28812
+ }
28813
+ }
28814
+ function stamp_hidden_string_lens(root, status) {
28815
+ const value_referenced = collect_value_referenced_names(root, status);
28816
+ const owners = collect_functions(root, status);
28817
+ const string_struct = status.structs.find((s) => s.name === "string");
28818
+ for (const { func, struct, is_trait_func } of owners) {
28819
+ if (func.hidden_len_stamped) continue;
28820
+ func.hidden_len_stamped = true;
28821
+ if (!func.has_body || func.statements.length === 0) continue;
28822
+ if (func.is_inline || is_trait_func) continue;
28823
+ const bare_name = func.name.replace(/^#/, "");
28824
+ if (!is_plain_identifier(func.name)) continue;
28825
+ if ([
28826
+ "main",
28827
+ "init",
28828
+ "destroy"
28829
+ ].includes(bare_name)) continue;
28830
+ if (value_referenced.has(func.name)) continue;
28831
+ if (func.params.some((p) => p.is_variadic || p.is_variadic_tuple)) continue;
28832
+ if (struct && trait_declares_method(struct, func.name, status)) continue;
28833
+ const candidates = /* @__PURE__ */ new Set();
28834
+ for (const param of func.params) if (param_is_hidden_len_candidate(param)) candidates.add(param.name);
28835
+ if (candidates.size === 0) continue;
28836
+ const reads = /* @__PURE__ */ new Set();
28837
+ const invalidated = /* @__PURE__ */ new Set();
28838
+ const declared_names = /* @__PURE__ */ new Set();
28839
+ let has_raw = false;
28840
+ const visit_children = (n, in_boundary) => {
28841
+ const record = n;
28842
+ for (const key of Object.keys(record)) {
28843
+ if (key === "parent" || key === "scope" || key === "node_type") continue;
28844
+ const child = record[key];
28845
+ if (Array.isArray(child)) {
28846
+ for (const item of child) if (item && typeof item === "object" && item.node_type) visit(item, in_boundary);
28847
+ } else if (child && typeof child === "object" && child.node_type) visit(child, in_boundary);
28848
+ }
28849
+ };
28850
+ const visit = (n, in_boundary) => {
28851
+ switch (n.node_type) {
28852
+ case "raw":
28853
+ has_raw = true;
28854
+ return;
28855
+ case "func":
28856
+ case "async_block":
28857
+ visit_children(n, true);
28858
+ return;
28859
+ case "value": {
28860
+ const v = n.value;
28861
+ if (in_boundary && is_plain_identifier(v)) invalidated.add(v);
28862
+ return;
28863
+ }
28864
+ case "access": {
28865
+ const access = n;
28866
+ if (!in_boundary && access.access.node_type === "access_field" && access.access.name === "length" && access.target.node_type === "value") {
28867
+ const name = access.target.value;
28868
+ if (name !== void 0 && candidates.has(name)) reads.add(name);
28869
+ }
28870
+ if (!in_boundary && access.access.node_type === "access_func" && access.target.node_type === "value") {
28871
+ const name = access.target.value;
28872
+ const call = access.access;
28873
+ if (name !== void 0 && candidates.has(name)) {
28874
+ const self_param = (string_struct?.functions.find((f) => f.name === call.name || f.name === `#${call.name}`))?.params?.[0];
28875
+ if (self_param?.is_self_param && (self_param.declaration === "var" || self_param.is_ref)) invalidated.add(name);
28876
+ }
28877
+ }
28878
+ visit_children(n, in_boundary);
28879
+ return;
28880
+ }
28881
+ case "assign": {
28882
+ const assign = n;
28883
+ const lhs = root_name(assign.left_value);
28884
+ if (lhs) invalidated.add(lhs);
28885
+ const swap = root_name(assign.swap);
28886
+ if (swap) invalidated.add(swap);
28887
+ visit_children(n, in_boundary);
28888
+ return;
28889
+ }
28890
+ case "declare":
28891
+ case "param": {
28892
+ const name = n.name;
28893
+ if (name) declared_names.add(name);
28894
+ visit_children(n, in_boundary);
28895
+ return;
28896
+ }
28897
+ case "for": {
28898
+ const item = n.item;
28899
+ if (item?.value) declared_names.add(item.value);
28900
+ visit_children(n, in_boundary);
28901
+ return;
28902
+ }
28903
+ case "func_call":
28904
+ case "access_func": {
28905
+ const call = n;
28906
+ for (const indices of [call.ref_param_indices, call.mov_param_indices]) for (const i of indices ?? []) {
28907
+ const arg = root_name(call.params?.[i]);
28908
+ if (arg) invalidated.add(arg);
28909
+ }
28910
+ if (call.swap_params) {
28911
+ 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);
28912
+ }
28913
+ visit_children(n, in_boundary);
28914
+ return;
28915
+ }
28916
+ default:
28917
+ visit_children(n, in_boundary);
28918
+ return;
28919
+ }
28920
+ };
28921
+ for (const stmt of func.statements) visit(stmt, false);
28922
+ if (has_raw) continue;
28923
+ for (const name of candidates) {
28924
+ if (!reads.has(name)) continue;
28925
+ if (invalidated.has(name)) continue;
28926
+ if (declared_names.has(`_${name}_len`)) continue;
28927
+ const param = func.params.find((p) => p.name === name);
28928
+ param.hidden_len = true;
28929
+ }
28930
+ }
28931
+ }
28932
+ function trait_declares_method(struct, method_name, status) {
28933
+ 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;
28934
+ return false;
28935
+ }
28936
+ function param_is_hidden_len_candidate(param) {
28937
+ 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;
28938
+ }
28939
+ //#endregion
27275
28940
  //#region ../src/check/warnings.ts
27276
28941
  /**
27277
28942
  * Lint pass run after a successful check. It walks the AST to surface three
@@ -27474,6 +29139,7 @@ function check(root) {
27474
29139
  mutated_local_names: /* @__PURE__ */ new Set()
27475
29140
  };
27476
29141
  check_node(root, status);
29142
+ stamp_hidden_string_lens(root, status);
27477
29143
  if (!status.errors.length) emit_warnings(root, status);
27478
29144
  return {
27479
29145
  ok: !status.errors.length,
@@ -29168,7 +30834,8 @@ function parse_visibility(visibility, status) {
29168
30834
  break;
29169
30835
  case "inline":
29170
30836
  consume(status);
29171
- if (peek_next(status) === "func") parse_function(visibility, status, void 0, true);
30837
+ if (peek_current(status) === "inline") consume(status);
30838
+ if (peek_current(status) === "func") parse_function(visibility, status, void 0, true);
29172
30839
  else add_error(status, "Expected func after inline", get_index(status));
29173
30840
  break;
29174
30841
  case "#": {