nomen-lang 0.6.2 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/System/Text/Regex.nm +416 -15
- package/core/docs/System/Text/Regex.md +1 -1
- package/dist/index.mjs +905 -304
- package/package.json +1 -1
- package/src/index.ts +8 -56
- package/src/init.ts +12 -3
- package/src/test.ts +34 -2
package/dist/index.mjs
CHANGED
|
@@ -6538,6 +6538,18 @@ var FunctionCallNode = class extends BaseNode {
|
|
|
6538
6538
|
ref_param_indices;
|
|
6539
6539
|
move_param_indices;
|
|
6540
6540
|
/**
|
|
6541
|
+
* Indices of arguments bound to a `move T` parameter where T is a
|
|
6542
|
+
* pass-by-value owning struct on a registered free function: ownership
|
|
6543
|
+
* TRANSFERS at the boundary (the callee seeds the param's string-field
|
|
6544
|
+
* records and frees them at its exit), so the caller drops its records
|
|
6545
|
+
* instead of freeing them. The plain `move_param_indices` convention
|
|
6546
|
+
* (callee deep-copies, caller frees the originals) still governs
|
|
6547
|
+
* container stores and every non-transfer shape. Stamped by
|
|
6548
|
+
* check_function_call alongside owned_value_param_indices.
|
|
6549
|
+
*/
|
|
6550
|
+
move_owned_param_indices;
|
|
6551
|
+
owned_value_param_indices;
|
|
6552
|
+
/**
|
|
6541
6553
|
* Indices of arguments whose corresponding callee parameter is a nullable
|
|
6542
6554
|
* struct value type (`T?` where T is a non-class struct). Populated during
|
|
6543
6555
|
* the check pass when the callee's signature is resolved. The build uses
|
|
@@ -6958,6 +6970,12 @@ var FunctionNode = class extends BaseNode {
|
|
|
6958
6970
|
* are dual-natured: called under their own name AND passed as values).
|
|
6959
6971
|
*/
|
|
6960
6972
|
is_closure;
|
|
6973
|
+
/** The function's address escapes into a func-typed value (bound to a
|
|
6974
|
+
* `var func …` variable/field, passed as a func value, …) — so it may be
|
|
6975
|
+
* invoked through a pointer with a BORROWED argument: pass-by-value
|
|
6976
|
+
* normalization (owned-param record seeding / call-site shell
|
|
6977
|
+
* materialization) must not apply. Set during check. */
|
|
6978
|
+
address_escaped;
|
|
6961
6979
|
/**
|
|
6962
6980
|
* Captured outer locals (CLOSURE.md Phase 2). One entry per variable the
|
|
6963
6981
|
* lambda body references from an enclosing function; the backends emit an
|
|
@@ -7366,6 +7384,16 @@ var DeclarationNode = class extends BaseNode {
|
|
|
7366
7384
|
* paths strdup the initializer and free the temp's copy at scope exit.
|
|
7367
7385
|
* Set at build time by hoist_field_overrides. */
|
|
7368
7386
|
force_owned_string;
|
|
7387
|
+
/** True for an OWNED value-struct PARAMETER whose string fields the
|
|
7388
|
+
* callee frees at scope exit: the field accesses (and the record-free
|
|
7389
|
+
* emitted by free_scoped_declarations) go through the param POINTER
|
|
7390
|
+
* (`p->field`) instead of value syntax (`p.field`). Set at build time
|
|
7391
|
+
* when the function builder seeds pass-by-value struct params. */
|
|
7392
|
+
string_fields_via_pointer;
|
|
7393
|
+
/** True alongside string_fields_via_pointer when the caller materialized
|
|
7394
|
+
* a heap shell for the argument (pass-by-value): the callee frees the
|
|
7395
|
+
* shell itself at scope exit (`free(p)`) after freeing the fields. */
|
|
7396
|
+
owned_heap_shell;
|
|
7369
7397
|
constructor(start, visibility, declaration, name, type, value) {
|
|
7370
7398
|
super("declare", start);
|
|
7371
7399
|
this.visibility = visibility;
|
|
@@ -10913,6 +10941,117 @@ function array_struct_name(type, status) {
|
|
|
10913
10941
|
return status.structs.find((s) => s.name === mono && !s.is_generic) ? mono : void 0;
|
|
10914
10942
|
}
|
|
10915
10943
|
//#endregion
|
|
10944
|
+
//#region ../src/build_common/destroy_analysis.ts
|
|
10945
|
+
/**
|
|
10946
|
+
* Whether any field of the struct is an enum-with-data value owning heap
|
|
10947
|
+
* payloads (a string payload — always heap, case construction strdups it —
|
|
10948
|
+
* or a class/trait reference payload — construction transfers ownership).
|
|
10949
|
+
* Such a field must be reclaimed when the struct dies.
|
|
10950
|
+
*/
|
|
10951
|
+
function struct_has_owning_enum_field(struct, table) {
|
|
10952
|
+
for (const field of struct.fields) {
|
|
10953
|
+
if (field.type.is_ref) continue;
|
|
10954
|
+
const en = table.enums?.find((e) => e.name === field.type.name);
|
|
10955
|
+
if (!en?.has_associated_data) continue;
|
|
10956
|
+
for (const c of en.cases) for (const p of c.params) {
|
|
10957
|
+
if (p.type.name === "string" && !p.type.is_array) return true;
|
|
10958
|
+
if (table.traits?.some((t) => t.name === p.type.name)) return true;
|
|
10959
|
+
if (table.structs.find((s) => s.name === p.type.name)?.is_class) return true;
|
|
10960
|
+
}
|
|
10961
|
+
}
|
|
10962
|
+
return false;
|
|
10963
|
+
}
|
|
10964
|
+
/**
|
|
10965
|
+
* Whether a struct declares its own `#destroy` (user side effects /
|
|
10966
|
+
* resource release).
|
|
10967
|
+
*/
|
|
10968
|
+
function has_destroy(struct) {
|
|
10969
|
+
return !!struct.functions.find((f) => f.name === "#destroy");
|
|
10970
|
+
}
|
|
10971
|
+
/**
|
|
10972
|
+
* Whether any field (recursively) requires destroy handling — a class
|
|
10973
|
+
* field (heap instance to reclaim) or a nested struct with its own
|
|
10974
|
+
* `#destroy` (or nested fields that do). Deliberately excludes `string`
|
|
10975
|
+
* fields: a struct local whose only owning fields are strings is NOT
|
|
10976
|
+
* auto-destroyed at scope exit (the strings may be raw rodata arg
|
|
10977
|
+
* pointers, not heap) — see `struct_needs_auto_destroy` for the broader
|
|
10978
|
+
* form used to decide whether a `<Struct>_destroy` function is generated.
|
|
10979
|
+
*/
|
|
10980
|
+
function has_struct_fields_with_destroy(struct, table) {
|
|
10981
|
+
for (const field of struct.fields) {
|
|
10982
|
+
if (field.type.is_ref) continue;
|
|
10983
|
+
const field_struct = resolve_struct_type(field.type, table);
|
|
10984
|
+
if (!field_struct) continue;
|
|
10985
|
+
if (field_struct.is_class) return true;
|
|
10986
|
+
if (has_destroy(field_struct)) return true;
|
|
10987
|
+
if (has_struct_fields_with_destroy(field_struct, table)) return true;
|
|
10988
|
+
}
|
|
10989
|
+
return false;
|
|
10990
|
+
}
|
|
10991
|
+
/**
|
|
10992
|
+
* Whether a struct (or any embedded struct field, recursively) needs a
|
|
10993
|
+
* destroy call at scope exit — it has a `#destroy`, a class-typed field,
|
|
10994
|
+
* or a nested struct field that itself needs destroying. This does NOT
|
|
10995
|
+
* count `string` fields (see `has_struct_fields_with_destroy`).
|
|
10996
|
+
*/
|
|
10997
|
+
function struct_needs_destroy(struct, table) {
|
|
10998
|
+
return has_destroy(struct) || has_struct_fields_with_destroy(struct, table) || struct_has_owning_enum_field(struct, table);
|
|
10999
|
+
}
|
|
11000
|
+
/**
|
|
11001
|
+
* Whether a struct needs an auto-generated `<Struct>_destroy` function.
|
|
11002
|
+
* Broader than `struct_needs_destroy`: also returns true for structs with
|
|
11003
|
+
* `string` fields, because a Buffer<T> for such a struct deep-copies the
|
|
11004
|
+
* strings into slots (strdup on store) and the per-element destroy must
|
|
11005
|
+
* free them. The generated destroy frees each string field; it is called
|
|
11006
|
+
* from Buffer's specialized #destroy / replace_T, NOT from struct local
|
|
11007
|
+
* scope exit (`struct_needs_destroy` governs that path and excludes
|
|
11008
|
+
* strings).
|
|
11009
|
+
*/
|
|
11010
|
+
function struct_needs_auto_destroy(struct, table) {
|
|
11011
|
+
if (struct_needs_destroy(struct, table)) return true;
|
|
11012
|
+
if (struct_has_owning_enum_field(struct, table)) return true;
|
|
11013
|
+
for (const field of struct.fields) {
|
|
11014
|
+
if (field.type.is_ref) continue;
|
|
11015
|
+
if (field.type.is_view) continue;
|
|
11016
|
+
if (field.type.name === "string" && !field.type.is_array) return true;
|
|
11017
|
+
const field_struct = resolve_struct_type(field.type, table);
|
|
11018
|
+
if (field_struct && !field_struct.is_class && struct_needs_auto_destroy(field_struct, table)) return true;
|
|
11019
|
+
}
|
|
11020
|
+
return false;
|
|
11021
|
+
}
|
|
11022
|
+
//#endregion
|
|
11023
|
+
//#region ../src/build_common/has_string_fields.ts
|
|
11024
|
+
/**
|
|
11025
|
+
* Whether any field (recursively, through nested owning value structs)
|
|
11026
|
+
* is an owned `string`. Shared by BOTH backends' owning-Buffer element
|
|
11027
|
+
* specialization (the slot deep-copies strdup'd strings, so a
|
|
11028
|
+
* string-field element is owning even when `struct_needs_destroy`
|
|
11029
|
+
* ignores string-only locals) — the C comment in
|
|
11030
|
+
* owning_buffer_specialize records the divergence this once caused.
|
|
11031
|
+
* `ref` and `view T` fields are skipped: a ref aliases the caller's
|
|
11032
|
+
* storage and a view's byte copy owns nothing.
|
|
11033
|
+
*/
|
|
11034
|
+
function has_string_fields(node, status) {
|
|
11035
|
+
for (const field of node.fields) {
|
|
11036
|
+
if (field.type.is_ref) continue;
|
|
11037
|
+
if (field.type.is_view) continue;
|
|
11038
|
+
if (field.type.name === "string" && !field.type.is_array) return true;
|
|
11039
|
+
const field_struct = status.structs.find((s) => s.name === field.type.name && !s.is_simple_type && !s.is_generic);
|
|
11040
|
+
if (field_struct && !field_struct.is_class && has_string_fields(field_struct, status)) return true;
|
|
11041
|
+
}
|
|
11042
|
+
return false;
|
|
11043
|
+
}
|
|
11044
|
+
/**
|
|
11045
|
+
* The DIRECT `string` fields of a value struct (nested struct fields are not
|
|
11046
|
+
* descended into — nested string fields are outside the per-field ownership
|
|
11047
|
+
* tracking everywhere else too). Used by the return-boundary normalization:
|
|
11048
|
+
* the returned struct's unrecorded fields are strdup'd so the value the
|
|
11049
|
+
* caller receives is uniformly heap-owned.
|
|
11050
|
+
*/
|
|
11051
|
+
function direct_string_fields(node) {
|
|
11052
|
+
return node.fields.filter((f) => !f.type.is_ref && !f.type.is_view && !f.type.is_array && f.type.name === "string");
|
|
11053
|
+
}
|
|
11054
|
+
//#endregion
|
|
10916
11055
|
//#region ../src/build_common/scan_force_heap_strings.ts
|
|
10917
11056
|
function scan_force_heap_strings(statements, structs) {
|
|
10918
11057
|
const result = /* @__PURE__ */ new Set();
|
|
@@ -13391,85 +13530,6 @@ function emit_strdup(status) {
|
|
|
13391
13530
|
emit_asm(status, `ldr x1, [sp], #16\n`);
|
|
13392
13531
|
}
|
|
13393
13532
|
//#endregion
|
|
13394
|
-
//#region ../src/build_common/destroy_analysis.ts
|
|
13395
|
-
/**
|
|
13396
|
-
* Whether any field of the struct is an enum-with-data value owning heap
|
|
13397
|
-
* payloads (a string payload — always heap, case construction strdups it —
|
|
13398
|
-
* or a class/trait reference payload — construction transfers ownership).
|
|
13399
|
-
* Such a field must be reclaimed when the struct dies.
|
|
13400
|
-
*/
|
|
13401
|
-
function struct_has_owning_enum_field(struct, table) {
|
|
13402
|
-
for (const field of struct.fields) {
|
|
13403
|
-
if (field.type.is_ref) continue;
|
|
13404
|
-
const en = table.enums?.find((e) => e.name === field.type.name);
|
|
13405
|
-
if (!en?.has_associated_data) continue;
|
|
13406
|
-
for (const c of en.cases) for (const p of c.params) {
|
|
13407
|
-
if (p.type.name === "string" && !p.type.is_array) return true;
|
|
13408
|
-
if (table.traits?.some((t) => t.name === p.type.name)) return true;
|
|
13409
|
-
if (table.structs.find((s) => s.name === p.type.name)?.is_class) return true;
|
|
13410
|
-
}
|
|
13411
|
-
}
|
|
13412
|
-
return false;
|
|
13413
|
-
}
|
|
13414
|
-
/**
|
|
13415
|
-
* Whether a struct declares its own `#destroy` (user side effects /
|
|
13416
|
-
* resource release).
|
|
13417
|
-
*/
|
|
13418
|
-
function has_destroy(struct) {
|
|
13419
|
-
return !!struct.functions.find((f) => f.name === "#destroy");
|
|
13420
|
-
}
|
|
13421
|
-
/**
|
|
13422
|
-
* Whether any field (recursively) requires destroy handling — a class
|
|
13423
|
-
* field (heap instance to reclaim) or a nested struct with its own
|
|
13424
|
-
* `#destroy` (or nested fields that do). Deliberately excludes `string`
|
|
13425
|
-
* fields: a struct local whose only owning fields are strings is NOT
|
|
13426
|
-
* auto-destroyed at scope exit (the strings may be raw rodata arg
|
|
13427
|
-
* pointers, not heap) — see `struct_needs_auto_destroy` for the broader
|
|
13428
|
-
* form used to decide whether a `<Struct>_destroy` function is generated.
|
|
13429
|
-
*/
|
|
13430
|
-
function has_struct_fields_with_destroy(struct, table) {
|
|
13431
|
-
for (const field of struct.fields) {
|
|
13432
|
-
if (field.type.is_ref) continue;
|
|
13433
|
-
const field_struct = resolve_struct_type(field.type, table);
|
|
13434
|
-
if (!field_struct) continue;
|
|
13435
|
-
if (field_struct.is_class) return true;
|
|
13436
|
-
if (has_destroy(field_struct)) return true;
|
|
13437
|
-
if (has_struct_fields_with_destroy(field_struct, table)) return true;
|
|
13438
|
-
}
|
|
13439
|
-
return false;
|
|
13440
|
-
}
|
|
13441
|
-
/**
|
|
13442
|
-
* Whether a struct (or any embedded struct field, recursively) needs a
|
|
13443
|
-
* destroy call at scope exit — it has a `#destroy`, a class-typed field,
|
|
13444
|
-
* or a nested struct field that itself needs destroying. This does NOT
|
|
13445
|
-
* count `string` fields (see `has_struct_fields_with_destroy`).
|
|
13446
|
-
*/
|
|
13447
|
-
function struct_needs_destroy(struct, table) {
|
|
13448
|
-
return has_destroy(struct) || has_struct_fields_with_destroy(struct, table) || struct_has_owning_enum_field(struct, table);
|
|
13449
|
-
}
|
|
13450
|
-
/**
|
|
13451
|
-
* Whether a struct needs an auto-generated `<Struct>_destroy` function.
|
|
13452
|
-
* Broader than `struct_needs_destroy`: also returns true for structs with
|
|
13453
|
-
* `string` fields, because a Buffer<T> for such a struct deep-copies the
|
|
13454
|
-
* strings into slots (strdup on store) and the per-element destroy must
|
|
13455
|
-
* free them. The generated destroy frees each string field; it is called
|
|
13456
|
-
* from Buffer's specialized #destroy / replace_T, NOT from struct local
|
|
13457
|
-
* scope exit (`struct_needs_destroy` governs that path and excludes
|
|
13458
|
-
* strings).
|
|
13459
|
-
*/
|
|
13460
|
-
function struct_needs_auto_destroy(struct, table) {
|
|
13461
|
-
if (struct_needs_destroy(struct, table)) return true;
|
|
13462
|
-
if (struct_has_owning_enum_field(struct, table)) return true;
|
|
13463
|
-
for (const field of struct.fields) {
|
|
13464
|
-
if (field.type.is_ref) continue;
|
|
13465
|
-
if (field.type.is_view) continue;
|
|
13466
|
-
if (field.type.name === "string" && !field.type.is_array) return true;
|
|
13467
|
-
const field_struct = resolve_struct_type(field.type, table);
|
|
13468
|
-
if (field_struct && !field_struct.is_class && struct_needs_auto_destroy(field_struct, table)) return true;
|
|
13469
|
-
}
|
|
13470
|
-
return false;
|
|
13471
|
-
}
|
|
13472
|
-
//#endregion
|
|
13473
13533
|
//#region ../src/build_common/temp_anchor_consolidation.ts
|
|
13474
13534
|
/**
|
|
13475
13535
|
* Shared OWNERSHIP decision for hoisted call-temporary consolidation.
|
|
@@ -13504,6 +13564,144 @@ function superseded_param_temp_names(table, call_node, result_type_name) {
|
|
|
13504
13564
|
return names;
|
|
13505
13565
|
}
|
|
13506
13566
|
//#endregion
|
|
13567
|
+
//#region ../src/build_aarch64/utils/string_pair.ts
|
|
13568
|
+
/**
|
|
13569
|
+
* Load the string variable `name` into the consecutive register pair
|
|
13570
|
+
* (base, base+1) — default x0/x1. Handles the plain slot case; callers
|
|
13571
|
+
* that have register-allocated or param forms handle theirs first.
|
|
13572
|
+
*/
|
|
13573
|
+
function emit_string_pair_load(status, name, base = "x0") {
|
|
13574
|
+
const offset = status.stack_offsets?.get(name);
|
|
13575
|
+
if (offset === void 0) return false;
|
|
13576
|
+
const w = base.startsWith("x") ? base : "x0";
|
|
13577
|
+
const n = parseInt(w.substring(1), 10);
|
|
13578
|
+
if (offset + 8 > 504) {
|
|
13579
|
+
emit_asm(status, `ldr ${w}, [x29, #${offset}]\n`);
|
|
13580
|
+
emit_asm(status, `ldr x${n + 1}, [x29, #${offset + 8}]\n`);
|
|
13581
|
+
return true;
|
|
13582
|
+
}
|
|
13583
|
+
emit_asm(status, `ldp ${w}, x${n + 1}, [x29, #${offset}]\n`);
|
|
13584
|
+
return true;
|
|
13585
|
+
}
|
|
13586
|
+
/** Load the pair at [addr_reg, #offset] into (base, base+1). */
|
|
13587
|
+
function emit_string_pair_load_at(status, addr_reg, offset, base = "x0") {
|
|
13588
|
+
const w = base.startsWith("x") ? base : "x0";
|
|
13589
|
+
emit_asm(status, `ldp ${w}, x${parseInt(w.substring(1), 10) + 1}, [${addr_reg}, #${offset}]\n`);
|
|
13590
|
+
}
|
|
13591
|
+
/**
|
|
13592
|
+
* strdup the fat string in (x0=ptr, x1=len): preserve the len half across
|
|
13593
|
+
* the call and leave the owned copy's pair in (x0, x1). The caller keeps
|
|
13594
|
+
* ownership of the original.
|
|
13595
|
+
*/
|
|
13596
|
+
function emit_strdup_string(status) {
|
|
13597
|
+
emit_asm(status, `str x1, [sp, #-16]!\n`);
|
|
13598
|
+
emit_asm(status, status.audit ? `bl _nomen_strdup_wrap\n` : `bl _strdup\n`);
|
|
13599
|
+
emit_asm(status, `ldr x1, [sp], #16\n`);
|
|
13600
|
+
}
|
|
13601
|
+
/**
|
|
13602
|
+
* Pair load/store at [x29, #offset] with an arbitrary register pair —
|
|
13603
|
+
* splits into two single-word accesses beyond the ldp/stp +504 range.
|
|
13604
|
+
*/
|
|
13605
|
+
function emit_pair_load_x29(status, offset, a = "x0", b = "x1") {
|
|
13606
|
+
if (offset + 8 > 504) {
|
|
13607
|
+
emit_asm(status, `ldr ${a}, [x29, #${offset}]\n`);
|
|
13608
|
+
emit_asm(status, `ldr ${b}, [x29, #${offset + 8}]\n`);
|
|
13609
|
+
return;
|
|
13610
|
+
}
|
|
13611
|
+
emit_asm(status, `ldp ${a}, ${b}, [x29, #${offset}]\n`);
|
|
13612
|
+
}
|
|
13613
|
+
function emit_pair_store_x29(status, offset, a = "x0", b = "x1") {
|
|
13614
|
+
if (offset + 8 > 504) {
|
|
13615
|
+
emit_asm(status, `str ${a}, [x29, #${offset}]\n`);
|
|
13616
|
+
emit_asm(status, `str ${b}, [x29, #${offset + 8}]\n`);
|
|
13617
|
+
return;
|
|
13618
|
+
}
|
|
13619
|
+
emit_asm(status, `stp ${a}, ${b}, [x29, #${offset}]\n`);
|
|
13620
|
+
}
|
|
13621
|
+
/**
|
|
13622
|
+
* Owning specializations for Array<string>'s raw T-generic bodies
|
|
13623
|
+
* (`with` / `set`), replacing the shared-pointer copies with per-slot deep
|
|
13624
|
+
* copies — the aarch64 analog of Array.nm's `#if T_NEEDS_STRDUP` C branch.
|
|
13625
|
+
*
|
|
13626
|
+
* Conventions (matching Array.nm raw bodies):
|
|
13627
|
+
* - `with` (static): x0/x1 = value pair, x2 = count. Returns x0 = heap
|
|
13628
|
+
* buffer ([ptr] = length, elements at +8..).
|
|
13629
|
+
* - `set` (method): x19 = first element, x1 = index, x2/x3 = value pair.
|
|
13630
|
+
* Bounds are guaranteed by the Nomen-level constraints.
|
|
13631
|
+
*/
|
|
13632
|
+
function emit_owning_array_string_specialize(func_name, status, self_reg = "x19") {
|
|
13633
|
+
if (func_name === "with") {
|
|
13634
|
+
emit_asm(status, `stp x19, x20, [sp, #-16]!\n`);
|
|
13635
|
+
emit_asm(status, `stp x21, x22, [sp, #-16]!\n`);
|
|
13636
|
+
emit_asm(status, `stp x24, x25, [sp, #-16]!\n`);
|
|
13637
|
+
emit_asm(status, `mov x19, x0\n`);
|
|
13638
|
+
emit_asm(status, `mov x20, x1\n`);
|
|
13639
|
+
emit_asm(status, `mov x21, x2\n`);
|
|
13640
|
+
emit_asm(status, `add x0, x21, #1\n`);
|
|
13641
|
+
emit_asm(status, `lsl x0, x0, #4\n`);
|
|
13642
|
+
emit_malloc(status);
|
|
13643
|
+
emit_asm(status, `mov x22, x0\n`);
|
|
13644
|
+
emit_asm(status, `str x21, [x22]\n`);
|
|
13645
|
+
emit_asm(status, `mov x23, #8\n`);
|
|
13646
|
+
emit_asm(status, `mov x24, #0\n`);
|
|
13647
|
+
emit_asm(status, `.Larr_str_with_loop:\n`);
|
|
13648
|
+
emit_asm(status, `cmp x24, x21\n`);
|
|
13649
|
+
emit_asm(status, `b.ge .Larr_str_with_done\n`);
|
|
13650
|
+
emit_asm(status, `mov x0, x19\n`);
|
|
13651
|
+
emit_strdup(status);
|
|
13652
|
+
emit_asm(status, `add x25, x22, x23\n`);
|
|
13653
|
+
emit_asm(status, `stp x0, x20, [x25]\n`);
|
|
13654
|
+
emit_asm(status, `add x23, x23, #16\n`);
|
|
13655
|
+
emit_asm(status, `add x24, x24, #1\n`);
|
|
13656
|
+
emit_asm(status, `b .Larr_str_with_loop\n`);
|
|
13657
|
+
emit_asm(status, `.Larr_str_with_done:\n`);
|
|
13658
|
+
emit_asm(status, `mov x0, x22\n`);
|
|
13659
|
+
emit_asm(status, `ldp x24, x25, [sp], #16\n`);
|
|
13660
|
+
emit_asm(status, `ldp x21, x22, [sp], #16\n`);
|
|
13661
|
+
emit_asm(status, `ldp x19, x20, [sp], #16\n`);
|
|
13662
|
+
return true;
|
|
13663
|
+
}
|
|
13664
|
+
if (func_name === "at" || func_name === "first" || func_name === "at_end") {
|
|
13665
|
+
emit_asm(status, `stp x20, x21, [sp, #-16]!\n`);
|
|
13666
|
+
if (func_name === "at") {
|
|
13667
|
+
emit_asm(status, `lsl x9, x1, #4\n`);
|
|
13668
|
+
emit_asm(status, `add x9, ${self_reg}, x9\n`);
|
|
13669
|
+
} else if (func_name === "at_end") {
|
|
13670
|
+
emit_asm(status, `ldr x9, [${self_reg}, #-8]\n`);
|
|
13671
|
+
emit_asm(status, `sub x9, x9, #1\n`);
|
|
13672
|
+
emit_asm(status, `lsl x9, x9, #4\n`);
|
|
13673
|
+
emit_asm(status, `add x9, ${self_reg}, x9\n`);
|
|
13674
|
+
} else emit_asm(status, `add x9, ${self_reg}, #0\n`);
|
|
13675
|
+
emit_asm(status, `ldp x0, x1, [x9]\n`);
|
|
13676
|
+
emit_asm(status, `ldp x20, x21, [sp], #16\n`);
|
|
13677
|
+
return true;
|
|
13678
|
+
}
|
|
13679
|
+
if (func_name === "set") {
|
|
13680
|
+
emit_asm(status, `stp x20, x21, [sp, #-16]!\n`);
|
|
13681
|
+
emit_asm(status, `lsl x9, x1, #4\n`);
|
|
13682
|
+
emit_asm(status, `add x9, ${self_reg}, x9\n`);
|
|
13683
|
+
emit_asm(status, `ldr x0, [x9]\n`);
|
|
13684
|
+
emit_asm(status, `mov x20, x2\n`);
|
|
13685
|
+
emit_asm(status, `mov x21, x3\n`);
|
|
13686
|
+
emit_free(status);
|
|
13687
|
+
emit_asm(status, `mov x0, x20\n`);
|
|
13688
|
+
emit_strdup(status);
|
|
13689
|
+
emit_asm(status, `str x0, [x9]\n`);
|
|
13690
|
+
emit_asm(status, `str x21, [x9, #8]\n`);
|
|
13691
|
+
emit_asm(status, `ldp x20, x21, [sp], #16\n`);
|
|
13692
|
+
return true;
|
|
13693
|
+
}
|
|
13694
|
+
return false;
|
|
13695
|
+
}
|
|
13696
|
+
/** Pair store to [base_reg, #offset] with stur fallback (misaligned/far). */
|
|
13697
|
+
function emit_pair_store_to(status, base_reg, offset, a, b) {
|
|
13698
|
+
if (offset % 8 === 0 && offset + 8 <= 504 && offset >= 0) emit_asm(status, `stp ${a}, ${b}, [${base_reg}, #${offset}]\n`);
|
|
13699
|
+
else {
|
|
13700
|
+
emit_asm(status, `stur ${a}, [${base_reg}, #${offset}]\n`);
|
|
13701
|
+
emit_asm(status, `stur ${b}, [${base_reg}, #${offset + 8}]\n`);
|
|
13702
|
+
}
|
|
13703
|
+
}
|
|
13704
|
+
//#endregion
|
|
13507
13705
|
//#region ../src/build_aarch64/utils/auto_destroy.ts
|
|
13508
13706
|
/**
|
|
13509
13707
|
* Whether a declaration's initializer is a non-`move` FIELD ACCESS — a shallow
|
|
@@ -14346,6 +14544,27 @@ function mark_moved_if_struct(value, status, opts) {
|
|
|
14346
14544
|
status.moved.add(var_name);
|
|
14347
14545
|
}
|
|
14348
14546
|
}
|
|
14547
|
+
/**
|
|
14548
|
+
* strdup the UNRECORDED direct string fields of a value struct whose base is
|
|
14549
|
+
* in `base_reg` (+ base_offset) — the return-boundary normalization: the
|
|
14550
|
+
* sret byte-copy transfers the RECORDED (heap) fields raw, but unrecorded
|
|
14551
|
+
* fields may hold rodata/borrowed bytes the caller cannot free, so they are
|
|
14552
|
+
* deep-copied here and the returned struct is uniformly heap-owned.
|
|
14553
|
+
*/
|
|
14554
|
+
function emit_string_field_strdups_at(status, struct_name, base_reg, base_offset = 0, skip_fields) {
|
|
14555
|
+
const struct = status.structs.find((s) => s.name === struct_name && !s.is_simple_type && !s.is_class);
|
|
14556
|
+
if (!struct) return;
|
|
14557
|
+
for (const field of direct_string_fields(struct)) {
|
|
14558
|
+
if (skip_fields?.has(field.name)) continue;
|
|
14559
|
+
const off = base_offset + get_field_offset(struct_name, field.name, status);
|
|
14560
|
+
emit_asm(status, `str ${base_reg}, [sp, #-16]!\n`);
|
|
14561
|
+
emit_string_pair_load_at(status, base_reg, off);
|
|
14562
|
+
emit_strdup_string(status);
|
|
14563
|
+
emit_asm(status, `mov x13, x0\n`);
|
|
14564
|
+
emit_asm(status, `ldr ${base_reg}, [sp], #16\n`);
|
|
14565
|
+
emit_pair_store_to(status, base_reg, off, "x13", "x1");
|
|
14566
|
+
}
|
|
14567
|
+
}
|
|
14349
14568
|
//#endregion
|
|
14350
14569
|
//#region ../src/build_aarch64/utils/closure_a64.ts
|
|
14351
14570
|
/**
|
|
@@ -16823,28 +17042,6 @@ function value_number_loops(body, ast_list, status, mutate_ast) {
|
|
|
16823
17042
|
};
|
|
16824
17043
|
}
|
|
16825
17044
|
//#endregion
|
|
16826
|
-
//#region ../src/build_common/has_string_fields.ts
|
|
16827
|
-
/**
|
|
16828
|
-
* Whether any field (recursively, through nested owning value structs)
|
|
16829
|
-
* is an owned `string`. Shared by BOTH backends' owning-Buffer element
|
|
16830
|
-
* specialization (the slot deep-copies strdup'd strings, so a
|
|
16831
|
-
* string-field element is owning even when `struct_needs_destroy`
|
|
16832
|
-
* ignores string-only locals) — the C comment in
|
|
16833
|
-
* owning_buffer_specialize records the divergence this once caused.
|
|
16834
|
-
* `ref` and `view T` fields are skipped: a ref aliases the caller's
|
|
16835
|
-
* storage and a view's byte copy owns nothing.
|
|
16836
|
-
*/
|
|
16837
|
-
function has_string_fields(node, status) {
|
|
16838
|
-
for (const field of node.fields) {
|
|
16839
|
-
if (field.type.is_ref) continue;
|
|
16840
|
-
if (field.type.is_view) continue;
|
|
16841
|
-
if (field.type.name === "string" && !field.type.is_array) return true;
|
|
16842
|
-
const field_struct = status.structs.find((s) => s.name === field.type.name && !s.is_simple_type && !s.is_generic);
|
|
16843
|
-
if (field_struct && !field_struct.is_class && has_string_fields(field_struct, status)) return true;
|
|
16844
|
-
}
|
|
16845
|
-
return false;
|
|
16846
|
-
}
|
|
16847
|
-
//#endregion
|
|
16848
17045
|
//#region ../src/build_aarch64/utils/owning_buffer_specialize.ts
|
|
16849
17046
|
/**
|
|
16850
17047
|
* Detect whether a monomorphized struct is a `Buffer_<T>` whose element type
|
|
@@ -19458,6 +19655,16 @@ function build_function_node$1(node, status) {
|
|
|
19458
19655
|
}
|
|
19459
19656
|
param_idx++;
|
|
19460
19657
|
if (param.declaration === "var") status.function_param_vars.add(param.name);
|
|
19658
|
+
const is_free_function = (!node.scope || node.scope.node_type === "root" || node.scope.node_type === "func") && !node.params.some((p) => p.is_self_param);
|
|
19659
|
+
const owned_value_param = !param.is_self_param && !param.is_variadic && !param.type.is_ref && !param.type.is_nullable && !param.type.is_view && !param.type.is_array && param.type.name ? status.structs.find((st) => st.name === param.type.name && !st.is_simple_type && !st.is_class) : void 0;
|
|
19660
|
+
if (is_free_function && !node.address_escaped && owned_value_param && direct_string_fields(owned_value_param).length > 0 && !(owned_value_param.traits ?? []).length && !struct_needs_destroy(owned_value_param, status) && (status.normalized_struct_returners?.has(node.name) || status.normalized_struct_returners?.has(label_name)) && !status.func_address_taken?.has(node.name) && !status.func_address_taken?.has(label_name)) {
|
|
19661
|
+
status.function_param_vars.add(param.name);
|
|
19662
|
+
const param_decl = new DeclarationNode(param.start, "private", "var", param.name, param.type);
|
|
19663
|
+
param_decl.string_fields_via_pointer = true;
|
|
19664
|
+
status.scoped_declarations.push(param_decl);
|
|
19665
|
+
if (!status.heap_string_fields) status.heap_string_fields = /* @__PURE__ */ new Set();
|
|
19666
|
+
for (const field of direct_string_fields(owned_value_param)) status.heap_string_fields.add(`${param.name}.${field.name}`);
|
|
19667
|
+
}
|
|
19461
19668
|
if (param.type.is_array) {
|
|
19462
19669
|
if (!param.is_variadic ? array_struct_name(param.type, status) : void 0) {
|
|
19463
19670
|
if (!status.heap_array_vars) status.heap_array_vars = /* @__PURE__ */ new Set();
|
|
@@ -19669,144 +19876,6 @@ function build_function_node$1(node, status) {
|
|
|
19669
19876
|
status.function_return_type = void 0;
|
|
19670
19877
|
}
|
|
19671
19878
|
//#endregion
|
|
19672
|
-
//#region ../src/build_aarch64/utils/string_pair.ts
|
|
19673
|
-
/**
|
|
19674
|
-
* Load the string variable `name` into the consecutive register pair
|
|
19675
|
-
* (base, base+1) — default x0/x1. Handles the plain slot case; callers
|
|
19676
|
-
* that have register-allocated or param forms handle theirs first.
|
|
19677
|
-
*/
|
|
19678
|
-
function emit_string_pair_load(status, name, base = "x0") {
|
|
19679
|
-
const offset = status.stack_offsets?.get(name);
|
|
19680
|
-
if (offset === void 0) return false;
|
|
19681
|
-
const w = base.startsWith("x") ? base : "x0";
|
|
19682
|
-
const n = parseInt(w.substring(1), 10);
|
|
19683
|
-
if (offset + 8 > 504) {
|
|
19684
|
-
emit_asm(status, `ldr ${w}, [x29, #${offset}]\n`);
|
|
19685
|
-
emit_asm(status, `ldr x${n + 1}, [x29, #${offset + 8}]\n`);
|
|
19686
|
-
return true;
|
|
19687
|
-
}
|
|
19688
|
-
emit_asm(status, `ldp ${w}, x${n + 1}, [x29, #${offset}]\n`);
|
|
19689
|
-
return true;
|
|
19690
|
-
}
|
|
19691
|
-
/** Load the pair at [addr_reg, #offset] into (base, base+1). */
|
|
19692
|
-
function emit_string_pair_load_at(status, addr_reg, offset, base = "x0") {
|
|
19693
|
-
const w = base.startsWith("x") ? base : "x0";
|
|
19694
|
-
emit_asm(status, `ldp ${w}, x${parseInt(w.substring(1), 10) + 1}, [${addr_reg}, #${offset}]\n`);
|
|
19695
|
-
}
|
|
19696
|
-
/**
|
|
19697
|
-
* strdup the fat string in (x0=ptr, x1=len): preserve the len half across
|
|
19698
|
-
* the call and leave the owned copy's pair in (x0, x1). The caller keeps
|
|
19699
|
-
* ownership of the original.
|
|
19700
|
-
*/
|
|
19701
|
-
function emit_strdup_string(status) {
|
|
19702
|
-
emit_asm(status, `str x1, [sp, #-16]!\n`);
|
|
19703
|
-
emit_asm(status, status.audit ? `bl _nomen_strdup_wrap\n` : `bl _strdup\n`);
|
|
19704
|
-
emit_asm(status, `ldr x1, [sp], #16\n`);
|
|
19705
|
-
}
|
|
19706
|
-
/**
|
|
19707
|
-
* Pair load/store at [x29, #offset] with an arbitrary register pair —
|
|
19708
|
-
* splits into two single-word accesses beyond the ldp/stp +504 range.
|
|
19709
|
-
*/
|
|
19710
|
-
function emit_pair_load_x29(status, offset, a = "x0", b = "x1") {
|
|
19711
|
-
if (offset + 8 > 504) {
|
|
19712
|
-
emit_asm(status, `ldr ${a}, [x29, #${offset}]\n`);
|
|
19713
|
-
emit_asm(status, `ldr ${b}, [x29, #${offset + 8}]\n`);
|
|
19714
|
-
return;
|
|
19715
|
-
}
|
|
19716
|
-
emit_asm(status, `ldp ${a}, ${b}, [x29, #${offset}]\n`);
|
|
19717
|
-
}
|
|
19718
|
-
function emit_pair_store_x29(status, offset, a = "x0", b = "x1") {
|
|
19719
|
-
if (offset + 8 > 504) {
|
|
19720
|
-
emit_asm(status, `str ${a}, [x29, #${offset}]\n`);
|
|
19721
|
-
emit_asm(status, `str ${b}, [x29, #${offset + 8}]\n`);
|
|
19722
|
-
return;
|
|
19723
|
-
}
|
|
19724
|
-
emit_asm(status, `stp ${a}, ${b}, [x29, #${offset}]\n`);
|
|
19725
|
-
}
|
|
19726
|
-
/**
|
|
19727
|
-
* Owning specializations for Array<string>'s raw T-generic bodies
|
|
19728
|
-
* (`with` / `set`), replacing the shared-pointer copies with per-slot deep
|
|
19729
|
-
* copies — the aarch64 analog of Array.nm's `#if T_NEEDS_STRDUP` C branch.
|
|
19730
|
-
*
|
|
19731
|
-
* Conventions (matching Array.nm raw bodies):
|
|
19732
|
-
* - `with` (static): x0/x1 = value pair, x2 = count. Returns x0 = heap
|
|
19733
|
-
* buffer ([ptr] = length, elements at +8..).
|
|
19734
|
-
* - `set` (method): x19 = first element, x1 = index, x2/x3 = value pair.
|
|
19735
|
-
* Bounds are guaranteed by the Nomen-level constraints.
|
|
19736
|
-
*/
|
|
19737
|
-
function emit_owning_array_string_specialize(func_name, status, self_reg = "x19") {
|
|
19738
|
-
if (func_name === "with") {
|
|
19739
|
-
emit_asm(status, `stp x19, x20, [sp, #-16]!\n`);
|
|
19740
|
-
emit_asm(status, `stp x21, x22, [sp, #-16]!\n`);
|
|
19741
|
-
emit_asm(status, `stp x24, x25, [sp, #-16]!\n`);
|
|
19742
|
-
emit_asm(status, `mov x19, x0\n`);
|
|
19743
|
-
emit_asm(status, `mov x20, x1\n`);
|
|
19744
|
-
emit_asm(status, `mov x21, x2\n`);
|
|
19745
|
-
emit_asm(status, `add x0, x21, #1\n`);
|
|
19746
|
-
emit_asm(status, `lsl x0, x0, #4\n`);
|
|
19747
|
-
emit_malloc(status);
|
|
19748
|
-
emit_asm(status, `mov x22, x0\n`);
|
|
19749
|
-
emit_asm(status, `str x21, [x22]\n`);
|
|
19750
|
-
emit_asm(status, `mov x23, #8\n`);
|
|
19751
|
-
emit_asm(status, `mov x24, #0\n`);
|
|
19752
|
-
emit_asm(status, `.Larr_str_with_loop:\n`);
|
|
19753
|
-
emit_asm(status, `cmp x24, x21\n`);
|
|
19754
|
-
emit_asm(status, `b.ge .Larr_str_with_done\n`);
|
|
19755
|
-
emit_asm(status, `mov x0, x19\n`);
|
|
19756
|
-
emit_strdup(status);
|
|
19757
|
-
emit_asm(status, `add x25, x22, x23\n`);
|
|
19758
|
-
emit_asm(status, `stp x0, x20, [x25]\n`);
|
|
19759
|
-
emit_asm(status, `add x23, x23, #16\n`);
|
|
19760
|
-
emit_asm(status, `add x24, x24, #1\n`);
|
|
19761
|
-
emit_asm(status, `b .Larr_str_with_loop\n`);
|
|
19762
|
-
emit_asm(status, `.Larr_str_with_done:\n`);
|
|
19763
|
-
emit_asm(status, `mov x0, x22\n`);
|
|
19764
|
-
emit_asm(status, `ldp x24, x25, [sp], #16\n`);
|
|
19765
|
-
emit_asm(status, `ldp x21, x22, [sp], #16\n`);
|
|
19766
|
-
emit_asm(status, `ldp x19, x20, [sp], #16\n`);
|
|
19767
|
-
return true;
|
|
19768
|
-
}
|
|
19769
|
-
if (func_name === "at" || func_name === "first" || func_name === "at_end") {
|
|
19770
|
-
emit_asm(status, `stp x20, x21, [sp, #-16]!\n`);
|
|
19771
|
-
if (func_name === "at") {
|
|
19772
|
-
emit_asm(status, `lsl x9, x1, #4\n`);
|
|
19773
|
-
emit_asm(status, `add x9, ${self_reg}, x9\n`);
|
|
19774
|
-
} else if (func_name === "at_end") {
|
|
19775
|
-
emit_asm(status, `ldr x9, [${self_reg}, #-8]\n`);
|
|
19776
|
-
emit_asm(status, `sub x9, x9, #1\n`);
|
|
19777
|
-
emit_asm(status, `lsl x9, x9, #4\n`);
|
|
19778
|
-
emit_asm(status, `add x9, ${self_reg}, x9\n`);
|
|
19779
|
-
} else emit_asm(status, `add x9, ${self_reg}, #0\n`);
|
|
19780
|
-
emit_asm(status, `ldp x0, x1, [x9]\n`);
|
|
19781
|
-
emit_asm(status, `ldp x20, x21, [sp], #16\n`);
|
|
19782
|
-
return true;
|
|
19783
|
-
}
|
|
19784
|
-
if (func_name === "set") {
|
|
19785
|
-
emit_asm(status, `stp x20, x21, [sp, #-16]!\n`);
|
|
19786
|
-
emit_asm(status, `lsl x9, x1, #4\n`);
|
|
19787
|
-
emit_asm(status, `add x9, ${self_reg}, x9\n`);
|
|
19788
|
-
emit_asm(status, `ldr x0, [x9]\n`);
|
|
19789
|
-
emit_asm(status, `mov x20, x2\n`);
|
|
19790
|
-
emit_asm(status, `mov x21, x3\n`);
|
|
19791
|
-
emit_free(status);
|
|
19792
|
-
emit_asm(status, `mov x0, x20\n`);
|
|
19793
|
-
emit_strdup(status);
|
|
19794
|
-
emit_asm(status, `str x0, [x9]\n`);
|
|
19795
|
-
emit_asm(status, `str x21, [x9, #8]\n`);
|
|
19796
|
-
emit_asm(status, `ldp x20, x21, [sp], #16\n`);
|
|
19797
|
-
return true;
|
|
19798
|
-
}
|
|
19799
|
-
return false;
|
|
19800
|
-
}
|
|
19801
|
-
/** Pair store to [base_reg, #offset] with stur fallback (misaligned/far). */
|
|
19802
|
-
function emit_pair_store_to(status, base_reg, offset, a, b) {
|
|
19803
|
-
if (offset % 8 === 0 && offset + 8 <= 504 && offset >= 0) emit_asm(status, `stp ${a}, ${b}, [${base_reg}, #${offset}]\n`);
|
|
19804
|
-
else {
|
|
19805
|
-
emit_asm(status, `stur ${a}, [${base_reg}, #${offset}]\n`);
|
|
19806
|
-
emit_asm(status, `stur ${b}, [${base_reg}, #${offset + 8}]\n`);
|
|
19807
|
-
}
|
|
19808
|
-
}
|
|
19809
|
-
//#endregion
|
|
19810
19879
|
//#region ../src/build_aarch64/build_struct_node.ts
|
|
19811
19880
|
let field_strdup_guard_counter = 0;
|
|
19812
19881
|
function emit_typed_store(status, src_reg, dst_base, offset, size) {
|
|
@@ -21327,7 +21396,7 @@ function build_block_node$1(node, status) {
|
|
|
21327
21396
|
heap_slots: [],
|
|
21328
21397
|
struct_decls: []
|
|
21329
21398
|
});
|
|
21330
|
-
const declarations_before = status.scoped_declarations.length;
|
|
21399
|
+
const declarations_before = node.node_type === "func" ? 0 : status.scoped_declarations.length;
|
|
21331
21400
|
for (let child of node.statements) if (is_struct_node(child)) {
|
|
21332
21401
|
if (!should_emit_definition(child, status.emit_mode, status.structs, status.system_struct_names)) continue;
|
|
21333
21402
|
build_struct_node$1(child, status);
|
|
@@ -21421,6 +21490,69 @@ function call_in_set(set, call) {
|
|
|
21421
21490
|
return set.has(call.name.replace(/#/g, ""));
|
|
21422
21491
|
}
|
|
21423
21492
|
//#endregion
|
|
21493
|
+
//#region ../src/build_common/call_init_string_fields.ts
|
|
21494
|
+
/**
|
|
21495
|
+
* Whether a declaration-initializer expression is a value the callee's
|
|
21496
|
+
* return-boundary normalization made UNIFORMLY heap-owned, so the fresh
|
|
21497
|
+
* binding records every string field and frees them at scope exit:
|
|
21498
|
+
*
|
|
21499
|
+
* - a plain function call registered as a normalized struct returner
|
|
21500
|
+
* (the registry is populated at the callee's return sites — a callee
|
|
21501
|
+
* built later, or one that returns slot borrows, is not registered and
|
|
21502
|
+
* the binding keeps the pre-existing borrow behavior);
|
|
21503
|
+
* - an owned accessor (`pop`/`move_T`/`copy`) — move-out semantics
|
|
21504
|
+
* transfer slot-owned heap buffers to the binding.
|
|
21505
|
+
*
|
|
21506
|
+
* NOT normalized (never recorded): struct constructions (`Info(...)` — its
|
|
21507
|
+
* `#init` leaves rodata borrows and bypasses the return path) and container
|
|
21508
|
+
* BORROW accessors (`.at`/`.first`/`.slice`/`load` — the slot keeps
|
|
21509
|
+
* ownership; recording would free the container's storage).
|
|
21510
|
+
*/
|
|
21511
|
+
function is_normalized_struct_call(value, status) {
|
|
21512
|
+
if (!value) return false;
|
|
21513
|
+
if (value.node_type === "func_call") {
|
|
21514
|
+
const call = value;
|
|
21515
|
+
return !!status.normalized_struct_returners?.has(call.name);
|
|
21516
|
+
}
|
|
21517
|
+
if (value.node_type === "access") {
|
|
21518
|
+
const access = value.access;
|
|
21519
|
+
if (access?.node_type !== "access_func") return false;
|
|
21520
|
+
const name = access.name ?? "";
|
|
21521
|
+
if (is_container_borrow_accessor_name(name)) return false;
|
|
21522
|
+
if (status.normalized_struct_returners?.has(name)) return true;
|
|
21523
|
+
const target = value.target;
|
|
21524
|
+
const receiver_type = target ? target.type ?? void 0 : void 0;
|
|
21525
|
+
if (receiver_type?.name) {
|
|
21526
|
+
const label = `${mono_type_name(receiver_type)}_${name}`;
|
|
21527
|
+
if (status.normalized_struct_returners?.has(label)) return true;
|
|
21528
|
+
}
|
|
21529
|
+
return false;
|
|
21530
|
+
}
|
|
21531
|
+
return false;
|
|
21532
|
+
}
|
|
21533
|
+
/**
|
|
21534
|
+
* Record every direct string field of a call-initialized value-struct
|
|
21535
|
+
* binding (`var Info i = make()`) as heap-owned: the callee's return
|
|
21536
|
+
* normalization guarantees each field is a heap buffer, so scope-exit
|
|
21537
|
+
* auto_free must free them, and a later `i.field = …` write sees
|
|
21538
|
+
* old_was_heap and reclaims the displaced buffer instead of leaking it.
|
|
21539
|
+
* No-op for non-struct, class, view, and array types, for structs without
|
|
21540
|
+
* direct string fields, and for non-call initializers.
|
|
21541
|
+
*/
|
|
21542
|
+
function record_call_init_string_fields(node, status) {
|
|
21543
|
+
if (!is_normalized_struct_call(node.value, status)) return;
|
|
21544
|
+
const type = node.type;
|
|
21545
|
+
if (!type?.name) return;
|
|
21546
|
+
const mono_name = mono_type_name(type);
|
|
21547
|
+
if (mono_name.startsWith("_Tuple_")) return;
|
|
21548
|
+
const struct = status.structs.find((s) => s.name === mono_name && !s.is_simple_type);
|
|
21549
|
+
if (!struct || struct.is_class) return;
|
|
21550
|
+
const fields = direct_string_fields(struct);
|
|
21551
|
+
if (!fields.length) return;
|
|
21552
|
+
if (!status.heap_string_fields) status.heap_string_fields = /* @__PURE__ */ new Set();
|
|
21553
|
+
for (const field of fields) status.heap_string_fields.add(`${node.name}.${field.name}`);
|
|
21554
|
+
}
|
|
21555
|
+
//#endregion
|
|
21424
21556
|
//#region ../src/build_common/fold_string_const.ts
|
|
21425
21557
|
/**
|
|
21426
21558
|
* Fold a top-level `const`'s string `+` chain into a single string literal.
|
|
@@ -22554,12 +22686,15 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
|
|
|
22554
22686
|
build_swap_params(func_call, status);
|
|
22555
22687
|
} else {
|
|
22556
22688
|
const old_buffer = status.struct_return_buffer;
|
|
22689
|
+
const old_buffer_var = status.struct_return_buffer_var;
|
|
22557
22690
|
const old_preset = status.call_x8_preset;
|
|
22558
22691
|
emit_var_address(status, "x8", node.name);
|
|
22692
|
+
status.struct_return_buffer_var = node.name;
|
|
22559
22693
|
status.struct_return_buffer = "x8";
|
|
22560
22694
|
status.call_x8_preset = true;
|
|
22561
22695
|
emit_init_value$1(node.value, nir_init, status);
|
|
22562
22696
|
status.struct_return_buffer = old_buffer;
|
|
22697
|
+
status.struct_return_buffer_var = old_buffer_var;
|
|
22563
22698
|
status.call_x8_preset = old_preset;
|
|
22564
22699
|
}
|
|
22565
22700
|
return;
|
|
@@ -23085,14 +23220,18 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
|
|
|
23085
23220
|
build_swap_params(func_call, status);
|
|
23086
23221
|
} else if (status.structs.find((s) => s.name === (func_call.type?.name ?? func_call.name) && !s.is_simple_type && !s.is_class) && status.function_return_label) {
|
|
23087
23222
|
const old_buffer = status.struct_return_buffer;
|
|
23223
|
+
const old_buffer_var = status.struct_return_buffer_var;
|
|
23088
23224
|
const old_preset = status.call_x8_preset;
|
|
23089
23225
|
emit_var_address(status, "x8", node.name);
|
|
23226
|
+
status.struct_return_buffer_var = node.name;
|
|
23090
23227
|
status.struct_return_buffer = "x8";
|
|
23091
23228
|
status.call_x8_preset = true;
|
|
23092
23229
|
emit_init_value$1(node.value, nir_init, status);
|
|
23093
23230
|
status.struct_return_buffer = old_buffer;
|
|
23231
|
+
status.struct_return_buffer_var = old_buffer_var;
|
|
23094
23232
|
status.call_x8_preset = old_preset;
|
|
23095
23233
|
emit_var_address(status, "x0", node.name);
|
|
23234
|
+
record_call_init_string_fields(node, status);
|
|
23096
23235
|
} else {
|
|
23097
23236
|
emit_init_value$1(node.value, nir_init, status);
|
|
23098
23237
|
emit_var_store(status, "x0", node.name, struct_size);
|
|
@@ -23128,6 +23267,7 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
|
|
|
23128
23267
|
const field_type = (target_type.name ? status.structs.find((s) => s.name === target_type.name) : void 0)?.fields.find((f) => f.name === field_name)?.type ?? type_from_value_node$1(access.access);
|
|
23129
23268
|
emit_struct_copy("x1", "x0", get_field_offset(target_type.name, field_name, status), get_struct_size(field_type.name, status), status);
|
|
23130
23269
|
}
|
|
23270
|
+
record_call_init_string_fields(node, status);
|
|
23131
23271
|
}
|
|
23132
23272
|
}
|
|
23133
23273
|
} else if (node.value) {
|
|
@@ -24967,6 +25107,18 @@ function build_return_node$1(node, status, nir_value) {
|
|
|
24967
25107
|
const field_off = get_field_offset(ret_struct.name, field.name, status);
|
|
24968
25108
|
emit_enum_payload_strdups_at(status, field_enum.name, "x8", field_off);
|
|
24969
25109
|
}
|
|
25110
|
+
if (ret_struct && !returns_nullable_struct) {
|
|
25111
|
+
const returned_var = node.value.node_type === "value" ? node.value.value : "";
|
|
25112
|
+
if (returned_var !== "" && returned_var !== "0" && returned_var !== "null" && all_scope_frames(status).flat().some((d) => d.name === returned_var)) {
|
|
25113
|
+
let skip_fields;
|
|
25114
|
+
const prefix = `${node.value.value}.`;
|
|
25115
|
+
for (const key of status.heap_string_fields ?? []) if (key.startsWith(prefix)) {
|
|
25116
|
+
skip_fields ??= /* @__PURE__ */ new Set();
|
|
25117
|
+
skip_fields.add(key.slice(prefix.length));
|
|
25118
|
+
}
|
|
25119
|
+
emit_string_field_strdups_at(status, ret_struct.name, "x8", 0, skip_fields);
|
|
25120
|
+
}
|
|
25121
|
+
}
|
|
24970
25122
|
}
|
|
24971
25123
|
}
|
|
24972
25124
|
if (status.return_assign) {
|
|
@@ -28596,8 +28748,39 @@ function build_function_call_node$1(node, status) {
|
|
|
28596
28748
|
emit_asm(status, `str x9, [x29, #${off + struct_size}]\n`);
|
|
28597
28749
|
emit_asm(status, `add x0, x29, #${off}\n`);
|
|
28598
28750
|
}
|
|
28599
|
-
} else if (is_struct_type$3(param_type, status) || is_enum_with_data_type$2(param_type, status))
|
|
28600
|
-
|
|
28751
|
+
} else if (is_struct_type$3(param_type, status) || is_enum_with_data_type$2(param_type, status)) {
|
|
28752
|
+
if (node.move_owned_param_indices?.includes(i) && node.params[i].node_type === "value") {
|
|
28753
|
+
const transfer_struct = status.structs.find((st) => st.name === resolve_struct_name(param_type, param.type?.type_args, status) && !st.is_simple_type && !st.is_class && !st.is_generic);
|
|
28754
|
+
const transfer_var = node.params[i].value;
|
|
28755
|
+
if (transfer_struct && direct_string_fields(transfer_struct).length > 0 && transfer_var) {
|
|
28756
|
+
const recorded = /* @__PURE__ */ new Set();
|
|
28757
|
+
for (const key of status.heap_string_fields ?? []) if (key.startsWith(`${transfer_var}.`)) recorded.add(key.slice(transfer_var.length + 1));
|
|
28758
|
+
if (direct_string_fields(transfer_struct).filter((f) => !recorded.has(f.name)).length > 0) {
|
|
28759
|
+
emit_struct_address(node.params[i], status);
|
|
28760
|
+
ensure_newline(status);
|
|
28761
|
+
emit_string_field_strdups_at(status, transfer_struct.name, "x0", 0, recorded);
|
|
28762
|
+
if (status.call_x8_preset && status.struct_return_buffer_var) emit_var_address(status, "x8", status.struct_return_buffer_var);
|
|
28763
|
+
}
|
|
28764
|
+
}
|
|
28765
|
+
}
|
|
28766
|
+
const arg_struct = status.structs.find((st) => st.name === resolve_struct_name(param_type, param.type?.type_args, status) && !st.is_simple_type && !st.is_class && !st.is_generic);
|
|
28767
|
+
if (node.owned_value_param_indices?.includes(i) && arg_struct && !(arg_struct.traits ?? []).length && direct_string_fields(arg_struct).length > 0 && status.normalized_struct_returners?.has(func_name) && !status.func_address_taken?.has(func_name)) {
|
|
28768
|
+
const mono_name = arg_struct.name;
|
|
28769
|
+
const struct_size = get_struct_size(mono_name, status);
|
|
28770
|
+
const off = allocate_stack_space(status, struct_size);
|
|
28771
|
+
emit_struct_address(node.params[i], status);
|
|
28772
|
+
ensure_newline(status);
|
|
28773
|
+
emit_asm(status, `mov x1, x0\n`);
|
|
28774
|
+
emit_asm(status, `add x0, x29, #${off}\n`);
|
|
28775
|
+
for (let b = 0; b < struct_size; b += 8) {
|
|
28776
|
+
emit_asm(status, `ldr x2, [x1, #${b}]\n`);
|
|
28777
|
+
emit_asm(status, `str x2, [x0, #${b}]\n`);
|
|
28778
|
+
}
|
|
28779
|
+
emit_asm(status, `add x0, x29, #${off}\n`);
|
|
28780
|
+
emit_string_field_strdups_at(status, mono_name, "x0");
|
|
28781
|
+
if (status.call_x8_preset && status.struct_return_buffer_var) emit_var_address(status, "x8", status.struct_return_buffer_var);
|
|
28782
|
+
} else emit_struct_address(node.params[i], status);
|
|
28783
|
+
} else if (arg_deferrable(i)) {
|
|
28601
28784
|
deferred_args.push({
|
|
28602
28785
|
param: node.params[i],
|
|
28603
28786
|
slot: arg_slot[i]
|
|
@@ -28729,6 +28912,7 @@ function build_function_call_node$1(node, status) {
|
|
|
28729
28912
|
const tname = all_scope_frames(status).flat().find((d) => d.name === vname)?.type?.name ?? param.type?.name;
|
|
28730
28913
|
if (tname === "string") continue;
|
|
28731
28914
|
if (tname && status.enums.find((e) => e.name === tname && e.has_associated_data)) continue;
|
|
28915
|
+
if (vname !== void 0 && (node.move_owned_param_indices?.includes(idx) ?? false)) clear_heap_string_fields(status, vname);
|
|
28732
28916
|
}
|
|
28733
28917
|
if (param) mark_moved_if_struct(param, status);
|
|
28734
28918
|
}
|
|
@@ -31057,11 +31241,23 @@ function free_scoped_declarations(status, decls, persist_string_field_records =
|
|
|
31057
31241
|
commented = true;
|
|
31058
31242
|
}
|
|
31059
31243
|
const dot = key.indexOf(".");
|
|
31060
|
-
|
|
31244
|
+
const base = c_function_name(key.substring(0, dot));
|
|
31245
|
+
const arrow = dec.string_fields_via_pointer ? "->" : ".";
|
|
31246
|
+
const field = key.substring(dot + 1);
|
|
31247
|
+
const free_fn = status.audit ? "nomen_free_wrap" : "free";
|
|
31248
|
+
status.code += `${free_fn}(${base}${arrow}${field}.ptr);\n`;
|
|
31061
31249
|
if (!persist_string_field_records) status.heap_string_fields.delete(key);
|
|
31062
31250
|
}
|
|
31063
31251
|
}
|
|
31064
31252
|
for (const dec of decls) {
|
|
31253
|
+
if (dec.owned_heap_shell) {
|
|
31254
|
+
if (!commented) {
|
|
31255
|
+
status.code += "\n// Auto-free\n";
|
|
31256
|
+
commented = true;
|
|
31257
|
+
}
|
|
31258
|
+
status.code += `${status.audit ? "nomen_free_wrap" : "free"}(${c_function_name(dec.name)});\n`;
|
|
31259
|
+
continue;
|
|
31260
|
+
}
|
|
31065
31261
|
if (persist_string_field_records && dec.force_owned_string === true) continue;
|
|
31066
31262
|
if (status.moved?.has(dec.name)) continue;
|
|
31067
31263
|
const cname = c_function_name(dec.name);
|
|
@@ -31659,7 +31855,8 @@ function build_assignment_node$1(node, status, nir_rhs, nir_swap) {
|
|
|
31659
31855
|
status.init_assigned_fields.add(tracked_key);
|
|
31660
31856
|
}
|
|
31661
31857
|
const old_was_heap = !!target_struct?.is_class && !first_init_write || !!status.heap_string_fields?.has(tracked_key);
|
|
31662
|
-
|
|
31858
|
+
const is_class_target = !!target_struct?.is_class;
|
|
31859
|
+
if (target_struct && (target_var || is_class_target) && (!self_target || is_class_target)) {
|
|
31663
31860
|
const fresh_heap = is_owned_heap_temp$1(node.right_value, status);
|
|
31664
31861
|
const rhs_is_null_value = node.right_value.node_type === "value" && node.right_value.value === "null";
|
|
31665
31862
|
const saved_len = begin_code_scratch(status);
|
|
@@ -32741,6 +32938,7 @@ function build_declaration_node(node, status, nir_init, nir_swap) {
|
|
|
32741
32938
|
emit_swap_value$1(node.swap, nir_swap, status);
|
|
32742
32939
|
}
|
|
32743
32940
|
if (has_field_overrides(node.value)) emit_field_overrides(safe_name, node.value, build_node$1, status, ";\n", ";\n");
|
|
32941
|
+
record_call_init_string_fields(node, status);
|
|
32744
32942
|
}
|
|
32745
32943
|
}
|
|
32746
32944
|
}
|
|
@@ -33256,6 +33454,7 @@ function build_return_node(node, status, nir_value) {
|
|
|
33256
33454
|
}
|
|
33257
33455
|
}
|
|
33258
33456
|
let returned_value_decl;
|
|
33457
|
+
let returned_recorded_string_fields;
|
|
33259
33458
|
if (node.value.node_type === "value") {
|
|
33260
33459
|
const value = node.value.value;
|
|
33261
33460
|
returned_value_decl = find_decl_across_scopes(value, status);
|
|
@@ -33269,7 +33468,11 @@ function build_return_node(node, status, nir_value) {
|
|
|
33269
33468
|
}
|
|
33270
33469
|
if (status.heap_string_fields?.size) {
|
|
33271
33470
|
const prefix = `${value}.`;
|
|
33272
|
-
for (const key of Array.from(status.heap_string_fields)) if (key.startsWith(prefix))
|
|
33471
|
+
for (const key of Array.from(status.heap_string_fields)) if (key.startsWith(prefix)) {
|
|
33472
|
+
returned_recorded_string_fields ??= /* @__PURE__ */ new Set();
|
|
33473
|
+
returned_recorded_string_fields.add(key.slice(prefix.length));
|
|
33474
|
+
status.heap_string_fields.delete(key);
|
|
33475
|
+
}
|
|
33273
33476
|
}
|
|
33274
33477
|
}
|
|
33275
33478
|
if (ret_type?.is_array && return_array_var && return_array_len > 0) {
|
|
@@ -33378,6 +33581,11 @@ function build_return_node(node, status, nir_value) {
|
|
|
33378
33581
|
status.code += `;\n`;
|
|
33379
33582
|
if (node.value.node_type === "access" && node.value.access?.node_type === "access_field" && !returns_string_zero && !returns_struct_zero && !returns_view_value && ret_type.name !== "string" && !ret_type.is_view && !ret_type.is_array && status.enums.find((e) => e.name === ret_type.name && e.has_associated_data)) status.code += `_return_val = ${ret_type.name}_copy(_return_val);\n`;
|
|
33380
33583
|
if (has_field_overrides(node.value)) emit_field_overrides("_return_val", node.value, build_node$1, status, "", ";\n");
|
|
33584
|
+
const bare_local_return = node.value.node_type === "value" && node.value.value !== "0" && node.value.value !== "null" && !!returned_value_decl;
|
|
33585
|
+
if (is_struct && !return_is_class && !ret_type.is_view && !ret_type.is_array && return_struct && !returns_struct_zero && bare_local_return) for (const field of direct_string_fields(return_struct)) {
|
|
33586
|
+
if (returned_recorded_string_fields?.has(field.name)) continue;
|
|
33587
|
+
status.code += `_return_val.${field.name} = nomen_str_dup(_return_val.${field.name});\n`;
|
|
33588
|
+
}
|
|
33381
33589
|
emit_nursery_joins_on_return_c(status);
|
|
33382
33590
|
reclaim_all_c_scopes(status);
|
|
33383
33591
|
status.code += `return _return_val;\n`;
|
|
@@ -35328,6 +35536,18 @@ function build_function_node(node, status) {
|
|
|
35328
35536
|
if (param_trait && !param_struct?.is_class) decl.trait_class_trait = param.type.name;
|
|
35329
35537
|
status.scoped_declarations.push(decl);
|
|
35330
35538
|
}
|
|
35539
|
+
const is_free_function = (!node.scope || node.scope.node_type === "root" || node.scope.node_type === "func") && !node.params.some((p) => p.is_self_param);
|
|
35540
|
+
const fn_name = node.name;
|
|
35541
|
+
const fn_label = emission_label(node);
|
|
35542
|
+
const owned_value_struct = is_free_function && !node.address_escaped && !param.is_self_param && !param.is_variadic && !param.type.is_ref && !param.type.is_nullable && !param.type.is_view && !param.type.is_array && param.type.name ? status.structs.find((st) => st.name === param.type.name && !st.is_simple_type && !st.is_class) : void 0;
|
|
35543
|
+
if (owned_value_struct && !(owned_value_struct.traits ?? []).length && direct_string_fields(owned_value_struct).length > 0 && !struct_needs_destroy(owned_value_struct, status) && (status.normalized_struct_returners?.has(fn_name) || status.normalized_struct_returners?.has(fn_label)) && !status.func_address_taken?.has(fn_name) && !status.func_address_taken?.has(fn_label)) {
|
|
35544
|
+
const decl = new DeclarationNode(param.start, "private", "var", pname, param.type);
|
|
35545
|
+
decl.string_fields_via_pointer = true;
|
|
35546
|
+
decl.owned_heap_shell = !param.is_moved;
|
|
35547
|
+
status.scoped_declarations.push(decl);
|
|
35548
|
+
if (!status.heap_string_fields) status.heap_string_fields = /* @__PURE__ */ new Set();
|
|
35549
|
+
for (const field of direct_string_fields(owned_value_struct)) status.heap_string_fields.add(`${pname}.${field.name}`);
|
|
35550
|
+
}
|
|
35331
35551
|
}
|
|
35332
35552
|
}
|
|
35333
35553
|
const nir = node.statements?.length ? lower_function(node) : void 0;
|
|
@@ -36568,6 +36788,27 @@ function build_function_call_node(node, status) {
|
|
|
36568
36788
|
continue;
|
|
36569
36789
|
}
|
|
36570
36790
|
const param_type = type_from_value_node$1(node.params[i]);
|
|
36791
|
+
if (node.move_owned_param_indices?.includes(i) && node.params[i].node_type === "value") {
|
|
36792
|
+
const transfer_struct = status.structs.find((st) => st.name === mono_type_name(param_type) && !st.is_simple_type && !st.is_class && !st.is_generic);
|
|
36793
|
+
const transfer_var = node.params[i].value;
|
|
36794
|
+
if (transfer_struct && direct_string_fields(transfer_struct).length > 0 && transfer_var) {
|
|
36795
|
+
const recorded = /* @__PURE__ */ new Set();
|
|
36796
|
+
for (const key of status.heap_string_fields ?? []) if (key.startsWith(`${transfer_var}.`)) recorded.add(key.slice(transfer_var.length + 1));
|
|
36797
|
+
const unrecorded = direct_string_fields(transfer_struct).filter((f) => !recorded.has(f.name));
|
|
36798
|
+
if (unrecorded.length > 0) {
|
|
36799
|
+
const saved_arg = begin_code_scratch(status);
|
|
36800
|
+
status.code += `(void *)&`;
|
|
36801
|
+
status.suppress_dereference = true;
|
|
36802
|
+
build_node$1(node.params[i], status);
|
|
36803
|
+
status.suppress_dereference = false;
|
|
36804
|
+
const addr_text = end_code_scratch(status, saved_arg);
|
|
36805
|
+
status.code += `(void *)({ struct ${transfer_struct.name} *_t = (struct ${transfer_struct.name} *)${addr_text}; `;
|
|
36806
|
+
for (const f of unrecorded) status.code += `_t->${f.name} = nomen_str_dup(_t->${f.name}); `;
|
|
36807
|
+
status.code += `(void *)_t; })`;
|
|
36808
|
+
continue;
|
|
36809
|
+
}
|
|
36810
|
+
}
|
|
36811
|
+
}
|
|
36571
36812
|
if (node.nullable_param_indices?.includes(i) && node.params[i].node_type === "value" && node.params[i].value === "null") {
|
|
36572
36813
|
status.code += `(void *)&(struct ${param_type.name}){0}, 0`;
|
|
36573
36814
|
continue;
|
|
@@ -36614,6 +36855,23 @@ function build_function_call_node(node, status) {
|
|
|
36614
36855
|
status.suppress_dereference = false;
|
|
36615
36856
|
continue;
|
|
36616
36857
|
}
|
|
36858
|
+
const arg_struct = status.structs.find((st) => st.name === mono_type_name(param_type) && !st.is_simple_type && !st.is_class && !st.is_generic);
|
|
36859
|
+
if (node.owned_value_param_indices?.includes(i) && !param_is_class && arg_struct && !(arg_struct.traits ?? []).length && direct_string_fields(arg_struct).length > 0 && status.normalized_struct_returners?.has(func_name) && !status.func_address_taken?.has(func_name)) {
|
|
36860
|
+
const struct_name = arg_struct.name;
|
|
36861
|
+
const fields = direct_string_fields(arg_struct);
|
|
36862
|
+
const saved_arg = begin_code_scratch(status);
|
|
36863
|
+
status.code += `(void *)&`;
|
|
36864
|
+
status.suppress_dereference = true;
|
|
36865
|
+
build_node$1(node.params[i], status);
|
|
36866
|
+
status.suppress_dereference = false;
|
|
36867
|
+
const addr_text = end_code_scratch(status, saved_arg);
|
|
36868
|
+
status.code += `(void *)({ struct ${struct_name} *_src = (struct ${struct_name} *)${addr_text}; `;
|
|
36869
|
+
status.code += `struct ${struct_name} *_own = (struct ${struct_name} *)malloc(sizeof(struct ${struct_name})); `;
|
|
36870
|
+
status.code += `*_own = *_src; `;
|
|
36871
|
+
for (const field of fields) status.code += `_own->${field.name} = nomen_str_dup(_own->${field.name}); `;
|
|
36872
|
+
status.code += `_own; })`;
|
|
36873
|
+
continue;
|
|
36874
|
+
}
|
|
36617
36875
|
if (!param_is_class && !!status.structs.find((s) => s.name === param_type.name && !s.is_simple_type) || !!status.traits.find((t) => t.name === param_type.name) || is_ref_param) {
|
|
36618
36876
|
if (node.params[i].node_type === "value" && (status.function_ref_params?.has(node.params[i].value) || status.class_vars?.has(node.params[i].value))) status.suppress_dereference = true;
|
|
36619
36877
|
else if (is_ref_param) status.code += `&`;
|
|
@@ -36690,9 +36948,12 @@ function build_function_call_node(node, status) {
|
|
|
36690
36948
|
if (decl_hit) decl_hit.frame.splice(decl_hit.index, 1);
|
|
36691
36949
|
if (is_value_struct) {
|
|
36692
36950
|
const prefix = `${vname}.`;
|
|
36951
|
+
const transfer = node.move_owned_param_indices?.includes(idx) ?? false;
|
|
36693
36952
|
for (const key of Array.from(status.heap_string_fields ?? [])) if (key.startsWith(prefix)) {
|
|
36694
|
-
if (!
|
|
36695
|
-
|
|
36953
|
+
if (!transfer) {
|
|
36954
|
+
if (!status.pending_string_releases) status.pending_string_releases = [];
|
|
36955
|
+
status.pending_string_releases.push(`free(${key}.ptr);`);
|
|
36956
|
+
}
|
|
36696
36957
|
status.heap_string_fields.delete(key);
|
|
36697
36958
|
}
|
|
36698
36959
|
}
|
|
@@ -38353,6 +38614,10 @@ function get_base_address(access, status, reg) {
|
|
|
38353
38614
|
if (paramReg !== reg) emit_asm(status, `mov ${reg}, ${paramReg}\n`);
|
|
38354
38615
|
} else if (is_local_ref_var(name, status)) emit_deref_var_address(status, reg, name);
|
|
38355
38616
|
else emit_var_address(status, reg, name);
|
|
38617
|
+
} else if (access_chain_crosses_class(access, status)) {
|
|
38618
|
+
build_node(access.target, status);
|
|
38619
|
+
ensure_newline(status);
|
|
38620
|
+
if (reg !== "x0") emit_asm(status, `mov ${reg}, x0\n`);
|
|
38356
38621
|
} else {
|
|
38357
38622
|
emit_address_of(access.target, status);
|
|
38358
38623
|
ensure_newline(status);
|
|
@@ -41871,6 +42136,9 @@ function emit_address_of(node, status) {
|
|
|
41871
42136
|
if (paramReg !== "x0") emit_asm(status, `mov x0, ${paramReg}\n`);
|
|
41872
42137
|
} else if (is_local_ref_var(name, status)) emit_deref_var_address(status, "x0", name);
|
|
41873
42138
|
else emit_var_address(status, "x0", name);
|
|
42139
|
+
} else if (access_chain_crosses_class(access, status)) {
|
|
42140
|
+
build_node(access.target, status);
|
|
42141
|
+
ensure_newline(status);
|
|
41874
42142
|
} else {
|
|
41875
42143
|
emit_address_of(access.target, status);
|
|
41876
42144
|
ensure_newline(status);
|
|
@@ -44254,6 +44522,195 @@ function scan_func(func, struct_name, result, class_type_names) {
|
|
|
44254
44522
|
}
|
|
44255
44523
|
}
|
|
44256
44524
|
//#endregion
|
|
44525
|
+
//#region ../src/build_common/struct_return_classification.ts
|
|
44526
|
+
/**
|
|
44527
|
+
* Pre-build classification of which functions return value structs whose
|
|
44528
|
+
* string fields are UNIFORMLY heap-owned via return-boundary normalization.
|
|
44529
|
+
* Populates `status.normalized_struct_returners`; a caller binding a call
|
|
44530
|
+
* result of a registered function records the struct's string fields and
|
|
44531
|
+
* frees them at scope exit, while bindings of unregistered callees keep the
|
|
44532
|
+
* pre-existing borrow behavior (never recorded, never freed — sound, though
|
|
44533
|
+
* it may leak on slot overwrites, which is the documented status quo).
|
|
44534
|
+
*
|
|
44535
|
+
* The rule is deliberately narrow: a function normalizes iff EVERY
|
|
44536
|
+
* struct-return statement returns a BARE VARIABLE (a local or parameter).
|
|
44537
|
+
* That is the transfer shape — the returned variable dies at the return, so
|
|
44538
|
+
* the boundary normalization (transfer recorded heap fields, strdup the
|
|
44539
|
+
* rest) is sound and leak-free. Every other shape keeps the status quo:
|
|
44540
|
+
*
|
|
44541
|
+
* - container borrow accessors (`at`/`first`/`slice`/`load` — e.g.
|
|
44542
|
+
* `Map.get`'s `return self.values.load_T(idx)`) yield SLOT-owned bytes;
|
|
44543
|
+
* normalizing them would leak in expression-temp consumers
|
|
44544
|
+
* (`m.get(k).field`) that nobody records or frees;
|
|
44545
|
+
* - owned accessors (`pop`/`move_T`/`copy`) transfer slot-owned heap
|
|
44546
|
+
* buffers — the caller's recording is sound WITHOUT normalization, so
|
|
44547
|
+
* they record via the owned-accessor rule (see
|
|
44548
|
+
* call_init_string_fields.ts) and the callee skips the copy;
|
|
44549
|
+
* - forwarded calls (`return make()`) are skipped when the callee is
|
|
44550
|
+
* registered (its result is already uniformly owned — copying it would
|
|
44551
|
+
* leak the inner buffers) and keep the status quo when it is not.
|
|
44552
|
+
*
|
|
44553
|
+
* This runs as a WHOLE-PROGRAM AST PRE-PASS (before any function body is
|
|
44554
|
+
* built) because a build-order-sensitive registry cannot work: nested
|
|
44555
|
+
* functions are built after their enclosing function's body, so a runtime
|
|
44556
|
+
* registration would be invisible to the caller's earlier lookup.
|
|
44557
|
+
*/
|
|
44558
|
+
/** Collect every function and struct declaration under the AST (including
|
|
44559
|
+
* nested functions, struct methods, and function bodies at any depth). */
|
|
44560
|
+
function collect(node, fns, structs, seen) {
|
|
44561
|
+
if (!node || typeof node !== "object" || seen.has(node)) return;
|
|
44562
|
+
seen.add(node);
|
|
44563
|
+
if (node.node_type === "func") fns.push(node);
|
|
44564
|
+
if (node.node_type === "struct") structs.push(node);
|
|
44565
|
+
for (const key of Object.keys(node)) {
|
|
44566
|
+
if (key === "parent" || key === "scope") continue;
|
|
44567
|
+
const value = node[key];
|
|
44568
|
+
if (Array.isArray(value)) {
|
|
44569
|
+
for (const item of value) if (item && typeof item === "object" && "node_type" in item) collect(item, fns, structs, seen);
|
|
44570
|
+
} else if (value && typeof value === "object" && "node_type" in value) collect(value, fns, structs, seen);
|
|
44571
|
+
}
|
|
44572
|
+
}
|
|
44573
|
+
/** The DIRECT `return <expr>` statements of a function body (nested function
|
|
44574
|
+
* declarations are not descended into — their returns belong to them). */
|
|
44575
|
+
function direct_returns(fn) {
|
|
44576
|
+
const out = [];
|
|
44577
|
+
const seen = /* @__PURE__ */ new Set();
|
|
44578
|
+
const walk = (node) => {
|
|
44579
|
+
if (!node || typeof node !== "object" || seen.has(node)) return;
|
|
44580
|
+
seen.add(node);
|
|
44581
|
+
if (node.node_type === "func" && node !== fn) return;
|
|
44582
|
+
if (node.node_type === "return") out.push(node);
|
|
44583
|
+
for (const key of Object.keys(node)) {
|
|
44584
|
+
if (key === "parent" || key === "scope") continue;
|
|
44585
|
+
const value = node[key];
|
|
44586
|
+
if (Array.isArray(value)) {
|
|
44587
|
+
for (const item of value) if (item && typeof item === "object" && "node_type" in item) walk(item);
|
|
44588
|
+
} else if (value && typeof value === "object" && "node_type" in value) walk(value);
|
|
44589
|
+
}
|
|
44590
|
+
};
|
|
44591
|
+
for (const statement of fn.statements ?? []) walk(statement);
|
|
44592
|
+
return out;
|
|
44593
|
+
}
|
|
44594
|
+
/** Whether the function is a FREE function: not a method of a struct (whose
|
|
44595
|
+
* element/slot params are passed by address for in-place access) and not a
|
|
44596
|
+
* monomorphized container clone. */
|
|
44597
|
+
function is_free_function_node(fn) {
|
|
44598
|
+
const scope = fn.scope;
|
|
44599
|
+
return !scope || scope.node_type === "root" || scope.node_type === "func";
|
|
44600
|
+
}
|
|
44601
|
+
/**
|
|
44602
|
+
* Whole-program pre-pass: classify free functions that take an OWNING value
|
|
44603
|
+
* struct parameter (string fields) — those get pass-by-value semantics:
|
|
44604
|
+
*
|
|
44605
|
+
* - the CALL SITE materializes a uniformly heap-owned shell (struct-copy
|
|
44606
|
+
* + per-string-field strdup) and passes its address;
|
|
44607
|
+
* - the CALLEE seeds the param's string-field records at entry and frees
|
|
44608
|
+
* the surviving fields (and the shell) at scope exit.
|
|
44609
|
+
*
|
|
44610
|
+
* Writes through the param then stay local to the callee; the caller's
|
|
44611
|
+
* variable is never aliased or mutated. Functions whose address escapes
|
|
44612
|
+
* into a func-typed value (`var f = g`, `apply(g, …)`, `b.modify(0, touch)`)
|
|
44613
|
+
* are EXCLUDED: they may be invoked through a pointer with a borrowed
|
|
44614
|
+
* argument, and keep the status quo (borrow + the documented write leak).
|
|
44615
|
+
*
|
|
44616
|
+
* Populates `status.normalized_struct_returners` (the materializing set) and
|
|
44617
|
+
* `status.func_address_taken` (the excluded set) for the call-site and
|
|
44618
|
+
* function-definition builders.
|
|
44619
|
+
*/
|
|
44620
|
+
function gather_normalized_struct_returners(root, status) {
|
|
44621
|
+
const fns = [];
|
|
44622
|
+
const structs = [];
|
|
44623
|
+
collect(root, fns, structs, /* @__PURE__ */ new Set());
|
|
44624
|
+
const address_taken = /* @__PURE__ */ new Set();
|
|
44625
|
+
const value_walk = (node) => {
|
|
44626
|
+
if (!node || typeof node !== "object") return;
|
|
44627
|
+
if (node.node_type === "value") {
|
|
44628
|
+
const v = node;
|
|
44629
|
+
if (typeof v.value === "string" && v.type?.name === "func" && /^[A-Za-z_]/.test(v.value)) address_taken.add(v.value);
|
|
44630
|
+
}
|
|
44631
|
+
for (const key of Object.keys(node)) {
|
|
44632
|
+
if (key === "parent" || key === "scope") continue;
|
|
44633
|
+
const child = node[key];
|
|
44634
|
+
if (Array.isArray(child)) {
|
|
44635
|
+
for (const item of child) if (item && typeof item === "object" && "node_type" in item) value_walk(item);
|
|
44636
|
+
} else if (child && typeof child === "object" && "node_type" in child) value_walk(child);
|
|
44637
|
+
}
|
|
44638
|
+
};
|
|
44639
|
+
value_walk(root);
|
|
44640
|
+
const decl_walk = (node) => {
|
|
44641
|
+
if (!node || typeof node !== "object") return;
|
|
44642
|
+
const d = node;
|
|
44643
|
+
if (d.node_type === "declaration" && typeof d.name === "string" && d.type?.name === "func") address_taken.add(d.name);
|
|
44644
|
+
for (const key of Object.keys(node)) {
|
|
44645
|
+
if (key === "parent" || key === "scope") continue;
|
|
44646
|
+
const child = node[key];
|
|
44647
|
+
if (Array.isArray(child)) {
|
|
44648
|
+
for (const item of child) if (item && typeof item === "object" && "node_type" in item) decl_walk(item);
|
|
44649
|
+
} else if (child && typeof child === "object" && "node_type" in child) decl_walk(child);
|
|
44650
|
+
}
|
|
44651
|
+
};
|
|
44652
|
+
decl_walk(root);
|
|
44653
|
+
const return_candidates = [];
|
|
44654
|
+
for (const fn of fns) {
|
|
44655
|
+
if (!is_free_function_node(fn)) continue;
|
|
44656
|
+
if (address_taken.has(fn.name)) continue;
|
|
44657
|
+
const ret = fn.return_type;
|
|
44658
|
+
if (!ret || ret.is_array || ret.is_view) continue;
|
|
44659
|
+
const ret_struct = structs.find((st) => st.name === ret.name && !st.is_simple_type && !st.is_class);
|
|
44660
|
+
if (!ret_struct || direct_string_fields(ret_struct).length === 0) continue;
|
|
44661
|
+
return_candidates.push(fn);
|
|
44662
|
+
}
|
|
44663
|
+
const return_normalizing = new Set(return_candidates.map((c) => c.name));
|
|
44664
|
+
let changed = true;
|
|
44665
|
+
while (changed) {
|
|
44666
|
+
changed = false;
|
|
44667
|
+
for (const fn of return_candidates) {
|
|
44668
|
+
if (!return_normalizing.has(fn.name)) continue;
|
|
44669
|
+
for (const ret of direct_returns(fn)) {
|
|
44670
|
+
const value = ret.value ?? void 0;
|
|
44671
|
+
if (value && value.node_type === "value") continue;
|
|
44672
|
+
if (value && value.node_type === "func_call" && return_normalizing.has(value.name ?? "")) continue;
|
|
44673
|
+
return_normalizing.delete(fn.name);
|
|
44674
|
+
changed = true;
|
|
44675
|
+
break;
|
|
44676
|
+
}
|
|
44677
|
+
}
|
|
44678
|
+
}
|
|
44679
|
+
for (const fn of fns) {
|
|
44680
|
+
if (!is_free_function_node(fn)) continue;
|
|
44681
|
+
if (address_taken.has(fn.name)) continue;
|
|
44682
|
+
if (fn.params.some((p) => p.is_self_param)) continue;
|
|
44683
|
+
if (fn.address_escaped) continue;
|
|
44684
|
+
if (!fn.has_body) continue;
|
|
44685
|
+
for (const param of fn.params ?? []) {
|
|
44686
|
+
if (param.is_self_param || param.is_variadic) continue;
|
|
44687
|
+
if (param.type.is_ref || param.type.is_nullable || param.type.is_view || param.type.is_array) continue;
|
|
44688
|
+
const struct = param.type.name ? structs.find((st) => st.name === param.type.name && !st.is_simple_type && !st.is_class) : void 0;
|
|
44689
|
+
if (!struct) continue;
|
|
44690
|
+
if (direct_string_fields(struct).length > 0 && !(struct.traits ?? []).length && !struct_needs_destroy(struct, status)) {
|
|
44691
|
+
if (!status.normalized_struct_returners) status.normalized_struct_returners = /* @__PURE__ */ new Set();
|
|
44692
|
+
status.normalized_struct_returners.add(fn.name);
|
|
44693
|
+
status.normalized_struct_returners.add(emission_label(fn));
|
|
44694
|
+
break;
|
|
44695
|
+
}
|
|
44696
|
+
}
|
|
44697
|
+
if (return_normalizing.has(fn.name)) {
|
|
44698
|
+
if (!status.normalized_struct_returners) status.normalized_struct_returners = /* @__PURE__ */ new Set();
|
|
44699
|
+
status.normalized_struct_returners.add(fn.name);
|
|
44700
|
+
status.normalized_struct_returners.add(emission_label(fn));
|
|
44701
|
+
}
|
|
44702
|
+
}
|
|
44703
|
+
for (const struct of structs) {
|
|
44704
|
+
if (struct.is_class || struct.is_generic || struct.is_simple_type) continue;
|
|
44705
|
+
if (!struct.functions.some((f) => f.name === "copy" && f.has_body)) continue;
|
|
44706
|
+
if (direct_string_fields(struct).length === 0) continue;
|
|
44707
|
+
if (!status.normalized_struct_returners) status.normalized_struct_returners = /* @__PURE__ */ new Set();
|
|
44708
|
+
status.normalized_struct_returners.add(`${struct.name}_copy`);
|
|
44709
|
+
}
|
|
44710
|
+
if (!status.func_address_taken) status.func_address_taken = /* @__PURE__ */ new Set();
|
|
44711
|
+
for (const name of address_taken) status.func_address_taken.add(name);
|
|
44712
|
+
}
|
|
44713
|
+
//#endregion
|
|
44257
44714
|
//#region ../src/build.ts
|
|
44258
44715
|
function build(root, options = {}) {
|
|
44259
44716
|
let status = {
|
|
@@ -44287,6 +44744,7 @@ function build(root, options = {}) {
|
|
|
44287
44744
|
reset_match_temp_counter();
|
|
44288
44745
|
reset_ns_tmp_counter();
|
|
44289
44746
|
stamp_last_use_moves(root);
|
|
44747
|
+
gather_normalized_struct_returners(root, status);
|
|
44290
44748
|
if (options.arch === "aarch64") {
|
|
44291
44749
|
install_asm_code_buffer(status);
|
|
44292
44750
|
reset_string_counter$1();
|
|
@@ -46510,6 +46968,23 @@ function is_owning_struct_type_requiring_move(type, status) {
|
|
|
46510
46968
|
if (!s || s.is_class) return false;
|
|
46511
46969
|
return struct_owns_non_string_heap(s, status, /* @__PURE__ */ new Set());
|
|
46512
46970
|
}
|
|
46971
|
+
/**
|
|
46972
|
+
* Whether `type` is a concrete value struct with direct string fields whose
|
|
46973
|
+
* only ownership those strings — the pass-by-value convention's domain: a
|
|
46974
|
+
* parameter of this type is fully owned by the callee (the call boundary
|
|
46975
|
+
* materializes a uniformly heap-owned copy), so `var` field writes inside
|
|
46976
|
+
* the body stay local and no call-site acknowledgment is needed. Excludes
|
|
46977
|
+
* structs that own non-string heap (those keep `move ... swap` / `ref`
|
|
46978
|
+
* semantics) and structs conforming to traits (trait dispatch aliases).
|
|
46979
|
+
*/
|
|
46980
|
+
function is_pass_by_value_owning_struct(type, status) {
|
|
46981
|
+
const s = resolve_struct$1(type, status);
|
|
46982
|
+
if (!s || s.is_class || s.is_generic || s.is_simple_type) return false;
|
|
46983
|
+
if ((s.traits ?? []).length > 0) return false;
|
|
46984
|
+
if (direct_string_fields(s).length === 0) return false;
|
|
46985
|
+
if (struct_owns_non_string_heap(s, status, /* @__PURE__ */ new Set())) return false;
|
|
46986
|
+
return true;
|
|
46987
|
+
}
|
|
46513
46988
|
function struct_owns_non_string_heap(s, status, visited) {
|
|
46514
46989
|
if (visited.has(s.name)) return false;
|
|
46515
46990
|
visited.add(s.name);
|
|
@@ -49150,6 +49625,12 @@ function check_declaration_node(decl, status) {
|
|
|
49150
49625
|
if (is_view_keyword && decl.type_start !== void 0) decl.type.is_view = true;
|
|
49151
49626
|
if (decl.func_params) {
|
|
49152
49627
|
if (is_view_keyword) add_error(status, `'view' cannot declare a function-typed binding`, decl.start);
|
|
49628
|
+
if (decl.value?.node_type === "func") decl.value.address_escaped = true;
|
|
49629
|
+
else if (decl.value?.node_type === "value") {
|
|
49630
|
+
const fn = status.functions.findLast((f) => f.name === decl.value.value);
|
|
49631
|
+
if (fn) fn.address_escaped = true;
|
|
49632
|
+
}
|
|
49633
|
+
if (decl.value?.node_type === "value" && decl.value.value === "null" && !(decl.type.name === "func" && decl.type.is_nullable)) add_error(status, `cannot assign null to a non-nullable func — declare it 'func?' (or 'Func<...>?')`, decl.value.start);
|
|
49153
49634
|
if (decl.func_return_type) {
|
|
49154
49635
|
check_type_exists(decl.func_return_type, status, -1);
|
|
49155
49636
|
const materialized = materialize_type(decl.func_return_type, status);
|
|
@@ -49214,7 +49695,7 @@ function check_declaration_node(decl, status) {
|
|
|
49214
49695
|
status.values.push({
|
|
49215
49696
|
declaration,
|
|
49216
49697
|
name: decl.name,
|
|
49217
|
-
type: decl.func_return_type || decl.type,
|
|
49698
|
+
type: decl.type.name === "func" && decl.type.is_nullable ? decl.type : decl.func_return_type || decl.type,
|
|
49218
49699
|
is_set: !!decl.value,
|
|
49219
49700
|
start: decl.start,
|
|
49220
49701
|
is_null: decl.value?.node_type === "value" && decl.value.value === "null",
|
|
@@ -49712,7 +50193,7 @@ function check_struct_node(struct, status) {
|
|
|
49712
50193
|
continue;
|
|
49713
50194
|
}
|
|
49714
50195
|
if (decl.func_params) {
|
|
49715
|
-
decl.type = new Type("func");
|
|
50196
|
+
if (decl.type.name !== "func") decl.type = new Type("func");
|
|
49716
50197
|
check_declaration_node(decl, status);
|
|
49717
50198
|
continue;
|
|
49718
50199
|
}
|
|
@@ -50020,6 +50501,7 @@ function synthesize_for_struct(struct, status) {
|
|
|
50020
50501
|
if (struct.traits.includes("Stringable") && !struct_has_function(struct, "to_string") && fields.every((f) => field_is_stringable(f, status, /* @__PURE__ */ new Set()))) struct.functions.push(build_to_string(struct, fields));
|
|
50021
50502
|
if (struct.traits.includes("Equatable") && !struct_has_function(struct, "eq") && !struct_has_function(struct, "ne") && fields.every((f) => field_is_equatable(f, status, /* @__PURE__ */ new Set()))) struct.functions.push(build_eq(struct, fields));
|
|
50022
50503
|
if (struct.traits.includes("Hashable") && !struct_has_function(struct, "hash") && fields.every((f) => field_is_hashable(f, status, /* @__PURE__ */ new Set()))) struct.functions.push(build_hash(struct, fields));
|
|
50504
|
+
synthesize_copy_method(struct, status);
|
|
50023
50505
|
}
|
|
50024
50506
|
function build_to_string(struct, fields) {
|
|
50025
50507
|
const parts = [string_literal(`${struct.name}(`)];
|
|
@@ -50051,6 +50533,81 @@ function build_hash(struct, fields) {
|
|
|
50051
50533
|
const ret = new ReturnNode(-1, expr);
|
|
50052
50534
|
return new FunctionNode(-1, "pub", "hash", new Type("uint"), [self_param(struct)], [ret]);
|
|
50053
50535
|
}
|
|
50536
|
+
/**
|
|
50537
|
+
* Whether `struct` is eligible for a synthesized `copy` method: a value
|
|
50538
|
+
* struct whose only ownership is its DIRECT string fields — the same set
|
|
50539
|
+
* the pass-by-value argument convention covers. A struct that additionally
|
|
50540
|
+
* owns non-string heap (a resource-releasing `#destroy`, a class field, a
|
|
50541
|
+
* nested requiring-move struct) cannot be deep-copied by "byte copy +
|
|
50542
|
+
* strdup the strings" — its copy stays unwritten (move/swap remains the
|
|
50543
|
+
* escape hatch) — and a struct with no string fields is already soundly
|
|
50544
|
+
* byte-copyable, so it needs no method.
|
|
50545
|
+
*/
|
|
50546
|
+
function struct_is_copyable(struct, status) {
|
|
50547
|
+
if (struct.is_class || struct.is_generic || struct.is_simple_type) return false;
|
|
50548
|
+
if (direct_string_fields(struct).length === 0) return false;
|
|
50549
|
+
if (struct_owns_non_string_heap(struct, status, /* @__PURE__ */ new Set())) return false;
|
|
50550
|
+
return struct_copy_ctor_args(struct) !== null;
|
|
50551
|
+
}
|
|
50552
|
+
/**
|
|
50553
|
+
* How a synthesized `copy` constructs the fresh value: the zero-argument
|
|
50554
|
+
* `#init` overload when one exists (auto or user), else the bodyless AUTO
|
|
50555
|
+
* `#init` whose params map 1:1 onto non-defaulted fields (pass
|
|
50556
|
+
* `self.<param>` for each). Null when neither shape exists — a user #init
|
|
50557
|
+
* with params only cannot be invoked without re-running its custom logic,
|
|
50558
|
+
* so no `copy` is synthesized.
|
|
50559
|
+
*/
|
|
50560
|
+
function struct_copy_ctor_args(struct) {
|
|
50561
|
+
const inits = struct.functions.filter((f) => f.name === "#init");
|
|
50562
|
+
if (inits.some((f) => f.params.filter((p) => !p.is_self_param).length === 0)) return [];
|
|
50563
|
+
const auto = inits.find((f) => !f.has_body);
|
|
50564
|
+
if (!auto) return null;
|
|
50565
|
+
return auto.params.filter((p) => !p.is_self_param).map((p) => p.name);
|
|
50566
|
+
}
|
|
50567
|
+
/** Whether `type` names a struct that has (or will get) a `copy` method.
|
|
50568
|
+
* Order-independent: it re-evaluates the eligibility predicate instead of
|
|
50569
|
+
* looking for an already-synthesized method, so an outer struct nested in
|
|
50570
|
+
* the same pre-pass resolves a later-declared field struct. */
|
|
50571
|
+
function field_type_is_copyable(type, status) {
|
|
50572
|
+
if (!type.name || type.is_array || type.is_ref || type.is_view || type.is_nullable) return false;
|
|
50573
|
+
const nested = status.structs.find((s) => s.name === type.name);
|
|
50574
|
+
return !!nested && struct_is_copyable(nested, status);
|
|
50575
|
+
}
|
|
50576
|
+
/**
|
|
50577
|
+
* Synthesize `pub func copy(self) -> Struct` for owning value structs: a
|
|
50578
|
+
* fresh `Struct()` whose every field is rewritten from `self` — string
|
|
50579
|
+
* field writes strdup the source (the backends' field-write path), nested
|
|
50580
|
+
* copyable structs recurse through their own `copy`, everything else byte
|
|
50581
|
+
* copies. `return c` hits the return-boundary normalization (all fields
|
|
50582
|
+
* recorded → transfer raw), so the caller receives a uniformly heap-owned
|
|
50583
|
+
* value with no extra copies.
|
|
50584
|
+
*/
|
|
50585
|
+
function build_copy(struct, status) {
|
|
50586
|
+
const ctor_args = struct_copy_ctor_args(struct) ?? [];
|
|
50587
|
+
const ctor = new FunctionCallNode(-1, struct.name, new Type(struct.name), ctor_args.map((name) => field_access("self", name)));
|
|
50588
|
+
const statements = [new DeclarationNode(-1, "private", "var", "c", new Type(struct.name), ctor)];
|
|
50589
|
+
for (const field of struct.fields) {
|
|
50590
|
+
const right = field_type_is_copyable(field.type, status) ? method_call(field_access("self", field.name), "copy") : field_access("self", field.name);
|
|
50591
|
+
statements.push(new AssignmentNode(-1, field_access("c", field.name), right, "="));
|
|
50592
|
+
}
|
|
50593
|
+
statements.push(new ReturnNode(-1, new ValueNode(-1, "c")));
|
|
50594
|
+
return new FunctionNode(-1, "pub", "copy", new Type(struct.name), [self_param(struct)], statements);
|
|
50595
|
+
}
|
|
50596
|
+
/**
|
|
50597
|
+
* Synthesize the `copy` method on `struct` when eligible and not already
|
|
50598
|
+
* present. Called from the block-level derive pre-pass (which runs before
|
|
50599
|
+
* the struct is checked, so the method's body is checked with it) AND from
|
|
50600
|
+
* `monomorphize` — a mono struct materializes mid-check, after that pre-pass
|
|
50601
|
+
* ran, so without this hook `Box<string>.copy()` — advertised by the
|
|
50602
|
+
* copy-discipline error messages — would resolve to "Function not found".
|
|
50603
|
+
* The caller is responsible for checking the synthesized body when no later
|
|
50604
|
+
* struct check will (see monomorphize).
|
|
50605
|
+
*/
|
|
50606
|
+
function synthesize_copy_method(struct, status) {
|
|
50607
|
+
if (struct_has_function(struct, "copy")) return;
|
|
50608
|
+
if (!struct_is_copyable(struct, status)) return;
|
|
50609
|
+
struct.functions.push(build_copy(struct, status));
|
|
50610
|
+
}
|
|
50054
50611
|
//#endregion
|
|
50055
50612
|
//#region ../src/check/check_block_node.ts
|
|
50056
50613
|
function check_block_node(node, status) {
|
|
@@ -50502,8 +51059,8 @@ function check_function_parameter_node(param, status, is_library_scope = false)
|
|
|
50502
51059
|
if (param.type.is_pointer && !is_library_scope) add_error(status, `'ptr' parameters are reserved for the System library — raw pointer manipulation is not available to user code`, param.type_start ?? param.start);
|
|
50503
51060
|
instantiate_generic_type(param.type, status);
|
|
50504
51061
|
}
|
|
50505
|
-
if (param.declaration === "var" && !param.is_moved && !param.is_self_param && !param.type.is_ref) add_error(status, `'var' parameters are not allowed — use 'ref' to mutate the caller's value (with 'ref' at the call site), or take it read-only and make a local 'var' copy`, param.start);
|
|
50506
|
-
if (param.is_moved && param.type.name && !is_class_type$1(param.type.name, status) && !is_trait_type(param.type.name, status) && !is_owning_struct_type_requiring_move(param.type, status) && !status.type_params.includes(param.type.name) && !status.structs.some((s) => s.type_params.includes(param.type.name))) add_error(status, `move is only allowed for class, trait, or owning struct types, not '${param.type.name}'`, param.start);
|
|
51062
|
+
if (param.declaration === "var" && !param.is_moved && !param.is_self_param && !param.type.is_ref && !is_pass_by_value_owning_struct(param.type, status)) add_error(status, `'var' parameters are not allowed — use 'ref' to mutate the caller's value (with 'ref' at the call site), or take it read-only and make a local 'var' copy`, param.start);
|
|
51063
|
+
if (param.is_moved && param.type.name && !is_class_type$1(param.type.name, status) && !is_trait_type(param.type.name, status) && !is_owning_struct_type_requiring_move(param.type, status) && !is_pass_by_value_owning_struct(param.type, status) && !status.type_params.includes(param.type.name) && !status.structs.some((s) => s.type_params.includes(param.type.name))) add_error(status, `move is only allowed for class, trait, or owning struct types, not '${param.type.name}'`, param.start);
|
|
50507
51064
|
if (param.is_variadic && param.default_value) {
|
|
50508
51065
|
add_error(status, `Variadic parameter cannot have a default value`, param.start);
|
|
50509
51066
|
return;
|
|
@@ -50515,7 +51072,7 @@ function check_function_parameter_node(param, status, is_library_scope = false)
|
|
|
50515
51072
|
}
|
|
50516
51073
|
if (status.values.some((v) => v.name === param.name && v.declaration !== "const" && !(is_library_scope && v.is_global))) add_error(status, `Parameter already declared: ${param.name}`, param.start);
|
|
50517
51074
|
status.values.push({
|
|
50518
|
-
declaration: param.declaration,
|
|
51075
|
+
declaration: param.declaration === "var" || is_pass_by_value_owning_struct(param.type, status) ? "var" : param.declaration,
|
|
50519
51076
|
name: param.name,
|
|
50520
51077
|
type: param.type,
|
|
50521
51078
|
is_set: true,
|
|
@@ -50932,7 +51489,7 @@ function check_function_call_node(node, status) {
|
|
|
50932
51489
|
return false;
|
|
50933
51490
|
}
|
|
50934
51491
|
if (magic_ctor && spawn_ctor_shape) return check_magic_ctor(node, status, magic_ctor);
|
|
50935
|
-
let func = find_free_function(status, node.name);
|
|
51492
|
+
let func = status.values.findLast((v) => v.name === node.name && v.func_params !== void 0) ? void 0 : find_free_function(status, node.name);
|
|
50936
51493
|
if (func?.is_closure && func.captures?.length) func = void 0;
|
|
50937
51494
|
if (!func) {
|
|
50938
51495
|
const struct = resolve_declared_struct(node.name, status);
|
|
@@ -51001,6 +51558,10 @@ function check_function_call_node(node, status) {
|
|
|
51001
51558
|
const param_index = status.values.findLastIndex((v) => v.name === node.name);
|
|
51002
51559
|
const param_value = param_index >= 0 ? status.values[param_index] : void 0;
|
|
51003
51560
|
if (!!param_value && (param_value.type.name === "func" || param_value.func_params !== void 0 || param_value.func_return_type !== void 0) && param_value) {
|
|
51561
|
+
if (param_value.is_null && !status.allow_null_value) {
|
|
51562
|
+
add_error(status, `Variable '${node.name}' may be null`, node.start);
|
|
51563
|
+
return false;
|
|
51564
|
+
}
|
|
51004
51565
|
if (status.moved_variables?.has(node.name)) {
|
|
51005
51566
|
add_error(status, `Variable '${node.name}' used after move`, node.start);
|
|
51006
51567
|
return false;
|
|
@@ -51229,6 +51790,30 @@ function monomorphize(generic_struct, type_args, status) {
|
|
|
51229
51790
|
const init_func = new FunctionNode(generic_struct.start, "pub", "#init", init_return_type, init_params);
|
|
51230
51791
|
mono_struct.functions.push(init_func);
|
|
51231
51792
|
}
|
|
51793
|
+
synthesize_copy_method(mono_struct, status);
|
|
51794
|
+
const mono_copy = mono_struct.functions.find((f) => f.name === "copy");
|
|
51795
|
+
if (mono_copy && !mono_copy.checked) {
|
|
51796
|
+
mono_copy.scope = mono_struct;
|
|
51797
|
+
check_function_node(mono_copy, {
|
|
51798
|
+
stack: status.stack,
|
|
51799
|
+
scope_depth: status.scope_depth,
|
|
51800
|
+
types: status.types,
|
|
51801
|
+
values: [],
|
|
51802
|
+
function_value_base: 0,
|
|
51803
|
+
structs: status.structs,
|
|
51804
|
+
traits: status.traits,
|
|
51805
|
+
enums: status.enums,
|
|
51806
|
+
bitsets: status.bitsets,
|
|
51807
|
+
functions: status.functions,
|
|
51808
|
+
allocations: [],
|
|
51809
|
+
var_name_counter: status.var_name_counter,
|
|
51810
|
+
type_params: [],
|
|
51811
|
+
errors: status.errors,
|
|
51812
|
+
function_emission_names: status.function_emission_names,
|
|
51813
|
+
allow_internal: status.allow_internal,
|
|
51814
|
+
library_boundary: status.library_boundary
|
|
51815
|
+
});
|
|
51816
|
+
}
|
|
51232
51817
|
const root = status.stack[0];
|
|
51233
51818
|
if (!root.statements.some((s) => s.node_type === "struct" && s.name === mono_name)) root.statements.push(mono_struct);
|
|
51234
51819
|
return mono_struct;
|
|
@@ -51732,6 +52317,8 @@ function check_magic_ctor(node, status, name) {
|
|
|
51732
52317
|
add_error(status, `Spawned call '${call.name}' did not resolve`, node.start);
|
|
51733
52318
|
return false;
|
|
51734
52319
|
}
|
|
52320
|
+
const spawned = call.resolved_function;
|
|
52321
|
+
if (spawned) spawned.address_escaped = true;
|
|
51735
52322
|
validate_spawn_args_sendable(call, status);
|
|
51736
52323
|
stamp_spawn_ctor(node, name);
|
|
51737
52324
|
const return_type = call.type;
|
|
@@ -53318,6 +53905,13 @@ function check_function_call(node, status, func, target_type, self_value, self_p
|
|
|
53318
53905
|
const param_type = type_from_value_node(param, status);
|
|
53319
53906
|
const param_value = value_from_value_node(param);
|
|
53320
53907
|
const has_ref_keyword = node.ref_param_indices?.includes(i) ?? false;
|
|
53908
|
+
if (func && func_param && func.has_body && (!func.scope || func.scope.node_type !== "struct") && !func.params.some((p) => p.is_self_param) && !func.address_escaped && !func_param.is_self_param && !func_param.is_variadic && !func_param.type.is_ref && !func_param.type.is_nullable && func_param.type.name && !is_owning_struct_type_requiring_move(func_param.type, status)) {
|
|
53909
|
+
const owned_struct = status.structs.find((st) => st.name === func_param.type.name && !st.is_simple_type && !st.is_class);
|
|
53910
|
+
if (owned_struct && !(owned_struct.traits ?? []).length && direct_string_fields(owned_struct).length > 0 && !struct_needs_destroy(owned_struct, status)) {
|
|
53911
|
+
const indices = func_param.is_moved ? node.move_owned_param_indices ??= [] : node.owned_value_param_indices ??= [];
|
|
53912
|
+
if (!indices.includes(i)) indices.push(i);
|
|
53913
|
+
}
|
|
53914
|
+
}
|
|
53321
53915
|
const has_mov_keyword = node.move_param_indices?.includes(i) ?? false;
|
|
53322
53916
|
if (func_param && func_param.type.name === "string" && !func_param.type.is_view && !func_param.type.is_array && param_type.name === "string" && param_type.is_view) add_error(status, `cannot pass a 'view string' to string parameter '${func_param.name}' — call .to_string() to materialize an owned copy`, param.start);
|
|
53323
53917
|
if (func_param.type.is_ref && !has_ref_keyword) add_error(status, `Missing 'ref' keyword for ref parameter '${func_param.name}'`, param.start);
|
|
@@ -53342,12 +53936,13 @@ function check_function_call(node, status, func, target_type, self_value, self_p
|
|
|
53342
53936
|
else if (!func_param.type.is_ref && (arg_is_stack_array_var || arg_is_literal) && elem_struct && !elem_struct.is_simple_type && !elem_struct.is_class && struct_has_string_fields(elem_struct, status)) add_error(status, `Cannot pass an array of owning value struct '${param_type.name}' (string fields) to 'Array<${param_type.name}>' parameter '${func_param.name}' — the materialised heap copy would share the element strings. Use 'List<${param_type.name}>' instead: its Buffer deep-copies owning elements soundly`, param.start);
|
|
53343
53937
|
}
|
|
53344
53938
|
const param_is_class = func_param.type.name && is_class_type$1(func_param.type.name, status);
|
|
53345
|
-
const
|
|
53939
|
+
const param_transfers = !!node.move_owned_param_indices?.includes(i);
|
|
53940
|
+
const param_is_owning_struct = func_param.is_moved && !func_param.type.is_nullable && !!func_param.type.name && (is_owning_struct_type_requiring_move(func_param.type, status) || param_transfers);
|
|
53346
53941
|
const arg_is_owned_value = param.node_type === "value" && borrow_depth_of(param, status) === void 0;
|
|
53347
53942
|
if (func_param.is_moved && !has_mov_keyword && (param_is_class || param_is_owning_struct) && arg_is_owned_value) add_error(status, `Missing 'move' keyword for move parameter '${func_param.name}'`, param.start);
|
|
53348
53943
|
else if (!func_param.is_moved && has_mov_keyword) add_error(status, `Unexpected 'move' keyword for non-move parameter '${func_param.name}'`, param.start);
|
|
53349
53944
|
if (has_mov_keyword && param_value && !node.swap_params?.has(i)) {
|
|
53350
|
-
if (func_param.type.name && (is_class_type$1(func_param.type.name, status) || func_param.is_moved &&
|
|
53945
|
+
if (func_param.type.name && (is_class_type$1(func_param.type.name, status) || func_param.is_moved && is_owning_struct_type(func_param.type, status))) {
|
|
53351
53946
|
if (!status.moved_variables) status.moved_variables = /* @__PURE__ */ new Set();
|
|
53352
53947
|
status.moved_variables.add(param_value);
|
|
53353
53948
|
}
|
|
@@ -53358,7 +53953,7 @@ function check_function_call(node, status, func, target_type, self_value, self_p
|
|
|
53358
53953
|
if (field_type.name && is_class_type$1(field_type.name, status)) {
|
|
53359
53954
|
const field_name = access.access.name;
|
|
53360
53955
|
add_error(status, `cannot move '${field_name}' out of struct — struct owns its class fields`, param.start);
|
|
53361
|
-
} else if (field_type.name && !field_type.is_nullable && is_owning_struct_type_requiring_move(field_type, status)) {
|
|
53956
|
+
} else if (field_type.name && !field_type.is_nullable && (is_owning_struct_type_requiring_move(field_type, status) || param_transfers && is_pass_by_value_owning_struct(field_type, status))) {
|
|
53362
53957
|
const field_name = access.access.name;
|
|
53363
53958
|
add_error(status, `cannot move '${field_name}' out of struct by value — it owns heap resources; use 'move ... swap <replacement>' to revalidate the field`, param.start);
|
|
53364
53959
|
}
|
|
@@ -54731,14 +55326,17 @@ function check_assignment_node(assign, status) {
|
|
|
54731
55326
|
return_type: fd?.func_return_type
|
|
54732
55327
|
};
|
|
54733
55328
|
}
|
|
54734
|
-
} else if (lhs_value?.func_params
|
|
55329
|
+
} else if (lhs_value?.func_params !== void 0) lambda_signature = {
|
|
54735
55330
|
params: lhs_value.func_params,
|
|
54736
55331
|
return_type: lhs_value.func_return_type
|
|
54737
55332
|
};
|
|
54738
|
-
if (assign.right_value.node_type === "func" &&
|
|
55333
|
+
if (assign.right_value.node_type === "func" && Array.isArray(lambda_signature.params)) {
|
|
55334
|
+
if (!lhs_value && assign.left_value.node_type === "value") {
|
|
55335
|
+
add_error(status, `cannot assign a lambda to '${lhs_value_name}' — the binding was initialized with a capture-free lambda and has no reassignable slot; declare it with '= null' (or from a named function) to reassign`, assign.right_value.start);
|
|
55336
|
+
return false;
|
|
55337
|
+
}
|
|
54739
55338
|
const rhs_func = assign.right_value;
|
|
54740
|
-
if (assign.left_value.node_type === "value") rhs_func
|
|
54741
|
-
else if (!rhs_func.name) synthesize_lambda_name(rhs_func, status);
|
|
55339
|
+
if (!rhs_func.name || assign.left_value.node_type === "value") synthesize_lambda_name(rhs_func, status);
|
|
54742
55340
|
if (rhs_func.params.length === lambda_signature.params.length) {
|
|
54743
55341
|
for (let i = 0; i < rhs_func.params.length; i++) if (!rhs_func.params[i].type.name && lambda_signature.params[i].type.name) rhs_func.params[i].type = lambda_signature.params[i].type;
|
|
54744
55342
|
}
|
|
@@ -55970,6 +56568,8 @@ function check_spawn_node(node, status) {
|
|
|
55970
56568
|
add_error(status, "Spawned call did not resolve", node.start);
|
|
55971
56569
|
return false;
|
|
55972
56570
|
}
|
|
56571
|
+
const spawned = node.call.resolved_function;
|
|
56572
|
+
if (spawned) spawned.address_escaped = true;
|
|
55973
56573
|
for (const param of node.call.params) {
|
|
55974
56574
|
let arg_type = type_from_value_node(param, status);
|
|
55975
56575
|
const stamped = param.type;
|
|
@@ -56787,13 +57387,14 @@ function parse_type(status) {
|
|
|
56787
57387
|
type.type_args = void 0;
|
|
56788
57388
|
}
|
|
56789
57389
|
if (type.name === "Func" && type.type_args?.length) {
|
|
56790
|
-
if (type.
|
|
56791
|
-
add_error(status, `Func<...> cannot be combined with '
|
|
57390
|
+
if (type.is_array || type.is_pointer || type.is_view || type.is_ref) {
|
|
57391
|
+
add_error(status, `Func<...> cannot be combined with '[]'/'ptr'/'view'/'ref'`, start);
|
|
56792
57392
|
return type;
|
|
56793
57393
|
}
|
|
56794
57394
|
const args = type.type_args;
|
|
56795
57395
|
const func_type = new Type("func");
|
|
56796
57396
|
func_type.start = type.start;
|
|
57397
|
+
if (type.is_nullable) func_type.is_nullable = true;
|
|
56797
57398
|
func_type.func_params = [];
|
|
56798
57399
|
for (let i = 0; i < args.length - 1; i++) {
|
|
56799
57400
|
if (args[i].name === "void") {
|
|
@@ -56906,10 +57507,14 @@ function default_visibility(status) {
|
|
|
56906
57507
|
* - a func RETURN carries its signature on the Type
|
|
56907
57508
|
* (`type.func_params` / `type.func_return_type`).
|
|
56908
57509
|
*/
|
|
56909
|
-
/** Whether the current token starts a nested func type: `func`
|
|
56910
|
-
* followed by `(` (the bare word `func` alone
|
|
57510
|
+
/** Whether the current token starts a nested func type: `func` (optionally
|
|
57511
|
+
* nullable, `func?`) immediately followed by `(` (the bare word `func` alone
|
|
57512
|
+
* is the plain marker type). */
|
|
56911
57513
|
function at_func_type(status) {
|
|
56912
|
-
|
|
57514
|
+
if (peek_current(status) !== "func") return false;
|
|
57515
|
+
const next = status.tokens[status.i + 1]?.value;
|
|
57516
|
+
if (next === "(") return true;
|
|
57517
|
+
return next === "?" && status.tokens[status.i + 2]?.value === "(";
|
|
56913
57518
|
}
|
|
56914
57519
|
/**
|
|
56915
57520
|
* Reject `void` where it has no meaning. Only the last type argument of
|
|
@@ -56930,6 +57535,7 @@ function reject_void_type(type, status, at) {
|
|
|
56930
57535
|
function adopt_func_param_type(param, type) {
|
|
56931
57536
|
if (type.name !== "func") return false;
|
|
56932
57537
|
param.type = new Type("func");
|
|
57538
|
+
param.type.is_nullable = type.is_nullable;
|
|
56933
57539
|
param.func_params = type.func_params;
|
|
56934
57540
|
param.func_return_type = type.func_return_type;
|
|
56935
57541
|
return true;
|
|
@@ -56952,11 +57558,13 @@ function parse_func_type_signature(param, status) {
|
|
|
56952
57558
|
}
|
|
56953
57559
|
/**
|
|
56954
57560
|
* Parse a full nested func type — the `func` word is consumed by the caller
|
|
56955
|
-
* (at_func_type confirmed the `(` follows) — and return
|
|
56956
|
-
* func_params/func_return_type carry the signature.
|
|
57561
|
+
* (at_func_type confirmed the `(` follows, possibly after a `?`) — and return
|
|
57562
|
+
* it as a Type whose func_params/func_return_type carry the signature. A
|
|
57563
|
+
* `func?` spelling marks the type nullable (it may hold `null`).
|
|
56957
57564
|
*/
|
|
56958
57565
|
function parse_func_type(status) {
|
|
56959
57566
|
const type = new Type("func");
|
|
57567
|
+
if (accept("?", status)) type.is_nullable = true;
|
|
56960
57568
|
const param = new ParameterNode(get_index(status), "");
|
|
56961
57569
|
parse_func_type_signature(param, status);
|
|
56962
57570
|
type.func_params = param.func_params;
|
|
@@ -56996,6 +57604,7 @@ function parse_signature_params(params, status) {
|
|
|
56996
57604
|
if (at_func_type(status)) {
|
|
56997
57605
|
consume(status);
|
|
56998
57606
|
param.type = new Type("func");
|
|
57607
|
+
if (accept("?", status)) param.type.is_nullable = true;
|
|
56999
57608
|
parse_func_type_signature(param, status);
|
|
57000
57609
|
} else {
|
|
57001
57610
|
param.type = parse_type(status);
|
|
@@ -57036,7 +57645,11 @@ function parse_declaration(visibility, declaration, status, is_readonly = false)
|
|
|
57036
57645
|
if (is_func_alias) {
|
|
57037
57646
|
decl.func_params = decl.type.func_params ?? [];
|
|
57038
57647
|
decl.func_return_type = decl.type.func_return_type;
|
|
57039
|
-
decl.type
|
|
57648
|
+
if (decl.type.is_nullable) {
|
|
57649
|
+
const nullable_marker = new Type("func");
|
|
57650
|
+
nullable_marker.is_nullable = true;
|
|
57651
|
+
decl.type = nullable_marker;
|
|
57652
|
+
} else decl.type = new Type("");
|
|
57040
57653
|
}
|
|
57041
57654
|
if (peek_current(status) === "=" || status.i >= status.tokens.length) {
|
|
57042
57655
|
const modifier = status.tokens[saved_i]?.value;
|
|
@@ -57098,6 +57711,7 @@ function parse_declaration(visibility, declaration, status, is_readonly = false)
|
|
|
57098
57711
|
}
|
|
57099
57712
|
function parse_function_type_declaration(decl, status) {
|
|
57100
57713
|
accept("func", status);
|
|
57714
|
+
const func_nullable = accept("?", status);
|
|
57101
57715
|
if (expect("(", status)) {
|
|
57102
57716
|
const params = [];
|
|
57103
57717
|
let return_type;
|
|
@@ -57110,6 +57724,10 @@ function parse_function_type_declaration(decl, status) {
|
|
|
57110
57724
|
decl.name = consume_name(status);
|
|
57111
57725
|
decl.func_params = params.filter((p) => !p.type.is_return_type);
|
|
57112
57726
|
decl.func_return_type = return_type;
|
|
57727
|
+
if (func_nullable) {
|
|
57728
|
+
decl.type = new Type("func");
|
|
57729
|
+
decl.type.is_nullable = true;
|
|
57730
|
+
}
|
|
57113
57731
|
const has_equals = accept("=", status);
|
|
57114
57732
|
if (peek_current(status) === "{") {
|
|
57115
57733
|
const func = new FunctionNode(get_index(status), default_visibility(status), decl.name, new Type(""));
|
|
@@ -57209,6 +57827,7 @@ function parse_anon_function_parameter(func, status) {
|
|
|
57209
57827
|
if (at_func_type(status)) {
|
|
57210
57828
|
consume(status);
|
|
57211
57829
|
param.type = new Type("func");
|
|
57830
|
+
if (accept("?", status)) param.type.is_nullable = true;
|
|
57212
57831
|
parse_func_type_signature(param, status);
|
|
57213
57832
|
param.name_start = get_index(status);
|
|
57214
57833
|
param.name = consume_name(status);
|
|
@@ -58142,6 +58761,7 @@ function parse_function_parameter(parent, func, status) {
|
|
|
58142
58761
|
if (at_func_type(status)) {
|
|
58143
58762
|
consume(status);
|
|
58144
58763
|
param.type = new Type("func");
|
|
58764
|
+
if (accept("?", status)) param.type.is_nullable = true;
|
|
58145
58765
|
parse_func_type_signature(param, status);
|
|
58146
58766
|
param.name_start = get_index(status);
|
|
58147
58767
|
param.name = consume_name(status);
|
|
@@ -59500,7 +60120,8 @@ nomen test
|
|
|
59500
60120
|
function find_bundled(filename) {
|
|
59501
60121
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
59502
60122
|
const cli_root = path.dirname(here);
|
|
59503
|
-
const
|
|
60123
|
+
const repo_root = path.join(cli_root, "..");
|
|
60124
|
+
const candidates = !cli_root.includes("node_modules") ? [path.join(repo_root, filename), path.join(cli_root, filename)] : [path.join(cli_root, filename), path.join(repo_root, filename)];
|
|
59504
60125
|
for (const c of candidates) if (fs.existsSync(c)) return path.resolve(c);
|
|
59505
60126
|
}
|
|
59506
60127
|
/**
|
|
@@ -59722,6 +60343,10 @@ function parse_records(stdout) {
|
|
|
59722
60343
|
other
|
|
59723
60344
|
};
|
|
59724
60345
|
}
|
|
60346
|
+
/** Pull the exit-time audit "LEAK: N allocation(s)" lines out of stdout. */
|
|
60347
|
+
function extract_leaks(stdout) {
|
|
60348
|
+
return stdout.split("\n").map((l) => l.trim()).filter((l) => l.startsWith("LEAK:"));
|
|
60349
|
+
}
|
|
59725
60350
|
/**
|
|
59726
60351
|
* Compile one `*.test.nm` file (with the generated harness) and run it,
|
|
59727
60352
|
* returning its parsed records and any crash info.
|
|
@@ -59737,6 +60362,7 @@ function run_test_file(entry_path, lib_path, arch, audit = false, audit_runtime)
|
|
|
59737
60362
|
fails: [],
|
|
59738
60363
|
benches: [],
|
|
59739
60364
|
other: [],
|
|
60365
|
+
leaks: [],
|
|
59740
60366
|
ms: 0
|
|
59741
60367
|
};
|
|
59742
60368
|
const resolved = path.resolve(entry_path);
|
|
@@ -59834,9 +60460,11 @@ function run_test_file(entry_path, lib_path, arch, audit = false, audit_runtime)
|
|
|
59834
60460
|
result.fails = records.fails;
|
|
59835
60461
|
result.benches = records.benches;
|
|
59836
60462
|
result.other = records.other;
|
|
60463
|
+
result.leaks = audit ? extract_leaks(runStdout) : [];
|
|
60464
|
+
if (result.leaks.length) result.other = result.other.filter((l) => !l.trim().startsWith("LEAK:"));
|
|
59837
60465
|
result.crashed = crashed;
|
|
59838
60466
|
if (crashed) result.phase = "run";
|
|
59839
|
-
result.ok = !(result.fails.length > 0 || result.tests.some((t) => t.failed > 0)) && crashed === void 0;
|
|
60467
|
+
result.ok = !(result.fails.length > 0 || result.tests.some((t) => t.failed > 0)) && crashed === void 0 && result.leaks.length === 0;
|
|
59840
60468
|
result.ms = performance.now() - start;
|
|
59841
60469
|
return result;
|
|
59842
60470
|
}
|
|
@@ -59873,6 +60501,7 @@ function report_file(result) {
|
|
|
59873
60501
|
console.log(` ${mark} ${rel} ${C.dim(`(${totalTests} tests)`)} ${C.dim(format_duration(result.ms))}`);
|
|
59874
60502
|
if (result.crashed) console.log(C.red(` ${result.crashed}`));
|
|
59875
60503
|
for (const f of result.fails) console.log(` ${C.red("✗")} ${f.test} ${C.dim(">")} ${f.message}`);
|
|
60504
|
+
for (const leak of result.leaks) console.log(` ${C.yellow("⚠")} ${C.yellow(leak)}`);
|
|
59876
60505
|
for (const b of result.benches) console.log(` ${C.cyan("⏱")} ${b.label} ${C.dim(`(n=${b.n})`)} ${C.dim("min")} ${fmt_ns(b.min)} ${C.dim("median")} ${fmt_ns(b.median)} ${C.dim("mean")} ${fmt_ns(Math.round(b.mean))} ${C.dim("max")} ${fmt_ns(b.max)} ${C.dim("±")} ${fmt_ns(Math.round(b.stddev))}`);
|
|
59877
60506
|
if (result.other.length) {
|
|
59878
60507
|
console.log(C.dim(" --- test stdout ---"));
|
|
@@ -59914,10 +60543,12 @@ function runTests(root, options = {}) {
|
|
|
59914
60543
|
const totalTests = results.reduce((a, r) => a + r.tests.length, 0);
|
|
59915
60544
|
const totalFailed = results.reduce((a, r) => a + r.tests.reduce((x, t) => x + t.failed, 0), 0);
|
|
59916
60545
|
const totalPassed = totalTests - totalFailed;
|
|
60546
|
+
const totalLeaks = results.reduce((a, r) => a + r.leaks.length, 0);
|
|
59917
60547
|
const anyFailed = results.some((r) => !r.ok);
|
|
59918
60548
|
console.log("");
|
|
59919
60549
|
console.log(` ${C.dim("Files ")} ${C.green(`${totalFilesPassed} passed`)}` + (totalFilesFailed ? ` ${C.dim("|")} ${C.red(`${totalFilesFailed} failed`)}` : "") + C.dim(` (${totalFiles})`));
|
|
59920
60550
|
console.log(` ${C.dim("Tests ")} ${C.green(`${totalPassed} passed`)}` + (totalFailed ? ` ${C.dim("|")} ${C.red(`${totalFailed} failed`)}` : "") + C.dim(` (${totalTests})`));
|
|
60551
|
+
if (totalLeaks) console.log(` ${C.dim("Leaks ")} ${C.yellow(`${totalLeaks} reported`)}` + C.dim(" (--audit; leaked files count as failures)"));
|
|
59921
60552
|
console.log(` ${C.dim(" Time ")} ${format_duration(elapsed)}`);
|
|
59922
60553
|
console.log("");
|
|
59923
60554
|
return !anyFailed;
|
|
@@ -60137,7 +60768,7 @@ function processFolder(folder, config, mode, program_args) {
|
|
|
60137
60768
|
const dir = fs.opendirSync(folder);
|
|
60138
60769
|
let dirent;
|
|
60139
60770
|
while ((dirent = dir.readSync()) !== null) if (shouldProcessFile(dirent.name)) {
|
|
60140
|
-
processFile(path.join(folder, dirent.name), config, mode, program_args
|
|
60771
|
+
processFile(path.join(folder, dirent.name), config, mode, program_args);
|
|
60141
60772
|
fs.watch;
|
|
60142
60773
|
}
|
|
60143
60774
|
dir.closeSync();
|
|
@@ -60145,45 +60776,15 @@ function processFolder(folder, config, mode, program_args) {
|
|
|
60145
60776
|
function shouldProcessFile(filename) {
|
|
60146
60777
|
return path.extname(filename) === SUPPORTED_EXTENSION;
|
|
60147
60778
|
}
|
|
60148
|
-
|
|
60149
|
-
* The project's entry file for `file_path`: walk up to the nearest
|
|
60150
|
-
* `package.jsonc` and resolve its `entry` field, so any file inside a project
|
|
60151
|
-
* is checked with the entry's import context (`import types` resolves
|
|
60152
|
-
* against the project's `src/`, not the file's own folder). Returns
|
|
60153
|
-
* undefined when the file is standalone, IS the entry, sits in an
|
|
60154
|
-
* entry-less project (e.g. a library checkout), or the config is malformed.
|
|
60155
|
-
*/
|
|
60156
|
-
function project_entry_for(file_path) {
|
|
60157
|
-
let dir = path.dirname(file_path);
|
|
60158
|
-
for (let i = 0; i < 20; i++) {
|
|
60159
|
-
const config_path = path.join(dir, "package.jsonc");
|
|
60160
|
-
if (fs.existsSync(config_path)) {
|
|
60161
|
-
try {
|
|
60162
|
-
const json = fs.readFileSync(config_path, "utf8").replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
60163
|
-
const parsed = JSON.parse(json);
|
|
60164
|
-
if (parsed.entry) {
|
|
60165
|
-
const entry = path.resolve(dir, parsed.entry);
|
|
60166
|
-
if (entry !== file_path && fs.existsSync(entry)) return entry;
|
|
60167
|
-
}
|
|
60168
|
-
} catch {}
|
|
60169
|
-
return;
|
|
60170
|
-
}
|
|
60171
|
-
const parent = path.dirname(dir);
|
|
60172
|
-
if (parent === dir) return void 0;
|
|
60173
|
-
dir = parent;
|
|
60174
|
-
}
|
|
60175
|
-
}
|
|
60176
|
-
function processFile(filename, config, mode, program_args, anchor_to_project = true) {
|
|
60779
|
+
function processFile(filename, config, mode, program_args) {
|
|
60177
60780
|
console.log("Processing", filename);
|
|
60178
60781
|
const arch = config.arch || "aarch64";
|
|
60179
60782
|
const platform = config.platform || default_platform();
|
|
60180
60783
|
const resolved = path.resolve(filename);
|
|
60181
60784
|
if (!config.lib) config.lib = resolve_lib(resolved);
|
|
60182
|
-
const anchor = anchor_to_project && !resolved.endsWith(".test.nm") ? project_entry_for(resolved) ?? resolved : resolved;
|
|
60183
|
-
if (anchor !== resolved) console.log("Anchored to project entry", anchor);
|
|
60184
60785
|
let startTime = performance.now();
|
|
60185
|
-
const input = join$1(
|
|
60186
|
-
const parsed = parse(input, config.lib ? get_library(config.lib) : void 0,
|
|
60786
|
+
const input = join$1(resolved, config.lib);
|
|
60787
|
+
const parsed = parse(input, config.lib ? get_library(config.lib) : void 0, resolved);
|
|
60187
60788
|
let errors = parsed.errors;
|
|
60188
60789
|
if (errors.length) {
|
|
60189
60790
|
console.log(render_errors(input, errors));
|