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/core/System/Array.nm +20 -0
- package/core/System/Buffer.nm +78 -2
- package/core/System/ClassBuffer.nm +45 -0
- package/core/System/LinkedList.nm +16 -0
- package/core/System/List.nm +21 -1
- package/core/System/Map.nm +45 -11
- package/core/System/Set.nm +41 -9
- package/core/System/String.nm +18 -0
- package/core/docs/System/Array.md +2 -0
- package/dist/index.mjs +1751 -84
- package/package.json +1 -1
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 =
|
|
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, #${
|
|
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
|
-
|
|
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" && !
|
|
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 =
|
|
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
|
|
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 +
|
|
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, #${
|
|
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
|
|
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,
|
|
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
|
|
8452
|
-
const slot = start_reg +
|
|
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 +
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
15231
|
-
|
|
15232
|
-
const
|
|
15233
|
-
|
|
15234
|
-
|
|
15235
|
-
|
|
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.
|
|
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)
|
|
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))
|
|
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
|
-
|
|
21088
|
-
|
|
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
|
-
|
|
21102
|
-
|
|
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)
|
|
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
|
-
|
|
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 (
|
|
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 "#": {
|