nomen-lang 0.6.1 → 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/List.nm +12 -0
- package/core/System/StringBuilder.nm +12 -4
- package/core/System/Text/Regex.nm +704 -77
- package/core/docs/System/Text/Regex.md +1 -1
- package/dist/index.mjs +1362 -362
- package/package.json +1 -1
- package/src/index.ts +8 -56
- package/src/init.ts +12 -3
- package/src/test.ts +57 -3
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,130 @@ 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
|
|
21556
|
+
//#region ../src/build_common/fold_string_const.ts
|
|
21557
|
+
/**
|
|
21558
|
+
* Fold a top-level `const`'s string `+` chain into a single string literal.
|
|
21559
|
+
*
|
|
21560
|
+
* Module-level `const` declarations are inlined at every use site (see
|
|
21561
|
+
* `top_level_consts` in both backends' BuildStatus): the build replays the
|
|
21562
|
+
* const's initializer expression wherever the name appears. For a composed
|
|
21563
|
+
* pattern like `pub const HTML_TAG_REGEX = "^(" + TAG_NAME + "|…)"` that
|
|
21564
|
+
* replay lowers to a malloc/copy/free `string_add` chain PER USE — the
|
|
21565
|
+
* allmark port's inline-token loop rebuilt its ~1 KB pattern on every
|
|
21566
|
+
* attempt, and the chain's calls were also the trigger for the aarch64
|
|
21567
|
+
* ref-argument clobber (PORT.md).
|
|
21568
|
+
*
|
|
21569
|
+
* When the initializer is a pure `+` chain of plain string literals and
|
|
21570
|
+
* references to other foldable consts, this returns one merged literal
|
|
21571
|
+
* ValueNode — semantically the string the source spells — that the use site
|
|
21572
|
+
* emits as rodata instead. Anything else (a runtime operand, a struct ctor,
|
|
21573
|
+
* an operator overload) returns null and the tree builds as before.
|
|
21574
|
+
*
|
|
21575
|
+
* Raw token merging is byte-preserving: escape sequences are self-contained
|
|
21576
|
+
* within a part's content (a content ending in a lone `\` would have
|
|
21577
|
+
* escaped that part's closing quote, so it cannot occur), and both
|
|
21578
|
+
* emitters' `\x`-as-octal re-encoding plus the 2/3-digit caps keep merged
|
|
21579
|
+
* escapes from absorbing a following part's text.
|
|
21580
|
+
*/
|
|
21581
|
+
const fold_cache = /* @__PURE__ */ new WeakMap();
|
|
21582
|
+
function fold_string_const(decl, consts) {
|
|
21583
|
+
if (fold_cache.has(decl)) return fold_cache.get(decl);
|
|
21584
|
+
const folded = fold_value(decl.value, consts, /* @__PURE__ */ new Set());
|
|
21585
|
+
fold_cache.set(decl, folded);
|
|
21586
|
+
return folded;
|
|
21587
|
+
}
|
|
21588
|
+
function fold_value(node, consts, active) {
|
|
21589
|
+
if (!node) return null;
|
|
21590
|
+
if (node.node_type === "value") {
|
|
21591
|
+
const raw = node.value;
|
|
21592
|
+
if (typeof raw !== "string") return null;
|
|
21593
|
+
if (raw.startsWith("\"")) return literal(raw, node.start);
|
|
21594
|
+
if (active.has(raw)) return null;
|
|
21595
|
+
const ref = consts.get(raw);
|
|
21596
|
+
if (!ref) return null;
|
|
21597
|
+
active.add(raw);
|
|
21598
|
+
const folded = fold_value(ref.value, consts, active);
|
|
21599
|
+
active.delete(raw);
|
|
21600
|
+
return folded;
|
|
21601
|
+
}
|
|
21602
|
+
if (node.node_type === "op") {
|
|
21603
|
+
const op = node;
|
|
21604
|
+
if (!(op.op === "+" && (!op.operator_func || op.operator_func.struct_name === "string"))) return null;
|
|
21605
|
+
const left = fold_value(op.left_value, consts, active);
|
|
21606
|
+
if (!left) return null;
|
|
21607
|
+
const right = fold_value(op.right_value, consts, active);
|
|
21608
|
+
if (!right) return null;
|
|
21609
|
+
return literal(left.value.slice(0, -1) + right.value.slice(1), node.start);
|
|
21610
|
+
}
|
|
21611
|
+
return null;
|
|
21612
|
+
}
|
|
21613
|
+
function literal(raw, start) {
|
|
21614
|
+
return new ValueNode(start, raw, new Type("string", true));
|
|
21615
|
+
}
|
|
21616
|
+
//#endregion
|
|
21424
21617
|
//#region ../src/build_common/string_escapes.ts
|
|
21425
21618
|
/**
|
|
21426
21619
|
* Source string-literal escape handling shared by both backends.
|
|
@@ -21455,8 +21648,8 @@ function reencode_hex_escapes(raw) {
|
|
|
21455
21648
|
let j = i + 2;
|
|
21456
21649
|
let value = 0;
|
|
21457
21650
|
let digits = 0;
|
|
21458
|
-
while (j < raw.length && digits < 2 && is_hex_digit(raw[j])) {
|
|
21459
|
-
value = value * 16 + hex_value(raw[j]);
|
|
21651
|
+
while (j < raw.length && digits < 2 && is_hex_digit$1(raw[j])) {
|
|
21652
|
+
value = value * 16 + hex_value$1(raw[j]);
|
|
21460
21653
|
j += 1;
|
|
21461
21654
|
digits += 1;
|
|
21462
21655
|
}
|
|
@@ -21471,10 +21664,10 @@ function reencode_hex_escapes(raw) {
|
|
|
21471
21664
|
}
|
|
21472
21665
|
return out;
|
|
21473
21666
|
}
|
|
21474
|
-
function is_hex_digit(c) {
|
|
21667
|
+
function is_hex_digit$1(c) {
|
|
21475
21668
|
return c >= "0" && c <= "9" || c >= "a" && c <= "f" || c >= "A" && c <= "F";
|
|
21476
21669
|
}
|
|
21477
|
-
function hex_value(c) {
|
|
21670
|
+
function hex_value$1(c) {
|
|
21478
21671
|
if (c <= "9") return c.charCodeAt(0) - 48;
|
|
21479
21672
|
if (c <= "F") return c.charCodeAt(0) - 55;
|
|
21480
21673
|
return c.charCodeAt(0) - 87;
|
|
@@ -21504,7 +21697,7 @@ function scan_string_escapes(raw) {
|
|
|
21504
21697
|
if (next === "x") {
|
|
21505
21698
|
let j = i + 2;
|
|
21506
21699
|
let digits = 0;
|
|
21507
|
-
while (j < end && digits < 2 && is_hex_digit(raw[j])) {
|
|
21700
|
+
while (j < end && digits < 2 && is_hex_digit$1(raw[j])) {
|
|
21508
21701
|
j += 1;
|
|
21509
21702
|
digits += 1;
|
|
21510
21703
|
}
|
|
@@ -21539,6 +21732,16 @@ function scan_string_escapes(raw) {
|
|
|
21539
21732
|
}
|
|
21540
21733
|
return issues;
|
|
21541
21734
|
}
|
|
21735
|
+
/** Escape one raw string-literal token (quotes included) for a GAS
|
|
21736
|
+
* `.asciz` directive: source `\xHH` hex escapes re-encode as 3-digit octal
|
|
21737
|
+
* (GAS consumes `\x` greedily — see reencode_hex_escapes) and raw newlines
|
|
21738
|
+
* (multi-line strings) become `\n` so the directive stays one line. */
|
|
21739
|
+
function escape_asciz(value) {
|
|
21740
|
+
const reencoded = reencode_hex_escapes(value);
|
|
21741
|
+
if (!reencoded.includes("\n")) return reencoded;
|
|
21742
|
+
const quote = reencoded[0];
|
|
21743
|
+
return quote + reencoded.slice(1, reencoded.endsWith(quote) ? -1 : void 0).replace(/\n/g, "\\n") + (reencoded.endsWith(quote) ? quote : "");
|
|
21744
|
+
}
|
|
21542
21745
|
//#endregion
|
|
21543
21746
|
//#region ../src/build_aarch64/build_range_node.ts
|
|
21544
21747
|
function build_range_node$1(node, status) {
|
|
@@ -21752,12 +21955,6 @@ function is_value_struct_type_node(node, status) {
|
|
|
21752
21955
|
const t = type_from_value_node$1(node);
|
|
21753
21956
|
return !!t?.name && !!status.structs.find((s) => s.name === t.name && !s.is_simple_type && !s.is_class);
|
|
21754
21957
|
}
|
|
21755
|
-
function escape_asciz(value) {
|
|
21756
|
-
const reencoded = reencode_hex_escapes(value);
|
|
21757
|
-
if (!reencoded.includes("\n")) return reencoded;
|
|
21758
|
-
const quote = reencoded[0];
|
|
21759
|
-
return quote + reencoded.slice(1, reencoded.endsWith(quote) ? -1 : void 0).replace(/\n/g, "\\n") + (reencoded.endsWith(quote) ? quote : "");
|
|
21760
|
-
}
|
|
21761
21958
|
/** Allocate an array on the stack with an 8-byte length prefix.
|
|
21762
21959
|
* Writes the length at the start and returns the offset of the first element. */
|
|
21763
21960
|
function alloc_array_with_prefix(status, length, element_size) {
|
|
@@ -22227,6 +22424,93 @@ function resolve_string_op(op, status) {
|
|
|
22227
22424
|
}
|
|
22228
22425
|
return null;
|
|
22229
22426
|
}
|
|
22427
|
+
const FLOAT_LITERAL_RE = /^(\+|-)?\d+\.\d+([eE](\+|-)?\d+)?$/;
|
|
22428
|
+
/**
|
|
22429
|
+
* Fold a module-level primitive `const`/`var` initializer to a literal string
|
|
22430
|
+
* when it is a compile-time constant: literal operands, references to other
|
|
22431
|
+
* foldable module-level consts, and arithmetic/bitwise operators over them.
|
|
22432
|
+
* Returns null when the value can only be computed at runtime (a call, a
|
|
22433
|
+
* field read, …), which a file-scope `.quad` initializer cannot express.
|
|
22434
|
+
*
|
|
22435
|
+
* Without this, `pub const N = 3 + 4` at module scope reserved
|
|
22436
|
+
* `N: .space 8` and emitted its store as dead code between functions, so
|
|
22437
|
+
* every read observed 0 (FOLLOWUP.md). Integer math is exact via BigInt so
|
|
22438
|
+
* 64-bit constants are not rounded through JS doubles; float arithmetic
|
|
22439
|
+
* falls back to JS doubles (which match Nomen's `float`).
|
|
22440
|
+
*/
|
|
22441
|
+
function fold_const_scalar(node, status, seen) {
|
|
22442
|
+
if (!node) return null;
|
|
22443
|
+
if (node.node_type === "grouped") return fold_const_scalar(node.value, status, seen);
|
|
22444
|
+
if (node.node_type === "value") {
|
|
22445
|
+
const raw = get_raw_value$1(node, status);
|
|
22446
|
+
if (is_int_literal(raw)) return to_decimal_string(raw);
|
|
22447
|
+
if (FLOAT_LITERAL_RE.test(raw)) return raw;
|
|
22448
|
+
if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(raw) && !seen.has(raw)) {
|
|
22449
|
+
const decl = find_global_const(raw, status);
|
|
22450
|
+
if (decl?.value) {
|
|
22451
|
+
seen.add(raw);
|
|
22452
|
+
const folded = fold_const_scalar(decl.value, status, seen);
|
|
22453
|
+
seen.delete(raw);
|
|
22454
|
+
return folded;
|
|
22455
|
+
}
|
|
22456
|
+
}
|
|
22457
|
+
return null;
|
|
22458
|
+
}
|
|
22459
|
+
if (node.node_type === "op") {
|
|
22460
|
+
const op = node;
|
|
22461
|
+
if (op.operator_func) return null;
|
|
22462
|
+
const left = fold_const_scalar(op.left_value, status, seen);
|
|
22463
|
+
if (left === null) return null;
|
|
22464
|
+
const right = fold_const_scalar(op.right_value, status, seen);
|
|
22465
|
+
if (right === null) return null;
|
|
22466
|
+
return apply_const_op(op.op, left, right);
|
|
22467
|
+
}
|
|
22468
|
+
return null;
|
|
22469
|
+
}
|
|
22470
|
+
/** Apply one arithmetic/bitwise operator to two folded literal strings.
|
|
22471
|
+
* Float operands use JS doubles; integral operands stay exact via BigInt. */
|
|
22472
|
+
function apply_const_op(op, left, right) {
|
|
22473
|
+
if (FLOAT_LITERAL_RE.test(left) || FLOAT_LITERAL_RE.test(right)) {
|
|
22474
|
+
const a = Number(left);
|
|
22475
|
+
const b = Number(right);
|
|
22476
|
+
if (!isFinite(a) || !isFinite(b)) return null;
|
|
22477
|
+
switch (op) {
|
|
22478
|
+
case "+": return String(a + b);
|
|
22479
|
+
case "-": return String(a - b);
|
|
22480
|
+
case "*": return String(a * b);
|
|
22481
|
+
case "/": return b === 0 ? null : String(a / b);
|
|
22482
|
+
default: return null;
|
|
22483
|
+
}
|
|
22484
|
+
}
|
|
22485
|
+
let a;
|
|
22486
|
+
let b;
|
|
22487
|
+
try {
|
|
22488
|
+
a = BigInt(left);
|
|
22489
|
+
b = BigInt(right);
|
|
22490
|
+
} catch {
|
|
22491
|
+
return null;
|
|
22492
|
+
}
|
|
22493
|
+
switch (op) {
|
|
22494
|
+
case "+": return (a + b).toString();
|
|
22495
|
+
case "-": return (a - b).toString();
|
|
22496
|
+
case "*": return (a * b).toString();
|
|
22497
|
+
case "/": return b === 0n ? null : (a / b).toString();
|
|
22498
|
+
case "%": return b === 0n ? null : (a % b).toString();
|
|
22499
|
+
case "<<": return (a << b).toString();
|
|
22500
|
+
case ">>": return (a >> b).toString();
|
|
22501
|
+
case "&": return (a & b).toString();
|
|
22502
|
+
case "|": return (a | b).toString();
|
|
22503
|
+
case "^": return (a ^ b).toString();
|
|
22504
|
+
default: return null;
|
|
22505
|
+
}
|
|
22506
|
+
}
|
|
22507
|
+
/** The root-scope `const` declaration named `name` (used to fold a const
|
|
22508
|
+
* reference through to its initializer). Mutable `var`s are deliberately
|
|
22509
|
+
* excluded: their value is established by module init, so baking in the
|
|
22510
|
+
* declaration's initializer could differ from the runtime value. */
|
|
22511
|
+
function find_global_const(name, status) {
|
|
22512
|
+
return (status.root.statements ?? []).find((s) => s.node_type === "declare" && s.declaration === "const" && s.name === name);
|
|
22513
|
+
}
|
|
22230
22514
|
/**
|
|
22231
22515
|
* Emit a declaration INITIALIZER expression. Under NIR-driven emission the
|
|
22232
22516
|
* lowered `NirExpr` rides in and descends through `emit_expr_from_nir` (the
|
|
@@ -22258,7 +22542,7 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
|
|
|
22258
22542
|
const prev_heap = status.last_result_is_heap;
|
|
22259
22543
|
if (node.value?.node_type === "value") {
|
|
22260
22544
|
const inlined = status.top_level_consts?.get(node.value.value);
|
|
22261
|
-
if (inlined?.value) node.value = inlined.value;
|
|
22545
|
+
if (inlined?.value) node.value = fold_string_const(inlined, status.top_level_consts) ?? inlined.value;
|
|
22262
22546
|
}
|
|
22263
22547
|
function check_heap() {
|
|
22264
22548
|
if (status.last_result_is_heap) {
|
|
@@ -22402,12 +22686,15 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
|
|
|
22402
22686
|
build_swap_params(func_call, status);
|
|
22403
22687
|
} else {
|
|
22404
22688
|
const old_buffer = status.struct_return_buffer;
|
|
22689
|
+
const old_buffer_var = status.struct_return_buffer_var;
|
|
22405
22690
|
const old_preset = status.call_x8_preset;
|
|
22406
22691
|
emit_var_address(status, "x8", node.name);
|
|
22692
|
+
status.struct_return_buffer_var = node.name;
|
|
22407
22693
|
status.struct_return_buffer = "x8";
|
|
22408
22694
|
status.call_x8_preset = true;
|
|
22409
22695
|
emit_init_value$1(node.value, nir_init, status);
|
|
22410
22696
|
status.struct_return_buffer = old_buffer;
|
|
22697
|
+
status.struct_return_buffer_var = old_buffer_var;
|
|
22411
22698
|
status.call_x8_preset = old_preset;
|
|
22412
22699
|
}
|
|
22413
22700
|
return;
|
|
@@ -22933,14 +23220,18 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
|
|
|
22933
23220
|
build_swap_params(func_call, status);
|
|
22934
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) {
|
|
22935
23222
|
const old_buffer = status.struct_return_buffer;
|
|
23223
|
+
const old_buffer_var = status.struct_return_buffer_var;
|
|
22936
23224
|
const old_preset = status.call_x8_preset;
|
|
22937
23225
|
emit_var_address(status, "x8", node.name);
|
|
23226
|
+
status.struct_return_buffer_var = node.name;
|
|
22938
23227
|
status.struct_return_buffer = "x8";
|
|
22939
23228
|
status.call_x8_preset = true;
|
|
22940
23229
|
emit_init_value$1(node.value, nir_init, status);
|
|
22941
23230
|
status.struct_return_buffer = old_buffer;
|
|
23231
|
+
status.struct_return_buffer_var = old_buffer_var;
|
|
22942
23232
|
status.call_x8_preset = old_preset;
|
|
22943
23233
|
emit_var_address(status, "x0", node.name);
|
|
23234
|
+
record_call_init_string_fields(node, status);
|
|
22944
23235
|
} else {
|
|
22945
23236
|
emit_init_value$1(node.value, nir_init, status);
|
|
22946
23237
|
emit_var_store(status, "x0", node.name, struct_size);
|
|
@@ -22976,6 +23267,7 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
|
|
|
22976
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);
|
|
22977
23268
|
emit_struct_copy("x1", "x0", get_field_offset(target_type.name, field_name, status), get_struct_size(field_type.name, status), status);
|
|
22978
23269
|
}
|
|
23270
|
+
record_call_init_string_fields(node, status);
|
|
22979
23271
|
}
|
|
22980
23272
|
}
|
|
22981
23273
|
} else if (node.value) {
|
|
@@ -23200,7 +23492,15 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
|
|
|
23200
23492
|
if (status.function_return_label) {
|
|
23201
23493
|
const offset = declare_slot_offset(status, node.name, size);
|
|
23202
23494
|
status.stack_offsets.set(node.name, offset);
|
|
23203
|
-
} else
|
|
23495
|
+
} else {
|
|
23496
|
+
const folded = fold_const_scalar(node.value, status, /* @__PURE__ */ new Set());
|
|
23497
|
+
if (folded !== null) {
|
|
23498
|
+
emit_data(status, `${node.name}: ${directive} ${folded}\n`);
|
|
23499
|
+
if (size % 4 !== 0) emit_data(status, `.p2align 2\n`);
|
|
23500
|
+
return;
|
|
23501
|
+
}
|
|
23502
|
+
emit_data(status, `${node.name}: .space ${size}\n`);
|
|
23503
|
+
}
|
|
23204
23504
|
if (node.type.name === "string" && !node.type.is_view && is_view_value(node.value, status)) {
|
|
23205
23505
|
emit_view_string_arg(node.value, status);
|
|
23206
23506
|
emit_view_materialize_owned(status);
|
|
@@ -24807,6 +25107,18 @@ function build_return_node$1(node, status, nir_value) {
|
|
|
24807
25107
|
const field_off = get_field_offset(ret_struct.name, field.name, status);
|
|
24808
25108
|
emit_enum_payload_strdups_at(status, field_enum.name, "x8", field_off);
|
|
24809
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
|
+
}
|
|
24810
25122
|
}
|
|
24811
25123
|
}
|
|
24812
25124
|
if (status.return_assign) {
|
|
@@ -28436,8 +28748,39 @@ function build_function_call_node$1(node, status) {
|
|
|
28436
28748
|
emit_asm(status, `str x9, [x29, #${off + struct_size}]\n`);
|
|
28437
28749
|
emit_asm(status, `add x0, x29, #${off}\n`);
|
|
28438
28750
|
}
|
|
28439
|
-
} else if (is_struct_type$3(param_type, status) || is_enum_with_data_type$2(param_type, status))
|
|
28440
|
-
|
|
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)) {
|
|
28441
28784
|
deferred_args.push({
|
|
28442
28785
|
param: node.params[i],
|
|
28443
28786
|
slot: arg_slot[i]
|
|
@@ -28569,6 +28912,7 @@ function build_function_call_node$1(node, status) {
|
|
|
28569
28912
|
const tname = all_scope_frames(status).flat().find((d) => d.name === vname)?.type?.name ?? param.type?.name;
|
|
28570
28913
|
if (tname === "string") continue;
|
|
28571
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);
|
|
28572
28916
|
}
|
|
28573
28917
|
if (param) mark_moved_if_struct(param, status);
|
|
28574
28918
|
}
|
|
@@ -30897,11 +31241,23 @@ function free_scoped_declarations(status, decls, persist_string_field_records =
|
|
|
30897
31241
|
commented = true;
|
|
30898
31242
|
}
|
|
30899
31243
|
const dot = key.indexOf(".");
|
|
30900
|
-
|
|
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`;
|
|
30901
31249
|
if (!persist_string_field_records) status.heap_string_fields.delete(key);
|
|
30902
31250
|
}
|
|
30903
31251
|
}
|
|
30904
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
|
+
}
|
|
30905
31261
|
if (persist_string_field_records && dec.force_owned_string === true) continue;
|
|
30906
31262
|
if (status.moved?.has(dec.name)) continue;
|
|
30907
31263
|
const cname = c_function_name(dec.name);
|
|
@@ -31499,7 +31855,8 @@ function build_assignment_node$1(node, status, nir_rhs, nir_swap) {
|
|
|
31499
31855
|
status.init_assigned_fields.add(tracked_key);
|
|
31500
31856
|
}
|
|
31501
31857
|
const old_was_heap = !!target_struct?.is_class && !first_init_write || !!status.heap_string_fields?.has(tracked_key);
|
|
31502
|
-
|
|
31858
|
+
const is_class_target = !!target_struct?.is_class;
|
|
31859
|
+
if (target_struct && (target_var || is_class_target) && (!self_target || is_class_target)) {
|
|
31503
31860
|
const fresh_heap = is_owned_heap_temp$1(node.right_value, status);
|
|
31504
31861
|
const rhs_is_null_value = node.right_value.node_type === "value" && node.right_value.value === "null";
|
|
31505
31862
|
const saved_len = begin_code_scratch(status);
|
|
@@ -32581,6 +32938,7 @@ function build_declaration_node(node, status, nir_init, nir_swap) {
|
|
|
32581
32938
|
emit_swap_value$1(node.swap, nir_swap, status);
|
|
32582
32939
|
}
|
|
32583
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);
|
|
32584
32942
|
}
|
|
32585
32943
|
}
|
|
32586
32944
|
}
|
|
@@ -33096,6 +33454,7 @@ function build_return_node(node, status, nir_value) {
|
|
|
33096
33454
|
}
|
|
33097
33455
|
}
|
|
33098
33456
|
let returned_value_decl;
|
|
33457
|
+
let returned_recorded_string_fields;
|
|
33099
33458
|
if (node.value.node_type === "value") {
|
|
33100
33459
|
const value = node.value.value;
|
|
33101
33460
|
returned_value_decl = find_decl_across_scopes(value, status);
|
|
@@ -33109,7 +33468,11 @@ function build_return_node(node, status, nir_value) {
|
|
|
33109
33468
|
}
|
|
33110
33469
|
if (status.heap_string_fields?.size) {
|
|
33111
33470
|
const prefix = `${value}.`;
|
|
33112
|
-
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
|
+
}
|
|
33113
33476
|
}
|
|
33114
33477
|
}
|
|
33115
33478
|
if (ret_type?.is_array && return_array_var && return_array_len > 0) {
|
|
@@ -33218,6 +33581,11 @@ function build_return_node(node, status, nir_value) {
|
|
|
33218
33581
|
status.code += `;\n`;
|
|
33219
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`;
|
|
33220
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
|
+
}
|
|
33221
33589
|
emit_nursery_joins_on_return_c(status);
|
|
33222
33590
|
reclaim_all_c_scopes(status);
|
|
33223
33591
|
status.code += `return _return_val;\n`;
|
|
@@ -35168,6 +35536,18 @@ function build_function_node(node, status) {
|
|
|
35168
35536
|
if (param_trait && !param_struct?.is_class) decl.trait_class_trait = param.type.name;
|
|
35169
35537
|
status.scoped_declarations.push(decl);
|
|
35170
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
|
+
}
|
|
35171
35551
|
}
|
|
35172
35552
|
}
|
|
35173
35553
|
const nir = node.statements?.length ? lower_function(node) : void 0;
|
|
@@ -36408,6 +36788,27 @@ function build_function_call_node(node, status) {
|
|
|
36408
36788
|
continue;
|
|
36409
36789
|
}
|
|
36410
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
|
+
}
|
|
36411
36812
|
if (node.nullable_param_indices?.includes(i) && node.params[i].node_type === "value" && node.params[i].value === "null") {
|
|
36412
36813
|
status.code += `(void *)&(struct ${param_type.name}){0}, 0`;
|
|
36413
36814
|
continue;
|
|
@@ -36454,6 +36855,23 @@ function build_function_call_node(node, status) {
|
|
|
36454
36855
|
status.suppress_dereference = false;
|
|
36455
36856
|
continue;
|
|
36456
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
|
+
}
|
|
36457
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) {
|
|
36458
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;
|
|
36459
36877
|
else if (is_ref_param) status.code += `&`;
|
|
@@ -36530,9 +36948,12 @@ function build_function_call_node(node, status) {
|
|
|
36530
36948
|
if (decl_hit) decl_hit.frame.splice(decl_hit.index, 1);
|
|
36531
36949
|
if (is_value_struct) {
|
|
36532
36950
|
const prefix = `${vname}.`;
|
|
36951
|
+
const transfer = node.move_owned_param_indices?.includes(idx) ?? false;
|
|
36533
36952
|
for (const key of Array.from(status.heap_string_fields ?? [])) if (key.startsWith(prefix)) {
|
|
36534
|
-
if (!
|
|
36535
|
-
|
|
36953
|
+
if (!transfer) {
|
|
36954
|
+
if (!status.pending_string_releases) status.pending_string_releases = [];
|
|
36955
|
+
status.pending_string_releases.push(`free(${key}.ptr);`);
|
|
36956
|
+
}
|
|
36536
36957
|
status.heap_string_fields.delete(key);
|
|
36537
36958
|
}
|
|
36538
36959
|
}
|
|
@@ -36796,7 +37217,7 @@ function build_value_node$1(node, status) {
|
|
|
36796
37217
|
}
|
|
36797
37218
|
const inlined = status.top_level_consts?.get(value);
|
|
36798
37219
|
if (inlined?.value) {
|
|
36799
|
-
build_node$1(inlined.value, status);
|
|
37220
|
+
build_node$1(fold_string_const(inlined, status.top_level_consts) ?? inlined.value, status);
|
|
36800
37221
|
return;
|
|
36801
37222
|
}
|
|
36802
37223
|
if (node.is_enum_shorthand) {
|
|
@@ -37737,7 +38158,7 @@ function build_value_node(node, status) {
|
|
|
37737
38158
|
}
|
|
37738
38159
|
const inlined = status.top_level_consts?.get(original_value);
|
|
37739
38160
|
if (inlined?.value) {
|
|
37740
|
-
build_node(inlined.value, status);
|
|
38161
|
+
build_node(fold_string_const(inlined, status.top_level_consts) ?? inlined.value, status);
|
|
37741
38162
|
return;
|
|
37742
38163
|
}
|
|
37743
38164
|
if (node.is_enum_shorthand) {
|
|
@@ -38193,6 +38614,10 @@ function get_base_address(access, status, reg) {
|
|
|
38193
38614
|
if (paramReg !== reg) emit_asm(status, `mov ${reg}, ${paramReg}\n`);
|
|
38194
38615
|
} else if (is_local_ref_var(name, status)) emit_deref_var_address(status, reg, name);
|
|
38195
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`);
|
|
38196
38621
|
} else {
|
|
38197
38622
|
emit_address_of(access.target, status);
|
|
38198
38623
|
ensure_newline(status);
|
|
@@ -41711,6 +42136,9 @@ function emit_address_of(node, status) {
|
|
|
41711
42136
|
if (paramReg !== "x0") emit_asm(status, `mov x0, ${paramReg}\n`);
|
|
41712
42137
|
} else if (is_local_ref_var(name, status)) emit_deref_var_address(status, "x0", name);
|
|
41713
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);
|
|
41714
42142
|
} else {
|
|
41715
42143
|
emit_address_of(access.target, status);
|
|
41716
42144
|
ensure_newline(status);
|
|
@@ -42980,8 +43408,6 @@ function build_access_method(node, access_func, status) {
|
|
|
42980
43408
|
return true;
|
|
42981
43409
|
};
|
|
42982
43410
|
const overflow_count = Math.max(0, total_arg_slots - (8 - start_reg));
|
|
42983
|
-
let overflow_base = 0;
|
|
42984
|
-
if (overflow_count > 0) overflow_base = allocate_stack_space(status, overflow_count * 8, 16);
|
|
42985
43411
|
const lambda_arg_indices = /* @__PURE__ */ new Set();
|
|
42986
43412
|
for (let i = 0; i < access_func.params.length; i++) {
|
|
42987
43413
|
const p = access_func.params[i];
|
|
@@ -42992,21 +43418,17 @@ function build_access_method(node, access_func, status) {
|
|
|
42992
43418
|
lambda_arg_indices.add(i);
|
|
42993
43419
|
}
|
|
42994
43420
|
const lambda_arg_slots = [];
|
|
42995
|
-
const
|
|
42996
|
-
|
|
42997
|
-
|
|
42998
|
-
|
|
42999
|
-
|
|
43000
|
-
return
|
|
43421
|
+
const arg_builds_in_loop = (i) => {
|
|
43422
|
+
if (view_arg_set.has(i) || string_arg_set.has(i)) return true;
|
|
43423
|
+
if ((access_func.ref_param_indices ?? []).includes(i)) return true;
|
|
43424
|
+
const param_type = access_func.params[i].type?.name || "";
|
|
43425
|
+
if (is_struct_type(param_type, status) || is_enum_with_data_type(param_type, status)) return true;
|
|
43426
|
+
return !arg_deferrable(i);
|
|
43001
43427
|
};
|
|
43002
|
-
const
|
|
43003
|
-
|
|
43004
|
-
|
|
43005
|
-
const
|
|
43006
|
-
const param0_builds_in_loop = access_func.params.length > 0 && (param0_is_ref || is_struct_type(param0_type_name, status) || is_enum_with_data_type(param0_type_name, status) || !arg_deferrable(0));
|
|
43007
|
-
const static_arg0_needs_spill = start_reg === 0 && access_func.params.length > 0 && !param0_is_pair && param0_builds_in_loop;
|
|
43008
|
-
let static_arg0_spill = 0;
|
|
43009
|
-
if (static_arg0_needs_spill) static_arg0_spill = allocate_stack_space(status, 8, 8);
|
|
43428
|
+
const any_in_loop_arg = access_func.params.some((_, i) => arg_builds_in_loop(i));
|
|
43429
|
+
let arg_spill_base = 0;
|
|
43430
|
+
if (any_in_loop_arg) arg_spill_base = allocate_stack_space(status, total_arg_slots * 8, 16);
|
|
43431
|
+
const view_half_store = (j, half) => arg_spill_base + (arg_slot[j] + half) * 8;
|
|
43010
43432
|
const ref_class_param_reload = [];
|
|
43011
43433
|
const ref_class_sync_names = [];
|
|
43012
43434
|
for (let i = access_func.params.length - 1; i >= 0; i--) {
|
|
@@ -43076,31 +43498,20 @@ function build_access_method(node, access_func, status) {
|
|
|
43076
43498
|
lambda_arg_slots.push(dispose_slot);
|
|
43077
43499
|
}
|
|
43078
43500
|
}
|
|
43079
|
-
const slot = start_reg + arg_slot[i];
|
|
43080
43501
|
ensure_newline(status);
|
|
43081
|
-
|
|
43082
|
-
else {
|
|
43083
|
-
const reg = `x${slot}`;
|
|
43084
|
-
if (reg !== "x0") emit_asm(status, `mov ${reg}, x0\n`);
|
|
43085
|
-
else if (static_arg0_needs_spill) emit_asm(status, `str x0, [x29, #${static_arg0_spill}]\n`);
|
|
43086
|
-
}
|
|
43502
|
+
emit_asm(status, `str x0, [x29, #${arg_spill_base + arg_slot[i] * 8}]\n`);
|
|
43087
43503
|
}
|
|
43088
43504
|
deferred_args.sort((a, b) => b.slot - a.slot);
|
|
43089
43505
|
for (const d of deferred_args) {
|
|
43090
43506
|
build_operand(d.param, `x${start_reg + d.slot}`, status);
|
|
43091
43507
|
ensure_newline(status);
|
|
43092
43508
|
}
|
|
43093
|
-
|
|
43094
|
-
|
|
43095
|
-
|
|
43096
|
-
|
|
43097
|
-
|
|
43098
|
-
|
|
43099
|
-
}
|
|
43100
|
-
}
|
|
43101
|
-
if (static_arg0_needs_spill) {
|
|
43102
|
-
ensure_newline(status);
|
|
43103
|
-
emit_asm(status, `ldr x0, [x29, #${static_arg0_spill}]\n`);
|
|
43509
|
+
const deferred_slots = new Set(deferred_args.map((d) => start_reg + d.slot));
|
|
43510
|
+
for (let s = 0; s < total_arg_slots; s++) {
|
|
43511
|
+
const slot = start_reg + s;
|
|
43512
|
+
if (slot >= 8) continue;
|
|
43513
|
+
if (deferred_slots.has(slot)) continue;
|
|
43514
|
+
emit_asm(status, `ldr x${slot}, [x29, #${arg_spill_base + s * 8}]\n`);
|
|
43104
43515
|
}
|
|
43105
43516
|
ensure_newline(status);
|
|
43106
43517
|
if (receiver_is_string) emit_asm(status, frees_string_receiver ? `ldp x0, x1, [sp]\n` : `ldp x0, x1, [sp], #16\n`);
|
|
@@ -43110,8 +43521,9 @@ function build_access_method(node, access_func, status) {
|
|
|
43110
43521
|
if (overflow_count > 0) {
|
|
43111
43522
|
outgoing_size = Math.ceil(overflow_count * 8 / 16) * 16;
|
|
43112
43523
|
emit_asm(status, `sub sp, sp, #${outgoing_size}\n`);
|
|
43524
|
+
const overflow_first = 8 - start_reg;
|
|
43113
43525
|
for (let k = 0; k < overflow_count; k++) {
|
|
43114
|
-
emit_asm(status, `ldr x9, [x29, #${
|
|
43526
|
+
emit_asm(status, `ldr x9, [x29, #${arg_spill_base + (overflow_first + k) * 8}]\n`);
|
|
43115
43527
|
emit_asm(status, `str x9, [sp, #${k * 8}]\n`);
|
|
43116
43528
|
}
|
|
43117
43529
|
}
|
|
@@ -44110,6 +44522,195 @@ function scan_func(func, struct_name, result, class_type_names) {
|
|
|
44110
44522
|
}
|
|
44111
44523
|
}
|
|
44112
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
|
|
44113
44714
|
//#region ../src/build.ts
|
|
44114
44715
|
function build(root, options = {}) {
|
|
44115
44716
|
let status = {
|
|
@@ -44143,6 +44744,7 @@ function build(root, options = {}) {
|
|
|
44143
44744
|
reset_match_temp_counter();
|
|
44144
44745
|
reset_ns_tmp_counter();
|
|
44145
44746
|
stamp_last_use_moves(root);
|
|
44747
|
+
gather_normalized_struct_returners(root, status);
|
|
44146
44748
|
if (options.arch === "aarch64") {
|
|
44147
44749
|
install_asm_code_buffer(status);
|
|
44148
44750
|
reset_string_counter$1();
|
|
@@ -44190,10 +44792,7 @@ function build(root, options = {}) {
|
|
|
44190
44792
|
}
|
|
44191
44793
|
if (status.strings && status.strings.size > 0) {
|
|
44192
44794
|
status.code += "\n";
|
|
44193
|
-
for (const [label, value] of status.strings) {
|
|
44194
|
-
const escaped = value.replace(/\n/g, "\\n");
|
|
44195
|
-
status.code += `${label}: .asciz ${escaped}\n`;
|
|
44196
|
-
}
|
|
44795
|
+
for (const [label, value] of status.strings) status.code += `${label}: .asciz ${escape_asciz(value)}\n`;
|
|
44197
44796
|
}
|
|
44198
44797
|
if (status.float_literals && status.float_literals.size > 0) {
|
|
44199
44798
|
status.code += "\n.p2align 2\n";
|
|
@@ -46369,6 +46968,23 @@ function is_owning_struct_type_requiring_move(type, status) {
|
|
|
46369
46968
|
if (!s || s.is_class) return false;
|
|
46370
46969
|
return struct_owns_non_string_heap(s, status, /* @__PURE__ */ new Set());
|
|
46371
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
|
+
}
|
|
46372
46988
|
function struct_owns_non_string_heap(s, status, visited) {
|
|
46373
46989
|
if (visited.has(s.name)) return false;
|
|
46374
46990
|
visited.add(s.name);
|
|
@@ -49009,6 +49625,12 @@ function check_declaration_node(decl, status) {
|
|
|
49009
49625
|
if (is_view_keyword && decl.type_start !== void 0) decl.type.is_view = true;
|
|
49010
49626
|
if (decl.func_params) {
|
|
49011
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);
|
|
49012
49634
|
if (decl.func_return_type) {
|
|
49013
49635
|
check_type_exists(decl.func_return_type, status, -1);
|
|
49014
49636
|
const materialized = materialize_type(decl.func_return_type, status);
|
|
@@ -49073,7 +49695,7 @@ function check_declaration_node(decl, status) {
|
|
|
49073
49695
|
status.values.push({
|
|
49074
49696
|
declaration,
|
|
49075
49697
|
name: decl.name,
|
|
49076
|
-
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,
|
|
49077
49699
|
is_set: !!decl.value,
|
|
49078
49700
|
start: decl.start,
|
|
49079
49701
|
is_null: decl.value?.node_type === "value" && decl.value.value === "null",
|
|
@@ -49571,7 +50193,7 @@ function check_struct_node(struct, status) {
|
|
|
49571
50193
|
continue;
|
|
49572
50194
|
}
|
|
49573
50195
|
if (decl.func_params) {
|
|
49574
|
-
decl.type = new Type("func");
|
|
50196
|
+
if (decl.type.name !== "func") decl.type = new Type("func");
|
|
49575
50197
|
check_declaration_node(decl, status);
|
|
49576
50198
|
continue;
|
|
49577
50199
|
}
|
|
@@ -49879,6 +50501,7 @@ function synthesize_for_struct(struct, status) {
|
|
|
49879
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));
|
|
49880
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));
|
|
49881
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);
|
|
49882
50505
|
}
|
|
49883
50506
|
function build_to_string(struct, fields) {
|
|
49884
50507
|
const parts = [string_literal(`${struct.name}(`)];
|
|
@@ -49910,6 +50533,81 @@ function build_hash(struct, fields) {
|
|
|
49910
50533
|
const ret = new ReturnNode(-1, expr);
|
|
49911
50534
|
return new FunctionNode(-1, "pub", "hash", new Type("uint"), [self_param(struct)], [ret]);
|
|
49912
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
|
+
}
|
|
49913
50611
|
//#endregion
|
|
49914
50612
|
//#region ../src/check/check_block_node.ts
|
|
49915
50613
|
function check_block_node(node, status) {
|
|
@@ -50018,16 +50716,22 @@ function must_use_type_display_name(en, type) {
|
|
|
50018
50716
|
*/
|
|
50019
50717
|
function gather_top_level_consts(block, status) {
|
|
50020
50718
|
const seen = /* @__PURE__ */ new Set();
|
|
50719
|
+
const candidates = [];
|
|
50021
50720
|
for (const child of block.statements) {
|
|
50022
50721
|
if (child.node_type !== "declare") continue;
|
|
50023
50722
|
const decl = child;
|
|
50024
50723
|
if (decl.declaration !== "const") continue;
|
|
50025
|
-
if (!decl.name
|
|
50026
|
-
if (decl.type.is_array) continue;
|
|
50724
|
+
if (!decl.name) continue;
|
|
50027
50725
|
if (seen.has(decl.name)) continue;
|
|
50028
50726
|
seen.add(decl.name);
|
|
50727
|
+
candidates.push(decl);
|
|
50728
|
+
}
|
|
50729
|
+
infer_const_decl_types(candidates, status);
|
|
50730
|
+
for (const decl of candidates) {
|
|
50731
|
+
if (!decl.type?.name) continue;
|
|
50732
|
+
if (decl.type.is_array) continue;
|
|
50029
50733
|
status.values.push({
|
|
50030
|
-
declaration:
|
|
50734
|
+
declaration: "const",
|
|
50031
50735
|
name: decl.name,
|
|
50032
50736
|
type: decl.type,
|
|
50033
50737
|
is_set: !!decl.value,
|
|
@@ -50037,6 +50741,60 @@ function gather_top_level_consts(block, status) {
|
|
|
50037
50741
|
}
|
|
50038
50742
|
}
|
|
50039
50743
|
/**
|
|
50744
|
+
* Give every root-level `const` a declared type BEFORE the statement walk,
|
|
50745
|
+
* so a const declared BELOW its first use resolves like a free function
|
|
50746
|
+
* (which hoists across the whole file). Parse can only infer the type when
|
|
50747
|
+
* the initializer is a bare literal; a composed chain (`const TAG = "<" +
|
|
50748
|
+
* TAG_NAME + ">"`, the allmark htmlPatterns shape) stayed typeless and its
|
|
50749
|
+
* use-above-declaration failed with "Unknown value".
|
|
50750
|
+
*
|
|
50751
|
+
* The inference iterates until nothing new is derivable, so a chain may
|
|
50752
|
+
* reference consts declared BELOW itself too. Recognized initializers:
|
|
50753
|
+
* literals, references to other (typed) consts, and arithmetic/concat
|
|
50754
|
+
* operations (the result type is the left operand's, mirroring
|
|
50755
|
+
* check_operation_node). Anything else (constructor calls, function calls)
|
|
50756
|
+
* keeps today's behavior: annotate the type or declare before use.
|
|
50757
|
+
*/
|
|
50758
|
+
function infer_const_decl_types(decls, status) {
|
|
50759
|
+
const known = /* @__PURE__ */ new Map();
|
|
50760
|
+
for (const decl of decls) if (decl.type?.name) known.set(decl.name, decl.type);
|
|
50761
|
+
let progress = true;
|
|
50762
|
+
while (progress) {
|
|
50763
|
+
progress = false;
|
|
50764
|
+
for (const decl of decls) {
|
|
50765
|
+
if (decl.type?.name) continue;
|
|
50766
|
+
const inferred = infer_const_expr_type(decl.value, known, status);
|
|
50767
|
+
if (!inferred) continue;
|
|
50768
|
+
decl.type = inferred;
|
|
50769
|
+
known.set(decl.name, inferred);
|
|
50770
|
+
progress = true;
|
|
50771
|
+
}
|
|
50772
|
+
}
|
|
50773
|
+
}
|
|
50774
|
+
function infer_const_expr_type(node, known, status) {
|
|
50775
|
+
if (!node) return void 0;
|
|
50776
|
+
if (node.node_type === "grouped") return infer_const_expr_type(node.value, known, status);
|
|
50777
|
+
if (node.node_type === "value") {
|
|
50778
|
+
const raw = node.value;
|
|
50779
|
+
if (typeof raw !== "string") return void 0;
|
|
50780
|
+
if (raw.startsWith("\"")) return new Type("string", true);
|
|
50781
|
+
if (raw === "true" || raw === "false") return new Type("bool", true);
|
|
50782
|
+
if (raw.startsWith("'") && raw.endsWith("'")) return new Type("char", true);
|
|
50783
|
+
if (is_int_literal(raw)) return new Type("int", true);
|
|
50784
|
+
if (/^(\+|-)*\d+.\d+([eE](\+|-)?\d+)?$/.test(raw)) return new Type("float", true);
|
|
50785
|
+
const declared = known.get(raw);
|
|
50786
|
+
if (declared) return declared;
|
|
50787
|
+
return;
|
|
50788
|
+
}
|
|
50789
|
+
if (node.node_type === "op") return infer_const_expr_type(node.left_value, known, status);
|
|
50790
|
+
if (node.node_type === "func_call") {
|
|
50791
|
+
const call = node;
|
|
50792
|
+
const target = status.structs.find((s) => s.name === call.name && !s.is_simple_type);
|
|
50793
|
+
if (target && target.type_params.length === 0) return new Type(target.name);
|
|
50794
|
+
return;
|
|
50795
|
+
}
|
|
50796
|
+
}
|
|
50797
|
+
/**
|
|
50040
50798
|
* A bare statement that constructs (or obtains) a class instance without
|
|
50041
50799
|
* binding it to a variable would leak: classes are heap-allocated and freed
|
|
50042
50800
|
* by the owning variable's scope-exit cleanup, so an unbound instance is
|
|
@@ -50301,8 +51059,8 @@ function check_function_parameter_node(param, status, is_library_scope = false)
|
|
|
50301
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);
|
|
50302
51060
|
instantiate_generic_type(param.type, status);
|
|
50303
51061
|
}
|
|
50304
|
-
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);
|
|
50305
|
-
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);
|
|
50306
51064
|
if (param.is_variadic && param.default_value) {
|
|
50307
51065
|
add_error(status, `Variadic parameter cannot have a default value`, param.start);
|
|
50308
51066
|
return;
|
|
@@ -50314,7 +51072,7 @@ function check_function_parameter_node(param, status, is_library_scope = false)
|
|
|
50314
51072
|
}
|
|
50315
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);
|
|
50316
51074
|
status.values.push({
|
|
50317
|
-
declaration: param.declaration,
|
|
51075
|
+
declaration: param.declaration === "var" || is_pass_by_value_owning_struct(param.type, status) ? "var" : param.declaration,
|
|
50318
51076
|
name: param.name,
|
|
50319
51077
|
type: param.type,
|
|
50320
51078
|
is_set: true,
|
|
@@ -50374,6 +51132,7 @@ function clone_status(status) {
|
|
|
50374
51132
|
var_name_counter: status.var_name_counter,
|
|
50375
51133
|
type_params: status.type_params,
|
|
50376
51134
|
errors: status.errors,
|
|
51135
|
+
warnings: status.warnings,
|
|
50377
51136
|
buffer_caps: status.buffer_caps,
|
|
50378
51137
|
equal_lengths: status.equal_lengths?.slice(),
|
|
50379
51138
|
mutated_local_names: status.mutated_local_names,
|
|
@@ -50730,7 +51489,7 @@ function check_function_call_node(node, status) {
|
|
|
50730
51489
|
return false;
|
|
50731
51490
|
}
|
|
50732
51491
|
if (magic_ctor && spawn_ctor_shape) return check_magic_ctor(node, status, magic_ctor);
|
|
50733
|
-
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);
|
|
50734
51493
|
if (func?.is_closure && func.captures?.length) func = void 0;
|
|
50735
51494
|
if (!func) {
|
|
50736
51495
|
const struct = resolve_declared_struct(node.name, status);
|
|
@@ -50799,6 +51558,10 @@ function check_function_call_node(node, status) {
|
|
|
50799
51558
|
const param_index = status.values.findLastIndex((v) => v.name === node.name);
|
|
50800
51559
|
const param_value = param_index >= 0 ? status.values[param_index] : void 0;
|
|
50801
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
|
+
}
|
|
50802
51565
|
if (status.moved_variables?.has(node.name)) {
|
|
50803
51566
|
add_error(status, `Variable '${node.name}' used after move`, node.start);
|
|
50804
51567
|
return false;
|
|
@@ -51027,6 +51790,30 @@ function monomorphize(generic_struct, type_args, status) {
|
|
|
51027
51790
|
const init_func = new FunctionNode(generic_struct.start, "pub", "#init", init_return_type, init_params);
|
|
51028
51791
|
mono_struct.functions.push(init_func);
|
|
51029
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
|
+
}
|
|
51030
51817
|
const root = status.stack[0];
|
|
51031
51818
|
if (!root.statements.some((s) => s.node_type === "struct" && s.name === mono_name)) root.statements.push(mono_struct);
|
|
51032
51819
|
return mono_struct;
|
|
@@ -51530,6 +52317,8 @@ function check_magic_ctor(node, status, name) {
|
|
|
51530
52317
|
add_error(status, `Spawned call '${call.name}' did not resolve`, node.start);
|
|
51531
52318
|
return false;
|
|
51532
52319
|
}
|
|
52320
|
+
const spawned = call.resolved_function;
|
|
52321
|
+
if (spawned) spawned.address_escaped = true;
|
|
51533
52322
|
validate_spawn_args_sendable(call, status);
|
|
51534
52323
|
stamp_spawn_ctor(node, name);
|
|
51535
52324
|
const return_type = call.type;
|
|
@@ -52474,6 +53263,143 @@ function is_visible(declaring_scope, visibility, access_scope, stack, declaring_
|
|
|
52474
53263
|
return access_scope === declaring_scope;
|
|
52475
53264
|
}
|
|
52476
53265
|
//#endregion
|
|
53266
|
+
//#region ../src/check/utils/regex_pattern_lint.ts
|
|
53267
|
+
/**
|
|
53268
|
+
* Compile-time lint for Regex pattern literals (FOLLOWUP "Regex pattern
|
|
53269
|
+
* escapes").
|
|
53270
|
+
*
|
|
53271
|
+
* The Regex engine (core/System/Text/Regex.nm) recognizes only the
|
|
53272
|
+
* shorthands (`\d \D \w \W \s \S`), backreferences (`\1`-`\9`), the control
|
|
53273
|
+
* escapes (`\n \r \t`), and escaped metacharacters. Any OTHER engine escape
|
|
53274
|
+
— most notably a typo like `\z` — silently matches a literal letter. When a
|
|
53275
|
+
* Regex entry point's pattern argument is a static string literal, the check
|
|
53276
|
+
* can flag those as probable typos; dynamic runtime patterns can't be
|
|
53277
|
+
* validated this way.
|
|
53278
|
+
*
|
|
53279
|
+
* The literal arrives RAW (quotes included, Nomen escapes unresolved), so it
|
|
53280
|
+
* must first be decoded to the engine-visible bytes: a Nomen `"\n"` is a real
|
|
53281
|
+
* LF byte the engine treats as a literal, while `"\n"` reaches the engine as
|
|
53282
|
+
* a backslash + 'n' escape. Only the second spelling can carry a typo.
|
|
53283
|
+
*/
|
|
53284
|
+
/** Decode a raw string-literal token (quotes included) to its engine-visible
|
|
53285
|
+
* bytes, resolving Nomen's own string escapes. Mirrors the pair-counting of
|
|
53286
|
+
* string_literal_length and the decode rules of decode_char_literal. */
|
|
53287
|
+
function decode_string_literal_bytes(raw) {
|
|
53288
|
+
const bytes = [];
|
|
53289
|
+
let i = 1;
|
|
53290
|
+
const end = raw.endsWith("\"") ? raw.length - 1 : raw.length;
|
|
53291
|
+
while (i < end) {
|
|
53292
|
+
if (raw[i] !== "\\") {
|
|
53293
|
+
const cp = raw.codePointAt(i) ?? 0;
|
|
53294
|
+
if (cp < 128) {
|
|
53295
|
+
bytes.push(cp);
|
|
53296
|
+
i += 1;
|
|
53297
|
+
} else if (cp < 2048) {
|
|
53298
|
+
bytes.push(192 | cp >> 6, 128 | cp & 63);
|
|
53299
|
+
i += 1;
|
|
53300
|
+
} else if (cp < 65536) {
|
|
53301
|
+
bytes.push(224 | cp >> 12, 128 | cp >> 6 & 63, 128 | cp & 63);
|
|
53302
|
+
i += 1;
|
|
53303
|
+
} else {
|
|
53304
|
+
bytes.push(240 | cp >> 18, 128 | cp >> 12 & 63, 128 | cp >> 6 & 63, 128 | cp & 63);
|
|
53305
|
+
i += 2;
|
|
53306
|
+
}
|
|
53307
|
+
continue;
|
|
53308
|
+
}
|
|
53309
|
+
const next = raw[i + 1];
|
|
53310
|
+
if (next === void 0) break;
|
|
53311
|
+
if (next === "x") {
|
|
53312
|
+
let j = i + 2;
|
|
53313
|
+
let value = 0;
|
|
53314
|
+
let digits = 0;
|
|
53315
|
+
while (j < end && digits < 2 && is_hex_digit(raw[j])) {
|
|
53316
|
+
value = value * 16 + hex_value(raw[j]);
|
|
53317
|
+
j += 1;
|
|
53318
|
+
digits += 1;
|
|
53319
|
+
}
|
|
53320
|
+
if (digits > 0) {
|
|
53321
|
+
bytes.push(value & 255);
|
|
53322
|
+
i = j;
|
|
53323
|
+
continue;
|
|
53324
|
+
}
|
|
53325
|
+
i += 2;
|
|
53326
|
+
continue;
|
|
53327
|
+
}
|
|
53328
|
+
if (next >= "0" && next <= "7") {
|
|
53329
|
+
let j = i + 1;
|
|
53330
|
+
let value = 0;
|
|
53331
|
+
let digits = 0;
|
|
53332
|
+
while (j < end && digits < 3 && raw[j] >= "0" && raw[j] <= "7") {
|
|
53333
|
+
value = value * 8 + (raw[j].charCodeAt(0) - 48);
|
|
53334
|
+
j += 1;
|
|
53335
|
+
digits += 1;
|
|
53336
|
+
}
|
|
53337
|
+
bytes.push(value & 255);
|
|
53338
|
+
i = j;
|
|
53339
|
+
continue;
|
|
53340
|
+
}
|
|
53341
|
+
switch (next) {
|
|
53342
|
+
case "\\":
|
|
53343
|
+
bytes.push(92);
|
|
53344
|
+
break;
|
|
53345
|
+
case "n":
|
|
53346
|
+
bytes.push(10);
|
|
53347
|
+
break;
|
|
53348
|
+
case "t":
|
|
53349
|
+
bytes.push(9);
|
|
53350
|
+
break;
|
|
53351
|
+
case "r":
|
|
53352
|
+
bytes.push(13);
|
|
53353
|
+
break;
|
|
53354
|
+
case "0":
|
|
53355
|
+
bytes.push(0);
|
|
53356
|
+
break;
|
|
53357
|
+
case "\"":
|
|
53358
|
+
bytes.push(34);
|
|
53359
|
+
break;
|
|
53360
|
+
case "'":
|
|
53361
|
+
bytes.push(39);
|
|
53362
|
+
break;
|
|
53363
|
+
default: bytes.push(next.charCodeAt(0));
|
|
53364
|
+
}
|
|
53365
|
+
i += 2;
|
|
53366
|
+
}
|
|
53367
|
+
return bytes;
|
|
53368
|
+
}
|
|
53369
|
+
function is_hex_digit(c) {
|
|
53370
|
+
return c >= "0" && c <= "9" || c >= "a" && c <= "f" || c >= "A" && c <= "F";
|
|
53371
|
+
}
|
|
53372
|
+
function hex_value(c) {
|
|
53373
|
+
if (c <= "9") return c.charCodeAt(0) - 48;
|
|
53374
|
+
if (c <= "F") return c.charCodeAt(0) - 55;
|
|
53375
|
+
return c.charCodeAt(0) - 87;
|
|
53376
|
+
}
|
|
53377
|
+
function is_ascii_letter(code) {
|
|
53378
|
+
return code >= 65 && code <= 90 || code >= 97 && code <= 122;
|
|
53379
|
+
}
|
|
53380
|
+
/** Scan the decoded pattern bytes for escapes outside the engine's known set
|
|
53381
|
+
* (`\d \D \w \W \s \S`, `\1`-`\9`, `\n \r \t`, escaped punctuation) and
|
|
53382
|
+
* return one warning message per probable typo. */
|
|
53383
|
+
function lint_regex_pattern(raw) {
|
|
53384
|
+
const bytes = decode_string_literal_bytes(raw);
|
|
53385
|
+
const messages = [];
|
|
53386
|
+
let i = 0;
|
|
53387
|
+
while (i < bytes.length) {
|
|
53388
|
+
if (bytes[i] !== 92) {
|
|
53389
|
+
i += 1;
|
|
53390
|
+
continue;
|
|
53391
|
+
}
|
|
53392
|
+
const escaped = bytes[i + 1];
|
|
53393
|
+
if (escaped === void 0) break;
|
|
53394
|
+
if (!(escaped === 100 || escaped === 68 || escaped === 119 || escaped === 87 || escaped === 115 || escaped === 83 || escaped === 110 || escaped === 114 || escaped === 116 || escaped >= 49 && escaped <= 57) && is_ascii_letter(escaped)) {
|
|
53395
|
+
const letter = String.fromCharCode(escaped);
|
|
53396
|
+
messages.push(`Regex pattern: unsupported escape '\\${letter}' matches a literal '${letter}' — supported escapes are \\d \\D \\w \\W \\s \\S \\1-\\9 \\n \\r \\t and escaped punctuation`);
|
|
53397
|
+
}
|
|
53398
|
+
i += 2;
|
|
53399
|
+
}
|
|
53400
|
+
return messages;
|
|
53401
|
+
}
|
|
53402
|
+
//#endregion
|
|
52477
53403
|
//#region ../src/check/utils/string_mutation_scan.ts
|
|
52478
53404
|
/**
|
|
52479
53405
|
* Interprocedural no-mutation scan for borrow-position `to_string()` elision
|
|
@@ -52867,6 +53793,7 @@ function is_inside_core_method(status) {
|
|
|
52867
53793
|
}
|
|
52868
53794
|
function check_function_call(node, status, func, target_type, self_value, self_path) {
|
|
52869
53795
|
set_resolved_function(node, func);
|
|
53796
|
+
lint_regex_entry_pattern(node, func, status);
|
|
52870
53797
|
const access_scope = status.stack.at(-1);
|
|
52871
53798
|
if (func.name === "#init") {
|
|
52872
53799
|
const struct_name = target_type?.name || func.return_type.name;
|
|
@@ -52978,6 +53905,13 @@ function check_function_call(node, status, func, target_type, self_value, self_p
|
|
|
52978
53905
|
const param_type = type_from_value_node(param, status);
|
|
52979
53906
|
const param_value = value_from_value_node(param);
|
|
52980
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
|
+
}
|
|
52981
53915
|
const has_mov_keyword = node.move_param_indices?.includes(i) ?? false;
|
|
52982
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);
|
|
52983
53917
|
if (func_param.type.is_ref && !has_ref_keyword) add_error(status, `Missing 'ref' keyword for ref parameter '${func_param.name}'`, param.start);
|
|
@@ -53002,12 +53936,13 @@ function check_function_call(node, status, func, target_type, self_value, self_p
|
|
|
53002
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);
|
|
53003
53937
|
}
|
|
53004
53938
|
const param_is_class = func_param.type.name && is_class_type$1(func_param.type.name, status);
|
|
53005
|
-
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);
|
|
53006
53941
|
const arg_is_owned_value = param.node_type === "value" && borrow_depth_of(param, status) === void 0;
|
|
53007
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);
|
|
53008
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);
|
|
53009
53944
|
if (has_mov_keyword && param_value && !node.swap_params?.has(i)) {
|
|
53010
|
-
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))) {
|
|
53011
53946
|
if (!status.moved_variables) status.moved_variables = /* @__PURE__ */ new Set();
|
|
53012
53947
|
status.moved_variables.add(param_value);
|
|
53013
53948
|
}
|
|
@@ -53018,7 +53953,7 @@ function check_function_call(node, status, func, target_type, self_value, self_p
|
|
|
53018
53953
|
if (field_type.name && is_class_type$1(field_type.name, status)) {
|
|
53019
53954
|
const field_name = access.access.name;
|
|
53020
53955
|
add_error(status, `cannot move '${field_name}' out of struct — struct owns its class fields`, param.start);
|
|
53021
|
-
} 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))) {
|
|
53022
53957
|
const field_name = access.access.name;
|
|
53023
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);
|
|
53024
53959
|
}
|
|
@@ -53378,6 +54313,59 @@ function receiver_is_const(target_type, self_value, status) {
|
|
|
53378
54313
|
const binding = status.values.findLast((v) => v.name === self_value);
|
|
53379
54314
|
return !!binding && binding.declaration === "const" && !binding.type.is_ref;
|
|
53380
54315
|
}
|
|
54316
|
+
/**
|
|
54317
|
+
* Regex entry points whose FIRST parameter is the pattern. When that argument
|
|
54318
|
+
* is a static string literal, flag engine escapes outside the known set as
|
|
54319
|
+
* probable typos (see regex_pattern_lint.ts) — a dynamic runtime pattern
|
|
54320
|
+
* can't be validated this way.
|
|
54321
|
+
*/
|
|
54322
|
+
const REGEX_PATTERN_ENTRY_POINTS = /* @__PURE__ */ new Set([
|
|
54323
|
+
"test",
|
|
54324
|
+
"match",
|
|
54325
|
+
"count",
|
|
54326
|
+
"find",
|
|
54327
|
+
"replace_all",
|
|
54328
|
+
"captures",
|
|
54329
|
+
"test_ci",
|
|
54330
|
+
"match_ci",
|
|
54331
|
+
"find_ci",
|
|
54332
|
+
"captures_ci",
|
|
54333
|
+
"replace_all_ci"
|
|
54334
|
+
]);
|
|
54335
|
+
/**
|
|
54336
|
+
* Lint a static string-literal pattern passed to a Regex entry point. Only
|
|
54337
|
+
* the library's own `Regex` struct qualifies (a user struct of the same name
|
|
54338
|
+
* with a same-named method is left alone), and only when the first argument
|
|
54339
|
+
* is a literal — anything else is a runtime value.
|
|
54340
|
+
*/
|
|
54341
|
+
function lint_regex_entry_pattern(node, func, status) {
|
|
54342
|
+
if (!is_library_regex_func(func, status)) return;
|
|
54343
|
+
if (!REGEX_PATTERN_ENTRY_POINTS.has(func.name)) return;
|
|
54344
|
+
const pattern = node.params[0];
|
|
54345
|
+
if (!pattern || pattern.node_type !== "value") return;
|
|
54346
|
+
const raw = pattern.value;
|
|
54347
|
+
if (!raw.startsWith("\"")) return;
|
|
54348
|
+
if (!status.warnings) status.warnings = [];
|
|
54349
|
+
for (const message of lint_regex_pattern(raw)) status.warnings.push({
|
|
54350
|
+
message,
|
|
54351
|
+
start: pattern.start,
|
|
54352
|
+
line: 0,
|
|
54353
|
+
column: 0
|
|
54354
|
+
});
|
|
54355
|
+
}
|
|
54356
|
+
/**
|
|
54357
|
+
* Whether `func` is a method of the library's `Regex` struct. Two paths: the
|
|
54358
|
+
* method's enclosing struct is stamped on it for calls checked after the
|
|
54359
|
+
* struct gather (library bodies, later files), and for user call sites —
|
|
54360
|
+
* which are checked BEFORE the gather stamps `func.scope` — the callee is
|
|
54361
|
+
* matched against the gathered library struct's own methods.
|
|
54362
|
+
*/
|
|
54363
|
+
function is_library_regex_func(func, status) {
|
|
54364
|
+
const scope = func.scope;
|
|
54365
|
+
if (scope && scope.node_type === "struct" && scope.is_library && scope.name === "Regex") return true;
|
|
54366
|
+
const regex_struct = status.structs.find((s) => s.is_library && s.name === "Regex");
|
|
54367
|
+
return !!regex_struct && regex_struct.functions.includes(func);
|
|
54368
|
+
}
|
|
53381
54369
|
//#endregion
|
|
53382
54370
|
//#region ../src/check/check_access_node.ts
|
|
53383
54371
|
function check_access_node(node, status) {
|
|
@@ -54338,14 +55326,17 @@ function check_assignment_node(assign, status) {
|
|
|
54338
55326
|
return_type: fd?.func_return_type
|
|
54339
55327
|
};
|
|
54340
55328
|
}
|
|
54341
|
-
} else if (lhs_value?.func_params
|
|
55329
|
+
} else if (lhs_value?.func_params !== void 0) lambda_signature = {
|
|
54342
55330
|
params: lhs_value.func_params,
|
|
54343
55331
|
return_type: lhs_value.func_return_type
|
|
54344
55332
|
};
|
|
54345
|
-
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
|
+
}
|
|
54346
55338
|
const rhs_func = assign.right_value;
|
|
54347
|
-
if (assign.left_value.node_type === "value") rhs_func
|
|
54348
|
-
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);
|
|
54349
55340
|
if (rhs_func.params.length === lambda_signature.params.length) {
|
|
54350
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;
|
|
54351
55342
|
}
|
|
@@ -55577,6 +56568,8 @@ function check_spawn_node(node, status) {
|
|
|
55577
56568
|
add_error(status, "Spawned call did not resolve", node.start);
|
|
55578
56569
|
return false;
|
|
55579
56570
|
}
|
|
56571
|
+
const spawned = node.call.resolved_function;
|
|
56572
|
+
if (spawned) spawned.address_escaped = true;
|
|
55580
56573
|
for (const param of node.call.params) {
|
|
55581
56574
|
let arg_type = type_from_value_node(param, status);
|
|
55582
56575
|
const stamped = param.type;
|
|
@@ -56394,13 +57387,14 @@ function parse_type(status) {
|
|
|
56394
57387
|
type.type_args = void 0;
|
|
56395
57388
|
}
|
|
56396
57389
|
if (type.name === "Func" && type.type_args?.length) {
|
|
56397
|
-
if (type.
|
|
56398
|
-
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);
|
|
56399
57392
|
return type;
|
|
56400
57393
|
}
|
|
56401
57394
|
const args = type.type_args;
|
|
56402
57395
|
const func_type = new Type("func");
|
|
56403
57396
|
func_type.start = type.start;
|
|
57397
|
+
if (type.is_nullable) func_type.is_nullable = true;
|
|
56404
57398
|
func_type.func_params = [];
|
|
56405
57399
|
for (let i = 0; i < args.length - 1; i++) {
|
|
56406
57400
|
if (args[i].name === "void") {
|
|
@@ -56513,10 +57507,14 @@ function default_visibility(status) {
|
|
|
56513
57507
|
* - a func RETURN carries its signature on the Type
|
|
56514
57508
|
* (`type.func_params` / `type.func_return_type`).
|
|
56515
57509
|
*/
|
|
56516
|
-
/** Whether the current token starts a nested func type: `func`
|
|
56517
|
-
* 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). */
|
|
56518
57513
|
function at_func_type(status) {
|
|
56519
|
-
|
|
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 === "(";
|
|
56520
57518
|
}
|
|
56521
57519
|
/**
|
|
56522
57520
|
* Reject `void` where it has no meaning. Only the last type argument of
|
|
@@ -56537,6 +57535,7 @@ function reject_void_type(type, status, at) {
|
|
|
56537
57535
|
function adopt_func_param_type(param, type) {
|
|
56538
57536
|
if (type.name !== "func") return false;
|
|
56539
57537
|
param.type = new Type("func");
|
|
57538
|
+
param.type.is_nullable = type.is_nullable;
|
|
56540
57539
|
param.func_params = type.func_params;
|
|
56541
57540
|
param.func_return_type = type.func_return_type;
|
|
56542
57541
|
return true;
|
|
@@ -56559,11 +57558,13 @@ function parse_func_type_signature(param, status) {
|
|
|
56559
57558
|
}
|
|
56560
57559
|
/**
|
|
56561
57560
|
* Parse a full nested func type — the `func` word is consumed by the caller
|
|
56562
|
-
* (at_func_type confirmed the `(` follows) — and return
|
|
56563
|
-
* 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`).
|
|
56564
57564
|
*/
|
|
56565
57565
|
function parse_func_type(status) {
|
|
56566
57566
|
const type = new Type("func");
|
|
57567
|
+
if (accept("?", status)) type.is_nullable = true;
|
|
56567
57568
|
const param = new ParameterNode(get_index(status), "");
|
|
56568
57569
|
parse_func_type_signature(param, status);
|
|
56569
57570
|
type.func_params = param.func_params;
|
|
@@ -56603,6 +57604,7 @@ function parse_signature_params(params, status) {
|
|
|
56603
57604
|
if (at_func_type(status)) {
|
|
56604
57605
|
consume(status);
|
|
56605
57606
|
param.type = new Type("func");
|
|
57607
|
+
if (accept("?", status)) param.type.is_nullable = true;
|
|
56606
57608
|
parse_func_type_signature(param, status);
|
|
56607
57609
|
} else {
|
|
56608
57610
|
param.type = parse_type(status);
|
|
@@ -56643,7 +57645,11 @@ function parse_declaration(visibility, declaration, status, is_readonly = false)
|
|
|
56643
57645
|
if (is_func_alias) {
|
|
56644
57646
|
decl.func_params = decl.type.func_params ?? [];
|
|
56645
57647
|
decl.func_return_type = decl.type.func_return_type;
|
|
56646
|
-
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("");
|
|
56647
57653
|
}
|
|
56648
57654
|
if (peek_current(status) === "=" || status.i >= status.tokens.length) {
|
|
56649
57655
|
const modifier = status.tokens[saved_i]?.value;
|
|
@@ -56705,6 +57711,7 @@ function parse_declaration(visibility, declaration, status, is_readonly = false)
|
|
|
56705
57711
|
}
|
|
56706
57712
|
function parse_function_type_declaration(decl, status) {
|
|
56707
57713
|
accept("func", status);
|
|
57714
|
+
const func_nullable = accept("?", status);
|
|
56708
57715
|
if (expect("(", status)) {
|
|
56709
57716
|
const params = [];
|
|
56710
57717
|
let return_type;
|
|
@@ -56717,6 +57724,10 @@ function parse_function_type_declaration(decl, status) {
|
|
|
56717
57724
|
decl.name = consume_name(status);
|
|
56718
57725
|
decl.func_params = params.filter((p) => !p.type.is_return_type);
|
|
56719
57726
|
decl.func_return_type = return_type;
|
|
57727
|
+
if (func_nullable) {
|
|
57728
|
+
decl.type = new Type("func");
|
|
57729
|
+
decl.type.is_nullable = true;
|
|
57730
|
+
}
|
|
56720
57731
|
const has_equals = accept("=", status);
|
|
56721
57732
|
if (peek_current(status) === "{") {
|
|
56722
57733
|
const func = new FunctionNode(get_index(status), default_visibility(status), decl.name, new Type(""));
|
|
@@ -56816,6 +57827,7 @@ function parse_anon_function_parameter(func, status) {
|
|
|
56816
57827
|
if (at_func_type(status)) {
|
|
56817
57828
|
consume(status);
|
|
56818
57829
|
param.type = new Type("func");
|
|
57830
|
+
if (accept("?", status)) param.type.is_nullable = true;
|
|
56819
57831
|
parse_func_type_signature(param, status);
|
|
56820
57832
|
param.name_start = get_index(status);
|
|
56821
57833
|
param.name = consume_name(status);
|
|
@@ -57749,6 +58761,7 @@ function parse_function_parameter(parent, func, status) {
|
|
|
57749
58761
|
if (at_func_type(status)) {
|
|
57750
58762
|
consume(status);
|
|
57751
58763
|
param.type = new Type("func");
|
|
58764
|
+
if (accept("?", status)) param.type.is_nullable = true;
|
|
57752
58765
|
parse_func_type_signature(param, status);
|
|
57753
58766
|
param.name_start = get_index(status);
|
|
57754
58767
|
param.name = consume_name(status);
|
|
@@ -59107,7 +60120,8 @@ nomen test
|
|
|
59107
60120
|
function find_bundled(filename) {
|
|
59108
60121
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
59109
60122
|
const cli_root = path.dirname(here);
|
|
59110
|
-
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)];
|
|
59111
60125
|
for (const c of candidates) if (fs.existsSync(c)) return path.resolve(c);
|
|
59112
60126
|
}
|
|
59113
60127
|
/**
|
|
@@ -59329,6 +60343,10 @@ function parse_records(stdout) {
|
|
|
59329
60343
|
other
|
|
59330
60344
|
};
|
|
59331
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
|
+
}
|
|
59332
60350
|
/**
|
|
59333
60351
|
* Compile one `*.test.nm` file (with the generated harness) and run it,
|
|
59334
60352
|
* returning its parsed records and any crash info.
|
|
@@ -59344,6 +60362,7 @@ function run_test_file(entry_path, lib_path, arch, audit = false, audit_runtime)
|
|
|
59344
60362
|
fails: [],
|
|
59345
60363
|
benches: [],
|
|
59346
60364
|
other: [],
|
|
60365
|
+
leaks: [],
|
|
59347
60366
|
ms: 0
|
|
59348
60367
|
};
|
|
59349
60368
|
const resolved = path.resolve(entry_path);
|
|
@@ -59352,6 +60371,7 @@ function run_test_file(entry_path, lib_path, arch, audit = false, audit_runtime)
|
|
|
59352
60371
|
const parsed = parse(input + "\n" + harness, library, resolved);
|
|
59353
60372
|
if (parsed.errors.length) {
|
|
59354
60373
|
result.ok = false;
|
|
60374
|
+
result.phase = "parse";
|
|
59355
60375
|
result.crashed = parsed.errors.map((e) => `${e.message} (${e.line ?? "?"}:${e.column ?? "?"})`).join("\n");
|
|
59356
60376
|
result.ms = performance.now() - start;
|
|
59357
60377
|
return result;
|
|
@@ -59362,6 +60382,7 @@ function run_test_file(entry_path, lib_path, arch, audit = false, audit_runtime)
|
|
|
59362
60382
|
});
|
|
59363
60383
|
if (buildResult.errors && buildResult.errors.length) {
|
|
59364
60384
|
result.ok = false;
|
|
60385
|
+
result.phase = "build";
|
|
59365
60386
|
result.crashed = buildResult.errors.map((e) => `${e.message} (${e.line ?? "?"}:${e.column ?? "?"})`).join("\n");
|
|
59366
60387
|
result.ms = performance.now() - start;
|
|
59367
60388
|
return result;
|
|
@@ -59373,6 +60394,7 @@ function run_test_file(entry_path, lib_path, arch, audit = false, audit_runtime)
|
|
|
59373
60394
|
const runtime_src = audit_runtime ? path.resolve(audit_runtime) : find_audit_runtime(path.dirname(resolved));
|
|
59374
60395
|
if (!runtime_src || !fs.existsSync(runtime_src)) {
|
|
59375
60396
|
result.ok = false;
|
|
60397
|
+
result.phase = "setup";
|
|
59376
60398
|
result.crashed = "Audit enabled but audit_runtime.c was not found. Pass --audit-runtime <path>.";
|
|
59377
60399
|
result.ms = performance.now() - start;
|
|
59378
60400
|
return result;
|
|
@@ -59410,6 +60432,7 @@ function run_test_file(entry_path, lib_path, arch, audit = false, audit_runtime)
|
|
|
59410
60432
|
} catch (err) {
|
|
59411
60433
|
const stderr = err.stderr ? err.stderr.toString() : err.message ?? "";
|
|
59412
60434
|
result.ok = false;
|
|
60435
|
+
result.phase = "link";
|
|
59413
60436
|
result.crashed = `link failed: ${stderr.trim() || "clang error"}`;
|
|
59414
60437
|
result.ms = performance.now() - start;
|
|
59415
60438
|
return result;
|
|
@@ -59437,8 +60460,11 @@ function run_test_file(entry_path, lib_path, arch, audit = false, audit_runtime)
|
|
|
59437
60460
|
result.fails = records.fails;
|
|
59438
60461
|
result.benches = records.benches;
|
|
59439
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:"));
|
|
59440
60465
|
result.crashed = crashed;
|
|
59441
|
-
|
|
60466
|
+
if (crashed) result.phase = "run";
|
|
60467
|
+
result.ok = !(result.fails.length > 0 || result.tests.some((t) => t.failed > 0)) && crashed === void 0 && result.leaks.length === 0;
|
|
59442
60468
|
result.ms = performance.now() - start;
|
|
59443
60469
|
return result;
|
|
59444
60470
|
}
|
|
@@ -59465,7 +60491,8 @@ const C = {
|
|
|
59465
60491
|
function report_file(result) {
|
|
59466
60492
|
const rel = path.relative(process.cwd(), result.file);
|
|
59467
60493
|
if (result.crashed && result.tests.length === 0 && result.fails.length === 0) {
|
|
59468
|
-
|
|
60494
|
+
const label = result.phase === "run" ? "(crashed before records)" : result.phase === "link" ? "(link failed)" : result.phase === "setup" ? "(setup failed)" : "(failed to build)";
|
|
60495
|
+
console.log(` ${C.red("✗")} ${rel} ${C.red(label)}`);
|
|
59469
60496
|
console.log(C.red(result.crashed));
|
|
59470
60497
|
return;
|
|
59471
60498
|
}
|
|
@@ -59474,6 +60501,7 @@ function report_file(result) {
|
|
|
59474
60501
|
console.log(` ${mark} ${rel} ${C.dim(`(${totalTests} tests)`)} ${C.dim(format_duration(result.ms))}`);
|
|
59475
60502
|
if (result.crashed) console.log(C.red(` ${result.crashed}`));
|
|
59476
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)}`);
|
|
59477
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))}`);
|
|
59478
60506
|
if (result.other.length) {
|
|
59479
60507
|
console.log(C.dim(" --- test stdout ---"));
|
|
@@ -59515,10 +60543,12 @@ function runTests(root, options = {}) {
|
|
|
59515
60543
|
const totalTests = results.reduce((a, r) => a + r.tests.length, 0);
|
|
59516
60544
|
const totalFailed = results.reduce((a, r) => a + r.tests.reduce((x, t) => x + t.failed, 0), 0);
|
|
59517
60545
|
const totalPassed = totalTests - totalFailed;
|
|
60546
|
+
const totalLeaks = results.reduce((a, r) => a + r.leaks.length, 0);
|
|
59518
60547
|
const anyFailed = results.some((r) => !r.ok);
|
|
59519
60548
|
console.log("");
|
|
59520
60549
|
console.log(` ${C.dim("Files ")} ${C.green(`${totalFilesPassed} passed`)}` + (totalFilesFailed ? ` ${C.dim("|")} ${C.red(`${totalFilesFailed} failed`)}` : "") + C.dim(` (${totalFiles})`));
|
|
59521
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)"));
|
|
59522
60552
|
console.log(` ${C.dim(" Time ")} ${format_duration(elapsed)}`);
|
|
59523
60553
|
console.log("");
|
|
59524
60554
|
return !anyFailed;
|
|
@@ -59738,7 +60768,7 @@ function processFolder(folder, config, mode, program_args) {
|
|
|
59738
60768
|
const dir = fs.opendirSync(folder);
|
|
59739
60769
|
let dirent;
|
|
59740
60770
|
while ((dirent = dir.readSync()) !== null) if (shouldProcessFile(dirent.name)) {
|
|
59741
|
-
processFile(path.join(folder, dirent.name), config, mode, program_args
|
|
60771
|
+
processFile(path.join(folder, dirent.name), config, mode, program_args);
|
|
59742
60772
|
fs.watch;
|
|
59743
60773
|
}
|
|
59744
60774
|
dir.closeSync();
|
|
@@ -59746,45 +60776,15 @@ function processFolder(folder, config, mode, program_args) {
|
|
|
59746
60776
|
function shouldProcessFile(filename) {
|
|
59747
60777
|
return path.extname(filename) === SUPPORTED_EXTENSION;
|
|
59748
60778
|
}
|
|
59749
|
-
|
|
59750
|
-
* The project's entry file for `file_path`: walk up to the nearest
|
|
59751
|
-
* `package.jsonc` and resolve its `entry` field, so any file inside a project
|
|
59752
|
-
* is checked with the entry's import context (`import types` resolves
|
|
59753
|
-
* against the project's `src/`, not the file's own folder). Returns
|
|
59754
|
-
* undefined when the file is standalone, IS the entry, sits in an
|
|
59755
|
-
* entry-less project (e.g. a library checkout), or the config is malformed.
|
|
59756
|
-
*/
|
|
59757
|
-
function project_entry_for(file_path) {
|
|
59758
|
-
let dir = path.dirname(file_path);
|
|
59759
|
-
for (let i = 0; i < 20; i++) {
|
|
59760
|
-
const config_path = path.join(dir, "package.jsonc");
|
|
59761
|
-
if (fs.existsSync(config_path)) {
|
|
59762
|
-
try {
|
|
59763
|
-
const json = fs.readFileSync(config_path, "utf8").replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
59764
|
-
const parsed = JSON.parse(json);
|
|
59765
|
-
if (parsed.entry) {
|
|
59766
|
-
const entry = path.resolve(dir, parsed.entry);
|
|
59767
|
-
if (entry !== file_path && fs.existsSync(entry)) return entry;
|
|
59768
|
-
}
|
|
59769
|
-
} catch {}
|
|
59770
|
-
return;
|
|
59771
|
-
}
|
|
59772
|
-
const parent = path.dirname(dir);
|
|
59773
|
-
if (parent === dir) return void 0;
|
|
59774
|
-
dir = parent;
|
|
59775
|
-
}
|
|
59776
|
-
}
|
|
59777
|
-
function processFile(filename, config, mode, program_args, anchor_to_project = true) {
|
|
60779
|
+
function processFile(filename, config, mode, program_args) {
|
|
59778
60780
|
console.log("Processing", filename);
|
|
59779
60781
|
const arch = config.arch || "aarch64";
|
|
59780
60782
|
const platform = config.platform || default_platform();
|
|
59781
60783
|
const resolved = path.resolve(filename);
|
|
59782
60784
|
if (!config.lib) config.lib = resolve_lib(resolved);
|
|
59783
|
-
const anchor = anchor_to_project && !resolved.endsWith(".test.nm") ? project_entry_for(resolved) ?? resolved : resolved;
|
|
59784
|
-
if (anchor !== resolved) console.log("Anchored to project entry", anchor);
|
|
59785
60785
|
let startTime = performance.now();
|
|
59786
|
-
const input = join$1(
|
|
59787
|
-
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);
|
|
59788
60788
|
let errors = parsed.errors;
|
|
59789
60789
|
if (errors.length) {
|
|
59790
60790
|
console.log(render_errors(input, errors));
|