nomen-lang 0.0.13 → 0.0.15
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 +1226 -524
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -2,8 +2,12 @@
|
|
|
2
2
|
import { execFileSync, execSync } from "node:child_process";
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
import chokidar from "chokidar";
|
|
6
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
+
//#region node_modules/chokidar/index.mjs
|
|
7
|
+
var chokidar_default = { watch() {
|
|
8
|
+
throw new Error("chokidar stub: watch mode unavailable");
|
|
9
|
+
} };
|
|
10
|
+
//#endregion
|
|
7
11
|
//#region ../src/nodes/BaseNode.ts
|
|
8
12
|
/**
|
|
9
13
|
* The base node type which all nodes extend
|
|
@@ -480,30 +484,6 @@ function resolve_mono_type(type, table) {
|
|
|
480
484
|
return resolved;
|
|
481
485
|
}
|
|
482
486
|
//#endregion
|
|
483
|
-
//#region ../src/built_in_types.ts
|
|
484
|
-
const built_in_types = [
|
|
485
|
-
"bool",
|
|
486
|
-
"int",
|
|
487
|
-
"uint",
|
|
488
|
-
"int8",
|
|
489
|
-
"uint8",
|
|
490
|
-
"int16",
|
|
491
|
-
"uint16",
|
|
492
|
-
"int32",
|
|
493
|
-
"uint32",
|
|
494
|
-
"int64",
|
|
495
|
-
"uint64",
|
|
496
|
-
"float",
|
|
497
|
-
"ufloat",
|
|
498
|
-
"float32",
|
|
499
|
-
"ufloat32",
|
|
500
|
-
"float64",
|
|
501
|
-
"ufloat64",
|
|
502
|
-
"char",
|
|
503
|
-
"string",
|
|
504
|
-
"func"
|
|
505
|
-
];
|
|
506
|
-
//#endregion
|
|
507
487
|
//#region ../src/nodes/BranchNode.ts
|
|
508
488
|
/**
|
|
509
489
|
* A branch such as the result of an IfElseNode, or the arm of a SwitchNode
|
|
@@ -639,6 +619,30 @@ var FunctionNode = class extends BaseNode {
|
|
|
639
619
|
}
|
|
640
620
|
};
|
|
641
621
|
//#endregion
|
|
622
|
+
//#region ../src/built_in_types.ts
|
|
623
|
+
const built_in_types = [
|
|
624
|
+
"bool",
|
|
625
|
+
"int",
|
|
626
|
+
"uint",
|
|
627
|
+
"int8",
|
|
628
|
+
"uint8",
|
|
629
|
+
"int16",
|
|
630
|
+
"uint16",
|
|
631
|
+
"int32",
|
|
632
|
+
"uint32",
|
|
633
|
+
"int64",
|
|
634
|
+
"uint64",
|
|
635
|
+
"float",
|
|
636
|
+
"ufloat",
|
|
637
|
+
"float32",
|
|
638
|
+
"ufloat32",
|
|
639
|
+
"float64",
|
|
640
|
+
"ufloat64",
|
|
641
|
+
"char",
|
|
642
|
+
"string",
|
|
643
|
+
"func"
|
|
644
|
+
];
|
|
645
|
+
//#endregion
|
|
642
646
|
//#region ../src/nodes/DeclarationNode.ts
|
|
643
647
|
var DeclarationNode = class extends BaseNode {
|
|
644
648
|
visibility;
|
|
@@ -736,6 +740,82 @@ var StructNode = class extends BaseNode {
|
|
|
736
740
|
}
|
|
737
741
|
};
|
|
738
742
|
//#endregion
|
|
743
|
+
//#region ../src/build_common/scan_self_string_writes.ts
|
|
744
|
+
/**
|
|
745
|
+
* The plain (owned, non-ref, non-array) `string` fields of a VALUE struct that
|
|
746
|
+
* a method may overwrite through `self` — direct `self.<field> = ...` writes
|
|
747
|
+
* plus writes made by same-struct methods the method calls on `self`
|
|
748
|
+
* (transitively). Nested function/struct declarations inside the body are
|
|
749
|
+
* boundaries and are not descended into (they are separate functions).
|
|
750
|
+
*/
|
|
751
|
+
function scan_self_string_field_writes(struct, method) {
|
|
752
|
+
const string_fields = new Set(struct.fields.filter((f) => f.type.name === "string" && !f.type.is_ref && !f.type.is_array).map((f) => f.name));
|
|
753
|
+
const written = /* @__PURE__ */ new Set();
|
|
754
|
+
if (!string_fields.size) return written;
|
|
755
|
+
const visited = /* @__PURE__ */ new Set();
|
|
756
|
+
const scan_method = (func) => {
|
|
757
|
+
if (visited.has(func.name)) return;
|
|
758
|
+
visited.add(func.name);
|
|
759
|
+
walk$2(func.statements ?? [], (n) => {
|
|
760
|
+
if (n.node_type === "assign") {
|
|
761
|
+
const lhs = n.left_value;
|
|
762
|
+
if (lhs?.node_type !== "access") return;
|
|
763
|
+
const access = lhs;
|
|
764
|
+
if (access.access.node_type !== "access_field") return;
|
|
765
|
+
const target = access.target;
|
|
766
|
+
const field = access.access.name ?? "";
|
|
767
|
+
if (target?.node_type === "value" && target.value === "self") {
|
|
768
|
+
if (string_fields.has(field)) written.add(field);
|
|
769
|
+
}
|
|
770
|
+
} else if (n.node_type === "access") {
|
|
771
|
+
const access = n;
|
|
772
|
+
if (access.access.node_type !== "access_func") return;
|
|
773
|
+
const target = access.target;
|
|
774
|
+
if (target?.node_type === "value" && target.value === "self") {
|
|
775
|
+
const callee = struct.functions.find((f) => f.name === access.access.name);
|
|
776
|
+
if (callee) scan_method(callee);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
});
|
|
780
|
+
};
|
|
781
|
+
scan_method(method);
|
|
782
|
+
return written;
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Drop a receiver's heap_string_fields records for the fields a value-struct
|
|
786
|
+
* method may have overwritten through `self`. The method's writes go through
|
|
787
|
+
* to the caller's storage, and the method cannot know whether the displaced
|
|
788
|
+
* values were heap-owned — that knowledge lives in the CALLER's records. A
|
|
789
|
+
* surviving record could free a non-heap value at scope exit (invalid free),
|
|
790
|
+
* so the records are dropped WITHOUT emitting frees. Conservative: a heap
|
|
791
|
+
* value the method displaced or wrote leaks instead of being freed — never a
|
|
792
|
+
* double-free.
|
|
793
|
+
*/
|
|
794
|
+
function drop_self_written_string_field_records(status, receiver_name, fields) {
|
|
795
|
+
if (!status.heap_string_fields?.size || !fields.size) return;
|
|
796
|
+
for (const field of fields) status.heap_string_fields.delete(`${receiver_name}.${field}`);
|
|
797
|
+
}
|
|
798
|
+
/** Visit every AST node reachable from `value` — through arrays AND
|
|
799
|
+
* single-node properties (an `if` node's branch blocks are node objects, not
|
|
800
|
+
* statement arrays) — skipping `parent`/`scope` back-references and NOT
|
|
801
|
+
* descending INTO nested `func`/`struct`/`trait` declarations (a nested
|
|
802
|
+
* function's body is a separate function, not part of this one's writes). */
|
|
803
|
+
function walk$2(value, cb) {
|
|
804
|
+
if (!value || typeof value !== "object") return;
|
|
805
|
+
if (Array.isArray(value)) {
|
|
806
|
+
for (const item of value) walk$2(item, cb);
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
const n = value;
|
|
810
|
+
const is_boundary = n.node_type === "func" || n.node_type === "struct" || n.node_type === "trait";
|
|
811
|
+
if (typeof n.node_type === "string") cb(n);
|
|
812
|
+
if (is_boundary) return;
|
|
813
|
+
for (const key of Object.keys(value)) {
|
|
814
|
+
if (key === "parent" || key === "scope" || key === "node_type") continue;
|
|
815
|
+
walk$2(value[key], cb);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
//#endregion
|
|
739
819
|
//#region ../src/check/utils/function_overload.ts
|
|
740
820
|
function find_function_by_params(functions, name, arg_types) {
|
|
741
821
|
const candidates = functions.filter((f) => f.name === name);
|
|
@@ -1368,6 +1448,50 @@ function array_struct_name(type, status) {
|
|
|
1368
1448
|
return status.structs.find((s) => s.name === mono && !s.is_generic) ? mono : void 0;
|
|
1369
1449
|
}
|
|
1370
1450
|
//#endregion
|
|
1451
|
+
//#region ../src/build_common/scan_moved_param_consumed.ts
|
|
1452
|
+
/**
|
|
1453
|
+
* Whether a `mov` class parameter's ownership escapes the function body —
|
|
1454
|
+
* i.e. it is passed (as an argument or receiver) into some call/constructor
|
|
1455
|
+
* whose result may outlive the function (stored into a returned
|
|
1456
|
+
* container/struct), or it is a bare value used as an argument. In those cases
|
|
1457
|
+
* the callee must NOT destroy it at exit (it would double-free / leave a
|
|
1458
|
+
* dangling pointer in the escaping value). A bare reference that is only read
|
|
1459
|
+
* (e.g. field access `x.value` or interpolation) does NOT consume it.
|
|
1460
|
+
*
|
|
1461
|
+
* Shared by the C backend's function epilogue and the aarch64 function /
|
|
1462
|
+
* method mov-param reclaims so both agree on when a mov'd param is reclaimed.
|
|
1463
|
+
*/
|
|
1464
|
+
function moved_param_is_consumed(root, name) {
|
|
1465
|
+
let consumed = false;
|
|
1466
|
+
const refs_name = (n) => !!n && n.node_type === "value" && n.value === name;
|
|
1467
|
+
const walk = (n) => {
|
|
1468
|
+
if (!n || typeof n !== "object" || consumed) return;
|
|
1469
|
+
const node = n;
|
|
1470
|
+
if (node.node_type === "func_call") {
|
|
1471
|
+
for (const p of node.params ?? []) if (refs_name(p)) consumed = true;
|
|
1472
|
+
}
|
|
1473
|
+
if (node.node_type === "access") {
|
|
1474
|
+
const access = node.access;
|
|
1475
|
+
if (access?.node_type === "access_func" && refs_name(node.target)) consumed = true;
|
|
1476
|
+
for (const p of access?.params ?? []) if (refs_name(p)) consumed = true;
|
|
1477
|
+
}
|
|
1478
|
+
if (node.node_type === "array") {
|
|
1479
|
+
for (const v of node.values ?? []) if (refs_name(v)) consumed = true;
|
|
1480
|
+
}
|
|
1481
|
+
if (node.node_type === "return" && refs_name(node.value)) consumed = true;
|
|
1482
|
+
if (node.node_type === "assign" && refs_name(node.right_value)) consumed = true;
|
|
1483
|
+
if (node.node_type === "declare" && refs_name(node.value)) consumed = true;
|
|
1484
|
+
for (const key of Object.keys(node)) {
|
|
1485
|
+
if (key === "node_type") continue;
|
|
1486
|
+
const v = node[key];
|
|
1487
|
+
if (Array.isArray(v)) for (const item of v) walk(item);
|
|
1488
|
+
else if (v && typeof v === "object") walk(v);
|
|
1489
|
+
}
|
|
1490
|
+
};
|
|
1491
|
+
for (const stmt of root.statements ?? []) walk(stmt);
|
|
1492
|
+
return consumed;
|
|
1493
|
+
}
|
|
1494
|
+
//#endregion
|
|
1371
1495
|
//#region ../src/build_aarch64/utils/aarch64_size.ts
|
|
1372
1496
|
function aarch64_size(type) {
|
|
1373
1497
|
switch (type) {
|
|
@@ -1684,8 +1808,10 @@ function emit_var_address(status, reg, name) {
|
|
|
1684
1808
|
if (offset !== void 0) status.code += `str ${alloc_reg}, [x29, #${offset}]\n`;
|
|
1685
1809
|
}
|
|
1686
1810
|
const offset = status.stack_offsets?.get(name);
|
|
1687
|
-
if (offset !== void 0)
|
|
1688
|
-
|
|
1811
|
+
if (offset !== void 0) {
|
|
1812
|
+
if (status.function_struct_param_slots?.has(name)) status.code += `ldr ${reg}, [x29, #${offset}]\n`;
|
|
1813
|
+
else status.code += `add ${reg}, x29, #${offset}\n`;
|
|
1814
|
+
} else {
|
|
1689
1815
|
const param_reg = status.function_param_regs?.get(name);
|
|
1690
1816
|
if (param_reg) status.code += `mov ${reg}, ${param_reg}\n`;
|
|
1691
1817
|
else status.code += `adr ${reg}, ${name}\n`;
|
|
@@ -1752,6 +1878,78 @@ function emit_var_store(status, reg, name, size) {
|
|
|
1752
1878
|
}
|
|
1753
1879
|
//#endregion
|
|
1754
1880
|
//#region ../src/build_aarch64/utils/auto_destroy.ts
|
|
1881
|
+
/**
|
|
1882
|
+
* Whether a declaration's initializer is a non-`mov` FIELD ACCESS — a shallow
|
|
1883
|
+
* struct borrow (`diff.changes`, the checker-hoisted `_param_N` temp for a
|
|
1884
|
+
* struct call arg). The struct bytes are copied but any embedded buffer data
|
|
1885
|
+
* belongs to the owner, so the declaration must NOT be destroyed at scope
|
|
1886
|
+
* exit / return (mirrors the C backend's is_destructured_field_access).
|
|
1887
|
+
*/
|
|
1888
|
+
function is_field_struct_borrow(decl) {
|
|
1889
|
+
if (!decl.value || typeof decl.value !== "object") return false;
|
|
1890
|
+
const value = decl.value;
|
|
1891
|
+
return value.node_type === "access" && value.access.node_type === "access_field" && !value.is_moved;
|
|
1892
|
+
}
|
|
1893
|
+
/**
|
|
1894
|
+
* Record that a VALUE-struct local's `string` field now holds a heap-owned
|
|
1895
|
+
* value ("var.field"). See BuildStatus.heap_string_fields.
|
|
1896
|
+
*/
|
|
1897
|
+
function record_heap_string_field(status, var_name, field) {
|
|
1898
|
+
if (!status.heap_string_fields) status.heap_string_fields = /* @__PURE__ */ new Set();
|
|
1899
|
+
status.heap_string_fields.add(`${var_name}.${field}`);
|
|
1900
|
+
}
|
|
1901
|
+
/**
|
|
1902
|
+
* Drop a local's heap-string-field records — used when the struct's bytes
|
|
1903
|
+
* (and thus its string pointers) transfer to the caller, e.g. `return u`.
|
|
1904
|
+
*/
|
|
1905
|
+
function clear_heap_string_fields(status, var_name) {
|
|
1906
|
+
if (!status.heap_string_fields) return;
|
|
1907
|
+
const prefix = `${var_name}.`;
|
|
1908
|
+
for (const key of Array.from(status.heap_string_fields)) if (key.startsWith(prefix)) status.heap_string_fields.delete(key);
|
|
1909
|
+
}
|
|
1910
|
+
/**
|
|
1911
|
+
* Free every heap-owned string field recorded for `decl_name` (a VALUE-struct
|
|
1912
|
+
* local) and drop the records. Class locals are never recorded — their string
|
|
1913
|
+
* fields are unconditionally heap and freed by the destroy path. Called from
|
|
1914
|
+
* emit_destroy_for_decl and directly from cleanup loops that skip moved
|
|
1915
|
+
* declarations before reaching it.
|
|
1916
|
+
*/
|
|
1917
|
+
function release_heap_string_fields(status, decl_name, decl_type_name) {
|
|
1918
|
+
if (!status.heap_string_fields?.size) return;
|
|
1919
|
+
const prefix = `${decl_name}.`;
|
|
1920
|
+
const fields = Array.from(status.heap_string_fields).filter((k) => k.startsWith(prefix)).map((k) => k.slice(prefix.length));
|
|
1921
|
+
if (!fields.length) return;
|
|
1922
|
+
for (const field of fields) {
|
|
1923
|
+
const offset = get_field_offset(decl_type_name, field, status);
|
|
1924
|
+
emit_var_address(status, "x0", decl_name);
|
|
1925
|
+
status.code += `ldr x0, [x0, #${offset}]\n`;
|
|
1926
|
+
emit_free(status);
|
|
1927
|
+
status.heap_string_fields.delete(`${decl_name}.${field}`);
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
/**
|
|
1931
|
+
* Swap in a fresh scoped_declarations frame for a nested scope (if/while/
|
|
1932
|
+
* for/switch/match body), pushing the enclosing array onto
|
|
1933
|
+
* outer_scope_declarations so return-path cleanup can still reach it.
|
|
1934
|
+
* Pair with exit_scope_frame.
|
|
1935
|
+
*/
|
|
1936
|
+
function enter_scope_frame(status) {
|
|
1937
|
+
const old = status.scoped_declarations ?? [];
|
|
1938
|
+
if (!status.outer_scope_declarations) status.outer_scope_declarations = [];
|
|
1939
|
+
status.outer_scope_declarations.push(old);
|
|
1940
|
+
status.scoped_declarations = [];
|
|
1941
|
+
return old;
|
|
1942
|
+
}
|
|
1943
|
+
/** Restore the enclosing scoped_declarations frame (enter_scope_frame's pair). */
|
|
1944
|
+
function exit_scope_frame(status, old) {
|
|
1945
|
+
status.outer_scope_declarations?.pop();
|
|
1946
|
+
status.scoped_declarations = old;
|
|
1947
|
+
}
|
|
1948
|
+
/** Every declaration frame a `return` must clean: enclosing scopes first,
|
|
1949
|
+
* the current (innermost) frame last — matching fall-through cleanup order. */
|
|
1950
|
+
function all_scope_frames(status) {
|
|
1951
|
+
return [...status.outer_scope_declarations ?? [], status.scoped_declarations ?? []];
|
|
1952
|
+
}
|
|
1755
1953
|
function mark_heap_string(status, name) {
|
|
1756
1954
|
if (!status.heap_strings) status.heap_strings = /* @__PURE__ */ new Set();
|
|
1757
1955
|
status.heap_strings.add(name);
|
|
@@ -1898,7 +2096,7 @@ function emit_field_destroys_from_slot(status, struct_type, base_offset) {
|
|
|
1898
2096
|
const field_size = get_type_size(field.type, status);
|
|
1899
2097
|
emit_nested_field_destroys_from_slot(status, field_struct, base_offset + offset);
|
|
1900
2098
|
offset += field_size;
|
|
1901
|
-
} else if (field.type.name === "string" && !field.type.is_array && !field.type.is_ref
|
|
2099
|
+
} else if (field.type.name === "string" && !field.type.is_array && !field.type.is_ref) {
|
|
1902
2100
|
status.code += `ldr x0, [x29, #${base_offset}]\n`;
|
|
1903
2101
|
status.code += `ldr x0, [x0, #${offset}]\n`;
|
|
1904
2102
|
emit_free(status);
|
|
@@ -1949,7 +2147,9 @@ function is_struct_type$4(type_name, status) {
|
|
|
1949
2147
|
return status.structs.find((s) => s.name === type_name && !s.is_simple_type);
|
|
1950
2148
|
}
|
|
1951
2149
|
function emit_destroy_for_decl(status, decl_name, decl_type_name, addr_offset, type_args, is_nullable) {
|
|
1952
|
-
|
|
2150
|
+
const moved = status.moved ?? /* @__PURE__ */ new Set();
|
|
2151
|
+
release_heap_string_fields(status, decl_name, decl_type_name);
|
|
2152
|
+
if (moved.has(decl_name)) return;
|
|
1953
2153
|
if (status.heap_strings?.has(decl_name)) {
|
|
1954
2154
|
if (addr_offset !== void 0) status.code += `add x0, x0, #${addr_offset}\n`;
|
|
1955
2155
|
else emit_var_load(status, "x0", decl_name, 8);
|
|
@@ -1966,7 +2166,7 @@ function emit_destroy_for_decl(status, decl_name, decl_type_name, addr_offset, t
|
|
|
1966
2166
|
skip_label = `.Lskip_nd_${status.label_counter = (status.label_counter ?? 0) + 1}`;
|
|
1967
2167
|
status.code += `cbz x0, ${skip_label}\n`;
|
|
1968
2168
|
}
|
|
1969
|
-
if (has_destroy(struct_type)) {
|
|
2169
|
+
if (has_destroy(struct_type) || struct_type.is_class) {
|
|
1970
2170
|
if (struct_type.is_class) {
|
|
1971
2171
|
if (addr_offset !== void 0) status.code += `ldr x0, [x0, #${addr_offset}]\n`;
|
|
1972
2172
|
else emit_var_load(status, "x0", decl_name, 8);
|
|
@@ -1980,7 +2180,6 @@ function emit_destroy_for_decl(status, decl_name, decl_type_name, addr_offset, t
|
|
|
1980
2180
|
else emit_var_load(status, "x0", decl_name, 8);
|
|
1981
2181
|
emit_free(status);
|
|
1982
2182
|
}
|
|
1983
|
-
emit_field_destroys(status, struct_type, decl_name, addr_offset, true);
|
|
1984
2183
|
} else if (struct_needs_destroy(struct_type, status)) emit_field_destroys(status, struct_type, decl_name, addr_offset, void 0, false);
|
|
1985
2184
|
if (skip_label) status.code += `${skip_label}:\n`;
|
|
1986
2185
|
}
|
|
@@ -2016,7 +2215,7 @@ function emit_field_destroys(status, struct_type, decl_name, base_offset, is_cla
|
|
|
2016
2215
|
}
|
|
2017
2216
|
const field_size = get_type_size(field.type, status);
|
|
2018
2217
|
offset += field_size;
|
|
2019
|
-
} else if (free_strings && field.type.name === "string" && !field.type.is_array && !field.type.is_ref
|
|
2218
|
+
} else if (free_strings && field.type.name === "string" && !field.type.is_array && !field.type.is_ref) {
|
|
2020
2219
|
const actual_offset = base_offset !== void 0 ? base_offset + offset : offset;
|
|
2021
2220
|
if (decl_name) emit_base_ptr(status, decl_name, is_class_parent);
|
|
2022
2221
|
status.code += `ldr x0, [x0, #${actual_offset}]\n`;
|
|
@@ -2101,6 +2300,7 @@ function emit_destroy_for_scope(status, declarations_before) {
|
|
|
2101
2300
|
if (current_scope?.heap_slots.length) {
|
|
2102
2301
|
for (let i = declarations_before; i < status.scoped_declarations.length; i++) {
|
|
2103
2302
|
const decl = status.scoped_declarations[i];
|
|
2303
|
+
release_heap_string_fields(status, decl.name, decl.type.name);
|
|
2104
2304
|
if (moved.has(decl.name)) continue;
|
|
2105
2305
|
if (status.heap_string_arrays?.has(decl.name)) {
|
|
2106
2306
|
const len = status.heap_string_arrays.get(decl.name);
|
|
@@ -2138,6 +2338,7 @@ function emit_destroy_for_scope(status, declarations_before) {
|
|
|
2138
2338
|
emit_free(status);
|
|
2139
2339
|
continue;
|
|
2140
2340
|
}
|
|
2341
|
+
if (is_field_struct_borrow(decl)) continue;
|
|
2141
2342
|
const resolved_decl = resolve_decl_struct(decl, status);
|
|
2142
2343
|
if (!resolved_decl) continue;
|
|
2143
2344
|
const struct_type = resolved_decl.struct_type;
|
|
@@ -2152,6 +2353,7 @@ function emit_destroy_for_scope(status, declarations_before) {
|
|
|
2152
2353
|
}
|
|
2153
2354
|
for (let i = declarations_before; i < status.scoped_declarations.length; i++) {
|
|
2154
2355
|
const decl = status.scoped_declarations[i];
|
|
2356
|
+
release_heap_string_fields(status, decl.name, decl.type.name);
|
|
2155
2357
|
if (moved.has(decl.name)) continue;
|
|
2156
2358
|
if (status.heap_string_arrays?.has(decl.name)) {
|
|
2157
2359
|
const len = status.heap_string_arrays.get(decl.name);
|
|
@@ -2194,6 +2396,7 @@ function emit_destroy_for_scope(status, declarations_before) {
|
|
|
2194
2396
|
emit_free(status);
|
|
2195
2397
|
continue;
|
|
2196
2398
|
}
|
|
2399
|
+
if (is_field_struct_borrow(decl)) continue;
|
|
2197
2400
|
const resolved_decl = resolve_decl_struct(decl, status);
|
|
2198
2401
|
if (!resolved_decl) continue;
|
|
2199
2402
|
const struct_type = resolved_decl.struct_type;
|
|
@@ -2259,13 +2462,13 @@ function consolidate_temp_anchors$1(status, call_node, result_type_name) {
|
|
|
2259
2462
|
status.moved.add(pname);
|
|
2260
2463
|
}
|
|
2261
2464
|
}
|
|
2262
|
-
function mark_moved_if_struct(value, status) {
|
|
2465
|
+
function mark_moved_if_struct(value, status, opts) {
|
|
2263
2466
|
if (value?.node_type !== "value") return;
|
|
2264
2467
|
const var_name = value.value;
|
|
2265
2468
|
let var_type = value.type;
|
|
2266
|
-
if (!var_type?.name) var_type = status.
|
|
2469
|
+
if (!var_type?.name) var_type = all_scope_frames(status).flat().find((d) => d.name === var_name)?.type;
|
|
2267
2470
|
if (!var_type) return;
|
|
2268
|
-
const is_local = status.
|
|
2471
|
+
const is_local = all_scope_frames(status).some((frame) => frame.some((d) => d.name === var_name));
|
|
2269
2472
|
const has_anchor = find_anchor_slot(status, var_name) !== void 0;
|
|
2270
2473
|
const is_class_param = !!status.moved_class_params?.has(var_name) || !!status.function_param_regs?.has(var_name) && is_struct_type$4(var_type.name, status);
|
|
2271
2474
|
if (!is_local && !has_anchor && !is_class_param) return;
|
|
@@ -2273,7 +2476,7 @@ function mark_moved_if_struct(value, status) {
|
|
|
2273
2476
|
if (!status.moved) status.moved = /* @__PURE__ */ new Set();
|
|
2274
2477
|
status.moved.add(var_name);
|
|
2275
2478
|
}
|
|
2276
|
-
if (status.heap_strings?.has(var_name)) {
|
|
2479
|
+
if (opts?.for_return && status.heap_strings?.has(var_name)) {
|
|
2277
2480
|
if (!status.moved) status.moved = /* @__PURE__ */ new Set();
|
|
2278
2481
|
status.moved.add(var_name);
|
|
2279
2482
|
}
|
|
@@ -2318,10 +2521,10 @@ var SwitchNode = class extends BaseNode {
|
|
|
2318
2521
|
//#region ../src/build_aarch64/utils/scan_force_heap_strings.ts
|
|
2319
2522
|
function scan_force_heap_strings(statements) {
|
|
2320
2523
|
const result = /* @__PURE__ */ new Set();
|
|
2321
|
-
walk(statements, result);
|
|
2524
|
+
walk$1(statements, result);
|
|
2322
2525
|
return result;
|
|
2323
2526
|
}
|
|
2324
|
-
function walk(statements, result) {
|
|
2527
|
+
function walk$1(statements, result) {
|
|
2325
2528
|
if (!statements) return;
|
|
2326
2529
|
for (const stmt of statements) visit(stmt, result);
|
|
2327
2530
|
}
|
|
@@ -2335,18 +2538,18 @@ function visit(node, result) {
|
|
|
2335
2538
|
}
|
|
2336
2539
|
case "while":
|
|
2337
2540
|
case "for":
|
|
2338
|
-
walk(node.statements, result);
|
|
2541
|
+
walk$1(node.statements, result);
|
|
2339
2542
|
break;
|
|
2340
2543
|
case "if": {
|
|
2341
2544
|
const n = node;
|
|
2342
|
-
walk(n.if_branch?.statements, result);
|
|
2343
|
-
walk(n.else_branch?.statements, result);
|
|
2545
|
+
walk$1(n.if_branch?.statements, result);
|
|
2546
|
+
walk$1(n.else_branch?.statements, result);
|
|
2344
2547
|
break;
|
|
2345
2548
|
}
|
|
2346
2549
|
case "switch": {
|
|
2347
2550
|
const n = node;
|
|
2348
|
-
for (const c of n.cases) walk(c.branch?.statements, result);
|
|
2349
|
-
walk(n.else_branch?.statements, result);
|
|
2551
|
+
for (const c of n.cases) walk$1(c.branch?.statements, result);
|
|
2552
|
+
walk$1(n.else_branch?.statements, result);
|
|
2350
2553
|
break;
|
|
2351
2554
|
}
|
|
2352
2555
|
}
|
|
@@ -2594,11 +2797,13 @@ function build_function_node$1(node, status) {
|
|
|
2594
2797
|
const old_function_array_params = status.function_array_params;
|
|
2595
2798
|
const old_function_ref_params = status.function_ref_params;
|
|
2596
2799
|
const old_ref_class_slots = status.ref_class_slots;
|
|
2800
|
+
const old_struct_param_slots = status.function_struct_param_slots;
|
|
2597
2801
|
status.function_param_regs = /* @__PURE__ */ new Map();
|
|
2598
2802
|
status.function_param_vars = /* @__PURE__ */ new Set();
|
|
2599
2803
|
status.function_array_params = /* @__PURE__ */ new Set();
|
|
2600
2804
|
status.function_ref_params = /* @__PURE__ */ new Set();
|
|
2601
2805
|
status.ref_class_slots = /* @__PURE__ */ new Map();
|
|
2806
|
+
status.function_struct_param_slots = /* @__PURE__ */ new Set();
|
|
2602
2807
|
const old_variadic_params_aarch64 = status.function_variadic_params;
|
|
2603
2808
|
status.function_variadic_params = /* @__PURE__ */ new Set();
|
|
2604
2809
|
status.moved_class_params = /* @__PURE__ */ new Map();
|
|
@@ -2637,6 +2842,9 @@ function build_function_node$1(node, status) {
|
|
|
2637
2842
|
const size = is_ref ? 8 : aarch64_size(param.type.name);
|
|
2638
2843
|
const offset = allocate_stack_space(status, size, is_ref ? 8 : size);
|
|
2639
2844
|
status.stack_offsets.set(param.name, offset);
|
|
2845
|
+
if (!is_ref && !param.type.is_array && !param.is_variadic) {
|
|
2846
|
+
if (!!status.structs.find((s) => s.name === param.type.name && !s.is_simple_type) || !!status.traits.find((t) => t.name === param.type.name) || !!status.enums.find((e) => e.name === param.type.name && e.has_associated_data)) status.function_struct_param_slots.add(param.name);
|
|
2847
|
+
}
|
|
2640
2848
|
if (param_idx < 8) {
|
|
2641
2849
|
const reg = param_regs[param_idx];
|
|
2642
2850
|
if (size === 1) status.code += `strb ${reg.replace("x", "w")}, [x29, #${offset}]\n`;
|
|
@@ -2726,13 +2934,15 @@ function build_function_node$1(node, status) {
|
|
|
2726
2934
|
const moved_set = status.moved;
|
|
2727
2935
|
if (moved_param_save_slots.size > 0 && node.name !== "main") {
|
|
2728
2936
|
const need_guard = return_is_class;
|
|
2937
|
+
const need_save = !!node.return_type?.name;
|
|
2729
2938
|
let return_save;
|
|
2730
|
-
if (need_guard) {
|
|
2939
|
+
if (need_guard || need_save) {
|
|
2731
2940
|
return_save = allocate_stack_space(status, 8);
|
|
2732
2941
|
status.code += `str x0, [x29, #${return_save}]\n`;
|
|
2733
2942
|
}
|
|
2734
2943
|
for (const [name, info] of moved_param_save_slots) {
|
|
2735
2944
|
if (moved_set?.has(name)) continue;
|
|
2945
|
+
if (moved_param_is_consumed(node, name)) continue;
|
|
2736
2946
|
if (need_guard) {
|
|
2737
2947
|
status.code += `ldr x0, [x29, #${info.offset}]\n`;
|
|
2738
2948
|
status.code += `ldr x1, [x29, #${return_save}]\n`;
|
|
@@ -2744,7 +2954,7 @@ function build_function_node$1(node, status) {
|
|
|
2744
2954
|
emit_free(status);
|
|
2745
2955
|
if (need_guard) status.code += `${keep_prefix}_${name}:\n`;
|
|
2746
2956
|
}
|
|
2747
|
-
if (need_guard) status.code += `ldr x0, [x29, #${return_save}]\n`;
|
|
2957
|
+
if (need_guard || need_save) status.code += `ldr x0, [x29, #${return_save}]\n`;
|
|
2748
2958
|
}
|
|
2749
2959
|
const total_stack = Math.ceil((status.stack_size || 0) / 16) * 16;
|
|
2750
2960
|
status.code = status.code.replace(`sub sp, sp, #${stack_placeholder}`, total_stack > 0 ? `sub sp, sp, #${total_stack}` : `// no stack needed`);
|
|
@@ -2787,6 +2997,7 @@ function build_function_node$1(node, status) {
|
|
|
2787
2997
|
status.function_array_params = old_function_array_params;
|
|
2788
2998
|
status.function_ref_params = old_function_ref_params;
|
|
2789
2999
|
status.ref_class_slots = old_ref_class_slots;
|
|
3000
|
+
status.function_struct_param_slots = old_struct_param_slots;
|
|
2790
3001
|
status.function_variadic_params = old_variadic_params_aarch64;
|
|
2791
3002
|
status.function_return_label = old_return_label;
|
|
2792
3003
|
status.struct_return_buffer = void 0;
|
|
@@ -2965,6 +3176,30 @@ function is_mutable_param(name, status) {
|
|
|
2965
3176
|
return !!(status.function_param_vars?.has(name) || status.function_ref_params?.has(name));
|
|
2966
3177
|
}
|
|
2967
3178
|
/**
|
|
3179
|
+
* Whether `obj.field = rhs` writes a plain `string` field of a struct or
|
|
3180
|
+
* class instance. CLASS string fields follow an always-heap-owned convention
|
|
3181
|
+
* (see the assignment branch that uses this): `_init` strdup's defaults and
|
|
3182
|
+
* assignments strdup non-heap RHS, so destroys can free them unconditionally.
|
|
3183
|
+
* VALUE-struct string fields keep per-assignment ownership tracking instead
|
|
3184
|
+
* (construction may leave rodata in them): non-heap RHS is strdup'd and the
|
|
3185
|
+
* field is recorded in heap_string_fields for release at scope exit.
|
|
3186
|
+
* `ref` string fields (borrows) are excluded either way.
|
|
3187
|
+
*/
|
|
3188
|
+
function field_is_struct_string(target_type, field_type, status) {
|
|
3189
|
+
if (!target_type?.name || !field_type) return void 0;
|
|
3190
|
+
if (field_type.name !== "string" || field_type.is_ref || field_type.is_array) return void 0;
|
|
3191
|
+
const target_struct = status.structs.find((s) => s.name === target_type.name && !s.is_simple_type);
|
|
3192
|
+
if (!target_struct) return void 0;
|
|
3193
|
+
if (target_struct.is_class) return {
|
|
3194
|
+
name: field_type.name,
|
|
3195
|
+
target_is_class: true
|
|
3196
|
+
};
|
|
3197
|
+
return {
|
|
3198
|
+
name: field_type.name,
|
|
3199
|
+
target_is_class: false
|
|
3200
|
+
};
|
|
3201
|
+
}
|
|
3202
|
+
/**
|
|
2968
3203
|
* Load a `ref T` parameter's caller-side pointer into `reg`. The pointer is the
|
|
2969
3204
|
* 8-byte value held in the parameter's stack slot (or, rarely, a register
|
|
2970
3205
|
* allocation). Only writes `reg` — safe to run after a RHS has been built into
|
|
@@ -3530,6 +3765,38 @@ function build_assignment_node$1(node, status) {
|
|
|
3530
3765
|
status.code += `mov x2, x0\n`;
|
|
3531
3766
|
status.code += `ldr x0, [sp], #16\n`;
|
|
3532
3767
|
status.code += `str x2, [x0, #${offset}]\n`;
|
|
3768
|
+
} else if (field_is_struct_string(target_type, field_type, status) && !node.operator && (() => {
|
|
3769
|
+
const st = field_is_struct_string(target_type, field_type, status);
|
|
3770
|
+
const tv = access.target.node_type === "value" ? access.target.value : "";
|
|
3771
|
+
return st.target_is_class || tv !== "" && tv !== "self";
|
|
3772
|
+
})()) {
|
|
3773
|
+
const string_target = field_is_struct_string(target_type, field_type, status);
|
|
3774
|
+
const target_var = access.target.node_type === "value" ? access.target.value : "";
|
|
3775
|
+
const tracked_key = `${target_var}.${field_name}`;
|
|
3776
|
+
const is_class_target = string_target.target_is_class;
|
|
3777
|
+
const old_was_heap = is_class_target || !!status.heap_string_fields?.has(tracked_key);
|
|
3778
|
+
const offset = get_field_offset(target_type.name, field_name, status);
|
|
3779
|
+
get_base_address(access, status, "x0");
|
|
3780
|
+
status.code += `str x0, [sp, #-16]!\n`;
|
|
3781
|
+
status.last_result_is_heap = false;
|
|
3782
|
+
build_node$1(node.right_value, status);
|
|
3783
|
+
if (!status.code.endsWith("\n")) status.code += "\n";
|
|
3784
|
+
const rhs_is_heap = status.last_result_is_heap;
|
|
3785
|
+
if (is_class_target && !rhs_is_heap) emit_strdup(status);
|
|
3786
|
+
mark_moved_if_struct(node.right_value, status);
|
|
3787
|
+
status.code += `str x0, [sp, #-16]!\n`;
|
|
3788
|
+
if (old_was_heap) {
|
|
3789
|
+
status.code += `ldr x0, [sp, #16]\n`;
|
|
3790
|
+
status.code += `ldr x0, [x0, #${offset}]\n`;
|
|
3791
|
+
emit_free(status);
|
|
3792
|
+
}
|
|
3793
|
+
status.code += `ldr x2, [sp], #16\n`;
|
|
3794
|
+
status.code += `ldr x0, [sp], #16\n`;
|
|
3795
|
+
status.code += `str x2, [x0, #${offset}]\n`;
|
|
3796
|
+
if (!is_class_target && target_var && target_var !== "self") {
|
|
3797
|
+
if (rhs_is_heap) record_heap_string_field(status, target_var, field_name);
|
|
3798
|
+
else status.heap_string_fields?.delete(tracked_key);
|
|
3799
|
+
}
|
|
3533
3800
|
} else if (field_is_struct && !node.operator) {
|
|
3534
3801
|
const field_struct = status.structs.find((s) => s.name === field_type.name);
|
|
3535
3802
|
if (field_struct?.is_class) {
|
|
@@ -4810,6 +5077,7 @@ function build_declaration_node$1(node, status) {
|
|
|
4810
5077
|
if (status.heap_cleanup_stack?.length) status.heap_cleanup_stack[status.heap_cleanup_stack.length - 1].heap_strings.add(node.name);
|
|
4811
5078
|
}
|
|
4812
5079
|
} else if (!node.type.is_array && status.structs.find((s) => s.name === node.type.name && s.is_class)) {
|
|
5080
|
+
if (process.env.NOMEN_DEBUG_ANCHOR) console.error("ANCHOR check_heap:", node.name, "value:", node.value?.node_type, node.value?.name ?? "");
|
|
4813
5081
|
emit_var_load(status, "x0", node.name, 8);
|
|
4814
5082
|
anchor_heap_pointer(status, node.name);
|
|
4815
5083
|
consolidate_temp_anchors$1(status, node.value, node.type.name);
|
|
@@ -4946,7 +5214,9 @@ function build_declaration_node$1(node, status) {
|
|
|
4946
5214
|
}
|
|
4947
5215
|
const value_is_field_borrow = node.value?.node_type === "access" && node.value.access.node_type === "access_field";
|
|
4948
5216
|
const value_is_var_borrow = node.value?.node_type === "value" && !node.value.is_moved && node.value.value !== "null";
|
|
4949
|
-
const
|
|
5217
|
+
const value_is_method_borrow = node.value?.node_type === "access" && node.value.access.node_type === "access_func" && !node.value.access.owned_return;
|
|
5218
|
+
const value_is_borrowing_call = node.value?.node_type === "func_call" && !!status.borrow_returning_functions?.has(node.value.name);
|
|
5219
|
+
const is_borrowed_class_ref = !!(node.type?.name && struct_type && struct_type.is_class && (value_is_field_borrow || value_is_var_borrow || value_is_method_borrow || value_is_borrowing_call));
|
|
4950
5220
|
if (!is_borrowed_class_ref) status.scoped_declarations.push(node);
|
|
4951
5221
|
if (struct_type?.is_class) {
|
|
4952
5222
|
const top = (status.heap_cleanup_stack?.length ?? 1) - 1;
|
|
@@ -4960,7 +5230,7 @@ function build_declaration_node$1(node, status) {
|
|
|
4960
5230
|
status.alias_owns_flag?.set(node.name, flag_offset);
|
|
4961
5231
|
}
|
|
4962
5232
|
}
|
|
4963
|
-
if (!is_borrowed_class_ref && struct_type && struct_needs_destroy(struct_type, status)) track_struct_decl(status, node.name, node.type.name, node.type.type_args, node.type.is_nullable);
|
|
5233
|
+
if (!is_borrowed_class_ref && !is_field_struct_borrow(node) && struct_type && struct_needs_destroy(struct_type, status)) track_struct_decl(status, node.name, node.type.name, node.type.type_args, node.type.is_nullable);
|
|
4964
5234
|
if (status.enums.find((e) => e.name === node.type.name && e.has_associated_data)) {
|
|
4965
5235
|
const enum_size = get_enum_size(node.type.name, status);
|
|
4966
5236
|
if (status.function_return_label) {
|
|
@@ -5372,8 +5642,10 @@ function build_declaration_node$1(node, status) {
|
|
|
5372
5642
|
build_node$1(func_call, status);
|
|
5373
5643
|
if (!status.code.endsWith("\n")) status.code += "\n";
|
|
5374
5644
|
emit_var_store(status, "x0", node.name, 8);
|
|
5375
|
-
status.
|
|
5376
|
-
|
|
5645
|
+
if (!status.borrow_returning_functions?.has(func_call.name)) {
|
|
5646
|
+
status.last_result_is_heap = true;
|
|
5647
|
+
check_heap();
|
|
5648
|
+
}
|
|
5377
5649
|
}
|
|
5378
5650
|
} else if (node.value) {
|
|
5379
5651
|
if (node.value.node_type === "value") {
|
|
@@ -5942,8 +6214,7 @@ function reset_label_counter$4() {
|
|
|
5942
6214
|
label_counter$4 = 0;
|
|
5943
6215
|
}
|
|
5944
6216
|
function build_for_loop_node$1(node, status) {
|
|
5945
|
-
const old_scoped_declarations = status
|
|
5946
|
-
status.scoped_declarations = [];
|
|
6217
|
+
const old_scoped_declarations = enter_scope_frame(status);
|
|
5947
6218
|
const label = label_counter$4++;
|
|
5948
6219
|
const item_name = node.item.value;
|
|
5949
6220
|
const start_label = `.for_${label}`;
|
|
@@ -6233,7 +6504,7 @@ function build_for_loop_node$1(node, status) {
|
|
|
6233
6504
|
status.buffer_data_cache = saved_buffer_cache;
|
|
6234
6505
|
status.loop_labels.pop();
|
|
6235
6506
|
status.loop_writebacks?.pop();
|
|
6236
|
-
status
|
|
6507
|
+
exit_scope_frame(status, old_scoped_declarations);
|
|
6237
6508
|
}
|
|
6238
6509
|
function is_enumerable_type$1(node, status) {
|
|
6239
6510
|
if (node.node_type !== "value") return false;
|
|
@@ -6640,7 +6911,7 @@ function build_function_call_node$1(node, status) {
|
|
|
6640
6911
|
const param = node.params[idx];
|
|
6641
6912
|
if (param?.node_type === "value") {
|
|
6642
6913
|
const vname = param.value;
|
|
6643
|
-
if (((status.
|
|
6914
|
+
if ((all_scope_frames(status).flat().find((d) => d.name === vname)?.type?.name ?? param.type?.name) === "string") continue;
|
|
6644
6915
|
}
|
|
6645
6916
|
if (param) mark_moved_if_struct(param, status);
|
|
6646
6917
|
}
|
|
@@ -6653,8 +6924,7 @@ function reset_label_counter$3() {
|
|
|
6653
6924
|
}
|
|
6654
6925
|
function build_if_else_node$1(node, status) {
|
|
6655
6926
|
const label = label_counter$3++;
|
|
6656
|
-
const old_scoped_declarations = status
|
|
6657
|
-
status.scoped_declarations = [];
|
|
6927
|
+
const old_scoped_declarations = enter_scope_frame(status);
|
|
6658
6928
|
build_node$1(node.condition, status);
|
|
6659
6929
|
status.code += `\ncmp x0, #0\n`;
|
|
6660
6930
|
const pre_cache = status.buffer_data_cache;
|
|
@@ -6675,7 +6945,7 @@ function build_if_else_node$1(node, status) {
|
|
|
6675
6945
|
}
|
|
6676
6946
|
status.buffer_data_cache = pre_cache;
|
|
6677
6947
|
status.code += `end_${label}:\n`;
|
|
6678
|
-
status
|
|
6948
|
+
exit_scope_frame(status, old_scoped_declarations);
|
|
6679
6949
|
}
|
|
6680
6950
|
//#endregion
|
|
6681
6951
|
//#region ../src/build_aarch64/build_let_node.ts
|
|
@@ -6737,7 +7007,7 @@ function emit_pattern_tag(match_value, enum_name, status) {
|
|
|
6737
7007
|
}
|
|
6738
7008
|
function build_match_node$1(node, status) {
|
|
6739
7009
|
const label = label_counter$2++;
|
|
6740
|
-
const old_scoped_declarations = status
|
|
7010
|
+
const old_scoped_declarations = enter_scope_frame(status);
|
|
6741
7011
|
const old_stack_offsets = status.stack_offsets;
|
|
6742
7012
|
status.stack_offsets = new Map(old_stack_offsets);
|
|
6743
7013
|
const match_type_name = type_from_value_node$1(node.value)?.name;
|
|
@@ -6805,7 +7075,7 @@ function build_match_node$1(node, status) {
|
|
|
6805
7075
|
}
|
|
6806
7076
|
status.buffer_data_cache = pre_cache;
|
|
6807
7077
|
status.code += `end_match_${label}:\n`;
|
|
6808
|
-
status
|
|
7078
|
+
exit_scope_frame(status, old_scoped_declarations);
|
|
6809
7079
|
status.stack_offsets = old_stack_offsets;
|
|
6810
7080
|
}
|
|
6811
7081
|
//#endregion
|
|
@@ -7449,8 +7719,12 @@ function build_return_node$1(node, status) {
|
|
|
7449
7719
|
status.code += `str xzr, [x8, #${struct_size}]\n`;
|
|
7450
7720
|
}
|
|
7451
7721
|
const finalized = status.moved ?? /* @__PURE__ */ new Set();
|
|
7452
|
-
for (const decl of status.
|
|
7453
|
-
if (finalized.has(decl.name))
|
|
7722
|
+
for (const decl of all_scope_frames(status).flat()) {
|
|
7723
|
+
if (finalized.has(decl.name)) {
|
|
7724
|
+
release_heap_string_fields(status, decl.name, decl.type.name);
|
|
7725
|
+
continue;
|
|
7726
|
+
}
|
|
7727
|
+
if (is_field_struct_borrow(decl)) continue;
|
|
7454
7728
|
emit_destroy_for_decl(status, decl.name, decl.type.name, void 0, decl.type.type_args, decl.type.is_nullable);
|
|
7455
7729
|
}
|
|
7456
7730
|
emit_heap_slots_cleanup_for_return(status);
|
|
@@ -7464,8 +7738,12 @@ function build_return_node$1(node, status) {
|
|
|
7464
7738
|
emit_var_store(status, "x0", status.return_assign, size);
|
|
7465
7739
|
} else if (status.function_return_label) {
|
|
7466
7740
|
const finalized = status.moved ?? /* @__PURE__ */ new Set();
|
|
7467
|
-
for (const decl of status.
|
|
7468
|
-
if (finalized.has(decl.name))
|
|
7741
|
+
for (const decl of all_scope_frames(status).flat()) {
|
|
7742
|
+
if (finalized.has(decl.name)) {
|
|
7743
|
+
release_heap_string_fields(status, decl.name, decl.type.name);
|
|
7744
|
+
continue;
|
|
7745
|
+
}
|
|
7746
|
+
if (is_field_struct_borrow(decl)) continue;
|
|
7469
7747
|
emit_destroy_for_decl(status, decl.name, decl.type.name, void 0, decl.type.type_args, decl.type.is_nullable);
|
|
7470
7748
|
}
|
|
7471
7749
|
emit_heap_slots_cleanup_for_return(status);
|
|
@@ -7629,11 +7907,16 @@ function build_return_node$1(node, status) {
|
|
|
7629
7907
|
status.moved.add(var_name);
|
|
7630
7908
|
}
|
|
7631
7909
|
}
|
|
7632
|
-
mark_moved_if_struct(node.value, status);
|
|
7910
|
+
mark_moved_if_struct(node.value, status, { for_return: true });
|
|
7911
|
+
if (node.value?.node_type === "value") clear_heap_string_fields(status, node.value.value);
|
|
7633
7912
|
const finalized = status.moved ?? /* @__PURE__ */ new Set();
|
|
7634
7913
|
status.code += `str x0, [sp, #-16]!\n`;
|
|
7635
|
-
for (const decl of status.
|
|
7636
|
-
if (finalized.has(decl.name))
|
|
7914
|
+
for (const decl of all_scope_frames(status).flat()) {
|
|
7915
|
+
if (finalized.has(decl.name)) {
|
|
7916
|
+
release_heap_string_fields(status, decl.name, decl.type.name);
|
|
7917
|
+
continue;
|
|
7918
|
+
}
|
|
7919
|
+
if (is_field_struct_borrow(decl)) continue;
|
|
7637
7920
|
emit_destroy_for_decl(status, decl.name, decl.type.name, void 0, decl.type.type_args, decl.type.is_nullable);
|
|
7638
7921
|
}
|
|
7639
7922
|
emit_heap_slots_cleanup_for_return(status);
|
|
@@ -7649,7 +7932,7 @@ function reset_label_counter$1() {
|
|
|
7649
7932
|
}
|
|
7650
7933
|
function build_switch_node$1(node, status) {
|
|
7651
7934
|
const label = label_counter$1++;
|
|
7652
|
-
const old_scoped_declarations = status
|
|
7935
|
+
const old_scoped_declarations = enter_scope_frame(status);
|
|
7653
7936
|
const pre_cache = status.buffer_data_cache;
|
|
7654
7937
|
for (let i = 0; i < node.cases.length; i++) {
|
|
7655
7938
|
status.scoped_declarations = [];
|
|
@@ -7669,7 +7952,7 @@ function build_switch_node$1(node, status) {
|
|
|
7669
7952
|
}
|
|
7670
7953
|
status.buffer_data_cache = pre_cache;
|
|
7671
7954
|
status.code += `end_switch_${label}:\n`;
|
|
7672
|
-
status
|
|
7955
|
+
exit_scope_frame(status, old_scoped_declarations);
|
|
7673
7956
|
}
|
|
7674
7957
|
//#endregion
|
|
7675
7958
|
//#region ../src/build_aarch64/build_todo_node.ts
|
|
@@ -7896,8 +8179,7 @@ function reset_label_counter() {
|
|
|
7896
8179
|
label_counter = 0;
|
|
7897
8180
|
}
|
|
7898
8181
|
function build_while_loop_node$1(node, status) {
|
|
7899
|
-
const old_scoped_declarations = status
|
|
7900
|
-
status.scoped_declarations = [];
|
|
8182
|
+
const old_scoped_declarations = enter_scope_frame(status);
|
|
7901
8183
|
const label = label_counter++;
|
|
7902
8184
|
const start_label = `.while_${label}`;
|
|
7903
8185
|
const end_label = `.end_while_${label}`;
|
|
@@ -8017,7 +8299,7 @@ function build_while_loop_node$1(node, status) {
|
|
|
8017
8299
|
else status.register_allocations = void 0;
|
|
8018
8300
|
status.buffer_data_cache = saved_buffer_cache;
|
|
8019
8301
|
status.loop_labels.pop();
|
|
8020
|
-
status
|
|
8302
|
+
exit_scope_frame(status, old_scoped_declarations);
|
|
8021
8303
|
}
|
|
8022
8304
|
//#endregion
|
|
8023
8305
|
//#region ../src/build_aarch64/build_node.ts
|
|
@@ -8738,7 +9020,7 @@ function build_auto_destroy_function(node, status) {
|
|
|
8738
9020
|
status.function_param_regs.set("self", "x19");
|
|
8739
9021
|
status.code += `sub sp, sp, #${stack_placeholder}\n`;
|
|
8740
9022
|
status.code += `mov x29, sp\n`;
|
|
8741
|
-
emit_field_destroys(status, node, "self", void 0, false);
|
|
9023
|
+
emit_field_destroys(status, node, "self", void 0, false, node.is_class);
|
|
8742
9024
|
status.code += `${return_label}:\n`;
|
|
8743
9025
|
const total_stack = Math.ceil((status.stack_size || 0) / 16) * 16;
|
|
8744
9026
|
status.code = status.code.replace(`sub sp, sp, #${stack_placeholder}`, total_stack > 0 ? `sub sp, sp, #${total_stack}` : `// no stack needed`);
|
|
@@ -8821,6 +9103,13 @@ function build_init_function(node, status) {
|
|
|
8821
9103
|
}
|
|
8822
9104
|
} else {
|
|
8823
9105
|
const field_size = get_type_size(field.type, status);
|
|
9106
|
+
if (node.is_class && field.type.name === "string" && !field.type.is_ref) {
|
|
9107
|
+
status.code += `str ${src_reg}, [sp, #-16]!\n`;
|
|
9108
|
+
status.code += `mov x0, ${src_reg}\n`;
|
|
9109
|
+
status.code += `bl _strdup\n`;
|
|
9110
|
+
status.code += `mov ${src_reg}, x0\n`;
|
|
9111
|
+
status.code += `ldr x0, [sp], #16\n`;
|
|
9112
|
+
}
|
|
8824
9113
|
emit_typed_store(status, src_reg, "x19", offset, field_size);
|
|
8825
9114
|
}
|
|
8826
9115
|
}
|
|
@@ -8838,6 +9127,11 @@ function build_init_function(node, status) {
|
|
|
8838
9127
|
const label = `_str_${func_name}_${field.name}`;
|
|
8839
9128
|
status.strings.set(label, val);
|
|
8840
9129
|
status.code += `adr x1, ${label}\n`;
|
|
9130
|
+
if (node.is_class && field.type.name === "string" && !field.type.is_ref) {
|
|
9131
|
+
status.code += `mov x0, x1\n`;
|
|
9132
|
+
status.code += `bl _strdup\n`;
|
|
9133
|
+
status.code += `mov x1, x0\n`;
|
|
9134
|
+
}
|
|
8841
9135
|
} else {
|
|
8842
9136
|
const resolved = resolve_global_const_value(val, status);
|
|
8843
9137
|
if (resolved !== void 0) status.code += `ldr x1, =${resolved}\n`;
|
|
@@ -8975,6 +9269,11 @@ function build_custom_init_function(node, func, status) {
|
|
|
8975
9269
|
const label = `_str_${func_name}_${field.name}`;
|
|
8976
9270
|
status.strings.set(label, val);
|
|
8977
9271
|
status.code += `adr x1, ${label}\n`;
|
|
9272
|
+
if (node.is_class && field.type.name === "string" && !field.type.is_ref) {
|
|
9273
|
+
status.code += `mov x0, x1\n`;
|
|
9274
|
+
status.code += `bl _strdup\n`;
|
|
9275
|
+
status.code += `mov x1, x0\n`;
|
|
9276
|
+
}
|
|
8978
9277
|
} else {
|
|
8979
9278
|
const resolved = resolve_global_const_value(val, status);
|
|
8980
9279
|
if (resolved !== void 0) status.code += `ldr x1, =${resolved}\n`;
|
|
@@ -9183,8 +9482,33 @@ function build_struct_functions$1(node, status) {
|
|
|
9183
9482
|
}
|
|
9184
9483
|
second_slot_idx++;
|
|
9185
9484
|
}
|
|
9485
|
+
const moved_param_save_slots = /* @__PURE__ */ new Map();
|
|
9486
|
+
for (const param of func.params) {
|
|
9487
|
+
if (!param.is_moved || param.is_self_param) continue;
|
|
9488
|
+
if (!status.structs.find((s) => s.name === param.type.name && s.is_class)) continue;
|
|
9489
|
+
const reg = status.function_param_regs.get(param.name);
|
|
9490
|
+
if (reg) {
|
|
9491
|
+
const save_offset = allocate_stack_space(status, 8);
|
|
9492
|
+
status.code += `str ${reg}, [x29, #${save_offset}]\n`;
|
|
9493
|
+
moved_param_save_slots.set(param.name, {
|
|
9494
|
+
offset: save_offset,
|
|
9495
|
+
type_name: param.type.name,
|
|
9496
|
+
type_args: param.type.type_args,
|
|
9497
|
+
is_nullable: param.type.is_nullable
|
|
9498
|
+
});
|
|
9499
|
+
} else {
|
|
9500
|
+
const offset = status.stack_offsets.get(param.name);
|
|
9501
|
+
if (offset !== void 0) moved_param_save_slots.set(param.name, {
|
|
9502
|
+
offset,
|
|
9503
|
+
type_name: param.type.name,
|
|
9504
|
+
type_args: param.type.type_args,
|
|
9505
|
+
is_nullable: param.type.is_nullable
|
|
9506
|
+
});
|
|
9507
|
+
}
|
|
9508
|
+
}
|
|
9186
9509
|
status.force_heap_strings = scan_force_heap_strings(func.statements);
|
|
9187
9510
|
status.buffer_data_cache = void 0;
|
|
9511
|
+
const moved_before = new Set(status.moved ?? []);
|
|
9188
9512
|
if (!emit_owning_buffer_standalone_aarch64(node, func.name, status)) build_block_node$1(func, status);
|
|
9189
9513
|
const loop_regs_used = status.callee_saved_regs_used ? [...status.callee_saved_regs_used].sort() : [];
|
|
9190
9514
|
status.callee_saved_regs_used = void 0;
|
|
@@ -9199,6 +9523,31 @@ function build_struct_functions$1(node, status) {
|
|
|
9199
9523
|
}
|
|
9200
9524
|
}
|
|
9201
9525
|
status.code += `${return_label}:\n`;
|
|
9526
|
+
if (moved_param_save_slots.size > 0) {
|
|
9527
|
+
const ret_is_class = !!func.return_type?.name && !!status.structs.find((s) => s.name === func.return_type.name && s.is_class);
|
|
9528
|
+
const need_save = !!func.return_type?.name;
|
|
9529
|
+
const keep_prefix = `.Lkeep_mparam_${func_label.replace(/[^\w]/g, "_")}`;
|
|
9530
|
+
let return_save;
|
|
9531
|
+
if (ret_is_class || need_save) {
|
|
9532
|
+
return_save = allocate_stack_space(status, 8);
|
|
9533
|
+
status.code += `str x0, [x29, #${return_save}]\n`;
|
|
9534
|
+
}
|
|
9535
|
+
for (const [name, info] of moved_param_save_slots) {
|
|
9536
|
+
if (status.moved?.has(name) && !moved_before.has(name)) continue;
|
|
9537
|
+
if (moved_param_is_consumed(func, name)) continue;
|
|
9538
|
+
if (ret_is_class) {
|
|
9539
|
+
status.code += `ldr x0, [x29, #${info.offset}]\n`;
|
|
9540
|
+
status.code += `ldr x1, [x29, #${return_save}]\n`;
|
|
9541
|
+
status.code += `cmp x0, x1\n`;
|
|
9542
|
+
status.code += `beq ${keep_prefix}_${name}\n`;
|
|
9543
|
+
}
|
|
9544
|
+
emit_destroy_for_anchor_slot(status, info.offset, info.type_name, info.type_args, info.is_nullable);
|
|
9545
|
+
status.code += `ldr x0, [x29, #${info.offset}]\n`;
|
|
9546
|
+
emit_free(status);
|
|
9547
|
+
if (ret_is_class) status.code += `${keep_prefix}_${name}:\n`;
|
|
9548
|
+
}
|
|
9549
|
+
if (ret_is_class || need_save) status.code += `ldr x0, [x29, #${return_save}]\n`;
|
|
9550
|
+
}
|
|
9202
9551
|
const total_stack = Math.ceil((status.stack_size || 0) / 16) * 16;
|
|
9203
9552
|
status.code = status.code.replace(`sub sp, sp, #${stack_placeholder}`, total_stack > 0 ? `sub sp, sp, #${total_stack}` : `// no stack needed`);
|
|
9204
9553
|
status.code = patch_overflow_placeholders(status.code, func_label, callee_idx + loop_regs_used.length, total_stack);
|
|
@@ -9655,6 +10004,8 @@ function build_inline_method(struct_node, func, status) {
|
|
|
9655
10004
|
const old_function_return_type = status.function_return_type;
|
|
9656
10005
|
const old_register_allocations = status.register_allocations;
|
|
9657
10006
|
const old_buffer_data_cache = status.buffer_data_cache;
|
|
10007
|
+
const old_heap_cleanup_stack = status.heap_cleanup_stack;
|
|
10008
|
+
const old_moved = status.moved;
|
|
9658
10009
|
const return_label = `.inline_ret_${inline_counter++}`;
|
|
9659
10010
|
status.function_return_label = return_label;
|
|
9660
10011
|
status.scoped_declarations = [];
|
|
@@ -9662,6 +10013,8 @@ function build_inline_method(struct_node, func, status) {
|
|
|
9662
10013
|
status.struct_return_buffer = void 0;
|
|
9663
10014
|
status.return_buffer_stack_offset = void 0;
|
|
9664
10015
|
status.buffer_data_cache = void 0;
|
|
10016
|
+
status.heap_cleanup_stack = [];
|
|
10017
|
+
status.moved = /* @__PURE__ */ new Set();
|
|
9665
10018
|
if (needs_x19) {
|
|
9666
10019
|
status.code += `str x19, [sp, #-16]!\n`;
|
|
9667
10020
|
status.code += `mov x19, x0\n`;
|
|
@@ -9740,6 +10093,8 @@ function build_inline_method(struct_node, func, status) {
|
|
|
9740
10093
|
status.function_return_type = old_function_return_type;
|
|
9741
10094
|
status.register_allocations = old_register_allocations;
|
|
9742
10095
|
status.buffer_data_cache = old_buffer_data_cache;
|
|
10096
|
+
status.heap_cleanup_stack = old_heap_cleanup_stack;
|
|
10097
|
+
status.moved = old_moved;
|
|
9743
10098
|
}
|
|
9744
10099
|
let inline_fn_depth = 0;
|
|
9745
10100
|
const MAX_INLINE_DEPTH = 2;
|
|
@@ -9757,6 +10112,8 @@ function build_inline_function(func, status) {
|
|
|
9757
10112
|
const old_function_return_type = status.function_return_type;
|
|
9758
10113
|
const old_register_allocations = status.register_allocations;
|
|
9759
10114
|
const old_buffer_data_cache = status.buffer_data_cache;
|
|
10115
|
+
const old_heap_cleanup_stack = status.heap_cleanup_stack;
|
|
10116
|
+
const old_moved = status.moved;
|
|
9760
10117
|
const return_label = `.inline_fn_ret_${inline_counter++}`;
|
|
9761
10118
|
status.function_return_label = return_label;
|
|
9762
10119
|
status.scoped_declarations = [];
|
|
@@ -9764,6 +10121,8 @@ function build_inline_function(func, status) {
|
|
|
9764
10121
|
status.struct_return_buffer = void 0;
|
|
9765
10122
|
status.return_buffer_stack_offset = void 0;
|
|
9766
10123
|
status.buffer_data_cache = void 0;
|
|
10124
|
+
status.heap_cleanup_stack = [];
|
|
10125
|
+
status.moved = /* @__PURE__ */ new Set();
|
|
9767
10126
|
const param_regs = [
|
|
9768
10127
|
"x0",
|
|
9769
10128
|
"x1",
|
|
@@ -9830,6 +10189,8 @@ function build_inline_function(func, status) {
|
|
|
9830
10189
|
status.function_return_type = old_function_return_type;
|
|
9831
10190
|
status.register_allocations = old_register_allocations;
|
|
9832
10191
|
status.buffer_data_cache = old_buffer_data_cache;
|
|
10192
|
+
status.heap_cleanup_stack = old_heap_cleanup_stack;
|
|
10193
|
+
status.moved = old_moved;
|
|
9833
10194
|
inline_fn_depth--;
|
|
9834
10195
|
return true;
|
|
9835
10196
|
}
|
|
@@ -10438,6 +10799,12 @@ function build_access_field(node, status) {
|
|
|
10438
10799
|
if (paramReg !== "x0") status.code += `mov x0, ${paramReg}\n`;
|
|
10439
10800
|
} else emit_var_load(status, "x0", name, 8);
|
|
10440
10801
|
const final_offset = get_field_offset(target_type?.name || "", access_field.name, status);
|
|
10802
|
+
const field_type_obj = resolve_field_type(access_field, target_type?.name, status);
|
|
10803
|
+
const resolved_field_type = field_type_obj?.name || "";
|
|
10804
|
+
if (!!resolved_field_type && !field_type_obj?.is_ref && !field_type_obj?.is_nullable && is_struct_type(resolved_field_type, status)) {
|
|
10805
|
+
if (final_offset > 0) status.code += `add x0, x0, #${final_offset}\n`;
|
|
10806
|
+
return;
|
|
10807
|
+
}
|
|
10441
10808
|
const field_type = access_field.type?.name || "";
|
|
10442
10809
|
const size = aarch64_size(field_type);
|
|
10443
10810
|
const signed = field_type.startsWith("int") || field_type === "float" || field_type === "float32" || field_type === "float64";
|
|
@@ -10991,10 +11358,14 @@ function build_access_method(node, access_func, status) {
|
|
|
10991
11358
|
const param = access_func.params[idx];
|
|
10992
11359
|
if (param?.node_type === "value") {
|
|
10993
11360
|
const vname = param.value;
|
|
10994
|
-
if (((status.
|
|
11361
|
+
if ((all_scope_frames(status).flat().find((d) => d.name === vname)?.type?.name ?? param.type?.name) === "string") continue;
|
|
10995
11362
|
}
|
|
10996
11363
|
if (param) mark_moved_if_struct(param, status);
|
|
10997
11364
|
}
|
|
11365
|
+
if (node.target.node_type === "value" && target_struct && !target_struct.is_class && !trait_target) {
|
|
11366
|
+
const target_method = target_struct.functions.find((f) => f.name === access_func.name);
|
|
11367
|
+
if (target_method) drop_self_written_string_field_records(status, node.target.value, scan_self_string_field_writes(target_struct, target_method));
|
|
11368
|
+
}
|
|
10998
11369
|
if (method_name.endsWith("_to_string") && method_name !== "string_to_string") status.last_result_is_heap = true;
|
|
10999
11370
|
if (status.heap_returning_functions?.has(method_name)) status.last_result_is_heap = true;
|
|
11000
11371
|
if (method_name === "Buffer_string_move_T") status.last_result_is_heap = true;
|
|
@@ -11739,13 +12110,7 @@ function build_spawn_node(node, status) {
|
|
|
11739
12110
|
if (!status.headers.includes("__nomen_pool_submit")) status.headers += POOL_HEADER;
|
|
11740
12111
|
const struct_name = `__nomen_spawn_${id}_args`;
|
|
11741
12112
|
const tramp_name = `__nomen_spawn_${id}_trampoline`;
|
|
11742
|
-
const arg_c_types =
|
|
11743
|
-
for (let i = 0; i < call.params.length; i++) {
|
|
11744
|
-
const mono_name = mono_type_name(type_from_value_node$1(call.params[i]));
|
|
11745
|
-
const is_class = !!status.structs.find((s) => s.name === mono_name && s.is_class);
|
|
11746
|
-
const is_trait = !!status.traits.find((t) => t.name === mono_name);
|
|
11747
|
-
arg_c_types.push(is_class || is_trait ? `struct ${mono_name} *` : c_type(mono_name));
|
|
11748
|
-
}
|
|
12113
|
+
const arg_c_types = spawn_arg_c_types(call, status);
|
|
11749
12114
|
const return_type_name = node.function_return_type?.name;
|
|
11750
12115
|
const returns_value = !!(return_type_name && return_type_name !== "void" && return_type_name !== "?");
|
|
11751
12116
|
const is_class_ret = returns_value && !!status.structs.find((s) => s.name === return_type_name && s.is_class);
|
|
@@ -11818,6 +12183,98 @@ function build_spawn_node(node, status) {
|
|
|
11818
12183
|
}
|
|
11819
12184
|
status.code += `})\n`;
|
|
11820
12185
|
}
|
|
12186
|
+
const C_BUILTIN_TYPES = /* @__PURE__ */ new Set([
|
|
12187
|
+
"bool",
|
|
12188
|
+
"int",
|
|
12189
|
+
"uint",
|
|
12190
|
+
"int8",
|
|
12191
|
+
"uint8",
|
|
12192
|
+
"int16",
|
|
12193
|
+
"uint16",
|
|
12194
|
+
"int32",
|
|
12195
|
+
"uint32",
|
|
12196
|
+
"int64",
|
|
12197
|
+
"uint64",
|
|
12198
|
+
"float",
|
|
12199
|
+
"ufloat",
|
|
12200
|
+
"float32",
|
|
12201
|
+
"ufloat32",
|
|
12202
|
+
"float64",
|
|
12203
|
+
"ufloat64",
|
|
12204
|
+
"char",
|
|
12205
|
+
"string",
|
|
12206
|
+
"func",
|
|
12207
|
+
"void",
|
|
12208
|
+
"null"
|
|
12209
|
+
]);
|
|
12210
|
+
/**
|
|
12211
|
+
* Resolve each spawn argument's C type. Classes/traits are pointers;
|
|
12212
|
+
* primitives and by-value structs use c_type's output directly. Generic
|
|
12213
|
+
* instantiations (e.g. Channel<uint64>) use the monomorphized C name
|
|
12214
|
+
* (`Channel_uint64`). The type comes from the CALLEE's declared parameter
|
|
12215
|
+
* whenever it can be resolved — an argument's own node type can differ
|
|
12216
|
+
* (e.g. an int literal `41` passed for a `uint64` param lowers to `long`,
|
|
12217
|
+
* conflicting with the emitted `unsigned long long` prototype). Falls back
|
|
12218
|
+
* to the argument's type when the callee (or its param type) can't be
|
|
12219
|
+
* resolved at build time.
|
|
12220
|
+
*/
|
|
12221
|
+
function spawn_arg_c_types(call, status) {
|
|
12222
|
+
const arg_c_types = [];
|
|
12223
|
+
const callee_params = find_spawn_callee(call.name, status)?.params?.filter((p) => !p.is_self_param) ?? [];
|
|
12224
|
+
for (let i = 0; i < call.params.length; i++) {
|
|
12225
|
+
const mono_name = mono_type_name(callee_params[i]?.type && is_resolvable_c_type(callee_params[i].type, status) ? callee_params[i].type : type_from_value_node$1(call.params[i]));
|
|
12226
|
+
const is_class = !!status.structs.find((s) => s.name === mono_name && s.is_class);
|
|
12227
|
+
const is_trait = !!status.traits.find((t) => t.name === mono_name);
|
|
12228
|
+
arg_c_types.push(is_class || is_trait ? `struct ${mono_name} *` : c_type(mono_name));
|
|
12229
|
+
}
|
|
12230
|
+
return arg_c_types;
|
|
12231
|
+
}
|
|
12232
|
+
/** Whether a Nomen type name lowers to a real C type in this build: a
|
|
12233
|
+
* builtin primitive or a struct/enum the backend knows (post-monomorphization).
|
|
12234
|
+
* Unresolved generic type params (`T`) fail this check. */
|
|
12235
|
+
function is_resolvable_c_type(type, status) {
|
|
12236
|
+
if (C_BUILTIN_TYPES.has(type.name)) return true;
|
|
12237
|
+
return !!status.structs.find((s) => s.name === type.name);
|
|
12238
|
+
}
|
|
12239
|
+
/**
|
|
12240
|
+
* Find the spawned function's definition — a top-level function, a function
|
|
12241
|
+
* nested in a block (parse wrappers hoist user code into `main`), or a
|
|
12242
|
+
* struct/trait method (matched by its mangled `Struct_method` name) — so the
|
|
12243
|
+
* trampoline's forward declaration can copy the callee's DECLARED parameter
|
|
12244
|
+
* types. Monomorphized clones are also reachable this way (they are appended
|
|
12245
|
+
* to the AST). A same-named pair would already collide at C level, so the
|
|
12246
|
+
* first match is as good as any.
|
|
12247
|
+
*/
|
|
12248
|
+
function find_spawn_callee(name, status) {
|
|
12249
|
+
let found;
|
|
12250
|
+
const visit = (node) => {
|
|
12251
|
+
if (found || !node || typeof node !== "object") return;
|
|
12252
|
+
if (node.node_type === "func") {
|
|
12253
|
+
if (node.name === name) {
|
|
12254
|
+
found = node;
|
|
12255
|
+
return;
|
|
12256
|
+
}
|
|
12257
|
+
} else if (node.node_type === "struct" || node.node_type === "trait") {
|
|
12258
|
+
const functions = node.functions ?? [];
|
|
12259
|
+
for (const func of functions) {
|
|
12260
|
+
const owner = node.name;
|
|
12261
|
+
if (func.name === name || `${owner}_${func.name}` === name) {
|
|
12262
|
+
found = func;
|
|
12263
|
+
return;
|
|
12264
|
+
}
|
|
12265
|
+
}
|
|
12266
|
+
}
|
|
12267
|
+
for (const key of Object.keys(node)) {
|
|
12268
|
+
if (key === "parent" || key === "scope") continue;
|
|
12269
|
+
const v = node[key];
|
|
12270
|
+
if (Array.isArray(v)) for (const item of v) visit(item);
|
|
12271
|
+
else if (v && typeof v === "object" && "node_type" in v) visit(v);
|
|
12272
|
+
if (found) return;
|
|
12273
|
+
}
|
|
12274
|
+
};
|
|
12275
|
+
visit(status.root);
|
|
12276
|
+
return found;
|
|
12277
|
+
}
|
|
11821
12278
|
//#endregion
|
|
11822
12279
|
//#region ../src/build_c/build_nursery_spawn.ts
|
|
11823
12280
|
/**
|
|
@@ -11843,13 +12300,7 @@ function build_nursery_spawn(node, nursery_ptr, status) {
|
|
|
11843
12300
|
if (!status.headers.includes("__nomen_pool_submit")) status.headers += POOL_HEADER;
|
|
11844
12301
|
const struct_name = `__nomen_spawn_${id}_args`;
|
|
11845
12302
|
const tramp_name = `__nomen_spawn_${id}_trampoline`;
|
|
11846
|
-
const arg_c_types =
|
|
11847
|
-
for (let i = 0; i < args.length; i++) {
|
|
11848
|
-
const mono_name = mono_type_name(type_from_value_node$1(args[i]));
|
|
11849
|
-
const is_class = !!status.structs.find((s) => s.name === mono_name && s.is_class);
|
|
11850
|
-
const is_trait = !!status.traits.find((t) => t.name === mono_name);
|
|
11851
|
-
arg_c_types.push(is_class || is_trait ? `struct ${mono_name} *` : c_type(mono_name));
|
|
11852
|
-
}
|
|
12303
|
+
const arg_c_types = spawn_arg_c_types(call, status);
|
|
11853
12304
|
const return_type_name = node.function_return_type?.name;
|
|
11854
12305
|
const returns_value = !!(return_type_name && return_type_name !== "void" && return_type_name !== "?");
|
|
11855
12306
|
const is_class_ret = returns_value && !!status.structs.find((s) => s.name === return_type_name && s.is_class);
|
|
@@ -12060,21 +12511,25 @@ function build_operation_node(node, status) {
|
|
|
12060
12511
|
} else if (node.operator_func) {
|
|
12061
12512
|
const label = node.operator_func.mangled_name || `${node.operator_func.struct_name}_${node.operator_func.func_name}`;
|
|
12062
12513
|
const is_string_op = node.type?.name === "string";
|
|
12063
|
-
const
|
|
12064
|
-
const
|
|
12514
|
+
const is_string_cmp = node.op === "==" || node.op === "!=";
|
|
12515
|
+
const left_temp = (is_string_op || is_string_cmp) && is_owned_heap_temp(node.left_value, status);
|
|
12516
|
+
const right_temp = (is_string_op || is_string_cmp) && is_owned_heap_temp(node.right_value, status);
|
|
12065
12517
|
if (left_temp || right_temp) {
|
|
12066
12518
|
const id = status.label_counter = (status.label_counter ?? 0) + 1;
|
|
12067
12519
|
const lt = `_ltmp_${id}`;
|
|
12068
12520
|
const rt = `_rtmp_${id}`;
|
|
12521
|
+
const cres = `_cres_${id}`;
|
|
12069
12522
|
status.code += `({ `;
|
|
12070
12523
|
status.code += `char* ${lt} = `;
|
|
12071
12524
|
build_operand(node.left_value, status);
|
|
12072
12525
|
status.code += `; char* ${rt} = `;
|
|
12073
12526
|
build_operand(node.right_value, status);
|
|
12074
|
-
status.code += `;
|
|
12527
|
+
status.code += `; `;
|
|
12528
|
+
if (is_string_op) status.code += `char* ${cres} = ${label}(${lt}, ${rt}); `;
|
|
12529
|
+
else status.code += `int ${cres} = ${label}(${lt}, ${rt}); `;
|
|
12075
12530
|
if (left_temp) status.code += `free(${lt}); `;
|
|
12076
12531
|
if (right_temp) status.code += `free(${rt}); `;
|
|
12077
|
-
status.code +=
|
|
12532
|
+
status.code += `${cres}; })`;
|
|
12078
12533
|
} else {
|
|
12079
12534
|
const is_array_op = node.operator_func.struct_name.startsWith("Array") && (type_from_value_node$1(node.left_value).is_array || type_from_value_node$1(node.right_value).is_array);
|
|
12080
12535
|
if (node.operator_func.invert) status.code += `(!`;
|
|
@@ -12133,7 +12588,7 @@ function is_owned_heap_temp(node, status) {
|
|
|
12133
12588
|
if (heap_set?.has(mangled)) return true;
|
|
12134
12589
|
if (heap_set && target_value && heap_set.has(`${target_value}_${raw_name}`)) return true;
|
|
12135
12590
|
if (heap_set && target_type_name && heap_set.has(`${target_type_name}_${raw_name}`)) return true;
|
|
12136
|
-
return !(check_node.node_type === "access_func" && (raw_name === "at" || raw_name === "first") && !check_node.owned_return);
|
|
12591
|
+
return !(check_node.node_type === "access_func" && (raw_name === "at" || raw_name === "first" || raw_name === "load_T") && !check_node.owned_return);
|
|
12137
12592
|
}
|
|
12138
12593
|
return false;
|
|
12139
12594
|
}
|
|
@@ -12241,11 +12696,356 @@ function build_array_operand_for_call(node, status) {
|
|
|
12241
12696
|
}
|
|
12242
12697
|
let ns_tmp_counter = 0;
|
|
12243
12698
|
//#endregion
|
|
12244
|
-
//#region ../src/build_c/
|
|
12699
|
+
//#region ../src/build_c/utils/is_string_borrow.ts
|
|
12245
12700
|
/**
|
|
12246
|
-
*
|
|
12247
|
-
*
|
|
12248
|
-
* `
|
|
12701
|
+
* Whether a value node denotes a BORROWED string — a pointer into storage the
|
|
12702
|
+
* receiver does not own (an array element accessed via `.at()`/`.first()`, or
|
|
12703
|
+
* `init.args.at(n)` which points into the C runtime's `argv`). Borrowed
|
|
12704
|
+
* strings must NOT be freed by auto_free or by reassignment: freeing them
|
|
12705
|
+
* reclaims memory owned by the container (or argv), crashing with
|
|
12706
|
+
* "pointer being freed was not allocated". Mirrors aarch64's `heap_strings`
|
|
12707
|
+
* ownership tracking, which only frees freshly-allocated strings.
|
|
12708
|
+
*/
|
|
12709
|
+
function is_string_borrow(node) {
|
|
12710
|
+
if (!node || node.node_type !== "access") return false;
|
|
12711
|
+
const access = node.access;
|
|
12712
|
+
if (access.node_type !== "access_func") return false;
|
|
12713
|
+
const func = access;
|
|
12714
|
+
return (func.name === "at" || func.name === "first") && !func.owned_return;
|
|
12715
|
+
}
|
|
12716
|
+
//#endregion
|
|
12717
|
+
//#region ../src/build_c/build_auto_free.ts
|
|
12718
|
+
function build_auto_free(status) {
|
|
12719
|
+
free_scoped_declarations(status, status.scoped_declarations);
|
|
12720
|
+
if (status.deferred_frees?.length) {
|
|
12721
|
+
status.code += "\n// Deferred frees\n";
|
|
12722
|
+
for (const d of status.deferred_frees) if (d.is_nullable) status.code += `if (${d.temp}) { ${d.struct_name}_destroy(${d.temp}); free(${d.temp}); }\n`;
|
|
12723
|
+
else status.code += `${d.struct_name}_destroy(${d.temp}); free(${d.temp});\n`;
|
|
12724
|
+
status.deferred_frees.length = 0;
|
|
12725
|
+
}
|
|
12726
|
+
status.scoped_declarations = [];
|
|
12727
|
+
}
|
|
12728
|
+
/**
|
|
12729
|
+
* Emit scope-exit free/destroy code for a list of declarations. Extracted from
|
|
12730
|
+
* build_auto_free so that break/continue can reclaim declarations from the
|
|
12731
|
+
* current scope AND enclosing scopes (up to the loop body) before jumping —
|
|
12732
|
+
* see build_break_node. Does NOT process deferred_frees or clear the list
|
|
12733
|
+
* (those are scope-exit-only concerns handled by build_auto_free).
|
|
12734
|
+
*/
|
|
12735
|
+
function free_scoped_declarations(status, decls, persist_string_field_records = false) {
|
|
12736
|
+
let commented = false;
|
|
12737
|
+
if (status.heap_string_fields?.size) for (const dec of decls) {
|
|
12738
|
+
const prefix = `${dec.name}.`;
|
|
12739
|
+
for (const key of Array.from(status.heap_string_fields)) if (key.startsWith(prefix)) {
|
|
12740
|
+
if (!commented) {
|
|
12741
|
+
status.code += "\n// Auto-free\n";
|
|
12742
|
+
commented = true;
|
|
12743
|
+
}
|
|
12744
|
+
status.code += `free(${key});\n`;
|
|
12745
|
+
if (!persist_string_field_records) status.heap_string_fields.delete(key);
|
|
12746
|
+
}
|
|
12747
|
+
}
|
|
12748
|
+
for (const dec of decls) {
|
|
12749
|
+
const struct = status.structs.find((s) => s.name === dec.type.name);
|
|
12750
|
+
if (struct && struct.traits.includes("Disposable")) {
|
|
12751
|
+
const trait = status.traits.find((t) => t.name === "Disposable");
|
|
12752
|
+
const func = trait?.functions.find((f) => f.name == "dispose");
|
|
12753
|
+
if (trait && func) {
|
|
12754
|
+
if (!commented) {
|
|
12755
|
+
status.code += "\n// Auto-free\n";
|
|
12756
|
+
commented = true;
|
|
12757
|
+
}
|
|
12758
|
+
const cast = "(void *(*)(void *))";
|
|
12759
|
+
const traitIndex = status.traits.indexOf(trait);
|
|
12760
|
+
const funcIndex = trait.functions.indexOf(func);
|
|
12761
|
+
status.code += `(${cast}_get_trait_func((void *)&${dec.name}, ${traitIndex}, ${funcIndex}))(&${dec.name});\n`;
|
|
12762
|
+
}
|
|
12763
|
+
}
|
|
12764
|
+
const is_destructured_field_access = dec.value?.node_type === "access" && dec.value.access.node_type === "access_field" && !dec.value.is_moved;
|
|
12765
|
+
const is_borrowed_string = is_string_borrow(dec.value) || !!status.string_borrow_vars?.has(dec.name);
|
|
12766
|
+
const value_is_heap_string = dec.type.name === "string" && (dec.value?.node_type === "access" && dec.value.access.node_type === "access_func" || dec.value?.node_type === "func_call");
|
|
12767
|
+
const dec_value = dec.value;
|
|
12768
|
+
const dec_val_is_string_literal = dec.value?.node_type === "value" && dec_value.value.length >= 2 && dec_value.value.startsWith("\"") && dec_value.value.endsWith("\"");
|
|
12769
|
+
const dec_val_is_heap_string_var = dec.value?.node_type === "value" && !dec_val_is_string_literal && !!status.scoped_declarations.find((d) => d.name === dec_value.value);
|
|
12770
|
+
const was_strdup_string_var = dec.declaration === "var" && !dec.type.is_view && !is_borrowed_string && (dec_val_is_string_literal || dec_val_is_heap_string_var);
|
|
12771
|
+
const is_normalized_join_string = !!status.string_join_owned_vars?.has(dec.name);
|
|
12772
|
+
const dec_struct = status.structs.find((s) => s.name === dec.type.name);
|
|
12773
|
+
const is_class_var = !!dec_struct?.is_class;
|
|
12774
|
+
const trait_class_trait = status.trait_class_locals?.get(dec.name);
|
|
12775
|
+
if (trait_class_trait !== void 0 && !is_destructured_field_access) {
|
|
12776
|
+
if (!commented) {
|
|
12777
|
+
status.code += "\n// Auto-free\n";
|
|
12778
|
+
commented = true;
|
|
12779
|
+
}
|
|
12780
|
+
if (dec.type.is_nullable) status.code += `if (${dec.name}) { ${trait_class_trait}_destroy(${dec.name}); free(${dec.name}); }\n`;
|
|
12781
|
+
else status.code += `${trait_class_trait}_destroy(${dec.name}); free(${dec.name});\n`;
|
|
12782
|
+
}
|
|
12783
|
+
if (!is_destructured_field_access && !is_borrowed_string && (!dec.type.is_static || value_is_heap_string || was_strdup_string_var || is_normalized_join_string) && dec.type.name === "string" && !dec.type.is_array) {
|
|
12784
|
+
if (!commented) {
|
|
12785
|
+
status.code += "\n// Auto-free\n";
|
|
12786
|
+
commented = true;
|
|
12787
|
+
}
|
|
12788
|
+
status.code += `free(${dec.name});\n`;
|
|
12789
|
+
}
|
|
12790
|
+
if (!is_destructured_field_access && is_class_var && !dec.type.is_array) {
|
|
12791
|
+
if (!commented) {
|
|
12792
|
+
status.code += "\n// Auto-free\n";
|
|
12793
|
+
commented = true;
|
|
12794
|
+
}
|
|
12795
|
+
const cls = struct ?? dec_struct;
|
|
12796
|
+
const mono_cls_name = cls ? mono_type_name(dec.type) : void 0;
|
|
12797
|
+
const has_destroy_fn = !!cls?.functions.find((f) => f.name === "#destroy") || !!cls?.is_class;
|
|
12798
|
+
if (cls) {
|
|
12799
|
+
const destroy_call = has_destroy_fn ? `${mono_cls_name}_destroy(${dec.name}); ` : "";
|
|
12800
|
+
if (dec.type.is_nullable) status.code += `if (${dec.name}) { ${destroy_call}free(${dec.name}); }\n`;
|
|
12801
|
+
else status.code += `${destroy_call}free(${dec.name});\n`;
|
|
12802
|
+
} else status.code += `free(${dec.name});\n`;
|
|
12803
|
+
}
|
|
12804
|
+
if (!is_destructured_field_access && !is_class_var && !dec.type.is_array && dec.type.name !== "string") {
|
|
12805
|
+
const mono_name = mono_type_name(dec.type);
|
|
12806
|
+
const struct_type = status.structs.find((s) => s.name === mono_name && !s.is_simple_type && !s.is_generic);
|
|
12807
|
+
if (struct_type && struct_needs_destroy(struct_type, status)) {
|
|
12808
|
+
if (!commented) {
|
|
12809
|
+
status.code += "\n// Auto-free\n";
|
|
12810
|
+
commented = true;
|
|
12811
|
+
}
|
|
12812
|
+
emit_struct_destroys(status, struct_type, dec.name);
|
|
12813
|
+
}
|
|
12814
|
+
}
|
|
12815
|
+
if (!!status.traits.find((t) => t.name === dec.type.name) && !is_destructured_field_access && !dec.type.is_array && dec.value) {
|
|
12816
|
+
const val_type = type_from_value_node$1(dec.value);
|
|
12817
|
+
const concrete = val_type?.name ? status.structs.find((s) => s.name === val_type.name && !s.is_simple_type && !s.is_generic) : void 0;
|
|
12818
|
+
if (concrete && struct_needs_destroy(concrete, status)) {
|
|
12819
|
+
if (!commented) {
|
|
12820
|
+
status.code += "\n// Auto-free\n";
|
|
12821
|
+
commented = true;
|
|
12822
|
+
}
|
|
12823
|
+
emit_struct_destroys(status, concrete, dec.name);
|
|
12824
|
+
}
|
|
12825
|
+
}
|
|
12826
|
+
if (!is_destructured_field_access && !is_class_var && !dec.type.is_array && is_nullable_struct_type(dec.type, status)) {
|
|
12827
|
+
const inner = status.structs.find((s) => s.name === dec.type.name);
|
|
12828
|
+
if (inner && struct_needs_destroy(inner, status)) {
|
|
12829
|
+
if (!commented) {
|
|
12830
|
+
status.code += "\n// Auto-free\n";
|
|
12831
|
+
commented = true;
|
|
12832
|
+
}
|
|
12833
|
+
const body = capture_destroys(status, inner, dec.name, ".");
|
|
12834
|
+
status.code += `if (${has_flag_name(dec.name)}) { ${body} }\n`;
|
|
12835
|
+
}
|
|
12836
|
+
}
|
|
12837
|
+
if (!is_destructured_field_access && dec.type.is_array && status.heap_array_vars?.has(dec.name)) {
|
|
12838
|
+
if (!commented) {
|
|
12839
|
+
status.code += "\n// Auto-free\n";
|
|
12840
|
+
commented = true;
|
|
12841
|
+
}
|
|
12842
|
+
const elem_name = dec.type.name;
|
|
12843
|
+
const elem_is_class = !!status.structs.find((s) => s.name === elem_name)?.is_class;
|
|
12844
|
+
const elem_is_string = elem_name === "string";
|
|
12845
|
+
const elem_c_type = elem_is_class ? `struct ${elem_name}*` : elem_name;
|
|
12846
|
+
if (elem_is_class) {
|
|
12847
|
+
status.code += `for (long _i = 0; _i < ${dec.name}->length; _i++) {\n`;
|
|
12848
|
+
status.code += `\t${elem_c_type}* _data = (${elem_c_type}*)((char*)${dec.name} + sizeof(struct Array_${elem_name}));\n`;
|
|
12849
|
+
status.code += `\t${elem_name}_destroy(_data[_i]); free(_data[_i]);\n`;
|
|
12850
|
+
status.code += `}\n`;
|
|
12851
|
+
} else if (elem_is_string) {
|
|
12852
|
+
status.code += `for (long _i = 0; _i < ${dec.name}->length; _i++) {\n`;
|
|
12853
|
+
status.code += `\tchar** _data = (char**)((char*)${dec.name} + sizeof(struct Array_string));\n`;
|
|
12854
|
+
status.code += `\tfree(_data[_i]);\n`;
|
|
12855
|
+
status.code += `}\n`;
|
|
12856
|
+
}
|
|
12857
|
+
status.code += `free(${dec.name});\n`;
|
|
12858
|
+
}
|
|
12859
|
+
if (!is_destructured_field_access && dec.type.is_array && status.stack_array_vars?.has(dec.name)) {
|
|
12860
|
+
if (!commented) {
|
|
12861
|
+
status.code += "\n// Auto-free\n";
|
|
12862
|
+
commented = true;
|
|
12863
|
+
}
|
|
12864
|
+
const elem_name = dec.type.name;
|
|
12865
|
+
const elem_struct = status.structs.find((s) => s.name === elem_name);
|
|
12866
|
+
const elem_is_class = !!elem_struct?.is_class;
|
|
12867
|
+
const elem_is_string = elem_name === "string";
|
|
12868
|
+
const elem_struct_type = status.structs.find((s) => s.name === elem_name && !s.is_simple_type && !s.is_generic);
|
|
12869
|
+
const arr_len = status.stack_array_lengths?.get(dec.name) ?? "0";
|
|
12870
|
+
if (elem_is_string) status.code += `for (long _i = 0; _i < ${arr_len}; _i++) { free(${dec.name}[_i]); }\n`;
|
|
12871
|
+
else if (elem_is_class) {
|
|
12872
|
+
if (has_destroy(elem_struct)) status.code += `for (long _i = 0; _i < ${arr_len}; _i++) { if (${dec.name}[_i]) { ${elem_name}_destroy(${dec.name}[_i]); free(${dec.name}[_i]); } }\n`;
|
|
12873
|
+
else status.code += `for (long _i = 0; _i < ${arr_len}; _i++) { free(${dec.name}[_i]); }\n`;
|
|
12874
|
+
} else if (elem_struct_type && struct_needs_destroy(elem_struct_type, status)) {
|
|
12875
|
+
status.code += `for (long _i = 0; _i < ${arr_len}; _i++) {\n`;
|
|
12876
|
+
emit_struct_destroys(status, elem_struct_type, `${dec.name}[_i]`);
|
|
12877
|
+
status.code += `}\n`;
|
|
12878
|
+
}
|
|
12879
|
+
}
|
|
12880
|
+
}
|
|
12881
|
+
}
|
|
12882
|
+
/** Name-based variant of struct_needs_destroy for callers without the StructNode. */
|
|
12883
|
+
function struct_needs_destroy_by_name(name, status) {
|
|
12884
|
+
const struct = status.structs.find((s) => s.name === name && !s.is_simple_type && !s.is_generic);
|
|
12885
|
+
if (!struct) return false;
|
|
12886
|
+
return struct_needs_destroy(struct, status);
|
|
12887
|
+
}
|
|
12888
|
+
/**
|
|
12889
|
+
* Emit destroy calls for a struct variable at scope exit. Calls the struct's
|
|
12890
|
+
* own `#destroy` first (if any), then walks each field: class-typed fields
|
|
12891
|
+
* are destroyed + freed (pointer); nested struct fields are recursively
|
|
12892
|
+
* destroyed via their own `#destroy`. Mirrors aarch64's
|
|
12893
|
+
* `emit_destroy_for_decl` + `emit_field_destroys`.
|
|
12894
|
+
*/
|
|
12895
|
+
function emit_struct_destroys(status, struct, var_expr) {
|
|
12896
|
+
if (has_destroy(struct)) status.code += `${struct.name}_destroy(&${var_expr});\n`;
|
|
12897
|
+
for (const field of struct.fields) {
|
|
12898
|
+
if (field.type.is_ref) continue;
|
|
12899
|
+
const field_struct = resolve_struct_type(field.type, status);
|
|
12900
|
+
if (!field_struct) continue;
|
|
12901
|
+
const field_expr = `${var_expr}.${field.name}`;
|
|
12902
|
+
if (field_struct.is_class) {
|
|
12903
|
+
if (has_destroy(field_struct)) status.code += `if (${field_expr}) { ${field_struct.name}_destroy(${field_expr}); free(${field_expr}); }\n`;
|
|
12904
|
+
} else if (is_nullable_struct_type(field.type, status)) {
|
|
12905
|
+
if (struct_needs_destroy(field_struct, status)) {
|
|
12906
|
+
const body = capture_destroys(status, field_struct, field_expr, ".");
|
|
12907
|
+
status.code += `if (${field_expr}_has) { ${body} }\n`;
|
|
12908
|
+
}
|
|
12909
|
+
} else emit_struct_destroys(status, field_struct, field_expr);
|
|
12910
|
+
}
|
|
12911
|
+
}
|
|
12912
|
+
/**
|
|
12913
|
+
* Capture the destroy calls for a struct value as a single line (no trailing
|
|
12914
|
+
* newline) so it can be embedded inside an `if (...) { ... }` guard. Uses
|
|
12915
|
+
* `accessor` (`.` or `->`) for nested field expressions — `.` for by-value
|
|
12916
|
+
* locals/fields, `->` when the container is a class pointer.
|
|
12917
|
+
*/
|
|
12918
|
+
function capture_destroys(status, struct, var_expr, accessor) {
|
|
12919
|
+
const before = status.code.length;
|
|
12920
|
+
if (has_destroy(struct)) status.code += `${struct.name}_destroy(&${var_expr}); `;
|
|
12921
|
+
for (const field of struct.fields) {
|
|
12922
|
+
if (field.type.is_ref) continue;
|
|
12923
|
+
const field_struct = resolve_struct_type(field.type, status);
|
|
12924
|
+
if (!field_struct) continue;
|
|
12925
|
+
const field_expr = `${var_expr}${accessor}${field.name}`;
|
|
12926
|
+
if (field_struct.is_class) {
|
|
12927
|
+
if (has_destroy(field_struct)) status.code += `if (${field_expr}) { ${field_struct.name}_destroy(${field_expr}); free(${field_expr}); } `;
|
|
12928
|
+
} else if (is_nullable_struct_type(field.type, status)) {
|
|
12929
|
+
if (struct_needs_destroy(field_struct, status)) {
|
|
12930
|
+
const inner_before = status.code.length;
|
|
12931
|
+
capture_destroys(status, field_struct, field_expr, accessor);
|
|
12932
|
+
const inner_body = status.code.substring(inner_before).trim();
|
|
12933
|
+
status.code = status.code.substring(0, inner_before);
|
|
12934
|
+
status.code += `if (${field_expr}_has) { ${inner_body} } `;
|
|
12935
|
+
}
|
|
12936
|
+
} else capture_destroys(status, field_struct, field_expr, accessor);
|
|
12937
|
+
}
|
|
12938
|
+
const captured = status.code.substring(before).replace(/\s+/g, " ").trim();
|
|
12939
|
+
status.code = status.code.substring(0, before);
|
|
12940
|
+
return captured;
|
|
12941
|
+
}
|
|
12942
|
+
//#endregion
|
|
12943
|
+
//#region ../src/build_c/utils/c_scope.ts
|
|
12944
|
+
/**
|
|
12945
|
+
* Begin a new C scope frame: allocate a fresh declarations array, push it onto
|
|
12946
|
+
* c_scope_stack, and make it the active scoped_declarations. Returns the frame
|
|
12947
|
+
* so the caller can assign it to status.scoped_declarations (mirroring the
|
|
12948
|
+
* existing save/restore idiom). Pair with leave_c_scope at scope exit.
|
|
12949
|
+
*/
|
|
12950
|
+
function enter_c_scope(status) {
|
|
12951
|
+
const frame = [];
|
|
12952
|
+
if (!status.c_scope_stack) status.c_scope_stack = [];
|
|
12953
|
+
status.c_scope_stack.push(frame);
|
|
12954
|
+
return frame;
|
|
12955
|
+
}
|
|
12956
|
+
/** Pop the current scope frame from c_scope_stack (scope-exit counterpart to enter_c_scope). */
|
|
12957
|
+
function leave_c_scope(status) {
|
|
12958
|
+
status.c_scope_stack?.pop();
|
|
12959
|
+
}
|
|
12960
|
+
/**
|
|
12961
|
+
* Find a declaration by name in the active scope frame or any enclosing frame
|
|
12962
|
+
* on c_scope_stack (innermost frame first, so a shadowing inner declaration
|
|
12963
|
+
* wins). Returns the owning frame and the declaration's index, so a mov site
|
|
12964
|
+
* can resolve and splice a declaration living in an OUTER scope — a `mov`
|
|
12965
|
+
* inside an if/loop branch must still transfer ownership of variables
|
|
12966
|
+
* declared before the branch (mirrors aarch64's all_scope_frames).
|
|
12967
|
+
*/
|
|
12968
|
+
function find_decl_in_c_scopes(status, name) {
|
|
12969
|
+
const stack = status.c_scope_stack ?? [];
|
|
12970
|
+
for (let i = stack.length - 1; i >= 0; i--) {
|
|
12971
|
+
const index = stack[i].findIndex((d) => d.name === name);
|
|
12972
|
+
if (index !== -1) return {
|
|
12973
|
+
frame: stack[i],
|
|
12974
|
+
index
|
|
12975
|
+
};
|
|
12976
|
+
}
|
|
12977
|
+
const index = status.scoped_declarations.findIndex((d) => d.name === name);
|
|
12978
|
+
return index === -1 ? void 0 : {
|
|
12979
|
+
frame: status.scoped_declarations,
|
|
12980
|
+
index
|
|
12981
|
+
};
|
|
12982
|
+
}
|
|
12983
|
+
/**
|
|
12984
|
+
* Splice a declaration out of whichever scope frame holds it (the current
|
|
12985
|
+
* frame or an enclosing frame on c_scope_stack). Used at ownership-transfer
|
|
12986
|
+
* sites (`mov` args, alias moves) — without this, a declaration left in an
|
|
12987
|
+
* outer frame is reclaimed by that scope's exit cleanup even though the
|
|
12988
|
+
* callee/new owner now owns the value (latent double-free).
|
|
12989
|
+
*/
|
|
12990
|
+
function splice_decl_from_c_scopes(status, name) {
|
|
12991
|
+
const hit = find_decl_in_c_scopes(status, name);
|
|
12992
|
+
return hit ? hit.frame.splice(hit.index, 1)[0] : void 0;
|
|
12993
|
+
}
|
|
12994
|
+
/**
|
|
12995
|
+
* Mark the current top frame as a loop body, so break/continue know how far up
|
|
12996
|
+
* the scope stack to reclaim. Call AFTER entering the loop body scope.
|
|
12997
|
+
*/
|
|
12998
|
+
function push_c_loop_frame(status) {
|
|
12999
|
+
if (!status.c_scope_stack?.length) return;
|
|
13000
|
+
if (!status.c_loop_frame_depth) status.c_loop_frame_depth = [];
|
|
13001
|
+
status.c_loop_frame_depth.push(status.c_scope_stack.length - 1);
|
|
13002
|
+
}
|
|
13003
|
+
function pop_c_loop_frame(status) {
|
|
13004
|
+
status.c_loop_frame_depth?.pop();
|
|
13005
|
+
}
|
|
13006
|
+
/**
|
|
13007
|
+
* Reclaim declarations from every scope frame on c_scope_stack — a `return`
|
|
13008
|
+
* exits ALL enclosing scopes up to the function boundary, not just the
|
|
13009
|
+
* current one, so declarations living in outer frames (e.g. a class instance
|
|
13010
|
+
* declared before an `if (...) { ... return }`) must be freed before the
|
|
13011
|
+
* jump. Nothing is cleared: sibling return statements and the fall-through
|
|
13012
|
+
* path are mutually exclusive at runtime but are ALL emitted, so every path
|
|
13013
|
+
* needs its own copy of the frees (the function-tail scope-exit auto_free
|
|
13014
|
+
* serves the fall-through). Deferred frees are handled like build_auto_free.
|
|
13015
|
+
*/
|
|
13016
|
+
function reclaim_all_c_scopes(status) {
|
|
13017
|
+
const stack = status.c_scope_stack;
|
|
13018
|
+
if (!stack?.length) free_scoped_declarations(status, status.scoped_declarations, true);
|
|
13019
|
+
else for (const frame of stack) free_scoped_declarations(status, frame, true);
|
|
13020
|
+
if (status.deferred_frees?.length) {
|
|
13021
|
+
status.code += "\n// Deferred frees\n";
|
|
13022
|
+
for (const d of status.deferred_frees) if (d.is_nullable) status.code += `if (${d.temp}) { ${d.struct_name}_destroy(${d.temp}); free(${d.temp}); }\n`;
|
|
13023
|
+
else status.code += `${d.struct_name}_destroy(${d.temp}); free(${d.temp});\n`;
|
|
13024
|
+
}
|
|
13025
|
+
}
|
|
13026
|
+
/**
|
|
13027
|
+
* Reclaim declarations from every frame between the current scope and the
|
|
13028
|
+
* innermost loop's body frame (inclusive), then return the loop body index.
|
|
13029
|
+
* Used by break/continue: the freed declarations' scope-exit auto_free either
|
|
13030
|
+
* runs on the (mutually exclusive) non-jump path or is dead code after the
|
|
13031
|
+
* jump, so this never double-frees. The innermost frame is cleared afterwards
|
|
13032
|
+
* so its dead post-jump auto_free emits nothing.
|
|
13033
|
+
*/
|
|
13034
|
+
function reclaim_to_loop_body(status) {
|
|
13035
|
+
const stack = status.c_scope_stack;
|
|
13036
|
+
const loopDepth = status.c_loop_frame_depth;
|
|
13037
|
+
if (!stack?.length || !loopDepth?.length) return void 0;
|
|
13038
|
+
const loopBodyIdx = loopDepth[loopDepth.length - 1];
|
|
13039
|
+
for (let i = stack.length - 1; i >= loopBodyIdx; i--) free_scoped_declarations(status, stack[i]);
|
|
13040
|
+
stack[stack.length - 1].length = 0;
|
|
13041
|
+
return loopBodyIdx;
|
|
13042
|
+
}
|
|
13043
|
+
//#endregion
|
|
13044
|
+
//#region ../src/build_c/build_access_node.ts
|
|
13045
|
+
/**
|
|
13046
|
+
* The C type of a single element of a `view T` slice, used to cast the
|
|
13047
|
+
* universal `nomen_view.ptr` for `.at`/`.set`. `view string`'s element is a
|
|
13048
|
+
* `char`; every other view's element is its own type name.
|
|
12249
13049
|
*/
|
|
12250
13050
|
function view_element_c_type(view_type, status) {
|
|
12251
13051
|
const elem_name = view_type.name === "string" ? "char" : view_type.name;
|
|
@@ -12591,7 +13391,9 @@ function build_access_node(node, status) {
|
|
|
12591
13391
|
const specialized = status.structs.find((s) => s.name.startsWith(sname) && !s.is_generic && s.functions.find((f) => f.name === access_func.name));
|
|
12592
13392
|
if (specialized) mono_struct_name = specialized.name;
|
|
12593
13393
|
}
|
|
12594
|
-
const
|
|
13394
|
+
const target_struct_for_method = mono_struct_name ? status.structs.find((s) => s.name === mono_struct_name && !s.is_generic) : void 0;
|
|
13395
|
+
const target_method = target_struct_for_method?.functions.find((f) => f.name === access_func.name);
|
|
13396
|
+
if (node.target.node_type === "value" && target_struct_for_method && !target_struct_for_method.is_class && target_method) drop_self_written_string_field_records(status, node.target.value, scan_self_string_field_writes(target_struct_for_method, target_method));
|
|
12595
13397
|
const self_offset = target_method?.params?.some((p) => p.is_self_param) ? 1 : 0;
|
|
12596
13398
|
let trait_default_label = "";
|
|
12597
13399
|
if (mono_struct_name && !access_func.mangled_name) {
|
|
@@ -12647,9 +13449,20 @@ function build_access_node(node, status) {
|
|
|
12647
13449
|
const param = access_func.params[idx];
|
|
12648
13450
|
if (param?.node_type === "value") {
|
|
12649
13451
|
const vname = param.value;
|
|
12650
|
-
const
|
|
12651
|
-
|
|
12652
|
-
if (
|
|
13452
|
+
const decl_hit = find_decl_in_c_scopes(status, vname);
|
|
13453
|
+
const tname = decl_hit?.frame[decl_hit.index].type?.name ?? param.type?.name;
|
|
13454
|
+
if (tname === "string") continue;
|
|
13455
|
+
const decl_struct = decl_hit ? status.structs.find((s) => s.name === tname && !s.is_simple_type) : void 0;
|
|
13456
|
+
const is_value_struct = !!decl_struct && !decl_struct.is_class;
|
|
13457
|
+
if (decl_hit) decl_hit.frame.splice(decl_hit.index, 1);
|
|
13458
|
+
if (is_value_struct) {
|
|
13459
|
+
const prefix = `${vname}.`;
|
|
13460
|
+
for (const key of Array.from(status.heap_string_fields ?? [])) if (key.startsWith(prefix)) {
|
|
13461
|
+
if (!status.pending_string_releases) status.pending_string_releases = [];
|
|
13462
|
+
status.pending_string_releases.push(`free(${key});`);
|
|
13463
|
+
status.heap_string_fields.delete(key);
|
|
13464
|
+
}
|
|
13465
|
+
}
|
|
12653
13466
|
}
|
|
12654
13467
|
}
|
|
12655
13468
|
break;
|
|
@@ -12667,312 +13480,80 @@ function resolve_access_field_type(node, status) {
|
|
|
12667
13480
|
else if (name === "self" && status.current_struct) base_type = new Type(status.current_struct.name);
|
|
12668
13481
|
else if (status.variable_types?.has(name)) base_type = status.variable_types.get(name);
|
|
12669
13482
|
} else if (node.target.node_type === "access") base_type = resolve_access_field_type(node.target, status);
|
|
12670
|
-
if (!base_type?.name) return void 0;
|
|
12671
|
-
return (status.structs.find((s) => s.name === base_type.name && !s.is_simple_type)?.fields.find((f) => f.name === field_name))?.type;
|
|
12672
|
-
}
|
|
12673
|
-
function emit_string_length(target, status) {
|
|
12674
|
-
if (is_owned_heap_temp(target, status)) {
|
|
12675
|
-
const id = status.label_counter = (status.label_counter ?? 0) + 1;
|
|
12676
|
-
const tmp = `_slen_${id}`;
|
|
12677
|
-
status.code += `({ char* ${tmp} = `;
|
|
12678
|
-
build_node(target, status);
|
|
12679
|
-
status.code += `; long _slr_${id} = (long)strlen(${tmp}); free(${tmp}); _slr_${id}; })`;
|
|
12680
|
-
return;
|
|
12681
|
-
}
|
|
12682
|
-
status.code += "((long)strlen(";
|
|
12683
|
-
build_node(target, status);
|
|
12684
|
-
status.code += "))";
|
|
12685
|
-
}
|
|
12686
|
-
/**
|
|
12687
|
-
* Resolve the type of an access-chain expression by walking through the
|
|
12688
|
-
* monomorphized structs (field types and method return types). Used when a
|
|
12689
|
-
* cached node type is stale (a generic type param like "T" that wasn't
|
|
12690
|
-
* substituted because it belonged to a nested generic, not the enclosing one).
|
|
12691
|
-
*/
|
|
12692
|
-
function resolve_access_type(node, status) {
|
|
12693
|
-
const inner = node.access;
|
|
12694
|
-
if (inner.node_type === "access_func") {
|
|
12695
|
-
const access_func = inner;
|
|
12696
|
-
let base_type = resolve_receiver_type(node.target, status);
|
|
12697
|
-
if (!base_type?.name) return null;
|
|
12698
|
-
const mono_name = mono_type_name(base_type);
|
|
12699
|
-
const struct = status.structs.find((s) => s.name === mono_name && !s.is_generic) || status.structs.find((s) => s.name === base_type.name);
|
|
12700
|
-
if (!struct) return null;
|
|
12701
|
-
return struct.functions.find((f) => f.name === access_func.name || f.name === `#${access_func.name}`)?.return_type || null;
|
|
12702
|
-
}
|
|
12703
|
-
if (inner.node_type !== "access_field") return null;
|
|
12704
|
-
const field_name = inner.name;
|
|
12705
|
-
let base_type = resolve_receiver_type(node.target, status);
|
|
12706
|
-
if (!base_type?.name) return null;
|
|
12707
|
-
const struct = status.structs.find((s) => s.name === base_type.name);
|
|
12708
|
-
if (!struct) return null;
|
|
12709
|
-
return struct.fields.find((f) => f.name === field_name)?.type || null;
|
|
12710
|
-
}
|
|
12711
|
-
function resolve_receiver_type(node, status) {
|
|
12712
|
-
if (node.node_type === "value") {
|
|
12713
|
-
const name = node.value;
|
|
12714
|
-
const vtype = node.type;
|
|
12715
|
-
if (vtype?.name && status.structs.find((s) => s.name === vtype.name)) return vtype;
|
|
12716
|
-
if (name === "self" && status.current_struct) return new Type(status.current_struct.name);
|
|
12717
|
-
return vtype?.name ? vtype : null;
|
|
12718
|
-
}
|
|
12719
|
-
if (node.node_type === "access") {
|
|
12720
|
-
const resolved = resolve_access_type(node, status);
|
|
12721
|
-
if (resolved) return resolved;
|
|
12722
|
-
return type_from_value_node$1(node);
|
|
12723
|
-
}
|
|
12724
|
-
return null;
|
|
12725
|
-
}
|
|
12726
|
-
//#endregion
|
|
12727
|
-
//#region ../src/build_c/build_array_values_node.ts
|
|
12728
|
-
function build_array_values_node(node, status) {
|
|
12729
|
-
status.code += `{`;
|
|
12730
|
-
const elem_is_string = node.type?.name === "string";
|
|
12731
|
-
node.values.forEach((value, i) => {
|
|
12732
|
-
if (i > 0) status.code += ", ";
|
|
12733
|
-
if (elem_is_string && value.node_type === "value" && value.value.length >= 2 && value.value.startsWith("\"") && value.value.endsWith("\"")) {
|
|
12734
|
-
status.code += `nomen_strdup_wrap(`;
|
|
12735
|
-
build_node(value, status);
|
|
12736
|
-
status.code += `)`;
|
|
12737
|
-
} else build_node(value, status);
|
|
12738
|
-
});
|
|
12739
|
-
status.code += `}`;
|
|
12740
|
-
}
|
|
12741
|
-
//#endregion
|
|
12742
|
-
//#region ../src/build_c/utils/is_string_borrow.ts
|
|
12743
|
-
/**
|
|
12744
|
-
* Whether a value node denotes a BORROWED string — a pointer into storage the
|
|
12745
|
-
* receiver does not own (an array element accessed via `.at()`/`.first()`, or
|
|
12746
|
-
* `init.args.at(n)` which points into the C runtime's `argv`). Borrowed
|
|
12747
|
-
* strings must NOT be freed by auto_free or by reassignment: freeing them
|
|
12748
|
-
* reclaims memory owned by the container (or argv), crashing with
|
|
12749
|
-
* "pointer being freed was not allocated". Mirrors aarch64's `heap_strings`
|
|
12750
|
-
* ownership tracking, which only frees freshly-allocated strings.
|
|
12751
|
-
*/
|
|
12752
|
-
function is_string_borrow(node) {
|
|
12753
|
-
if (!node || node.node_type !== "access") return false;
|
|
12754
|
-
const access = node.access;
|
|
12755
|
-
if (access.node_type !== "access_func") return false;
|
|
12756
|
-
const func = access;
|
|
12757
|
-
return (func.name === "at" || func.name === "first") && !func.owned_return;
|
|
12758
|
-
}
|
|
12759
|
-
//#endregion
|
|
12760
|
-
//#region ../src/build_c/build_auto_free.ts
|
|
12761
|
-
function build_auto_free(status) {
|
|
12762
|
-
free_scoped_declarations(status, status.scoped_declarations);
|
|
12763
|
-
if (status.deferred_frees?.length) {
|
|
12764
|
-
status.code += "\n// Deferred frees\n";
|
|
12765
|
-
for (const d of status.deferred_frees) if (d.is_nullable) status.code += `if (${d.temp}) { ${d.struct_name}_destroy(${d.temp}); free(${d.temp}); }\n`;
|
|
12766
|
-
else status.code += `${d.struct_name}_destroy(${d.temp}); free(${d.temp});\n`;
|
|
12767
|
-
status.deferred_frees.length = 0;
|
|
12768
|
-
}
|
|
12769
|
-
status.scoped_declarations = [];
|
|
12770
|
-
}
|
|
12771
|
-
/**
|
|
12772
|
-
* Emit scope-exit free/destroy code for a list of declarations. Extracted from
|
|
12773
|
-
* build_auto_free so that break/continue can reclaim declarations from the
|
|
12774
|
-
* current scope AND enclosing scopes (up to the loop body) before jumping —
|
|
12775
|
-
* see build_break_node. Does NOT process deferred_frees or clear the list
|
|
12776
|
-
* (those are scope-exit-only concerns handled by build_auto_free).
|
|
12777
|
-
*/
|
|
12778
|
-
function free_scoped_declarations(status, decls) {
|
|
12779
|
-
let commented = false;
|
|
12780
|
-
for (const dec of decls) {
|
|
12781
|
-
const struct = status.structs.find((s) => s.name === dec.type.name);
|
|
12782
|
-
if (struct && struct.traits.includes("Disposable")) {
|
|
12783
|
-
const trait = status.traits.find((t) => t.name === "Disposable");
|
|
12784
|
-
const func = trait?.functions.find((f) => f.name == "dispose");
|
|
12785
|
-
if (trait && func) {
|
|
12786
|
-
if (!commented) {
|
|
12787
|
-
status.code += "\n// Auto-free\n";
|
|
12788
|
-
commented = true;
|
|
12789
|
-
}
|
|
12790
|
-
const cast = "(void *(*)(void *))";
|
|
12791
|
-
const traitIndex = status.traits.indexOf(trait);
|
|
12792
|
-
const funcIndex = trait.functions.indexOf(func);
|
|
12793
|
-
status.code += `(${cast}_get_trait_func((void *)&${dec.name}, ${traitIndex}, ${funcIndex}))(&${dec.name});\n`;
|
|
12794
|
-
}
|
|
12795
|
-
}
|
|
12796
|
-
const is_destructured_field_access = dec.value?.node_type === "access" && dec.value.access.node_type === "access_field" && !dec.value.is_moved;
|
|
12797
|
-
const is_borrowed_string = is_string_borrow(dec.value) || !!status.string_borrow_vars?.has(dec.name);
|
|
12798
|
-
const value_is_heap_string = dec.type.name === "string" && (dec.value?.node_type === "access" && dec.value.access.node_type === "access_func" || dec.value?.node_type === "func_call");
|
|
12799
|
-
const dec_value = dec.value;
|
|
12800
|
-
const dec_val_is_string_literal = dec.value?.node_type === "value" && dec_value.value.length >= 2 && dec_value.value.startsWith("\"") && dec_value.value.endsWith("\"");
|
|
12801
|
-
const dec_val_is_heap_string_var = dec.value?.node_type === "value" && !dec_val_is_string_literal && !!status.scoped_declarations.find((d) => d.name === dec_value.value);
|
|
12802
|
-
const was_strdup_string_var = dec.declaration === "var" && !dec.type.is_view && !is_borrowed_string && (dec_val_is_string_literal || dec_val_is_heap_string_var);
|
|
12803
|
-
const is_normalized_join_string = !!status.string_join_owned_vars?.has(dec.name);
|
|
12804
|
-
const dec_struct = status.structs.find((s) => s.name === dec.type.name);
|
|
12805
|
-
const is_class_var = !!dec_struct?.is_class;
|
|
12806
|
-
const trait_class_trait = status.trait_class_locals?.get(dec.name);
|
|
12807
|
-
if (trait_class_trait !== void 0 && !is_destructured_field_access) {
|
|
12808
|
-
if (!commented) {
|
|
12809
|
-
status.code += "\n// Auto-free\n";
|
|
12810
|
-
commented = true;
|
|
12811
|
-
}
|
|
12812
|
-
if (dec.type.is_nullable) status.code += `if (${dec.name}) { ${trait_class_trait}_destroy(${dec.name}); free(${dec.name}); }\n`;
|
|
12813
|
-
else status.code += `${trait_class_trait}_destroy(${dec.name}); free(${dec.name});\n`;
|
|
12814
|
-
}
|
|
12815
|
-
if (!is_destructured_field_access && !is_borrowed_string && (!dec.type.is_static || value_is_heap_string || was_strdup_string_var || is_normalized_join_string) && dec.type.name === "string" && !dec.type.is_array) {
|
|
12816
|
-
if (!commented) {
|
|
12817
|
-
status.code += "\n// Auto-free\n";
|
|
12818
|
-
commented = true;
|
|
12819
|
-
}
|
|
12820
|
-
status.code += `free(${dec.name});\n`;
|
|
12821
|
-
}
|
|
12822
|
-
if (!is_destructured_field_access && is_class_var && !dec.type.is_array) {
|
|
12823
|
-
if (!commented) {
|
|
12824
|
-
status.code += "\n// Auto-free\n";
|
|
12825
|
-
commented = true;
|
|
12826
|
-
}
|
|
12827
|
-
const cls = struct ?? dec_struct;
|
|
12828
|
-
const mono_cls_name = cls ? mono_type_name(dec.type) : void 0;
|
|
12829
|
-
const has_destroy_fn = !!cls?.functions.find((f) => f.name === "#destroy") || !!cls?.is_class;
|
|
12830
|
-
if (cls) {
|
|
12831
|
-
const destroy_call = has_destroy_fn ? `${mono_cls_name}_destroy(${dec.name}); ` : "";
|
|
12832
|
-
if (dec.type.is_nullable) status.code += `if (${dec.name}) { ${destroy_call}free(${dec.name}); }\n`;
|
|
12833
|
-
else status.code += `${destroy_call}free(${dec.name});\n`;
|
|
12834
|
-
} else status.code += `free(${dec.name});\n`;
|
|
12835
|
-
}
|
|
12836
|
-
if (!is_destructured_field_access && !is_class_var && !dec.type.is_array && dec.type.name !== "string") {
|
|
12837
|
-
const mono_name = mono_type_name(dec.type);
|
|
12838
|
-
const struct_type = status.structs.find((s) => s.name === mono_name && !s.is_simple_type && !s.is_generic);
|
|
12839
|
-
if (struct_type && struct_needs_destroy(struct_type, status)) {
|
|
12840
|
-
if (!commented) {
|
|
12841
|
-
status.code += "\n// Auto-free\n";
|
|
12842
|
-
commented = true;
|
|
12843
|
-
}
|
|
12844
|
-
emit_struct_destroys(status, struct_type, dec.name);
|
|
12845
|
-
}
|
|
12846
|
-
}
|
|
12847
|
-
if (!!status.traits.find((t) => t.name === dec.type.name) && !is_destructured_field_access && !dec.type.is_array && dec.value) {
|
|
12848
|
-
const val_type = type_from_value_node$1(dec.value);
|
|
12849
|
-
const concrete = val_type?.name ? status.structs.find((s) => s.name === val_type.name && !s.is_simple_type && !s.is_generic) : void 0;
|
|
12850
|
-
if (concrete && struct_needs_destroy(concrete, status)) {
|
|
12851
|
-
if (!commented) {
|
|
12852
|
-
status.code += "\n// Auto-free\n";
|
|
12853
|
-
commented = true;
|
|
12854
|
-
}
|
|
12855
|
-
emit_struct_destroys(status, concrete, dec.name);
|
|
12856
|
-
}
|
|
12857
|
-
}
|
|
12858
|
-
if (!is_destructured_field_access && !is_class_var && !dec.type.is_array && is_nullable_struct_type(dec.type, status)) {
|
|
12859
|
-
const inner = status.structs.find((s) => s.name === dec.type.name);
|
|
12860
|
-
if (inner && struct_needs_destroy(inner, status)) {
|
|
12861
|
-
if (!commented) {
|
|
12862
|
-
status.code += "\n// Auto-free\n";
|
|
12863
|
-
commented = true;
|
|
12864
|
-
}
|
|
12865
|
-
const body = capture_destroys(status, inner, dec.name, ".");
|
|
12866
|
-
status.code += `if (${has_flag_name(dec.name)}) { ${body} }\n`;
|
|
12867
|
-
}
|
|
12868
|
-
}
|
|
12869
|
-
if (!is_destructured_field_access && dec.type.is_array && status.heap_array_vars?.has(dec.name)) {
|
|
12870
|
-
if (!commented) {
|
|
12871
|
-
status.code += "\n// Auto-free\n";
|
|
12872
|
-
commented = true;
|
|
12873
|
-
}
|
|
12874
|
-
const elem_name = dec.type.name;
|
|
12875
|
-
const elem_is_class = !!status.structs.find((s) => s.name === elem_name)?.is_class;
|
|
12876
|
-
const elem_is_string = elem_name === "string";
|
|
12877
|
-
const elem_c_type = elem_is_class ? `struct ${elem_name}*` : elem_name;
|
|
12878
|
-
if (elem_is_class) {
|
|
12879
|
-
status.code += `for (long _i = 0; _i < ${dec.name}->length; _i++) {\n`;
|
|
12880
|
-
status.code += `\t${elem_c_type}* _data = (${elem_c_type}*)((char*)${dec.name} + sizeof(struct Array_${elem_name}));\n`;
|
|
12881
|
-
status.code += `\t${elem_name}_destroy(_data[_i]); free(_data[_i]);\n`;
|
|
12882
|
-
status.code += `}\n`;
|
|
12883
|
-
} else if (elem_is_string) {
|
|
12884
|
-
status.code += `for (long _i = 0; _i < ${dec.name}->length; _i++) {\n`;
|
|
12885
|
-
status.code += `\tchar** _data = (char**)((char*)${dec.name} + sizeof(struct Array_string));\n`;
|
|
12886
|
-
status.code += `\tfree(_data[_i]);\n`;
|
|
12887
|
-
status.code += `}\n`;
|
|
12888
|
-
}
|
|
12889
|
-
status.code += `free(${dec.name});\n`;
|
|
12890
|
-
}
|
|
12891
|
-
if (!is_destructured_field_access && dec.type.is_array && status.stack_array_vars?.has(dec.name)) {
|
|
12892
|
-
if (!commented) {
|
|
12893
|
-
status.code += "\n// Auto-free\n";
|
|
12894
|
-
commented = true;
|
|
12895
|
-
}
|
|
12896
|
-
const elem_name = dec.type.name;
|
|
12897
|
-
const elem_struct = status.structs.find((s) => s.name === elem_name);
|
|
12898
|
-
const elem_is_class = !!elem_struct?.is_class;
|
|
12899
|
-
const elem_is_string = elem_name === "string";
|
|
12900
|
-
const elem_struct_type = status.structs.find((s) => s.name === elem_name && !s.is_simple_type && !s.is_generic);
|
|
12901
|
-
const arr_len = status.stack_array_lengths?.get(dec.name) ?? "0";
|
|
12902
|
-
if (elem_is_string) status.code += `for (long _i = 0; _i < ${arr_len}; _i++) { free(${dec.name}[_i]); }\n`;
|
|
12903
|
-
else if (elem_is_class) {
|
|
12904
|
-
if (has_destroy(elem_struct)) status.code += `for (long _i = 0; _i < ${arr_len}; _i++) { if (${dec.name}[_i]) { ${elem_name}_destroy(${dec.name}[_i]); free(${dec.name}[_i]); } }\n`;
|
|
12905
|
-
else status.code += `for (long _i = 0; _i < ${arr_len}; _i++) { free(${dec.name}[_i]); }\n`;
|
|
12906
|
-
} else if (elem_struct_type && struct_needs_destroy(elem_struct_type, status)) {
|
|
12907
|
-
status.code += `for (long _i = 0; _i < ${arr_len}; _i++) {\n`;
|
|
12908
|
-
emit_struct_destroys(status, elem_struct_type, `${dec.name}[_i]`);
|
|
12909
|
-
status.code += `}\n`;
|
|
12910
|
-
}
|
|
12911
|
-
}
|
|
12912
|
-
}
|
|
13483
|
+
if (!base_type?.name) return void 0;
|
|
13484
|
+
return (status.structs.find((s) => s.name === base_type.name && !s.is_simple_type)?.fields.find((f) => f.name === field_name))?.type;
|
|
12913
13485
|
}
|
|
12914
|
-
|
|
12915
|
-
|
|
12916
|
-
|
|
12917
|
-
|
|
12918
|
-
|
|
13486
|
+
function emit_string_length(target, status) {
|
|
13487
|
+
if (is_owned_heap_temp(target, status)) {
|
|
13488
|
+
const id = status.label_counter = (status.label_counter ?? 0) + 1;
|
|
13489
|
+
const tmp = `_slen_${id}`;
|
|
13490
|
+
status.code += `({ char* ${tmp} = `;
|
|
13491
|
+
build_node(target, status);
|
|
13492
|
+
status.code += `; long _slr_${id} = (long)strlen(${tmp}); free(${tmp}); _slr_${id}; })`;
|
|
13493
|
+
return;
|
|
13494
|
+
}
|
|
13495
|
+
status.code += "((long)strlen(";
|
|
13496
|
+
build_node(target, status);
|
|
13497
|
+
status.code += "))";
|
|
12919
13498
|
}
|
|
12920
13499
|
/**
|
|
12921
|
-
*
|
|
12922
|
-
*
|
|
12923
|
-
*
|
|
12924
|
-
*
|
|
12925
|
-
* `emit_destroy_for_decl` + `emit_field_destroys`.
|
|
13500
|
+
* Resolve the type of an access-chain expression by walking through the
|
|
13501
|
+
* monomorphized structs (field types and method return types). Used when a
|
|
13502
|
+
* cached node type is stale (a generic type param like "T" that wasn't
|
|
13503
|
+
* substituted because it belonged to a nested generic, not the enclosing one).
|
|
12926
13504
|
*/
|
|
12927
|
-
function
|
|
12928
|
-
|
|
12929
|
-
|
|
12930
|
-
|
|
12931
|
-
|
|
12932
|
-
if (!
|
|
12933
|
-
const
|
|
12934
|
-
|
|
12935
|
-
|
|
12936
|
-
|
|
12937
|
-
if (struct_needs_destroy(field_struct, status)) {
|
|
12938
|
-
const body = capture_destroys(status, field_struct, field_expr, ".");
|
|
12939
|
-
status.code += `if (${field_expr}_has) { ${body} }\n`;
|
|
12940
|
-
}
|
|
12941
|
-
} else emit_struct_destroys(status, field_struct, field_expr);
|
|
13505
|
+
function resolve_access_type(node, status) {
|
|
13506
|
+
const inner = node.access;
|
|
13507
|
+
if (inner.node_type === "access_func") {
|
|
13508
|
+
const access_func = inner;
|
|
13509
|
+
let base_type = resolve_receiver_type(node.target, status);
|
|
13510
|
+
if (!base_type?.name) return null;
|
|
13511
|
+
const mono_name = mono_type_name(base_type);
|
|
13512
|
+
const struct = status.structs.find((s) => s.name === mono_name && !s.is_generic) || status.structs.find((s) => s.name === base_type.name);
|
|
13513
|
+
if (!struct) return null;
|
|
13514
|
+
return struct.functions.find((f) => f.name === access_func.name || f.name === `#${access_func.name}`)?.return_type || null;
|
|
12942
13515
|
}
|
|
13516
|
+
if (inner.node_type !== "access_field") return null;
|
|
13517
|
+
const field_name = inner.name;
|
|
13518
|
+
let base_type = resolve_receiver_type(node.target, status);
|
|
13519
|
+
if (!base_type?.name) return null;
|
|
13520
|
+
const struct = status.structs.find((s) => s.name === base_type.name);
|
|
13521
|
+
if (!struct) return null;
|
|
13522
|
+
return struct.fields.find((f) => f.name === field_name)?.type || null;
|
|
12943
13523
|
}
|
|
12944
|
-
|
|
12945
|
-
|
|
12946
|
-
|
|
12947
|
-
|
|
12948
|
-
|
|
12949
|
-
|
|
12950
|
-
|
|
12951
|
-
const before = status.code.length;
|
|
12952
|
-
if (has_destroy(struct)) status.code += `${struct.name}_destroy(&${var_expr}); `;
|
|
12953
|
-
for (const field of struct.fields) {
|
|
12954
|
-
if (field.type.is_ref) continue;
|
|
12955
|
-
const field_struct = resolve_struct_type(field.type, status);
|
|
12956
|
-
if (!field_struct) continue;
|
|
12957
|
-
const field_expr = `${var_expr}${accessor}${field.name}`;
|
|
12958
|
-
if (field_struct.is_class) {
|
|
12959
|
-
if (has_destroy(field_struct)) status.code += `if (${field_expr}) { ${field_struct.name}_destroy(${field_expr}); free(${field_expr}); } `;
|
|
12960
|
-
} else if (is_nullable_struct_type(field.type, status)) {
|
|
12961
|
-
if (struct_needs_destroy(field_struct, status)) {
|
|
12962
|
-
const inner_before = status.code.length;
|
|
12963
|
-
capture_destroys(status, field_struct, field_expr, accessor);
|
|
12964
|
-
const inner_body = status.code.substring(inner_before).trim();
|
|
12965
|
-
status.code = status.code.substring(0, inner_before);
|
|
12966
|
-
status.code += `if (${field_expr}_has) { ${inner_body} } `;
|
|
12967
|
-
}
|
|
12968
|
-
} else capture_destroys(status, field_struct, field_expr, accessor);
|
|
13524
|
+
function resolve_receiver_type(node, status) {
|
|
13525
|
+
if (node.node_type === "value") {
|
|
13526
|
+
const name = node.value;
|
|
13527
|
+
const vtype = node.type;
|
|
13528
|
+
if (vtype?.name && status.structs.find((s) => s.name === vtype.name)) return vtype;
|
|
13529
|
+
if (name === "self" && status.current_struct) return new Type(status.current_struct.name);
|
|
13530
|
+
return vtype?.name ? vtype : null;
|
|
12969
13531
|
}
|
|
12970
|
-
|
|
12971
|
-
|
|
12972
|
-
|
|
13532
|
+
if (node.node_type === "access") {
|
|
13533
|
+
const resolved = resolve_access_type(node, status);
|
|
13534
|
+
if (resolved) return resolved;
|
|
13535
|
+
return type_from_value_node$1(node);
|
|
13536
|
+
}
|
|
13537
|
+
return null;
|
|
13538
|
+
}
|
|
13539
|
+
//#endregion
|
|
13540
|
+
//#region ../src/build_c/build_array_values_node.ts
|
|
13541
|
+
function build_array_values_node(node, status) {
|
|
13542
|
+
status.code += `{`;
|
|
13543
|
+
const elem_is_string = node.type?.name === "string";
|
|
13544
|
+
node.values.forEach((value, i) => {
|
|
13545
|
+
if (i > 0) status.code += ", ";
|
|
13546
|
+
if (elem_is_string && value.node_type === "value" && value.value.length >= 2 && value.value.startsWith("\"") && value.value.endsWith("\"")) {
|
|
13547
|
+
status.code += `nomen_strdup_wrap(`;
|
|
13548
|
+
build_node(value, status);
|
|
13549
|
+
status.code += `)`;
|
|
13550
|
+
} else build_node(value, status);
|
|
13551
|
+
});
|
|
13552
|
+
status.code += `}`;
|
|
12973
13553
|
}
|
|
12974
13554
|
//#endregion
|
|
12975
13555
|
//#region ../src/build_c/build_assignment_node.ts
|
|
13556
|
+
let string_field_counter = 0;
|
|
12976
13557
|
function build_assignment_node(node, status) {
|
|
12977
13558
|
if (node.left_value.node_type === "access") {
|
|
12978
13559
|
const accessNode = node.left_value;
|
|
@@ -13009,26 +13590,53 @@ function build_assignment_node(node, status) {
|
|
|
13009
13590
|
status.code = status.code.substring(0, before_len);
|
|
13010
13591
|
if (field_type?.is_nullable) status.code += `if (${field_access}) { ${field_struct.name}_destroy(${field_access}); free(${field_access}); }\n`;
|
|
13011
13592
|
else status.code += `${field_struct.name}_destroy(${field_access}); free(${field_access});\n`;
|
|
13012
|
-
if (node.right_value.node_type === "value")
|
|
13013
|
-
|
|
13014
|
-
|
|
13015
|
-
|
|
13016
|
-
|
|
13593
|
+
if (node.right_value.node_type === "value") splice_decl_from_c_scopes(status, node.right_value.value);
|
|
13594
|
+
}
|
|
13595
|
+
}
|
|
13596
|
+
}
|
|
13597
|
+
if (!node.operator && node.left_value.node_type === "access" && node.left_value.access.node_type === "access_field" && node.left_value.access.type?.name === "string" && !node.left_value.access.type?.is_ref && !node.left_value.access.type?.is_array) {
|
|
13598
|
+
const access_lhs = node.left_value;
|
|
13599
|
+
const field_access_node = access_lhs.access;
|
|
13600
|
+
let target_type = type_from_value_node$1(access_lhs.target);
|
|
13601
|
+
if (!target_type?.name && access_lhs.target.node_type === "value" && access_lhs.target.value === "self" && status.current_struct) target_type = new Type(status.current_struct.name);
|
|
13602
|
+
const target_struct = target_type?.name ? status.structs.find((s) => s.name === target_type.name && !s.is_simple_type) : null;
|
|
13603
|
+
const target_var = access_lhs.target.node_type === "value" ? access_lhs.target.value : "";
|
|
13604
|
+
const self_target = target_var === "self";
|
|
13605
|
+
const tracked_key = `${target_var}.${field_access_node.name}`;
|
|
13606
|
+
const old_was_heap = !!target_struct?.is_class || !!status.heap_string_fields?.has(tracked_key);
|
|
13607
|
+
if (target_struct && target_var && (!self_target || target_struct.is_class)) {
|
|
13608
|
+
const fresh_heap = is_owned_heap_temp(node.right_value, status);
|
|
13609
|
+
const before_len = status.code.length;
|
|
13610
|
+
build_node(node.left_value, status);
|
|
13611
|
+
const field_access = status.code.substring(before_len);
|
|
13612
|
+
status.code = status.code.substring(0, before_len);
|
|
13613
|
+
const temp = `_nomen_strfield_${string_field_counter++}`;
|
|
13614
|
+
status.code += `{\nchar* ${temp} = `;
|
|
13615
|
+
if (fresh_heap) build_node(node.right_value, status);
|
|
13616
|
+
else {
|
|
13617
|
+
status.code += `strdup(`;
|
|
13618
|
+
build_node(node.right_value, status);
|
|
13619
|
+
status.code += `)`;
|
|
13620
|
+
}
|
|
13621
|
+
status.code += `;\n`;
|
|
13622
|
+
if (old_was_heap) status.code += `free(${field_access});\n`;
|
|
13623
|
+
status.code += `${field_access} = ${temp};\n}\n`;
|
|
13624
|
+
if (!target_struct.is_class) {
|
|
13625
|
+
if (!status.heap_string_fields) status.heap_string_fields = /* @__PURE__ */ new Set();
|
|
13626
|
+
status.heap_string_fields.add(tracked_key);
|
|
13017
13627
|
}
|
|
13628
|
+
return;
|
|
13018
13629
|
}
|
|
13019
13630
|
}
|
|
13020
13631
|
if (!node.operator && node.left_value.node_type === "value" && is_string_borrow(node.right_value)) {
|
|
13021
13632
|
const lhs_name = node.left_value.value;
|
|
13022
|
-
const
|
|
13023
|
-
if ((
|
|
13633
|
+
const lhs_hit = find_decl_in_c_scopes(status, lhs_name);
|
|
13634
|
+
if (((lhs_hit ? lhs_hit.frame[lhs_hit.index] : void 0)?.type || status.variable_types?.get(lhs_name))?.name === "string") {
|
|
13024
13635
|
const was_borrow = !!status.string_borrow_vars?.has(lhs_name);
|
|
13025
13636
|
if (!status.string_borrow_vars) status.string_borrow_vars = /* @__PURE__ */ new Set();
|
|
13026
13637
|
status.string_borrow_vars.add(lhs_name);
|
|
13027
13638
|
if (!was_borrow) {
|
|
13028
|
-
if (
|
|
13029
|
-
const idx = status.scoped_declarations.indexOf(lhs_decl);
|
|
13030
|
-
if (idx !== -1) status.scoped_declarations.splice(idx, 1);
|
|
13031
|
-
}
|
|
13639
|
+
if (lhs_hit) lhs_hit.frame.splice(lhs_hit.index, 1);
|
|
13032
13640
|
status.code += `free(${lhs_name});\n`;
|
|
13033
13641
|
}
|
|
13034
13642
|
}
|
|
@@ -13113,21 +13721,15 @@ function build_assignment_node(node, status) {
|
|
|
13113
13721
|
}
|
|
13114
13722
|
if (rhs_is_bare_value) {
|
|
13115
13723
|
if (lhs_is_class) {
|
|
13116
|
-
if (!node.swap)
|
|
13117
|
-
|
|
13118
|
-
const rhs_idx = status.scoped_declarations.findIndex((d) => d.name === rhs_name);
|
|
13119
|
-
if (rhs_idx !== -1) status.scoped_declarations.splice(rhs_idx, 1);
|
|
13120
|
-
}
|
|
13121
|
-
} else if (lhs_decl) {
|
|
13122
|
-
const idx = status.scoped_declarations.indexOf(lhs_decl);
|
|
13123
|
-
if (idx !== -1) status.scoped_declarations.splice(idx, 1);
|
|
13124
|
-
}
|
|
13724
|
+
if (!node.swap) splice_decl_from_c_scopes(status, rhs.value);
|
|
13725
|
+
} else if (lhs_decl) splice_decl_from_c_scopes(status, lhs_name);
|
|
13125
13726
|
}
|
|
13126
13727
|
}
|
|
13127
13728
|
}
|
|
13128
13729
|
if (!node.operator && node.left_value.node_type === "value") {
|
|
13129
13730
|
const lhs_name = node.left_value.value;
|
|
13130
|
-
const
|
|
13731
|
+
const lhs_hit = find_decl_in_c_scopes(status, lhs_name);
|
|
13732
|
+
const lhs_decl = lhs_hit ? lhs_hit.frame[lhs_hit.index] : void 0;
|
|
13131
13733
|
if (lhs_decl) {
|
|
13132
13734
|
const lhs_struct = lhs_decl.type?.name ? status.structs.find((s) => s.name === lhs_decl.type.name && !s.is_simple_type && !s.is_class) : null;
|
|
13133
13735
|
const lhs_mono = lhs_decl.type ? mono_type_name(lhs_decl.type) : void 0;
|
|
@@ -13137,9 +13739,7 @@ function build_assignment_node(node, status) {
|
|
|
13137
13739
|
if (rhs.node_type === "value" && rhs.is_moved) {
|
|
13138
13740
|
const mov_struct_type = lhs_mono_struct ?? lhs_struct;
|
|
13139
13741
|
if (mov_struct_type && struct_needs_destroy_by_name(mov_struct_type.name, status)) emit_struct_destroys(status, mov_struct_type, lhs_name);
|
|
13140
|
-
|
|
13141
|
-
const rhs_idx = status.scoped_declarations.findIndex((d) => d.name === rhs_name);
|
|
13142
|
-
if (rhs_idx !== -1) status.scoped_declarations.splice(rhs_idx, 1);
|
|
13742
|
+
splice_decl_from_c_scopes(status, rhs.value);
|
|
13143
13743
|
} else {
|
|
13144
13744
|
const struct_type = lhs_mono_struct ?? lhs_struct;
|
|
13145
13745
|
const needs_destroy = struct_type ? struct_needs_destroy_by_name(struct_type.name, status) : false;
|
|
@@ -13148,12 +13748,8 @@ function build_assignment_node(node, status) {
|
|
|
13148
13748
|
if (needs_destroy) emit_struct_destroys(status, struct_type, lhs_name);
|
|
13149
13749
|
} else if (is_self_method_call(node, lhs_name)) {} else if (!rhs_references_var(node, lhs_name)) {
|
|
13150
13750
|
if (needs_destroy) emit_struct_destroys(status, struct_type, lhs_name);
|
|
13151
|
-
|
|
13152
|
-
|
|
13153
|
-
} else {
|
|
13154
|
-
const idx = status.scoped_declarations.indexOf(lhs_decl);
|
|
13155
|
-
if (idx !== -1) status.scoped_declarations.splice(idx, 1);
|
|
13156
|
-
}
|
|
13751
|
+
if (lhs_hit) lhs_hit.frame.splice(lhs_hit.index, 1);
|
|
13752
|
+
} else if (lhs_hit) lhs_hit.frame.splice(lhs_hit.index, 1);
|
|
13157
13753
|
}
|
|
13158
13754
|
}
|
|
13159
13755
|
}
|
|
@@ -13450,53 +14046,6 @@ function embedded_value_struct(type, status) {
|
|
|
13450
14046
|
return s;
|
|
13451
14047
|
}
|
|
13452
14048
|
//#endregion
|
|
13453
|
-
//#region ../src/build_c/utils/c_scope.ts
|
|
13454
|
-
/**
|
|
13455
|
-
* Begin a new C scope frame: allocate a fresh declarations array, push it onto
|
|
13456
|
-
* c_scope_stack, and make it the active scoped_declarations. Returns the frame
|
|
13457
|
-
* so the caller can assign it to status.scoped_declarations (mirroring the
|
|
13458
|
-
* existing save/restore idiom). Pair with leave_c_scope at scope exit.
|
|
13459
|
-
*/
|
|
13460
|
-
function enter_c_scope(status) {
|
|
13461
|
-
const frame = [];
|
|
13462
|
-
if (!status.c_scope_stack) status.c_scope_stack = [];
|
|
13463
|
-
status.c_scope_stack.push(frame);
|
|
13464
|
-
return frame;
|
|
13465
|
-
}
|
|
13466
|
-
/** Pop the current scope frame from c_scope_stack (scope-exit counterpart to enter_c_scope). */
|
|
13467
|
-
function leave_c_scope(status) {
|
|
13468
|
-
status.c_scope_stack?.pop();
|
|
13469
|
-
}
|
|
13470
|
-
/**
|
|
13471
|
-
* Mark the current top frame as a loop body, so break/continue know how far up
|
|
13472
|
-
* the scope stack to reclaim. Call AFTER entering the loop body scope.
|
|
13473
|
-
*/
|
|
13474
|
-
function push_c_loop_frame(status) {
|
|
13475
|
-
if (!status.c_scope_stack?.length) return;
|
|
13476
|
-
if (!status.c_loop_frame_depth) status.c_loop_frame_depth = [];
|
|
13477
|
-
status.c_loop_frame_depth.push(status.c_scope_stack.length - 1);
|
|
13478
|
-
}
|
|
13479
|
-
function pop_c_loop_frame(status) {
|
|
13480
|
-
status.c_loop_frame_depth?.pop();
|
|
13481
|
-
}
|
|
13482
|
-
/**
|
|
13483
|
-
* Reclaim declarations from every frame between the current scope and the
|
|
13484
|
-
* innermost loop's body frame (inclusive), then return the loop body index.
|
|
13485
|
-
* Used by break/continue: the freed declarations' scope-exit auto_free either
|
|
13486
|
-
* runs on the (mutually exclusive) non-jump path or is dead code after the
|
|
13487
|
-
* jump, so this never double-frees. The innermost frame is cleared afterwards
|
|
13488
|
-
* so its dead post-jump auto_free emits nothing.
|
|
13489
|
-
*/
|
|
13490
|
-
function reclaim_to_loop_body(status) {
|
|
13491
|
-
const stack = status.c_scope_stack;
|
|
13492
|
-
const loopDepth = status.c_loop_frame_depth;
|
|
13493
|
-
if (!stack?.length || !loopDepth?.length) return void 0;
|
|
13494
|
-
const loopBodyIdx = loopDepth[loopDepth.length - 1];
|
|
13495
|
-
for (let i = stack.length - 1; i >= loopBodyIdx; i--) free_scoped_declarations(status, stack[i]);
|
|
13496
|
-
stack[stack.length - 1].length = 0;
|
|
13497
|
-
return loopBodyIdx;
|
|
13498
|
-
}
|
|
13499
|
-
//#endregion
|
|
13500
14049
|
//#region ../src/build_c/utils/owning_buffer_specialize.ts
|
|
13501
14050
|
/**
|
|
13502
14051
|
* Detect whether a monomorphized struct is a `Buffer_<T>` whose element type
|
|
@@ -13859,7 +14408,11 @@ function build_struct_node(node, status) {
|
|
|
13859
14408
|
status.code += `${object_name}${accessor}${field.name} = *${field.name};\n`;
|
|
13860
14409
|
status.code += `${object_name}${accessor}${has_flag_name(field.name)} = ${has_flag_name(field.name)};\n`;
|
|
13861
14410
|
} else {
|
|
14411
|
+
const field_is_class_string = is_class && field.type.name === "string" && !field.type.is_array && !field.type.is_ref;
|
|
14412
|
+
const value_is_fresh_heap = !!field.value && is_owned_heap_temp(field.value, status);
|
|
14413
|
+
const wrap_strdup = field_is_class_string && !value_is_fresh_heap;
|
|
13862
14414
|
status.code += `${object_name}${accessor}${field.name} = `;
|
|
14415
|
+
if (wrap_strdup) status.code += `strdup(`;
|
|
13863
14416
|
if (field.value) build_node(field.value, status);
|
|
13864
14417
|
else {
|
|
13865
14418
|
const field_struct = status.structs.find((s) => s.name === mono_struct_name(field.type, status) && !s.is_simple_type);
|
|
@@ -13867,6 +14420,7 @@ function build_struct_node(node, status) {
|
|
|
13867
14420
|
if (field_struct && !field_struct.is_class || field_trait) status.code += `*`;
|
|
13868
14421
|
status.code += field.name;
|
|
13869
14422
|
}
|
|
14423
|
+
if (wrap_strdup) status.code += `)`;
|
|
13870
14424
|
status.code += ";\n";
|
|
13871
14425
|
}
|
|
13872
14426
|
for (let traitName of node.traits) {
|
|
@@ -13875,7 +14429,10 @@ function build_struct_node(node, status) {
|
|
|
13875
14429
|
status.code += `${object_name}${accessor}${field.name}`;
|
|
13876
14430
|
if (field.value) {
|
|
13877
14431
|
status.code += " = ";
|
|
14432
|
+
const wrap = is_class && field.type.name === "string" && !field.type.is_array && !field.type.is_ref && !is_owned_heap_temp(field.value, status);
|
|
14433
|
+
if (wrap) status.code += "strdup(";
|
|
13878
14434
|
build_node(field.value, status);
|
|
14435
|
+
if (wrap) status.code += ")";
|
|
13879
14436
|
}
|
|
13880
14437
|
status.code += ";\n";
|
|
13881
14438
|
}
|
|
@@ -13971,6 +14528,14 @@ function build_struct_functions(node, status, skip_init = false) {
|
|
|
13971
14528
|
} else status.function_ref_params.add(pname);
|
|
13972
14529
|
}
|
|
13973
14530
|
}
|
|
14531
|
+
for (const param of func.params) {
|
|
14532
|
+
if (param.is_self_param) continue;
|
|
14533
|
+
const param_struct = status.structs.find((s) => s.name === param.type.name);
|
|
14534
|
+
if (param.is_moved && param_struct?.is_class && !moved_param_is_consumed(func, param.name)) {
|
|
14535
|
+
const pname = c_function_name(param.name);
|
|
14536
|
+
status.scoped_declarations.push(new DeclarationNode(param.start, "private", "mov", pname, param.type));
|
|
14537
|
+
}
|
|
14538
|
+
}
|
|
13974
14539
|
const func_start = status.code.length;
|
|
13975
14540
|
let return_type = func.return_type.name || "void";
|
|
13976
14541
|
if (return_type !== node.name && node.name.startsWith(return_type + "_")) return_type = node.name;
|
|
@@ -14013,6 +14578,10 @@ function build_struct_functions(node, status, skip_init = false) {
|
|
|
14013
14578
|
}
|
|
14014
14579
|
const owning_elem = owning_buffer_element(node, status);
|
|
14015
14580
|
if (!(owning_elem && emit_owning_buffer_body(func.name, owning_elem, status) || owning_buffer_is_string_elem(node) && emit_owning_buffer_string_body(func.name, status))) for (let child of func.statements) build_node(child, status, true);
|
|
14581
|
+
if (func.name === "#destroy" && node.is_class) for (const field of node.fields) {
|
|
14582
|
+
if (field.type.is_ref || field.type.is_array) continue;
|
|
14583
|
+
if (field.type.name === "string") status.code += `free(self->${field.name});\n`;
|
|
14584
|
+
}
|
|
14016
14585
|
build_auto_free(status);
|
|
14017
14586
|
status.code += `}\n`;
|
|
14018
14587
|
status.function_ref_params = old_ref_params;
|
|
@@ -14048,7 +14617,7 @@ function build_auto_destroy(node, status) {
|
|
|
14048
14617
|
status.code += `${sig}\n{\n`;
|
|
14049
14618
|
for (const field of node.fields) {
|
|
14050
14619
|
if (field.type.is_ref) continue;
|
|
14051
|
-
if (field.type.name === "string" && !field.type.is_array
|
|
14620
|
+
if (field.type.name === "string" && !field.type.is_array) {
|
|
14052
14621
|
status.code += `free(self->${field.name});\n`;
|
|
14053
14622
|
continue;
|
|
14054
14623
|
}
|
|
@@ -14232,7 +14801,7 @@ function build_function_node(node, status) {
|
|
|
14232
14801
|
status.ref_class_param_types.set(pname, param.type);
|
|
14233
14802
|
}
|
|
14234
14803
|
} else if (!status.heap_array_vars?.has(pname)) status.function_ref_params.add(pname);
|
|
14235
|
-
if (param.is_moved && param_struct?.is_class && node.name !== "main" && !
|
|
14804
|
+
if (param.is_moved && param_struct?.is_class && node.name !== "main" && !moved_param_is_consumed(node, param.name)) {
|
|
14236
14805
|
const decl = new DeclarationNode(param.start, "private", "mov", pname, param.type);
|
|
14237
14806
|
status.scoped_declarations.push(decl);
|
|
14238
14807
|
}
|
|
@@ -14288,34 +14857,6 @@ function emit_nested_declarations(node, status) {
|
|
|
14288
14857
|
for (let child of block.statements) if (is_struct_node(child)) build_struct_node(child, status);
|
|
14289
14858
|
for (let child of block.statements) if (is_function_node(child)) build_function_node(child, status);
|
|
14290
14859
|
}
|
|
14291
|
-
function param_is_consumed(root, name) {
|
|
14292
|
-
let consumed = false;
|
|
14293
|
-
const refs_name = (n) => !!n && n.node_type === "value" && n.value === name;
|
|
14294
|
-
const walk = (n) => {
|
|
14295
|
-
if (!n || typeof n !== "object" || consumed) return;
|
|
14296
|
-
if (n.node_type === "func_call") {
|
|
14297
|
-
for (const p of n.params ?? []) if (refs_name(p)) consumed = true;
|
|
14298
|
-
}
|
|
14299
|
-
if (n.node_type === "access") {
|
|
14300
|
-
if (n.access?.node_type === "access_func" && refs_name(n.target)) consumed = true;
|
|
14301
|
-
for (const p of n.access?.params ?? []) if (refs_name(p)) consumed = true;
|
|
14302
|
-
}
|
|
14303
|
-
if (n.node_type === "array") {
|
|
14304
|
-
for (const v of n.values ?? []) if (refs_name(v)) consumed = true;
|
|
14305
|
-
}
|
|
14306
|
-
if (n.node_type === "return" && refs_name(n.value)) consumed = true;
|
|
14307
|
-
if (n.node_type === "assign" && refs_name(n.right_value)) consumed = true;
|
|
14308
|
-
if (n.node_type === "declare" && refs_name(n.value)) consumed = true;
|
|
14309
|
-
for (const key of Object.keys(n)) {
|
|
14310
|
-
if (key === "node_type") continue;
|
|
14311
|
-
const v = n[key];
|
|
14312
|
-
if (Array.isArray(v)) for (const item of v) walk(item);
|
|
14313
|
-
else if (v && typeof v === "object") walk(v);
|
|
14314
|
-
}
|
|
14315
|
-
};
|
|
14316
|
-
for (const stmt of root.statements ?? []) walk(stmt);
|
|
14317
|
-
return consumed;
|
|
14318
|
-
}
|
|
14319
14860
|
//#endregion
|
|
14320
14861
|
//#region ../src/build_c/utils/emit_allocations.ts
|
|
14321
14862
|
/**
|
|
@@ -14818,7 +15359,8 @@ function build_declaration_node(node, status) {
|
|
|
14818
15359
|
return;
|
|
14819
15360
|
}
|
|
14820
15361
|
const val_is_owned_return = node.value?.node_type === "access" && node.value.access.node_type === "access_func" && !!node.value.access.owned_return;
|
|
14821
|
-
const
|
|
15362
|
+
const val_is_borrowing_call = node.value?.node_type === "func_call" && !!status.borrow_returning_functions?.has(node.value.name);
|
|
15363
|
+
const val_is_class_alias = is_class_type && (node.value?.node_type === "value" && !!status.class_vars?.has(node.value.value) || val_is_borrowing_call || node.value?.node_type === "access" && !val_is_owned_return);
|
|
14822
15364
|
const val_is_string_literal = node.value?.node_type === "value" && node.value.value.length >= 2 && node.value.value.startsWith("\"") && node.value.value.endsWith("\"");
|
|
14823
15365
|
const is_borrow_only_string = node.type.name === "string" && val_is_string_literal && (node.declaration === "const" || node.declaration === "var" && !!status.c_borrow_only_strings?.has(safe_name));
|
|
14824
15366
|
if (is_borrow_only_string) {
|
|
@@ -14846,11 +15388,7 @@ function build_declaration_node(node, status) {
|
|
|
14846
15388
|
}
|
|
14847
15389
|
}
|
|
14848
15390
|
}
|
|
14849
|
-
if (node.value?.node_type === "value" && node.value.is_moved && !is_class_type)
|
|
14850
|
-
const src_name = node.value.value;
|
|
14851
|
-
const src_idx = status.scoped_declarations.findIndex((d) => d.name === src_name);
|
|
14852
|
-
if (src_idx !== -1) status.scoped_declarations.splice(src_idx, 1);
|
|
14853
|
-
}
|
|
15391
|
+
if (node.value?.node_type === "value" && node.value.is_moved && !is_class_type) splice_decl_from_c_scopes(status, node.value.value);
|
|
14854
15392
|
if (node.type?.name) {
|
|
14855
15393
|
if (!status.variable_types) status.variable_types = /* @__PURE__ */ new Map();
|
|
14856
15394
|
status.variable_types.set(safe_name, node.type);
|
|
@@ -15299,9 +15837,20 @@ function build_function_call_node(node, status) {
|
|
|
15299
15837
|
const param = node.params[idx];
|
|
15300
15838
|
if (param?.node_type === "value") {
|
|
15301
15839
|
const vname = param.value;
|
|
15302
|
-
const
|
|
15303
|
-
|
|
15304
|
-
if (
|
|
15840
|
+
const decl_hit = find_decl_in_c_scopes(status, vname);
|
|
15841
|
+
const tname = decl_hit?.frame[decl_hit.index].type?.name ?? param.type?.name;
|
|
15842
|
+
if (tname === "string") continue;
|
|
15843
|
+
const decl_struct = decl_hit ? status.structs.find((s) => s.name === tname && !s.is_simple_type) : void 0;
|
|
15844
|
+
const is_value_struct = !!decl_struct && !decl_struct.is_class;
|
|
15845
|
+
if (decl_hit) decl_hit.frame.splice(decl_hit.index, 1);
|
|
15846
|
+
if (is_value_struct) {
|
|
15847
|
+
const prefix = `${vname}.`;
|
|
15848
|
+
for (const key of Array.from(status.heap_string_fields ?? [])) if (key.startsWith(prefix)) {
|
|
15849
|
+
if (!status.pending_string_releases) status.pending_string_releases = [];
|
|
15850
|
+
status.pending_string_releases.push(`free(${key});`);
|
|
15851
|
+
status.heap_string_fields.delete(key);
|
|
15852
|
+
}
|
|
15853
|
+
}
|
|
15305
15854
|
if (!status.moved) status.moved = /* @__PURE__ */ new Set();
|
|
15306
15855
|
status.moved.add(vname);
|
|
15307
15856
|
}
|
|
@@ -15341,6 +15890,52 @@ function emit_nullable_arg_flag(arg, status) {
|
|
|
15341
15890
|
status.code += `1`;
|
|
15342
15891
|
}
|
|
15343
15892
|
//#endregion
|
|
15893
|
+
//#region ../src/build_c/utils/build_condition.ts
|
|
15894
|
+
/**
|
|
15895
|
+
* Build a controlling expression (an if/while/for condition or a switch-case
|
|
15896
|
+
* comparison) and emit it without redundant fully-wrapping outer parentheses.
|
|
15897
|
+
* The codegen wraps every binary operation in parens (build_default_binary),
|
|
15898
|
+
* so emitting the condition verbatim would read `if ((a == b))`, which clang
|
|
15899
|
+
* flags as -Wparentheses-equality ("equality comparison with extraneous
|
|
15900
|
+
* parentheses").
|
|
15901
|
+
*/
|
|
15902
|
+
function build_condition(node, status) {
|
|
15903
|
+
const before = status.code.length;
|
|
15904
|
+
build_node(node, status);
|
|
15905
|
+
status.code = status.code.substring(0, before) + strip_outer_parens(status.code.substring(before));
|
|
15906
|
+
}
|
|
15907
|
+
/**
|
|
15908
|
+
* Strip outer paren layers that wrap the ENTIRE expression (the opening paren
|
|
15909
|
+
* matches only the final character). A layer is removed only when the inner
|
|
15910
|
+
* text does not start with `{` — a GCC/clang statement-expression
|
|
15911
|
+
* `({ ... })` must keep its wrapper to stay a valid expression. Parens
|
|
15912
|
+
* inside string/char literals can only make the scan conservative (they
|
|
15913
|
+
* unbalance the depth count, so nothing is stripped), never incorrect.
|
|
15914
|
+
*/
|
|
15915
|
+
function strip_outer_parens(expr) {
|
|
15916
|
+
let code = expr.trim();
|
|
15917
|
+
while (code.length > 1 && code.startsWith("(") && code.endsWith(")")) {
|
|
15918
|
+
let depth = 0;
|
|
15919
|
+
let wraps_whole = true;
|
|
15920
|
+
for (let i = 0; i < code.length; i++) {
|
|
15921
|
+
const ch = code[i];
|
|
15922
|
+
if (ch === "(") depth++;
|
|
15923
|
+
else if (ch === ")") {
|
|
15924
|
+
depth--;
|
|
15925
|
+
if (depth === 0 && i < code.length - 1) {
|
|
15926
|
+
wraps_whole = false;
|
|
15927
|
+
break;
|
|
15928
|
+
}
|
|
15929
|
+
}
|
|
15930
|
+
}
|
|
15931
|
+
if (!wraps_whole || depth !== 0) break;
|
|
15932
|
+
const inner = code.slice(1, -1).trim();
|
|
15933
|
+
if (inner.startsWith("{")) break;
|
|
15934
|
+
code = inner;
|
|
15935
|
+
}
|
|
15936
|
+
return code;
|
|
15937
|
+
}
|
|
15938
|
+
//#endregion
|
|
15344
15939
|
//#region ../src/build_c/build_if_else_node.ts
|
|
15345
15940
|
function build_if_else_node(node, status) {
|
|
15346
15941
|
const old_scoped_declarations = status.scoped_declarations;
|
|
@@ -15349,7 +15944,7 @@ function build_if_else_node(node, status) {
|
|
|
15349
15944
|
status.deferred_frees = [];
|
|
15350
15945
|
emit_allocations(node.condition, status);
|
|
15351
15946
|
status.code += `if (`;
|
|
15352
|
-
|
|
15947
|
+
build_condition(node.condition, status);
|
|
15353
15948
|
status.code += `) {\n`;
|
|
15354
15949
|
if (node.if_branch) {
|
|
15355
15950
|
build_block_node(node.if_branch, status);
|
|
@@ -15493,7 +16088,7 @@ function build_return_node(node, status) {
|
|
|
15493
16088
|
const ret_is_null = returns_nullable_struct && (!node.value || node.value.node_type === "value" && node.value.value === "null");
|
|
15494
16089
|
if (returns_nullable_struct) {
|
|
15495
16090
|
if (ret_is_null) {
|
|
15496
|
-
|
|
16091
|
+
reclaim_all_c_scopes(status);
|
|
15497
16092
|
status.code += `*${ret_has} = 0;\n`;
|
|
15498
16093
|
status.code += `return (struct ${status.function_return_type.name}){0};\n`;
|
|
15499
16094
|
return;
|
|
@@ -15501,7 +16096,7 @@ function build_return_node(node, status) {
|
|
|
15501
16096
|
status.code += `*${ret_has} = 1;\n`;
|
|
15502
16097
|
}
|
|
15503
16098
|
if (!node.value) {
|
|
15504
|
-
|
|
16099
|
+
reclaim_all_c_scopes(status);
|
|
15505
16100
|
if (status.return_assign) status.code += `${status.return_assign} = 0;\n`;
|
|
15506
16101
|
else if (status.current_function_name?.toLocaleLowerCase() === "main") status.code += `return 0;\n`;
|
|
15507
16102
|
else status.code += `return;\n`;
|
|
@@ -15532,8 +16127,18 @@ function build_return_node(node, status) {
|
|
|
15532
16127
|
if (node.value.node_type === "value") {
|
|
15533
16128
|
const value = node.value.value;
|
|
15534
16129
|
returned_value_decl = find_decl_across_scopes(value, status);
|
|
15535
|
-
|
|
15536
|
-
|
|
16130
|
+
const frames = [status.scoped_declarations, ...status.c_scope_stack ?? []];
|
|
16131
|
+
for (const frame of frames) {
|
|
16132
|
+
const di = frame.indexOf(returned_value_decl);
|
|
16133
|
+
if (di !== -1) {
|
|
16134
|
+
frame.splice(di, 1);
|
|
16135
|
+
break;
|
|
16136
|
+
}
|
|
16137
|
+
}
|
|
16138
|
+
if (status.heap_string_fields?.size) {
|
|
16139
|
+
const prefix = `${value}.`;
|
|
16140
|
+
for (const key of Array.from(status.heap_string_fields)) if (key.startsWith(prefix)) status.heap_string_fields.delete(key);
|
|
16141
|
+
}
|
|
15537
16142
|
}
|
|
15538
16143
|
if (ret_type?.is_array && return_array_var && return_array_len > 0) {
|
|
15539
16144
|
const elem_name = ret_type.name;
|
|
@@ -15543,7 +16148,7 @@ function build_return_node(node, status) {
|
|
|
15543
16148
|
status.code += `_return_val->length = ${return_array_len};\n`;
|
|
15544
16149
|
status.code += `${elem_c_type}* _return_data = (${elem_c_type}*)((char*)_return_val + sizeof(struct ${array_struct}));\n`;
|
|
15545
16150
|
status.code += `for (long _i = 0; _i < ${return_array_len}; _i++) _return_data[_i] = ${return_array_var}[_i];\n`;
|
|
15546
|
-
|
|
16151
|
+
reclaim_all_c_scopes(status);
|
|
15547
16152
|
status.code += `return _return_val;\n`;
|
|
15548
16153
|
return;
|
|
15549
16154
|
}
|
|
@@ -15553,7 +16158,7 @@ function build_return_node(node, status) {
|
|
|
15553
16158
|
status.code += `${old_return_assign} = `;
|
|
15554
16159
|
build_node(node.value, status);
|
|
15555
16160
|
status.code += `;\n`;
|
|
15556
|
-
|
|
16161
|
+
reclaim_all_c_scopes(status);
|
|
15557
16162
|
} else {
|
|
15558
16163
|
emit_allocations(node.value, status);
|
|
15559
16164
|
const ret_type = status.function_return_type || node.type;
|
|
@@ -15574,7 +16179,7 @@ function build_return_node(node, status) {
|
|
|
15574
16179
|
build_node(node.value, status);
|
|
15575
16180
|
status.join_needs_owned_string = old_join_owned;
|
|
15576
16181
|
status.return_assign = old_return_assign;
|
|
15577
|
-
|
|
16182
|
+
reclaim_all_c_scopes(status);
|
|
15578
16183
|
if (string_join) status.code += any_branch_owned ? `return _return_val;\n` : `return strdup(_return_val);\n`;
|
|
15579
16184
|
else status.code += `return _return_val;\n`;
|
|
15580
16185
|
return;
|
|
@@ -15622,7 +16227,7 @@ function build_return_node(node, status) {
|
|
|
15622
16227
|
if (returns_borrowed_string || returns_string_literal || returns_borrow_var) status.code += `)`;
|
|
15623
16228
|
status.code += `;\n`;
|
|
15624
16229
|
if (node.value.node_type === "func_call" && node.value.field_overrides?.length) emit_field_overrides("_return_val", node.value, build_node, status, "", ";\n");
|
|
15625
|
-
|
|
16230
|
+
reclaim_all_c_scopes(status);
|
|
15626
16231
|
status.code += `return _return_val;\n`;
|
|
15627
16232
|
}
|
|
15628
16233
|
}
|
|
@@ -15752,6 +16357,7 @@ function build_switch_node(node, status) {
|
|
|
15752
16357
|
}
|
|
15753
16358
|
cond_code = cond_code.trim();
|
|
15754
16359
|
while (cond_code.startsWith("(") && !cond_code.endsWith(")")) cond_code = cond_code.substring(1).trim();
|
|
16360
|
+
cond_code = strip_outer_parens(cond_code);
|
|
15755
16361
|
if (decls.length > 0) status.code += decls.join("\n") + "\n";
|
|
15756
16362
|
const prefix = status.code.endsWith("} else ") ? "" : "";
|
|
15757
16363
|
status.code += `${prefix}if (${cond_code}) {\n`;
|
|
@@ -15828,13 +16434,13 @@ function build_while_loop_node(node, status) {
|
|
|
15828
16434
|
emit_allocations(node.condition, status);
|
|
15829
16435
|
if (node.update) {
|
|
15830
16436
|
status.code += `for (; `;
|
|
15831
|
-
|
|
16437
|
+
build_condition(node.condition, status);
|
|
15832
16438
|
status.code += `; `;
|
|
15833
16439
|
build_node(node.update, status);
|
|
15834
16440
|
status.code += `) {\n`;
|
|
15835
16441
|
} else {
|
|
15836
16442
|
status.code += `while (`;
|
|
15837
|
-
|
|
16443
|
+
build_condition(node.condition, status);
|
|
15838
16444
|
status.code += `) {\n`;
|
|
15839
16445
|
}
|
|
15840
16446
|
build_block_node(node, status);
|
|
@@ -15980,6 +16586,95 @@ function build_node(node, status, with_semicolon = false) {
|
|
|
15980
16586
|
}
|
|
15981
16587
|
if (with_semicolon) {
|
|
15982
16588
|
if (!status.code.endsWith("}\n")) status.code += ";\n";
|
|
16589
|
+
if (status.pending_string_releases?.length) {
|
|
16590
|
+
status.code += status.pending_string_releases.join("\n") + "\n";
|
|
16591
|
+
status.pending_string_releases.length = 0;
|
|
16592
|
+
}
|
|
16593
|
+
}
|
|
16594
|
+
}
|
|
16595
|
+
//#endregion
|
|
16596
|
+
//#region ../src/build_common/scan_borrow_returns.ts
|
|
16597
|
+
/**
|
|
16598
|
+
* Functions (and methods) whose CLASS-typed return value is a BORROWED
|
|
16599
|
+
* reference — e.g. `func box_at = (List<Box> xs, int i, out Box) { var Box
|
|
16600
|
+
* got = xs.at(j); return mov got }` hands back the container's element, not a
|
|
16601
|
+
* fresh instance. A caller-side declaration initialized from such a call must
|
|
16602
|
+
* NOT be destroy-tracked (the callee's owner frees it) — mirroring the
|
|
16603
|
+
* syntactic borrow rules at declaration sites (field access / non-`mov out`
|
|
16604
|
+
* method call).
|
|
16605
|
+
*
|
|
16606
|
+
* Syntactic, build-time: a return of a local whose initializer is a field
|
|
16607
|
+
* access or a non-owned-return method call (`.at()`, `.first()`, …) marks the
|
|
16608
|
+
* enclosing function. Mixed functions (some returns fresh, some borrowed) are
|
|
16609
|
+
* classified as borrowing — never freeing is safe (worst case a leak), while
|
|
16610
|
+
* the opposite risks a double-free.
|
|
16611
|
+
*/
|
|
16612
|
+
function scan_borrow_returning_functions(root) {
|
|
16613
|
+
const statements = root.statements ?? [];
|
|
16614
|
+
const class_type_names = /* @__PURE__ */ new Set();
|
|
16615
|
+
const result = /* @__PURE__ */ new Set();
|
|
16616
|
+
walk(statements, (n) => {
|
|
16617
|
+
if (n.node_type === "struct" && n.is_class) class_type_names.add(n.name);
|
|
16618
|
+
}, true, true);
|
|
16619
|
+
walk(statements, (n) => {
|
|
16620
|
+
if (n.node_type === "struct") for (const f of n.functions ?? []) scan_func(f, n.name, result, class_type_names);
|
|
16621
|
+
else if (n.node_type === "func") scan_func(n, void 0, result, class_type_names);
|
|
16622
|
+
});
|
|
16623
|
+
return result;
|
|
16624
|
+
}
|
|
16625
|
+
/**
|
|
16626
|
+
* Visit every AST node reachable from `value` — through arrays AND
|
|
16627
|
+
* single-node properties (an `if` node's branch blocks are node objects, not
|
|
16628
|
+
* statement arrays) — skipping `parent`/`scope` back-references. By default
|
|
16629
|
+
* does NOT descend INTO nested `func`/`struct`/`trait` declarations (a
|
|
16630
|
+
* function's own body must not leak into its enclosing function's
|
|
16631
|
+
* classification); `descend_boundaries` walks them too (used by the
|
|
16632
|
+
* class-name gather, which must see classes declared anywhere, including
|
|
16633
|
+
* inside the synthetic wrapper `main`).
|
|
16634
|
+
*/
|
|
16635
|
+
function walk(value, cb, top = true, descend_boundaries = false) {
|
|
16636
|
+
if (!value || typeof value !== "object") return;
|
|
16637
|
+
if (Array.isArray(value)) {
|
|
16638
|
+
for (const item of value) walk(item, cb, false, descend_boundaries);
|
|
16639
|
+
return;
|
|
16640
|
+
}
|
|
16641
|
+
const n = value;
|
|
16642
|
+
const is_boundary = (n.node_type === "func" || n.node_type === "struct" || n.node_type === "trait") && !top;
|
|
16643
|
+
if (typeof n.node_type === "string") cb(n);
|
|
16644
|
+
if (is_boundary && !descend_boundaries) return;
|
|
16645
|
+
for (const key of Object.keys(value)) {
|
|
16646
|
+
if (key === "parent" || key === "scope" || key === "node_type") continue;
|
|
16647
|
+
walk(value[key], cb, false, descend_boundaries);
|
|
16648
|
+
}
|
|
16649
|
+
}
|
|
16650
|
+
function scan_func(func, struct_name, result, class_type_names) {
|
|
16651
|
+
walk(func.statements ?? [], (n) => {
|
|
16652
|
+
if (n.node_type === "func") scan_func(n, void 0, result, class_type_names);
|
|
16653
|
+
else if (n.node_type === "struct") for (const f of n.functions ?? []) scan_func(f, n.name, result, class_type_names);
|
|
16654
|
+
});
|
|
16655
|
+
if (!func.return_type?.name || !class_type_names.has(func.return_type.name)) return;
|
|
16656
|
+
const borrowed_locals = /* @__PURE__ */ new Set();
|
|
16657
|
+
let returns_borrowed = false;
|
|
16658
|
+
walk(func.statements ?? [], (n) => {
|
|
16659
|
+
if (n.node_type === "declare") {
|
|
16660
|
+
const decl = n;
|
|
16661
|
+
if (!decl.type?.name || !class_type_names.has(decl.type.name)) return;
|
|
16662
|
+
const value = decl.value;
|
|
16663
|
+
if (!value || value.node_type !== "access") return;
|
|
16664
|
+
const access = value;
|
|
16665
|
+
if (access.access.node_type === "access_field") borrowed_locals.add(decl.name);
|
|
16666
|
+
else if (access.access.node_type === "access_func") {
|
|
16667
|
+
if (!access.access.owned_return) borrowed_locals.add(decl.name);
|
|
16668
|
+
}
|
|
16669
|
+
} else if (n.node_type === "return") {
|
|
16670
|
+
const value = n.value;
|
|
16671
|
+
if (value && value.node_type === "value" && borrowed_locals.has(value.value)) returns_borrowed = true;
|
|
16672
|
+
}
|
|
16673
|
+
});
|
|
16674
|
+
if (returns_borrowed) {
|
|
16675
|
+
const sanitized = func.name.replace(/#/g, "");
|
|
16676
|
+
const label = struct_name ? `${struct_name}_${sanitized}` : sanitized;
|
|
16677
|
+
result.add(label);
|
|
15983
16678
|
}
|
|
15984
16679
|
}
|
|
15985
16680
|
//#endregion
|
|
@@ -16024,6 +16719,7 @@ function build(root, options = {}) {
|
|
|
16024
16719
|
reset_inline_counter();
|
|
16025
16720
|
reset_decl_const_counters();
|
|
16026
16721
|
status.heap_returning_functions = scan_heap_returning_functions(root);
|
|
16722
|
+
status.borrow_returning_functions = scan_borrow_returning_functions(root);
|
|
16027
16723
|
status.inline_functions = scan_inline_candidates(root);
|
|
16028
16724
|
status.heap_returning_functions.add("int_to_string");
|
|
16029
16725
|
status.heap_returning_functions.add("uint_to_string");
|
|
@@ -16135,9 +16831,13 @@ function build(root, options = {}) {
|
|
|
16135
16831
|
}
|
|
16136
16832
|
if (options.audit) {
|
|
16137
16833
|
status.code = status.code.replaceAll("bl _malloc\n", "bl _nomen_malloc_wrap\n");
|
|
16834
|
+
status.code = status.code.replaceAll("bl _calloc\n", "bl _nomen_calloc_wrap\n");
|
|
16835
|
+
status.code = status.code.replaceAll("bl _realloc\n", "bl _nomen_realloc_wrap\n");
|
|
16836
|
+
status.code = status.code.replaceAll("bl _strdup\n", "bl _nomen_strdup_wrap\n");
|
|
16138
16837
|
status.code = status.code.replaceAll("bl _free\n", "bl _nomen_free_wrap\n");
|
|
16139
16838
|
}
|
|
16140
16839
|
} else {
|
|
16840
|
+
status.borrow_returning_functions = scan_borrow_returning_functions(root);
|
|
16141
16841
|
set_c_typedef_mangling(build_needs_objc(root, status.platform));
|
|
16142
16842
|
build_node(root, status);
|
|
16143
16843
|
status.code = `typedef struct { void* ptr; long len; } nomen_view;\n` + status.code;
|
|
@@ -24367,7 +25067,9 @@ function check_return_node(ret, status) {
|
|
|
24367
25067
|
}
|
|
24368
25068
|
}
|
|
24369
25069
|
if (func && borrow_depth_of(ret.value, status) !== void 0) {
|
|
24370
|
-
|
|
25070
|
+
const safe_view_from_self = !!func.return_type?.is_view && borrow_owner_of(ret.value, status) === "self";
|
|
25071
|
+
const explicit_mov = !!get_inner_value_node(ret.value)?.is_moved;
|
|
25072
|
+
if (!safe_view_from_self && !explicit_mov) add_error(status, `cannot return a borrowed reference — use 'mov' (with swap) to transfer ownership`, ret.value.start);
|
|
24371
25073
|
}
|
|
24372
25074
|
if (func) {
|
|
24373
25075
|
if (func.return_type.name) {
|
|
@@ -28012,7 +28714,7 @@ function compile_audit_runtime(config, input_path, buildDir) {
|
|
|
28012
28714
|
return audit_obj;
|
|
28013
28715
|
}
|
|
28014
28716
|
function watchPath(p, config, mode, program_args) {
|
|
28015
|
-
|
|
28717
|
+
chokidar_default.watch(p).on("all", (event, filePath) => {
|
|
28016
28718
|
if (shouldProcessFile(filePath)) processFile(filePath, config, mode, program_args);
|
|
28017
28719
|
});
|
|
28018
28720
|
}
|