nomen-lang 0.6.7 → 0.6.9
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/NOMEN_AGENTS.md +12 -3
- package/audit_runtime.c +50 -0
- package/core/System/Buffer.nm +38 -0
- package/core/System/StringBuilder.nm +10 -1
- package/dist/index.mjs +520 -97
- package/package.json +3 -2
- package/src/args.ts +8 -3
- package/src/index.ts +8 -3
- package/src/init.ts +10 -0
- package/src/test.ts +98 -23
package/dist/index.mjs
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
#! /usr/bin/env node
|
|
2
|
-
import { execFileSync, execSync } from "node:child_process";
|
|
2
|
+
import { execFile, execFileSync, execSync } from "node:child_process";
|
|
3
3
|
import fs, { stat, unwatchFile, watch, watchFile } from "node:fs";
|
|
4
4
|
import * as sp from "node:path";
|
|
5
5
|
import path, { join, resolve, sep } from "node:path";
|
|
6
6
|
import { EventEmitter } from "node:events";
|
|
7
7
|
import { lstat, open, readdir, realpath, stat as stat$1 } from "node:fs/promises";
|
|
8
8
|
import { Readable } from "node:stream";
|
|
9
|
-
import { type } from "node:os";
|
|
9
|
+
import os, { type } from "node:os";
|
|
10
10
|
import { fileURLToPath } from "node:url";
|
|
11
|
+
import { promisify } from "node:util";
|
|
11
12
|
//#region ../node_modules/.pnpm/readdirp@5.1.1/node_modules/readdirp/index.js
|
|
12
13
|
const EntryTypes = {
|
|
13
14
|
FILE_TYPE: "files",
|
|
@@ -6811,6 +6812,27 @@ function find_enum(name, status) {
|
|
|
6811
6812
|
}
|
|
6812
6813
|
//#endregion
|
|
6813
6814
|
//#region ../src/build_common/mono_name.ts
|
|
6815
|
+
/**
|
|
6816
|
+
* Whether `name` is a monomorphized owning-container struct whose
|
|
6817
|
+
* `<Mono>_copy` is safe to call from the owning-`Buffer.load_T`
|
|
6818
|
+
* specialization — `List_<T>` / `Buffer_<T>` (and their bare generics).
|
|
6819
|
+
* These own a heap slab their `#destroy` frees unconditionally, and `copy`
|
|
6820
|
+
* yields an independent slab, so a returned element's field must be
|
|
6821
|
+
* deep-copied or the caller's unconditional container-field teardown
|
|
6822
|
+
* double-frees the slot's slab.
|
|
6823
|
+
*
|
|
6824
|
+
* `Map`/`Set` are deliberately EXCLUDED from the automatic deep copy: their
|
|
6825
|
+
* monomorphized `copy` (which composes several `Buffer.copy` calls) is
|
|
6826
|
+
* verified standalone on both backends, but invoking it from the load
|
|
6827
|
+
* specialization corrupts the returned struct on aarch64 (a codegen
|
|
6828
|
+
* interaction still open — see FOLLOWUP.md). A `Map`/`Set` FIELD reached
|
|
6829
|
+
* through a container element's `load` therefore stays shallow (the
|
|
6830
|
+
* pre-existing bounded gap); an EXPLICIT `.copy()` on the owning struct or
|
|
6831
|
+
* the Map/Set itself works on both backends.
|
|
6832
|
+
*/
|
|
6833
|
+
function is_load_deep_copy_container(name) {
|
|
6834
|
+
return name === "List" || name === "Buffer" || name.startsWith("List_") || name.startsWith("Buffer_");
|
|
6835
|
+
}
|
|
6814
6836
|
function mono_type_name(type_or_name, type_args) {
|
|
6815
6837
|
const name = typeof type_or_name === "string" ? type_or_name : type_or_name.name;
|
|
6816
6838
|
const args = typeof type_or_name === "string" ? type_args : type_or_name.type_args;
|
|
@@ -7676,10 +7698,15 @@ function direct_string_fields(node) {
|
|
|
7676
7698
|
* callee's own records are scope-local and die at return, so without the
|
|
7677
7699
|
* transfer the stored copy leaks.
|
|
7678
7700
|
*
|
|
7679
|
-
* Only DIRECT statements qualify: a store nested in a
|
|
7680
|
-
* execute, and a record over the pre-call value would
|
|
7681
|
-
* scope exit (possibly rodata — an invalid free).
|
|
7682
|
-
*
|
|
7701
|
+
* Only DIRECT statements qualify for the plain transfer: a store nested in a
|
|
7702
|
+
* branch/loop may not execute, and a record over the pre-call value would
|
|
7703
|
+
* free it at the caller's scope exit (possibly rodata — an invalid free).
|
|
7704
|
+
* Those CONDITIONAL stores are reported separately
|
|
7705
|
+
* (scan_ref_param_string_field_may_writes): the callee's function builder
|
|
7706
|
+
* emits an ENTRY DUP for each of them (`field = strdup(field)` at function
|
|
7707
|
+
* entry, recorded), which makes the field heap-owned on EVERY path and the
|
|
7708
|
+
* may-store record sound to transfer. Memoized on the function node;
|
|
7709
|
+
* forwarding cycles are cut by a visited set (the cycle member's own
|
|
7683
7710
|
* contribution is skipped, bounded-leak).
|
|
7684
7711
|
*/
|
|
7685
7712
|
function scan_ref_param_string_field_writes(func, structs) {
|
|
@@ -7687,6 +7714,51 @@ function scan_ref_param_string_field_writes(func, structs) {
|
|
|
7687
7714
|
if (stamp._ref_param_string_field_writes) return stamp._ref_param_string_field_writes;
|
|
7688
7715
|
const result = /* @__PURE__ */ new Map();
|
|
7689
7716
|
stamp._ref_param_string_field_writes = result;
|
|
7717
|
+
const tracked = tracked_ref_params(func, structs);
|
|
7718
|
+
if (tracked.size) scan_top_level(func.statements ?? [], tracked, structs, result);
|
|
7719
|
+
return result;
|
|
7720
|
+
}
|
|
7721
|
+
/**
|
|
7722
|
+
* The CONDITIONAL (`may-executed`) ref-param field stores: direct stores
|
|
7723
|
+
* nested in if/else branches, switch cases, and loop bodies. Forwarded
|
|
7724
|
+
* calls from a nested position are deliberately EXCLUDED: a helper's store
|
|
7725
|
+
* builds with its own (fresh) record set, so it cannot reclaim the
|
|
7726
|
+
* displaced entry-dup — dup'ing for a forwarded store would trade the
|
|
7727
|
+
* old bounded leak for a new one. Those chains keep the documented
|
|
7728
|
+
* bounded-leak posture (the stored copy leaks, never an invalid free).
|
|
7729
|
+
* The function builder entry-dups these fields (see
|
|
7730
|
+
* ref_param_entry_dup_fields), making the may-store record transferable.
|
|
7731
|
+
*/
|
|
7732
|
+
function scan_ref_param_string_field_may_writes(func, structs) {
|
|
7733
|
+
const stamp = func;
|
|
7734
|
+
if (stamp._ref_param_string_field_may_writes) return stamp._ref_param_string_field_may_writes;
|
|
7735
|
+
const result = /* @__PURE__ */ new Map();
|
|
7736
|
+
stamp._ref_param_string_field_may_writes = result;
|
|
7737
|
+
const tracked = tracked_ref_params(func, structs);
|
|
7738
|
+
if (tracked.size) scan_nested(func.statements ?? [], tracked, result);
|
|
7739
|
+
return result;
|
|
7740
|
+
}
|
|
7741
|
+
/**
|
|
7742
|
+
* The fields a function's builder must ENTRY DUP: the conditional
|
|
7743
|
+
* (may-executed) ref-param string-field stores that are NOT also
|
|
7744
|
+
* definitely-executed (a must-store already leaves the field heap-owned on
|
|
7745
|
+
* every path — dup'ing it too would just add a pointless strdup per call).
|
|
7746
|
+
* The dup runs at callee entry on both backends and records the field, so a
|
|
7747
|
+
* call site may transfer these as heap records soundly.
|
|
7748
|
+
*/
|
|
7749
|
+
function ref_param_entry_dup_fields(func, structs) {
|
|
7750
|
+
const must = scan_ref_param_string_field_writes(func, structs);
|
|
7751
|
+
const may = scan_ref_param_string_field_may_writes(func, structs);
|
|
7752
|
+
const result = /* @__PURE__ */ new Map();
|
|
7753
|
+
for (const [param, fields] of may) {
|
|
7754
|
+
const dup = /* @__PURE__ */ new Set();
|
|
7755
|
+
for (const field of fields) if (!must.get(param)?.has(field)) dup.add(field);
|
|
7756
|
+
if (dup.size) result.set(param, dup);
|
|
7757
|
+
}
|
|
7758
|
+
return result;
|
|
7759
|
+
}
|
|
7760
|
+
/** The tracked ref params of `func`: name -> writable plain string fields. */
|
|
7761
|
+
function tracked_ref_params(func, structs) {
|
|
7690
7762
|
const tracked = /* @__PURE__ */ new Map();
|
|
7691
7763
|
for (const param of func.params ?? []) {
|
|
7692
7764
|
if (param.is_self_param) continue;
|
|
@@ -7695,8 +7767,7 @@ function scan_ref_param_string_field_writes(func, structs) {
|
|
|
7695
7767
|
const fields = ref_param_writable_fields(param.type.name, structs);
|
|
7696
7768
|
if (fields.size) tracked.set(param.name, fields);
|
|
7697
7769
|
}
|
|
7698
|
-
|
|
7699
|
-
return result;
|
|
7770
|
+
return tracked;
|
|
7700
7771
|
}
|
|
7701
7772
|
/** The writable plain string fields of the value struct `type_name` resolves
|
|
7702
7773
|
* to, or the empty set (non-struct, class, or no string fields). */
|
|
@@ -7706,46 +7777,91 @@ function ref_param_writable_fields(type_name, structs) {
|
|
|
7706
7777
|
if (!struct) return /* @__PURE__ */ new Set();
|
|
7707
7778
|
return new Set(direct_string_fields(struct).map((f) => f.name));
|
|
7708
7779
|
}
|
|
7709
|
-
|
|
7710
|
-
|
|
7711
|
-
|
|
7780
|
+
/**
|
|
7781
|
+
* The top-level (definitely-executed) walk: direct `param.field = …`
|
|
7782
|
+
* statements and top-level forwarded calls only — nothing nested.
|
|
7783
|
+
*/
|
|
7784
|
+
function scan_top_level(statements, tracked, structs, result) {
|
|
7785
|
+
for (const stmt of statements) {
|
|
7712
7786
|
if (stmt.node_type === "assign") {
|
|
7713
|
-
|
|
7714
|
-
if (lhs?.node_type === "access") {
|
|
7715
|
-
const access = lhs;
|
|
7716
|
-
if (access.access.node_type === "access_field" && access.target.node_type === "value") {
|
|
7717
|
-
const name = access.target.value ?? "";
|
|
7718
|
-
const fields = tracked.get(name);
|
|
7719
|
-
const field = access.access.name ?? "";
|
|
7720
|
-
if (fields?.has(field)) {
|
|
7721
|
-
if (!result.has(name)) result.set(name, /* @__PURE__ */ new Set());
|
|
7722
|
-
result.get(name).add(field);
|
|
7723
|
-
}
|
|
7724
|
-
}
|
|
7725
|
-
}
|
|
7787
|
+
record_direct_store(stmt, tracked, result);
|
|
7726
7788
|
continue;
|
|
7727
7789
|
}
|
|
7728
7790
|
const call = ref_forward_call(stmt);
|
|
7729
|
-
if (
|
|
7730
|
-
|
|
7731
|
-
|
|
7732
|
-
|
|
7733
|
-
|
|
7734
|
-
|
|
7735
|
-
|
|
7736
|
-
|
|
7737
|
-
|
|
7738
|
-
|
|
7739
|
-
|
|
7740
|
-
|
|
7741
|
-
|
|
7742
|
-
|
|
7743
|
-
|
|
7744
|
-
|
|
7745
|
-
|
|
7746
|
-
|
|
7747
|
-
|
|
7748
|
-
|
|
7791
|
+
if (call) merge_forwarded(call, tracked, structs, result);
|
|
7792
|
+
}
|
|
7793
|
+
}
|
|
7794
|
+
/**
|
|
7795
|
+
* The nested walk: direct stores at ANY depth below the top level are
|
|
7796
|
+
* may-stores. Forwarded calls are skipped everywhere (their contributions
|
|
7797
|
+
* would need cross-function displaced-value knowledge the callee's fresh
|
|
7798
|
+
* record set cannot carry — see the may-scan doc).
|
|
7799
|
+
*/
|
|
7800
|
+
function scan_nested(statements, tracked, result) {
|
|
7801
|
+
for (const stmt of statements) {
|
|
7802
|
+
if (stmt.node_type === "assign") {
|
|
7803
|
+
record_direct_store(stmt, tracked, result);
|
|
7804
|
+
continue;
|
|
7805
|
+
}
|
|
7806
|
+
for (const nested of nested_statement_lists(stmt)) scan_nested(nested, tracked, result);
|
|
7807
|
+
}
|
|
7808
|
+
}
|
|
7809
|
+
/** Record a direct `param.<field> = …` store into `result`. */
|
|
7810
|
+
function record_direct_store(stmt, tracked, result) {
|
|
7811
|
+
const lhs = stmt.left_value;
|
|
7812
|
+
if (lhs?.node_type !== "access") return;
|
|
7813
|
+
const access = lhs;
|
|
7814
|
+
if (access.access.node_type !== "access_field" || access.target.node_type !== "value") return;
|
|
7815
|
+
const name = access.target.value ?? "";
|
|
7816
|
+
const fields = tracked.get(name);
|
|
7817
|
+
const field = access.access.name ?? "";
|
|
7818
|
+
if (!fields?.has(field)) return;
|
|
7819
|
+
if (!result.has(name)) result.set(name, /* @__PURE__ */ new Set());
|
|
7820
|
+
result.get(name).add(field);
|
|
7821
|
+
}
|
|
7822
|
+
/** Merge a forwarded call's callee stores for the forwarded param: the
|
|
7823
|
+
* callee's MUST stores at a top-level position. */
|
|
7824
|
+
function merge_forwarded(call, tracked, structs, result) {
|
|
7825
|
+
const [callee, args] = call;
|
|
7826
|
+
const non_self_params = (callee.params ?? []).filter((p) => !p.is_self_param);
|
|
7827
|
+
for (let i = 0; i < args.length && i < non_self_params.length; i++) {
|
|
7828
|
+
const arg = args[i];
|
|
7829
|
+
if (arg?.node_type !== "value") continue;
|
|
7830
|
+
const arg_name = arg.value ?? "";
|
|
7831
|
+
const my_fields = tracked.get(arg_name);
|
|
7832
|
+
if (!my_fields) continue;
|
|
7833
|
+
const callee_param = non_self_params[i];
|
|
7834
|
+
if (!(callee_param.is_ref || callee_param.type.is_ref)) continue;
|
|
7835
|
+
const source = scan_ref_param_string_field_writes(callee, structs).get(callee_param.name);
|
|
7836
|
+
if (!source?.size) continue;
|
|
7837
|
+
if (!result.has(arg_name)) result.set(arg_name, /* @__PURE__ */ new Set());
|
|
7838
|
+
const mine = result.get(arg_name);
|
|
7839
|
+
for (const field of source) if (my_fields.has(field)) mine.add(field);
|
|
7840
|
+
}
|
|
7841
|
+
}
|
|
7842
|
+
/** The statement lists nested inside `stmt` (branch bodies, loop bodies) —
|
|
7843
|
+
* the same walk scan_reassigned_vars uses. */
|
|
7844
|
+
function nested_statement_lists(stmt) {
|
|
7845
|
+
const branch_of = (b) => b?.statements ?? [];
|
|
7846
|
+
const lists = [];
|
|
7847
|
+
if (stmt.node_type === "while" || stmt.node_type === "for") {
|
|
7848
|
+
const nested = stmt.statements;
|
|
7849
|
+
if (nested) lists.push(nested);
|
|
7850
|
+
return lists;
|
|
7851
|
+
}
|
|
7852
|
+
if (stmt.node_type === "if") {
|
|
7853
|
+
const n = stmt;
|
|
7854
|
+
if (n.if_branch) lists.push(branch_of(n.if_branch));
|
|
7855
|
+
if (n.else_branch) lists.push(branch_of(n.else_branch));
|
|
7856
|
+
return lists;
|
|
7857
|
+
}
|
|
7858
|
+
if (stmt.node_type === "switch" || stmt.node_type === "match") {
|
|
7859
|
+
const n = stmt;
|
|
7860
|
+
for (const c of n.cases ?? []) if (c.branch) lists.push(branch_of(c.branch));
|
|
7861
|
+
if (n.else_branch) lists.push(branch_of(n.else_branch));
|
|
7862
|
+
return lists;
|
|
7863
|
+
}
|
|
7864
|
+
return lists;
|
|
7749
7865
|
}
|
|
7750
7866
|
/**
|
|
7751
7867
|
* The forwarded-call shape of a top-level statement: a resolved free-function
|
|
@@ -7776,6 +7892,12 @@ function ref_forward_call(stmt) {
|
|
|
7776
7892
|
* stores (scan_ref_param_string_field_writes) onto the caller's records for
|
|
7777
7893
|
* the argument variable, making the caller's scope exit free them.
|
|
7778
7894
|
*
|
|
7895
|
+
* The callee's CONDITIONAL stores transfer too
|
|
7896
|
+
* (ref_param_entry_dup_fields): the callee's builder entry-dups those
|
|
7897
|
+
* fields (`field = strdup(field)` at entry, recorded), so the field is
|
|
7898
|
+
* heap-owned on EVERY path out of the call — the not-taken path holds the
|
|
7899
|
+
* dup — and a caller-side record is sound.
|
|
7900
|
+
*
|
|
7779
7901
|
* `arg_is_trackable_local` must report true only for variables whose storage
|
|
7780
7902
|
* THIS function's scope exit owns — a plain local. A `ref` param of the
|
|
7781
7903
|
* enclosing function forwards caller storage, so a record here would free the
|
|
@@ -7783,19 +7905,22 @@ function ref_forward_call(stmt) {
|
|
|
7783
7905
|
*/
|
|
7784
7906
|
function transfer_ref_param_field_records(callee, args, status, arg_is_trackable_local) {
|
|
7785
7907
|
const writes = scan_ref_param_string_field_writes(callee, status.structs);
|
|
7786
|
-
|
|
7908
|
+
const dups = ref_param_entry_dup_fields(callee, status.structs);
|
|
7909
|
+
if (!writes.size && !dups.size) return;
|
|
7787
7910
|
const non_self_params = (callee.params ?? []).filter((p) => !p.is_self_param);
|
|
7788
7911
|
for (let i = 0; i < args.length && i < non_self_params.length; i++) {
|
|
7789
7912
|
const param = non_self_params[i];
|
|
7790
7913
|
if (!(param.is_ref || param.type.is_ref)) continue;
|
|
7791
7914
|
const fields = writes.get(param.name);
|
|
7792
|
-
|
|
7915
|
+
const dup_fields = dups.get(param.name);
|
|
7916
|
+
if (!fields?.size && !dup_fields?.size) continue;
|
|
7793
7917
|
const arg = args[i];
|
|
7794
7918
|
if (arg?.node_type !== "value") continue;
|
|
7795
7919
|
const name = arg.value;
|
|
7796
7920
|
if (!name || !arg_is_trackable_local(name)) continue;
|
|
7797
7921
|
if (!status.heap_string_fields) status.heap_string_fields = /* @__PURE__ */ new Set();
|
|
7798
|
-
for (const field of fields) status.heap_string_fields.add(`${name}.${field}`);
|
|
7922
|
+
for (const field of fields ?? []) status.heap_string_fields.add(`${name}.${field}`);
|
|
7923
|
+
for (const field of dup_fields ?? []) status.heap_string_fields.add(`${name}.${field}`);
|
|
7799
7924
|
}
|
|
7800
7925
|
}
|
|
7801
7926
|
//#endregion
|
|
@@ -8288,7 +8413,7 @@ function is_container_borrow_accessor_name(name) {
|
|
|
8288
8413
|
* site (see build_return_node), so it counts as owned for classification.
|
|
8289
8414
|
*/
|
|
8290
8415
|
function is_call_site_borrow_accessor(fn_name) {
|
|
8291
|
-
return fn_name === "at" || fn_name === "first";
|
|
8416
|
+
return fn_name === "at" || fn_name === "first" || fn_name === "load";
|
|
8292
8417
|
}
|
|
8293
8418
|
/**
|
|
8294
8419
|
* Whether `node` is a container/buffer borrow accessor CALL (an
|
|
@@ -15010,6 +15135,33 @@ function emit_destroy_for_scope(status, declarations_before) {
|
|
|
15010
15135
|
emit_asm(status, `ldr x19, [sp], #16\n`);
|
|
15011
15136
|
continue;
|
|
15012
15137
|
}
|
|
15138
|
+
if (status.heap_array_vars?.has(decl.name)) {
|
|
15139
|
+
if (status.heap_owned_string_arrays?.has(decl.name)) {
|
|
15140
|
+
emit_asm(status, `str x19, [sp, #-16]!\n`);
|
|
15141
|
+
emit_asm(status, `str x20, [sp, #-16]!\n`);
|
|
15142
|
+
emit_var_load(status, "x0", decl.name, 8);
|
|
15143
|
+
emit_asm(status, `mov x19, x0\n`);
|
|
15144
|
+
emit_asm(status, `ldr x20, [x19]\n`);
|
|
15145
|
+
emit_asm(status, `add x19, x19, #8\n`);
|
|
15146
|
+
const loop = `.Lhosa_${decl.name}`;
|
|
15147
|
+
emit_asm(status, `${loop}:\n`);
|
|
15148
|
+
emit_asm(status, `cbz x20, .Lhosa_done_${decl.name}\n`);
|
|
15149
|
+
emit_asm(status, `ldr x0, [x19]\n`);
|
|
15150
|
+
emit_free(status);
|
|
15151
|
+
emit_asm(status, `add x19, x19, #16\n`);
|
|
15152
|
+
emit_asm(status, `sub x20, x20, #1\n`);
|
|
15153
|
+
emit_asm(status, `b ${loop}\n`);
|
|
15154
|
+
emit_asm(status, `.Lhosa_done_${decl.name}:\n`);
|
|
15155
|
+
emit_var_load(status, "x0", decl.name, 8);
|
|
15156
|
+
emit_free(status);
|
|
15157
|
+
emit_asm(status, `ldr x20, [sp], #16\n`);
|
|
15158
|
+
emit_asm(status, `ldr x19, [sp], #16\n`);
|
|
15159
|
+
continue;
|
|
15160
|
+
}
|
|
15161
|
+
emit_var_load(status, "x0", decl.name, 8);
|
|
15162
|
+
emit_free(status);
|
|
15163
|
+
continue;
|
|
15164
|
+
}
|
|
15013
15165
|
if (status.heap_strings?.has(decl.name)) {
|
|
15014
15166
|
emit_var_load(status, "x0", decl.name, 8);
|
|
15015
15167
|
emit_free(status);
|
|
@@ -17957,7 +18109,7 @@ function emit_load_deep_copy_fields$1(elem, status, sret, slot) {
|
|
|
17957
18109
|
const size = get_type_size(field.type, status);
|
|
17958
18110
|
if (!field.type.is_ref && !field.type.is_view && !field.type.is_array && !field.type.is_nullable && field.type.name !== "string") {
|
|
17959
18111
|
const field_struct = resolve_struct_type(field.type, status);
|
|
17960
|
-
if (field_struct && !field_struct.is_class && field_struct.name
|
|
18112
|
+
if (field_struct && !field_struct.is_class && is_load_deep_copy_container(field_struct.name) && struct_needs_destroy(field_struct, status) && field_struct.functions.find((f) => f.name === "copy")) {
|
|
17961
18113
|
emit_asm(status, `add x8, ${sret}, #${offset}\n`);
|
|
17962
18114
|
emit_asm(status, `add x0, ${slot}, #${offset}\n`);
|
|
17963
18115
|
emit_asm(status, `bl ${field_struct.name}_copy\n`);
|
|
@@ -20611,6 +20763,7 @@ function build_function_node$1(node, status) {
|
|
|
20611
20763
|
} else status.nir_emit_ctx = void 0;
|
|
20612
20764
|
const old_raw_param_reloads = status.raw_param_reloads;
|
|
20613
20765
|
install_raw_reload_plan(status, raw_reload_plan_lines);
|
|
20766
|
+
emit_ref_param_entry_dups$1(node, status);
|
|
20614
20767
|
try {
|
|
20615
20768
|
build_block_node$1(node, status);
|
|
20616
20769
|
} finally {
|
|
@@ -20756,6 +20909,49 @@ function build_function_node$1(node, status) {
|
|
|
20756
20909
|
status.return_buffer_stack_offset = void 0;
|
|
20757
20910
|
status.function_return_type = void 0;
|
|
20758
20911
|
}
|
|
20912
|
+
/**
|
|
20913
|
+
* Entry-dup the ref-param string fields the body stores through
|
|
20914
|
+
* CONDITIONALLY (see ref_param_entry_dup_fields): load the field pair's
|
|
20915
|
+
* ptr half, strdup it, store the fresh pointer back — with the field
|
|
20916
|
+
* recorded in heap_string_fields — so the field is heap-owned on every
|
|
20917
|
+
* path out of the function and the call-site record transfer is sound.
|
|
20918
|
+
* Mirrors the C backend's emit_ref_param_entry_dups; the len half is
|
|
20919
|
+
* unchanged by a dup, so only the ptr half is rewritten. A nullable
|
|
20920
|
+
* string field guards the dup (strdup(NULL) traps; a null pair stores as
|
|
20921
|
+
* null and every reclaim path no-ops on free(NULL)).
|
|
20922
|
+
*/
|
|
20923
|
+
function emit_ref_param_entry_dups$1(node, status) {
|
|
20924
|
+
const dups = ref_param_entry_dup_fields(node, status.structs);
|
|
20925
|
+
if (!dups.size) return;
|
|
20926
|
+
for (const param of node.params ?? []) {
|
|
20927
|
+
if (param.is_self_param) continue;
|
|
20928
|
+
if (!(param.is_ref || param.type.is_ref)) continue;
|
|
20929
|
+
const fields = dups.get(param.name);
|
|
20930
|
+
if (!fields?.size) continue;
|
|
20931
|
+
const struct = status.structs.find((s) => s.name === param.type.name && !s.is_simple_type && !s.is_class);
|
|
20932
|
+
for (const field of fields) {
|
|
20933
|
+
const offset = get_field_offset(param.type.name, field, status);
|
|
20934
|
+
const paramReg = status.function_param_regs?.get(param.name);
|
|
20935
|
+
if (paramReg) emit_asm(status, `mov x0, ${paramReg}\n`);
|
|
20936
|
+
else if (is_local_ref_var(param.name, status)) emit_deref_var_address(status, "x0", param.name);
|
|
20937
|
+
else emit_var_address(status, "x0", param.name);
|
|
20938
|
+
emit_asm(status, `str x0, [sp, #-16]!\n`);
|
|
20939
|
+
emit_asm(status, `ldr x0, [x0, #${offset}]\n`);
|
|
20940
|
+
const nullable = !!struct?.fields.find((f) => f.name === field)?.type.is_nullable;
|
|
20941
|
+
let skip_label;
|
|
20942
|
+
if (nullable) {
|
|
20943
|
+
skip_label = `.Lentry_dup_${status.label_counter = (status.label_counter ?? 0) + 1}`;
|
|
20944
|
+
emit_asm(status, `cbz x0, ${skip_label}\n`);
|
|
20945
|
+
}
|
|
20946
|
+
emit_strdup(status);
|
|
20947
|
+
if (skip_label) emit_asm(status, `${skip_label}:\n`);
|
|
20948
|
+
emit_asm(status, `ldr x1, [sp], #16\n`);
|
|
20949
|
+
emit_asm(status, `str x0, [x1, #${offset}]\n`);
|
|
20950
|
+
if (!status.heap_string_fields) status.heap_string_fields = /* @__PURE__ */ new Set();
|
|
20951
|
+
status.heap_string_fields.add(`${param.name}.${field}`);
|
|
20952
|
+
}
|
|
20953
|
+
}
|
|
20954
|
+
}
|
|
20759
20955
|
//#endregion
|
|
20760
20956
|
//#region ../src/build_aarch64/build_struct_node.ts
|
|
20761
20957
|
let field_strdup_guard_counter = 0;
|
|
@@ -23997,6 +24193,14 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
|
|
|
23997
24193
|
if (element_size === 1) emit_asm(status, `strb w0, [x9, #${slot}]\n`);
|
|
23998
24194
|
else if (element_size === 4) emit_asm(status, `str w0, [x9, #${slot}]\n`);
|
|
23999
24195
|
else emit_asm(status, `str x0, [x9, #${slot}]\n`);
|
|
24196
|
+
} else if (element_size > 8 && struct_element && !struct_element.is_class) {
|
|
24197
|
+
emit_struct_address_param(value, status);
|
|
24198
|
+
ensure_newline(status);
|
|
24199
|
+
emit_asm(status, `ldr x9, [x29, #${offset}]\n`);
|
|
24200
|
+
emit_asm(status, `mov x1, x0\n`);
|
|
24201
|
+
emit_asm(status, `add x0, x9, #${slot}\n`);
|
|
24202
|
+
emit_asm(status, `mov x2, #${element_size}\n`);
|
|
24203
|
+
emit_asm(status, `bl _memcpy\n`);
|
|
24000
24204
|
} else {
|
|
24001
24205
|
emit_init_value$1(value, nir_array_element(nir_init, array_literal_node, i), status);
|
|
24002
24206
|
ensure_newline(status);
|
|
@@ -24036,6 +24240,14 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
|
|
|
24036
24240
|
if (element_size === 1) emit_asm(status, `strb w0, [x9, #${slot}]\n`);
|
|
24037
24241
|
else if (element_size === 4) emit_asm(status, `str w0, [x9, #${slot}]\n`);
|
|
24038
24242
|
else emit_asm(status, `str x0, [x9, #${slot}]\n`);
|
|
24243
|
+
} else if (element_size > 8 && struct_element && !struct_element.is_class) {
|
|
24244
|
+
emit_struct_address_param(value, status);
|
|
24245
|
+
ensure_newline(status);
|
|
24246
|
+
emit_asm(status, `ldr x9, [${node.name}]\n`);
|
|
24247
|
+
emit_asm(status, `mov x1, x0\n`);
|
|
24248
|
+
emit_asm(status, `add x0, x9, #${slot}\n`);
|
|
24249
|
+
emit_asm(status, `mov x2, #${element_size}\n`);
|
|
24250
|
+
emit_asm(status, `bl _memcpy\n`);
|
|
24039
24251
|
} else {
|
|
24040
24252
|
emit_init_value$1(value, nir_array_element(nir_init, array_literal_node, i), status);
|
|
24041
24253
|
ensure_newline(status);
|
|
@@ -24142,6 +24354,13 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
|
|
|
24142
24354
|
if (element_size === 1) emit_asm(status, `strb w0, [x29, #${slot_offset}]\n`);
|
|
24143
24355
|
else if (element_size === 4) emit_asm(status, `str w0, [x29, #${slot_offset}]\n`);
|
|
24144
24356
|
else emit_asm(status, `str x0, [x29, #${slot_offset}]\n`);
|
|
24357
|
+
} else if (element_size > 8 && struct_element && !struct_element.is_class) {
|
|
24358
|
+
emit_struct_address_param(value, status);
|
|
24359
|
+
ensure_newline(status);
|
|
24360
|
+
emit_asm(status, `mov x1, x0\n`);
|
|
24361
|
+
emit_asm(status, `add x0, x29, #${slot_offset}\n`);
|
|
24362
|
+
emit_asm(status, `mov x2, #${element_size}\n`);
|
|
24363
|
+
emit_asm(status, `bl _memcpy\n`);
|
|
24145
24364
|
} else {
|
|
24146
24365
|
emit_init_value$1(value, nir_array_element(nir_init, array_values, i), status);
|
|
24147
24366
|
ensure_newline(status);
|
|
@@ -24167,6 +24386,13 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
|
|
|
24167
24386
|
if (element_size === 1) emit_asm(status, `strb w0, [${node.name} + ${i * element_size}]\n`);
|
|
24168
24387
|
else if (element_size === 4) emit_asm(status, `str w0, [${node.name} + ${i * element_size}]\n`);
|
|
24169
24388
|
else emit_asm(status, `str x0, [${node.name} + ${i * element_size}]\n`);
|
|
24389
|
+
} else if (element_size > 8 && struct_element && !struct_element.is_class) {
|
|
24390
|
+
emit_struct_address_param(value, status);
|
|
24391
|
+
ensure_newline(status);
|
|
24392
|
+
emit_asm(status, `mov x1, x0\n`);
|
|
24393
|
+
emit_global_slot_addr(status, node.name, i * element_size);
|
|
24394
|
+
emit_asm(status, `mov x2, #${element_size}\n`);
|
|
24395
|
+
emit_asm(status, `bl _memcpy\n`);
|
|
24170
24396
|
} else {
|
|
24171
24397
|
emit_init_value$1(value, nir_array_element(nir_init, array_values, i), status);
|
|
24172
24398
|
ensure_newline(status);
|
|
@@ -33342,7 +33568,8 @@ function build_assignment_node$1(node, status, nir_rhs, nir_swap) {
|
|
|
33342
33568
|
if (!node.operator && node.left_value.node_type === "access" && node.left_value.access.node_type === "access_field" && node.right_value.node_type === "value" && node.right_value.is_moved) {
|
|
33343
33569
|
const access_lhs = node.left_value;
|
|
33344
33570
|
const field_type = access_lhs.access.type;
|
|
33345
|
-
const
|
|
33571
|
+
const field_resolved = field_type?.name && !field_type.is_view && !field_type.is_array ? resolve_struct_type(field_type, status) : void 0;
|
|
33572
|
+
const field_struct = field_resolved && !field_resolved.is_class ? field_resolved : void 0;
|
|
33346
33573
|
if (field_struct) {
|
|
33347
33574
|
splice_decl_from_c_scopes(status, node.right_value.value);
|
|
33348
33575
|
const saved_len = begin_code_scratch(status);
|
|
@@ -34953,6 +35180,7 @@ function build_if_else_node(node, status, nir) {
|
|
|
34953
35180
|
status.code += `) {\n`;
|
|
34954
35181
|
let then_fields;
|
|
34955
35182
|
if (node.if_branch) {
|
|
35183
|
+
status.heap_string_fields = new Set(pre_heap_string_fields ?? []);
|
|
34956
35184
|
build_block_with_cursor(node.if_branch, nir?.then_branch, status);
|
|
34957
35185
|
build_auto_free(status);
|
|
34958
35186
|
}
|
|
@@ -34961,7 +35189,7 @@ function build_if_else_node(node, status, nir) {
|
|
|
34961
35189
|
if (node.else_branch) {
|
|
34962
35190
|
status.scoped_declarations = enter_c_scope(status);
|
|
34963
35191
|
status.deferred_frees = [];
|
|
34964
|
-
status.heap_string_fields = pre_heap_string_fields;
|
|
35192
|
+
status.heap_string_fields = new Set(pre_heap_string_fields ?? []);
|
|
34965
35193
|
status.code += `} else {\n`;
|
|
34966
35194
|
build_block_with_cursor(node.else_branch, nir?.else_branch, status);
|
|
34967
35195
|
build_auto_free(status);
|
|
@@ -35395,7 +35623,7 @@ function build_switch_node(node, status, nir) {
|
|
|
35395
35623
|
for (let i = 0; i < node.cases.length; i++) {
|
|
35396
35624
|
const c = node.cases[i];
|
|
35397
35625
|
status.scoped_declarations = enter_c_scope(status);
|
|
35398
|
-
status.heap_string_fields = pre_heap_string_fields;
|
|
35626
|
+
status.heap_string_fields = new Set(pre_heap_string_fields ?? []);
|
|
35399
35627
|
const saved_code = begin_code_scratch(status);
|
|
35400
35628
|
build_node$1(c.condition, status);
|
|
35401
35629
|
let cond_code = end_code_scratch(status, saved_code);
|
|
@@ -35441,7 +35669,7 @@ function build_switch_node(node, status, nir) {
|
|
|
35441
35669
|
if (node.else_branch) {
|
|
35442
35670
|
status.code += "} else ";
|
|
35443
35671
|
status.scoped_declarations = enter_c_scope(status);
|
|
35444
|
-
status.heap_string_fields = pre_heap_string_fields;
|
|
35672
|
+
status.heap_string_fields = new Set(pre_heap_string_fields ?? []);
|
|
35445
35673
|
status.code += `{\n`;
|
|
35446
35674
|
build_block_with_cursor(node.else_branch, nir?.otherwise ?? void 0, status);
|
|
35447
35675
|
build_auto_free(status);
|
|
@@ -35864,11 +36092,13 @@ function emit_transfer_owning_fields(elem, src, status, prefix = "") {
|
|
|
35864
36092
|
/**
|
|
35865
36093
|
* Emit the deep-copy half of owning load_T: for each field whose type has a
|
|
35866
36094
|
* monomorphized `copy` helper that yields an independent value (a `List<T>`
|
|
35867
|
-
* field — `<
|
|
35868
|
-
* or an enum-with-data field whose string payloads
|
|
35869
|
-
* overwrite the shallow-copied bytes with an owned
|
|
35870
|
-
*
|
|
35871
|
-
*
|
|
36095
|
+
* /`Buffer<T>`/`Map`/`Set` field — `<Mono>_copy` deep-copies the slots or
|
|
36096
|
+
* member buffers — or an enum-with-data field whose string payloads
|
|
36097
|
+
* `<E>_copy` duplicates), overwrite the shallow-copied bytes with an owned
|
|
36098
|
+
* copy. A struct local's CONTAINER fields are reclaimed unconditionally at
|
|
36099
|
+
* scope exit, so a shallow return would double-free the slot's slab. Fields
|
|
36100
|
+
* without a copyable shape (class fields, nested owning user structs without
|
|
36101
|
+
* a synthesized copy) stay shallow — recorded in FOLLOWUP.md.
|
|
35872
36102
|
*/
|
|
35873
36103
|
function emit_load_deep_copy_fields(elem, dst, status) {
|
|
35874
36104
|
for (const field of elem.fields) {
|
|
@@ -35876,7 +36106,7 @@ function emit_load_deep_copy_fields(elem, dst, status) {
|
|
|
35876
36106
|
if (field.type.is_nullable) continue;
|
|
35877
36107
|
if (field.type.name === "string") continue;
|
|
35878
36108
|
const field_struct = resolve_struct_type(field.type, status);
|
|
35879
|
-
if (field_struct && !field_struct.is_class && field_struct.name
|
|
36109
|
+
if (field_struct && !field_struct.is_class && is_load_deep_copy_container(field_struct.name) && struct_needs_destroy(field_struct, status) && field_struct.functions.find((f) => f.name === "copy")) {
|
|
35880
36110
|
status.code += `${dst}.${field.name} = ${field_struct.name}_copy(&${dst}.${field.name});\n`;
|
|
35881
36111
|
continue;
|
|
35882
36112
|
}
|
|
@@ -37470,6 +37700,7 @@ function build_function_node(node, status) {
|
|
|
37470
37700
|
stmts: nir.body,
|
|
37471
37701
|
ast: node.statements
|
|
37472
37702
|
} : void 0;
|
|
37703
|
+
emit_ref_param_entry_dups(node, status);
|
|
37473
37704
|
build_block_node(node, status, false);
|
|
37474
37705
|
status.nir_emit_ctx = old_nir_ctx;
|
|
37475
37706
|
status.function_ref_params = old_ref_params;
|
|
@@ -37515,6 +37746,37 @@ function build_function_node(node, status) {
|
|
|
37515
37746
|
status.stack_array_lengths = old_stack_array_lengths;
|
|
37516
37747
|
status.moved = old_moved;
|
|
37517
37748
|
}
|
|
37749
|
+
/**
|
|
37750
|
+
* Emit the entry-dup for every `ref` value-struct param string field the
|
|
37751
|
+
* body stores through CONDITIONALLY (see ref_param_entry_dup_fields):
|
|
37752
|
+
* `{ field = nomen_str_dup(field) }` at function entry, recorded in
|
|
37753
|
+
* heap_string_fields, so the field is heap-owned on every path out of the
|
|
37754
|
+
* function and the call-site record transfer is sound. Nullable string
|
|
37755
|
+
* fields guard the dup (a null pair stores as null — free(NULL) is a
|
|
37756
|
+
* no-op on every reclaim path).
|
|
37757
|
+
*/
|
|
37758
|
+
function emit_ref_param_entry_dups(node, status) {
|
|
37759
|
+
const dups = ref_param_entry_dup_fields(node, status.structs);
|
|
37760
|
+
if (!dups.size) return;
|
|
37761
|
+
for (const param of node.params ?? []) {
|
|
37762
|
+
if (param.is_self_param) continue;
|
|
37763
|
+
if (!(param.is_ref || param.type.is_ref)) continue;
|
|
37764
|
+
const fields = dups.get(param.name);
|
|
37765
|
+
if (!fields?.size) continue;
|
|
37766
|
+
const struct = status.structs.find((s) => s.name === param.type.name && !s.is_simple_type && !s.is_class);
|
|
37767
|
+
const base = c_function_name(param.name);
|
|
37768
|
+
for (const field of fields) {
|
|
37769
|
+
const nullable = !!struct?.fields.find((f) => f.name === field)?.type.is_nullable;
|
|
37770
|
+
if (nullable) status.code += `if (${base}->${field}.ptr) {\n`;
|
|
37771
|
+
const temp = `_dup_t_${status.label_counter = (status.label_counter ?? 0) + 1}`;
|
|
37772
|
+
status.code += `nomen_string ${temp} = nomen_str_dup(${base}->${field});\n`;
|
|
37773
|
+
status.code += `${base}->${field} = ${temp};\n`;
|
|
37774
|
+
if (nullable) status.code += `}\n`;
|
|
37775
|
+
if (!status.heap_string_fields) status.heap_string_fields = /* @__PURE__ */ new Set();
|
|
37776
|
+
status.heap_string_fields.add(`${param.name}.${field}`);
|
|
37777
|
+
}
|
|
37778
|
+
}
|
|
37779
|
+
}
|
|
37518
37780
|
function emit_nested_declarations(node, status) {
|
|
37519
37781
|
const block = node;
|
|
37520
37782
|
for (let child of block.statements) switch (child.node_type) {
|
|
@@ -37805,6 +38067,17 @@ function build_function_call_node(node, status) {
|
|
|
37805
38067
|
status.code += `${func_name}(${is_closure_callee ? "NULL" : ""}`;
|
|
37806
38068
|
if (is_closure_callee && node.params.length > 0) status.code += ", ";
|
|
37807
38069
|
const variadic_idx = node.variadic_param_name ? node.params.findIndex((p) => p.node_type === "array") : -1;
|
|
38070
|
+
if (!node.variadic_param_name) {
|
|
38071
|
+
const resolved = node.resolved_function ?? (is_struct ? status.structs.find((s) => s.name === node.name)?.functions.find((f) => f.name === "#init") : void 0);
|
|
38072
|
+
const variadic_param = resolved?.params?.find((p) => p.is_variadic);
|
|
38073
|
+
if (resolved && variadic_param) {
|
|
38074
|
+
if (node.params.length === 1 && node.params[0].node_type === "array") node.variadic_param_name = variadic_param.name;
|
|
38075
|
+
else if (node.params.length === 0 && resolved.params.filter((p) => !p.is_self_param && !p.is_variadic).length === 0) {
|
|
38076
|
+
const elem_c = c_type(variadic_param.type.name);
|
|
38077
|
+
status.code += `0, (${elem_c}[]){ }`;
|
|
38078
|
+
}
|
|
38079
|
+
}
|
|
38080
|
+
}
|
|
37808
38081
|
for (let i = 0; i < node.params.length; i++) {
|
|
37809
38082
|
if (i > 0) status.code += ", ";
|
|
37810
38083
|
if (i === variadic_idx && node.params[i].node_type === "array") {
|
|
@@ -38773,10 +39046,31 @@ function build_access_node$1(node, status) {
|
|
|
38773
39046
|
status.code += `${ret_c} ${ctor_temp_val} = `;
|
|
38774
39047
|
}
|
|
38775
39048
|
}
|
|
39049
|
+
const owned_string_recv = !ctor_temp_receiver && (node.target.node_type === "func_call" || node.target.node_type === "access" && node.target.access.node_type === "access_func") && method_type?.name === "string" && !method_type.is_view && !is_string_borrow(node.target);
|
|
39050
|
+
let owned_recv_temp;
|
|
39051
|
+
let owned_recv_val;
|
|
39052
|
+
if (owned_string_recv) {
|
|
39053
|
+
const id = status.label_counter = (status.label_counter ?? 0) + 1;
|
|
39054
|
+
owned_recv_temp = `_osrecv_${id}`;
|
|
39055
|
+
status.code += `({ nomen_string ${owned_recv_temp} = `;
|
|
39056
|
+
{
|
|
39057
|
+
const saved_suppress = status.suppress_dereference;
|
|
39058
|
+
status.suppress_dereference = true;
|
|
39059
|
+
build_node$1(node.target, status);
|
|
39060
|
+
status.suppress_dereference = saved_suppress;
|
|
39061
|
+
}
|
|
39062
|
+
status.code += `; `;
|
|
39063
|
+
const recv_ret_c = c_return_type((target_method ?? trait_default_func)?.return_type ?? access_func.type, status);
|
|
39064
|
+
if (recv_ret_c !== "void") {
|
|
39065
|
+
owned_recv_val = `_osrecvv_${id}`;
|
|
39066
|
+
status.code += `${recv_ret_c} ${owned_recv_val} = `;
|
|
39067
|
+
}
|
|
39068
|
+
}
|
|
38776
39069
|
if (lambda_ret_tmp) status.code += `${lambda_ret_c} ${lambda_ret_tmp} = `;
|
|
38777
39070
|
status.code += `${label}(`;
|
|
38778
39071
|
if (!access_func.is_static) {
|
|
38779
39072
|
if (ctor_temp_free) status.code += ctor_temp_free;
|
|
39073
|
+
else if (owned_recv_temp) status.code += owned_recv_temp;
|
|
38780
39074
|
else if (array_wrap) status.code += array_wrap.self_expr;
|
|
38781
39075
|
else {
|
|
38782
39076
|
const method_self_is_ref = !!target_method?.params?.some((p) => p.is_self_param && (p.is_ref || p.type?.is_ref));
|
|
@@ -38861,6 +39155,11 @@ function build_access_node$1(node, status) {
|
|
|
38861
39155
|
if (ctor_temp_val) status.code += ` ${ctor_temp_val};`;
|
|
38862
39156
|
status.code += ` })`;
|
|
38863
39157
|
}
|
|
39158
|
+
if (owned_recv_temp) {
|
|
39159
|
+
status.code += `; ${status.audit ? "nomen_free_wrap" : "free"}(${owned_recv_temp}.ptr);`;
|
|
39160
|
+
if (owned_recv_val) status.code += ` ${owned_recv_val};`;
|
|
39161
|
+
status.code += ` })`;
|
|
39162
|
+
}
|
|
38864
39163
|
if (lambda_arg_temps.size > 0) {
|
|
38865
39164
|
for (const tmp of lambda_arg_temps.values()) status.code += `; ${closure_dispose_arm(tmp)}`;
|
|
38866
39165
|
if (lambda_ret_tmp) status.code += `; ${lambda_ret_tmp}`;
|
|
@@ -41412,7 +41711,11 @@ function build_assignment_node(node, status, nir_rhs, nir_swap) {
|
|
|
41412
41711
|
if (name === "self" && status.current_struct) target_type = new Type(status.current_struct.name);
|
|
41413
41712
|
else if (status.variable_types?.has(name)) target_type = status.variable_types.get(name);
|
|
41414
41713
|
}
|
|
41415
|
-
|
|
41714
|
+
let field_type = access.access.type;
|
|
41715
|
+
if (!field_type?.name && target_type?.name) {
|
|
41716
|
+
const tf = status.structs.find((s) => s.name === target_type.name)?.fields.find((f) => f.name === field_name);
|
|
41717
|
+
if (tf) field_type = tf.type;
|
|
41718
|
+
}
|
|
41416
41719
|
const field_is_struct = is_struct_type$2(field_type, status);
|
|
41417
41720
|
if (access.target.node_type === "value") {
|
|
41418
41721
|
const name = access.target.value;
|
|
@@ -45387,7 +45690,7 @@ function build_access_method(node, access_func, status) {
|
|
|
45387
45690
|
status.last_result_is_heap = false;
|
|
45388
45691
|
build_node(node.target, status);
|
|
45389
45692
|
ensure_newline(status);
|
|
45390
|
-
frees_string_receiver =
|
|
45693
|
+
frees_string_receiver = status.last_result_is_heap;
|
|
45391
45694
|
status.last_result_is_heap = false;
|
|
45392
45695
|
emit_asm(status, `stp x0, x1, [sp, #-16]!\n`);
|
|
45393
45696
|
}
|
|
@@ -56114,6 +56417,14 @@ function check_function_call(node, status, func, target_type, self_value, self_p
|
|
|
56114
56417
|
}
|
|
56115
56418
|
node.type = func.return_type;
|
|
56116
56419
|
node.is_static = func.is_static;
|
|
56420
|
+
if (target_type?.type_args?.length && node.type?.type_args?.length) {
|
|
56421
|
+
const owner = func.scope;
|
|
56422
|
+
if (owner && owner.node_type === "struct" && owner.is_generic && owner.type_params.length === target_type.type_args.length) {
|
|
56423
|
+
const rebind = /* @__PURE__ */ new Map();
|
|
56424
|
+
owner.type_params.forEach((tp, i) => rebind.set(tp, target_type.type_args[i].name));
|
|
56425
|
+
node.type = substitute_type(node.type, rebind);
|
|
56426
|
+
}
|
|
56427
|
+
}
|
|
56117
56428
|
if (node.type?.type_args?.length) instantiate_generic_type(node.type, status);
|
|
56118
56429
|
if (node.type && node.type.name && self_value && is_class_type$1(node.type.name, status) && receiver_is_const(target_type, self_value, status)) {
|
|
56119
56430
|
node.type = clone_type$2(node.type);
|
|
@@ -56297,7 +56608,10 @@ function check_function_call(node, status, func, target_type, self_value, self_p
|
|
|
56297
56608
|
else {
|
|
56298
56609
|
const stamped = String(func_param.type.length.value);
|
|
56299
56610
|
const incoming = String(param_type.length.value);
|
|
56300
|
-
if (stamped !== incoming)
|
|
56611
|
+
if (stamped !== incoming) {
|
|
56612
|
+
if (is_heap_array_type(func_param.type)) func_param.type.length = void 0;
|
|
56613
|
+
else add_error(status, `Call passes ${incoming} elements to array parameter '${func_param.name}' whose compile-time length is ${stamped} — the raw array ABI carries no runtime length, so one parameter serves one compile-time length; use Array<${func_param.type.name}> for varying lengths`, param.start);
|
|
56614
|
+
}
|
|
56301
56615
|
}
|
|
56302
56616
|
}
|
|
56303
56617
|
let arg_value;
|
|
@@ -56528,6 +56842,13 @@ function check_function_call(node, status, func, target_type, self_value, self_p
|
|
|
56528
56842
|
if (typeof size_val === "number" && size_val > 0) record_buffer_cap(self_path, size_val, status);
|
|
56529
56843
|
}
|
|
56530
56844
|
}
|
|
56845
|
+
if (self_path && self_path !== "?" && is_bufferish && func.name === "copy") {
|
|
56846
|
+
const source_cap = lookup_buffer_cap(self_path, status);
|
|
56847
|
+
if (source_cap !== void 0) {
|
|
56848
|
+
const lhs_name = find_lhs_var_name(status);
|
|
56849
|
+
if (lhs_name) record_buffer_cap(lhs_name, source_cap, status);
|
|
56850
|
+
}
|
|
56851
|
+
}
|
|
56531
56852
|
if (func.return_constraint) {
|
|
56532
56853
|
const param_to_arg = /* @__PURE__ */ new Map();
|
|
56533
56854
|
if (self_path && self_path !== "?") param_to_arg.set("self", path_to_node(self_path));
|
|
@@ -60581,6 +60902,68 @@ function parse_case_branch(status) {
|
|
|
60581
60902
|
return null;
|
|
60582
60903
|
}
|
|
60583
60904
|
//#endregion
|
|
60905
|
+
//#region ../src/parse/utils/parse_embed_file.ts
|
|
60906
|
+
/**
|
|
60907
|
+
* `#embed_file("relative/path")` — a compile-time string literal whose value
|
|
60908
|
+
* is the contents of the file at parse time. The path resolves against the
|
|
60909
|
+
* package root (the directory containing package.jsonc, found by climbing
|
|
60910
|
+
* from the entry file), falling back to the entry file's folder; with no
|
|
60911
|
+
* known base it resolves against the process CWD.
|
|
60912
|
+
*
|
|
60913
|
+
* The parsed form is a plain string-literal ValueNode: escapes are encoded
|
|
60914
|
+
* with the source-level escape set (`\\`, `\"`, `\n`, `\r`, `\t`, and
|
|
60915
|
+
* three-digit octal for other control bytes — self-terminating, matching the
|
|
60916
|
+
* escape scanner), so every downstream consumer (length counting, C/asm
|
|
60917
|
+
* emission) treats it exactly like a hand-written literal.
|
|
60918
|
+
*/
|
|
60919
|
+
function parse_embed_file(status) {
|
|
60920
|
+
const start = get_index(status);
|
|
60921
|
+
consume(status);
|
|
60922
|
+
const name = consume(status);
|
|
60923
|
+
if (name !== "embed_file") {
|
|
60924
|
+
add_error(status, `Unknown compiler directive '#${name}' — did you mean '#embed_file'?`, start);
|
|
60925
|
+
return new ValueNode(start, "\"\"");
|
|
60926
|
+
}
|
|
60927
|
+
if (!expect("(", status)) return new ValueNode(start, "\"\"");
|
|
60928
|
+
const path_token = status.tokens[status.i];
|
|
60929
|
+
if (!path_token || !path_token.value.startsWith("\"")) {
|
|
60930
|
+
add_error(status, "Expected a string literal path in #embed_file", start);
|
|
60931
|
+
return new ValueNode(start, "\"\"");
|
|
60932
|
+
}
|
|
60933
|
+
status.i += 1;
|
|
60934
|
+
const rel_path = path_token.value.slice(1, -1);
|
|
60935
|
+
if (!expect(")", status)) return new ValueNode(start, "\"\"");
|
|
60936
|
+
if (!rel_path) {
|
|
60937
|
+
add_error(status, "#embed_file path is empty", start);
|
|
60938
|
+
return new ValueNode(start, "\"\"");
|
|
60939
|
+
}
|
|
60940
|
+
const base = status.embed_root ?? process.cwd();
|
|
60941
|
+
const full = path.isAbsolute(rel_path) ? rel_path : path.join(base, rel_path);
|
|
60942
|
+
let content;
|
|
60943
|
+
try {
|
|
60944
|
+
content = fs.readFileSync(full, "utf8");
|
|
60945
|
+
} catch {
|
|
60946
|
+
add_error(status, `#embed_file could not read '${rel_path}' (resolved '${full}')`, start);
|
|
60947
|
+
return new ValueNode(start, "\"\"");
|
|
60948
|
+
}
|
|
60949
|
+
return new ValueNode(start, `"${escape_embedded(content)}"`);
|
|
60950
|
+
}
|
|
60951
|
+
/** Encode file contents as a source-level string literal body. */
|
|
60952
|
+
function escape_embedded(content) {
|
|
60953
|
+
let out = "";
|
|
60954
|
+
for (const ch of content) {
|
|
60955
|
+
const code = ch.charCodeAt(0);
|
|
60956
|
+
if (ch === "\\") out += "\\\\";
|
|
60957
|
+
else if (ch === "\"") out += "\\\"";
|
|
60958
|
+
else if (ch === "\n") out += "\\n";
|
|
60959
|
+
else if (ch === "\r") out += "\\r";
|
|
60960
|
+
else if (ch === " ") out += "\\t";
|
|
60961
|
+
else if (code < 32 || code === 127) out += `\\${code.toString(8).padStart(3, "0")}`;
|
|
60962
|
+
else out += ch;
|
|
60963
|
+
}
|
|
60964
|
+
return out;
|
|
60965
|
+
}
|
|
60966
|
+
//#endregion
|
|
60584
60967
|
//#region ../src/parse/parse_expression.ts
|
|
60585
60968
|
function restructure_op(start, current_op, current_prec, current_node, expression) {
|
|
60586
60969
|
if (current_prec <= operator_precedence(expression.op)) {
|
|
@@ -60720,6 +61103,7 @@ function parse_primary(status, value) {
|
|
|
60720
61103
|
expect(")", status);
|
|
60721
61104
|
return node;
|
|
60722
61105
|
}
|
|
61106
|
+
case "#": return parse_embed_file(status);
|
|
60723
61107
|
case "if": return parse_if_else(status);
|
|
60724
61108
|
case "match": return parse_match(status);
|
|
60725
61109
|
case "switch": return parse_switch(status);
|
|
@@ -61862,7 +62246,8 @@ function parse(source, library, file_path, options) {
|
|
|
61862
62246
|
qualified_paths: [],
|
|
61863
62247
|
library,
|
|
61864
62248
|
errors: [],
|
|
61865
|
-
allow_user_raw: options?.allow_user_raw
|
|
62249
|
+
allow_user_raw: options?.allow_user_raw,
|
|
62250
|
+
embed_root: embed_root_for(file_path)
|
|
61866
62251
|
};
|
|
61867
62252
|
if (library) status.unsafe_boundary = user_source_length;
|
|
61868
62253
|
parse_statement(status);
|
|
@@ -61939,6 +62324,23 @@ function mark_library_nodes(root, boundary) {
|
|
|
61939
62324
|
walk(root);
|
|
61940
62325
|
}
|
|
61941
62326
|
/**
|
|
62327
|
+
* The base directory for `#embed_file` paths: the package root (the nearest
|
|
62328
|
+
* enclosing directory with a package.jsonc), falling back to the entry
|
|
62329
|
+
* file's folder. No file path — undefined, so `#embed_file` resolves
|
|
62330
|
+
* against the process CWD.
|
|
62331
|
+
*/
|
|
62332
|
+
function embed_root_for(file_path) {
|
|
62333
|
+
if (!file_path) return void 0;
|
|
62334
|
+
let dir = path.dirname(path.resolve(file_path));
|
|
62335
|
+
for (let i = 0; i < 32; i++) {
|
|
62336
|
+
if (fs.existsSync(path.join(dir, "package.jsonc"))) return dir;
|
|
62337
|
+
const parent = path.dirname(dir);
|
|
62338
|
+
if (parent === dir) break;
|
|
62339
|
+
dir = parent;
|
|
62340
|
+
}
|
|
62341
|
+
return path.dirname(path.resolve(file_path));
|
|
62342
|
+
}
|
|
62343
|
+
/**
|
|
61942
62344
|
* Append the library source that `source` needs, exactly as `parse` does.
|
|
61943
62345
|
* Exported so tooling (e.g. the editor extension) can reproduce the combined
|
|
61944
62346
|
* source that node offsets refer to.
|
|
@@ -62096,7 +62498,9 @@ const STRING_OPTIONS = /* @__PURE__ */ new Map([
|
|
|
62096
62498
|
["p", "platform"],
|
|
62097
62499
|
["lib", "lib"],
|
|
62098
62500
|
["l", "lib"],
|
|
62099
|
-
["audit-runtime", "audit_runtime"]
|
|
62501
|
+
["audit-runtime", "audit_runtime"],
|
|
62502
|
+
["jobs", "jobs"],
|
|
62503
|
+
["j", "jobs"]
|
|
62100
62504
|
]);
|
|
62101
62505
|
const BOOLEAN_OPTIONS = /* @__PURE__ */ new Map([
|
|
62102
62506
|
["watch", "watch"],
|
|
@@ -62192,7 +62596,7 @@ function print_help() {
|
|
|
62192
62596
|
" nomen check --in [file/folder] Parse and check only",
|
|
62193
62597
|
" nomen format [--in folder] Reformat every .nm file",
|
|
62194
62598
|
" nomen docs [--in file] Generate markdown documentation",
|
|
62195
|
-
" nomen test [--in folder] Discover and run *.test.nm files",
|
|
62599
|
+
" nomen test [--in folder] Discover and run *.test.nm files (binary CWD = --in folder)",
|
|
62196
62600
|
" nomen init <name> Scaffold a new project in ./<name>",
|
|
62197
62601
|
" nomen lib-path Print the bundled System library path",
|
|
62198
62602
|
"",
|
|
@@ -62206,8 +62610,9 @@ function print_help() {
|
|
|
62206
62610
|
" --platform, -p <name> Target platform: macos, ios, linux, android, windows, web",
|
|
62207
62611
|
" --lib, -l <path> Path to System library directory (containing package.jsonc)",
|
|
62208
62612
|
" --audit Audit the generated program for memory issues",
|
|
62209
|
-
" --audit-runtime <path> Path to audit_runtime.c
|
|
62210
|
-
" --
|
|
62613
|
+
" --audit-runtime <path> Path to audit_runtime.c (default: auto-discovered — walk-up or the CLI-bundled copy)",
|
|
62614
|
+
" --jobs, -j <n> For `nomen test`: max test files run in parallel (default: CPU count)",
|
|
62615
|
+
" --release, -r Build with optimizations (clang -O2; aarch64 asm cleanup passes)",
|
|
62211
62616
|
" --fast-math Allow FP reassociation (float loop reductions vectorize on aarch64)",
|
|
62212
62617
|
" --check For `nomen format`: report files that would change",
|
|
62213
62618
|
" -h, --help Show this help"
|
|
@@ -62555,6 +62960,9 @@ function find_bundled(filename) {
|
|
|
62555
62960
|
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)];
|
|
62556
62961
|
for (const c of candidates) if (fs.existsSync(c)) return path.resolve(c);
|
|
62557
62962
|
}
|
|
62963
|
+
function find_bundled_audit_runtime() {
|
|
62964
|
+
return find_bundled("src/audit_runtime.c") ?? find_bundled("audit_runtime.c");
|
|
62965
|
+
}
|
|
62558
62966
|
/**
|
|
62559
62967
|
* `nomen init <name>`: scaffold a new project under `./<name>` with a sensible
|
|
62560
62968
|
* starting layout (package.jsonc, src/main.nm, a starter test, README and an
|
|
@@ -62633,6 +63041,7 @@ function outfile_for(input_path, is_test, out) {
|
|
|
62633
63041
|
//#endregion
|
|
62634
63042
|
//#region src/test.ts
|
|
62635
63043
|
const RECORD_PREFIX = "\\nomen|";
|
|
63044
|
+
const execFile_async = promisify(execFile);
|
|
62636
63045
|
/** Recursively collect `*.test.nm` files under `root`, skipping build output. */
|
|
62637
63046
|
function collect_test_files(root) {
|
|
62638
63047
|
const out = [];
|
|
@@ -62782,7 +63191,7 @@ function extract_leaks(stdout) {
|
|
|
62782
63191
|
* Compile one `*.test.nm` file (with the generated harness) and run it,
|
|
62783
63192
|
* returning its parsed records and any crash info.
|
|
62784
63193
|
*/
|
|
62785
|
-
function run_test_file(entry_path, lib_path, arch, audit = false, audit_runtime) {
|
|
63194
|
+
async function run_test_file(entry_path, lib_path, arch, audit = false, audit_runtime, release = false, cwd) {
|
|
62786
63195
|
const start = performance.now();
|
|
62787
63196
|
const source_text = fs.readFileSync(entry_path, "utf8");
|
|
62788
63197
|
const harness = generate_harness(extract_test_functions(source_text), extract_bench_functions(source_text));
|
|
@@ -62809,7 +63218,8 @@ function run_test_file(entry_path, lib_path, arch, audit = false, audit_runtime)
|
|
|
62809
63218
|
}
|
|
62810
63219
|
const buildResult = build(parsed.root, {
|
|
62811
63220
|
arch,
|
|
62812
|
-
audit
|
|
63221
|
+
audit,
|
|
63222
|
+
optimize: release
|
|
62813
63223
|
});
|
|
62814
63224
|
if (buildResult.errors && buildResult.errors.length) {
|
|
62815
63225
|
result.ok = false;
|
|
@@ -62822,7 +63232,7 @@ function run_test_file(entry_path, lib_path, arch, audit = false, audit_runtime)
|
|
|
62822
63232
|
if (!fs.existsSync(buildDir)) fs.mkdirSync(buildDir, { recursive: true });
|
|
62823
63233
|
let audit_obj;
|
|
62824
63234
|
if (audit) {
|
|
62825
|
-
const runtime_src = audit_runtime ? path.resolve(audit_runtime) : find_audit_runtime(path.dirname(resolved));
|
|
63235
|
+
const runtime_src = audit_runtime ? path.resolve(audit_runtime) : find_audit_runtime(path.dirname(resolved)) ?? find_bundled_audit_runtime();
|
|
62826
63236
|
if (!runtime_src || !fs.existsSync(runtime_src)) {
|
|
62827
63237
|
result.ok = false;
|
|
62828
63238
|
result.phase = "setup";
|
|
@@ -62831,7 +63241,7 @@ function run_test_file(entry_path, lib_path, arch, audit = false, audit_runtime)
|
|
|
62831
63241
|
return result;
|
|
62832
63242
|
}
|
|
62833
63243
|
audit_obj = path.join(buildDir, "audit_runtime.o");
|
|
62834
|
-
|
|
63244
|
+
await execFile_async("clang", [
|
|
62835
63245
|
"-c",
|
|
62836
63246
|
runtime_src,
|
|
62837
63247
|
"-o",
|
|
@@ -62844,22 +63254,23 @@ function run_test_file(entry_path, lib_path, arch, audit = false, audit_runtime)
|
|
|
62844
63254
|
const outfile = path.join(buildDir, path.basename(entry_path, ".nm"));
|
|
62845
63255
|
fs.writeFileSync(path.join(buildDir, "main.h"), buildResult.headers ?? "");
|
|
62846
63256
|
fs.writeFileSync(codefile, buildResult.code);
|
|
63257
|
+
let companionfile;
|
|
63258
|
+
if (buildResult.companion) {
|
|
63259
|
+
const comp_ext = platform === "macos" || platform === "ios" ? ".m" : ".c";
|
|
63260
|
+
companionfile = path.join(buildDir, path.basename(entry_path, ".nm") + "_companion" + comp_ext);
|
|
63261
|
+
fs.writeFileSync(companionfile, buildResult.companion);
|
|
63262
|
+
}
|
|
62847
63263
|
const link_args = [
|
|
62848
63264
|
"-o",
|
|
62849
63265
|
outfile,
|
|
62850
63266
|
codefile
|
|
62851
63267
|
];
|
|
63268
|
+
if (companionfile) link_args.push(companionfile);
|
|
63269
|
+
if (release) link_args.unshift("-O2");
|
|
62852
63270
|
if (audit_obj) link_args.push(audit_obj);
|
|
62853
63271
|
if (platform === "macos" || platform === "ios") link_args.push("-framework", "CoreGraphics", "-framework", "Foundation", "-framework", "AppKit", "-lobjc");
|
|
62854
63272
|
try {
|
|
62855
|
-
|
|
62856
|
-
encoding: "utf8",
|
|
62857
|
-
stdio: [
|
|
62858
|
-
"ignore",
|
|
62859
|
-
"pipe",
|
|
62860
|
-
"pipe"
|
|
62861
|
-
]
|
|
62862
|
-
});
|
|
63273
|
+
await execFile_async("clang", link_args, { encoding: "utf8" });
|
|
62863
63274
|
} catch (err) {
|
|
62864
63275
|
const stderr = err.stderr ? err.stderr.toString() : err.message ?? "";
|
|
62865
63276
|
result.ok = false;
|
|
@@ -62871,16 +63282,12 @@ function run_test_file(entry_path, lib_path, arch, audit = false, audit_runtime)
|
|
|
62871
63282
|
let runStdout = "";
|
|
62872
63283
|
let crashed;
|
|
62873
63284
|
try {
|
|
62874
|
-
runStdout =
|
|
63285
|
+
runStdout = (await execFile_async(outfile, [], {
|
|
62875
63286
|
encoding: "utf8",
|
|
62876
|
-
stdio: [
|
|
62877
|
-
"ignore",
|
|
62878
|
-
"pipe",
|
|
62879
|
-
"pipe"
|
|
62880
|
-
],
|
|
62881
63287
|
timeout: 3e4,
|
|
62882
|
-
maxBuffer: 16777216
|
|
62883
|
-
|
|
63288
|
+
maxBuffer: 16777216,
|
|
63289
|
+
cwd: cwd ?? process.cwd()
|
|
63290
|
+
})).stdout;
|
|
62884
63291
|
} catch (err) {
|
|
62885
63292
|
runStdout = err.stdout ? err.stdout.toString() : "";
|
|
62886
63293
|
if (err.signal === "SIGTERM") crashed = "test binary timed out after 30s";
|
|
@@ -62939,10 +63346,16 @@ function report_file(result) {
|
|
|
62939
63346
|
for (const line of result.other) console.log(C.dim(` ${line}`));
|
|
62940
63347
|
}
|
|
62941
63348
|
}
|
|
63349
|
+
/** The default concurrency for `runTests` when no `jobs` is given. */
|
|
63350
|
+
function default_jobs() {
|
|
63351
|
+
const n = typeof os.availableParallelism === "function" ? os.availableParallelism() : os.cpus().length;
|
|
63352
|
+
return Math.max(1, n);
|
|
63353
|
+
}
|
|
62942
63354
|
/**
|
|
62943
63355
|
* Locate audit_runtime.c for an audited test build: an explicit path wins,
|
|
62944
63356
|
* otherwise walk up from `dir` looking for a `src/audit_runtime.c` (the same
|
|
62945
|
-
* convention the run/build commands use).
|
|
63357
|
+
* convention the run/build commands use). When both miss, the caller falls
|
|
63358
|
+
* back to the CLI-bundled runtime (`find_bundled_audit_runtime`).
|
|
62946
63359
|
*/
|
|
62947
63360
|
function find_audit_runtime(dir) {
|
|
62948
63361
|
let cur = dir;
|
|
@@ -62955,18 +63368,25 @@ function find_audit_runtime(dir) {
|
|
|
62955
63368
|
}
|
|
62956
63369
|
}
|
|
62957
63370
|
/** Discover, run, and report every `*.test.nm` under `root`. */
|
|
62958
|
-
function runTests(root, options = {}) {
|
|
63371
|
+
async function runTests(root, options = {}) {
|
|
62959
63372
|
const arch = options.arch ?? "aarch64";
|
|
62960
63373
|
const lib = resolve_lib_for(root);
|
|
62961
63374
|
const files = collect_test_files(root).filter((f) => !options.filter || options.filter.test(f));
|
|
62962
63375
|
console.log(`Found ${files.length} test file(s)\n`);
|
|
62963
63376
|
const startTime = performance.now();
|
|
62964
|
-
const
|
|
62965
|
-
|
|
62966
|
-
|
|
62967
|
-
|
|
62968
|
-
|
|
62969
|
-
|
|
63377
|
+
const binary_cwd = path.resolve(root);
|
|
63378
|
+
const jobs = Math.max(1, Math.min(options.jobs ?? default_jobs(), files.length));
|
|
63379
|
+
const results = new Array(files.length);
|
|
63380
|
+
let next = 0;
|
|
63381
|
+
const worker = async () => {
|
|
63382
|
+
for (;;) {
|
|
63383
|
+
const i = next++;
|
|
63384
|
+
if (i >= files.length) return;
|
|
63385
|
+
results[i] = await run_test_file(files[i], lib, arch, options.audit, options.audit_runtime, options.release, binary_cwd);
|
|
63386
|
+
}
|
|
63387
|
+
};
|
|
63388
|
+
await Promise.all(Array.from({ length: jobs }, worker));
|
|
63389
|
+
for (const result of results) report_file(result);
|
|
62970
63390
|
const elapsed = performance.now() - startTime;
|
|
62971
63391
|
const totalFiles = results.length;
|
|
62972
63392
|
const totalFilesFailed = results.reduce((a, r) => a + (r.ok ? 0 : 1), 0);
|
|
@@ -63067,11 +63487,13 @@ try {
|
|
|
63067
63487
|
if (command === "test") {
|
|
63068
63488
|
const root = args.in ?? process.cwd();
|
|
63069
63489
|
const filter = args.filter ? new RegExp(args.filter) : void 0;
|
|
63070
|
-
const ok = runTests(root, {
|
|
63490
|
+
const ok = await runTests(root, {
|
|
63071
63491
|
arch: args.arch ?? "aarch64",
|
|
63072
63492
|
filter,
|
|
63073
63493
|
audit: args.audit,
|
|
63074
|
-
audit_runtime: args.audit_runtime
|
|
63494
|
+
audit_runtime: args.audit_runtime,
|
|
63495
|
+
release: args.release,
|
|
63496
|
+
jobs: args.jobs !== void 0 ? parseInt(args.jobs, 10) : void 0
|
|
63075
63497
|
});
|
|
63076
63498
|
process.exit(ok ? 0 : 1);
|
|
63077
63499
|
}
|
|
@@ -63182,6 +63604,7 @@ function resolve_audit_runtime(config, input_path) {
|
|
|
63182
63604
|
if (parent === dir) break;
|
|
63183
63605
|
dir = parent;
|
|
63184
63606
|
}
|
|
63607
|
+
return find_bundled_audit_runtime();
|
|
63185
63608
|
}
|
|
63186
63609
|
function compile_audit_runtime(config, input_path, buildDir) {
|
|
63187
63610
|
const runtime_src = resolve_audit_runtime(config, input_path);
|