nomen-lang 0.6.3 → 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/dist/index.mjs +955 -267
- package/package.json +1 -1
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;
|
|
@@ -6748,6 +6754,14 @@ var ValueNode = class extends BaseNode {
|
|
|
6748
6754
|
* name. Mirrors FunctionCallNode.resolved_function.
|
|
6749
6755
|
*/
|
|
6750
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;
|
|
6751
6765
|
constructor(start, value, type) {
|
|
6752
6766
|
super("value", start);
|
|
6753
6767
|
this.value = value;
|
|
@@ -7353,6 +7367,13 @@ var DeclarationNode = class extends BaseNode {
|
|
|
7353
7367
|
* struct layout. Set at check time by check_function_call; consumed by the
|
|
7354
7368
|
* build backends' declaration emitters. */
|
|
7355
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;
|
|
7356
7377
|
/** True for a hoisted call-argument temp (`_param_N`) that is a heap COPY
|
|
7357
7378
|
* of a stack-array local, bound to a heap `Array<T>` param (non-`ref`).
|
|
7358
7379
|
* `value` is the source ValueNode; the temp is materialised as a heap
|
|
@@ -8014,6 +8035,38 @@ function hoist_field_overrides(value, build, status, declaration_terminator = ""
|
|
|
8014
8035
|
carrier._fov_hoisted = true;
|
|
8015
8036
|
}
|
|
8016
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
|
|
8017
8070
|
//#region ../src/build_common/string_return_analysis.ts
|
|
8018
8071
|
const EMPTY_SET = /* @__PURE__ */ new Set();
|
|
8019
8072
|
/**
|
|
@@ -8357,6 +8410,69 @@ function is_owned_string_branch_value(node, table) {
|
|
|
8357
8410
|
return false;
|
|
8358
8411
|
}
|
|
8359
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
|
|
8360
8476
|
//#region ../src/nodes/child_nodes.ts
|
|
8361
8477
|
/** Whether `value` is an AST node (carries a string `node_type`). */
|
|
8362
8478
|
function is_ast_node(value) {
|
|
@@ -8473,7 +8589,21 @@ var Walk = class {
|
|
|
8473
8589
|
reads = [];
|
|
8474
8590
|
declares = [];
|
|
8475
8591
|
assignments = [];
|
|
8592
|
+
literals = [];
|
|
8476
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
|
+
}
|
|
8477
8607
|
interval(body) {
|
|
8478
8608
|
const enter = ++this.stamp;
|
|
8479
8609
|
body();
|
|
@@ -8610,6 +8740,80 @@ function classify_declare(decl, walk, param_names, func_label) {
|
|
|
8610
8740
|
desc: `var ${node.name} = ${source}`
|
|
8611
8741
|
};
|
|
8612
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
|
+
}
|
|
8613
8817
|
function walk_stmt(walk, node) {
|
|
8614
8818
|
switch (node.node_type) {
|
|
8615
8819
|
case "declare": {
|
|
@@ -8746,7 +8950,10 @@ function walk_expr$1(walk, node) {
|
|
|
8746
8950
|
}
|
|
8747
8951
|
case "array": {
|
|
8748
8952
|
const arr = node;
|
|
8749
|
-
|
|
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
|
+
});
|
|
8750
8957
|
return;
|
|
8751
8958
|
}
|
|
8752
8959
|
default: for (const child of child_nodes(node)) walk_expr$1(walk, child);
|
|
@@ -11020,38 +11227,6 @@ function struct_needs_auto_destroy(struct, table) {
|
|
|
11020
11227
|
return false;
|
|
11021
11228
|
}
|
|
11022
11229
|
//#endregion
|
|
11023
|
-
//#region ../src/build_common/has_string_fields.ts
|
|
11024
|
-
/**
|
|
11025
|
-
* Whether any field (recursively, through nested owning value structs)
|
|
11026
|
-
* is an owned `string`. Shared by BOTH backends' owning-Buffer element
|
|
11027
|
-
* specialization (the slot deep-copies strdup'd strings, so a
|
|
11028
|
-
* string-field element is owning even when `struct_needs_destroy`
|
|
11029
|
-
* ignores string-only locals) — the C comment in
|
|
11030
|
-
* owning_buffer_specialize records the divergence this once caused.
|
|
11031
|
-
* `ref` and `view T` fields are skipped: a ref aliases the caller's
|
|
11032
|
-
* storage and a view's byte copy owns nothing.
|
|
11033
|
-
*/
|
|
11034
|
-
function has_string_fields(node, status) {
|
|
11035
|
-
for (const field of node.fields) {
|
|
11036
|
-
if (field.type.is_ref) continue;
|
|
11037
|
-
if (field.type.is_view) continue;
|
|
11038
|
-
if (field.type.name === "string" && !field.type.is_array) return true;
|
|
11039
|
-
const field_struct = status.structs.find((s) => s.name === field.type.name && !s.is_simple_type && !s.is_generic);
|
|
11040
|
-
if (field_struct && !field_struct.is_class && has_string_fields(field_struct, status)) return true;
|
|
11041
|
-
}
|
|
11042
|
-
return false;
|
|
11043
|
-
}
|
|
11044
|
-
/**
|
|
11045
|
-
* The DIRECT `string` fields of a value struct (nested struct fields are not
|
|
11046
|
-
* descended into — nested string fields are outside the per-field ownership
|
|
11047
|
-
* tracking everywhere else too). Used by the return-boundary normalization:
|
|
11048
|
-
* the returned struct's unrecorded fields are strdup'd so the value the
|
|
11049
|
-
* caller receives is uniformly heap-owned.
|
|
11050
|
-
*/
|
|
11051
|
-
function direct_string_fields(node) {
|
|
11052
|
-
return node.fields.filter((f) => !f.type.is_ref && !f.type.is_view && !f.type.is_array && f.type.name === "string");
|
|
11053
|
-
}
|
|
11054
|
-
//#endregion
|
|
11055
11230
|
//#region ../src/build_common/scan_force_heap_strings.ts
|
|
11056
11231
|
function scan_force_heap_strings(statements, structs) {
|
|
11057
11232
|
const result = /* @__PURE__ */ new Set();
|
|
@@ -13738,12 +13913,27 @@ function clear_heap_string_fields(status, var_name) {
|
|
|
13738
13913
|
* fields are unconditionally heap and freed by the destroy path. Called from
|
|
13739
13914
|
* emit_destroy_for_decl and directly from cleanup loops that skip moved
|
|
13740
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.
|
|
13741
13922
|
*/
|
|
13742
|
-
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) {
|
|
13743
13924
|
if (!status.heap_string_fields?.size) return;
|
|
13744
13925
|
const prefix = `${decl_name}.`;
|
|
13745
13926
|
const fields = Array.from(status.heap_string_fields).filter((k) => k.startsWith(prefix)).map((k) => k.slice(prefix.length));
|
|
13746
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
|
+
}
|
|
13747
13937
|
for (const field of fields) {
|
|
13748
13938
|
const offset = get_field_offset(decl_type_name, field, status);
|
|
13749
13939
|
emit_var_address(status, "x0", decl_name);
|
|
@@ -13751,6 +13941,7 @@ function release_heap_string_fields(status, decl_name, decl_type_name) {
|
|
|
13751
13941
|
emit_free(status);
|
|
13752
13942
|
status.heap_string_fields.delete(`${decl_name}.${field}`);
|
|
13753
13943
|
}
|
|
13944
|
+
if (skip_label) emit_asm(status, `${skip_label}:\n`);
|
|
13754
13945
|
}
|
|
13755
13946
|
/**
|
|
13756
13947
|
* Swap in a fresh scoped_declarations frame for a nested scope (if/while/
|
|
@@ -14109,7 +14300,7 @@ function is_struct_type$4(type_name, status) {
|
|
|
14109
14300
|
}
|
|
14110
14301
|
function emit_destroy_for_decl(status, decl_name, decl_type_name, addr_offset, type_args, is_nullable) {
|
|
14111
14302
|
const moved = status.moved ?? /* @__PURE__ */ new Set();
|
|
14112
|
-
release_heap_string_fields(status, decl_name, decl_type_name);
|
|
14303
|
+
release_heap_string_fields(status, decl_name, decl_type_name, is_nullable);
|
|
14113
14304
|
if (moved.has(decl_name)) return;
|
|
14114
14305
|
if (decl_type_name === "func" && status.stack_offsets?.has(decl_name)) {
|
|
14115
14306
|
const skip = `.Lclosure_free_done_${status.label_counter = (status.label_counter ?? 0) + 1}`;
|
|
@@ -14162,11 +14353,19 @@ function emit_destroy_for_decl(status, decl_name, decl_type_name, addr_offset, t
|
|
|
14162
14353
|
return;
|
|
14163
14354
|
}
|
|
14164
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;
|
|
14165
14357
|
let skip_label;
|
|
14166
14358
|
if (guard_null) {
|
|
14167
14359
|
emit_var_load(status, "x0", decl_name, 8);
|
|
14168
14360
|
skip_label = `.Lskip_nd_${status.label_counter = (status.label_counter ?? 0) + 1}`;
|
|
14169
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
|
+
}
|
|
14170
14369
|
}
|
|
14171
14370
|
if (has_destroy(struct_type) || struct_type.is_class) {
|
|
14172
14371
|
if (struct_type.is_class) {
|
|
@@ -14321,13 +14520,51 @@ function resolve_decl_struct(decl, status) {
|
|
|
14321
14520
|
}
|
|
14322
14521
|
}
|
|
14323
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
|
+
}
|
|
14324
14561
|
function emit_destroy_for_scope(status, declarations_before) {
|
|
14325
14562
|
const moved = status.moved ?? /* @__PURE__ */ new Set();
|
|
14326
14563
|
const current_scope = status.heap_cleanup_stack?.[status.heap_cleanup_stack.length - 1];
|
|
14327
14564
|
if (current_scope?.heap_slots.length) {
|
|
14328
14565
|
for (let i = declarations_before; i < status.scoped_declarations.length; i++) {
|
|
14329
14566
|
const decl = status.scoped_declarations[i];
|
|
14330
|
-
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);
|
|
14331
14568
|
if (moved.has(decl.name)) continue;
|
|
14332
14569
|
const scope_enum = status.enums.find((e) => e.name === decl.type.name && e.has_associated_data);
|
|
14333
14570
|
if (scope_enum && !decl.type.is_array) {
|
|
@@ -14370,6 +14607,18 @@ function emit_destroy_for_scope(status, declarations_before) {
|
|
|
14370
14607
|
emit_free(status);
|
|
14371
14608
|
continue;
|
|
14372
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
|
+
}
|
|
14373
14622
|
if (decl.type.name === "func") {
|
|
14374
14623
|
emit_destroy_for_decl(status, decl.name, "func");
|
|
14375
14624
|
continue;
|
|
@@ -14389,7 +14638,7 @@ function emit_destroy_for_scope(status, declarations_before) {
|
|
|
14389
14638
|
}
|
|
14390
14639
|
for (let i = declarations_before; i < status.scoped_declarations.length; i++) {
|
|
14391
14640
|
const decl = status.scoped_declarations[i];
|
|
14392
|
-
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);
|
|
14393
14642
|
if (moved.has(decl.name)) continue;
|
|
14394
14643
|
const scope_enum = status.enums.find((e) => e.name === decl.type.name && e.has_associated_data);
|
|
14395
14644
|
if (scope_enum && !decl.type.is_array) {
|
|
@@ -14463,6 +14712,18 @@ function emit_destroy_for_scope(status, declarations_before) {
|
|
|
14463
14712
|
emit_free(status);
|
|
14464
14713
|
continue;
|
|
14465
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
|
+
}
|
|
14466
14727
|
if (is_field_struct_borrow(decl)) continue;
|
|
14467
14728
|
const resolved_decl = resolve_decl_struct(decl, status);
|
|
14468
14729
|
if (!resolved_decl) continue;
|
|
@@ -14739,13 +15000,31 @@ function materialize_func_value_a64(func, status) {
|
|
|
14739
15000
|
const lines = [];
|
|
14740
15001
|
lines.push(`.p2align 2`);
|
|
14741
15002
|
lines.push(`${thunk}:`);
|
|
14742
|
-
const
|
|
14743
|
-
|
|
14744
|
-
|
|
14745
|
-
|
|
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}]`);
|
|
14746
15017
|
lines.push(`str x9, [sp, #${k * 8}]`);
|
|
14747
15018
|
}
|
|
14748
|
-
|
|
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}`);
|
|
14749
15028
|
lines.push(``);
|
|
14750
15029
|
status.closure_definitions = (status.closure_definitions ?? "") + lines.join("\n") + "\n";
|
|
14751
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;
|
|
@@ -19267,6 +19546,8 @@ function build_function_node$1(node, status) {
|
|
|
19267
19546
|
if (check_c_fallback(node, void 0, status)) return;
|
|
19268
19547
|
const old_function_name = status.current_function_name;
|
|
19269
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;
|
|
19270
19551
|
status.function_nursery_depth = status.nursery_stack?.length ?? 0;
|
|
19271
19552
|
const old_scoped_declarations = status.scoped_declarations;
|
|
19272
19553
|
status.scoped_declarations = [];
|
|
@@ -19855,6 +20136,7 @@ function build_function_node$1(node, status) {
|
|
|
19855
20136
|
status.heap_cleanup_stack = old_heap_cleanup_stack;
|
|
19856
20137
|
status.trait_class_frames = old_trait_class_frames;
|
|
19857
20138
|
status.current_function_name = old_function_name;
|
|
20139
|
+
status.current_function_is_closure = old_is_closure;
|
|
19858
20140
|
status.stack_size = old_stack_size;
|
|
19859
20141
|
status.stack_offsets = old_stack_offsets;
|
|
19860
20142
|
status.closure_env_slot = old_closure_env_slot;
|
|
@@ -21381,7 +21663,13 @@ function build_block_node$1(node, status) {
|
|
|
21381
21663
|
if (is_struct_node(child) || is_trait_node(child) || is_function_node(child) || child.node_type === "enum" || child.node_type === "bitset") continue;
|
|
21382
21664
|
if (child.node_type === "declare") {
|
|
21383
21665
|
const decl = child;
|
|
21384
|
-
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
|
+
}
|
|
21385
21673
|
if (inlined_const_names.has(decl.name)) continue;
|
|
21386
21674
|
if (is_literal_init(decl.value)) continue;
|
|
21387
21675
|
module_init_statements.push(child);
|
|
@@ -21490,69 +21778,6 @@ function call_in_set(set, call) {
|
|
|
21490
21778
|
return set.has(call.name.replace(/#/g, ""));
|
|
21491
21779
|
}
|
|
21492
21780
|
//#endregion
|
|
21493
|
-
//#region ../src/build_common/call_init_string_fields.ts
|
|
21494
|
-
/**
|
|
21495
|
-
* Whether a declaration-initializer expression is a value the callee's
|
|
21496
|
-
* return-boundary normalization made UNIFORMLY heap-owned, so the fresh
|
|
21497
|
-
* binding records every string field and frees them at scope exit:
|
|
21498
|
-
*
|
|
21499
|
-
* - a plain function call registered as a normalized struct returner
|
|
21500
|
-
* (the registry is populated at the callee's return sites — a callee
|
|
21501
|
-
* built later, or one that returns slot borrows, is not registered and
|
|
21502
|
-
* the binding keeps the pre-existing borrow behavior);
|
|
21503
|
-
* - an owned accessor (`pop`/`move_T`/`copy`) — move-out semantics
|
|
21504
|
-
* transfer slot-owned heap buffers to the binding.
|
|
21505
|
-
*
|
|
21506
|
-
* NOT normalized (never recorded): struct constructions (`Info(...)` — its
|
|
21507
|
-
* `#init` leaves rodata borrows and bypasses the return path) and container
|
|
21508
|
-
* BORROW accessors (`.at`/`.first`/`.slice`/`load` — the slot keeps
|
|
21509
|
-
* ownership; recording would free the container's storage).
|
|
21510
|
-
*/
|
|
21511
|
-
function is_normalized_struct_call(value, status) {
|
|
21512
|
-
if (!value) return false;
|
|
21513
|
-
if (value.node_type === "func_call") {
|
|
21514
|
-
const call = value;
|
|
21515
|
-
return !!status.normalized_struct_returners?.has(call.name);
|
|
21516
|
-
}
|
|
21517
|
-
if (value.node_type === "access") {
|
|
21518
|
-
const access = value.access;
|
|
21519
|
-
if (access?.node_type !== "access_func") return false;
|
|
21520
|
-
const name = access.name ?? "";
|
|
21521
|
-
if (is_container_borrow_accessor_name(name)) return false;
|
|
21522
|
-
if (status.normalized_struct_returners?.has(name)) return true;
|
|
21523
|
-
const target = value.target;
|
|
21524
|
-
const receiver_type = target ? target.type ?? void 0 : void 0;
|
|
21525
|
-
if (receiver_type?.name) {
|
|
21526
|
-
const label = `${mono_type_name(receiver_type)}_${name}`;
|
|
21527
|
-
if (status.normalized_struct_returners?.has(label)) return true;
|
|
21528
|
-
}
|
|
21529
|
-
return false;
|
|
21530
|
-
}
|
|
21531
|
-
return false;
|
|
21532
|
-
}
|
|
21533
|
-
/**
|
|
21534
|
-
* Record every direct string field of a call-initialized value-struct
|
|
21535
|
-
* binding (`var Info i = make()`) as heap-owned: the callee's return
|
|
21536
|
-
* normalization guarantees each field is a heap buffer, so scope-exit
|
|
21537
|
-
* auto_free must free them, and a later `i.field = …` write sees
|
|
21538
|
-
* old_was_heap and reclaims the displaced buffer instead of leaking it.
|
|
21539
|
-
* No-op for non-struct, class, view, and array types, for structs without
|
|
21540
|
-
* direct string fields, and for non-call initializers.
|
|
21541
|
-
*/
|
|
21542
|
-
function record_call_init_string_fields(node, status) {
|
|
21543
|
-
if (!is_normalized_struct_call(node.value, status)) return;
|
|
21544
|
-
const type = node.type;
|
|
21545
|
-
if (!type?.name) return;
|
|
21546
|
-
const mono_name = mono_type_name(type);
|
|
21547
|
-
if (mono_name.startsWith("_Tuple_")) return;
|
|
21548
|
-
const struct = status.structs.find((s) => s.name === mono_name && !s.is_simple_type);
|
|
21549
|
-
if (!struct || struct.is_class) return;
|
|
21550
|
-
const fields = direct_string_fields(struct);
|
|
21551
|
-
if (!fields.length) return;
|
|
21552
|
-
if (!status.heap_string_fields) status.heap_string_fields = /* @__PURE__ */ new Set();
|
|
21553
|
-
for (const field of fields) status.heap_string_fields.add(`${node.name}.${field.name}`);
|
|
21554
|
-
}
|
|
21555
|
-
//#endregion
|
|
21556
21781
|
//#region ../src/build_common/fold_string_const.ts
|
|
21557
21782
|
/**
|
|
21558
21783
|
* Fold a top-level `const`'s string `+` chain into a single string literal.
|
|
@@ -21614,6 +21839,24 @@ function literal(raw, start) {
|
|
|
21614
21839
|
return new ValueNode(start, raw, new Type("string", true));
|
|
21615
21840
|
}
|
|
21616
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
|
|
21617
21860
|
//#region ../src/build_common/string_escapes.ts
|
|
21618
21861
|
/**
|
|
21619
21862
|
* Source string-literal escape handling shared by both backends.
|
|
@@ -21743,6 +21986,37 @@ function escape_asciz(value) {
|
|
|
21743
21986
|
return quote + reencoded.slice(1, reencoded.endsWith(quote) ? -1 : void 0).replace(/\n/g, "\\n") + (reencoded.endsWith(quote) ? quote : "");
|
|
21744
21987
|
}
|
|
21745
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
|
|
21746
22020
|
//#region ../src/build_aarch64/build_range_node.ts
|
|
21747
22021
|
function build_range_node$1(node, status) {
|
|
21748
22022
|
const start = evaluate_constant$1(node.left_value);
|
|
@@ -22138,6 +22412,7 @@ function emit_struct_constructor_to_slot(fc, slot_addr, status) {
|
|
|
22138
22412
|
* so register slots start at x1 — overflow begins at the 8th arg (slot 8).
|
|
22139
22413
|
*/
|
|
22140
22414
|
function build_constructor_params(fc, param_regs, status) {
|
|
22415
|
+
mark_tuple_literal_move_owners(fc, status);
|
|
22141
22416
|
const variadic_idx = fc.variadic_param_index;
|
|
22142
22417
|
if (variadic_idx !== void 0 && fc.params[variadic_idx] && fc.params[variadic_idx].node_type === "array") {
|
|
22143
22418
|
const arr = fc.params[variadic_idx];
|
|
@@ -22537,6 +22812,43 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
|
|
|
22537
22812
|
if (!status.function_local_types) status.function_local_types = /* @__PURE__ */ new Map();
|
|
22538
22813
|
status.function_local_types.set(node.name, node.type.name);
|
|
22539
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
|
+
}
|
|
22540
22852
|
hoist_field_overrides(node.value, build_node, status, "", node.name);
|
|
22541
22853
|
status.last_result_is_heap = false;
|
|
22542
22854
|
const prev_heap = status.last_result_is_heap;
|
|
@@ -23002,13 +23314,33 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
|
|
|
23002
23314
|
} else if (status.function_return_label) {
|
|
23003
23315
|
const labels = emit_string_array_labels(array_values.values, status);
|
|
23004
23316
|
if (needs_runtime_array_init(array_values.values, status)) {
|
|
23005
|
-
|
|
23006
|
-
|
|
23007
|
-
|
|
23008
|
-
|
|
23009
|
-
|
|
23010
|
-
|
|
23011
|
-
|
|
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
|
+
}
|
|
23012
23344
|
} else {
|
|
23013
23345
|
emit_data(status, `${node.name}: ${directive} `);
|
|
23014
23346
|
let first = true;
|
|
@@ -23024,19 +23356,16 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
|
|
|
23024
23356
|
emit_data(status, `\n.p2align 2\n`);
|
|
23025
23357
|
}
|
|
23026
23358
|
} else if (node.type.name === "string" && node.type.is_array) {
|
|
23027
|
-
|
|
23028
|
-
|
|
23029
|
-
|
|
23030
|
-
|
|
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) => {
|
|
23031
23365
|
const resolved = resolve_static_value(value, status);
|
|
23032
|
-
|
|
23033
|
-
else emit_asm(status, ".quad 0\n .quad 0");
|
|
23366
|
+
emit_data(status, `\t${directive} ${resolved !== null ? resolved : "0"}\n`);
|
|
23034
23367
|
});
|
|
23035
|
-
|
|
23036
|
-
} else {
|
|
23037
|
-
emit_asm(status, `${node.name}: ${directive} `);
|
|
23038
|
-
build_array_values_node$1(array_values, status);
|
|
23039
|
-
emit_asm(status, `\n.p2align 2\n`);
|
|
23368
|
+
emit_data(status, `\t.p2align 2\n`);
|
|
23040
23369
|
}
|
|
23041
23370
|
} else if (node.value && node.value.node_type === "range") {
|
|
23042
23371
|
if (status.function_return_label) {
|
|
@@ -24925,7 +25254,7 @@ function build_return_node$1(node, status, nir_value) {
|
|
|
24925
25254
|
const finalized = status.moved ?? /* @__PURE__ */ new Set();
|
|
24926
25255
|
for (const decl of all_scope_frames(status).flat()) {
|
|
24927
25256
|
if (finalized.has(decl.name)) {
|
|
24928
|
-
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);
|
|
24929
25258
|
continue;
|
|
24930
25259
|
}
|
|
24931
25260
|
if (is_field_struct_borrow(decl)) continue;
|
|
@@ -24945,7 +25274,7 @@ function build_return_node$1(node, status, nir_value) {
|
|
|
24945
25274
|
const finalized = status.moved ?? /* @__PURE__ */ new Set();
|
|
24946
25275
|
for (const decl of all_scope_frames(status).flat()) {
|
|
24947
25276
|
if (finalized.has(decl.name)) {
|
|
24948
|
-
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);
|
|
24949
25278
|
continue;
|
|
24950
25279
|
}
|
|
24951
25280
|
if (is_field_struct_borrow(decl)) continue;
|
|
@@ -25033,7 +25362,10 @@ function build_return_node$1(node, status, nir_value) {
|
|
|
25033
25362
|
if (anon.base.is_moved) mark_moved_if_struct(anon.base, status);
|
|
25034
25363
|
emit_field_overrides(temp_name, anon, build_node, status);
|
|
25035
25364
|
emit_var_address(status, "x0", temp_name);
|
|
25036
|
-
} else
|
|
25365
|
+
} else {
|
|
25366
|
+
status.last_result_is_heap = false;
|
|
25367
|
+
emit_return_value$1(node.value, nir_value, status);
|
|
25368
|
+
}
|
|
25037
25369
|
if (ret_is_float) {
|
|
25038
25370
|
ensure_newline(status);
|
|
25039
25371
|
if (status.float_result_in_d0) emit_asm(status, `fmov d0, x0\n`);
|
|
@@ -25045,7 +25377,7 @@ function build_return_node$1(node, status, nir_value) {
|
|
|
25045
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("\"")) {
|
|
25046
25378
|
const fn_name = status.current_function_name;
|
|
25047
25379
|
const mangled = status.current_struct ? `${status.current_struct.name}_${fn_name}` : fn_name;
|
|
25048
|
-
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))) {
|
|
25049
25381
|
emit_strdup_string(status);
|
|
25050
25382
|
ensure_newline(status);
|
|
25051
25383
|
status.last_result_is_heap = true;
|
|
@@ -25053,7 +25385,7 @@ function build_return_node$1(node, status, nir_value) {
|
|
|
25053
25385
|
}
|
|
25054
25386
|
const borrow_fn_name = status.current_function_name;
|
|
25055
25387
|
const borrow_mangled = status.current_struct && borrow_fn_name ? `${status.current_struct.name}_${borrow_fn_name}` : void 0;
|
|
25056
|
-
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));
|
|
25057
25389
|
let needs_borrow_strdup = false;
|
|
25058
25390
|
if (normalizes_borrow_returns) {
|
|
25059
25391
|
if (node.value.node_type === "value") {
|
|
@@ -25063,6 +25395,7 @@ function build_return_node$1(node, status, nir_value) {
|
|
|
25063
25395
|
if (!is_str_lit && !is_numeric && raw_value !== "null" && !status.heap_strings?.has(raw_value)) needs_borrow_strdup = true;
|
|
25064
25396
|
} else if (is_container_borrow_access(node.value)) needs_borrow_strdup = true;
|
|
25065
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;
|
|
25066
25399
|
else if ((node.value.node_type === "match" || node.value.node_type === "switch") && !return_join_owned_string) needs_borrow_strdup = true;
|
|
25067
25400
|
}
|
|
25068
25401
|
if (needs_borrow_strdup) {
|
|
@@ -25118,6 +25451,16 @@ function build_return_node$1(node, status, nir_value) {
|
|
|
25118
25451
|
}
|
|
25119
25452
|
emit_string_field_strdups_at(status, ret_struct.name, "x8", 0, skip_fields);
|
|
25120
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
|
+
}
|
|
25121
25464
|
}
|
|
25122
25465
|
}
|
|
25123
25466
|
}
|
|
@@ -25186,7 +25529,7 @@ function build_return_node$1(node, status, nir_value) {
|
|
|
25186
25529
|
emit_nursery_joins_on_return_a64(status);
|
|
25187
25530
|
for (const decl of all_scope_frames(status).flat()) {
|
|
25188
25531
|
if (finalized.has(decl.name)) {
|
|
25189
|
-
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);
|
|
25190
25533
|
continue;
|
|
25191
25534
|
}
|
|
25192
25535
|
if (is_field_struct_borrow(decl)) continue;
|
|
@@ -28423,6 +28766,7 @@ function build_function_call_node$1(node, status) {
|
|
|
28423
28766
|
}
|
|
28424
28767
|
}
|
|
28425
28768
|
const is_struct = status.structs.find((s) => s.name === node.name && !s.is_simple_type);
|
|
28769
|
+
mark_tuple_literal_move_owners(node, status);
|
|
28426
28770
|
const func_name = is_struct ? node.mangled_name ?? `${node.name}_init` : emission_label(node.resolved_function ?? node);
|
|
28427
28771
|
const param_regs = [
|
|
28428
28772
|
"x0",
|
|
@@ -28462,8 +28806,25 @@ function build_function_call_node$1(node, status) {
|
|
|
28462
28806
|
const env_off = status.closure_env_offsets?.get(node.name);
|
|
28463
28807
|
const paramReg = func_offset === void 0 ? status.function_param_regs?.get(node.name) : void 0;
|
|
28464
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);
|
|
28465
28820
|
const lambda_arg_slots = [];
|
|
28466
|
-
|
|
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
|
+
};
|
|
28467
28828
|
for (let i = node.params.length - 1; i >= 0; i--) {
|
|
28468
28829
|
const param = node.params[i];
|
|
28469
28830
|
const is_pair = type_from_value_node$1(param)?.name === "string";
|
|
@@ -28473,19 +28834,25 @@ function build_function_call_node$1(node, status) {
|
|
|
28473
28834
|
emit_asm(status, `str x0, [x29, #${dispose_slot}]\n`);
|
|
28474
28835
|
lambda_arg_slots.push(dispose_slot);
|
|
28475
28836
|
}
|
|
28476
|
-
const
|
|
28477
|
-
|
|
28478
|
-
|
|
28479
|
-
|
|
28480
|
-
|
|
28481
|
-
|
|
28482
|
-
|
|
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
|
+
}
|
|
28483
28848
|
}
|
|
28484
28849
|
emit_asm(status, desc_source);
|
|
28485
28850
|
emit_asm(status, `ldr x8, [x9]\n`);
|
|
28486
28851
|
emit_asm(status, `ldr x0, [x9, #8]\n`);
|
|
28487
28852
|
emit_asm(status, `blr x8\n`);
|
|
28488
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;
|
|
28489
28856
|
} else {
|
|
28490
28857
|
const variadic_idx = node.variadic_param_index;
|
|
28491
28858
|
const ref_class_sync_names = [];
|
|
@@ -31214,6 +31581,20 @@ function emit_enum_payload_reclaim(cname, enum_node, c, p, status) {
|
|
|
31214
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`;
|
|
31215
31582
|
else if (p.type.name === "string") status.code += `${guard} { free(${cname}._data._${c.name}.${p.name}.ptr); }\n`;
|
|
31216
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
|
+
}
|
|
31217
31598
|
function build_auto_free(status) {
|
|
31218
31599
|
free_scoped_declarations(status, status.scoped_declarations);
|
|
31219
31600
|
if (status.deferred_frees?.length) {
|
|
@@ -31235,11 +31616,15 @@ function free_scoped_declarations(status, decls, persist_string_field_records =
|
|
|
31235
31616
|
let commented = false;
|
|
31236
31617
|
if (status.heap_string_fields?.size) for (const dec of decls) {
|
|
31237
31618
|
const prefix = `${dec.name}.`;
|
|
31238
|
-
|
|
31239
|
-
|
|
31240
|
-
|
|
31241
|
-
|
|
31242
|
-
|
|
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) {
|
|
31243
31628
|
const dot = key.indexOf(".");
|
|
31244
31629
|
const base = c_function_name(key.substring(0, dot));
|
|
31245
31630
|
const arrow = dec.string_fields_via_pointer ? "->" : ".";
|
|
@@ -31248,6 +31633,7 @@ function free_scoped_declarations(status, decls, persist_string_field_records =
|
|
|
31248
31633
|
status.code += `${free_fn}(${base}${arrow}${field}.ptr);\n`;
|
|
31249
31634
|
if (!persist_string_field_records) status.heap_string_fields.delete(key);
|
|
31250
31635
|
}
|
|
31636
|
+
if (nullable_flag) status.code += `}\n`;
|
|
31251
31637
|
}
|
|
31252
31638
|
for (const dec of decls) {
|
|
31253
31639
|
if (dec.owned_heap_shell) {
|
|
@@ -31765,6 +32151,49 @@ function emit_rhs_value$1(rhs, nir, status) {
|
|
|
31765
32151
|
}
|
|
31766
32152
|
function build_assignment_node$1(node, status, nir_rhs, nir_swap) {
|
|
31767
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
|
+
}
|
|
31768
32197
|
if (node.left_value.node_type === "access") {
|
|
31769
32198
|
const accessNode = node.left_value;
|
|
31770
32199
|
if (accessNode.target.node_type === "value") {
|
|
@@ -32139,18 +32568,6 @@ function build_assignment_node$1(node, status, nir_rhs, nir_swap) {
|
|
|
32139
32568
|
}
|
|
32140
32569
|
}
|
|
32141
32570
|
status.code += ``;
|
|
32142
|
-
const lhs_nullable_type = lhs_nullable_struct_type(node, status);
|
|
32143
|
-
if (!node.operator && lhs_nullable_type) {
|
|
32144
|
-
const lhs_expr = capture_build(node.left_value, status);
|
|
32145
|
-
const flag = `${lhs_expr}_has`;
|
|
32146
|
-
if (node.right_value.node_type === "value" && node.right_value.value === "null") status.code += `${flag} = 0`;
|
|
32147
|
-
else {
|
|
32148
|
-
status.code += `${lhs_expr} = `;
|
|
32149
|
-
emit_rhs_value$1(node.right_value, nir_rhs, status);
|
|
32150
|
-
status.code += `;\n${flag} = 1`;
|
|
32151
|
-
}
|
|
32152
|
-
return;
|
|
32153
|
-
}
|
|
32154
32571
|
if (!node.operator && !node.swap && node.left_value.node_type === "value") {
|
|
32155
32572
|
const lhs_name = node.left_value.value;
|
|
32156
32573
|
const lhs_type = type_from_value_node$1(node.left_value);
|
|
@@ -32460,6 +32877,101 @@ function emit_nursery_joins_on_return_c(status) {
|
|
|
32460
32877
|
for (let i = status.nursery_stack.length - 1; i >= base; i--) emit_nursery_join_c(status, status.nursery_stack[i], true);
|
|
32461
32878
|
}
|
|
32462
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
|
|
32463
32975
|
//#region ../src/build_c/build_array_values_node.ts
|
|
32464
32976
|
/**
|
|
32465
32977
|
* Build an array literal's `{a, b, c}` initializer text. Under NIR-driven
|
|
@@ -32471,11 +32983,13 @@ function emit_nursery_joins_on_return_c(status) {
|
|
|
32471
32983
|
function build_array_values_node(node, status, nir_elements) {
|
|
32472
32984
|
status.code += `{`;
|
|
32473
32985
|
const elem_is_string = node.type?.name === "string";
|
|
32986
|
+
const static_init = status.static_string_array_inits === true;
|
|
32474
32987
|
node.values.forEach((value, i) => {
|
|
32475
32988
|
if (i > 0) status.code += ", ";
|
|
32476
32989
|
const is_string_literal = elem_is_string && value.node_type === "value" && value.value.length >= 2 && value.value.startsWith("\"") && value.value.endsWith("\"");
|
|
32477
32990
|
const nir_elem = nir_elements?.[i];
|
|
32478
|
-
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) {
|
|
32479
32993
|
status.code += `nomen_str_dup(`;
|
|
32480
32994
|
if (nir_elem && nir_elem.node === value) emit_expr_from_nir(nir_elem, status);
|
|
32481
32995
|
else build_node$1(value, status);
|
|
@@ -32618,6 +33132,42 @@ function build_declaration_node(node, status, nir_init, nir_swap) {
|
|
|
32618
33132
|
status.code += `] = {${variables.map((v) => `&${v}`).join(", ")}}`;
|
|
32619
33133
|
} else {
|
|
32620
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
|
+
}
|
|
32621
33171
|
const mono_name = mono_type_name(node.type);
|
|
32622
33172
|
const mono_struct = status.structs.find((s) => s.name === mono_name && !s.is_simple_type && !s.is_generic);
|
|
32623
33173
|
const is_class_type = !!mono_struct?.is_class;
|
|
@@ -32821,6 +33371,7 @@ function build_declaration_node(node, status, nir_init, nir_swap) {
|
|
|
32821
33371
|
status.current_nullable_call_flag = flag;
|
|
32822
33372
|
emit_init_value(node.value, nir_init, status);
|
|
32823
33373
|
status.current_nullable_call_flag = old;
|
|
33374
|
+
record_call_init_string_fields(node, status);
|
|
32824
33375
|
} else {
|
|
32825
33376
|
emit_init_value(node.value, nir_init, status);
|
|
32826
33377
|
status.code += `;\n${flag} = 1`;
|
|
@@ -32890,6 +33441,7 @@ function build_declaration_node(node, status, nir_init, nir_swap) {
|
|
|
32890
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}`;
|
|
32891
33442
|
else {
|
|
32892
33443
|
status.code += " = ";
|
|
33444
|
+
status.static_string_array_inits = is_stack_array && !status.current_function && node.type.name === "string";
|
|
32893
33445
|
const val_node = node.value;
|
|
32894
33446
|
const val_is_string_literal = node.value.node_type === "value" && val_node.value.length >= 2 && val_node.value.startsWith("\"") && val_node.value.endsWith("\"");
|
|
32895
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);
|
|
@@ -32927,6 +33479,7 @@ function build_declaration_node(node, status, nir_init, nir_swap) {
|
|
|
32927
33479
|
emit_init_value(node.value, nir_init, status);
|
|
32928
33480
|
status.suppress_dereference = false;
|
|
32929
33481
|
} else emit_init_value(node.value, nir_init, status);
|
|
33482
|
+
status.static_string_array_inits = false;
|
|
32930
33483
|
}
|
|
32931
33484
|
}
|
|
32932
33485
|
if (node.swap && node.value.node_type === "access") {
|
|
@@ -33586,6 +34139,15 @@ function build_return_node(node, status, nir_value) {
|
|
|
33586
34139
|
if (returned_recorded_string_fields?.has(field.name)) continue;
|
|
33587
34140
|
status.code += `_return_val.${field.name} = nomen_str_dup(_return_val.${field.name});\n`;
|
|
33588
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
|
+
}
|
|
33589
34151
|
emit_nursery_joins_on_return_c(status);
|
|
33590
34152
|
reclaim_all_c_scopes(status);
|
|
33591
34153
|
status.code += `return _return_val;\n`;
|
|
@@ -35350,6 +35912,22 @@ function emit_closure_env_type(func, status) {
|
|
|
35350
35912
|
}
|
|
35351
35913
|
//#endregion
|
|
35352
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
|
+
}
|
|
35353
35931
|
function build_function_node(node, status) {
|
|
35354
35932
|
if (node.is_generic) return;
|
|
35355
35933
|
if (node.is_extern) {
|
|
@@ -35571,7 +36149,8 @@ function build_function_node(node, status) {
|
|
|
35571
36149
|
status.current_function_name = old_function_name;
|
|
35572
36150
|
status.current_function = old_current_function;
|
|
35573
36151
|
status.closure_env = old_closure_env;
|
|
35574
|
-
|
|
36152
|
+
if (statements_definitely_return(node.statements)) discard_string_field_records(status, status.scoped_declarations);
|
|
36153
|
+
else build_auto_free(status);
|
|
35575
36154
|
if (node.name.toLocaleLowerCase() === "main" && status.used_fibers) {
|
|
35576
36155
|
status.code += `\n__nomen_fiber_drain_all();\n`;
|
|
35577
36156
|
status.code += `__nomen_io_shutdown();\n`;
|
|
@@ -36755,6 +37334,7 @@ function build_function_call_node(node, status) {
|
|
|
36755
37334
|
const func_name = is_struct ? node.mangled_name ?? `${node.name}_init` : c_function_name(emission_label(node.resolved_function ?? node));
|
|
36756
37335
|
const callee_params = node.resolved_function?.params?.filter((p) => !p.is_self_param);
|
|
36757
37336
|
const lambda_arg_temps = /* @__PURE__ */ new Map();
|
|
37337
|
+
mark_tuple_literal_move_owners(node, status);
|
|
36758
37338
|
if (!is_struct) for (let i = 0; i < node.params.length; i++) {
|
|
36759
37339
|
const p = node.params[i];
|
|
36760
37340
|
if (p.node_type !== "func") continue;
|
|
@@ -37156,101 +37736,6 @@ function build_todo_node$1(node, status) {
|
|
|
37156
37736
|
status.code += `exit(EXIT_FAILURE);\n`;
|
|
37157
37737
|
}
|
|
37158
37738
|
//#endregion
|
|
37159
|
-
//#region ../src/build_common/decode_char_literal.ts
|
|
37160
|
-
/**
|
|
37161
|
-
* Decode a Nomen char literal token (raw value INCLUDING its surrounding
|
|
37162
|
-
* quotes, as carried by ValueNode) to its numeric code. The tokenizer
|
|
37163
|
-
* consumes escape pairs as a unit, so multi-char forms like `'\\\\'`,
|
|
37164
|
-
* `'\\n'`, `'\\''` and `'\\xNN'` arrive raw and must be decoded here.
|
|
37165
|
-
* Returns undefined for malformed literals.
|
|
37166
|
-
*/
|
|
37167
|
-
function decode_char_literal(raw) {
|
|
37168
|
-
if (raw.length < 3 || raw[0] !== "'" || raw[raw.length - 1] !== "'") return;
|
|
37169
|
-
const inner = raw.slice(1, -1);
|
|
37170
|
-
if (inner.length === 1) return inner.charCodeAt(0);
|
|
37171
|
-
if (inner[0] !== "\\") return;
|
|
37172
|
-
const esc = inner[1];
|
|
37173
|
-
switch (esc) {
|
|
37174
|
-
case "\\": return 92;
|
|
37175
|
-
case "n": return 10;
|
|
37176
|
-
case "t": return 9;
|
|
37177
|
-
case "r": return 13;
|
|
37178
|
-
case "'": return 39;
|
|
37179
|
-
case "\"": return 34;
|
|
37180
|
-
case "0": return 0;
|
|
37181
|
-
case "x": {
|
|
37182
|
-
const hex = inner.slice(2);
|
|
37183
|
-
if (!/^[0-9a-fA-F]+$/.test(hex)) return void 0;
|
|
37184
|
-
return parseInt(hex, 16);
|
|
37185
|
-
}
|
|
37186
|
-
default: return esc.charCodeAt(0);
|
|
37187
|
-
}
|
|
37188
|
-
}
|
|
37189
|
-
//#endregion
|
|
37190
|
-
//#region ../src/build_c/utils/c_char_literal.ts
|
|
37191
|
-
function c_char_literal(value) {
|
|
37192
|
-
const code = decode_char_literal(value);
|
|
37193
|
-
if (code === void 0) return value;
|
|
37194
|
-
if (code === 92) return `'\\\\'`;
|
|
37195
|
-
if (code === 39) return `'\\''`;
|
|
37196
|
-
if (code === 10) return `'\\n'`;
|
|
37197
|
-
if (code === 13) return `'\\r'`;
|
|
37198
|
-
if (code === 9) return `'\\t'`;
|
|
37199
|
-
if (code < 32 || code > 126) return `'\\x${code.toString(16)}'`;
|
|
37200
|
-
return `'${String.fromCharCode(code)}'`;
|
|
37201
|
-
}
|
|
37202
|
-
//#endregion
|
|
37203
|
-
//#region ../src/build_c/build_value_node.ts
|
|
37204
|
-
const INT_LITERAL_SUFFIX = {
|
|
37205
|
-
int: "L",
|
|
37206
|
-
uint: "UL",
|
|
37207
|
-
int64: "LL",
|
|
37208
|
-
uint64: "ULL"
|
|
37209
|
-
};
|
|
37210
|
-
function build_value_node$1(node, status) {
|
|
37211
|
-
let value = node.value;
|
|
37212
|
-
if (status.closure_env?.has(value)) {
|
|
37213
|
-
if (!(!!status.current_function?.params.some((p) => p.name === value) || !!find_decl_in_c_scopes(status, value))) {
|
|
37214
|
-
status.code += status.closure_env.get(value);
|
|
37215
|
-
return;
|
|
37216
|
-
}
|
|
37217
|
-
}
|
|
37218
|
-
const inlined = status.top_level_consts?.get(value);
|
|
37219
|
-
if (inlined?.value) {
|
|
37220
|
-
build_node$1(fold_string_const(inlined, status.top_level_consts) ?? inlined.value, status);
|
|
37221
|
-
return;
|
|
37222
|
-
}
|
|
37223
|
-
if (node.is_enum_shorthand) {
|
|
37224
|
-
const enum_node = status.enums.find((e) => value.startsWith(e.name + "_"));
|
|
37225
|
-
if (enum_node) {
|
|
37226
|
-
if (enum_node.has_associated_data) status.code += `${value}_init()`;
|
|
37227
|
-
else status.code += value;
|
|
37228
|
-
return;
|
|
37229
|
-
}
|
|
37230
|
-
}
|
|
37231
|
-
if (value === "null") value = "0";
|
|
37232
|
-
else if (value === "true") value = "1";
|
|
37233
|
-
else if (value === "false") value = "0";
|
|
37234
|
-
else if (value === "default") value = "_nomen_default";
|
|
37235
|
-
else if (is_int_literal(value)) value = value.replace(/_/g, "") + (INT_LITERAL_SUFFIX[node.type?.name ?? "int"] ?? "");
|
|
37236
|
-
else if (node.resolved_function) {
|
|
37237
|
-
status.code += materialize_func_value(node.resolved_function, status);
|
|
37238
|
-
return;
|
|
37239
|
-
} else if (value.startsWith("'") && value.endsWith("'")) value = c_char_literal(value);
|
|
37240
|
-
else value = c_function_name(value);
|
|
37241
|
-
if (value.startsWith("\"")) value = `nomen_str_lit(${escape_c_string(value)}, ${string_literal_length(value)})`;
|
|
37242
|
-
if (value !== "self" && status.function_ref_params?.has(value) && !status.suppress_dereference) status.code += `*`;
|
|
37243
|
-
if (value === "self" && status.function_ref_params?.has("self") && !status.suppress_dereference) status.code += `*`;
|
|
37244
|
-
if (value !== "self" && status.ref_class_params?.has(value) && !status.suppress_dereference) {
|
|
37245
|
-
status.code += `(*${value})`;
|
|
37246
|
-
return;
|
|
37247
|
-
}
|
|
37248
|
-
status.code += value;
|
|
37249
|
-
}
|
|
37250
|
-
function escape_c_string(s) {
|
|
37251
|
-
return reencode_hex_escapes(s.replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t"));
|
|
37252
|
-
}
|
|
37253
|
-
//#endregion
|
|
37254
37739
|
//#region ../src/build_c/build_node.ts
|
|
37255
37740
|
function build_node$1(node, status, with_semicolon = false) {
|
|
37256
37741
|
if (node.allocations) {
|
|
@@ -38740,13 +39225,49 @@ function build_nullable_struct_assignment(node, status, nir_rhs) {
|
|
|
38740
39225
|
if (node.left_value.node_type === "value") {
|
|
38741
39226
|
const name = node.left_value.value;
|
|
38742
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}.`));
|
|
38743
39230
|
if (rhs_is_null) {
|
|
39231
|
+
if (reclaimable) emit_recorded_field_reclaim_for_slot(status, name, type_name);
|
|
38744
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;
|
|
38745
39260
|
return;
|
|
38746
39261
|
}
|
|
38747
39262
|
emit_rhs_value(node.right_value, nir_rhs, status);
|
|
38748
39263
|
ensure_newline(status);
|
|
38749
|
-
|
|
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);
|
|
38750
39271
|
const dst_offset = status.stack_offsets?.get(name);
|
|
38751
39272
|
emit_struct_copy("x0", "x29", dst_offset ?? 0, struct_size, status);
|
|
38752
39273
|
emit_asm(status, `mov x9, #1\n`);
|
|
@@ -41040,6 +41561,7 @@ function is_owned_heap_temp(node, status) {
|
|
|
41040
41561
|
if (check_type_name !== "string") return false;
|
|
41041
41562
|
if (check_node.node_type === "op") return true;
|
|
41042
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;
|
|
41043
41565
|
const raw_name = check_node.name;
|
|
41044
41566
|
const mangled = check_node.mangled_name || raw_name;
|
|
41045
41567
|
const dbg = process.env.NOMEN_DBG_TEMP && typeof mangled === "string" && mangled.indexOf("replace") >= 0;
|
|
@@ -42282,6 +42804,22 @@ function resolve_access_target_type(target, status) {
|
|
|
42282
42804
|
* final field is read. Without this, `state.rules.blocks.length` (State/
|
|
42283
42805
|
* BlockParserState is a class) summed 8+8+8 and loaded from `state + 24`.
|
|
42284
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
|
+
}
|
|
42285
42823
|
function access_chain_crosses_class(node, status) {
|
|
42286
42824
|
const base_type = resolve_access_target_type(get_base_target(node), status);
|
|
42287
42825
|
if (base_type?.name && status.structs.find((s) => s.name === base_type.name && s.is_class)) return true;
|
|
@@ -42704,6 +43242,12 @@ function build_access_field(node, status) {
|
|
|
42704
43242
|
ensure_newline(status);
|
|
42705
43243
|
const final_offset = offset;
|
|
42706
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
|
+
}
|
|
42707
43251
|
const at_reg = status.at_addr_reg;
|
|
42708
43252
|
status.at_addr_reg = void 0;
|
|
42709
43253
|
const load_base = at_reg ?? "x0";
|
|
@@ -42725,7 +43269,7 @@ function build_access_field(node, status) {
|
|
|
42725
43269
|
else emit_asm(status, `ldr x0, [${load_base}, #${final_offset}]\n`);
|
|
42726
43270
|
return;
|
|
42727
43271
|
}
|
|
42728
|
-
if (target_is_class_access) {
|
|
43272
|
+
if (target_is_class_access || !chain_is_pure_fields(node.target)) {
|
|
42729
43273
|
build_node(node.target, status);
|
|
42730
43274
|
ensure_newline(status);
|
|
42731
43275
|
const final_offset = get_field_offset(target_type?.name || "", access_field.name, status);
|
|
@@ -43328,9 +43872,11 @@ function build_access_method(node, access_func, status) {
|
|
|
43328
43872
|
} else if (!target_is_simple && node.target.node_type === "access") {
|
|
43329
43873
|
const access_target = node.target;
|
|
43330
43874
|
if (access_target.access.node_type === "access_field") {
|
|
43331
|
-
if (access_chain_crosses_class(access_target, status)) {
|
|
43332
|
-
|
|
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);
|
|
43333
43878
|
ensure_newline(status);
|
|
43879
|
+
if (field_offset > 0) emit_asm(status, `add x0, x0, #${field_offset}\n`);
|
|
43334
43880
|
} else {
|
|
43335
43881
|
const offset = compute_field_offset(access_target, status);
|
|
43336
43882
|
const base = get_base_target(access_target);
|
|
@@ -47526,9 +48072,11 @@ function mismatch(expected, actual, status, start) {
|
|
|
47526
48072
|
* when either side's signature is unavailable.
|
|
47527
48073
|
*/
|
|
47528
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;
|
|
47529
48077
|
const expected_sig = {
|
|
47530
|
-
params:
|
|
47531
|
-
return_type:
|
|
48078
|
+
params: expected_params ?? (expected_return ? [] : void 0),
|
|
48079
|
+
return_type: expected_return
|
|
47532
48080
|
};
|
|
47533
48081
|
if (!expected_sig.params && !expected_sig.return_type) return;
|
|
47534
48082
|
const actual_sig = arg_signature(arg, status);
|
|
@@ -49759,10 +50307,18 @@ function check_declaration_node(decl, status) {
|
|
|
49759
50307
|
if (decl.value?.node_type === "access" && decl.value.access.node_type === "access_field") {
|
|
49760
50308
|
const field_type = type_from_value_node(decl.value, status);
|
|
49761
50309
|
if (field_type.name && is_owning_struct_type_requiring_move(field_type, status)) {
|
|
49762
|
-
const
|
|
49763
|
-
|
|
49764
|
-
|
|
49765
|
-
|
|
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");
|
|
49766
50322
|
}
|
|
49767
50323
|
}
|
|
49768
50324
|
if (decl.value?.node_type === "value" && decl.value.is_moved && !decl.swap) {
|
|
@@ -49906,6 +50462,33 @@ function extract_const_value(value) {
|
|
|
49906
50462
|
if (/^[+-]?\d+$/.test(vn.value)) return parseInt(vn.value, 10);
|
|
49907
50463
|
if (/^[+-]?\d+\.\d+$/.test(vn.value)) return parseFloat(vn.value);
|
|
49908
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
|
+
}
|
|
49909
50492
|
//#endregion
|
|
49910
50493
|
//#region ../src/check/utils/check_init_fields.ts
|
|
49911
50494
|
const checked_structs = /* @__PURE__ */ new WeakSet();
|
|
@@ -54390,6 +54973,10 @@ function check_access_node(node, status) {
|
|
|
54390
54973
|
if (af.is_destructure) {
|
|
54391
54974
|
const i = af.destructure_index;
|
|
54392
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
|
+
}
|
|
54393
54980
|
if (af.is_destructure_rename) {
|
|
54394
54981
|
add_error(status, `Cannot rename elements when destructuring an array`, node.start);
|
|
54395
54982
|
return false;
|
|
@@ -55151,6 +55738,55 @@ function check_array_values_node(array, status) {
|
|
|
55151
55738
|
* On success, mutates `array` in-place into a FunctionCallNode that constructs
|
|
55152
55739
|
* the appropriate auto-generated tuple struct.
|
|
55153
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
|
+
}
|
|
55154
55790
|
function check_as_tuple(array, tuple_element_types, status) {
|
|
55155
55791
|
if (array.values.length !== tuple_element_types.length) return check_as_array_or_inferred_tuple(array, status, tuple_element_types);
|
|
55156
55792
|
let result = true;
|
|
@@ -55169,6 +55805,7 @@ function check_as_tuple(array, tuple_element_types, status) {
|
|
|
55169
55805
|
value_types.push(vt);
|
|
55170
55806
|
check_type_and_value_match(tuple_element_types[i], vt, value_from_value_node(value), status, value.start, "tuple");
|
|
55171
55807
|
}
|
|
55808
|
+
if (check_tuple_element_ownership(array, status)) result = false;
|
|
55172
55809
|
const struct_name = tuple_struct_name(tuple_element_types);
|
|
55173
55810
|
get_or_create_tuple_struct(tuple_element_types, status);
|
|
55174
55811
|
const constructor = new FunctionCallNode(array.start, struct_name);
|
|
@@ -55251,12 +55888,19 @@ function check_as_array_or_inferred_tuple(array, status, forced_tuple_types) {
|
|
|
55251
55888
|
if (!has_outer_expected) {
|
|
55252
55889
|
const meaningful = value_types.filter((t) => t.name && t.name !== "null");
|
|
55253
55890
|
const first_meaningful = meaningful[0];
|
|
55254
|
-
|
|
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) {
|
|
55255
55898
|
const elem_types = value_types.map((t, _i) => {
|
|
55256
55899
|
if (!t.name || t.name === "null") return meaningful[0] || new Type("int");
|
|
55257
55900
|
return t;
|
|
55258
55901
|
});
|
|
55259
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;
|
|
55260
55904
|
const struct_name = tuple_struct_name(elem_types);
|
|
55261
55905
|
get_or_create_tuple_struct(elem_types, status);
|
|
55262
55906
|
const constructor = new FunctionCallNode(array.start, struct_name);
|
|
@@ -55753,7 +56397,7 @@ function list_element_type(t, status) {
|
|
|
55753
56397
|
return struct.source_type_args?.[0];
|
|
55754
56398
|
}
|
|
55755
56399
|
function check_for_loop_node(for_loop, status) {
|
|
55756
|
-
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")) {
|
|
55757
56401
|
const list_type = type_from_value_node(for_loop.list, status);
|
|
55758
56402
|
const enumerable = list_type.name ? has_trait(list_type.name, "Enumerable", status) : false;
|
|
55759
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"));
|
|
@@ -55779,6 +56423,16 @@ function check_for_loop_node(for_loop, status) {
|
|
|
55779
56423
|
check_node(for_loop.list, for_status);
|
|
55780
56424
|
const list_type = type_from_value_node(for_loop.list, for_status);
|
|
55781
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
|
+
}
|
|
55782
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);
|
|
55783
56437
|
if (for_loop.item_is_ref && list_type.is_array && for_loop.list.node_type === "value") {
|
|
55784
56438
|
const list_name = for_loop.list.value;
|
|
@@ -55831,6 +56485,23 @@ function check_for_loop_node(for_loop, status) {
|
|
|
55831
56485
|
* break/continue so mutations persist on all exit paths. (The write-back is
|
|
55832
56486
|
* skipped on `return`, matching Rust's copy semantics.)
|
|
55833
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
|
+
}
|
|
55834
56505
|
function desugar_array_for_loop(for_loop, array_type, elem_type) {
|
|
55835
56506
|
const list = for_loop.list;
|
|
55836
56507
|
const start = list.start;
|
|
@@ -57076,6 +57747,7 @@ function check(root, options) {
|
|
|
57076
57747
|
allow_internal: options?.allow_internal,
|
|
57077
57748
|
library_boundary: options?.library_boundary
|
|
57078
57749
|
};
|
|
57750
|
+
stamp_literal_element_moves(root);
|
|
57079
57751
|
check_node(root, status);
|
|
57080
57752
|
unwrap_unsafe_blocks(root);
|
|
57081
57753
|
if (!status.errors.length) emit_warnings(root, status);
|
|
@@ -57883,6 +58555,10 @@ function looks_like_destructuring(status, start_idx) {
|
|
|
57883
58555
|
* - bare name: `a` — positional (tuples/arrays) or same-named field (structs)
|
|
57884
58556
|
* - rename: `field = name` — struct/class field accessed as `field`,
|
|
57885
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.
|
|
57886
58562
|
*/
|
|
57887
58563
|
function parse_destructuring(visibility, declaration, start, status) {
|
|
57888
58564
|
expect("[", status);
|
|
@@ -57893,8 +58569,17 @@ function parse_destructuring(visibility, declaration, start, status) {
|
|
|
57893
58569
|
let field = first;
|
|
57894
58570
|
let name = first;
|
|
57895
58571
|
let rename = false;
|
|
57896
|
-
|
|
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) === "=") {
|
|
57897
58578
|
accept("=", status);
|
|
58579
|
+
if (peek_current(status) === "move") {
|
|
58580
|
+
consume(status);
|
|
58581
|
+
move = true;
|
|
58582
|
+
}
|
|
57898
58583
|
name = consume_name(status);
|
|
57899
58584
|
field = first;
|
|
57900
58585
|
rename = true;
|
|
@@ -57902,7 +58587,8 @@ function parse_destructuring(visibility, declaration, start, status) {
|
|
|
57902
58587
|
bindings.push({
|
|
57903
58588
|
name,
|
|
57904
58589
|
field,
|
|
57905
|
-
rename
|
|
58590
|
+
rename,
|
|
58591
|
+
move
|
|
57906
58592
|
});
|
|
57907
58593
|
index++;
|
|
57908
58594
|
if (!accept(",", status)) break;
|
|
@@ -57932,7 +58618,7 @@ function parse_destructuring(visibility, declaration, start, status) {
|
|
|
57932
58618
|
const temp_counter = status.__tuple_destructure_counter || 0;
|
|
57933
58619
|
status.__tuple_destructure_counter = temp_counter + 1;
|
|
57934
58620
|
const temp_name = `_tuple_dst_${temp_counter}`;
|
|
57935
|
-
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));
|
|
57936
58622
|
base_node = new ValueNode(start, temp_name);
|
|
57937
58623
|
}
|
|
57938
58624
|
for (let i = 0; i < bindings.length; i++) {
|
|
@@ -57942,7 +58628,9 @@ function parse_destructuring(visibility, declaration, start, status) {
|
|
|
57942
58628
|
access_field.is_destructure = true;
|
|
57943
58629
|
access_field.destructure_index = i;
|
|
57944
58630
|
access_field.is_destructure_rename = binding.rename;
|
|
58631
|
+
access_field.destructure_move = binding.move;
|
|
57945
58632
|
const access = new AccessNode(start, base_node, access_field);
|
|
58633
|
+
access.is_moved = binding.move;
|
|
57946
58634
|
push_to_parent(new DeclarationNode(start, visibility, declaration, binding.name, new Type(""), access));
|
|
57947
58635
|
}
|
|
57948
58636
|
}
|