nomen-lang 0.6.2 → 0.6.4
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/Text/Regex.nm +416 -15
- package/core/docs/System/Text/Regex.md +1 -1
- package/dist/index.mjs +1765 -476
- package/package.json +1 -1
- package/src/index.ts +8 -56
- package/src/init.ts +12 -3
- package/src/test.ts +34 -2
package/dist/index.mjs
CHANGED
|
@@ -6352,6 +6352,12 @@ var AccessFieldNode = class extends BaseNode {
|
|
|
6352
6352
|
destructure_index;
|
|
6353
6353
|
/** `[field = new_name]` rename form — struct/class targets only. */
|
|
6354
6354
|
is_destructure_rename;
|
|
6355
|
+
/** `move` binding (`var [move a, field = move n] = expr`): the binding
|
|
6356
|
+
* TAKES OWNERSHIP of the field. The checker synthesizes the
|
|
6357
|
+
* revalidating swap (a fresh default of the field's type) once the
|
|
6358
|
+
* field resolves, so the build reuses the plain
|
|
6359
|
+
* `move field swap <replacement>` pipeline unchanged. */
|
|
6360
|
+
destructure_move;
|
|
6355
6361
|
constructor(start, name, type) {
|
|
6356
6362
|
super("access_field", start);
|
|
6357
6363
|
this.name = name;
|
|
@@ -6538,6 +6544,18 @@ var FunctionCallNode = class extends BaseNode {
|
|
|
6538
6544
|
ref_param_indices;
|
|
6539
6545
|
move_param_indices;
|
|
6540
6546
|
/**
|
|
6547
|
+
* Indices of arguments bound to a `move T` parameter where T is a
|
|
6548
|
+
* pass-by-value owning struct on a registered free function: ownership
|
|
6549
|
+
* TRANSFERS at the boundary (the callee seeds the param's string-field
|
|
6550
|
+
* records and frees them at its exit), so the caller drops its records
|
|
6551
|
+
* instead of freeing them. The plain `move_param_indices` convention
|
|
6552
|
+
* (callee deep-copies, caller frees the originals) still governs
|
|
6553
|
+
* container stores and every non-transfer shape. Stamped by
|
|
6554
|
+
* check_function_call alongside owned_value_param_indices.
|
|
6555
|
+
*/
|
|
6556
|
+
move_owned_param_indices;
|
|
6557
|
+
owned_value_param_indices;
|
|
6558
|
+
/**
|
|
6541
6559
|
* Indices of arguments whose corresponding callee parameter is a nullable
|
|
6542
6560
|
* struct value type (`T?` where T is a non-class struct). Populated during
|
|
6543
6561
|
* the check pass when the callee's signature is resolved. The build uses
|
|
@@ -6736,6 +6754,14 @@ var ValueNode = class extends BaseNode {
|
|
|
6736
6754
|
* name. Mirrors FunctionCallNode.resolved_function.
|
|
6737
6755
|
*/
|
|
6738
6756
|
resolved_function;
|
|
6757
|
+
/**
|
|
6758
|
+
* Stamped by the literal-element last-use pass (stamp_literal_element_moves):
|
|
6759
|
+
* this bare variable is a bracket-literal element (`[t, c]`) at its last
|
|
6760
|
+
* use, so the checker's tuple-element ownership rule treats it as an
|
|
6761
|
+
* inferred `move` (ownership transfers; the source's cleanup is
|
|
6762
|
+
* suppressed) instead of rejecting the aliasing copy.
|
|
6763
|
+
*/
|
|
6764
|
+
literal_last_use_move;
|
|
6739
6765
|
constructor(start, value, type) {
|
|
6740
6766
|
super("value", start);
|
|
6741
6767
|
this.value = value;
|
|
@@ -6958,6 +6984,12 @@ var FunctionNode = class extends BaseNode {
|
|
|
6958
6984
|
* are dual-natured: called under their own name AND passed as values).
|
|
6959
6985
|
*/
|
|
6960
6986
|
is_closure;
|
|
6987
|
+
/** The function's address escapes into a func-typed value (bound to a
|
|
6988
|
+
* `var func …` variable/field, passed as a func value, …) — so it may be
|
|
6989
|
+
* invoked through a pointer with a BORROWED argument: pass-by-value
|
|
6990
|
+
* normalization (owned-param record seeding / call-site shell
|
|
6991
|
+
* materialization) must not apply. Set during check. */
|
|
6992
|
+
address_escaped;
|
|
6961
6993
|
/**
|
|
6962
6994
|
* Captured outer locals (CLOSURE.md Phase 2). One entry per variable the
|
|
6963
6995
|
* lambda body references from an enclosing function; the backends emit an
|
|
@@ -7335,6 +7367,13 @@ var DeclarationNode = class extends BaseNode {
|
|
|
7335
7367
|
* struct layout. Set at check time by check_function_call; consumed by the
|
|
7336
7368
|
* build backends' declaration emitters. */
|
|
7337
7369
|
is_heap_array_literal;
|
|
7370
|
+
/** True for a root `const string[] = [...]` declaration: Mach-O arm64
|
|
7371
|
+
* forbids pointer relocations in data sections, so the file-scope data
|
|
7372
|
+
* emission lays out only the length word + zeroed element space, and the
|
|
7373
|
+
* declaration is collected into main's prologue to store the rodata
|
|
7374
|
+
* label addresses at runtime. Set by the aarch64 build_block_node root
|
|
7375
|
+
* scan; consumed by build_declaration_node's rebuild inside main. */
|
|
7376
|
+
global_runtime_init;
|
|
7338
7377
|
/** True for a hoisted call-argument temp (`_param_N`) that is a heap COPY
|
|
7339
7378
|
* of a stack-array local, bound to a heap `Array<T>` param (non-`ref`).
|
|
7340
7379
|
* `value` is the source ValueNode; the temp is materialised as a heap
|
|
@@ -7366,6 +7405,16 @@ var DeclarationNode = class extends BaseNode {
|
|
|
7366
7405
|
* paths strdup the initializer and free the temp's copy at scope exit.
|
|
7367
7406
|
* Set at build time by hoist_field_overrides. */
|
|
7368
7407
|
force_owned_string;
|
|
7408
|
+
/** True for an OWNED value-struct PARAMETER whose string fields the
|
|
7409
|
+
* callee frees at scope exit: the field accesses (and the record-free
|
|
7410
|
+
* emitted by free_scoped_declarations) go through the param POINTER
|
|
7411
|
+
* (`p->field`) instead of value syntax (`p.field`). Set at build time
|
|
7412
|
+
* when the function builder seeds pass-by-value struct params. */
|
|
7413
|
+
string_fields_via_pointer;
|
|
7414
|
+
/** True alongside string_fields_via_pointer when the caller materialized
|
|
7415
|
+
* a heap shell for the argument (pass-by-value): the callee frees the
|
|
7416
|
+
* shell itself at scope exit (`free(p)`) after freeing the fields. */
|
|
7417
|
+
owned_heap_shell;
|
|
7369
7418
|
constructor(start, visibility, declaration, name, type, value) {
|
|
7370
7419
|
super("declare", start);
|
|
7371
7420
|
this.visibility = visibility;
|
|
@@ -7986,6 +8035,38 @@ function hoist_field_overrides(value, build, status, declaration_terminator = ""
|
|
|
7986
8035
|
carrier._fov_hoisted = true;
|
|
7987
8036
|
}
|
|
7988
8037
|
//#endregion
|
|
8038
|
+
//#region ../src/build_common/has_string_fields.ts
|
|
8039
|
+
/**
|
|
8040
|
+
* Whether any field (recursively, through nested owning value structs)
|
|
8041
|
+
* is an owned `string`. Shared by BOTH backends' owning-Buffer element
|
|
8042
|
+
* specialization (the slot deep-copies strdup'd strings, so a
|
|
8043
|
+
* string-field element is owning even when `struct_needs_destroy`
|
|
8044
|
+
* ignores string-only locals) — the C comment in
|
|
8045
|
+
* owning_buffer_specialize records the divergence this once caused.
|
|
8046
|
+
* `ref` and `view T` fields are skipped: a ref aliases the caller's
|
|
8047
|
+
* storage and a view's byte copy owns nothing.
|
|
8048
|
+
*/
|
|
8049
|
+
function has_string_fields(node, status) {
|
|
8050
|
+
for (const field of node.fields) {
|
|
8051
|
+
if (field.type.is_ref) continue;
|
|
8052
|
+
if (field.type.is_view) continue;
|
|
8053
|
+
if (field.type.name === "string" && !field.type.is_array) return true;
|
|
8054
|
+
const field_struct = status.structs.find((s) => s.name === field.type.name && !s.is_simple_type && !s.is_generic);
|
|
8055
|
+
if (field_struct && !field_struct.is_class && has_string_fields(field_struct, status)) return true;
|
|
8056
|
+
}
|
|
8057
|
+
return false;
|
|
8058
|
+
}
|
|
8059
|
+
/**
|
|
8060
|
+
* The DIRECT `string` fields of a value struct (nested struct fields are not
|
|
8061
|
+
* descended into — nested string fields are outside the per-field ownership
|
|
8062
|
+
* tracking everywhere else too). Used by the return-boundary normalization:
|
|
8063
|
+
* the returned struct's unrecorded fields are strdup'd so the value the
|
|
8064
|
+
* caller receives is uniformly heap-owned.
|
|
8065
|
+
*/
|
|
8066
|
+
function direct_string_fields(node) {
|
|
8067
|
+
return node.fields.filter((f) => !f.type.is_ref && !f.type.is_view && !f.type.is_array && f.type.name === "string");
|
|
8068
|
+
}
|
|
8069
|
+
//#endregion
|
|
7989
8070
|
//#region ../src/build_common/string_return_analysis.ts
|
|
7990
8071
|
const EMPTY_SET = /* @__PURE__ */ new Set();
|
|
7991
8072
|
/**
|
|
@@ -8329,6 +8410,69 @@ function is_owned_string_branch_value(node, table) {
|
|
|
8329
8410
|
return false;
|
|
8330
8411
|
}
|
|
8331
8412
|
//#endregion
|
|
8413
|
+
//#region ../src/build_common/call_init_string_fields.ts
|
|
8414
|
+
/**
|
|
8415
|
+
* Whether a declaration-initializer expression is a value the callee's
|
|
8416
|
+
* return-boundary normalization made UNIFORMLY heap-owned, so the fresh
|
|
8417
|
+
* binding records every string field and frees them at scope exit:
|
|
8418
|
+
*
|
|
8419
|
+
* - a plain function call registered as a normalized struct returner
|
|
8420
|
+
* (the registry is populated at the callee's return sites — a callee
|
|
8421
|
+
* built later, or one that returns slot borrows, is not registered and
|
|
8422
|
+
* the binding keeps the pre-existing borrow behavior);
|
|
8423
|
+
* - an owned accessor (`pop`/`move_T`/`copy`) — move-out semantics
|
|
8424
|
+
* transfer slot-owned heap buffers to the binding.
|
|
8425
|
+
*
|
|
8426
|
+
* NOT normalized (never recorded): struct constructions (`Info(...)` — its
|
|
8427
|
+
* `#init` leaves rodata borrows and bypasses the return path) and container
|
|
8428
|
+
* BORROW accessors (`.at`/`.first`/`.slice`/`load` — the slot keeps
|
|
8429
|
+
* ownership; recording would free the container's storage).
|
|
8430
|
+
*/
|
|
8431
|
+
function is_normalized_struct_call(value, status) {
|
|
8432
|
+
if (!value) return false;
|
|
8433
|
+
if (value.node_type === "func_call") {
|
|
8434
|
+
const call = value;
|
|
8435
|
+
return !!status.normalized_struct_returners?.has(call.name);
|
|
8436
|
+
}
|
|
8437
|
+
if (value.node_type === "access") {
|
|
8438
|
+
const access = value.access;
|
|
8439
|
+
if (access?.node_type !== "access_func") return false;
|
|
8440
|
+
const name = access.name ?? "";
|
|
8441
|
+
if (is_container_borrow_accessor_name(name)) return false;
|
|
8442
|
+
if (status.normalized_struct_returners?.has(name)) return true;
|
|
8443
|
+
const target = value.target;
|
|
8444
|
+
const receiver_type = target ? target.type ?? void 0 : void 0;
|
|
8445
|
+
if (receiver_type?.name) {
|
|
8446
|
+
const label = `${mono_type_name(receiver_type)}_${name}`;
|
|
8447
|
+
if (status.normalized_struct_returners?.has(label)) return true;
|
|
8448
|
+
}
|
|
8449
|
+
return false;
|
|
8450
|
+
}
|
|
8451
|
+
return false;
|
|
8452
|
+
}
|
|
8453
|
+
/**
|
|
8454
|
+
* Record every direct string field of a call-initialized value-struct
|
|
8455
|
+
* binding (`var Info i = make()`) as heap-owned: the callee's return
|
|
8456
|
+
* normalization guarantees each field is a heap buffer, so scope-exit
|
|
8457
|
+
* auto_free must free them, and a later `i.field = …` write sees
|
|
8458
|
+
* old_was_heap and reclaims the displaced buffer instead of leaking it.
|
|
8459
|
+
* No-op for non-struct, class, view, and array types, for structs without
|
|
8460
|
+
* direct string fields, and for non-call initializers.
|
|
8461
|
+
*/
|
|
8462
|
+
function record_call_init_string_fields(node, status) {
|
|
8463
|
+
if (!is_normalized_struct_call(node.value, status)) return;
|
|
8464
|
+
const type = node.type;
|
|
8465
|
+
if (!type?.name) return;
|
|
8466
|
+
const mono_name = mono_type_name(type);
|
|
8467
|
+
if (mono_name.startsWith("_Tuple_")) return;
|
|
8468
|
+
const struct = status.structs.find((s) => s.name === mono_name && !s.is_simple_type);
|
|
8469
|
+
if (!struct || struct.is_class) return;
|
|
8470
|
+
const fields = direct_string_fields(struct);
|
|
8471
|
+
if (!fields.length) return;
|
|
8472
|
+
if (!status.heap_string_fields) status.heap_string_fields = /* @__PURE__ */ new Set();
|
|
8473
|
+
for (const field of fields) status.heap_string_fields.add(`${node.name}.${field.name}`);
|
|
8474
|
+
}
|
|
8475
|
+
//#endregion
|
|
8332
8476
|
//#region ../src/nodes/child_nodes.ts
|
|
8333
8477
|
/** Whether `value` is an AST node (carries a string `node_type`). */
|
|
8334
8478
|
function is_ast_node(value) {
|
|
@@ -8445,7 +8589,21 @@ var Walk = class {
|
|
|
8445
8589
|
reads = [];
|
|
8446
8590
|
declares = [];
|
|
8447
8591
|
assignments = [];
|
|
8592
|
+
literals = [];
|
|
8448
8593
|
loop_stack = [];
|
|
8594
|
+
literal(node, names, body) {
|
|
8595
|
+
const loop_ranges = [...this.loop_stack];
|
|
8596
|
+
const enter = ++this.stamp;
|
|
8597
|
+
body();
|
|
8598
|
+
const exit = ++this.stamp;
|
|
8599
|
+
this.literals.push({
|
|
8600
|
+
node,
|
|
8601
|
+
enter,
|
|
8602
|
+
exit,
|
|
8603
|
+
loop_ranges,
|
|
8604
|
+
names
|
|
8605
|
+
});
|
|
8606
|
+
}
|
|
8449
8607
|
interval(body) {
|
|
8450
8608
|
const enter = ++this.stamp;
|
|
8451
8609
|
body();
|
|
@@ -8582,6 +8740,80 @@ function classify_declare(decl, walk, param_names, func_label) {
|
|
|
8582
8740
|
desc: `var ${node.name} = ${source}`
|
|
8583
8741
|
};
|
|
8584
8742
|
}
|
|
8743
|
+
/**
|
|
8744
|
+
* Stamps `literal_last_use_move` on bracket-literal elements (`[a, b]`) that
|
|
8745
|
+
* are bare `var` locals at their LAST USE: the literal then TRANSFERS
|
|
8746
|
+
* ownership of those locals instead of aliasing them (the checker's
|
|
8747
|
+
* tuple-element ownership rule consumes the stamp as an inferred `move`).
|
|
8748
|
+
*
|
|
8749
|
+
* Same conservative posture as the string pass: it may MISS a last use (the
|
|
8750
|
+
* element then needs an explicit `move`), never invent one — reads after the
|
|
8751
|
+
* literal, reads under an enclosing loop whose binding outlives the loop,
|
|
8752
|
+
* reads in spawn/async subtrees, any re-assignment of the source, and any
|
|
8753
|
+
* raw `#arch` body in the function all refuse the inference. Types are NOT
|
|
8754
|
+
* consulted here (this runs before checking) — the checker applies the
|
|
8755
|
+
* owning-type filter when consuming the stamp.
|
|
8756
|
+
*
|
|
8757
|
+
* Only the FIRST occurrence of a name per literal is stamped: two elements
|
|
8758
|
+
* moving the same local would alias each other inside the tuple.
|
|
8759
|
+
*/
|
|
8760
|
+
function stamp_literal_element_moves(root) {
|
|
8761
|
+
let count = 0;
|
|
8762
|
+
const visit_functions = (node) => {
|
|
8763
|
+
if (node.node_type === "func") count += scan_function_literal_element_moves(node);
|
|
8764
|
+
for (const child of child_nodes(node)) visit_functions(child);
|
|
8765
|
+
};
|
|
8766
|
+
visit_functions(root);
|
|
8767
|
+
return count;
|
|
8768
|
+
}
|
|
8769
|
+
function scan_function_literal_element_moves(func) {
|
|
8770
|
+
if (!func.statements?.length) return 0;
|
|
8771
|
+
const walk = new Walk();
|
|
8772
|
+
const param_names = new Set(func.params.map((p) => p.name));
|
|
8773
|
+
for (const stmt of func.statements) walk_stmt(walk, stmt);
|
|
8774
|
+
if (walk.has_raw) return 0;
|
|
8775
|
+
let count = 0;
|
|
8776
|
+
for (const site of walk.literals) {
|
|
8777
|
+
const eligible = [];
|
|
8778
|
+
for (const name of site.names) {
|
|
8779
|
+
if (eligible.includes(name)) continue;
|
|
8780
|
+
if (param_names.has(name)) continue;
|
|
8781
|
+
const binding = walk.declares.filter((d) => d.name === name && d.enter < site.enter).at(-1);
|
|
8782
|
+
if (!binding || binding.declaration !== "var") continue;
|
|
8783
|
+
if (walk.writes.filter((w) => w.name === name).length > 1) continue;
|
|
8784
|
+
let killed = false;
|
|
8785
|
+
for (const r of walk.reads) {
|
|
8786
|
+
if (r.name !== name) continue;
|
|
8787
|
+
if (r.opaque) {
|
|
8788
|
+
killed = true;
|
|
8789
|
+
break;
|
|
8790
|
+
}
|
|
8791
|
+
if (r.enter > site.exit) {
|
|
8792
|
+
killed = true;
|
|
8793
|
+
break;
|
|
8794
|
+
}
|
|
8795
|
+
if (r.enter >= site.enter && r.enter <= site.exit) continue;
|
|
8796
|
+
for (const loop of site.loop_ranges) if (r.enter >= loop.enter && r.enter <= loop.exit) {
|
|
8797
|
+
if (!(binding.enter >= loop.enter && binding.exit <= loop.exit)) {
|
|
8798
|
+
killed = true;
|
|
8799
|
+
break;
|
|
8800
|
+
}
|
|
8801
|
+
}
|
|
8802
|
+
if (killed) break;
|
|
8803
|
+
}
|
|
8804
|
+
if (!killed) eligible.push(name);
|
|
8805
|
+
}
|
|
8806
|
+
const values = site.node.values;
|
|
8807
|
+
for (const v of values) {
|
|
8808
|
+
const vn = v;
|
|
8809
|
+
if (vn.node_type === "value" && typeof vn.value === "string" && eligible.includes(vn.value)) {
|
|
8810
|
+
vn.literal_last_use_move = true;
|
|
8811
|
+
count++;
|
|
8812
|
+
}
|
|
8813
|
+
}
|
|
8814
|
+
}
|
|
8815
|
+
return count;
|
|
8816
|
+
}
|
|
8585
8817
|
function walk_stmt(walk, node) {
|
|
8586
8818
|
switch (node.node_type) {
|
|
8587
8819
|
case "declare": {
|
|
@@ -8718,7 +8950,10 @@ function walk_expr$1(walk, node) {
|
|
|
8718
8950
|
}
|
|
8719
8951
|
case "array": {
|
|
8720
8952
|
const arr = node;
|
|
8721
|
-
|
|
8953
|
+
const names = arr.values.map((v) => v).filter((v) => v.node_type === "value" && typeof v.value === "string" && /^[A-Za-z_][A-Za-z0-9_]*$/.test(v.value)).map((v) => v.value);
|
|
8954
|
+
walk.literal(node, names, () => {
|
|
8955
|
+
for (const v of arr.values) walk_expr$1(walk, v);
|
|
8956
|
+
});
|
|
8722
8957
|
return;
|
|
8723
8958
|
}
|
|
8724
8959
|
default: for (const child of child_nodes(node)) walk_expr$1(walk, child);
|
|
@@ -10913,6 +11148,85 @@ function array_struct_name(type, status) {
|
|
|
10913
11148
|
return status.structs.find((s) => s.name === mono && !s.is_generic) ? mono : void 0;
|
|
10914
11149
|
}
|
|
10915
11150
|
//#endregion
|
|
11151
|
+
//#region ../src/build_common/destroy_analysis.ts
|
|
11152
|
+
/**
|
|
11153
|
+
* Whether any field of the struct is an enum-with-data value owning heap
|
|
11154
|
+
* payloads (a string payload — always heap, case construction strdups it —
|
|
11155
|
+
* or a class/trait reference payload — construction transfers ownership).
|
|
11156
|
+
* Such a field must be reclaimed when the struct dies.
|
|
11157
|
+
*/
|
|
11158
|
+
function struct_has_owning_enum_field(struct, table) {
|
|
11159
|
+
for (const field of struct.fields) {
|
|
11160
|
+
if (field.type.is_ref) continue;
|
|
11161
|
+
const en = table.enums?.find((e) => e.name === field.type.name);
|
|
11162
|
+
if (!en?.has_associated_data) continue;
|
|
11163
|
+
for (const c of en.cases) for (const p of c.params) {
|
|
11164
|
+
if (p.type.name === "string" && !p.type.is_array) return true;
|
|
11165
|
+
if (table.traits?.some((t) => t.name === p.type.name)) return true;
|
|
11166
|
+
if (table.structs.find((s) => s.name === p.type.name)?.is_class) return true;
|
|
11167
|
+
}
|
|
11168
|
+
}
|
|
11169
|
+
return false;
|
|
11170
|
+
}
|
|
11171
|
+
/**
|
|
11172
|
+
* Whether a struct declares its own `#destroy` (user side effects /
|
|
11173
|
+
* resource release).
|
|
11174
|
+
*/
|
|
11175
|
+
function has_destroy(struct) {
|
|
11176
|
+
return !!struct.functions.find((f) => f.name === "#destroy");
|
|
11177
|
+
}
|
|
11178
|
+
/**
|
|
11179
|
+
* Whether any field (recursively) requires destroy handling — a class
|
|
11180
|
+
* field (heap instance to reclaim) or a nested struct with its own
|
|
11181
|
+
* `#destroy` (or nested fields that do). Deliberately excludes `string`
|
|
11182
|
+
* fields: a struct local whose only owning fields are strings is NOT
|
|
11183
|
+
* auto-destroyed at scope exit (the strings may be raw rodata arg
|
|
11184
|
+
* pointers, not heap) — see `struct_needs_auto_destroy` for the broader
|
|
11185
|
+
* form used to decide whether a `<Struct>_destroy` function is generated.
|
|
11186
|
+
*/
|
|
11187
|
+
function has_struct_fields_with_destroy(struct, table) {
|
|
11188
|
+
for (const field of struct.fields) {
|
|
11189
|
+
if (field.type.is_ref) continue;
|
|
11190
|
+
const field_struct = resolve_struct_type(field.type, table);
|
|
11191
|
+
if (!field_struct) continue;
|
|
11192
|
+
if (field_struct.is_class) return true;
|
|
11193
|
+
if (has_destroy(field_struct)) return true;
|
|
11194
|
+
if (has_struct_fields_with_destroy(field_struct, table)) return true;
|
|
11195
|
+
}
|
|
11196
|
+
return false;
|
|
11197
|
+
}
|
|
11198
|
+
/**
|
|
11199
|
+
* Whether a struct (or any embedded struct field, recursively) needs a
|
|
11200
|
+
* destroy call at scope exit — it has a `#destroy`, a class-typed field,
|
|
11201
|
+
* or a nested struct field that itself needs destroying. This does NOT
|
|
11202
|
+
* count `string` fields (see `has_struct_fields_with_destroy`).
|
|
11203
|
+
*/
|
|
11204
|
+
function struct_needs_destroy(struct, table) {
|
|
11205
|
+
return has_destroy(struct) || has_struct_fields_with_destroy(struct, table) || struct_has_owning_enum_field(struct, table);
|
|
11206
|
+
}
|
|
11207
|
+
/**
|
|
11208
|
+
* Whether a struct needs an auto-generated `<Struct>_destroy` function.
|
|
11209
|
+
* Broader than `struct_needs_destroy`: also returns true for structs with
|
|
11210
|
+
* `string` fields, because a Buffer<T> for such a struct deep-copies the
|
|
11211
|
+
* strings into slots (strdup on store) and the per-element destroy must
|
|
11212
|
+
* free them. The generated destroy frees each string field; it is called
|
|
11213
|
+
* from Buffer's specialized #destroy / replace_T, NOT from struct local
|
|
11214
|
+
* scope exit (`struct_needs_destroy` governs that path and excludes
|
|
11215
|
+
* strings).
|
|
11216
|
+
*/
|
|
11217
|
+
function struct_needs_auto_destroy(struct, table) {
|
|
11218
|
+
if (struct_needs_destroy(struct, table)) return true;
|
|
11219
|
+
if (struct_has_owning_enum_field(struct, table)) return true;
|
|
11220
|
+
for (const field of struct.fields) {
|
|
11221
|
+
if (field.type.is_ref) continue;
|
|
11222
|
+
if (field.type.is_view) continue;
|
|
11223
|
+
if (field.type.name === "string" && !field.type.is_array) return true;
|
|
11224
|
+
const field_struct = resolve_struct_type(field.type, table);
|
|
11225
|
+
if (field_struct && !field_struct.is_class && struct_needs_auto_destroy(field_struct, table)) return true;
|
|
11226
|
+
}
|
|
11227
|
+
return false;
|
|
11228
|
+
}
|
|
11229
|
+
//#endregion
|
|
10916
11230
|
//#region ../src/build_common/scan_force_heap_strings.ts
|
|
10917
11231
|
function scan_force_heap_strings(statements, structs) {
|
|
10918
11232
|
const result = /* @__PURE__ */ new Set();
|
|
@@ -13391,85 +13705,6 @@ function emit_strdup(status) {
|
|
|
13391
13705
|
emit_asm(status, `ldr x1, [sp], #16\n`);
|
|
13392
13706
|
}
|
|
13393
13707
|
//#endregion
|
|
13394
|
-
//#region ../src/build_common/destroy_analysis.ts
|
|
13395
|
-
/**
|
|
13396
|
-
* Whether any field of the struct is an enum-with-data value owning heap
|
|
13397
|
-
* payloads (a string payload — always heap, case construction strdups it —
|
|
13398
|
-
* or a class/trait reference payload — construction transfers ownership).
|
|
13399
|
-
* Such a field must be reclaimed when the struct dies.
|
|
13400
|
-
*/
|
|
13401
|
-
function struct_has_owning_enum_field(struct, table) {
|
|
13402
|
-
for (const field of struct.fields) {
|
|
13403
|
-
if (field.type.is_ref) continue;
|
|
13404
|
-
const en = table.enums?.find((e) => e.name === field.type.name);
|
|
13405
|
-
if (!en?.has_associated_data) continue;
|
|
13406
|
-
for (const c of en.cases) for (const p of c.params) {
|
|
13407
|
-
if (p.type.name === "string" && !p.type.is_array) return true;
|
|
13408
|
-
if (table.traits?.some((t) => t.name === p.type.name)) return true;
|
|
13409
|
-
if (table.structs.find((s) => s.name === p.type.name)?.is_class) return true;
|
|
13410
|
-
}
|
|
13411
|
-
}
|
|
13412
|
-
return false;
|
|
13413
|
-
}
|
|
13414
|
-
/**
|
|
13415
|
-
* Whether a struct declares its own `#destroy` (user side effects /
|
|
13416
|
-
* resource release).
|
|
13417
|
-
*/
|
|
13418
|
-
function has_destroy(struct) {
|
|
13419
|
-
return !!struct.functions.find((f) => f.name === "#destroy");
|
|
13420
|
-
}
|
|
13421
|
-
/**
|
|
13422
|
-
* Whether any field (recursively) requires destroy handling — a class
|
|
13423
|
-
* field (heap instance to reclaim) or a nested struct with its own
|
|
13424
|
-
* `#destroy` (or nested fields that do). Deliberately excludes `string`
|
|
13425
|
-
* fields: a struct local whose only owning fields are strings is NOT
|
|
13426
|
-
* auto-destroyed at scope exit (the strings may be raw rodata arg
|
|
13427
|
-
* pointers, not heap) — see `struct_needs_auto_destroy` for the broader
|
|
13428
|
-
* form used to decide whether a `<Struct>_destroy` function is generated.
|
|
13429
|
-
*/
|
|
13430
|
-
function has_struct_fields_with_destroy(struct, table) {
|
|
13431
|
-
for (const field of struct.fields) {
|
|
13432
|
-
if (field.type.is_ref) continue;
|
|
13433
|
-
const field_struct = resolve_struct_type(field.type, table);
|
|
13434
|
-
if (!field_struct) continue;
|
|
13435
|
-
if (field_struct.is_class) return true;
|
|
13436
|
-
if (has_destroy(field_struct)) return true;
|
|
13437
|
-
if (has_struct_fields_with_destroy(field_struct, table)) return true;
|
|
13438
|
-
}
|
|
13439
|
-
return false;
|
|
13440
|
-
}
|
|
13441
|
-
/**
|
|
13442
|
-
* Whether a struct (or any embedded struct field, recursively) needs a
|
|
13443
|
-
* destroy call at scope exit — it has a `#destroy`, a class-typed field,
|
|
13444
|
-
* or a nested struct field that itself needs destroying. This does NOT
|
|
13445
|
-
* count `string` fields (see `has_struct_fields_with_destroy`).
|
|
13446
|
-
*/
|
|
13447
|
-
function struct_needs_destroy(struct, table) {
|
|
13448
|
-
return has_destroy(struct) || has_struct_fields_with_destroy(struct, table) || struct_has_owning_enum_field(struct, table);
|
|
13449
|
-
}
|
|
13450
|
-
/**
|
|
13451
|
-
* Whether a struct needs an auto-generated `<Struct>_destroy` function.
|
|
13452
|
-
* Broader than `struct_needs_destroy`: also returns true for structs with
|
|
13453
|
-
* `string` fields, because a Buffer<T> for such a struct deep-copies the
|
|
13454
|
-
* strings into slots (strdup on store) and the per-element destroy must
|
|
13455
|
-
* free them. The generated destroy frees each string field; it is called
|
|
13456
|
-
* from Buffer's specialized #destroy / replace_T, NOT from struct local
|
|
13457
|
-
* scope exit (`struct_needs_destroy` governs that path and excludes
|
|
13458
|
-
* strings).
|
|
13459
|
-
*/
|
|
13460
|
-
function struct_needs_auto_destroy(struct, table) {
|
|
13461
|
-
if (struct_needs_destroy(struct, table)) return true;
|
|
13462
|
-
if (struct_has_owning_enum_field(struct, table)) return true;
|
|
13463
|
-
for (const field of struct.fields) {
|
|
13464
|
-
if (field.type.is_ref) continue;
|
|
13465
|
-
if (field.type.is_view) continue;
|
|
13466
|
-
if (field.type.name === "string" && !field.type.is_array) return true;
|
|
13467
|
-
const field_struct = resolve_struct_type(field.type, table);
|
|
13468
|
-
if (field_struct && !field_struct.is_class && struct_needs_auto_destroy(field_struct, table)) return true;
|
|
13469
|
-
}
|
|
13470
|
-
return false;
|
|
13471
|
-
}
|
|
13472
|
-
//#endregion
|
|
13473
13708
|
//#region ../src/build_common/temp_anchor_consolidation.ts
|
|
13474
13709
|
/**
|
|
13475
13710
|
* Shared OWNERSHIP decision for hoisted call-temporary consolidation.
|
|
@@ -13504,6 +13739,144 @@ function superseded_param_temp_names(table, call_node, result_type_name) {
|
|
|
13504
13739
|
return names;
|
|
13505
13740
|
}
|
|
13506
13741
|
//#endregion
|
|
13742
|
+
//#region ../src/build_aarch64/utils/string_pair.ts
|
|
13743
|
+
/**
|
|
13744
|
+
* Load the string variable `name` into the consecutive register pair
|
|
13745
|
+
* (base, base+1) — default x0/x1. Handles the plain slot case; callers
|
|
13746
|
+
* that have register-allocated or param forms handle theirs first.
|
|
13747
|
+
*/
|
|
13748
|
+
function emit_string_pair_load(status, name, base = "x0") {
|
|
13749
|
+
const offset = status.stack_offsets?.get(name);
|
|
13750
|
+
if (offset === void 0) return false;
|
|
13751
|
+
const w = base.startsWith("x") ? base : "x0";
|
|
13752
|
+
const n = parseInt(w.substring(1), 10);
|
|
13753
|
+
if (offset + 8 > 504) {
|
|
13754
|
+
emit_asm(status, `ldr ${w}, [x29, #${offset}]\n`);
|
|
13755
|
+
emit_asm(status, `ldr x${n + 1}, [x29, #${offset + 8}]\n`);
|
|
13756
|
+
return true;
|
|
13757
|
+
}
|
|
13758
|
+
emit_asm(status, `ldp ${w}, x${n + 1}, [x29, #${offset}]\n`);
|
|
13759
|
+
return true;
|
|
13760
|
+
}
|
|
13761
|
+
/** Load the pair at [addr_reg, #offset] into (base, base+1). */
|
|
13762
|
+
function emit_string_pair_load_at(status, addr_reg, offset, base = "x0") {
|
|
13763
|
+
const w = base.startsWith("x") ? base : "x0";
|
|
13764
|
+
emit_asm(status, `ldp ${w}, x${parseInt(w.substring(1), 10) + 1}, [${addr_reg}, #${offset}]\n`);
|
|
13765
|
+
}
|
|
13766
|
+
/**
|
|
13767
|
+
* strdup the fat string in (x0=ptr, x1=len): preserve the len half across
|
|
13768
|
+
* the call and leave the owned copy's pair in (x0, x1). The caller keeps
|
|
13769
|
+
* ownership of the original.
|
|
13770
|
+
*/
|
|
13771
|
+
function emit_strdup_string(status) {
|
|
13772
|
+
emit_asm(status, `str x1, [sp, #-16]!\n`);
|
|
13773
|
+
emit_asm(status, status.audit ? `bl _nomen_strdup_wrap\n` : `bl _strdup\n`);
|
|
13774
|
+
emit_asm(status, `ldr x1, [sp], #16\n`);
|
|
13775
|
+
}
|
|
13776
|
+
/**
|
|
13777
|
+
* Pair load/store at [x29, #offset] with an arbitrary register pair —
|
|
13778
|
+
* splits into two single-word accesses beyond the ldp/stp +504 range.
|
|
13779
|
+
*/
|
|
13780
|
+
function emit_pair_load_x29(status, offset, a = "x0", b = "x1") {
|
|
13781
|
+
if (offset + 8 > 504) {
|
|
13782
|
+
emit_asm(status, `ldr ${a}, [x29, #${offset}]\n`);
|
|
13783
|
+
emit_asm(status, `ldr ${b}, [x29, #${offset + 8}]\n`);
|
|
13784
|
+
return;
|
|
13785
|
+
}
|
|
13786
|
+
emit_asm(status, `ldp ${a}, ${b}, [x29, #${offset}]\n`);
|
|
13787
|
+
}
|
|
13788
|
+
function emit_pair_store_x29(status, offset, a = "x0", b = "x1") {
|
|
13789
|
+
if (offset + 8 > 504) {
|
|
13790
|
+
emit_asm(status, `str ${a}, [x29, #${offset}]\n`);
|
|
13791
|
+
emit_asm(status, `str ${b}, [x29, #${offset + 8}]\n`);
|
|
13792
|
+
return;
|
|
13793
|
+
}
|
|
13794
|
+
emit_asm(status, `stp ${a}, ${b}, [x29, #${offset}]\n`);
|
|
13795
|
+
}
|
|
13796
|
+
/**
|
|
13797
|
+
* Owning specializations for Array<string>'s raw T-generic bodies
|
|
13798
|
+
* (`with` / `set`), replacing the shared-pointer copies with per-slot deep
|
|
13799
|
+
* copies — the aarch64 analog of Array.nm's `#if T_NEEDS_STRDUP` C branch.
|
|
13800
|
+
*
|
|
13801
|
+
* Conventions (matching Array.nm raw bodies):
|
|
13802
|
+
* - `with` (static): x0/x1 = value pair, x2 = count. Returns x0 = heap
|
|
13803
|
+
* buffer ([ptr] = length, elements at +8..).
|
|
13804
|
+
* - `set` (method): x19 = first element, x1 = index, x2/x3 = value pair.
|
|
13805
|
+
* Bounds are guaranteed by the Nomen-level constraints.
|
|
13806
|
+
*/
|
|
13807
|
+
function emit_owning_array_string_specialize(func_name, status, self_reg = "x19") {
|
|
13808
|
+
if (func_name === "with") {
|
|
13809
|
+
emit_asm(status, `stp x19, x20, [sp, #-16]!\n`);
|
|
13810
|
+
emit_asm(status, `stp x21, x22, [sp, #-16]!\n`);
|
|
13811
|
+
emit_asm(status, `stp x24, x25, [sp, #-16]!\n`);
|
|
13812
|
+
emit_asm(status, `mov x19, x0\n`);
|
|
13813
|
+
emit_asm(status, `mov x20, x1\n`);
|
|
13814
|
+
emit_asm(status, `mov x21, x2\n`);
|
|
13815
|
+
emit_asm(status, `add x0, x21, #1\n`);
|
|
13816
|
+
emit_asm(status, `lsl x0, x0, #4\n`);
|
|
13817
|
+
emit_malloc(status);
|
|
13818
|
+
emit_asm(status, `mov x22, x0\n`);
|
|
13819
|
+
emit_asm(status, `str x21, [x22]\n`);
|
|
13820
|
+
emit_asm(status, `mov x23, #8\n`);
|
|
13821
|
+
emit_asm(status, `mov x24, #0\n`);
|
|
13822
|
+
emit_asm(status, `.Larr_str_with_loop:\n`);
|
|
13823
|
+
emit_asm(status, `cmp x24, x21\n`);
|
|
13824
|
+
emit_asm(status, `b.ge .Larr_str_with_done\n`);
|
|
13825
|
+
emit_asm(status, `mov x0, x19\n`);
|
|
13826
|
+
emit_strdup(status);
|
|
13827
|
+
emit_asm(status, `add x25, x22, x23\n`);
|
|
13828
|
+
emit_asm(status, `stp x0, x20, [x25]\n`);
|
|
13829
|
+
emit_asm(status, `add x23, x23, #16\n`);
|
|
13830
|
+
emit_asm(status, `add x24, x24, #1\n`);
|
|
13831
|
+
emit_asm(status, `b .Larr_str_with_loop\n`);
|
|
13832
|
+
emit_asm(status, `.Larr_str_with_done:\n`);
|
|
13833
|
+
emit_asm(status, `mov x0, x22\n`);
|
|
13834
|
+
emit_asm(status, `ldp x24, x25, [sp], #16\n`);
|
|
13835
|
+
emit_asm(status, `ldp x21, x22, [sp], #16\n`);
|
|
13836
|
+
emit_asm(status, `ldp x19, x20, [sp], #16\n`);
|
|
13837
|
+
return true;
|
|
13838
|
+
}
|
|
13839
|
+
if (func_name === "at" || func_name === "first" || func_name === "at_end") {
|
|
13840
|
+
emit_asm(status, `stp x20, x21, [sp, #-16]!\n`);
|
|
13841
|
+
if (func_name === "at") {
|
|
13842
|
+
emit_asm(status, `lsl x9, x1, #4\n`);
|
|
13843
|
+
emit_asm(status, `add x9, ${self_reg}, x9\n`);
|
|
13844
|
+
} else if (func_name === "at_end") {
|
|
13845
|
+
emit_asm(status, `ldr x9, [${self_reg}, #-8]\n`);
|
|
13846
|
+
emit_asm(status, `sub x9, x9, #1\n`);
|
|
13847
|
+
emit_asm(status, `lsl x9, x9, #4\n`);
|
|
13848
|
+
emit_asm(status, `add x9, ${self_reg}, x9\n`);
|
|
13849
|
+
} else emit_asm(status, `add x9, ${self_reg}, #0\n`);
|
|
13850
|
+
emit_asm(status, `ldp x0, x1, [x9]\n`);
|
|
13851
|
+
emit_asm(status, `ldp x20, x21, [sp], #16\n`);
|
|
13852
|
+
return true;
|
|
13853
|
+
}
|
|
13854
|
+
if (func_name === "set") {
|
|
13855
|
+
emit_asm(status, `stp x20, x21, [sp, #-16]!\n`);
|
|
13856
|
+
emit_asm(status, `lsl x9, x1, #4\n`);
|
|
13857
|
+
emit_asm(status, `add x9, ${self_reg}, x9\n`);
|
|
13858
|
+
emit_asm(status, `ldr x0, [x9]\n`);
|
|
13859
|
+
emit_asm(status, `mov x20, x2\n`);
|
|
13860
|
+
emit_asm(status, `mov x21, x3\n`);
|
|
13861
|
+
emit_free(status);
|
|
13862
|
+
emit_asm(status, `mov x0, x20\n`);
|
|
13863
|
+
emit_strdup(status);
|
|
13864
|
+
emit_asm(status, `str x0, [x9]\n`);
|
|
13865
|
+
emit_asm(status, `str x21, [x9, #8]\n`);
|
|
13866
|
+
emit_asm(status, `ldp x20, x21, [sp], #16\n`);
|
|
13867
|
+
return true;
|
|
13868
|
+
}
|
|
13869
|
+
return false;
|
|
13870
|
+
}
|
|
13871
|
+
/** Pair store to [base_reg, #offset] with stur fallback (misaligned/far). */
|
|
13872
|
+
function emit_pair_store_to(status, base_reg, offset, a, b) {
|
|
13873
|
+
if (offset % 8 === 0 && offset + 8 <= 504 && offset >= 0) emit_asm(status, `stp ${a}, ${b}, [${base_reg}, #${offset}]\n`);
|
|
13874
|
+
else {
|
|
13875
|
+
emit_asm(status, `stur ${a}, [${base_reg}, #${offset}]\n`);
|
|
13876
|
+
emit_asm(status, `stur ${b}, [${base_reg}, #${offset + 8}]\n`);
|
|
13877
|
+
}
|
|
13878
|
+
}
|
|
13879
|
+
//#endregion
|
|
13507
13880
|
//#region ../src/build_aarch64/utils/auto_destroy.ts
|
|
13508
13881
|
/**
|
|
13509
13882
|
* Whether a declaration's initializer is a non-`move` FIELD ACCESS — a shallow
|
|
@@ -13540,12 +13913,27 @@ function clear_heap_string_fields(status, var_name) {
|
|
|
13540
13913
|
* fields are unconditionally heap and freed by the destroy path. Called from
|
|
13541
13914
|
* emit_destroy_for_decl and directly from cleanup loops that skip moved
|
|
13542
13915
|
* declarations before reaching it.
|
|
13916
|
+
*
|
|
13917
|
+
* For a nullable struct local (`is_nullable`), the frees are guarded on the
|
|
13918
|
+
* companion `_has` flag: the null representation leaves the value bytes
|
|
13919
|
+
* uninitialized (a `return null` callee writes only the flag), so reading a
|
|
13920
|
+
* field pointer without the guard frees stale stack garbage. Mirrors the C
|
|
13921
|
+
* backend's `if (<name>_has)` auto-free guard.
|
|
13543
13922
|
*/
|
|
13544
|
-
function release_heap_string_fields(status, decl_name, decl_type_name) {
|
|
13923
|
+
function release_heap_string_fields(status, decl_name, decl_type_name, is_nullable) {
|
|
13545
13924
|
if (!status.heap_string_fields?.size) return;
|
|
13546
13925
|
const prefix = `${decl_name}.`;
|
|
13547
13926
|
const fields = Array.from(status.heap_string_fields).filter((k) => k.startsWith(prefix)).map((k) => k.slice(prefix.length));
|
|
13548
13927
|
if (!fields.length) return;
|
|
13928
|
+
let skip_label;
|
|
13929
|
+
if (is_nullable) {
|
|
13930
|
+
const flag_off = status.stack_offsets?.get(has_flag_name(decl_name));
|
|
13931
|
+
if (flag_off !== void 0 && flag_off >= 0) {
|
|
13932
|
+
skip_label = `.Lskip_rhs_${status.label_counter = (status.label_counter ?? 0) + 1}`;
|
|
13933
|
+
emit_asm(status, `ldr x0, [x29, #${flag_off}]\n`);
|
|
13934
|
+
emit_asm(status, `cbz x0, ${skip_label}\n`);
|
|
13935
|
+
}
|
|
13936
|
+
}
|
|
13549
13937
|
for (const field of fields) {
|
|
13550
13938
|
const offset = get_field_offset(decl_type_name, field, status);
|
|
13551
13939
|
emit_var_address(status, "x0", decl_name);
|
|
@@ -13553,6 +13941,7 @@ function release_heap_string_fields(status, decl_name, decl_type_name) {
|
|
|
13553
13941
|
emit_free(status);
|
|
13554
13942
|
status.heap_string_fields.delete(`${decl_name}.${field}`);
|
|
13555
13943
|
}
|
|
13944
|
+
if (skip_label) emit_asm(status, `${skip_label}:\n`);
|
|
13556
13945
|
}
|
|
13557
13946
|
/**
|
|
13558
13947
|
* Swap in a fresh scoped_declarations frame for a nested scope (if/while/
|
|
@@ -13911,7 +14300,7 @@ function is_struct_type$4(type_name, status) {
|
|
|
13911
14300
|
}
|
|
13912
14301
|
function emit_destroy_for_decl(status, decl_name, decl_type_name, addr_offset, type_args, is_nullable) {
|
|
13913
14302
|
const moved = status.moved ?? /* @__PURE__ */ new Set();
|
|
13914
|
-
release_heap_string_fields(status, decl_name, decl_type_name);
|
|
14303
|
+
release_heap_string_fields(status, decl_name, decl_type_name, is_nullable);
|
|
13915
14304
|
if (moved.has(decl_name)) return;
|
|
13916
14305
|
if (decl_type_name === "func" && status.stack_offsets?.has(decl_name)) {
|
|
13917
14306
|
const skip = `.Lclosure_free_done_${status.label_counter = (status.label_counter ?? 0) + 1}`;
|
|
@@ -13964,11 +14353,19 @@ function emit_destroy_for_decl(status, decl_name, decl_type_name, addr_offset, t
|
|
|
13964
14353
|
return;
|
|
13965
14354
|
}
|
|
13966
14355
|
const guard_null = !!is_nullable && struct_type.is_class && addr_offset === void 0;
|
|
14356
|
+
const guard_has_flag = !!is_nullable && !struct_type.is_class && addr_offset === void 0;
|
|
13967
14357
|
let skip_label;
|
|
13968
14358
|
if (guard_null) {
|
|
13969
14359
|
emit_var_load(status, "x0", decl_name, 8);
|
|
13970
14360
|
skip_label = `.Lskip_nd_${status.label_counter = (status.label_counter ?? 0) + 1}`;
|
|
13971
14361
|
emit_asm(status, `cbz x0, ${skip_label}\n`);
|
|
14362
|
+
} else if (guard_has_flag && (has_destroy(struct_type) || struct_needs_destroy(struct_type, status))) {
|
|
14363
|
+
const flag_off = status.stack_offsets?.get(has_flag_name(decl_name));
|
|
14364
|
+
if (flag_off !== void 0 && flag_off >= 0) {
|
|
14365
|
+
skip_label = `.Lskip_nd_${status.label_counter = (status.label_counter ?? 0) + 1}`;
|
|
14366
|
+
emit_asm(status, `ldr x0, [x29, #${flag_off}]\n`);
|
|
14367
|
+
emit_asm(status, `cbz x0, ${skip_label}\n`);
|
|
14368
|
+
}
|
|
13972
14369
|
}
|
|
13973
14370
|
if (has_destroy(struct_type) || struct_type.is_class) {
|
|
13974
14371
|
if (struct_type.is_class) {
|
|
@@ -14123,13 +14520,51 @@ function resolve_decl_struct(decl, status) {
|
|
|
14123
14520
|
}
|
|
14124
14521
|
}
|
|
14125
14522
|
}
|
|
14523
|
+
/**
|
|
14524
|
+
* Emit the reclaim of a nullable struct local's OLD value before a
|
|
14525
|
+
* reassignment (`p = <value>` / `p = null`): when the slot's `_has` flag is
|
|
14526
|
+
* set, free every heap string field currently recorded for it. The frees are
|
|
14527
|
+
* emitted reading the slot's CURRENT bytes, so this must run before the new
|
|
14528
|
+
* value is copied in. Records are left intact — the caller retargets them
|
|
14529
|
+
* for the new value (the scope-exit release still owns the final state).
|
|
14530
|
+
* Returns true when anything was emitted.
|
|
14531
|
+
*/
|
|
14532
|
+
function emit_recorded_field_reclaim_for_slot(status, decl_name, decl_type_name) {
|
|
14533
|
+
if (!status.heap_string_fields?.size) return false;
|
|
14534
|
+
const prefix = `${decl_name}.`;
|
|
14535
|
+
const fields = Array.from(status.heap_string_fields).filter((k) => k.startsWith(prefix)).map((k) => k.slice(prefix.length));
|
|
14536
|
+
if (!fields.length) return false;
|
|
14537
|
+
const flag_off = status.stack_offsets?.get(has_flag_name(decl_name));
|
|
14538
|
+
if (flag_off === void 0 || flag_off < 0) return false;
|
|
14539
|
+
const skip = `.Lskip_reclaim_${status.label_counter = (status.label_counter ?? 0) + 1}`;
|
|
14540
|
+
emit_asm(status, `ldr x0, [x29, #${flag_off}]\n`);
|
|
14541
|
+
emit_asm(status, `cbz x0, ${skip}\n`);
|
|
14542
|
+
for (const field of fields) {
|
|
14543
|
+
const offset = get_field_offset(decl_type_name, field, status);
|
|
14544
|
+
emit_var_address(status, "x0", decl_name);
|
|
14545
|
+
emit_asm(status, `ldr x0, [x0, #${offset}]\n`);
|
|
14546
|
+
emit_free(status);
|
|
14547
|
+
}
|
|
14548
|
+
emit_asm(status, `${skip}:\n`);
|
|
14549
|
+
return true;
|
|
14550
|
+
}
|
|
14551
|
+
/**
|
|
14552
|
+
* Drop every heap-string-field record under `prefix` (a variable name plus
|
|
14553
|
+
* dot). Used when a slot's ownership is retargeted — the records describe
|
|
14554
|
+
* the OLD value, which the caller has just reclaimed or displaced.
|
|
14555
|
+
*/
|
|
14556
|
+
function drop_heap_string_field_records(status, decl_name) {
|
|
14557
|
+
if (!status.heap_string_fields?.size) return;
|
|
14558
|
+
const prefix = `${decl_name}.`;
|
|
14559
|
+
for (const key of Array.from(status.heap_string_fields)) if (key.startsWith(prefix)) status.heap_string_fields.delete(key);
|
|
14560
|
+
}
|
|
14126
14561
|
function emit_destroy_for_scope(status, declarations_before) {
|
|
14127
14562
|
const moved = status.moved ?? /* @__PURE__ */ new Set();
|
|
14128
14563
|
const current_scope = status.heap_cleanup_stack?.[status.heap_cleanup_stack.length - 1];
|
|
14129
14564
|
if (current_scope?.heap_slots.length) {
|
|
14130
14565
|
for (let i = declarations_before; i < status.scoped_declarations.length; i++) {
|
|
14131
14566
|
const decl = status.scoped_declarations[i];
|
|
14132
|
-
release_heap_string_fields(status, decl.name, decl.type.name);
|
|
14567
|
+
release_heap_string_fields(status, decl.name, decl.type.name, decl.type.is_nullable);
|
|
14133
14568
|
if (moved.has(decl.name)) continue;
|
|
14134
14569
|
const scope_enum = status.enums.find((e) => e.name === decl.type.name && e.has_associated_data);
|
|
14135
14570
|
if (scope_enum && !decl.type.is_array) {
|
|
@@ -14172,6 +14607,18 @@ function emit_destroy_for_scope(status, declarations_before) {
|
|
|
14172
14607
|
emit_free(status);
|
|
14173
14608
|
continue;
|
|
14174
14609
|
}
|
|
14610
|
+
const tuple_field_offsets = status.heap_string_tuple_fields?.get(decl.name);
|
|
14611
|
+
if (tuple_field_offsets?.length) {
|
|
14612
|
+
const var_offset = status.stack_offsets?.get(decl.name);
|
|
14613
|
+
if (var_offset !== void 0 && var_offset >= 0) {
|
|
14614
|
+
for (const field_offset of tuple_field_offsets) {
|
|
14615
|
+
emit_asm(status, `add x0, x29, #${var_offset}\n`);
|
|
14616
|
+
emit_asm(status, `ldr x0, [x0, #${field_offset}]\n`);
|
|
14617
|
+
emit_free(status);
|
|
14618
|
+
}
|
|
14619
|
+
continue;
|
|
14620
|
+
}
|
|
14621
|
+
}
|
|
14175
14622
|
if (decl.type.name === "func") {
|
|
14176
14623
|
emit_destroy_for_decl(status, decl.name, "func");
|
|
14177
14624
|
continue;
|
|
@@ -14191,7 +14638,7 @@ function emit_destroy_for_scope(status, declarations_before) {
|
|
|
14191
14638
|
}
|
|
14192
14639
|
for (let i = declarations_before; i < status.scoped_declarations.length; i++) {
|
|
14193
14640
|
const decl = status.scoped_declarations[i];
|
|
14194
|
-
release_heap_string_fields(status, decl.name, decl.type.name);
|
|
14641
|
+
release_heap_string_fields(status, decl.name, decl.type.name, decl.type.is_nullable);
|
|
14195
14642
|
if (moved.has(decl.name)) continue;
|
|
14196
14643
|
const scope_enum = status.enums.find((e) => e.name === decl.type.name && e.has_associated_data);
|
|
14197
14644
|
if (scope_enum && !decl.type.is_array) {
|
|
@@ -14265,6 +14712,18 @@ function emit_destroy_for_scope(status, declarations_before) {
|
|
|
14265
14712
|
emit_free(status);
|
|
14266
14713
|
continue;
|
|
14267
14714
|
}
|
|
14715
|
+
const tuple_field_offsets = status.heap_string_tuple_fields?.get(decl.name);
|
|
14716
|
+
if (tuple_field_offsets?.length) {
|
|
14717
|
+
const var_offset = status.stack_offsets?.get(decl.name);
|
|
14718
|
+
if (var_offset !== void 0 && var_offset >= 0) {
|
|
14719
|
+
for (const field_offset of tuple_field_offsets) {
|
|
14720
|
+
emit_asm(status, `add x0, x29, #${var_offset}\n`);
|
|
14721
|
+
emit_asm(status, `ldr x0, [x0, #${field_offset}]\n`);
|
|
14722
|
+
emit_free(status);
|
|
14723
|
+
}
|
|
14724
|
+
continue;
|
|
14725
|
+
}
|
|
14726
|
+
}
|
|
14268
14727
|
if (is_field_struct_borrow(decl)) continue;
|
|
14269
14728
|
const resolved_decl = resolve_decl_struct(decl, status);
|
|
14270
14729
|
if (!resolved_decl) continue;
|
|
@@ -14346,6 +14805,27 @@ function mark_moved_if_struct(value, status, opts) {
|
|
|
14346
14805
|
status.moved.add(var_name);
|
|
14347
14806
|
}
|
|
14348
14807
|
}
|
|
14808
|
+
/**
|
|
14809
|
+
* strdup the UNRECORDED direct string fields of a value struct whose base is
|
|
14810
|
+
* in `base_reg` (+ base_offset) — the return-boundary normalization: the
|
|
14811
|
+
* sret byte-copy transfers the RECORDED (heap) fields raw, but unrecorded
|
|
14812
|
+
* fields may hold rodata/borrowed bytes the caller cannot free, so they are
|
|
14813
|
+
* deep-copied here and the returned struct is uniformly heap-owned.
|
|
14814
|
+
*/
|
|
14815
|
+
function emit_string_field_strdups_at(status, struct_name, base_reg, base_offset = 0, skip_fields) {
|
|
14816
|
+
const struct = status.structs.find((s) => s.name === struct_name && !s.is_simple_type && !s.is_class);
|
|
14817
|
+
if (!struct) return;
|
|
14818
|
+
for (const field of direct_string_fields(struct)) {
|
|
14819
|
+
if (skip_fields?.has(field.name)) continue;
|
|
14820
|
+
const off = base_offset + get_field_offset(struct_name, field.name, status);
|
|
14821
|
+
emit_asm(status, `str ${base_reg}, [sp, #-16]!\n`);
|
|
14822
|
+
emit_string_pair_load_at(status, base_reg, off);
|
|
14823
|
+
emit_strdup_string(status);
|
|
14824
|
+
emit_asm(status, `mov x13, x0\n`);
|
|
14825
|
+
emit_asm(status, `ldr ${base_reg}, [sp], #16\n`);
|
|
14826
|
+
emit_pair_store_to(status, base_reg, off, "x13", "x1");
|
|
14827
|
+
}
|
|
14828
|
+
}
|
|
14349
14829
|
//#endregion
|
|
14350
14830
|
//#region ../src/build_aarch64/utils/closure_a64.ts
|
|
14351
14831
|
/**
|
|
@@ -14520,13 +15000,31 @@ function materialize_func_value_a64(func, status) {
|
|
|
14520
15000
|
const lines = [];
|
|
14521
15001
|
lines.push(`.p2align 2`);
|
|
14522
15002
|
lines.push(`${thunk}:`);
|
|
14523
|
-
const
|
|
14524
|
-
|
|
14525
|
-
|
|
14526
|
-
|
|
15003
|
+
const normalizes_borrow_return = func.return_type?.name === "string" && !func.return_type?.is_view && !func.returns_move && func.returns_string_borrow !== false && !(status.heap_returning_functions?.has(target) || (status.heap_returning_functions?.has(func.name ?? "") ?? false));
|
|
15004
|
+
const V = slots - 1;
|
|
15005
|
+
const dest_stack_slots = Math.max(0, V - 8);
|
|
15006
|
+
const dest_area = normalizes_borrow_return ? Math.ceil(dest_stack_slots * 8 / 16) * 16 : 0;
|
|
15007
|
+
const frame_pad = normalizes_borrow_return ? dest_area + 16 : 0;
|
|
15008
|
+
if (normalizes_borrow_return) {
|
|
15009
|
+
lines.push(`stp x29, x30, [sp, #-16]!`);
|
|
15010
|
+
if (dest_area > 0) lines.push(`sub sp, sp, #${dest_area}`);
|
|
15011
|
+
}
|
|
15012
|
+
const reg_moves = Math.min(V, 7);
|
|
15013
|
+
for (let s = 0; s < reg_moves; s++) lines.push(`mov x${s}, x${s + 1}`);
|
|
15014
|
+
if (V >= 8) lines.push(`ldr x7, [sp, #${frame_pad}]`);
|
|
15015
|
+
for (let k = 0; k < V - 8; k++) {
|
|
15016
|
+
lines.push(`ldr x9, [sp, #${frame_pad + (k + 1) * 8}]`);
|
|
14527
15017
|
lines.push(`str x9, [sp, #${k * 8}]`);
|
|
14528
15018
|
}
|
|
14529
|
-
|
|
15019
|
+
if (normalizes_borrow_return) {
|
|
15020
|
+
lines.push(`bl ${target}`);
|
|
15021
|
+
lines.push(`str x1, [sp, #-16]!`);
|
|
15022
|
+
lines.push(status.audit ? `bl _nomen_strdup_wrap` : `bl _strdup`);
|
|
15023
|
+
lines.push(`ldr x1, [sp], #16`);
|
|
15024
|
+
if (dest_area > 0) lines.push(`add sp, sp, #${dest_area}`);
|
|
15025
|
+
lines.push(`ldp x29, x30, [sp], #16`);
|
|
15026
|
+
lines.push(`ret`);
|
|
15027
|
+
} else lines.push(`b ${target}`);
|
|
14530
15028
|
lines.push(``);
|
|
14531
15029
|
status.closure_definitions = (status.closure_definitions ?? "") + lines.join("\n") + "\n";
|
|
14532
15030
|
status.closure_definitions = (status.closure_definitions ?? "") + DATA_SECTION_OPEN + `.p2align 3\n${descriptor}:\n\t.quad ${thunk}\n\t.quad 0\n\t.quad 0\n\t.quad 0\n` + TEXT_SECTION_RESTORE;
|
|
@@ -16823,28 +17321,6 @@ function value_number_loops(body, ast_list, status, mutate_ast) {
|
|
|
16823
17321
|
};
|
|
16824
17322
|
}
|
|
16825
17323
|
//#endregion
|
|
16826
|
-
//#region ../src/build_common/has_string_fields.ts
|
|
16827
|
-
/**
|
|
16828
|
-
* Whether any field (recursively, through nested owning value structs)
|
|
16829
|
-
* is an owned `string`. Shared by BOTH backends' owning-Buffer element
|
|
16830
|
-
* specialization (the slot deep-copies strdup'd strings, so a
|
|
16831
|
-
* string-field element is owning even when `struct_needs_destroy`
|
|
16832
|
-
* ignores string-only locals) — the C comment in
|
|
16833
|
-
* owning_buffer_specialize records the divergence this once caused.
|
|
16834
|
-
* `ref` and `view T` fields are skipped: a ref aliases the caller's
|
|
16835
|
-
* storage and a view's byte copy owns nothing.
|
|
16836
|
-
*/
|
|
16837
|
-
function has_string_fields(node, status) {
|
|
16838
|
-
for (const field of node.fields) {
|
|
16839
|
-
if (field.type.is_ref) continue;
|
|
16840
|
-
if (field.type.is_view) continue;
|
|
16841
|
-
if (field.type.name === "string" && !field.type.is_array) return true;
|
|
16842
|
-
const field_struct = status.structs.find((s) => s.name === field.type.name && !s.is_simple_type && !s.is_generic);
|
|
16843
|
-
if (field_struct && !field_struct.is_class && has_string_fields(field_struct, status)) return true;
|
|
16844
|
-
}
|
|
16845
|
-
return false;
|
|
16846
|
-
}
|
|
16847
|
-
//#endregion
|
|
16848
17324
|
//#region ../src/build_aarch64/utils/owning_buffer_specialize.ts
|
|
16849
17325
|
/**
|
|
16850
17326
|
* Detect whether a monomorphized struct is a `Buffer_<T>` whose element type
|
|
@@ -19070,6 +19546,8 @@ function build_function_node$1(node, status) {
|
|
|
19070
19546
|
if (check_c_fallback(node, void 0, status)) return;
|
|
19071
19547
|
const old_function_name = status.current_function_name;
|
|
19072
19548
|
status.current_function_name = emission_label(node);
|
|
19549
|
+
const old_is_closure = status.current_function_is_closure;
|
|
19550
|
+
status.current_function_is_closure = !!node.is_closure;
|
|
19073
19551
|
status.function_nursery_depth = status.nursery_stack?.length ?? 0;
|
|
19074
19552
|
const old_scoped_declarations = status.scoped_declarations;
|
|
19075
19553
|
status.scoped_declarations = [];
|
|
@@ -19458,6 +19936,16 @@ function build_function_node$1(node, status) {
|
|
|
19458
19936
|
}
|
|
19459
19937
|
param_idx++;
|
|
19460
19938
|
if (param.declaration === "var") status.function_param_vars.add(param.name);
|
|
19939
|
+
const is_free_function = (!node.scope || node.scope.node_type === "root" || node.scope.node_type === "func") && !node.params.some((p) => p.is_self_param);
|
|
19940
|
+
const owned_value_param = !param.is_self_param && !param.is_variadic && !param.type.is_ref && !param.type.is_nullable && !param.type.is_view && !param.type.is_array && param.type.name ? status.structs.find((st) => st.name === param.type.name && !st.is_simple_type && !st.is_class) : void 0;
|
|
19941
|
+
if (is_free_function && !node.address_escaped && owned_value_param && direct_string_fields(owned_value_param).length > 0 && !(owned_value_param.traits ?? []).length && !struct_needs_destroy(owned_value_param, status) && (status.normalized_struct_returners?.has(node.name) || status.normalized_struct_returners?.has(label_name)) && !status.func_address_taken?.has(node.name) && !status.func_address_taken?.has(label_name)) {
|
|
19942
|
+
status.function_param_vars.add(param.name);
|
|
19943
|
+
const param_decl = new DeclarationNode(param.start, "private", "var", param.name, param.type);
|
|
19944
|
+
param_decl.string_fields_via_pointer = true;
|
|
19945
|
+
status.scoped_declarations.push(param_decl);
|
|
19946
|
+
if (!status.heap_string_fields) status.heap_string_fields = /* @__PURE__ */ new Set();
|
|
19947
|
+
for (const field of direct_string_fields(owned_value_param)) status.heap_string_fields.add(`${param.name}.${field.name}`);
|
|
19948
|
+
}
|
|
19461
19949
|
if (param.type.is_array) {
|
|
19462
19950
|
if (!param.is_variadic ? array_struct_name(param.type, status) : void 0) {
|
|
19463
19951
|
if (!status.heap_array_vars) status.heap_array_vars = /* @__PURE__ */ new Set();
|
|
@@ -19648,6 +20136,7 @@ function build_function_node$1(node, status) {
|
|
|
19648
20136
|
status.heap_cleanup_stack = old_heap_cleanup_stack;
|
|
19649
20137
|
status.trait_class_frames = old_trait_class_frames;
|
|
19650
20138
|
status.current_function_name = old_function_name;
|
|
20139
|
+
status.current_function_is_closure = old_is_closure;
|
|
19651
20140
|
status.stack_size = old_stack_size;
|
|
19652
20141
|
status.stack_offsets = old_stack_offsets;
|
|
19653
20142
|
status.closure_env_slot = old_closure_env_slot;
|
|
@@ -19669,144 +20158,6 @@ function build_function_node$1(node, status) {
|
|
|
19669
20158
|
status.function_return_type = void 0;
|
|
19670
20159
|
}
|
|
19671
20160
|
//#endregion
|
|
19672
|
-
//#region ../src/build_aarch64/utils/string_pair.ts
|
|
19673
|
-
/**
|
|
19674
|
-
* Load the string variable `name` into the consecutive register pair
|
|
19675
|
-
* (base, base+1) — default x0/x1. Handles the plain slot case; callers
|
|
19676
|
-
* that have register-allocated or param forms handle theirs first.
|
|
19677
|
-
*/
|
|
19678
|
-
function emit_string_pair_load(status, name, base = "x0") {
|
|
19679
|
-
const offset = status.stack_offsets?.get(name);
|
|
19680
|
-
if (offset === void 0) return false;
|
|
19681
|
-
const w = base.startsWith("x") ? base : "x0";
|
|
19682
|
-
const n = parseInt(w.substring(1), 10);
|
|
19683
|
-
if (offset + 8 > 504) {
|
|
19684
|
-
emit_asm(status, `ldr ${w}, [x29, #${offset}]\n`);
|
|
19685
|
-
emit_asm(status, `ldr x${n + 1}, [x29, #${offset + 8}]\n`);
|
|
19686
|
-
return true;
|
|
19687
|
-
}
|
|
19688
|
-
emit_asm(status, `ldp ${w}, x${n + 1}, [x29, #${offset}]\n`);
|
|
19689
|
-
return true;
|
|
19690
|
-
}
|
|
19691
|
-
/** Load the pair at [addr_reg, #offset] into (base, base+1). */
|
|
19692
|
-
function emit_string_pair_load_at(status, addr_reg, offset, base = "x0") {
|
|
19693
|
-
const w = base.startsWith("x") ? base : "x0";
|
|
19694
|
-
emit_asm(status, `ldp ${w}, x${parseInt(w.substring(1), 10) + 1}, [${addr_reg}, #${offset}]\n`);
|
|
19695
|
-
}
|
|
19696
|
-
/**
|
|
19697
|
-
* strdup the fat string in (x0=ptr, x1=len): preserve the len half across
|
|
19698
|
-
* the call and leave the owned copy's pair in (x0, x1). The caller keeps
|
|
19699
|
-
* ownership of the original.
|
|
19700
|
-
*/
|
|
19701
|
-
function emit_strdup_string(status) {
|
|
19702
|
-
emit_asm(status, `str x1, [sp, #-16]!\n`);
|
|
19703
|
-
emit_asm(status, status.audit ? `bl _nomen_strdup_wrap\n` : `bl _strdup\n`);
|
|
19704
|
-
emit_asm(status, `ldr x1, [sp], #16\n`);
|
|
19705
|
-
}
|
|
19706
|
-
/**
|
|
19707
|
-
* Pair load/store at [x29, #offset] with an arbitrary register pair —
|
|
19708
|
-
* splits into two single-word accesses beyond the ldp/stp +504 range.
|
|
19709
|
-
*/
|
|
19710
|
-
function emit_pair_load_x29(status, offset, a = "x0", b = "x1") {
|
|
19711
|
-
if (offset + 8 > 504) {
|
|
19712
|
-
emit_asm(status, `ldr ${a}, [x29, #${offset}]\n`);
|
|
19713
|
-
emit_asm(status, `ldr ${b}, [x29, #${offset + 8}]\n`);
|
|
19714
|
-
return;
|
|
19715
|
-
}
|
|
19716
|
-
emit_asm(status, `ldp ${a}, ${b}, [x29, #${offset}]\n`);
|
|
19717
|
-
}
|
|
19718
|
-
function emit_pair_store_x29(status, offset, a = "x0", b = "x1") {
|
|
19719
|
-
if (offset + 8 > 504) {
|
|
19720
|
-
emit_asm(status, `str ${a}, [x29, #${offset}]\n`);
|
|
19721
|
-
emit_asm(status, `str ${b}, [x29, #${offset + 8}]\n`);
|
|
19722
|
-
return;
|
|
19723
|
-
}
|
|
19724
|
-
emit_asm(status, `stp ${a}, ${b}, [x29, #${offset}]\n`);
|
|
19725
|
-
}
|
|
19726
|
-
/**
|
|
19727
|
-
* Owning specializations for Array<string>'s raw T-generic bodies
|
|
19728
|
-
* (`with` / `set`), replacing the shared-pointer copies with per-slot deep
|
|
19729
|
-
* copies — the aarch64 analog of Array.nm's `#if T_NEEDS_STRDUP` C branch.
|
|
19730
|
-
*
|
|
19731
|
-
* Conventions (matching Array.nm raw bodies):
|
|
19732
|
-
* - `with` (static): x0/x1 = value pair, x2 = count. Returns x0 = heap
|
|
19733
|
-
* buffer ([ptr] = length, elements at +8..).
|
|
19734
|
-
* - `set` (method): x19 = first element, x1 = index, x2/x3 = value pair.
|
|
19735
|
-
* Bounds are guaranteed by the Nomen-level constraints.
|
|
19736
|
-
*/
|
|
19737
|
-
function emit_owning_array_string_specialize(func_name, status, self_reg = "x19") {
|
|
19738
|
-
if (func_name === "with") {
|
|
19739
|
-
emit_asm(status, `stp x19, x20, [sp, #-16]!\n`);
|
|
19740
|
-
emit_asm(status, `stp x21, x22, [sp, #-16]!\n`);
|
|
19741
|
-
emit_asm(status, `stp x24, x25, [sp, #-16]!\n`);
|
|
19742
|
-
emit_asm(status, `mov x19, x0\n`);
|
|
19743
|
-
emit_asm(status, `mov x20, x1\n`);
|
|
19744
|
-
emit_asm(status, `mov x21, x2\n`);
|
|
19745
|
-
emit_asm(status, `add x0, x21, #1\n`);
|
|
19746
|
-
emit_asm(status, `lsl x0, x0, #4\n`);
|
|
19747
|
-
emit_malloc(status);
|
|
19748
|
-
emit_asm(status, `mov x22, x0\n`);
|
|
19749
|
-
emit_asm(status, `str x21, [x22]\n`);
|
|
19750
|
-
emit_asm(status, `mov x23, #8\n`);
|
|
19751
|
-
emit_asm(status, `mov x24, #0\n`);
|
|
19752
|
-
emit_asm(status, `.Larr_str_with_loop:\n`);
|
|
19753
|
-
emit_asm(status, `cmp x24, x21\n`);
|
|
19754
|
-
emit_asm(status, `b.ge .Larr_str_with_done\n`);
|
|
19755
|
-
emit_asm(status, `mov x0, x19\n`);
|
|
19756
|
-
emit_strdup(status);
|
|
19757
|
-
emit_asm(status, `add x25, x22, x23\n`);
|
|
19758
|
-
emit_asm(status, `stp x0, x20, [x25]\n`);
|
|
19759
|
-
emit_asm(status, `add x23, x23, #16\n`);
|
|
19760
|
-
emit_asm(status, `add x24, x24, #1\n`);
|
|
19761
|
-
emit_asm(status, `b .Larr_str_with_loop\n`);
|
|
19762
|
-
emit_asm(status, `.Larr_str_with_done:\n`);
|
|
19763
|
-
emit_asm(status, `mov x0, x22\n`);
|
|
19764
|
-
emit_asm(status, `ldp x24, x25, [sp], #16\n`);
|
|
19765
|
-
emit_asm(status, `ldp x21, x22, [sp], #16\n`);
|
|
19766
|
-
emit_asm(status, `ldp x19, x20, [sp], #16\n`);
|
|
19767
|
-
return true;
|
|
19768
|
-
}
|
|
19769
|
-
if (func_name === "at" || func_name === "first" || func_name === "at_end") {
|
|
19770
|
-
emit_asm(status, `stp x20, x21, [sp, #-16]!\n`);
|
|
19771
|
-
if (func_name === "at") {
|
|
19772
|
-
emit_asm(status, `lsl x9, x1, #4\n`);
|
|
19773
|
-
emit_asm(status, `add x9, ${self_reg}, x9\n`);
|
|
19774
|
-
} else if (func_name === "at_end") {
|
|
19775
|
-
emit_asm(status, `ldr x9, [${self_reg}, #-8]\n`);
|
|
19776
|
-
emit_asm(status, `sub x9, x9, #1\n`);
|
|
19777
|
-
emit_asm(status, `lsl x9, x9, #4\n`);
|
|
19778
|
-
emit_asm(status, `add x9, ${self_reg}, x9\n`);
|
|
19779
|
-
} else emit_asm(status, `add x9, ${self_reg}, #0\n`);
|
|
19780
|
-
emit_asm(status, `ldp x0, x1, [x9]\n`);
|
|
19781
|
-
emit_asm(status, `ldp x20, x21, [sp], #16\n`);
|
|
19782
|
-
return true;
|
|
19783
|
-
}
|
|
19784
|
-
if (func_name === "set") {
|
|
19785
|
-
emit_asm(status, `stp x20, x21, [sp, #-16]!\n`);
|
|
19786
|
-
emit_asm(status, `lsl x9, x1, #4\n`);
|
|
19787
|
-
emit_asm(status, `add x9, ${self_reg}, x9\n`);
|
|
19788
|
-
emit_asm(status, `ldr x0, [x9]\n`);
|
|
19789
|
-
emit_asm(status, `mov x20, x2\n`);
|
|
19790
|
-
emit_asm(status, `mov x21, x3\n`);
|
|
19791
|
-
emit_free(status);
|
|
19792
|
-
emit_asm(status, `mov x0, x20\n`);
|
|
19793
|
-
emit_strdup(status);
|
|
19794
|
-
emit_asm(status, `str x0, [x9]\n`);
|
|
19795
|
-
emit_asm(status, `str x21, [x9, #8]\n`);
|
|
19796
|
-
emit_asm(status, `ldp x20, x21, [sp], #16\n`);
|
|
19797
|
-
return true;
|
|
19798
|
-
}
|
|
19799
|
-
return false;
|
|
19800
|
-
}
|
|
19801
|
-
/** Pair store to [base_reg, #offset] with stur fallback (misaligned/far). */
|
|
19802
|
-
function emit_pair_store_to(status, base_reg, offset, a, b) {
|
|
19803
|
-
if (offset % 8 === 0 && offset + 8 <= 504 && offset >= 0) emit_asm(status, `stp ${a}, ${b}, [${base_reg}, #${offset}]\n`);
|
|
19804
|
-
else {
|
|
19805
|
-
emit_asm(status, `stur ${a}, [${base_reg}, #${offset}]\n`);
|
|
19806
|
-
emit_asm(status, `stur ${b}, [${base_reg}, #${offset + 8}]\n`);
|
|
19807
|
-
}
|
|
19808
|
-
}
|
|
19809
|
-
//#endregion
|
|
19810
20161
|
//#region ../src/build_aarch64/build_struct_node.ts
|
|
19811
20162
|
let field_strdup_guard_counter = 0;
|
|
19812
20163
|
function emit_typed_store(status, src_reg, dst_base, offset, size) {
|
|
@@ -21312,7 +21663,13 @@ function build_block_node$1(node, status) {
|
|
|
21312
21663
|
if (is_struct_node(child) || is_trait_node(child) || is_function_node(child) || child.node_type === "enum" || child.node_type === "bitset") continue;
|
|
21313
21664
|
if (child.node_type === "declare") {
|
|
21314
21665
|
const decl = child;
|
|
21315
|
-
if (decl.declaration === "const")
|
|
21666
|
+
if (decl.declaration === "const") {
|
|
21667
|
+
if (decl.type?.name === "string" && decl.type.is_array && decl.value?.node_type === "array") {
|
|
21668
|
+
decl.global_runtime_init = true;
|
|
21669
|
+
module_init_statements.push(child);
|
|
21670
|
+
}
|
|
21671
|
+
continue;
|
|
21672
|
+
}
|
|
21316
21673
|
if (inlined_const_names.has(decl.name)) continue;
|
|
21317
21674
|
if (is_literal_init(decl.value)) continue;
|
|
21318
21675
|
module_init_statements.push(child);
|
|
@@ -21327,7 +21684,7 @@ function build_block_node$1(node, status) {
|
|
|
21327
21684
|
heap_slots: [],
|
|
21328
21685
|
struct_decls: []
|
|
21329
21686
|
});
|
|
21330
|
-
const declarations_before = status.scoped_declarations.length;
|
|
21687
|
+
const declarations_before = node.node_type === "func" ? 0 : status.scoped_declarations.length;
|
|
21331
21688
|
for (let child of node.statements) if (is_struct_node(child)) {
|
|
21332
21689
|
if (!should_emit_definition(child, status.emit_mode, status.structs, status.system_struct_names)) continue;
|
|
21333
21690
|
build_struct_node$1(child, status);
|
|
@@ -21482,6 +21839,24 @@ function literal(raw, start) {
|
|
|
21482
21839
|
return new ValueNode(start, raw, new Type("string", true));
|
|
21483
21840
|
}
|
|
21484
21841
|
//#endregion
|
|
21842
|
+
//#region ../src/build_common/mark_tuple_literal_move_owners.ts
|
|
21843
|
+
/**
|
|
21844
|
+
* A tuple-literal constructor call (`[move t, move c]` lowered to
|
|
21845
|
+
* `_Tuple_.._init(&t, &c)`) copies its fields BY POINTER, and a `move`
|
|
21846
|
+
* element transfers ownership: the source local's scope-exit cleanup must
|
|
21847
|
+
* be suppressed (the tuple's own field cleanup now owns and frees the
|
|
21848
|
+
* buffer). Registers every bare `move <local>` argument into the build's
|
|
21849
|
+
* moved set — the same suppression the move-assignment path uses. Called
|
|
21850
|
+
* from every constructor-call build site (declarations, assignments, and
|
|
21851
|
+
* the generic call builder), since monomorphized tuple inits route through
|
|
21852
|
+
* each of them.
|
|
21853
|
+
*/
|
|
21854
|
+
function mark_tuple_literal_move_owners(node, status) {
|
|
21855
|
+
if (!node.type?.name?.startsWith("_Tuple_")) return;
|
|
21856
|
+
if (!status.moved) status.moved = /* @__PURE__ */ new Set();
|
|
21857
|
+
for (const p of node.params) if (p.node_type === "value" && p.is_moved && typeof p.value === "string") status.moved.add(p.value);
|
|
21858
|
+
}
|
|
21859
|
+
//#endregion
|
|
21485
21860
|
//#region ../src/build_common/string_escapes.ts
|
|
21486
21861
|
/**
|
|
21487
21862
|
* Source string-literal escape handling shared by both backends.
|
|
@@ -21611,6 +21986,37 @@ function escape_asciz(value) {
|
|
|
21611
21986
|
return quote + reencoded.slice(1, reencoded.endsWith(quote) ? -1 : void 0).replace(/\n/g, "\\n") + (reencoded.endsWith(quote) ? quote : "");
|
|
21612
21987
|
}
|
|
21613
21988
|
//#endregion
|
|
21989
|
+
//#region ../src/build_common/tuple_return_owned_element.ts
|
|
21990
|
+
/**
|
|
21991
|
+
* Whether a RETURN-position tuple-literal STRING element already owns a heap
|
|
21992
|
+
* buffer of its own, so the return boundary can leave it raw (normalization
|
|
21993
|
+
* skips it). Two shapes qualify:
|
|
21994
|
+
*
|
|
21995
|
+
* - a bare local that was TRANSFERRED into the tuple (`move`, or the
|
|
21996
|
+
* last-use inference) whose buffer is genuinely heap — the callee's
|
|
21997
|
+
* cleanup skips it, so the field owns the original allocation. A
|
|
21998
|
+
* transferred RODATA local (a string literal on aarch64) does NOT
|
|
21999
|
+
* qualify: the field would alias static storage and the caller's
|
|
22000
|
+
* ownership of it would free static memory.
|
|
22001
|
+
* - a fresh expression result that is not a borrow (a call, interpolation,
|
|
22002
|
+
* concat) — it owns its heap already.
|
|
22003
|
+
*
|
|
22004
|
+
* Everything else (string literals, non-transferred locals, parameters,
|
|
22005
|
+
* borrow accessors such as `.at(0)`) must be strdup'd at the return so the
|
|
22006
|
+
* caller can uniformly own every string field of the returned tuple (see the
|
|
22007
|
+
* caller-side binding ownership).
|
|
22008
|
+
*/
|
|
22009
|
+
function tuple_return_owned_element(element, heap_strings) {
|
|
22010
|
+
if (element.node_type === "value") {
|
|
22011
|
+
const v = element.value ?? "";
|
|
22012
|
+
if (typeof v !== "string") return false;
|
|
22013
|
+
if (v.startsWith("\"")) return false;
|
|
22014
|
+
if (!/^[A-Za-z_]/.test(v) || v === "null") return false;
|
|
22015
|
+
return !!element.is_moved && !!heap_strings?.has(v);
|
|
22016
|
+
}
|
|
22017
|
+
return !is_string_borrow(element);
|
|
22018
|
+
}
|
|
22019
|
+
//#endregion
|
|
21614
22020
|
//#region ../src/build_aarch64/build_range_node.ts
|
|
21615
22021
|
function build_range_node$1(node, status) {
|
|
21616
22022
|
const start = evaluate_constant$1(node.left_value);
|
|
@@ -22006,6 +22412,7 @@ function emit_struct_constructor_to_slot(fc, slot_addr, status) {
|
|
|
22006
22412
|
* so register slots start at x1 — overflow begins at the 8th arg (slot 8).
|
|
22007
22413
|
*/
|
|
22008
22414
|
function build_constructor_params(fc, param_regs, status) {
|
|
22415
|
+
mark_tuple_literal_move_owners(fc, status);
|
|
22009
22416
|
const variadic_idx = fc.variadic_param_index;
|
|
22010
22417
|
if (variadic_idx !== void 0 && fc.params[variadic_idx] && fc.params[variadic_idx].node_type === "array") {
|
|
22011
22418
|
const arr = fc.params[variadic_idx];
|
|
@@ -22405,6 +22812,43 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
|
|
|
22405
22812
|
if (!status.function_local_types) status.function_local_types = /* @__PURE__ */ new Map();
|
|
22406
22813
|
status.function_local_types.set(node.name, node.type.name);
|
|
22407
22814
|
}
|
|
22815
|
+
if (node.declaration === "var" && node.type.name?.startsWith("_Tuple_") && !node.name.startsWith("_tuple_dst_") && node.value?.node_type === "func_call") {
|
|
22816
|
+
const tuple_call = node.value;
|
|
22817
|
+
const tuple_struct = status.structs.find((st) => st.name === node.type.name && !st.is_simple_type);
|
|
22818
|
+
const init_is_tuple_call = !String(tuple_call.name ?? "").startsWith("_Tuple_") && !!tuple_call.type?.name?.startsWith("_Tuple_");
|
|
22819
|
+
const init_is_tuple_literal = String(tuple_call.name ?? "").startsWith("_Tuple_");
|
|
22820
|
+
if (tuple_struct && (init_is_tuple_call || init_is_tuple_literal)) {
|
|
22821
|
+
const offsets = [];
|
|
22822
|
+
for (const field of direct_string_fields(tuple_struct)) {
|
|
22823
|
+
if (init_is_tuple_literal) {
|
|
22824
|
+
const idx = parseInt(field.name.replace(/^_/, ""), 10);
|
|
22825
|
+
const element = tuple_call.params[idx];
|
|
22826
|
+
if (!element || !tuple_return_owned_element(element, status.heap_strings)) continue;
|
|
22827
|
+
}
|
|
22828
|
+
offsets.push(get_field_offset(tuple_struct.name, field.name, status));
|
|
22829
|
+
}
|
|
22830
|
+
if (offsets.length) {
|
|
22831
|
+
if (!status.heap_string_tuple_fields) status.heap_string_tuple_fields = /* @__PURE__ */ new Map();
|
|
22832
|
+
status.heap_string_tuple_fields.set(node.name, offsets);
|
|
22833
|
+
}
|
|
22834
|
+
}
|
|
22835
|
+
}
|
|
22836
|
+
if (node.declaration === "var" && node.type.name === "string" && !node.type.is_array) {
|
|
22837
|
+
const binding_value = node.value;
|
|
22838
|
+
if (binding_value?.node_type === "access" && binding_value.access.node_type === "access_field" && binding_value.target.node_type === "value" && String(binding_value.target.value ?? "").startsWith("_tuple_dst_")) {
|
|
22839
|
+
const temp_name = String(binding_value.target.value);
|
|
22840
|
+
const temp_init = status.scoped_declarations.findLast((d) => d.name === temp_name)?.value;
|
|
22841
|
+
const m = /^_(\d+)$/.exec(String(binding_value.access.name ?? ""));
|
|
22842
|
+
if (temp_init?.node_type === "func_call" && !String(temp_init.name ?? "").startsWith("_Tuple_") && !!temp_init.type?.name?.startsWith("_Tuple_")) {
|
|
22843
|
+
mark_heap_string(status, node.name);
|
|
22844
|
+
binding_value.is_moved = true;
|
|
22845
|
+
} else if (temp_init?.type?.name?.startsWith("_Tuple_") && m) {
|
|
22846
|
+
const param = temp_init.params[parseInt(m[1], 10)];
|
|
22847
|
+
const src_name = param?.node_type === "value" ? String(param.value) : "";
|
|
22848
|
+
if (param?.node_type === "value" && param.is_moved && !!src_name && !!status.heap_strings?.has(src_name)) mark_heap_string(status, node.name);
|
|
22849
|
+
}
|
|
22850
|
+
}
|
|
22851
|
+
}
|
|
22408
22852
|
hoist_field_overrides(node.value, build_node, status, "", node.name);
|
|
22409
22853
|
status.last_result_is_heap = false;
|
|
22410
22854
|
const prev_heap = status.last_result_is_heap;
|
|
@@ -22554,12 +22998,15 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
|
|
|
22554
22998
|
build_swap_params(func_call, status);
|
|
22555
22999
|
} else {
|
|
22556
23000
|
const old_buffer = status.struct_return_buffer;
|
|
23001
|
+
const old_buffer_var = status.struct_return_buffer_var;
|
|
22557
23002
|
const old_preset = status.call_x8_preset;
|
|
22558
23003
|
emit_var_address(status, "x8", node.name);
|
|
23004
|
+
status.struct_return_buffer_var = node.name;
|
|
22559
23005
|
status.struct_return_buffer = "x8";
|
|
22560
23006
|
status.call_x8_preset = true;
|
|
22561
23007
|
emit_init_value$1(node.value, nir_init, status);
|
|
22562
23008
|
status.struct_return_buffer = old_buffer;
|
|
23009
|
+
status.struct_return_buffer_var = old_buffer_var;
|
|
22563
23010
|
status.call_x8_preset = old_preset;
|
|
22564
23011
|
}
|
|
22565
23012
|
return;
|
|
@@ -22867,13 +23314,33 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
|
|
|
22867
23314
|
} else if (status.function_return_label) {
|
|
22868
23315
|
const labels = emit_string_array_labels(array_values.values, status);
|
|
22869
23316
|
if (needs_runtime_array_init(array_values.values, status)) {
|
|
22870
|
-
|
|
22871
|
-
|
|
22872
|
-
|
|
22873
|
-
|
|
22874
|
-
|
|
22875
|
-
|
|
22876
|
-
|
|
23317
|
+
if (node.global_runtime_init) {
|
|
23318
|
+
emit_data(status, `.data\n`);
|
|
23319
|
+
emit_data(status, `.quad ${array_values.values.length}\n`);
|
|
23320
|
+
emit_data(status, `${node.name}:\n`);
|
|
23321
|
+
emit_data(status, `.space ${array_values.values.length * 16}\n`);
|
|
23322
|
+
emit_data(status, `.p2align 2\n`);
|
|
23323
|
+
emit_data(status, `.text\n`);
|
|
23324
|
+
emit_asm(status, `adrp x0, ${node.name}@PAGE\n`);
|
|
23325
|
+
emit_asm(status, `add x0, x0, ${node.name}@PAGEOFF\n`);
|
|
23326
|
+
array_values.values.forEach((value, i) => {
|
|
23327
|
+
const resolved = resolve_static_value(value, status);
|
|
23328
|
+
if (resolved !== null && resolved.startsWith("\"")) {
|
|
23329
|
+
emit_asm(status, `adr x1, ${resolve_array_element(resolved, labels)}\n`);
|
|
23330
|
+
emit_asm(status, `str x1, [x0, #${i * 16}]\n`);
|
|
23331
|
+
emit_asm(status, `mov x1, #${string_literal_length(resolved)}\n`);
|
|
23332
|
+
emit_asm(status, `str x1, [x0, #${i * 16 + 8}]\n`);
|
|
23333
|
+
}
|
|
23334
|
+
});
|
|
23335
|
+
} else {
|
|
23336
|
+
const offset = alloc_array_with_prefix(status, array_values.values.length, element_size);
|
|
23337
|
+
status.stack_offsets.set(node.name, offset);
|
|
23338
|
+
array_values.values.forEach((value, i) => {
|
|
23339
|
+
const slot_offset = offset + i * element_size;
|
|
23340
|
+
const resolved = resolve_static_value(value, status);
|
|
23341
|
+
if (resolved !== null) emit_string_array_element(status, resolved, labels, "x29", slot_offset);
|
|
23342
|
+
});
|
|
23343
|
+
}
|
|
22877
23344
|
} else {
|
|
22878
23345
|
emit_data(status, `${node.name}: ${directive} `);
|
|
22879
23346
|
let first = true;
|
|
@@ -22889,19 +23356,16 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
|
|
|
22889
23356
|
emit_data(status, `\n.p2align 2\n`);
|
|
22890
23357
|
}
|
|
22891
23358
|
} else if (node.type.name === "string" && node.type.is_array) {
|
|
22892
|
-
|
|
22893
|
-
|
|
22894
|
-
|
|
22895
|
-
|
|
23359
|
+
emit_data(status, `.quad ${array_values.values.length}\n`);
|
|
23360
|
+
emit_data(status, `${node.name}: .space ${array_values.values.length * 16}\n\t.p2align 2\n`);
|
|
23361
|
+
} else {
|
|
23362
|
+
emit_data(status, `.quad ${array_values.values.length}\n`);
|
|
23363
|
+
emit_data(status, `${node.name}:\n`);
|
|
23364
|
+
array_values.values.forEach((value) => {
|
|
22896
23365
|
const resolved = resolve_static_value(value, status);
|
|
22897
|
-
|
|
22898
|
-
else emit_asm(status, ".quad 0\n .quad 0");
|
|
23366
|
+
emit_data(status, `\t${directive} ${resolved !== null ? resolved : "0"}\n`);
|
|
22899
23367
|
});
|
|
22900
|
-
|
|
22901
|
-
} else {
|
|
22902
|
-
emit_asm(status, `${node.name}: ${directive} `);
|
|
22903
|
-
build_array_values_node$1(array_values, status);
|
|
22904
|
-
emit_asm(status, `\n.p2align 2\n`);
|
|
23368
|
+
emit_data(status, `\t.p2align 2\n`);
|
|
22905
23369
|
}
|
|
22906
23370
|
} else if (node.value && node.value.node_type === "range") {
|
|
22907
23371
|
if (status.function_return_label) {
|
|
@@ -23085,14 +23549,18 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
|
|
|
23085
23549
|
build_swap_params(func_call, status);
|
|
23086
23550
|
} else if (status.structs.find((s) => s.name === (func_call.type?.name ?? func_call.name) && !s.is_simple_type && !s.is_class) && status.function_return_label) {
|
|
23087
23551
|
const old_buffer = status.struct_return_buffer;
|
|
23552
|
+
const old_buffer_var = status.struct_return_buffer_var;
|
|
23088
23553
|
const old_preset = status.call_x8_preset;
|
|
23089
23554
|
emit_var_address(status, "x8", node.name);
|
|
23555
|
+
status.struct_return_buffer_var = node.name;
|
|
23090
23556
|
status.struct_return_buffer = "x8";
|
|
23091
23557
|
status.call_x8_preset = true;
|
|
23092
23558
|
emit_init_value$1(node.value, nir_init, status);
|
|
23093
23559
|
status.struct_return_buffer = old_buffer;
|
|
23560
|
+
status.struct_return_buffer_var = old_buffer_var;
|
|
23094
23561
|
status.call_x8_preset = old_preset;
|
|
23095
23562
|
emit_var_address(status, "x0", node.name);
|
|
23563
|
+
record_call_init_string_fields(node, status);
|
|
23096
23564
|
} else {
|
|
23097
23565
|
emit_init_value$1(node.value, nir_init, status);
|
|
23098
23566
|
emit_var_store(status, "x0", node.name, struct_size);
|
|
@@ -23128,6 +23596,7 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
|
|
|
23128
23596
|
const field_type = (target_type.name ? status.structs.find((s) => s.name === target_type.name) : void 0)?.fields.find((f) => f.name === field_name)?.type ?? type_from_value_node$1(access.access);
|
|
23129
23597
|
emit_struct_copy("x1", "x0", get_field_offset(target_type.name, field_name, status), get_struct_size(field_type.name, status), status);
|
|
23130
23598
|
}
|
|
23599
|
+
record_call_init_string_fields(node, status);
|
|
23131
23600
|
}
|
|
23132
23601
|
}
|
|
23133
23602
|
} else if (node.value) {
|
|
@@ -24785,7 +25254,7 @@ function build_return_node$1(node, status, nir_value) {
|
|
|
24785
25254
|
const finalized = status.moved ?? /* @__PURE__ */ new Set();
|
|
24786
25255
|
for (const decl of all_scope_frames(status).flat()) {
|
|
24787
25256
|
if (finalized.has(decl.name)) {
|
|
24788
|
-
release_heap_string_fields(status, decl.name, decl.type.name);
|
|
25257
|
+
release_heap_string_fields(status, decl.name, decl.type.name, decl.type.is_nullable);
|
|
24789
25258
|
continue;
|
|
24790
25259
|
}
|
|
24791
25260
|
if (is_field_struct_borrow(decl)) continue;
|
|
@@ -24805,7 +25274,7 @@ function build_return_node$1(node, status, nir_value) {
|
|
|
24805
25274
|
const finalized = status.moved ?? /* @__PURE__ */ new Set();
|
|
24806
25275
|
for (const decl of all_scope_frames(status).flat()) {
|
|
24807
25276
|
if (finalized.has(decl.name)) {
|
|
24808
|
-
release_heap_string_fields(status, decl.name, decl.type.name);
|
|
25277
|
+
release_heap_string_fields(status, decl.name, decl.type.name, decl.type.is_nullable);
|
|
24809
25278
|
continue;
|
|
24810
25279
|
}
|
|
24811
25280
|
if (is_field_struct_borrow(decl)) continue;
|
|
@@ -24893,7 +25362,10 @@ function build_return_node$1(node, status, nir_value) {
|
|
|
24893
25362
|
if (anon.base.is_moved) mark_moved_if_struct(anon.base, status);
|
|
24894
25363
|
emit_field_overrides(temp_name, anon, build_node, status);
|
|
24895
25364
|
emit_var_address(status, "x0", temp_name);
|
|
24896
|
-
} else
|
|
25365
|
+
} else {
|
|
25366
|
+
status.last_result_is_heap = false;
|
|
25367
|
+
emit_return_value$1(node.value, nir_value, status);
|
|
25368
|
+
}
|
|
24897
25369
|
if (ret_is_float) {
|
|
24898
25370
|
ensure_newline(status);
|
|
24899
25371
|
if (status.float_result_in_d0) emit_asm(status, `fmov d0, x0\n`);
|
|
@@ -24905,7 +25377,7 @@ function build_return_node$1(node, status, nir_value) {
|
|
|
24905
25377
|
if (current_return_is_string(status) && node.value.node_type === "value" && node.value.value.length >= 2 && node.value.value.startsWith("\"") && node.value.value.endsWith("\"")) {
|
|
24906
25378
|
const fn_name = status.current_function_name;
|
|
24907
25379
|
const mangled = status.current_struct ? `${status.current_struct.name}_${fn_name}` : fn_name;
|
|
24908
|
-
if (fn_name && (!!status.heap_returning_functions && (status.heap_returning_functions.has(fn_name) || mangled !== void 0 && status.heap_returning_functions.has(mangled)) || current_function_returns_move_string(status))) {
|
|
25380
|
+
if (fn_name && (status.current_function_is_closure || !!status.heap_returning_functions && (status.heap_returning_functions.has(fn_name) || mangled !== void 0 && status.heap_returning_functions.has(mangled)) || current_function_returns_move_string(status))) {
|
|
24909
25381
|
emit_strdup_string(status);
|
|
24910
25382
|
ensure_newline(status);
|
|
24911
25383
|
status.last_result_is_heap = true;
|
|
@@ -24913,7 +25385,7 @@ function build_return_node$1(node, status, nir_value) {
|
|
|
24913
25385
|
}
|
|
24914
25386
|
const borrow_fn_name = status.current_function_name;
|
|
24915
25387
|
const borrow_mangled = status.current_struct && borrow_fn_name ? `${status.current_struct.name}_${borrow_fn_name}` : void 0;
|
|
24916
|
-
const normalizes_borrow_returns = !!borrow_fn_name && !is_call_site_borrow_accessor(borrow_fn_name) && current_return_is_string(status) && (!!status.heap_returning_functions && (status.heap_returning_functions.has(borrow_fn_name) || borrow_mangled !== void 0 && status.heap_returning_functions.has(borrow_mangled)) || current_function_returns_move_string(status));
|
|
25388
|
+
const normalizes_borrow_returns = !!borrow_fn_name && !is_call_site_borrow_accessor(borrow_fn_name) && current_return_is_string(status) && (!!status.current_function_is_closure || !!status.heap_returning_functions && (status.heap_returning_functions.has(borrow_fn_name) || borrow_mangled !== void 0 && status.heap_returning_functions.has(borrow_mangled)) || current_function_returns_move_string(status));
|
|
24917
25389
|
let needs_borrow_strdup = false;
|
|
24918
25390
|
if (normalizes_borrow_returns) {
|
|
24919
25391
|
if (node.value.node_type === "value") {
|
|
@@ -24923,6 +25395,7 @@ function build_return_node$1(node, status, nir_value) {
|
|
|
24923
25395
|
if (!is_str_lit && !is_numeric && raw_value !== "null" && !status.heap_strings?.has(raw_value)) needs_borrow_strdup = true;
|
|
24924
25396
|
} else if (is_container_borrow_access(node.value)) needs_borrow_strdup = true;
|
|
24925
25397
|
else if (node.value.node_type === "access" && node.value.access.node_type === "access_field") needs_borrow_strdup = true;
|
|
25398
|
+
else if (status.current_function_is_closure && (node.value.node_type === "func_call" || node.value.node_type === "access" && node.value.access.node_type === "access_func") && !status.last_result_is_heap && !is_container_borrow_access(node.value)) needs_borrow_strdup = true;
|
|
24926
25399
|
else if ((node.value.node_type === "match" || node.value.node_type === "switch") && !return_join_owned_string) needs_borrow_strdup = true;
|
|
24927
25400
|
}
|
|
24928
25401
|
if (needs_borrow_strdup) {
|
|
@@ -24967,6 +25440,28 @@ function build_return_node$1(node, status, nir_value) {
|
|
|
24967
25440
|
const field_off = get_field_offset(ret_struct.name, field.name, status);
|
|
24968
25441
|
emit_enum_payload_strdups_at(status, field_enum.name, "x8", field_off);
|
|
24969
25442
|
}
|
|
25443
|
+
if (ret_struct && !returns_nullable_struct) {
|
|
25444
|
+
const returned_var = node.value.node_type === "value" ? node.value.value : "";
|
|
25445
|
+
if (returned_var !== "" && returned_var !== "0" && returned_var !== "null" && all_scope_frames(status).flat().some((d) => d.name === returned_var)) {
|
|
25446
|
+
let skip_fields;
|
|
25447
|
+
const prefix = `${node.value.value}.`;
|
|
25448
|
+
for (const key of status.heap_string_fields ?? []) if (key.startsWith(prefix)) {
|
|
25449
|
+
skip_fields ??= /* @__PURE__ */ new Set();
|
|
25450
|
+
skip_fields.add(key.slice(prefix.length));
|
|
25451
|
+
}
|
|
25452
|
+
emit_string_field_strdups_at(status, ret_struct.name, "x8", 0, skip_fields);
|
|
25453
|
+
}
|
|
25454
|
+
if (node.value?.node_type === "func_call" && String(node.value.name ?? "").startsWith("_Tuple_") && node.value.type?.name?.startsWith("_Tuple_")) {
|
|
25455
|
+
const tuple_call = node.value;
|
|
25456
|
+
const skip_fields = /* @__PURE__ */ new Set();
|
|
25457
|
+
for (const field of direct_string_fields(ret_struct)) {
|
|
25458
|
+
const idx = parseInt(field.name.replace(/^_/, ""), 10);
|
|
25459
|
+
const element = tuple_call.params[idx];
|
|
25460
|
+
if (element && tuple_return_owned_element(element, status.heap_strings)) skip_fields.add(field.name);
|
|
25461
|
+
}
|
|
25462
|
+
emit_string_field_strdups_at(status, ret_struct.name, "x8", 0, skip_fields);
|
|
25463
|
+
}
|
|
25464
|
+
}
|
|
24970
25465
|
}
|
|
24971
25466
|
}
|
|
24972
25467
|
if (status.return_assign) {
|
|
@@ -25034,7 +25529,7 @@ function build_return_node$1(node, status, nir_value) {
|
|
|
25034
25529
|
emit_nursery_joins_on_return_a64(status);
|
|
25035
25530
|
for (const decl of all_scope_frames(status).flat()) {
|
|
25036
25531
|
if (finalized.has(decl.name)) {
|
|
25037
|
-
release_heap_string_fields(status, decl.name, decl.type.name);
|
|
25532
|
+
release_heap_string_fields(status, decl.name, decl.type.name, decl.type.is_nullable);
|
|
25038
25533
|
continue;
|
|
25039
25534
|
}
|
|
25040
25535
|
if (is_field_struct_borrow(decl)) continue;
|
|
@@ -28271,6 +28766,7 @@ function build_function_call_node$1(node, status) {
|
|
|
28271
28766
|
}
|
|
28272
28767
|
}
|
|
28273
28768
|
const is_struct = status.structs.find((s) => s.name === node.name && !s.is_simple_type);
|
|
28769
|
+
mark_tuple_literal_move_owners(node, status);
|
|
28274
28770
|
const func_name = is_struct ? node.mangled_name ?? `${node.name}_init` : emission_label(node.resolved_function ?? node);
|
|
28275
28771
|
const param_regs = [
|
|
28276
28772
|
"x0",
|
|
@@ -28310,8 +28806,25 @@ function build_function_call_node$1(node, status) {
|
|
|
28310
28806
|
const env_off = status.closure_env_offsets?.get(node.name);
|
|
28311
28807
|
const paramReg = func_offset === void 0 ? status.function_param_regs?.get(node.name) : void 0;
|
|
28312
28808
|
const desc_source = env_off !== void 0 ? `ldr x9, [x29, #${status.closure_env_slot}]\nldr x9, [x9, #${env_off}]\n` : func_offset !== void 0 ? `ldr x9, [x29, #${func_offset}]\n` : paramReg ? `mov x9, ${paramReg}\n` : `adr x9, ${node.name}\n`;
|
|
28809
|
+
const param_slots = [];
|
|
28810
|
+
let total_visible_slots = 0;
|
|
28811
|
+
for (const p of node.params) {
|
|
28812
|
+
param_slots.push(total_visible_slots);
|
|
28813
|
+
total_visible_slots += type_from_value_node$1(p)?.name === "string" ? 2 : 1;
|
|
28814
|
+
}
|
|
28815
|
+
const reg_capacity = 8 - (start_reg + 1);
|
|
28816
|
+
const overflow_count = Math.max(0, total_visible_slots - reg_capacity);
|
|
28817
|
+
let outgoing_size = 0;
|
|
28818
|
+
let spill_base = 0;
|
|
28819
|
+
if (overflow_count > 0) spill_base = allocate_stack_space(status, overflow_count * 8, 8);
|
|
28313
28820
|
const lambda_arg_slots = [];
|
|
28314
|
-
|
|
28821
|
+
const store_arg_half = (value_reg, slot) => {
|
|
28822
|
+
if (slot < 8) {
|
|
28823
|
+
const reg = param_regs[slot];
|
|
28824
|
+
if (reg === value_reg) emit_asm(status, `\n`);
|
|
28825
|
+
else emit_asm(status, `\nmov ${reg}, ${value_reg}\n`);
|
|
28826
|
+
} else emit_asm(status, `\nstr ${value_reg}, [x29, #${spill_base + (slot - 8) * 8}]\n`);
|
|
28827
|
+
};
|
|
28315
28828
|
for (let i = node.params.length - 1; i >= 0; i--) {
|
|
28316
28829
|
const param = node.params[i];
|
|
28317
28830
|
const is_pair = type_from_value_node$1(param)?.name === "string";
|
|
@@ -28321,19 +28834,25 @@ function build_function_call_node$1(node, status) {
|
|
|
28321
28834
|
emit_asm(status, `str x0, [x29, #${dispose_slot}]\n`);
|
|
28322
28835
|
lambda_arg_slots.push(dispose_slot);
|
|
28323
28836
|
}
|
|
28324
|
-
const
|
|
28325
|
-
|
|
28326
|
-
|
|
28327
|
-
|
|
28328
|
-
|
|
28329
|
-
|
|
28330
|
-
|
|
28837
|
+
const arg_slot = start_reg + 1 + param_slots[i];
|
|
28838
|
+
if (is_pair) store_arg_half("x1", arg_slot + 1);
|
|
28839
|
+
store_arg_half("x0", arg_slot);
|
|
28840
|
+
}
|
|
28841
|
+
if (overflow_count > 0) {
|
|
28842
|
+
outgoing_size = Math.ceil(overflow_count * 8 / 16) * 16;
|
|
28843
|
+
emit_asm(status, `sub sp, sp, #${outgoing_size}\n`);
|
|
28844
|
+
for (let k = 0; k < overflow_count; k++) {
|
|
28845
|
+
emit_asm(status, `ldr x9, [x29, #${spill_base + k * 8}]\n`);
|
|
28846
|
+
emit_asm(status, `str x9, [sp, #${k * 8}]\n`);
|
|
28847
|
+
}
|
|
28331
28848
|
}
|
|
28332
28849
|
emit_asm(status, desc_source);
|
|
28333
28850
|
emit_asm(status, `ldr x8, [x9]\n`);
|
|
28334
28851
|
emit_asm(status, `ldr x0, [x9, #8]\n`);
|
|
28335
28852
|
emit_asm(status, `blr x8\n`);
|
|
28336
28853
|
emit_dispose_lambda_args_a64(status, lambda_arg_slots);
|
|
28854
|
+
if (outgoing_size > 0) emit_asm(status, `add sp, sp, #${outgoing_size}\n`);
|
|
28855
|
+
if (node.type?.name === "string" && !node.type.is_view) status.last_result_is_heap = true;
|
|
28337
28856
|
} else {
|
|
28338
28857
|
const variadic_idx = node.variadic_param_index;
|
|
28339
28858
|
const ref_class_sync_names = [];
|
|
@@ -28596,8 +29115,39 @@ function build_function_call_node$1(node, status) {
|
|
|
28596
29115
|
emit_asm(status, `str x9, [x29, #${off + struct_size}]\n`);
|
|
28597
29116
|
emit_asm(status, `add x0, x29, #${off}\n`);
|
|
28598
29117
|
}
|
|
28599
|
-
} else if (is_struct_type$3(param_type, status) || is_enum_with_data_type$2(param_type, status))
|
|
28600
|
-
|
|
29118
|
+
} else if (is_struct_type$3(param_type, status) || is_enum_with_data_type$2(param_type, status)) {
|
|
29119
|
+
if (node.move_owned_param_indices?.includes(i) && node.params[i].node_type === "value") {
|
|
29120
|
+
const transfer_struct = status.structs.find((st) => st.name === resolve_struct_name(param_type, param.type?.type_args, status) && !st.is_simple_type && !st.is_class && !st.is_generic);
|
|
29121
|
+
const transfer_var = node.params[i].value;
|
|
29122
|
+
if (transfer_struct && direct_string_fields(transfer_struct).length > 0 && transfer_var) {
|
|
29123
|
+
const recorded = /* @__PURE__ */ new Set();
|
|
29124
|
+
for (const key of status.heap_string_fields ?? []) if (key.startsWith(`${transfer_var}.`)) recorded.add(key.slice(transfer_var.length + 1));
|
|
29125
|
+
if (direct_string_fields(transfer_struct).filter((f) => !recorded.has(f.name)).length > 0) {
|
|
29126
|
+
emit_struct_address(node.params[i], status);
|
|
29127
|
+
ensure_newline(status);
|
|
29128
|
+
emit_string_field_strdups_at(status, transfer_struct.name, "x0", 0, recorded);
|
|
29129
|
+
if (status.call_x8_preset && status.struct_return_buffer_var) emit_var_address(status, "x8", status.struct_return_buffer_var);
|
|
29130
|
+
}
|
|
29131
|
+
}
|
|
29132
|
+
}
|
|
29133
|
+
const arg_struct = status.structs.find((st) => st.name === resolve_struct_name(param_type, param.type?.type_args, status) && !st.is_simple_type && !st.is_class && !st.is_generic);
|
|
29134
|
+
if (node.owned_value_param_indices?.includes(i) && arg_struct && !(arg_struct.traits ?? []).length && direct_string_fields(arg_struct).length > 0 && status.normalized_struct_returners?.has(func_name) && !status.func_address_taken?.has(func_name)) {
|
|
29135
|
+
const mono_name = arg_struct.name;
|
|
29136
|
+
const struct_size = get_struct_size(mono_name, status);
|
|
29137
|
+
const off = allocate_stack_space(status, struct_size);
|
|
29138
|
+
emit_struct_address(node.params[i], status);
|
|
29139
|
+
ensure_newline(status);
|
|
29140
|
+
emit_asm(status, `mov x1, x0\n`);
|
|
29141
|
+
emit_asm(status, `add x0, x29, #${off}\n`);
|
|
29142
|
+
for (let b = 0; b < struct_size; b += 8) {
|
|
29143
|
+
emit_asm(status, `ldr x2, [x1, #${b}]\n`);
|
|
29144
|
+
emit_asm(status, `str x2, [x0, #${b}]\n`);
|
|
29145
|
+
}
|
|
29146
|
+
emit_asm(status, `add x0, x29, #${off}\n`);
|
|
29147
|
+
emit_string_field_strdups_at(status, mono_name, "x0");
|
|
29148
|
+
if (status.call_x8_preset && status.struct_return_buffer_var) emit_var_address(status, "x8", status.struct_return_buffer_var);
|
|
29149
|
+
} else emit_struct_address(node.params[i], status);
|
|
29150
|
+
} else if (arg_deferrable(i)) {
|
|
28601
29151
|
deferred_args.push({
|
|
28602
29152
|
param: node.params[i],
|
|
28603
29153
|
slot: arg_slot[i]
|
|
@@ -28729,6 +29279,7 @@ function build_function_call_node$1(node, status) {
|
|
|
28729
29279
|
const tname = all_scope_frames(status).flat().find((d) => d.name === vname)?.type?.name ?? param.type?.name;
|
|
28730
29280
|
if (tname === "string") continue;
|
|
28731
29281
|
if (tname && status.enums.find((e) => e.name === tname && e.has_associated_data)) continue;
|
|
29282
|
+
if (vname !== void 0 && (node.move_owned_param_indices?.includes(idx) ?? false)) clear_heap_string_fields(status, vname);
|
|
28732
29283
|
}
|
|
28733
29284
|
if (param) mark_moved_if_struct(param, status);
|
|
28734
29285
|
}
|
|
@@ -31030,6 +31581,20 @@ function emit_enum_payload_reclaim(cname, enum_node, c, p, status) {
|
|
|
31030
31581
|
if (payload_is_reference$2(p.type, status)) status.code += `${guard} { ${p.type.name}_destroy(${cname}._data._${c.name}.${p.name}); free(${cname}._data._${c.name}.${p.name}); }\n`;
|
|
31031
31582
|
else if (p.type.name === "string") status.code += `${guard} { free(${cname}._data._${c.name}.${p.name}.ptr); }\n`;
|
|
31032
31583
|
}
|
|
31584
|
+
/**
|
|
31585
|
+
* Drop the `name.field` heap-string records belonging to `decls` WITHOUT
|
|
31586
|
+
* emitting any free code. Used when the emitted reclaim would be unreachable
|
|
31587
|
+
* (a function body that cannot fall through — see build_function_node): the
|
|
31588
|
+
* return-site reclaim already emitted the frees, so the records must be
|
|
31589
|
+
* cleared for the next function build but must not produce a second block.
|
|
31590
|
+
*/
|
|
31591
|
+
function discard_string_field_records(status, decls) {
|
|
31592
|
+
if (!status.heap_string_fields?.size) return;
|
|
31593
|
+
for (const dec of decls) {
|
|
31594
|
+
const prefix = `${dec.name}.`;
|
|
31595
|
+
for (const key of Array.from(status.heap_string_fields)) if (key.startsWith(prefix)) status.heap_string_fields.delete(key);
|
|
31596
|
+
}
|
|
31597
|
+
}
|
|
31033
31598
|
function build_auto_free(status) {
|
|
31034
31599
|
free_scoped_declarations(status, status.scoped_declarations);
|
|
31035
31600
|
if (status.deferred_frees?.length) {
|
|
@@ -31051,17 +31616,34 @@ function free_scoped_declarations(status, decls, persist_string_field_records =
|
|
|
31051
31616
|
let commented = false;
|
|
31052
31617
|
if (status.heap_string_fields?.size) for (const dec of decls) {
|
|
31053
31618
|
const prefix = `${dec.name}.`;
|
|
31054
|
-
|
|
31055
|
-
|
|
31056
|
-
|
|
31057
|
-
|
|
31058
|
-
|
|
31619
|
+
const keys = Array.from(status.heap_string_fields).filter((key) => key.startsWith(prefix));
|
|
31620
|
+
if (!keys.length) continue;
|
|
31621
|
+
if (!commented) {
|
|
31622
|
+
status.code += "\n// Auto-free\n";
|
|
31623
|
+
commented = true;
|
|
31624
|
+
}
|
|
31625
|
+
const nullable_flag = is_nullable_struct_type(dec.type, status) ? has_flag_name(c_function_name(dec.name)) : void 0;
|
|
31626
|
+
if (nullable_flag) status.code += `if (${nullable_flag}) { `;
|
|
31627
|
+
for (const key of keys) {
|
|
31059
31628
|
const dot = key.indexOf(".");
|
|
31060
|
-
|
|
31629
|
+
const base = c_function_name(key.substring(0, dot));
|
|
31630
|
+
const arrow = dec.string_fields_via_pointer ? "->" : ".";
|
|
31631
|
+
const field = key.substring(dot + 1);
|
|
31632
|
+
const free_fn = status.audit ? "nomen_free_wrap" : "free";
|
|
31633
|
+
status.code += `${free_fn}(${base}${arrow}${field}.ptr);\n`;
|
|
31061
31634
|
if (!persist_string_field_records) status.heap_string_fields.delete(key);
|
|
31062
31635
|
}
|
|
31636
|
+
if (nullable_flag) status.code += `}\n`;
|
|
31063
31637
|
}
|
|
31064
31638
|
for (const dec of decls) {
|
|
31639
|
+
if (dec.owned_heap_shell) {
|
|
31640
|
+
if (!commented) {
|
|
31641
|
+
status.code += "\n// Auto-free\n";
|
|
31642
|
+
commented = true;
|
|
31643
|
+
}
|
|
31644
|
+
status.code += `${status.audit ? "nomen_free_wrap" : "free"}(${c_function_name(dec.name)});\n`;
|
|
31645
|
+
continue;
|
|
31646
|
+
}
|
|
31065
31647
|
if (persist_string_field_records && dec.force_owned_string === true) continue;
|
|
31066
31648
|
if (status.moved?.has(dec.name)) continue;
|
|
31067
31649
|
const cname = c_function_name(dec.name);
|
|
@@ -31569,6 +32151,49 @@ function emit_rhs_value$1(rhs, nir, status) {
|
|
|
31569
32151
|
}
|
|
31570
32152
|
function build_assignment_node$1(node, status, nir_rhs, nir_swap) {
|
|
31571
32153
|
hoist_field_overrides(node.right_value, build_node$1, status, ";\n", node.left_value.node_type === "value" ? node.left_value.value : void 0);
|
|
32154
|
+
const lhs_nullable_type = lhs_nullable_struct_type(node, status);
|
|
32155
|
+
if (!node.operator && lhs_nullable_type) {
|
|
32156
|
+
const lhs_expr = capture_build(node.left_value, status);
|
|
32157
|
+
const flag = `${lhs_expr}_has`;
|
|
32158
|
+
const rhs_is_null = node.right_value.node_type === "value" && node.right_value.value === "null";
|
|
32159
|
+
const lhs_is_local = node.left_value.node_type === "value";
|
|
32160
|
+
const lhs_name = lhs_is_local ? node.left_value.value : "";
|
|
32161
|
+
const rhs_is_same_var = lhs_is_local && node.right_value.node_type === "value" && node.right_value.value === lhs_name;
|
|
32162
|
+
const type_name = (lhs_is_local ? status.scoped_declarations.find((d) => d.name === lhs_name) : void 0)?.type?.name || "";
|
|
32163
|
+
if (lhs_is_local && !rhs_is_same_var && type_name) {
|
|
32164
|
+
const prefix = `${lhs_name}.`;
|
|
32165
|
+
const recorded = status.heap_string_fields ? Array.from(status.heap_string_fields).filter((k) => k.startsWith(prefix)) : [];
|
|
32166
|
+
if (recorded.length > 0) {
|
|
32167
|
+
const free_fn = status.audit ? "nomen_free_wrap" : "free";
|
|
32168
|
+
const frees = recorded.map((k) => {
|
|
32169
|
+
const dot = k.indexOf(".");
|
|
32170
|
+
return `${free_fn}(${lhs_expr}.${k.slice(dot + 1)}.ptr)`;
|
|
32171
|
+
});
|
|
32172
|
+
status.code += `if (${flag}) { ${frees.join("; ")}; }\n`;
|
|
32173
|
+
for (const k of recorded) status.heap_string_fields.delete(k);
|
|
32174
|
+
}
|
|
32175
|
+
if (!rhs_is_null && is_normalized_struct_call(node.right_value, status)) {
|
|
32176
|
+
const struct = status.structs.find((s) => s.name === type_name && !s.is_simple_type && !s.is_class);
|
|
32177
|
+
if (struct) {
|
|
32178
|
+
if (!status.heap_string_fields) status.heap_string_fields = /* @__PURE__ */ new Set();
|
|
32179
|
+
for (const field of direct_string_fields(struct)) status.heap_string_fields.add(`${lhs_name}.${field.name}`);
|
|
32180
|
+
}
|
|
32181
|
+
}
|
|
32182
|
+
}
|
|
32183
|
+
if (rhs_is_null) status.code += `${flag} = 0`;
|
|
32184
|
+
else if (node.right_value.node_type === "func_call" && is_nullable_struct_type(node.right_value.type, status)) {
|
|
32185
|
+
const old = status.current_nullable_call_flag;
|
|
32186
|
+
status.current_nullable_call_flag = flag;
|
|
32187
|
+
status.code += `${lhs_expr} = `;
|
|
32188
|
+
emit_rhs_value$1(node.right_value, nir_rhs, status);
|
|
32189
|
+
status.current_nullable_call_flag = old;
|
|
32190
|
+
} else {
|
|
32191
|
+
status.code += `${lhs_expr} = `;
|
|
32192
|
+
emit_rhs_value$1(node.right_value, nir_rhs, status);
|
|
32193
|
+
status.code += `;\n${flag} = 1`;
|
|
32194
|
+
}
|
|
32195
|
+
return;
|
|
32196
|
+
}
|
|
31572
32197
|
if (node.left_value.node_type === "access") {
|
|
31573
32198
|
const accessNode = node.left_value;
|
|
31574
32199
|
if (accessNode.target.node_type === "value") {
|
|
@@ -31659,7 +32284,8 @@ function build_assignment_node$1(node, status, nir_rhs, nir_swap) {
|
|
|
31659
32284
|
status.init_assigned_fields.add(tracked_key);
|
|
31660
32285
|
}
|
|
31661
32286
|
const old_was_heap = !!target_struct?.is_class && !first_init_write || !!status.heap_string_fields?.has(tracked_key);
|
|
31662
|
-
|
|
32287
|
+
const is_class_target = !!target_struct?.is_class;
|
|
32288
|
+
if (target_struct && (target_var || is_class_target) && (!self_target || is_class_target)) {
|
|
31663
32289
|
const fresh_heap = is_owned_heap_temp$1(node.right_value, status);
|
|
31664
32290
|
const rhs_is_null_value = node.right_value.node_type === "value" && node.right_value.value === "null";
|
|
31665
32291
|
const saved_len = begin_code_scratch(status);
|
|
@@ -31942,18 +32568,6 @@ function build_assignment_node$1(node, status, nir_rhs, nir_swap) {
|
|
|
31942
32568
|
}
|
|
31943
32569
|
}
|
|
31944
32570
|
status.code += ``;
|
|
31945
|
-
const lhs_nullable_type = lhs_nullable_struct_type(node, status);
|
|
31946
|
-
if (!node.operator && lhs_nullable_type) {
|
|
31947
|
-
const lhs_expr = capture_build(node.left_value, status);
|
|
31948
|
-
const flag = `${lhs_expr}_has`;
|
|
31949
|
-
if (node.right_value.node_type === "value" && node.right_value.value === "null") status.code += `${flag} = 0`;
|
|
31950
|
-
else {
|
|
31951
|
-
status.code += `${lhs_expr} = `;
|
|
31952
|
-
emit_rhs_value$1(node.right_value, nir_rhs, status);
|
|
31953
|
-
status.code += `;\n${flag} = 1`;
|
|
31954
|
-
}
|
|
31955
|
-
return;
|
|
31956
|
-
}
|
|
31957
32571
|
if (!node.operator && !node.swap && node.left_value.node_type === "value") {
|
|
31958
32572
|
const lhs_name = node.left_value.value;
|
|
31959
32573
|
const lhs_type = type_from_value_node$1(node.left_value);
|
|
@@ -32263,6 +32877,101 @@ function emit_nursery_joins_on_return_c(status) {
|
|
|
32263
32877
|
for (let i = status.nursery_stack.length - 1; i >= base; i--) emit_nursery_join_c(status, status.nursery_stack[i], true);
|
|
32264
32878
|
}
|
|
32265
32879
|
//#endregion
|
|
32880
|
+
//#region ../src/build_common/decode_char_literal.ts
|
|
32881
|
+
/**
|
|
32882
|
+
* Decode a Nomen char literal token (raw value INCLUDING its surrounding
|
|
32883
|
+
* quotes, as carried by ValueNode) to its numeric code. The tokenizer
|
|
32884
|
+
* consumes escape pairs as a unit, so multi-char forms like `'\\\\'`,
|
|
32885
|
+
* `'\\n'`, `'\\''` and `'\\xNN'` arrive raw and must be decoded here.
|
|
32886
|
+
* Returns undefined for malformed literals.
|
|
32887
|
+
*/
|
|
32888
|
+
function decode_char_literal(raw) {
|
|
32889
|
+
if (raw.length < 3 || raw[0] !== "'" || raw[raw.length - 1] !== "'") return;
|
|
32890
|
+
const inner = raw.slice(1, -1);
|
|
32891
|
+
if (inner.length === 1) return inner.charCodeAt(0);
|
|
32892
|
+
if (inner[0] !== "\\") return;
|
|
32893
|
+
const esc = inner[1];
|
|
32894
|
+
switch (esc) {
|
|
32895
|
+
case "\\": return 92;
|
|
32896
|
+
case "n": return 10;
|
|
32897
|
+
case "t": return 9;
|
|
32898
|
+
case "r": return 13;
|
|
32899
|
+
case "'": return 39;
|
|
32900
|
+
case "\"": return 34;
|
|
32901
|
+
case "0": return 0;
|
|
32902
|
+
case "x": {
|
|
32903
|
+
const hex = inner.slice(2);
|
|
32904
|
+
if (!/^[0-9a-fA-F]+$/.test(hex)) return void 0;
|
|
32905
|
+
return parseInt(hex, 16);
|
|
32906
|
+
}
|
|
32907
|
+
default: return esc.charCodeAt(0);
|
|
32908
|
+
}
|
|
32909
|
+
}
|
|
32910
|
+
//#endregion
|
|
32911
|
+
//#region ../src/build_c/utils/c_char_literal.ts
|
|
32912
|
+
function c_char_literal(value) {
|
|
32913
|
+
const code = decode_char_literal(value);
|
|
32914
|
+
if (code === void 0) return value;
|
|
32915
|
+
if (code === 92) return `'\\\\'`;
|
|
32916
|
+
if (code === 39) return `'\\''`;
|
|
32917
|
+
if (code === 10) return `'\\n'`;
|
|
32918
|
+
if (code === 13) return `'\\r'`;
|
|
32919
|
+
if (code === 9) return `'\\t'`;
|
|
32920
|
+
if (code < 32 || code > 126) return `'\\x${code.toString(16)}'`;
|
|
32921
|
+
return `'${String.fromCharCode(code)}'`;
|
|
32922
|
+
}
|
|
32923
|
+
//#endregion
|
|
32924
|
+
//#region ../src/build_c/build_value_node.ts
|
|
32925
|
+
const INT_LITERAL_SUFFIX = {
|
|
32926
|
+
int: "L",
|
|
32927
|
+
uint: "UL",
|
|
32928
|
+
int64: "LL",
|
|
32929
|
+
uint64: "ULL"
|
|
32930
|
+
};
|
|
32931
|
+
function build_value_node$1(node, status) {
|
|
32932
|
+
let value = node.value;
|
|
32933
|
+
if (status.closure_env?.has(value)) {
|
|
32934
|
+
if (!(!!status.current_function?.params.some((p) => p.name === value) || !!find_decl_in_c_scopes(status, value))) {
|
|
32935
|
+
status.code += status.closure_env.get(value);
|
|
32936
|
+
return;
|
|
32937
|
+
}
|
|
32938
|
+
}
|
|
32939
|
+
const inlined = status.top_level_consts?.get(value);
|
|
32940
|
+
if (inlined?.value) {
|
|
32941
|
+
build_node$1(fold_string_const(inlined, status.top_level_consts) ?? inlined.value, status);
|
|
32942
|
+
return;
|
|
32943
|
+
}
|
|
32944
|
+
if (node.is_enum_shorthand) {
|
|
32945
|
+
const enum_node = status.enums.find((e) => value.startsWith(e.name + "_"));
|
|
32946
|
+
if (enum_node) {
|
|
32947
|
+
if (enum_node.has_associated_data) status.code += `${value}_init()`;
|
|
32948
|
+
else status.code += value;
|
|
32949
|
+
return;
|
|
32950
|
+
}
|
|
32951
|
+
}
|
|
32952
|
+
if (value === "null") value = "0";
|
|
32953
|
+
else if (value === "true") value = "1";
|
|
32954
|
+
else if (value === "false") value = "0";
|
|
32955
|
+
else if (value === "default") value = "_nomen_default";
|
|
32956
|
+
else if (is_int_literal(value)) value = value.replace(/_/g, "") + (INT_LITERAL_SUFFIX[node.type?.name ?? "int"] ?? "");
|
|
32957
|
+
else if (node.resolved_function) {
|
|
32958
|
+
status.code += materialize_func_value(node.resolved_function, status);
|
|
32959
|
+
return;
|
|
32960
|
+
} else if (value.startsWith("'") && value.endsWith("'")) value = c_char_literal(value);
|
|
32961
|
+
else value = c_function_name(value);
|
|
32962
|
+
if (value.startsWith("\"")) value = `nomen_str_lit(${escape_c_string(value)}, ${string_literal_length(value)})`;
|
|
32963
|
+
if (value !== "self" && status.function_ref_params?.has(value) && !status.suppress_dereference) status.code += `*`;
|
|
32964
|
+
if (value === "self" && status.function_ref_params?.has("self") && !status.suppress_dereference) status.code += `*`;
|
|
32965
|
+
if (value !== "self" && status.ref_class_params?.has(value) && !status.suppress_dereference) {
|
|
32966
|
+
status.code += `(*${value})`;
|
|
32967
|
+
return;
|
|
32968
|
+
}
|
|
32969
|
+
status.code += value;
|
|
32970
|
+
}
|
|
32971
|
+
function escape_c_string(s) {
|
|
32972
|
+
return reencode_hex_escapes(s.replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t"));
|
|
32973
|
+
}
|
|
32974
|
+
//#endregion
|
|
32266
32975
|
//#region ../src/build_c/build_array_values_node.ts
|
|
32267
32976
|
/**
|
|
32268
32977
|
* Build an array literal's `{a, b, c}` initializer text. Under NIR-driven
|
|
@@ -32274,11 +32983,13 @@ function emit_nursery_joins_on_return_c(status) {
|
|
|
32274
32983
|
function build_array_values_node(node, status, nir_elements) {
|
|
32275
32984
|
status.code += `{`;
|
|
32276
32985
|
const elem_is_string = node.type?.name === "string";
|
|
32986
|
+
const static_init = status.static_string_array_inits === true;
|
|
32277
32987
|
node.values.forEach((value, i) => {
|
|
32278
32988
|
if (i > 0) status.code += ", ";
|
|
32279
32989
|
const is_string_literal = elem_is_string && value.node_type === "value" && value.value.length >= 2 && value.value.startsWith("\"") && value.value.endsWith("\"");
|
|
32280
32990
|
const nir_elem = nir_elements?.[i];
|
|
32281
|
-
if (is_string_literal) {
|
|
32991
|
+
if (is_string_literal && static_init) status.code += `{ ${escape_c_string(value.value)}, ${string_literal_length(value.value)} }`;
|
|
32992
|
+
else if (is_string_literal) {
|
|
32282
32993
|
status.code += `nomen_str_dup(`;
|
|
32283
32994
|
if (nir_elem && nir_elem.node === value) emit_expr_from_nir(nir_elem, status);
|
|
32284
32995
|
else build_node$1(value, status);
|
|
@@ -32421,6 +33132,42 @@ function build_declaration_node(node, status, nir_init, nir_swap) {
|
|
|
32421
33132
|
status.code += `] = {${variables.map((v) => `&${v}`).join(", ")}}`;
|
|
32422
33133
|
} else {
|
|
32423
33134
|
const safe_name = c_function_name(node.name);
|
|
33135
|
+
if (node.type.name?.startsWith("_Tuple_") && !node.name.startsWith("_tuple_dst_") && node.value?.node_type === "func_call") {
|
|
33136
|
+
const tuple_call = node.value;
|
|
33137
|
+
const tuple_struct = status.structs.find((st) => st.name === node.type.name && !st.is_simple_type);
|
|
33138
|
+
const init_is_tuple_call = !String(tuple_call.name ?? "").startsWith("_Tuple_") && !!tuple_call.type?.name?.startsWith("_Tuple_");
|
|
33139
|
+
const init_is_tuple_literal = String(tuple_call.name ?? "").startsWith("_Tuple_");
|
|
33140
|
+
if (tuple_struct && init_is_tuple_call) for (const field of direct_string_fields(tuple_struct)) {
|
|
33141
|
+
if (!status.heap_string_fields) status.heap_string_fields = /* @__PURE__ */ new Set();
|
|
33142
|
+
status.heap_string_fields.add(`${node.name}.${field.name}`);
|
|
33143
|
+
}
|
|
33144
|
+
else if (tuple_struct && init_is_tuple_literal) for (const field of direct_string_fields(tuple_struct)) {
|
|
33145
|
+
const idx = parseInt(field.name.replace(/^_/, ""), 10);
|
|
33146
|
+
const element = tuple_call.params[idx];
|
|
33147
|
+
if (element && tuple_return_owned_element(element, status.heap_strings)) {
|
|
33148
|
+
if (!status.heap_string_fields) status.heap_string_fields = /* @__PURE__ */ new Set();
|
|
33149
|
+
status.heap_string_fields.add(`${node.name}.${field.name}`);
|
|
33150
|
+
}
|
|
33151
|
+
}
|
|
33152
|
+
}
|
|
33153
|
+
if (node.declaration === "var" && node.type.name === "string" && !node.type.is_array && node.value?.node_type === "access" && node.value.access.node_type === "access_field" && node.value.target.node_type === "value" && String(node.value.target.value ?? "").startsWith("_tuple_dst_")) {
|
|
33154
|
+
const tuple_binding_value = node.value;
|
|
33155
|
+
const temp_name = String(tuple_binding_value.target.value);
|
|
33156
|
+
const temp_init = status.scoped_declarations.findLast((d) => d.name === temp_name)?.value;
|
|
33157
|
+
const m = /^_(\d+)$/.exec(String(tuple_binding_value.access.name ?? ""));
|
|
33158
|
+
if (temp_init?.node_type === "func_call" && !String(temp_init.name ?? "").startsWith("_Tuple_") && !!temp_init.type?.name?.startsWith("_Tuple_")) {
|
|
33159
|
+
if (!status.heap_strings) status.heap_strings = /* @__PURE__ */ new Set();
|
|
33160
|
+
status.heap_strings.add(safe_name);
|
|
33161
|
+
tuple_binding_value.is_moved = true;
|
|
33162
|
+
} else if (temp_init?.type?.name?.startsWith("_Tuple_") && m) {
|
|
33163
|
+
const param = temp_init.params[parseInt(m[1], 10)];
|
|
33164
|
+
if (param?.node_type === "value" && param.is_moved) {
|
|
33165
|
+
if (!status.heap_strings) status.heap_strings = /* @__PURE__ */ new Set();
|
|
33166
|
+
status.heap_strings.add(safe_name);
|
|
33167
|
+
tuple_binding_value.is_moved = true;
|
|
33168
|
+
}
|
|
33169
|
+
}
|
|
33170
|
+
}
|
|
32424
33171
|
const mono_name = mono_type_name(node.type);
|
|
32425
33172
|
const mono_struct = status.structs.find((s) => s.name === mono_name && !s.is_simple_type && !s.is_generic);
|
|
32426
33173
|
const is_class_type = !!mono_struct?.is_class;
|
|
@@ -32624,6 +33371,7 @@ function build_declaration_node(node, status, nir_init, nir_swap) {
|
|
|
32624
33371
|
status.current_nullable_call_flag = flag;
|
|
32625
33372
|
emit_init_value(node.value, nir_init, status);
|
|
32626
33373
|
status.current_nullable_call_flag = old;
|
|
33374
|
+
record_call_init_string_fields(node, status);
|
|
32627
33375
|
} else {
|
|
32628
33376
|
emit_init_value(node.value, nir_init, status);
|
|
32629
33377
|
status.code += `;\n${flag} = 1`;
|
|
@@ -32693,6 +33441,7 @@ function build_declaration_node(node, status, nir_init, nir_swap) {
|
|
|
32693
33441
|
} else if (node.value.node_type === "value" && node.value.value === "null" && node.type.name === "string" && !node.type.is_view && !node.type.is_array) status.code += ` = {0, 0}`;
|
|
32694
33442
|
else {
|
|
32695
33443
|
status.code += " = ";
|
|
33444
|
+
status.static_string_array_inits = is_stack_array && !status.current_function && node.type.name === "string";
|
|
32696
33445
|
const val_node = node.value;
|
|
32697
33446
|
const val_is_string_literal = node.value.node_type === "value" && val_node.value.length >= 2 && val_node.value.startsWith("\"") && val_node.value.endsWith("\"");
|
|
32698
33447
|
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);
|
|
@@ -32730,6 +33479,7 @@ function build_declaration_node(node, status, nir_init, nir_swap) {
|
|
|
32730
33479
|
emit_init_value(node.value, nir_init, status);
|
|
32731
33480
|
status.suppress_dereference = false;
|
|
32732
33481
|
} else emit_init_value(node.value, nir_init, status);
|
|
33482
|
+
status.static_string_array_inits = false;
|
|
32733
33483
|
}
|
|
32734
33484
|
}
|
|
32735
33485
|
if (node.swap && node.value.node_type === "access") {
|
|
@@ -32741,6 +33491,7 @@ function build_declaration_node(node, status, nir_init, nir_swap) {
|
|
|
32741
33491
|
emit_swap_value$1(node.swap, nir_swap, status);
|
|
32742
33492
|
}
|
|
32743
33493
|
if (has_field_overrides(node.value)) emit_field_overrides(safe_name, node.value, build_node$1, status, ";\n", ";\n");
|
|
33494
|
+
record_call_init_string_fields(node, status);
|
|
32744
33495
|
}
|
|
32745
33496
|
}
|
|
32746
33497
|
}
|
|
@@ -33256,6 +34007,7 @@ function build_return_node(node, status, nir_value) {
|
|
|
33256
34007
|
}
|
|
33257
34008
|
}
|
|
33258
34009
|
let returned_value_decl;
|
|
34010
|
+
let returned_recorded_string_fields;
|
|
33259
34011
|
if (node.value.node_type === "value") {
|
|
33260
34012
|
const value = node.value.value;
|
|
33261
34013
|
returned_value_decl = find_decl_across_scopes(value, status);
|
|
@@ -33269,7 +34021,11 @@ function build_return_node(node, status, nir_value) {
|
|
|
33269
34021
|
}
|
|
33270
34022
|
if (status.heap_string_fields?.size) {
|
|
33271
34023
|
const prefix = `${value}.`;
|
|
33272
|
-
for (const key of Array.from(status.heap_string_fields)) if (key.startsWith(prefix))
|
|
34024
|
+
for (const key of Array.from(status.heap_string_fields)) if (key.startsWith(prefix)) {
|
|
34025
|
+
returned_recorded_string_fields ??= /* @__PURE__ */ new Set();
|
|
34026
|
+
returned_recorded_string_fields.add(key.slice(prefix.length));
|
|
34027
|
+
status.heap_string_fields.delete(key);
|
|
34028
|
+
}
|
|
33273
34029
|
}
|
|
33274
34030
|
}
|
|
33275
34031
|
if (ret_type?.is_array && return_array_var && return_array_len > 0) {
|
|
@@ -33378,6 +34134,20 @@ function build_return_node(node, status, nir_value) {
|
|
|
33378
34134
|
status.code += `;\n`;
|
|
33379
34135
|
if (node.value.node_type === "access" && node.value.access?.node_type === "access_field" && !returns_string_zero && !returns_struct_zero && !returns_view_value && ret_type.name !== "string" && !ret_type.is_view && !ret_type.is_array && status.enums.find((e) => e.name === ret_type.name && e.has_associated_data)) status.code += `_return_val = ${ret_type.name}_copy(_return_val);\n`;
|
|
33380
34136
|
if (has_field_overrides(node.value)) emit_field_overrides("_return_val", node.value, build_node$1, status, "", ";\n");
|
|
34137
|
+
const bare_local_return = node.value.node_type === "value" && node.value.value !== "0" && node.value.value !== "null" && !!returned_value_decl;
|
|
34138
|
+
if (is_struct && !return_is_class && !ret_type.is_view && !ret_type.is_array && return_struct && !returns_struct_zero && bare_local_return) for (const field of direct_string_fields(return_struct)) {
|
|
34139
|
+
if (returned_recorded_string_fields?.has(field.name)) continue;
|
|
34140
|
+
status.code += `_return_val.${field.name} = nomen_str_dup(_return_val.${field.name});\n`;
|
|
34141
|
+
}
|
|
34142
|
+
if (is_struct && !return_is_class && !ret_type.is_view && !ret_type.is_array && return_struct && !returns_struct_zero && node.value?.node_type === "func_call" && String(node.value.name ?? "").startsWith("_Tuple_") && !!node.value.type?.name?.startsWith("_Tuple_")) {
|
|
34143
|
+
const tuple_call = node.value;
|
|
34144
|
+
for (const field of direct_string_fields(return_struct)) {
|
|
34145
|
+
const idx = parseInt(field.name.replace(/^_/, ""), 10);
|
|
34146
|
+
const element = tuple_call.params[idx];
|
|
34147
|
+
if (element && tuple_return_owned_element(element, status.heap_strings)) continue;
|
|
34148
|
+
status.code += `_return_val.${field.name} = nomen_str_dup(_return_val.${field.name});\n`;
|
|
34149
|
+
}
|
|
34150
|
+
}
|
|
33381
34151
|
emit_nursery_joins_on_return_c(status);
|
|
33382
34152
|
reclaim_all_c_scopes(status);
|
|
33383
34153
|
status.code += `return _return_val;\n`;
|
|
@@ -35142,6 +35912,22 @@ function emit_closure_env_type(func, status) {
|
|
|
35142
35912
|
}
|
|
35143
35913
|
//#endregion
|
|
35144
35914
|
//#region ../src/build_c/build_function_node.ts
|
|
35915
|
+
/**
|
|
35916
|
+
* True when control cannot reach the end of `statements` — the last statement
|
|
35917
|
+
* is a `return`, or an `if`/`else` whose every arm itself definitely returns.
|
|
35918
|
+
* Used to suppress the fall-through auto-free: when no path falls through, the
|
|
35919
|
+
* epilogue reclaim is unreachable text after the last `return`.
|
|
35920
|
+
*/
|
|
35921
|
+
function statements_definitely_return(statements) {
|
|
35922
|
+
if (!statements?.length) return false;
|
|
35923
|
+
const last = statements[statements.length - 1];
|
|
35924
|
+
if (last.node_type === "return") return true;
|
|
35925
|
+
if (last.node_type === "if") {
|
|
35926
|
+
const branch = last;
|
|
35927
|
+
return !!branch.if_branch && !!branch.else_branch && statements_definitely_return(branch.if_branch.statements) && statements_definitely_return(branch.else_branch.statements);
|
|
35928
|
+
}
|
|
35929
|
+
return false;
|
|
35930
|
+
}
|
|
35145
35931
|
function build_function_node(node, status) {
|
|
35146
35932
|
if (node.is_generic) return;
|
|
35147
35933
|
if (node.is_extern) {
|
|
@@ -35328,6 +36114,18 @@ function build_function_node(node, status) {
|
|
|
35328
36114
|
if (param_trait && !param_struct?.is_class) decl.trait_class_trait = param.type.name;
|
|
35329
36115
|
status.scoped_declarations.push(decl);
|
|
35330
36116
|
}
|
|
36117
|
+
const is_free_function = (!node.scope || node.scope.node_type === "root" || node.scope.node_type === "func") && !node.params.some((p) => p.is_self_param);
|
|
36118
|
+
const fn_name = node.name;
|
|
36119
|
+
const fn_label = emission_label(node);
|
|
36120
|
+
const owned_value_struct = is_free_function && !node.address_escaped && !param.is_self_param && !param.is_variadic && !param.type.is_ref && !param.type.is_nullable && !param.type.is_view && !param.type.is_array && param.type.name ? status.structs.find((st) => st.name === param.type.name && !st.is_simple_type && !st.is_class) : void 0;
|
|
36121
|
+
if (owned_value_struct && !(owned_value_struct.traits ?? []).length && direct_string_fields(owned_value_struct).length > 0 && !struct_needs_destroy(owned_value_struct, status) && (status.normalized_struct_returners?.has(fn_name) || status.normalized_struct_returners?.has(fn_label)) && !status.func_address_taken?.has(fn_name) && !status.func_address_taken?.has(fn_label)) {
|
|
36122
|
+
const decl = new DeclarationNode(param.start, "private", "var", pname, param.type);
|
|
36123
|
+
decl.string_fields_via_pointer = true;
|
|
36124
|
+
decl.owned_heap_shell = !param.is_moved;
|
|
36125
|
+
status.scoped_declarations.push(decl);
|
|
36126
|
+
if (!status.heap_string_fields) status.heap_string_fields = /* @__PURE__ */ new Set();
|
|
36127
|
+
for (const field of direct_string_fields(owned_value_struct)) status.heap_string_fields.add(`${pname}.${field.name}`);
|
|
36128
|
+
}
|
|
35331
36129
|
}
|
|
35332
36130
|
}
|
|
35333
36131
|
const nir = node.statements?.length ? lower_function(node) : void 0;
|
|
@@ -35351,7 +36149,8 @@ function build_function_node(node, status) {
|
|
|
35351
36149
|
status.current_function_name = old_function_name;
|
|
35352
36150
|
status.current_function = old_current_function;
|
|
35353
36151
|
status.closure_env = old_closure_env;
|
|
35354
|
-
|
|
36152
|
+
if (statements_definitely_return(node.statements)) discard_string_field_records(status, status.scoped_declarations);
|
|
36153
|
+
else build_auto_free(status);
|
|
35355
36154
|
if (node.name.toLocaleLowerCase() === "main" && status.used_fibers) {
|
|
35356
36155
|
status.code += `\n__nomen_fiber_drain_all();\n`;
|
|
35357
36156
|
status.code += `__nomen_io_shutdown();\n`;
|
|
@@ -36535,6 +37334,7 @@ function build_function_call_node(node, status) {
|
|
|
36535
37334
|
const func_name = is_struct ? node.mangled_name ?? `${node.name}_init` : c_function_name(emission_label(node.resolved_function ?? node));
|
|
36536
37335
|
const callee_params = node.resolved_function?.params?.filter((p) => !p.is_self_param);
|
|
36537
37336
|
const lambda_arg_temps = /* @__PURE__ */ new Map();
|
|
37337
|
+
mark_tuple_literal_move_owners(node, status);
|
|
36538
37338
|
if (!is_struct) for (let i = 0; i < node.params.length; i++) {
|
|
36539
37339
|
const p = node.params[i];
|
|
36540
37340
|
if (p.node_type !== "func") continue;
|
|
@@ -36568,6 +37368,27 @@ function build_function_call_node(node, status) {
|
|
|
36568
37368
|
continue;
|
|
36569
37369
|
}
|
|
36570
37370
|
const param_type = type_from_value_node$1(node.params[i]);
|
|
37371
|
+
if (node.move_owned_param_indices?.includes(i) && node.params[i].node_type === "value") {
|
|
37372
|
+
const transfer_struct = status.structs.find((st) => st.name === mono_type_name(param_type) && !st.is_simple_type && !st.is_class && !st.is_generic);
|
|
37373
|
+
const transfer_var = node.params[i].value;
|
|
37374
|
+
if (transfer_struct && direct_string_fields(transfer_struct).length > 0 && transfer_var) {
|
|
37375
|
+
const recorded = /* @__PURE__ */ new Set();
|
|
37376
|
+
for (const key of status.heap_string_fields ?? []) if (key.startsWith(`${transfer_var}.`)) recorded.add(key.slice(transfer_var.length + 1));
|
|
37377
|
+
const unrecorded = direct_string_fields(transfer_struct).filter((f) => !recorded.has(f.name));
|
|
37378
|
+
if (unrecorded.length > 0) {
|
|
37379
|
+
const saved_arg = begin_code_scratch(status);
|
|
37380
|
+
status.code += `(void *)&`;
|
|
37381
|
+
status.suppress_dereference = true;
|
|
37382
|
+
build_node$1(node.params[i], status);
|
|
37383
|
+
status.suppress_dereference = false;
|
|
37384
|
+
const addr_text = end_code_scratch(status, saved_arg);
|
|
37385
|
+
status.code += `(void *)({ struct ${transfer_struct.name} *_t = (struct ${transfer_struct.name} *)${addr_text}; `;
|
|
37386
|
+
for (const f of unrecorded) status.code += `_t->${f.name} = nomen_str_dup(_t->${f.name}); `;
|
|
37387
|
+
status.code += `(void *)_t; })`;
|
|
37388
|
+
continue;
|
|
37389
|
+
}
|
|
37390
|
+
}
|
|
37391
|
+
}
|
|
36571
37392
|
if (node.nullable_param_indices?.includes(i) && node.params[i].node_type === "value" && node.params[i].value === "null") {
|
|
36572
37393
|
status.code += `(void *)&(struct ${param_type.name}){0}, 0`;
|
|
36573
37394
|
continue;
|
|
@@ -36614,6 +37435,23 @@ function build_function_call_node(node, status) {
|
|
|
36614
37435
|
status.suppress_dereference = false;
|
|
36615
37436
|
continue;
|
|
36616
37437
|
}
|
|
37438
|
+
const arg_struct = status.structs.find((st) => st.name === mono_type_name(param_type) && !st.is_simple_type && !st.is_class && !st.is_generic);
|
|
37439
|
+
if (node.owned_value_param_indices?.includes(i) && !param_is_class && arg_struct && !(arg_struct.traits ?? []).length && direct_string_fields(arg_struct).length > 0 && status.normalized_struct_returners?.has(func_name) && !status.func_address_taken?.has(func_name)) {
|
|
37440
|
+
const struct_name = arg_struct.name;
|
|
37441
|
+
const fields = direct_string_fields(arg_struct);
|
|
37442
|
+
const saved_arg = begin_code_scratch(status);
|
|
37443
|
+
status.code += `(void *)&`;
|
|
37444
|
+
status.suppress_dereference = true;
|
|
37445
|
+
build_node$1(node.params[i], status);
|
|
37446
|
+
status.suppress_dereference = false;
|
|
37447
|
+
const addr_text = end_code_scratch(status, saved_arg);
|
|
37448
|
+
status.code += `(void *)({ struct ${struct_name} *_src = (struct ${struct_name} *)${addr_text}; `;
|
|
37449
|
+
status.code += `struct ${struct_name} *_own = (struct ${struct_name} *)malloc(sizeof(struct ${struct_name})); `;
|
|
37450
|
+
status.code += `*_own = *_src; `;
|
|
37451
|
+
for (const field of fields) status.code += `_own->${field.name} = nomen_str_dup(_own->${field.name}); `;
|
|
37452
|
+
status.code += `_own; })`;
|
|
37453
|
+
continue;
|
|
37454
|
+
}
|
|
36617
37455
|
if (!param_is_class && !!status.structs.find((s) => s.name === param_type.name && !s.is_simple_type) || !!status.traits.find((t) => t.name === param_type.name) || is_ref_param) {
|
|
36618
37456
|
if (node.params[i].node_type === "value" && (status.function_ref_params?.has(node.params[i].value) || status.class_vars?.has(node.params[i].value))) status.suppress_dereference = true;
|
|
36619
37457
|
else if (is_ref_param) status.code += `&`;
|
|
@@ -36690,9 +37528,12 @@ function build_function_call_node(node, status) {
|
|
|
36690
37528
|
if (decl_hit) decl_hit.frame.splice(decl_hit.index, 1);
|
|
36691
37529
|
if (is_value_struct) {
|
|
36692
37530
|
const prefix = `${vname}.`;
|
|
37531
|
+
const transfer = node.move_owned_param_indices?.includes(idx) ?? false;
|
|
36693
37532
|
for (const key of Array.from(status.heap_string_fields ?? [])) if (key.startsWith(prefix)) {
|
|
36694
|
-
if (!
|
|
36695
|
-
|
|
37533
|
+
if (!transfer) {
|
|
37534
|
+
if (!status.pending_string_releases) status.pending_string_releases = [];
|
|
37535
|
+
status.pending_string_releases.push(`free(${key}.ptr);`);
|
|
37536
|
+
}
|
|
36696
37537
|
status.heap_string_fields.delete(key);
|
|
36697
37538
|
}
|
|
36698
37539
|
}
|
|
@@ -36895,101 +37736,6 @@ function build_todo_node$1(node, status) {
|
|
|
36895
37736
|
status.code += `exit(EXIT_FAILURE);\n`;
|
|
36896
37737
|
}
|
|
36897
37738
|
//#endregion
|
|
36898
|
-
//#region ../src/build_common/decode_char_literal.ts
|
|
36899
|
-
/**
|
|
36900
|
-
* Decode a Nomen char literal token (raw value INCLUDING its surrounding
|
|
36901
|
-
* quotes, as carried by ValueNode) to its numeric code. The tokenizer
|
|
36902
|
-
* consumes escape pairs as a unit, so multi-char forms like `'\\\\'`,
|
|
36903
|
-
* `'\\n'`, `'\\''` and `'\\xNN'` arrive raw and must be decoded here.
|
|
36904
|
-
* Returns undefined for malformed literals.
|
|
36905
|
-
*/
|
|
36906
|
-
function decode_char_literal(raw) {
|
|
36907
|
-
if (raw.length < 3 || raw[0] !== "'" || raw[raw.length - 1] !== "'") return;
|
|
36908
|
-
const inner = raw.slice(1, -1);
|
|
36909
|
-
if (inner.length === 1) return inner.charCodeAt(0);
|
|
36910
|
-
if (inner[0] !== "\\") return;
|
|
36911
|
-
const esc = inner[1];
|
|
36912
|
-
switch (esc) {
|
|
36913
|
-
case "\\": return 92;
|
|
36914
|
-
case "n": return 10;
|
|
36915
|
-
case "t": return 9;
|
|
36916
|
-
case "r": return 13;
|
|
36917
|
-
case "'": return 39;
|
|
36918
|
-
case "\"": return 34;
|
|
36919
|
-
case "0": return 0;
|
|
36920
|
-
case "x": {
|
|
36921
|
-
const hex = inner.slice(2);
|
|
36922
|
-
if (!/^[0-9a-fA-F]+$/.test(hex)) return void 0;
|
|
36923
|
-
return parseInt(hex, 16);
|
|
36924
|
-
}
|
|
36925
|
-
default: return esc.charCodeAt(0);
|
|
36926
|
-
}
|
|
36927
|
-
}
|
|
36928
|
-
//#endregion
|
|
36929
|
-
//#region ../src/build_c/utils/c_char_literal.ts
|
|
36930
|
-
function c_char_literal(value) {
|
|
36931
|
-
const code = decode_char_literal(value);
|
|
36932
|
-
if (code === void 0) return value;
|
|
36933
|
-
if (code === 92) return `'\\\\'`;
|
|
36934
|
-
if (code === 39) return `'\\''`;
|
|
36935
|
-
if (code === 10) return `'\\n'`;
|
|
36936
|
-
if (code === 13) return `'\\r'`;
|
|
36937
|
-
if (code === 9) return `'\\t'`;
|
|
36938
|
-
if (code < 32 || code > 126) return `'\\x${code.toString(16)}'`;
|
|
36939
|
-
return `'${String.fromCharCode(code)}'`;
|
|
36940
|
-
}
|
|
36941
|
-
//#endregion
|
|
36942
|
-
//#region ../src/build_c/build_value_node.ts
|
|
36943
|
-
const INT_LITERAL_SUFFIX = {
|
|
36944
|
-
int: "L",
|
|
36945
|
-
uint: "UL",
|
|
36946
|
-
int64: "LL",
|
|
36947
|
-
uint64: "ULL"
|
|
36948
|
-
};
|
|
36949
|
-
function build_value_node$1(node, status) {
|
|
36950
|
-
let value = node.value;
|
|
36951
|
-
if (status.closure_env?.has(value)) {
|
|
36952
|
-
if (!(!!status.current_function?.params.some((p) => p.name === value) || !!find_decl_in_c_scopes(status, value))) {
|
|
36953
|
-
status.code += status.closure_env.get(value);
|
|
36954
|
-
return;
|
|
36955
|
-
}
|
|
36956
|
-
}
|
|
36957
|
-
const inlined = status.top_level_consts?.get(value);
|
|
36958
|
-
if (inlined?.value) {
|
|
36959
|
-
build_node$1(fold_string_const(inlined, status.top_level_consts) ?? inlined.value, status);
|
|
36960
|
-
return;
|
|
36961
|
-
}
|
|
36962
|
-
if (node.is_enum_shorthand) {
|
|
36963
|
-
const enum_node = status.enums.find((e) => value.startsWith(e.name + "_"));
|
|
36964
|
-
if (enum_node) {
|
|
36965
|
-
if (enum_node.has_associated_data) status.code += `${value}_init()`;
|
|
36966
|
-
else status.code += value;
|
|
36967
|
-
return;
|
|
36968
|
-
}
|
|
36969
|
-
}
|
|
36970
|
-
if (value === "null") value = "0";
|
|
36971
|
-
else if (value === "true") value = "1";
|
|
36972
|
-
else if (value === "false") value = "0";
|
|
36973
|
-
else if (value === "default") value = "_nomen_default";
|
|
36974
|
-
else if (is_int_literal(value)) value = value.replace(/_/g, "") + (INT_LITERAL_SUFFIX[node.type?.name ?? "int"] ?? "");
|
|
36975
|
-
else if (node.resolved_function) {
|
|
36976
|
-
status.code += materialize_func_value(node.resolved_function, status);
|
|
36977
|
-
return;
|
|
36978
|
-
} else if (value.startsWith("'") && value.endsWith("'")) value = c_char_literal(value);
|
|
36979
|
-
else value = c_function_name(value);
|
|
36980
|
-
if (value.startsWith("\"")) value = `nomen_str_lit(${escape_c_string(value)}, ${string_literal_length(value)})`;
|
|
36981
|
-
if (value !== "self" && status.function_ref_params?.has(value) && !status.suppress_dereference) status.code += `*`;
|
|
36982
|
-
if (value === "self" && status.function_ref_params?.has("self") && !status.suppress_dereference) status.code += `*`;
|
|
36983
|
-
if (value !== "self" && status.ref_class_params?.has(value) && !status.suppress_dereference) {
|
|
36984
|
-
status.code += `(*${value})`;
|
|
36985
|
-
return;
|
|
36986
|
-
}
|
|
36987
|
-
status.code += value;
|
|
36988
|
-
}
|
|
36989
|
-
function escape_c_string(s) {
|
|
36990
|
-
return reencode_hex_escapes(s.replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t"));
|
|
36991
|
-
}
|
|
36992
|
-
//#endregion
|
|
36993
37739
|
//#region ../src/build_c/build_node.ts
|
|
36994
37740
|
function build_node$1(node, status, with_semicolon = false) {
|
|
36995
37741
|
if (node.allocations) {
|
|
@@ -38353,6 +39099,10 @@ function get_base_address(access, status, reg) {
|
|
|
38353
39099
|
if (paramReg !== reg) emit_asm(status, `mov ${reg}, ${paramReg}\n`);
|
|
38354
39100
|
} else if (is_local_ref_var(name, status)) emit_deref_var_address(status, reg, name);
|
|
38355
39101
|
else emit_var_address(status, reg, name);
|
|
39102
|
+
} else if (access_chain_crosses_class(access, status)) {
|
|
39103
|
+
build_node(access.target, status);
|
|
39104
|
+
ensure_newline(status);
|
|
39105
|
+
if (reg !== "x0") emit_asm(status, `mov ${reg}, x0\n`);
|
|
38356
39106
|
} else {
|
|
38357
39107
|
emit_address_of(access.target, status);
|
|
38358
39108
|
ensure_newline(status);
|
|
@@ -38475,13 +39225,49 @@ function build_nullable_struct_assignment(node, status, nir_rhs) {
|
|
|
38475
39225
|
if (node.left_value.node_type === "value") {
|
|
38476
39226
|
const name = node.left_value.value;
|
|
38477
39227
|
const flag_name = has_flag_name(name);
|
|
39228
|
+
const type_name = status.scoped_declarations.find((d) => d.name === name)?.type?.name || status.variable_types?.get(name)?.name || "";
|
|
39229
|
+
const reclaimable = !(node.right_value.node_type === "value" && node.right_value.value === name) && !!status.heap_string_fields?.size && Array.from(status.heap_string_fields).some((k) => k.startsWith(`${name}.`));
|
|
38478
39230
|
if (rhs_is_null) {
|
|
39231
|
+
if (reclaimable) emit_recorded_field_reclaim_for_slot(status, name, type_name);
|
|
38479
39232
|
emit_var_store(status, "xzr", flag_name, 8);
|
|
39233
|
+
drop_heap_string_field_records(status, name);
|
|
39234
|
+
return;
|
|
39235
|
+
}
|
|
39236
|
+
const retarget_records = () => {
|
|
39237
|
+
drop_heap_string_field_records(status, name);
|
|
39238
|
+
if (is_normalized_struct_call(node.right_value, status)) {
|
|
39239
|
+
const struct = status.structs.find((s) => s.name === type_name && !s.is_simple_type && !s.is_class);
|
|
39240
|
+
if (struct) {
|
|
39241
|
+
if (!status.heap_string_fields) status.heap_string_fields = /* @__PURE__ */ new Set();
|
|
39242
|
+
for (const field of direct_string_fields(struct)) status.heap_string_fields.add(`${name}.${field.name}`);
|
|
39243
|
+
}
|
|
39244
|
+
}
|
|
39245
|
+
};
|
|
39246
|
+
if (node.right_value.node_type === "func_call" && is_nullable_struct_type(node.right_value.type, status)) {
|
|
39247
|
+
if (reclaimable) emit_recorded_field_reclaim_for_slot(status, name, type_name);
|
|
39248
|
+
retarget_records();
|
|
39249
|
+
const old_buffer = status.struct_return_buffer;
|
|
39250
|
+
const old_buffer_var = status.struct_return_buffer_var;
|
|
39251
|
+
const old_preset = status.call_x8_preset;
|
|
39252
|
+
emit_var_address(status, "x8", name);
|
|
39253
|
+
status.struct_return_buffer_var = name;
|
|
39254
|
+
status.struct_return_buffer = "x8";
|
|
39255
|
+
status.call_x8_preset = true;
|
|
39256
|
+
emit_rhs_value(node.right_value, nir_rhs, status);
|
|
39257
|
+
status.struct_return_buffer = old_buffer;
|
|
39258
|
+
status.struct_return_buffer_var = old_buffer_var;
|
|
39259
|
+
status.call_x8_preset = old_preset;
|
|
38480
39260
|
return;
|
|
38481
39261
|
}
|
|
38482
39262
|
emit_rhs_value(node.right_value, nir_rhs, status);
|
|
38483
39263
|
ensure_newline(status);
|
|
38484
|
-
|
|
39264
|
+
if (reclaimable) {
|
|
39265
|
+
emit_asm(status, `str x0, [sp, #-16]!\n`);
|
|
39266
|
+
emit_recorded_field_reclaim_for_slot(status, name, type_name);
|
|
39267
|
+
emit_asm(status, `ldr x0, [sp], #16\n`);
|
|
39268
|
+
}
|
|
39269
|
+
retarget_records();
|
|
39270
|
+
const struct_size = get_struct_size(type_name, status);
|
|
38485
39271
|
const dst_offset = status.stack_offsets?.get(name);
|
|
38486
39272
|
emit_struct_copy("x0", "x29", dst_offset ?? 0, struct_size, status);
|
|
38487
39273
|
emit_asm(status, `mov x9, #1\n`);
|
|
@@ -40775,6 +41561,7 @@ function is_owned_heap_temp(node, status) {
|
|
|
40775
41561
|
if (check_type_name !== "string") return false;
|
|
40776
41562
|
if (check_node.node_type === "op") return true;
|
|
40777
41563
|
if (check_node.node_type === "func_call" || check_node.node_type === "access_func") {
|
|
41564
|
+
if (!check_node.type?.is_view && (check_node.is_func_param || check_node.is_func_field_call)) return true;
|
|
40778
41565
|
const raw_name = check_node.name;
|
|
40779
41566
|
const mangled = check_node.mangled_name || raw_name;
|
|
40780
41567
|
const dbg = process.env.NOMEN_DBG_TEMP && typeof mangled === "string" && mangled.indexOf("replace") >= 0;
|
|
@@ -41871,6 +42658,9 @@ function emit_address_of(node, status) {
|
|
|
41871
42658
|
if (paramReg !== "x0") emit_asm(status, `mov x0, ${paramReg}\n`);
|
|
41872
42659
|
} else if (is_local_ref_var(name, status)) emit_deref_var_address(status, "x0", name);
|
|
41873
42660
|
else emit_var_address(status, "x0", name);
|
|
42661
|
+
} else if (access_chain_crosses_class(access, status)) {
|
|
42662
|
+
build_node(access.target, status);
|
|
42663
|
+
ensure_newline(status);
|
|
41874
42664
|
} else {
|
|
41875
42665
|
emit_address_of(access.target, status);
|
|
41876
42666
|
ensure_newline(status);
|
|
@@ -42014,6 +42804,22 @@ function resolve_access_target_type(target, status) {
|
|
|
42014
42804
|
* final field is read. Without this, `state.rules.blocks.length` (State/
|
|
42015
42805
|
* BlockParserState is a class) summed 8+8+8 and loaded from `state + 24`.
|
|
42016
42806
|
*/
|
|
42807
|
+
/**
|
|
42808
|
+
* True when every hop in the access chain is a FIELD access (no call
|
|
42809
|
+
* segments) — the shape whose field address is the root base plus the sum of
|
|
42810
|
+
* field offsets. A call segment (`arena.get(handle).children`,
|
|
42811
|
+
* `make().field`) makes the summed-offset computation invalid: the call
|
|
42812
|
+
* result is different storage than the root base, so the receiver address
|
|
42813
|
+
* must be built from the call (`build_node` of the object) plus the field
|
|
42814
|
+
* offset instead.
|
|
42815
|
+
*/
|
|
42816
|
+
function chain_is_pure_fields(node) {
|
|
42817
|
+
if (node.node_type === "value") return true;
|
|
42818
|
+
if (node.node_type !== "access") return false;
|
|
42819
|
+
const acc = node;
|
|
42820
|
+
if (acc.access.node_type !== "access_field") return false;
|
|
42821
|
+
return chain_is_pure_fields(acc.target);
|
|
42822
|
+
}
|
|
42017
42823
|
function access_chain_crosses_class(node, status) {
|
|
42018
42824
|
const base_type = resolve_access_target_type(get_base_target(node), status);
|
|
42019
42825
|
if (base_type?.name && status.structs.find((s) => s.name === base_type.name && s.is_class)) return true;
|
|
@@ -42436,6 +43242,12 @@ function build_access_field(node, status) {
|
|
|
42436
43242
|
ensure_newline(status);
|
|
42437
43243
|
const final_offset = offset;
|
|
42438
43244
|
const field_type = access_field.type?.name || "";
|
|
43245
|
+
const method_field_type = status.structs.find((s) => s.name === (target_type?.name || "") && !s.is_simple_type)?.fields.find((f) => f.name === access_field.name)?.type ?? access_field.type;
|
|
43246
|
+
const method_field_name = method_field_type?.type_args?.length ? mono_type_name(method_field_type) : method_field_type?.name || "";
|
|
43247
|
+
if (method_field_name && !method_field_type?.is_ref && !method_field_type?.is_nullable && is_struct_type(method_field_name, status)) {
|
|
43248
|
+
if (final_offset > 0) emit_asm(status, `add x0, x0, #${final_offset}\n`);
|
|
43249
|
+
return;
|
|
43250
|
+
}
|
|
42439
43251
|
const at_reg = status.at_addr_reg;
|
|
42440
43252
|
status.at_addr_reg = void 0;
|
|
42441
43253
|
const load_base = at_reg ?? "x0";
|
|
@@ -42457,7 +43269,7 @@ function build_access_field(node, status) {
|
|
|
42457
43269
|
else emit_asm(status, `ldr x0, [${load_base}, #${final_offset}]\n`);
|
|
42458
43270
|
return;
|
|
42459
43271
|
}
|
|
42460
|
-
if (target_is_class_access) {
|
|
43272
|
+
if (target_is_class_access || !chain_is_pure_fields(node.target)) {
|
|
42461
43273
|
build_node(node.target, status);
|
|
42462
43274
|
ensure_newline(status);
|
|
42463
43275
|
const final_offset = get_field_offset(target_type?.name || "", access_field.name, status);
|
|
@@ -43060,9 +43872,11 @@ function build_access_method(node, access_func, status) {
|
|
|
43060
43872
|
} else if (!target_is_simple && node.target.node_type === "access") {
|
|
43061
43873
|
const access_target = node.target;
|
|
43062
43874
|
if (access_target.access.node_type === "access_field") {
|
|
43063
|
-
if (access_chain_crosses_class(access_target, status)) {
|
|
43064
|
-
|
|
43875
|
+
if (!chain_is_pure_fields(access_target) || access_chain_crosses_class(access_target, status)) {
|
|
43876
|
+
const field_offset = get_field_offset(type_from_value_node$1(access_target.target)?.name || "", access_target.access.name, status);
|
|
43877
|
+
build_node(access_target.target, status);
|
|
43065
43878
|
ensure_newline(status);
|
|
43879
|
+
if (field_offset > 0) emit_asm(status, `add x0, x0, #${field_offset}\n`);
|
|
43066
43880
|
} else {
|
|
43067
43881
|
const offset = compute_field_offset(access_target, status);
|
|
43068
43882
|
const base = get_base_target(access_target);
|
|
@@ -44254,6 +45068,195 @@ function scan_func(func, struct_name, result, class_type_names) {
|
|
|
44254
45068
|
}
|
|
44255
45069
|
}
|
|
44256
45070
|
//#endregion
|
|
45071
|
+
//#region ../src/build_common/struct_return_classification.ts
|
|
45072
|
+
/**
|
|
45073
|
+
* Pre-build classification of which functions return value structs whose
|
|
45074
|
+
* string fields are UNIFORMLY heap-owned via return-boundary normalization.
|
|
45075
|
+
* Populates `status.normalized_struct_returners`; a caller binding a call
|
|
45076
|
+
* result of a registered function records the struct's string fields and
|
|
45077
|
+
* frees them at scope exit, while bindings of unregistered callees keep the
|
|
45078
|
+
* pre-existing borrow behavior (never recorded, never freed — sound, though
|
|
45079
|
+
* it may leak on slot overwrites, which is the documented status quo).
|
|
45080
|
+
*
|
|
45081
|
+
* The rule is deliberately narrow: a function normalizes iff EVERY
|
|
45082
|
+
* struct-return statement returns a BARE VARIABLE (a local or parameter).
|
|
45083
|
+
* That is the transfer shape — the returned variable dies at the return, so
|
|
45084
|
+
* the boundary normalization (transfer recorded heap fields, strdup the
|
|
45085
|
+
* rest) is sound and leak-free. Every other shape keeps the status quo:
|
|
45086
|
+
*
|
|
45087
|
+
* - container borrow accessors (`at`/`first`/`slice`/`load` — e.g.
|
|
45088
|
+
* `Map.get`'s `return self.values.load_T(idx)`) yield SLOT-owned bytes;
|
|
45089
|
+
* normalizing them would leak in expression-temp consumers
|
|
45090
|
+
* (`m.get(k).field`) that nobody records or frees;
|
|
45091
|
+
* - owned accessors (`pop`/`move_T`/`copy`) transfer slot-owned heap
|
|
45092
|
+
* buffers — the caller's recording is sound WITHOUT normalization, so
|
|
45093
|
+
* they record via the owned-accessor rule (see
|
|
45094
|
+
* call_init_string_fields.ts) and the callee skips the copy;
|
|
45095
|
+
* - forwarded calls (`return make()`) are skipped when the callee is
|
|
45096
|
+
* registered (its result is already uniformly owned — copying it would
|
|
45097
|
+
* leak the inner buffers) and keep the status quo when it is not.
|
|
45098
|
+
*
|
|
45099
|
+
* This runs as a WHOLE-PROGRAM AST PRE-PASS (before any function body is
|
|
45100
|
+
* built) because a build-order-sensitive registry cannot work: nested
|
|
45101
|
+
* functions are built after their enclosing function's body, so a runtime
|
|
45102
|
+
* registration would be invisible to the caller's earlier lookup.
|
|
45103
|
+
*/
|
|
45104
|
+
/** Collect every function and struct declaration under the AST (including
|
|
45105
|
+
* nested functions, struct methods, and function bodies at any depth). */
|
|
45106
|
+
function collect(node, fns, structs, seen) {
|
|
45107
|
+
if (!node || typeof node !== "object" || seen.has(node)) return;
|
|
45108
|
+
seen.add(node);
|
|
45109
|
+
if (node.node_type === "func") fns.push(node);
|
|
45110
|
+
if (node.node_type === "struct") structs.push(node);
|
|
45111
|
+
for (const key of Object.keys(node)) {
|
|
45112
|
+
if (key === "parent" || key === "scope") continue;
|
|
45113
|
+
const value = node[key];
|
|
45114
|
+
if (Array.isArray(value)) {
|
|
45115
|
+
for (const item of value) if (item && typeof item === "object" && "node_type" in item) collect(item, fns, structs, seen);
|
|
45116
|
+
} else if (value && typeof value === "object" && "node_type" in value) collect(value, fns, structs, seen);
|
|
45117
|
+
}
|
|
45118
|
+
}
|
|
45119
|
+
/** The DIRECT `return <expr>` statements of a function body (nested function
|
|
45120
|
+
* declarations are not descended into — their returns belong to them). */
|
|
45121
|
+
function direct_returns(fn) {
|
|
45122
|
+
const out = [];
|
|
45123
|
+
const seen = /* @__PURE__ */ new Set();
|
|
45124
|
+
const walk = (node) => {
|
|
45125
|
+
if (!node || typeof node !== "object" || seen.has(node)) return;
|
|
45126
|
+
seen.add(node);
|
|
45127
|
+
if (node.node_type === "func" && node !== fn) return;
|
|
45128
|
+
if (node.node_type === "return") out.push(node);
|
|
45129
|
+
for (const key of Object.keys(node)) {
|
|
45130
|
+
if (key === "parent" || key === "scope") continue;
|
|
45131
|
+
const value = node[key];
|
|
45132
|
+
if (Array.isArray(value)) {
|
|
45133
|
+
for (const item of value) if (item && typeof item === "object" && "node_type" in item) walk(item);
|
|
45134
|
+
} else if (value && typeof value === "object" && "node_type" in value) walk(value);
|
|
45135
|
+
}
|
|
45136
|
+
};
|
|
45137
|
+
for (const statement of fn.statements ?? []) walk(statement);
|
|
45138
|
+
return out;
|
|
45139
|
+
}
|
|
45140
|
+
/** Whether the function is a FREE function: not a method of a struct (whose
|
|
45141
|
+
* element/slot params are passed by address for in-place access) and not a
|
|
45142
|
+
* monomorphized container clone. */
|
|
45143
|
+
function is_free_function_node(fn) {
|
|
45144
|
+
const scope = fn.scope;
|
|
45145
|
+
return !scope || scope.node_type === "root" || scope.node_type === "func";
|
|
45146
|
+
}
|
|
45147
|
+
/**
|
|
45148
|
+
* Whole-program pre-pass: classify free functions that take an OWNING value
|
|
45149
|
+
* struct parameter (string fields) — those get pass-by-value semantics:
|
|
45150
|
+
*
|
|
45151
|
+
* - the CALL SITE materializes a uniformly heap-owned shell (struct-copy
|
|
45152
|
+
* + per-string-field strdup) and passes its address;
|
|
45153
|
+
* - the CALLEE seeds the param's string-field records at entry and frees
|
|
45154
|
+
* the surviving fields (and the shell) at scope exit.
|
|
45155
|
+
*
|
|
45156
|
+
* Writes through the param then stay local to the callee; the caller's
|
|
45157
|
+
* variable is never aliased or mutated. Functions whose address escapes
|
|
45158
|
+
* into a func-typed value (`var f = g`, `apply(g, …)`, `b.modify(0, touch)`)
|
|
45159
|
+
* are EXCLUDED: they may be invoked through a pointer with a borrowed
|
|
45160
|
+
* argument, and keep the status quo (borrow + the documented write leak).
|
|
45161
|
+
*
|
|
45162
|
+
* Populates `status.normalized_struct_returners` (the materializing set) and
|
|
45163
|
+
* `status.func_address_taken` (the excluded set) for the call-site and
|
|
45164
|
+
* function-definition builders.
|
|
45165
|
+
*/
|
|
45166
|
+
function gather_normalized_struct_returners(root, status) {
|
|
45167
|
+
const fns = [];
|
|
45168
|
+
const structs = [];
|
|
45169
|
+
collect(root, fns, structs, /* @__PURE__ */ new Set());
|
|
45170
|
+
const address_taken = /* @__PURE__ */ new Set();
|
|
45171
|
+
const value_walk = (node) => {
|
|
45172
|
+
if (!node || typeof node !== "object") return;
|
|
45173
|
+
if (node.node_type === "value") {
|
|
45174
|
+
const v = node;
|
|
45175
|
+
if (typeof v.value === "string" && v.type?.name === "func" && /^[A-Za-z_]/.test(v.value)) address_taken.add(v.value);
|
|
45176
|
+
}
|
|
45177
|
+
for (const key of Object.keys(node)) {
|
|
45178
|
+
if (key === "parent" || key === "scope") continue;
|
|
45179
|
+
const child = node[key];
|
|
45180
|
+
if (Array.isArray(child)) {
|
|
45181
|
+
for (const item of child) if (item && typeof item === "object" && "node_type" in item) value_walk(item);
|
|
45182
|
+
} else if (child && typeof child === "object" && "node_type" in child) value_walk(child);
|
|
45183
|
+
}
|
|
45184
|
+
};
|
|
45185
|
+
value_walk(root);
|
|
45186
|
+
const decl_walk = (node) => {
|
|
45187
|
+
if (!node || typeof node !== "object") return;
|
|
45188
|
+
const d = node;
|
|
45189
|
+
if (d.node_type === "declaration" && typeof d.name === "string" && d.type?.name === "func") address_taken.add(d.name);
|
|
45190
|
+
for (const key of Object.keys(node)) {
|
|
45191
|
+
if (key === "parent" || key === "scope") continue;
|
|
45192
|
+
const child = node[key];
|
|
45193
|
+
if (Array.isArray(child)) {
|
|
45194
|
+
for (const item of child) if (item && typeof item === "object" && "node_type" in item) decl_walk(item);
|
|
45195
|
+
} else if (child && typeof child === "object" && "node_type" in child) decl_walk(child);
|
|
45196
|
+
}
|
|
45197
|
+
};
|
|
45198
|
+
decl_walk(root);
|
|
45199
|
+
const return_candidates = [];
|
|
45200
|
+
for (const fn of fns) {
|
|
45201
|
+
if (!is_free_function_node(fn)) continue;
|
|
45202
|
+
if (address_taken.has(fn.name)) continue;
|
|
45203
|
+
const ret = fn.return_type;
|
|
45204
|
+
if (!ret || ret.is_array || ret.is_view) continue;
|
|
45205
|
+
const ret_struct = structs.find((st) => st.name === ret.name && !st.is_simple_type && !st.is_class);
|
|
45206
|
+
if (!ret_struct || direct_string_fields(ret_struct).length === 0) continue;
|
|
45207
|
+
return_candidates.push(fn);
|
|
45208
|
+
}
|
|
45209
|
+
const return_normalizing = new Set(return_candidates.map((c) => c.name));
|
|
45210
|
+
let changed = true;
|
|
45211
|
+
while (changed) {
|
|
45212
|
+
changed = false;
|
|
45213
|
+
for (const fn of return_candidates) {
|
|
45214
|
+
if (!return_normalizing.has(fn.name)) continue;
|
|
45215
|
+
for (const ret of direct_returns(fn)) {
|
|
45216
|
+
const value = ret.value ?? void 0;
|
|
45217
|
+
if (value && value.node_type === "value") continue;
|
|
45218
|
+
if (value && value.node_type === "func_call" && return_normalizing.has(value.name ?? "")) continue;
|
|
45219
|
+
return_normalizing.delete(fn.name);
|
|
45220
|
+
changed = true;
|
|
45221
|
+
break;
|
|
45222
|
+
}
|
|
45223
|
+
}
|
|
45224
|
+
}
|
|
45225
|
+
for (const fn of fns) {
|
|
45226
|
+
if (!is_free_function_node(fn)) continue;
|
|
45227
|
+
if (address_taken.has(fn.name)) continue;
|
|
45228
|
+
if (fn.params.some((p) => p.is_self_param)) continue;
|
|
45229
|
+
if (fn.address_escaped) continue;
|
|
45230
|
+
if (!fn.has_body) continue;
|
|
45231
|
+
for (const param of fn.params ?? []) {
|
|
45232
|
+
if (param.is_self_param || param.is_variadic) continue;
|
|
45233
|
+
if (param.type.is_ref || param.type.is_nullable || param.type.is_view || param.type.is_array) continue;
|
|
45234
|
+
const struct = param.type.name ? structs.find((st) => st.name === param.type.name && !st.is_simple_type && !st.is_class) : void 0;
|
|
45235
|
+
if (!struct) continue;
|
|
45236
|
+
if (direct_string_fields(struct).length > 0 && !(struct.traits ?? []).length && !struct_needs_destroy(struct, status)) {
|
|
45237
|
+
if (!status.normalized_struct_returners) status.normalized_struct_returners = /* @__PURE__ */ new Set();
|
|
45238
|
+
status.normalized_struct_returners.add(fn.name);
|
|
45239
|
+
status.normalized_struct_returners.add(emission_label(fn));
|
|
45240
|
+
break;
|
|
45241
|
+
}
|
|
45242
|
+
}
|
|
45243
|
+
if (return_normalizing.has(fn.name)) {
|
|
45244
|
+
if (!status.normalized_struct_returners) status.normalized_struct_returners = /* @__PURE__ */ new Set();
|
|
45245
|
+
status.normalized_struct_returners.add(fn.name);
|
|
45246
|
+
status.normalized_struct_returners.add(emission_label(fn));
|
|
45247
|
+
}
|
|
45248
|
+
}
|
|
45249
|
+
for (const struct of structs) {
|
|
45250
|
+
if (struct.is_class || struct.is_generic || struct.is_simple_type) continue;
|
|
45251
|
+
if (!struct.functions.some((f) => f.name === "copy" && f.has_body)) continue;
|
|
45252
|
+
if (direct_string_fields(struct).length === 0) continue;
|
|
45253
|
+
if (!status.normalized_struct_returners) status.normalized_struct_returners = /* @__PURE__ */ new Set();
|
|
45254
|
+
status.normalized_struct_returners.add(`${struct.name}_copy`);
|
|
45255
|
+
}
|
|
45256
|
+
if (!status.func_address_taken) status.func_address_taken = /* @__PURE__ */ new Set();
|
|
45257
|
+
for (const name of address_taken) status.func_address_taken.add(name);
|
|
45258
|
+
}
|
|
45259
|
+
//#endregion
|
|
44257
45260
|
//#region ../src/build.ts
|
|
44258
45261
|
function build(root, options = {}) {
|
|
44259
45262
|
let status = {
|
|
@@ -44287,6 +45290,7 @@ function build(root, options = {}) {
|
|
|
44287
45290
|
reset_match_temp_counter();
|
|
44288
45291
|
reset_ns_tmp_counter();
|
|
44289
45292
|
stamp_last_use_moves(root);
|
|
45293
|
+
gather_normalized_struct_returners(root, status);
|
|
44290
45294
|
if (options.arch === "aarch64") {
|
|
44291
45295
|
install_asm_code_buffer(status);
|
|
44292
45296
|
reset_string_counter$1();
|
|
@@ -46510,6 +47514,23 @@ function is_owning_struct_type_requiring_move(type, status) {
|
|
|
46510
47514
|
if (!s || s.is_class) return false;
|
|
46511
47515
|
return struct_owns_non_string_heap(s, status, /* @__PURE__ */ new Set());
|
|
46512
47516
|
}
|
|
47517
|
+
/**
|
|
47518
|
+
* Whether `type` is a concrete value struct with direct string fields whose
|
|
47519
|
+
* only ownership those strings — the pass-by-value convention's domain: a
|
|
47520
|
+
* parameter of this type is fully owned by the callee (the call boundary
|
|
47521
|
+
* materializes a uniformly heap-owned copy), so `var` field writes inside
|
|
47522
|
+
* the body stay local and no call-site acknowledgment is needed. Excludes
|
|
47523
|
+
* structs that own non-string heap (those keep `move ... swap` / `ref`
|
|
47524
|
+
* semantics) and structs conforming to traits (trait dispatch aliases).
|
|
47525
|
+
*/
|
|
47526
|
+
function is_pass_by_value_owning_struct(type, status) {
|
|
47527
|
+
const s = resolve_struct$1(type, status);
|
|
47528
|
+
if (!s || s.is_class || s.is_generic || s.is_simple_type) return false;
|
|
47529
|
+
if ((s.traits ?? []).length > 0) return false;
|
|
47530
|
+
if (direct_string_fields(s).length === 0) return false;
|
|
47531
|
+
if (struct_owns_non_string_heap(s, status, /* @__PURE__ */ new Set())) return false;
|
|
47532
|
+
return true;
|
|
47533
|
+
}
|
|
46513
47534
|
function struct_owns_non_string_heap(s, status, visited) {
|
|
46514
47535
|
if (visited.has(s.name)) return false;
|
|
46515
47536
|
visited.add(s.name);
|
|
@@ -47051,9 +48072,11 @@ function mismatch(expected, actual, status, start) {
|
|
|
47051
48072
|
* when either side's signature is unavailable.
|
|
47052
48073
|
*/
|
|
47053
48074
|
function check_func_argument_signature(expected, arg, status, start) {
|
|
48075
|
+
const expected_params = expected.func_params ?? expected.type?.func_params;
|
|
48076
|
+
const expected_return = expected.func_return_type ?? expected.type?.func_return_type;
|
|
47054
48077
|
const expected_sig = {
|
|
47055
|
-
params:
|
|
47056
|
-
return_type:
|
|
48078
|
+
params: expected_params ?? (expected_return ? [] : void 0),
|
|
48079
|
+
return_type: expected_return
|
|
47057
48080
|
};
|
|
47058
48081
|
if (!expected_sig.params && !expected_sig.return_type) return;
|
|
47059
48082
|
const actual_sig = arg_signature(arg, status);
|
|
@@ -49150,6 +50173,12 @@ function check_declaration_node(decl, status) {
|
|
|
49150
50173
|
if (is_view_keyword && decl.type_start !== void 0) decl.type.is_view = true;
|
|
49151
50174
|
if (decl.func_params) {
|
|
49152
50175
|
if (is_view_keyword) add_error(status, `'view' cannot declare a function-typed binding`, decl.start);
|
|
50176
|
+
if (decl.value?.node_type === "func") decl.value.address_escaped = true;
|
|
50177
|
+
else if (decl.value?.node_type === "value") {
|
|
50178
|
+
const fn = status.functions.findLast((f) => f.name === decl.value.value);
|
|
50179
|
+
if (fn) fn.address_escaped = true;
|
|
50180
|
+
}
|
|
50181
|
+
if (decl.value?.node_type === "value" && decl.value.value === "null" && !(decl.type.name === "func" && decl.type.is_nullable)) add_error(status, `cannot assign null to a non-nullable func — declare it 'func?' (or 'Func<...>?')`, decl.value.start);
|
|
49153
50182
|
if (decl.func_return_type) {
|
|
49154
50183
|
check_type_exists(decl.func_return_type, status, -1);
|
|
49155
50184
|
const materialized = materialize_type(decl.func_return_type, status);
|
|
@@ -49214,7 +50243,7 @@ function check_declaration_node(decl, status) {
|
|
|
49214
50243
|
status.values.push({
|
|
49215
50244
|
declaration,
|
|
49216
50245
|
name: decl.name,
|
|
49217
|
-
type: decl.func_return_type || decl.type,
|
|
50246
|
+
type: decl.type.name === "func" && decl.type.is_nullable ? decl.type : decl.func_return_type || decl.type,
|
|
49218
50247
|
is_set: !!decl.value,
|
|
49219
50248
|
start: decl.start,
|
|
49220
50249
|
is_null: decl.value?.node_type === "value" && decl.value.value === "null",
|
|
@@ -49278,10 +50307,18 @@ function check_declaration_node(decl, status) {
|
|
|
49278
50307
|
if (decl.value?.node_type === "access" && decl.value.access.node_type === "access_field") {
|
|
49279
50308
|
const field_type = type_from_value_node(decl.value, status);
|
|
49280
50309
|
if (field_type.name && is_owning_struct_type_requiring_move(field_type, status)) {
|
|
49281
|
-
const
|
|
49282
|
-
|
|
49283
|
-
|
|
49284
|
-
|
|
50310
|
+
const access_field = decl.value.access;
|
|
50311
|
+
const field_name = access_field.name;
|
|
50312
|
+
const temp_rhs = af_is_destructure_temp(decl.value.target.node_type === "value" ? decl.value.target.value : "");
|
|
50313
|
+
if (!decl.value.is_moved && !temp_rhs) add_error(status, `cannot copy '${field_type.name}' out of field '${field_name}' by value — it owns heap resources; use 'move ... swap <replacement>'`, decl.value.start);
|
|
50314
|
+
else if (!decl.swap) {
|
|
50315
|
+
if (!access_field.destructure_move && !temp_rhs) add_error(status, `move out of a field requires a swap to revalidate it`, decl.value.start);
|
|
50316
|
+
else {
|
|
50317
|
+
decl.value.is_moved = true;
|
|
50318
|
+
decl.swap = synthesized_swap_value(field_type, decl.value.start);
|
|
50319
|
+
if (check_node(decl.swap, status)) check_type_and_value_match(field_type, type_from_value_node(decl.swap, status), void 0, status, decl.swap.start, "swap");
|
|
50320
|
+
}
|
|
50321
|
+
} else if (check_node(decl.swap, status)) check_type_and_value_match(field_type, type_from_value_node(decl.swap, status), void 0, status, decl.swap.start, "swap");
|
|
49285
50322
|
}
|
|
49286
50323
|
}
|
|
49287
50324
|
if (decl.value?.node_type === "value" && decl.value.is_moved && !decl.swap) {
|
|
@@ -49425,6 +50462,33 @@ function extract_const_value(value) {
|
|
|
49425
50462
|
if (/^[+-]?\d+$/.test(vn.value)) return parseInt(vn.value, 10);
|
|
49426
50463
|
if (/^[+-]?\d+\.\d+$/.test(vn.value)) return parseFloat(vn.value);
|
|
49427
50464
|
}
|
|
50465
|
+
/**
|
|
50466
|
+
* The revalidating swap value synthesized for a move-DESTRUCTURING binding
|
|
50467
|
+
* (`var [move a] = t`): a fresh default of the moved field's type. A string
|
|
50468
|
+
* swaps in an empty literal; an owning struct (List/Buffer/Map/...) swaps in
|
|
50469
|
+
* its no-argument constructor (type_args carried so `List<string>` swaps in
|
|
50470
|
+
* `List<string>()`). The zero/empty replacement makes the source field's own
|
|
50471
|
+
* cleanup a no-op instead of a double-free of the transferred buffer.
|
|
50472
|
+
*/
|
|
50473
|
+
function synthesized_swap_value(field_type, start) {
|
|
50474
|
+
if (field_type.name === "string") return new ValueNode(start, "\"\"", new Type("string"));
|
|
50475
|
+
const constructor = new FunctionCallNode(start, field_type.name);
|
|
50476
|
+
constructor.type = new Type(field_type.name);
|
|
50477
|
+
if (field_type.type_args?.length) constructor.type_args = field_type.type_args;
|
|
50478
|
+
return constructor;
|
|
50479
|
+
}
|
|
50480
|
+
/**
|
|
50481
|
+
* Whether a destructuring binding's access targets the compiler-created
|
|
50482
|
+
* `_tuple_dst_N` temp (a non-simple destructuring RHS): the temp exists only
|
|
50483
|
+
* to feed the bindings and is never read again, so an owning field's
|
|
50484
|
+
* ownership transfer to the binding is INFERRED (a view binding would
|
|
50485
|
+
* dangle — the temp's cleanup frees its fields). (`is_destructure` itself
|
|
50486
|
+
* is cleared by the time the binding's owning-field check runs — the access
|
|
50487
|
+
* rewrite consumes it — so only the temp-name convention identifies this.)
|
|
50488
|
+
*/
|
|
50489
|
+
function af_is_destructure_temp(target_name) {
|
|
50490
|
+
return !!target_name && target_name.startsWith("_tuple_dst_");
|
|
50491
|
+
}
|
|
49428
50492
|
//#endregion
|
|
49429
50493
|
//#region ../src/check/utils/check_init_fields.ts
|
|
49430
50494
|
const checked_structs = /* @__PURE__ */ new WeakSet();
|
|
@@ -49712,7 +50776,7 @@ function check_struct_node(struct, status) {
|
|
|
49712
50776
|
continue;
|
|
49713
50777
|
}
|
|
49714
50778
|
if (decl.func_params) {
|
|
49715
|
-
decl.type = new Type("func");
|
|
50779
|
+
if (decl.type.name !== "func") decl.type = new Type("func");
|
|
49716
50780
|
check_declaration_node(decl, status);
|
|
49717
50781
|
continue;
|
|
49718
50782
|
}
|
|
@@ -50020,6 +51084,7 @@ function synthesize_for_struct(struct, status) {
|
|
|
50020
51084
|
if (struct.traits.includes("Stringable") && !struct_has_function(struct, "to_string") && fields.every((f) => field_is_stringable(f, status, /* @__PURE__ */ new Set()))) struct.functions.push(build_to_string(struct, fields));
|
|
50021
51085
|
if (struct.traits.includes("Equatable") && !struct_has_function(struct, "eq") && !struct_has_function(struct, "ne") && fields.every((f) => field_is_equatable(f, status, /* @__PURE__ */ new Set()))) struct.functions.push(build_eq(struct, fields));
|
|
50022
51086
|
if (struct.traits.includes("Hashable") && !struct_has_function(struct, "hash") && fields.every((f) => field_is_hashable(f, status, /* @__PURE__ */ new Set()))) struct.functions.push(build_hash(struct, fields));
|
|
51087
|
+
synthesize_copy_method(struct, status);
|
|
50023
51088
|
}
|
|
50024
51089
|
function build_to_string(struct, fields) {
|
|
50025
51090
|
const parts = [string_literal(`${struct.name}(`)];
|
|
@@ -50051,6 +51116,81 @@ function build_hash(struct, fields) {
|
|
|
50051
51116
|
const ret = new ReturnNode(-1, expr);
|
|
50052
51117
|
return new FunctionNode(-1, "pub", "hash", new Type("uint"), [self_param(struct)], [ret]);
|
|
50053
51118
|
}
|
|
51119
|
+
/**
|
|
51120
|
+
* Whether `struct` is eligible for a synthesized `copy` method: a value
|
|
51121
|
+
* struct whose only ownership is its DIRECT string fields — the same set
|
|
51122
|
+
* the pass-by-value argument convention covers. A struct that additionally
|
|
51123
|
+
* owns non-string heap (a resource-releasing `#destroy`, a class field, a
|
|
51124
|
+
* nested requiring-move struct) cannot be deep-copied by "byte copy +
|
|
51125
|
+
* strdup the strings" — its copy stays unwritten (move/swap remains the
|
|
51126
|
+
* escape hatch) — and a struct with no string fields is already soundly
|
|
51127
|
+
* byte-copyable, so it needs no method.
|
|
51128
|
+
*/
|
|
51129
|
+
function struct_is_copyable(struct, status) {
|
|
51130
|
+
if (struct.is_class || struct.is_generic || struct.is_simple_type) return false;
|
|
51131
|
+
if (direct_string_fields(struct).length === 0) return false;
|
|
51132
|
+
if (struct_owns_non_string_heap(struct, status, /* @__PURE__ */ new Set())) return false;
|
|
51133
|
+
return struct_copy_ctor_args(struct) !== null;
|
|
51134
|
+
}
|
|
51135
|
+
/**
|
|
51136
|
+
* How a synthesized `copy` constructs the fresh value: the zero-argument
|
|
51137
|
+
* `#init` overload when one exists (auto or user), else the bodyless AUTO
|
|
51138
|
+
* `#init` whose params map 1:1 onto non-defaulted fields (pass
|
|
51139
|
+
* `self.<param>` for each). Null when neither shape exists — a user #init
|
|
51140
|
+
* with params only cannot be invoked without re-running its custom logic,
|
|
51141
|
+
* so no `copy` is synthesized.
|
|
51142
|
+
*/
|
|
51143
|
+
function struct_copy_ctor_args(struct) {
|
|
51144
|
+
const inits = struct.functions.filter((f) => f.name === "#init");
|
|
51145
|
+
if (inits.some((f) => f.params.filter((p) => !p.is_self_param).length === 0)) return [];
|
|
51146
|
+
const auto = inits.find((f) => !f.has_body);
|
|
51147
|
+
if (!auto) return null;
|
|
51148
|
+
return auto.params.filter((p) => !p.is_self_param).map((p) => p.name);
|
|
51149
|
+
}
|
|
51150
|
+
/** Whether `type` names a struct that has (or will get) a `copy` method.
|
|
51151
|
+
* Order-independent: it re-evaluates the eligibility predicate instead of
|
|
51152
|
+
* looking for an already-synthesized method, so an outer struct nested in
|
|
51153
|
+
* the same pre-pass resolves a later-declared field struct. */
|
|
51154
|
+
function field_type_is_copyable(type, status) {
|
|
51155
|
+
if (!type.name || type.is_array || type.is_ref || type.is_view || type.is_nullable) return false;
|
|
51156
|
+
const nested = status.structs.find((s) => s.name === type.name);
|
|
51157
|
+
return !!nested && struct_is_copyable(nested, status);
|
|
51158
|
+
}
|
|
51159
|
+
/**
|
|
51160
|
+
* Synthesize `pub func copy(self) -> Struct` for owning value structs: a
|
|
51161
|
+
* fresh `Struct()` whose every field is rewritten from `self` — string
|
|
51162
|
+
* field writes strdup the source (the backends' field-write path), nested
|
|
51163
|
+
* copyable structs recurse through their own `copy`, everything else byte
|
|
51164
|
+
* copies. `return c` hits the return-boundary normalization (all fields
|
|
51165
|
+
* recorded → transfer raw), so the caller receives a uniformly heap-owned
|
|
51166
|
+
* value with no extra copies.
|
|
51167
|
+
*/
|
|
51168
|
+
function build_copy(struct, status) {
|
|
51169
|
+
const ctor_args = struct_copy_ctor_args(struct) ?? [];
|
|
51170
|
+
const ctor = new FunctionCallNode(-1, struct.name, new Type(struct.name), ctor_args.map((name) => field_access("self", name)));
|
|
51171
|
+
const statements = [new DeclarationNode(-1, "private", "var", "c", new Type(struct.name), ctor)];
|
|
51172
|
+
for (const field of struct.fields) {
|
|
51173
|
+
const right = field_type_is_copyable(field.type, status) ? method_call(field_access("self", field.name), "copy") : field_access("self", field.name);
|
|
51174
|
+
statements.push(new AssignmentNode(-1, field_access("c", field.name), right, "="));
|
|
51175
|
+
}
|
|
51176
|
+
statements.push(new ReturnNode(-1, new ValueNode(-1, "c")));
|
|
51177
|
+
return new FunctionNode(-1, "pub", "copy", new Type(struct.name), [self_param(struct)], statements);
|
|
51178
|
+
}
|
|
51179
|
+
/**
|
|
51180
|
+
* Synthesize the `copy` method on `struct` when eligible and not already
|
|
51181
|
+
* present. Called from the block-level derive pre-pass (which runs before
|
|
51182
|
+
* the struct is checked, so the method's body is checked with it) AND from
|
|
51183
|
+
* `monomorphize` — a mono struct materializes mid-check, after that pre-pass
|
|
51184
|
+
* ran, so without this hook `Box<string>.copy()` — advertised by the
|
|
51185
|
+
* copy-discipline error messages — would resolve to "Function not found".
|
|
51186
|
+
* The caller is responsible for checking the synthesized body when no later
|
|
51187
|
+
* struct check will (see monomorphize).
|
|
51188
|
+
*/
|
|
51189
|
+
function synthesize_copy_method(struct, status) {
|
|
51190
|
+
if (struct_has_function(struct, "copy")) return;
|
|
51191
|
+
if (!struct_is_copyable(struct, status)) return;
|
|
51192
|
+
struct.functions.push(build_copy(struct, status));
|
|
51193
|
+
}
|
|
50054
51194
|
//#endregion
|
|
50055
51195
|
//#region ../src/check/check_block_node.ts
|
|
50056
51196
|
function check_block_node(node, status) {
|
|
@@ -50502,8 +51642,8 @@ function check_function_parameter_node(param, status, is_library_scope = false)
|
|
|
50502
51642
|
if (param.type.is_pointer && !is_library_scope) add_error(status, `'ptr' parameters are reserved for the System library — raw pointer manipulation is not available to user code`, param.type_start ?? param.start);
|
|
50503
51643
|
instantiate_generic_type(param.type, status);
|
|
50504
51644
|
}
|
|
50505
|
-
if (param.declaration === "var" && !param.is_moved && !param.is_self_param && !param.type.is_ref) add_error(status, `'var' parameters are not allowed — use 'ref' to mutate the caller's value (with 'ref' at the call site), or take it read-only and make a local 'var' copy`, param.start);
|
|
50506
|
-
if (param.is_moved && param.type.name && !is_class_type$1(param.type.name, status) && !is_trait_type(param.type.name, status) && !is_owning_struct_type_requiring_move(param.type, status) && !status.type_params.includes(param.type.name) && !status.structs.some((s) => s.type_params.includes(param.type.name))) add_error(status, `move is only allowed for class, trait, or owning struct types, not '${param.type.name}'`, param.start);
|
|
51645
|
+
if (param.declaration === "var" && !param.is_moved && !param.is_self_param && !param.type.is_ref && !is_pass_by_value_owning_struct(param.type, status)) add_error(status, `'var' parameters are not allowed — use 'ref' to mutate the caller's value (with 'ref' at the call site), or take it read-only and make a local 'var' copy`, param.start);
|
|
51646
|
+
if (param.is_moved && param.type.name && !is_class_type$1(param.type.name, status) && !is_trait_type(param.type.name, status) && !is_owning_struct_type_requiring_move(param.type, status) && !is_pass_by_value_owning_struct(param.type, status) && !status.type_params.includes(param.type.name) && !status.structs.some((s) => s.type_params.includes(param.type.name))) add_error(status, `move is only allowed for class, trait, or owning struct types, not '${param.type.name}'`, param.start);
|
|
50507
51647
|
if (param.is_variadic && param.default_value) {
|
|
50508
51648
|
add_error(status, `Variadic parameter cannot have a default value`, param.start);
|
|
50509
51649
|
return;
|
|
@@ -50515,7 +51655,7 @@ function check_function_parameter_node(param, status, is_library_scope = false)
|
|
|
50515
51655
|
}
|
|
50516
51656
|
if (status.values.some((v) => v.name === param.name && v.declaration !== "const" && !(is_library_scope && v.is_global))) add_error(status, `Parameter already declared: ${param.name}`, param.start);
|
|
50517
51657
|
status.values.push({
|
|
50518
|
-
declaration: param.declaration,
|
|
51658
|
+
declaration: param.declaration === "var" || is_pass_by_value_owning_struct(param.type, status) ? "var" : param.declaration,
|
|
50519
51659
|
name: param.name,
|
|
50520
51660
|
type: param.type,
|
|
50521
51661
|
is_set: true,
|
|
@@ -50932,7 +52072,7 @@ function check_function_call_node(node, status) {
|
|
|
50932
52072
|
return false;
|
|
50933
52073
|
}
|
|
50934
52074
|
if (magic_ctor && spawn_ctor_shape) return check_magic_ctor(node, status, magic_ctor);
|
|
50935
|
-
let func = find_free_function(status, node.name);
|
|
52075
|
+
let func = status.values.findLast((v) => v.name === node.name && v.func_params !== void 0) ? void 0 : find_free_function(status, node.name);
|
|
50936
52076
|
if (func?.is_closure && func.captures?.length) func = void 0;
|
|
50937
52077
|
if (!func) {
|
|
50938
52078
|
const struct = resolve_declared_struct(node.name, status);
|
|
@@ -51001,6 +52141,10 @@ function check_function_call_node(node, status) {
|
|
|
51001
52141
|
const param_index = status.values.findLastIndex((v) => v.name === node.name);
|
|
51002
52142
|
const param_value = param_index >= 0 ? status.values[param_index] : void 0;
|
|
51003
52143
|
if (!!param_value && (param_value.type.name === "func" || param_value.func_params !== void 0 || param_value.func_return_type !== void 0) && param_value) {
|
|
52144
|
+
if (param_value.is_null && !status.allow_null_value) {
|
|
52145
|
+
add_error(status, `Variable '${node.name}' may be null`, node.start);
|
|
52146
|
+
return false;
|
|
52147
|
+
}
|
|
51004
52148
|
if (status.moved_variables?.has(node.name)) {
|
|
51005
52149
|
add_error(status, `Variable '${node.name}' used after move`, node.start);
|
|
51006
52150
|
return false;
|
|
@@ -51229,6 +52373,30 @@ function monomorphize(generic_struct, type_args, status) {
|
|
|
51229
52373
|
const init_func = new FunctionNode(generic_struct.start, "pub", "#init", init_return_type, init_params);
|
|
51230
52374
|
mono_struct.functions.push(init_func);
|
|
51231
52375
|
}
|
|
52376
|
+
synthesize_copy_method(mono_struct, status);
|
|
52377
|
+
const mono_copy = mono_struct.functions.find((f) => f.name === "copy");
|
|
52378
|
+
if (mono_copy && !mono_copy.checked) {
|
|
52379
|
+
mono_copy.scope = mono_struct;
|
|
52380
|
+
check_function_node(mono_copy, {
|
|
52381
|
+
stack: status.stack,
|
|
52382
|
+
scope_depth: status.scope_depth,
|
|
52383
|
+
types: status.types,
|
|
52384
|
+
values: [],
|
|
52385
|
+
function_value_base: 0,
|
|
52386
|
+
structs: status.structs,
|
|
52387
|
+
traits: status.traits,
|
|
52388
|
+
enums: status.enums,
|
|
52389
|
+
bitsets: status.bitsets,
|
|
52390
|
+
functions: status.functions,
|
|
52391
|
+
allocations: [],
|
|
52392
|
+
var_name_counter: status.var_name_counter,
|
|
52393
|
+
type_params: [],
|
|
52394
|
+
errors: status.errors,
|
|
52395
|
+
function_emission_names: status.function_emission_names,
|
|
52396
|
+
allow_internal: status.allow_internal,
|
|
52397
|
+
library_boundary: status.library_boundary
|
|
52398
|
+
});
|
|
52399
|
+
}
|
|
51232
52400
|
const root = status.stack[0];
|
|
51233
52401
|
if (!root.statements.some((s) => s.node_type === "struct" && s.name === mono_name)) root.statements.push(mono_struct);
|
|
51234
52402
|
return mono_struct;
|
|
@@ -51732,6 +52900,8 @@ function check_magic_ctor(node, status, name) {
|
|
|
51732
52900
|
add_error(status, `Spawned call '${call.name}' did not resolve`, node.start);
|
|
51733
52901
|
return false;
|
|
51734
52902
|
}
|
|
52903
|
+
const spawned = call.resolved_function;
|
|
52904
|
+
if (spawned) spawned.address_escaped = true;
|
|
51735
52905
|
validate_spawn_args_sendable(call, status);
|
|
51736
52906
|
stamp_spawn_ctor(node, name);
|
|
51737
52907
|
const return_type = call.type;
|
|
@@ -53318,6 +54488,13 @@ function check_function_call(node, status, func, target_type, self_value, self_p
|
|
|
53318
54488
|
const param_type = type_from_value_node(param, status);
|
|
53319
54489
|
const param_value = value_from_value_node(param);
|
|
53320
54490
|
const has_ref_keyword = node.ref_param_indices?.includes(i) ?? false;
|
|
54491
|
+
if (func && func_param && func.has_body && (!func.scope || func.scope.node_type !== "struct") && !func.params.some((p) => p.is_self_param) && !func.address_escaped && !func_param.is_self_param && !func_param.is_variadic && !func_param.type.is_ref && !func_param.type.is_nullable && func_param.type.name && !is_owning_struct_type_requiring_move(func_param.type, status)) {
|
|
54492
|
+
const owned_struct = status.structs.find((st) => st.name === func_param.type.name && !st.is_simple_type && !st.is_class);
|
|
54493
|
+
if (owned_struct && !(owned_struct.traits ?? []).length && direct_string_fields(owned_struct).length > 0 && !struct_needs_destroy(owned_struct, status)) {
|
|
54494
|
+
const indices = func_param.is_moved ? node.move_owned_param_indices ??= [] : node.owned_value_param_indices ??= [];
|
|
54495
|
+
if (!indices.includes(i)) indices.push(i);
|
|
54496
|
+
}
|
|
54497
|
+
}
|
|
53321
54498
|
const has_mov_keyword = node.move_param_indices?.includes(i) ?? false;
|
|
53322
54499
|
if (func_param && func_param.type.name === "string" && !func_param.type.is_view && !func_param.type.is_array && param_type.name === "string" && param_type.is_view) add_error(status, `cannot pass a 'view string' to string parameter '${func_param.name}' — call .to_string() to materialize an owned copy`, param.start);
|
|
53323
54500
|
if (func_param.type.is_ref && !has_ref_keyword) add_error(status, `Missing 'ref' keyword for ref parameter '${func_param.name}'`, param.start);
|
|
@@ -53342,12 +54519,13 @@ function check_function_call(node, status, func, target_type, self_value, self_p
|
|
|
53342
54519
|
else if (!func_param.type.is_ref && (arg_is_stack_array_var || arg_is_literal) && elem_struct && !elem_struct.is_simple_type && !elem_struct.is_class && struct_has_string_fields(elem_struct, status)) add_error(status, `Cannot pass an array of owning value struct '${param_type.name}' (string fields) to 'Array<${param_type.name}>' parameter '${func_param.name}' — the materialised heap copy would share the element strings. Use 'List<${param_type.name}>' instead: its Buffer deep-copies owning elements soundly`, param.start);
|
|
53343
54520
|
}
|
|
53344
54521
|
const param_is_class = func_param.type.name && is_class_type$1(func_param.type.name, status);
|
|
53345
|
-
const
|
|
54522
|
+
const param_transfers = !!node.move_owned_param_indices?.includes(i);
|
|
54523
|
+
const param_is_owning_struct = func_param.is_moved && !func_param.type.is_nullable && !!func_param.type.name && (is_owning_struct_type_requiring_move(func_param.type, status) || param_transfers);
|
|
53346
54524
|
const arg_is_owned_value = param.node_type === "value" && borrow_depth_of(param, status) === void 0;
|
|
53347
54525
|
if (func_param.is_moved && !has_mov_keyword && (param_is_class || param_is_owning_struct) && arg_is_owned_value) add_error(status, `Missing 'move' keyword for move parameter '${func_param.name}'`, param.start);
|
|
53348
54526
|
else if (!func_param.is_moved && has_mov_keyword) add_error(status, `Unexpected 'move' keyword for non-move parameter '${func_param.name}'`, param.start);
|
|
53349
54527
|
if (has_mov_keyword && param_value && !node.swap_params?.has(i)) {
|
|
53350
|
-
if (func_param.type.name && (is_class_type$1(func_param.type.name, status) || func_param.is_moved &&
|
|
54528
|
+
if (func_param.type.name && (is_class_type$1(func_param.type.name, status) || func_param.is_moved && is_owning_struct_type(func_param.type, status))) {
|
|
53351
54529
|
if (!status.moved_variables) status.moved_variables = /* @__PURE__ */ new Set();
|
|
53352
54530
|
status.moved_variables.add(param_value);
|
|
53353
54531
|
}
|
|
@@ -53358,7 +54536,7 @@ function check_function_call(node, status, func, target_type, self_value, self_p
|
|
|
53358
54536
|
if (field_type.name && is_class_type$1(field_type.name, status)) {
|
|
53359
54537
|
const field_name = access.access.name;
|
|
53360
54538
|
add_error(status, `cannot move '${field_name}' out of struct — struct owns its class fields`, param.start);
|
|
53361
|
-
} else if (field_type.name && !field_type.is_nullable && is_owning_struct_type_requiring_move(field_type, status)) {
|
|
54539
|
+
} else if (field_type.name && !field_type.is_nullable && (is_owning_struct_type_requiring_move(field_type, status) || param_transfers && is_pass_by_value_owning_struct(field_type, status))) {
|
|
53362
54540
|
const field_name = access.access.name;
|
|
53363
54541
|
add_error(status, `cannot move '${field_name}' out of struct by value — it owns heap resources; use 'move ... swap <replacement>' to revalidate the field`, param.start);
|
|
53364
54542
|
}
|
|
@@ -53795,6 +54973,10 @@ function check_access_node(node, status) {
|
|
|
53795
54973
|
if (af.is_destructure) {
|
|
53796
54974
|
const i = af.destructure_index;
|
|
53797
54975
|
if (target_type.is_array) {
|
|
54976
|
+
if (af.destructure_move) {
|
|
54977
|
+
add_error(status, `move destructuring requires a tuple or struct (an array element has no field storage to revalidate)`, node.start);
|
|
54978
|
+
return false;
|
|
54979
|
+
}
|
|
53798
54980
|
if (af.is_destructure_rename) {
|
|
53799
54981
|
add_error(status, `Cannot rename elements when destructuring an array`, node.start);
|
|
53800
54982
|
return false;
|
|
@@ -54556,6 +55738,55 @@ function check_array_values_node(array, status) {
|
|
|
54556
55738
|
* On success, mutates `array` in-place into a FunctionCallNode that constructs
|
|
54557
55739
|
* the appropriate auto-generated tuple struct.
|
|
54558
55740
|
*/
|
|
55741
|
+
/**
|
|
55742
|
+
* Tuple-element ownership resolution. A bare owning-struct variable element
|
|
55743
|
+
* (`[t, c]` where t is a `List<string>`) byte-copies the struct — the
|
|
55744
|
+
* tuple's field would alias the local's buffer, and both cleanups free it
|
|
55745
|
+
* (double-free). Three outcomes per element:
|
|
55746
|
+
*
|
|
55747
|
+
* - explicit `move t` — transfer (registered like the move-assignment
|
|
55748
|
+
* path; the tuple's field cleanup owns the buffer now);
|
|
55749
|
+
* - plain `t` stamped by the literal last-use pass — INFERRED transfer
|
|
55750
|
+
* (t is provably never read again, so copy-then-drop and move are
|
|
55751
|
+
* observationally identical; the move is free);
|
|
55752
|
+
* - plain `t` still referenced later — rejected, exactly like the
|
|
55753
|
+
* declaration-path owning-struct copy rule: transfer with `move` or
|
|
55754
|
+
* deep-copy with `.copy()`.
|
|
55755
|
+
*
|
|
55756
|
+
* Strings and non-owning structs are ordinary by-value copies (views).
|
|
55757
|
+
*/
|
|
55758
|
+
function check_tuple_element_ownership(array, status) {
|
|
55759
|
+
let rejected = false;
|
|
55760
|
+
for (const value of array.values) {
|
|
55761
|
+
const vn = value;
|
|
55762
|
+
if (vn.node_type !== "value") continue;
|
|
55763
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(vn.value) || vn.value === "null") continue;
|
|
55764
|
+
const t = type_from_value_node(value, status);
|
|
55765
|
+
if (!t.name) continue;
|
|
55766
|
+
if (is_owning_struct_type(t, status)) {
|
|
55767
|
+
if (vn.is_moved || vn.literal_last_use_move) {
|
|
55768
|
+
vn.is_moved = true;
|
|
55769
|
+
if (!status.moved_variables) status.moved_variables = /* @__PURE__ */ new Set();
|
|
55770
|
+
status.moved_variables.add(vn.value);
|
|
55771
|
+
} else {
|
|
55772
|
+
add_error(status, `cannot copy '${t.name}' by value — it owns heap resources; use 'move ${vn.value}' or '${vn.value}.copy()'`, value.start);
|
|
55773
|
+
rejected = true;
|
|
55774
|
+
}
|
|
55775
|
+
continue;
|
|
55776
|
+
}
|
|
55777
|
+
if (t.name === "string" && !t.is_view && !t.is_array) {
|
|
55778
|
+
if (vn.is_moved || vn.literal_last_use_move) {
|
|
55779
|
+
vn.is_moved = true;
|
|
55780
|
+
if (!status.moved_variables) status.moved_variables = /* @__PURE__ */ new Set();
|
|
55781
|
+
status.moved_variables.add(vn.value);
|
|
55782
|
+
} else {
|
|
55783
|
+
add_error(status, `string local '${vn.value}' is read again after the literal — use 'move ${vn.value}' or '${vn.value}.to_string()'`, value.start);
|
|
55784
|
+
rejected = true;
|
|
55785
|
+
}
|
|
55786
|
+
}
|
|
55787
|
+
}
|
|
55788
|
+
return rejected;
|
|
55789
|
+
}
|
|
54559
55790
|
function check_as_tuple(array, tuple_element_types, status) {
|
|
54560
55791
|
if (array.values.length !== tuple_element_types.length) return check_as_array_or_inferred_tuple(array, status, tuple_element_types);
|
|
54561
55792
|
let result = true;
|
|
@@ -54574,6 +55805,7 @@ function check_as_tuple(array, tuple_element_types, status) {
|
|
|
54574
55805
|
value_types.push(vt);
|
|
54575
55806
|
check_type_and_value_match(tuple_element_types[i], vt, value_from_value_node(value), status, value.start, "tuple");
|
|
54576
55807
|
}
|
|
55808
|
+
if (check_tuple_element_ownership(array, status)) result = false;
|
|
54577
55809
|
const struct_name = tuple_struct_name(tuple_element_types);
|
|
54578
55810
|
get_or_create_tuple_struct(tuple_element_types, status);
|
|
54579
55811
|
const constructor = new FunctionCallNode(array.start, struct_name);
|
|
@@ -54656,12 +55888,19 @@ function check_as_array_or_inferred_tuple(array, status, forced_tuple_types) {
|
|
|
54656
55888
|
if (!has_outer_expected) {
|
|
54657
55889
|
const meaningful = value_types.filter((t) => t.name && t.name !== "null");
|
|
54658
55890
|
const first_meaningful = meaningful[0];
|
|
54659
|
-
|
|
55891
|
+
const same_generic_shape = (a, b) => {
|
|
55892
|
+
if (a.name !== b.name) return false;
|
|
55893
|
+
const aa = a.type_args ?? [];
|
|
55894
|
+
const bb = b.type_args ?? [];
|
|
55895
|
+
return aa.length === bb.length && aa.every((t, i) => t.name === bb[i]?.name) && !a.tuple_types?.length && !b.tuple_types?.length;
|
|
55896
|
+
};
|
|
55897
|
+
if (!(meaningful.length > 0 && meaningful.every((t) => same_generic_shape(t, first_meaningful))) && array.values.length > 0) {
|
|
54660
55898
|
const elem_types = value_types.map((t, _i) => {
|
|
54661
55899
|
if (!t.name || t.name === "null") return meaningful[0] || new Type("int");
|
|
54662
55900
|
return t;
|
|
54663
55901
|
});
|
|
54664
55902
|
for (let i = 0; i < value_types.length; i++) check_type_and_value_match(elem_types[i], value_types[i], value_from_value_node(array.values[i]), status, array.values[i].start, "tuple");
|
|
55903
|
+
if (check_tuple_element_ownership(array, status)) result = false;
|
|
54665
55904
|
const struct_name = tuple_struct_name(elem_types);
|
|
54666
55905
|
get_or_create_tuple_struct(elem_types, status);
|
|
54667
55906
|
const constructor = new FunctionCallNode(array.start, struct_name);
|
|
@@ -54731,14 +55970,17 @@ function check_assignment_node(assign, status) {
|
|
|
54731
55970
|
return_type: fd?.func_return_type
|
|
54732
55971
|
};
|
|
54733
55972
|
}
|
|
54734
|
-
} else if (lhs_value?.func_params
|
|
55973
|
+
} else if (lhs_value?.func_params !== void 0) lambda_signature = {
|
|
54735
55974
|
params: lhs_value.func_params,
|
|
54736
55975
|
return_type: lhs_value.func_return_type
|
|
54737
55976
|
};
|
|
54738
|
-
if (assign.right_value.node_type === "func" &&
|
|
55977
|
+
if (assign.right_value.node_type === "func" && Array.isArray(lambda_signature.params)) {
|
|
55978
|
+
if (!lhs_value && assign.left_value.node_type === "value") {
|
|
55979
|
+
add_error(status, `cannot assign a lambda to '${lhs_value_name}' — the binding was initialized with a capture-free lambda and has no reassignable slot; declare it with '= null' (or from a named function) to reassign`, assign.right_value.start);
|
|
55980
|
+
return false;
|
|
55981
|
+
}
|
|
54739
55982
|
const rhs_func = assign.right_value;
|
|
54740
|
-
if (assign.left_value.node_type === "value") rhs_func
|
|
54741
|
-
else if (!rhs_func.name) synthesize_lambda_name(rhs_func, status);
|
|
55983
|
+
if (!rhs_func.name || assign.left_value.node_type === "value") synthesize_lambda_name(rhs_func, status);
|
|
54742
55984
|
if (rhs_func.params.length === lambda_signature.params.length) {
|
|
54743
55985
|
for (let i = 0; i < rhs_func.params.length; i++) if (!rhs_func.params[i].type.name && lambda_signature.params[i].type.name) rhs_func.params[i].type = lambda_signature.params[i].type;
|
|
54744
55986
|
}
|
|
@@ -55155,7 +56397,7 @@ function list_element_type(t, status) {
|
|
|
55155
56397
|
return struct.source_type_args?.[0];
|
|
55156
56398
|
}
|
|
55157
56399
|
function check_for_loop_node(for_loop, status) {
|
|
55158
|
-
if (for_loop.list && !(for_loop.list instanceof RangeNode) && for_loop.list.node_type === "value") {
|
|
56400
|
+
if (for_loop.list && !(for_loop.list instanceof RangeNode) && (for_loop.list.node_type === "value" || for_loop.list.node_type === "access")) {
|
|
55159
56401
|
const list_type = type_from_value_node(for_loop.list, status);
|
|
55160
56402
|
const enumerable = list_type.name ? has_trait(list_type.name, "Enumerable", status) : false;
|
|
55161
56403
|
const has_at = list_type.name !== void 0 && !!status.structs.find((s) => s.name === "Array_" + list_type.name && s.functions.some((f) => f.name === "at"));
|
|
@@ -55181,6 +56423,16 @@ function check_for_loop_node(for_loop, status) {
|
|
|
55181
56423
|
check_node(for_loop.list, for_status);
|
|
55182
56424
|
const list_type = type_from_value_node(for_loop.list, for_status);
|
|
55183
56425
|
const is_enumerable = list_type.name ? has_trait(list_type.name, "Enumerable", for_status) : false;
|
|
56426
|
+
const chained_list_elem = list_element_type(list_type, for_status);
|
|
56427
|
+
if (chained_list_elem && !is_enumerable && chain_is_call_free(for_loop.list)) {
|
|
56428
|
+
if (for_loop.item_is_ref) {
|
|
56429
|
+
add_error(for_status, `'ref' iteration is not supported for List<T> — index explicitly (for i of 0..xs.length) and use .set to write back`, for_loop.item.start);
|
|
56430
|
+
return;
|
|
56431
|
+
}
|
|
56432
|
+
desugar_array_for_loop(for_loop, list_type, chained_list_elem);
|
|
56433
|
+
check_for_loop_node(for_loop, for_status);
|
|
56434
|
+
return;
|
|
56435
|
+
}
|
|
55184
56436
|
if (!list_type.is_array && !is_enumerable && list_type.name) add_error(for_status, `For loop list must be an array, List, or Enumerable, not ${list_type.name}`, for_loop.list.start);
|
|
55185
56437
|
if (for_loop.item_is_ref && list_type.is_array && for_loop.list.node_type === "value") {
|
|
55186
56438
|
const list_name = for_loop.list.value;
|
|
@@ -55233,6 +56485,23 @@ function check_for_loop_node(for_loop, status) {
|
|
|
55233
56485
|
* break/continue so mutations persist on all exit paths. (The write-back is
|
|
55234
56486
|
* skipped on `return`, matching Rust's copy semantics.)
|
|
55235
56487
|
*/
|
|
56488
|
+
/**
|
|
56489
|
+
* Whether an iterable expression contains no call segments — the guard for
|
|
56490
|
+
* the post-check List desugar (`for x of holder.children`). A chain through
|
|
56491
|
+
* a CALL result (`arena.get(handle).children`) cannot discharge the
|
|
56492
|
+
* desugared `.at` bounds and miscompiles on aarch64 (FOLLOWUP.md), so it
|
|
56493
|
+
* keeps the clear must-be-array-or-Enumerable error.
|
|
56494
|
+
*/
|
|
56495
|
+
function chain_is_call_free(node) {
|
|
56496
|
+
if (!node || typeof node !== "object") return true;
|
|
56497
|
+
if (node.node_type === "func_call") return false;
|
|
56498
|
+
if (node.node_type === "access") {
|
|
56499
|
+
const access = node;
|
|
56500
|
+
if (access.access.node_type === "access_func") return false;
|
|
56501
|
+
return chain_is_call_free(access.target);
|
|
56502
|
+
}
|
|
56503
|
+
return true;
|
|
56504
|
+
}
|
|
55236
56505
|
function desugar_array_for_loop(for_loop, array_type, elem_type) {
|
|
55237
56506
|
const list = for_loop.list;
|
|
55238
56507
|
const start = list.start;
|
|
@@ -55970,6 +57239,8 @@ function check_spawn_node(node, status) {
|
|
|
55970
57239
|
add_error(status, "Spawned call did not resolve", node.start);
|
|
55971
57240
|
return false;
|
|
55972
57241
|
}
|
|
57242
|
+
const spawned = node.call.resolved_function;
|
|
57243
|
+
if (spawned) spawned.address_escaped = true;
|
|
55973
57244
|
for (const param of node.call.params) {
|
|
55974
57245
|
let arg_type = type_from_value_node(param, status);
|
|
55975
57246
|
const stamped = param.type;
|
|
@@ -56476,6 +57747,7 @@ function check(root, options) {
|
|
|
56476
57747
|
allow_internal: options?.allow_internal,
|
|
56477
57748
|
library_boundary: options?.library_boundary
|
|
56478
57749
|
};
|
|
57750
|
+
stamp_literal_element_moves(root);
|
|
56479
57751
|
check_node(root, status);
|
|
56480
57752
|
unwrap_unsafe_blocks(root);
|
|
56481
57753
|
if (!status.errors.length) emit_warnings(root, status);
|
|
@@ -56787,13 +58059,14 @@ function parse_type(status) {
|
|
|
56787
58059
|
type.type_args = void 0;
|
|
56788
58060
|
}
|
|
56789
58061
|
if (type.name === "Func" && type.type_args?.length) {
|
|
56790
|
-
if (type.
|
|
56791
|
-
add_error(status, `Func<...> cannot be combined with '
|
|
58062
|
+
if (type.is_array || type.is_pointer || type.is_view || type.is_ref) {
|
|
58063
|
+
add_error(status, `Func<...> cannot be combined with '[]'/'ptr'/'view'/'ref'`, start);
|
|
56792
58064
|
return type;
|
|
56793
58065
|
}
|
|
56794
58066
|
const args = type.type_args;
|
|
56795
58067
|
const func_type = new Type("func");
|
|
56796
58068
|
func_type.start = type.start;
|
|
58069
|
+
if (type.is_nullable) func_type.is_nullable = true;
|
|
56797
58070
|
func_type.func_params = [];
|
|
56798
58071
|
for (let i = 0; i < args.length - 1; i++) {
|
|
56799
58072
|
if (args[i].name === "void") {
|
|
@@ -56906,10 +58179,14 @@ function default_visibility(status) {
|
|
|
56906
58179
|
* - a func RETURN carries its signature on the Type
|
|
56907
58180
|
* (`type.func_params` / `type.func_return_type`).
|
|
56908
58181
|
*/
|
|
56909
|
-
/** Whether the current token starts a nested func type: `func`
|
|
56910
|
-
* followed by `(` (the bare word `func` alone
|
|
58182
|
+
/** Whether the current token starts a nested func type: `func` (optionally
|
|
58183
|
+
* nullable, `func?`) immediately followed by `(` (the bare word `func` alone
|
|
58184
|
+
* is the plain marker type). */
|
|
56911
58185
|
function at_func_type(status) {
|
|
56912
|
-
|
|
58186
|
+
if (peek_current(status) !== "func") return false;
|
|
58187
|
+
const next = status.tokens[status.i + 1]?.value;
|
|
58188
|
+
if (next === "(") return true;
|
|
58189
|
+
return next === "?" && status.tokens[status.i + 2]?.value === "(";
|
|
56913
58190
|
}
|
|
56914
58191
|
/**
|
|
56915
58192
|
* Reject `void` where it has no meaning. Only the last type argument of
|
|
@@ -56930,6 +58207,7 @@ function reject_void_type(type, status, at) {
|
|
|
56930
58207
|
function adopt_func_param_type(param, type) {
|
|
56931
58208
|
if (type.name !== "func") return false;
|
|
56932
58209
|
param.type = new Type("func");
|
|
58210
|
+
param.type.is_nullable = type.is_nullable;
|
|
56933
58211
|
param.func_params = type.func_params;
|
|
56934
58212
|
param.func_return_type = type.func_return_type;
|
|
56935
58213
|
return true;
|
|
@@ -56952,11 +58230,13 @@ function parse_func_type_signature(param, status) {
|
|
|
56952
58230
|
}
|
|
56953
58231
|
/**
|
|
56954
58232
|
* Parse a full nested func type — the `func` word is consumed by the caller
|
|
56955
|
-
* (at_func_type confirmed the `(` follows) — and return
|
|
56956
|
-
* func_params/func_return_type carry the signature.
|
|
58233
|
+
* (at_func_type confirmed the `(` follows, possibly after a `?`) — and return
|
|
58234
|
+
* it as a Type whose func_params/func_return_type carry the signature. A
|
|
58235
|
+
* `func?` spelling marks the type nullable (it may hold `null`).
|
|
56957
58236
|
*/
|
|
56958
58237
|
function parse_func_type(status) {
|
|
56959
58238
|
const type = new Type("func");
|
|
58239
|
+
if (accept("?", status)) type.is_nullable = true;
|
|
56960
58240
|
const param = new ParameterNode(get_index(status), "");
|
|
56961
58241
|
parse_func_type_signature(param, status);
|
|
56962
58242
|
type.func_params = param.func_params;
|
|
@@ -56996,6 +58276,7 @@ function parse_signature_params(params, status) {
|
|
|
56996
58276
|
if (at_func_type(status)) {
|
|
56997
58277
|
consume(status);
|
|
56998
58278
|
param.type = new Type("func");
|
|
58279
|
+
if (accept("?", status)) param.type.is_nullable = true;
|
|
56999
58280
|
parse_func_type_signature(param, status);
|
|
57000
58281
|
} else {
|
|
57001
58282
|
param.type = parse_type(status);
|
|
@@ -57036,7 +58317,11 @@ function parse_declaration(visibility, declaration, status, is_readonly = false)
|
|
|
57036
58317
|
if (is_func_alias) {
|
|
57037
58318
|
decl.func_params = decl.type.func_params ?? [];
|
|
57038
58319
|
decl.func_return_type = decl.type.func_return_type;
|
|
57039
|
-
decl.type
|
|
58320
|
+
if (decl.type.is_nullable) {
|
|
58321
|
+
const nullable_marker = new Type("func");
|
|
58322
|
+
nullable_marker.is_nullable = true;
|
|
58323
|
+
decl.type = nullable_marker;
|
|
58324
|
+
} else decl.type = new Type("");
|
|
57040
58325
|
}
|
|
57041
58326
|
if (peek_current(status) === "=" || status.i >= status.tokens.length) {
|
|
57042
58327
|
const modifier = status.tokens[saved_i]?.value;
|
|
@@ -57098,6 +58383,7 @@ function parse_declaration(visibility, declaration, status, is_readonly = false)
|
|
|
57098
58383
|
}
|
|
57099
58384
|
function parse_function_type_declaration(decl, status) {
|
|
57100
58385
|
accept("func", status);
|
|
58386
|
+
const func_nullable = accept("?", status);
|
|
57101
58387
|
if (expect("(", status)) {
|
|
57102
58388
|
const params = [];
|
|
57103
58389
|
let return_type;
|
|
@@ -57110,6 +58396,10 @@ function parse_function_type_declaration(decl, status) {
|
|
|
57110
58396
|
decl.name = consume_name(status);
|
|
57111
58397
|
decl.func_params = params.filter((p) => !p.type.is_return_type);
|
|
57112
58398
|
decl.func_return_type = return_type;
|
|
58399
|
+
if (func_nullable) {
|
|
58400
|
+
decl.type = new Type("func");
|
|
58401
|
+
decl.type.is_nullable = true;
|
|
58402
|
+
}
|
|
57113
58403
|
const has_equals = accept("=", status);
|
|
57114
58404
|
if (peek_current(status) === "{") {
|
|
57115
58405
|
const func = new FunctionNode(get_index(status), default_visibility(status), decl.name, new Type(""));
|
|
@@ -57209,6 +58499,7 @@ function parse_anon_function_parameter(func, status) {
|
|
|
57209
58499
|
if (at_func_type(status)) {
|
|
57210
58500
|
consume(status);
|
|
57211
58501
|
param.type = new Type("func");
|
|
58502
|
+
if (accept("?", status)) param.type.is_nullable = true;
|
|
57212
58503
|
parse_func_type_signature(param, status);
|
|
57213
58504
|
param.name_start = get_index(status);
|
|
57214
58505
|
param.name = consume_name(status);
|
|
@@ -57264,6 +58555,10 @@ function looks_like_destructuring(status, start_idx) {
|
|
|
57264
58555
|
* - bare name: `a` — positional (tuples/arrays) or same-named field (structs)
|
|
57265
58556
|
* - rename: `field = name` — struct/class field accessed as `field`,
|
|
57266
58557
|
* bound to `name`
|
|
58558
|
+
*
|
|
58559
|
+
* Either form may be prefixed with `move` (`[move a]`, `[field = move n]`):
|
|
58560
|
+
* the binding TAKES OWNERSHIP of the field (see destructure_move) instead of
|
|
58561
|
+
* binding a non-owning view.
|
|
57267
58562
|
*/
|
|
57268
58563
|
function parse_destructuring(visibility, declaration, start, status) {
|
|
57269
58564
|
expect("[", status);
|
|
@@ -57274,8 +58569,17 @@ function parse_destructuring(visibility, declaration, start, status) {
|
|
|
57274
58569
|
let field = first;
|
|
57275
58570
|
let name = first;
|
|
57276
58571
|
let rename = false;
|
|
57277
|
-
|
|
58572
|
+
let move = false;
|
|
58573
|
+
if (first === "move" && peek_current(status) !== "=" && peek_current(status) !== "]") {
|
|
58574
|
+
move = true;
|
|
58575
|
+
name = consume(status);
|
|
58576
|
+
field = name;
|
|
58577
|
+
} else if (peek_current(status) === "=") {
|
|
57278
58578
|
accept("=", status);
|
|
58579
|
+
if (peek_current(status) === "move") {
|
|
58580
|
+
consume(status);
|
|
58581
|
+
move = true;
|
|
58582
|
+
}
|
|
57279
58583
|
name = consume_name(status);
|
|
57280
58584
|
field = first;
|
|
57281
58585
|
rename = true;
|
|
@@ -57283,7 +58587,8 @@ function parse_destructuring(visibility, declaration, start, status) {
|
|
|
57283
58587
|
bindings.push({
|
|
57284
58588
|
name,
|
|
57285
58589
|
field,
|
|
57286
|
-
rename
|
|
58590
|
+
rename,
|
|
58591
|
+
move
|
|
57287
58592
|
});
|
|
57288
58593
|
index++;
|
|
57289
58594
|
if (!accept(",", status)) break;
|
|
@@ -57313,7 +58618,7 @@ function parse_destructuring(visibility, declaration, start, status) {
|
|
|
57313
58618
|
const temp_counter = status.__tuple_destructure_counter || 0;
|
|
57314
58619
|
status.__tuple_destructure_counter = temp_counter + 1;
|
|
57315
58620
|
const temp_name = `_tuple_dst_${temp_counter}`;
|
|
57316
|
-
push_to_parent(new DeclarationNode(start, "private", "const", temp_name, new Type(""), value));
|
|
58621
|
+
push_to_parent(new DeclarationNode(start, "private", bindings.some((b) => b.move) ? "var" : "const", temp_name, new Type(""), value));
|
|
57317
58622
|
base_node = new ValueNode(start, temp_name);
|
|
57318
58623
|
}
|
|
57319
58624
|
for (let i = 0; i < bindings.length; i++) {
|
|
@@ -57323,7 +58628,9 @@ function parse_destructuring(visibility, declaration, start, status) {
|
|
|
57323
58628
|
access_field.is_destructure = true;
|
|
57324
58629
|
access_field.destructure_index = i;
|
|
57325
58630
|
access_field.is_destructure_rename = binding.rename;
|
|
58631
|
+
access_field.destructure_move = binding.move;
|
|
57326
58632
|
const access = new AccessNode(start, base_node, access_field);
|
|
58633
|
+
access.is_moved = binding.move;
|
|
57327
58634
|
push_to_parent(new DeclarationNode(start, visibility, declaration, binding.name, new Type(""), access));
|
|
57328
58635
|
}
|
|
57329
58636
|
}
|
|
@@ -58142,6 +59449,7 @@ function parse_function_parameter(parent, func, status) {
|
|
|
58142
59449
|
if (at_func_type(status)) {
|
|
58143
59450
|
consume(status);
|
|
58144
59451
|
param.type = new Type("func");
|
|
59452
|
+
if (accept("?", status)) param.type.is_nullable = true;
|
|
58145
59453
|
parse_func_type_signature(param, status);
|
|
58146
59454
|
param.name_start = get_index(status);
|
|
58147
59455
|
param.name = consume_name(status);
|
|
@@ -59500,7 +60808,8 @@ nomen test
|
|
|
59500
60808
|
function find_bundled(filename) {
|
|
59501
60809
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
59502
60810
|
const cli_root = path.dirname(here);
|
|
59503
|
-
const
|
|
60811
|
+
const repo_root = path.join(cli_root, "..");
|
|
60812
|
+
const candidates = !cli_root.includes("node_modules") ? [path.join(repo_root, filename), path.join(cli_root, filename)] : [path.join(cli_root, filename), path.join(repo_root, filename)];
|
|
59504
60813
|
for (const c of candidates) if (fs.existsSync(c)) return path.resolve(c);
|
|
59505
60814
|
}
|
|
59506
60815
|
/**
|
|
@@ -59722,6 +61031,10 @@ function parse_records(stdout) {
|
|
|
59722
61031
|
other
|
|
59723
61032
|
};
|
|
59724
61033
|
}
|
|
61034
|
+
/** Pull the exit-time audit "LEAK: N allocation(s)" lines out of stdout. */
|
|
61035
|
+
function extract_leaks(stdout) {
|
|
61036
|
+
return stdout.split("\n").map((l) => l.trim()).filter((l) => l.startsWith("LEAK:"));
|
|
61037
|
+
}
|
|
59725
61038
|
/**
|
|
59726
61039
|
* Compile one `*.test.nm` file (with the generated harness) and run it,
|
|
59727
61040
|
* returning its parsed records and any crash info.
|
|
@@ -59737,6 +61050,7 @@ function run_test_file(entry_path, lib_path, arch, audit = false, audit_runtime)
|
|
|
59737
61050
|
fails: [],
|
|
59738
61051
|
benches: [],
|
|
59739
61052
|
other: [],
|
|
61053
|
+
leaks: [],
|
|
59740
61054
|
ms: 0
|
|
59741
61055
|
};
|
|
59742
61056
|
const resolved = path.resolve(entry_path);
|
|
@@ -59834,9 +61148,11 @@ function run_test_file(entry_path, lib_path, arch, audit = false, audit_runtime)
|
|
|
59834
61148
|
result.fails = records.fails;
|
|
59835
61149
|
result.benches = records.benches;
|
|
59836
61150
|
result.other = records.other;
|
|
61151
|
+
result.leaks = audit ? extract_leaks(runStdout) : [];
|
|
61152
|
+
if (result.leaks.length) result.other = result.other.filter((l) => !l.trim().startsWith("LEAK:"));
|
|
59837
61153
|
result.crashed = crashed;
|
|
59838
61154
|
if (crashed) result.phase = "run";
|
|
59839
|
-
result.ok = !(result.fails.length > 0 || result.tests.some((t) => t.failed > 0)) && crashed === void 0;
|
|
61155
|
+
result.ok = !(result.fails.length > 0 || result.tests.some((t) => t.failed > 0)) && crashed === void 0 && result.leaks.length === 0;
|
|
59840
61156
|
result.ms = performance.now() - start;
|
|
59841
61157
|
return result;
|
|
59842
61158
|
}
|
|
@@ -59873,6 +61189,7 @@ function report_file(result) {
|
|
|
59873
61189
|
console.log(` ${mark} ${rel} ${C.dim(`(${totalTests} tests)`)} ${C.dim(format_duration(result.ms))}`);
|
|
59874
61190
|
if (result.crashed) console.log(C.red(` ${result.crashed}`));
|
|
59875
61191
|
for (const f of result.fails) console.log(` ${C.red("✗")} ${f.test} ${C.dim(">")} ${f.message}`);
|
|
61192
|
+
for (const leak of result.leaks) console.log(` ${C.yellow("⚠")} ${C.yellow(leak)}`);
|
|
59876
61193
|
for (const b of result.benches) console.log(` ${C.cyan("⏱")} ${b.label} ${C.dim(`(n=${b.n})`)} ${C.dim("min")} ${fmt_ns(b.min)} ${C.dim("median")} ${fmt_ns(b.median)} ${C.dim("mean")} ${fmt_ns(Math.round(b.mean))} ${C.dim("max")} ${fmt_ns(b.max)} ${C.dim("±")} ${fmt_ns(Math.round(b.stddev))}`);
|
|
59877
61194
|
if (result.other.length) {
|
|
59878
61195
|
console.log(C.dim(" --- test stdout ---"));
|
|
@@ -59914,10 +61231,12 @@ function runTests(root, options = {}) {
|
|
|
59914
61231
|
const totalTests = results.reduce((a, r) => a + r.tests.length, 0);
|
|
59915
61232
|
const totalFailed = results.reduce((a, r) => a + r.tests.reduce((x, t) => x + t.failed, 0), 0);
|
|
59916
61233
|
const totalPassed = totalTests - totalFailed;
|
|
61234
|
+
const totalLeaks = results.reduce((a, r) => a + r.leaks.length, 0);
|
|
59917
61235
|
const anyFailed = results.some((r) => !r.ok);
|
|
59918
61236
|
console.log("");
|
|
59919
61237
|
console.log(` ${C.dim("Files ")} ${C.green(`${totalFilesPassed} passed`)}` + (totalFilesFailed ? ` ${C.dim("|")} ${C.red(`${totalFilesFailed} failed`)}` : "") + C.dim(` (${totalFiles})`));
|
|
59920
61238
|
console.log(` ${C.dim("Tests ")} ${C.green(`${totalPassed} passed`)}` + (totalFailed ? ` ${C.dim("|")} ${C.red(`${totalFailed} failed`)}` : "") + C.dim(` (${totalTests})`));
|
|
61239
|
+
if (totalLeaks) console.log(` ${C.dim("Leaks ")} ${C.yellow(`${totalLeaks} reported`)}` + C.dim(" (--audit; leaked files count as failures)"));
|
|
59921
61240
|
console.log(` ${C.dim(" Time ")} ${format_duration(elapsed)}`);
|
|
59922
61241
|
console.log("");
|
|
59923
61242
|
return !anyFailed;
|
|
@@ -60137,7 +61456,7 @@ function processFolder(folder, config, mode, program_args) {
|
|
|
60137
61456
|
const dir = fs.opendirSync(folder);
|
|
60138
61457
|
let dirent;
|
|
60139
61458
|
while ((dirent = dir.readSync()) !== null) if (shouldProcessFile(dirent.name)) {
|
|
60140
|
-
processFile(path.join(folder, dirent.name), config, mode, program_args
|
|
61459
|
+
processFile(path.join(folder, dirent.name), config, mode, program_args);
|
|
60141
61460
|
fs.watch;
|
|
60142
61461
|
}
|
|
60143
61462
|
dir.closeSync();
|
|
@@ -60145,45 +61464,15 @@ function processFolder(folder, config, mode, program_args) {
|
|
|
60145
61464
|
function shouldProcessFile(filename) {
|
|
60146
61465
|
return path.extname(filename) === SUPPORTED_EXTENSION;
|
|
60147
61466
|
}
|
|
60148
|
-
|
|
60149
|
-
* The project's entry file for `file_path`: walk up to the nearest
|
|
60150
|
-
* `package.jsonc` and resolve its `entry` field, so any file inside a project
|
|
60151
|
-
* is checked with the entry's import context (`import types` resolves
|
|
60152
|
-
* against the project's `src/`, not the file's own folder). Returns
|
|
60153
|
-
* undefined when the file is standalone, IS the entry, sits in an
|
|
60154
|
-
* entry-less project (e.g. a library checkout), or the config is malformed.
|
|
60155
|
-
*/
|
|
60156
|
-
function project_entry_for(file_path) {
|
|
60157
|
-
let dir = path.dirname(file_path);
|
|
60158
|
-
for (let i = 0; i < 20; i++) {
|
|
60159
|
-
const config_path = path.join(dir, "package.jsonc");
|
|
60160
|
-
if (fs.existsSync(config_path)) {
|
|
60161
|
-
try {
|
|
60162
|
-
const json = fs.readFileSync(config_path, "utf8").replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
60163
|
-
const parsed = JSON.parse(json);
|
|
60164
|
-
if (parsed.entry) {
|
|
60165
|
-
const entry = path.resolve(dir, parsed.entry);
|
|
60166
|
-
if (entry !== file_path && fs.existsSync(entry)) return entry;
|
|
60167
|
-
}
|
|
60168
|
-
} catch {}
|
|
60169
|
-
return;
|
|
60170
|
-
}
|
|
60171
|
-
const parent = path.dirname(dir);
|
|
60172
|
-
if (parent === dir) return void 0;
|
|
60173
|
-
dir = parent;
|
|
60174
|
-
}
|
|
60175
|
-
}
|
|
60176
|
-
function processFile(filename, config, mode, program_args, anchor_to_project = true) {
|
|
61467
|
+
function processFile(filename, config, mode, program_args) {
|
|
60177
61468
|
console.log("Processing", filename);
|
|
60178
61469
|
const arch = config.arch || "aarch64";
|
|
60179
61470
|
const platform = config.platform || default_platform();
|
|
60180
61471
|
const resolved = path.resolve(filename);
|
|
60181
61472
|
if (!config.lib) config.lib = resolve_lib(resolved);
|
|
60182
|
-
const anchor = anchor_to_project && !resolved.endsWith(".test.nm") ? project_entry_for(resolved) ?? resolved : resolved;
|
|
60183
|
-
if (anchor !== resolved) console.log("Anchored to project entry", anchor);
|
|
60184
61473
|
let startTime = performance.now();
|
|
60185
|
-
const input = join$1(
|
|
60186
|
-
const parsed = parse(input, config.lib ? get_library(config.lib) : void 0,
|
|
61474
|
+
const input = join$1(resolved, config.lib);
|
|
61475
|
+
const parsed = parse(input, config.lib ? get_library(config.lib) : void 0, resolved);
|
|
60187
61476
|
let errors = parsed.errors;
|
|
60188
61477
|
if (errors.length) {
|
|
60189
61478
|
console.log(render_errors(input, errors));
|