nomen-lang 0.6.10 → 0.6.11
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/Console.nm +26 -0
- package/dist/index.mjs +142 -28
- package/package.json +1 -1
- package/src/test.ts +72 -21
package/core/System/Console.nm
CHANGED
|
@@ -43,6 +43,32 @@ pub struct Console {
|
|
|
43
43
|
```
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
// Disable stdout buffering: every write goes straight to the file
|
|
47
|
+
// descriptor. The `nomen test` harness calls this at startup so a
|
|
48
|
+
// crashing binary still leaves every record it printed before the
|
|
49
|
+
// crash — a buffered stdout loses its pending bytes on an abnormal
|
|
50
|
+
// exit, which used to turn a mid-suite abort into "no records at all".
|
|
51
|
+
pub func unbuffered = () {
|
|
52
|
+
```
|
|
53
|
+
#arch: c
|
|
54
|
+
setbuf(stdout, NULL);
|
|
55
|
+
```
|
|
56
|
+
```
|
|
57
|
+
#arch: aarch64
|
|
58
|
+
// setbuf(stdout, NULL): Apple's stdio names the FILE* global
|
|
59
|
+
// __stdoutp (stdout itself is a macro), so the Mach-O symbol is
|
|
60
|
+
// ___stdoutp — and it must load through the GOT (@GOTPAGE), the
|
|
61
|
+
// only relocation the linker accepts for it. The aarch64 backend
|
|
62
|
+
// always emits Mach-O symbol names (see `bl _printf` above), so
|
|
63
|
+
// this is unconditional like every other core raw body.
|
|
64
|
+
adrp x0, ___stdoutp@GOTPAGE
|
|
65
|
+
ldr x0, [x0, ___stdoutp@GOTPAGEOFF]
|
|
66
|
+
ldr x0, [x0]
|
|
67
|
+
mov x1, #0
|
|
68
|
+
bl _setbuf
|
|
69
|
+
```
|
|
70
|
+
}
|
|
71
|
+
|
|
46
72
|
// Read one line from stdin (newline stripped). The raw body mallocs the
|
|
47
73
|
// buffer — `move out string` transfers ownership to the caller.
|
|
48
74
|
pub func read_line = (move out string) {
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#! /usr/bin/env node
|
|
2
|
-
import { execFile, execFileSync, execSync } from "node:child_process";
|
|
2
|
+
import { execFile, execFileSync, execSync, spawn } 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";
|
|
@@ -2306,6 +2306,13 @@ function parse_mem_inner(inner) {
|
|
|
2306
2306
|
if (tail_shift) mem.scale = 1 << parseInt(tail_shift[1], 10);
|
|
2307
2307
|
continue;
|
|
2308
2308
|
}
|
|
2309
|
+
if (/^[A-Za-z_.$][\w.$]*@[A-Za-z0-9_]+$/.test(comp)) {
|
|
2310
|
+
mem.offset = {
|
|
2311
|
+
kind: "reg",
|
|
2312
|
+
name: comp
|
|
2313
|
+
};
|
|
2314
|
+
continue;
|
|
2315
|
+
}
|
|
2309
2316
|
return null;
|
|
2310
2317
|
}
|
|
2311
2318
|
return mem;
|
|
@@ -7529,6 +7536,13 @@ var DeclarationNode = class extends BaseNode {
|
|
|
7529
7536
|
* a heap shell for the argument (pass-by-value): the callee frees the
|
|
7530
7537
|
* shell itself at scope exit (`free(p)`) after freeing the fields. */
|
|
7531
7538
|
owned_heap_shell;
|
|
7539
|
+
/** True for a `move` param of an owning VALUE-struct type: the caller
|
|
7540
|
+
* passed its storage by address, so the callee owns the CONTENTS but not
|
|
7541
|
+
* the storage. free_scoped_declarations destroys the fields' owned
|
|
7542
|
+
* resources at `(*param)` at scope exit — never a free. Set at build
|
|
7543
|
+
* time by the C function/method builders; consumed by
|
|
7544
|
+
* free_scoped_declarations. */
|
|
7545
|
+
moved_value_struct_param;
|
|
7532
7546
|
constructor(start, visibility, declaration, name, type, value) {
|
|
7533
7547
|
super("declare", start);
|
|
7534
7548
|
this.visibility = visibility;
|
|
@@ -20764,16 +20778,19 @@ function build_function_node$1(node, status) {
|
|
|
20764
20778
|
if (param.is_moved) {
|
|
20765
20779
|
const is_class = !!status.structs.find((s) => s.name === param.type.name && s.is_class);
|
|
20766
20780
|
const is_trait = !!status.traits.find((t) => t.name === param.type.name);
|
|
20767
|
-
|
|
20781
|
+
const vstruct = !is_class && !is_trait ? resolve_struct_type(param.type, status) : void 0;
|
|
20782
|
+
const is_owning_value_struct = !!vstruct && !vstruct.is_class && !param.type.is_nullable && struct_needs_destroy(vstruct, status);
|
|
20783
|
+
if (is_class || is_trait || is_owning_value_struct) {
|
|
20768
20784
|
const reg = callee_map.get(param.name) ?? param_regs[pidx];
|
|
20769
|
-
status.moved_class_params.set(param.name, reg);
|
|
20785
|
+
if (is_class || is_trait) status.moved_class_params.set(param.name, reg);
|
|
20770
20786
|
const save_offset = allocate_stack_space(status, 8);
|
|
20771
20787
|
emit_asm(status, `str ${reg}, [x29, #${save_offset}]\n`);
|
|
20772
20788
|
moved_param_save_slots.set(param.name, {
|
|
20773
20789
|
offset: save_offset,
|
|
20774
20790
|
type_name: param.type.name,
|
|
20775
20791
|
type_args: param.type.type_args,
|
|
20776
|
-
is_nullable: param.type.is_nullable
|
|
20792
|
+
is_nullable: param.type.is_nullable,
|
|
20793
|
+
value_struct_destroy_name: is_owning_value_struct ? vstruct.name : void 0
|
|
20777
20794
|
});
|
|
20778
20795
|
}
|
|
20779
20796
|
}
|
|
@@ -20881,6 +20898,16 @@ function build_function_node$1(node, status) {
|
|
|
20881
20898
|
emit_asm(status, `cmp x0, x1\n`);
|
|
20882
20899
|
emit_asm(status, `beq ${keep_prefix}_${name}\n`);
|
|
20883
20900
|
}
|
|
20901
|
+
if (info.value_struct_destroy_name) {
|
|
20902
|
+
const vstruct = status.structs.find((s) => s.name === info.value_struct_destroy_name);
|
|
20903
|
+
release_heap_string_fields(status, name, info.value_struct_destroy_name);
|
|
20904
|
+
if (vstruct && has_destroy(vstruct)) {
|
|
20905
|
+
emit_asm(status, `ldr x0, [x29, #${info.offset}]\n`);
|
|
20906
|
+
emit_asm(status, `bl ${info.value_struct_destroy_name}_destroy\n`);
|
|
20907
|
+
} else if (vstruct) emit_field_destroys(status, vstruct, name, void 0, void 0, false);
|
|
20908
|
+
if (need_guard) emit_asm(status, `${keep_prefix}_${name}:\n`);
|
|
20909
|
+
continue;
|
|
20910
|
+
}
|
|
20884
20911
|
emit_destroy_for_anchor_slot(status, info.offset, info.type_name, info.type_args, info.is_nullable);
|
|
20885
20912
|
emit_asm(status, `ldr x0, [x29, #${info.offset}]\n`);
|
|
20886
20913
|
emit_free(status);
|
|
@@ -21091,6 +21118,7 @@ function init_nullable_field_default(node, field, base_reg, status) {
|
|
|
21091
21118
|
* sites record it (record_ctor_heap_default_fields) so scope exit frees it.
|
|
21092
21119
|
*/
|
|
21093
21120
|
function init_expr_field_default(node, field, offset, base_reg, status) {
|
|
21121
|
+
status.last_result_is_heap = false;
|
|
21094
21122
|
build_node(field.value, status);
|
|
21095
21123
|
ensure_newline(status);
|
|
21096
21124
|
const t = field.type;
|
|
@@ -22216,7 +22244,10 @@ function build_struct_functions$1(node, status) {
|
|
|
22216
22244
|
const moved_param_save_slots = /* @__PURE__ */ new Map();
|
|
22217
22245
|
for (const param of func.params) {
|
|
22218
22246
|
if (!param.is_moved || param.is_self_param) continue;
|
|
22219
|
-
|
|
22247
|
+
const is_class = !!status.structs.find((s) => s.name === param.type.name && s.is_class);
|
|
22248
|
+
const vstruct = !is_class ? resolve_struct_type(param.type, status) : void 0;
|
|
22249
|
+
const is_owning_value_struct = !!vstruct && !vstruct.is_class && !param.type.is_nullable && struct_needs_destroy(vstruct, status);
|
|
22250
|
+
if (!is_class && !is_owning_value_struct) continue;
|
|
22220
22251
|
const reg = status.function_param_regs.get(param.name);
|
|
22221
22252
|
if (reg) {
|
|
22222
22253
|
const save_offset = allocate_stack_space(status, 8);
|
|
@@ -22225,7 +22256,8 @@ function build_struct_functions$1(node, status) {
|
|
|
22225
22256
|
offset: save_offset,
|
|
22226
22257
|
type_name: param.type.name,
|
|
22227
22258
|
type_args: param.type.type_args,
|
|
22228
|
-
is_nullable: param.type.is_nullable
|
|
22259
|
+
is_nullable: param.type.is_nullable,
|
|
22260
|
+
value_struct_destroy_name: is_owning_value_struct ? vstruct.name : void 0
|
|
22229
22261
|
});
|
|
22230
22262
|
} else {
|
|
22231
22263
|
const offset = status.stack_offsets.get(param.name);
|
|
@@ -22233,7 +22265,8 @@ function build_struct_functions$1(node, status) {
|
|
|
22233
22265
|
offset,
|
|
22234
22266
|
type_name: param.type.name,
|
|
22235
22267
|
type_args: param.type.type_args,
|
|
22236
|
-
is_nullable: param.type.is_nullable
|
|
22268
|
+
is_nullable: param.type.is_nullable,
|
|
22269
|
+
value_struct_destroy_name: is_owning_value_struct ? vstruct.name : void 0
|
|
22237
22270
|
});
|
|
22238
22271
|
}
|
|
22239
22272
|
}
|
|
@@ -22288,6 +22321,16 @@ function build_struct_functions$1(node, status) {
|
|
|
22288
22321
|
emit_asm(status, `cmp x0, x1\n`);
|
|
22289
22322
|
emit_asm(status, `beq ${keep_prefix}_${name}\n`);
|
|
22290
22323
|
}
|
|
22324
|
+
if (info.value_struct_destroy_name) {
|
|
22325
|
+
const vstruct = status.structs.find((s) => s.name === info.value_struct_destroy_name);
|
|
22326
|
+
release_heap_string_fields(status, name, info.value_struct_destroy_name);
|
|
22327
|
+
if (vstruct && has_destroy(vstruct)) {
|
|
22328
|
+
emit_asm(status, `ldr x0, [x29, #${info.offset}]\n`);
|
|
22329
|
+
emit_asm(status, `bl ${info.value_struct_destroy_name}_destroy\n`);
|
|
22330
|
+
} else if (vstruct) emit_field_destroys(status, vstruct, name, void 0, void 0, false);
|
|
22331
|
+
if (ret_is_class) emit_asm(status, `${keep_prefix}_${name}:\n`);
|
|
22332
|
+
continue;
|
|
22333
|
+
}
|
|
22291
22334
|
emit_destroy_for_anchor_slot(status, info.offset, info.type_name, info.type_args, info.is_nullable);
|
|
22292
22335
|
emit_asm(status, `ldr x0, [x29, #${info.offset}]\n`);
|
|
22293
22336
|
emit_free(status);
|
|
@@ -22733,6 +22776,7 @@ function build_block_node$1(node, status) {
|
|
|
22733
22776
|
const prologue = status.module_init_statements;
|
|
22734
22777
|
status.module_init_statements = void 0;
|
|
22735
22778
|
for (const stmt of prologue) {
|
|
22779
|
+
status.last_result_is_heap = false;
|
|
22736
22780
|
emit_allocations$1(stmt, status);
|
|
22737
22781
|
emit_stmt_from_nir$1(stmt, prologue.indexOf(stmt), prologue, status);
|
|
22738
22782
|
}
|
|
@@ -22742,6 +22786,7 @@ function build_block_node$1(node, status) {
|
|
|
22742
22786
|
if (module_init_set.has(child)) continue;
|
|
22743
22787
|
if (!is_trait_node(child) && !is_struct_node(child) && !is_function_node(child) && child.node_type !== "enum" && child.node_type !== "bitset" && !(child.node_type === "declare" && inlined_const_names.has(child.name))) {
|
|
22744
22788
|
if (child.node_type !== "while") emit_allocations$1(child, status);
|
|
22789
|
+
status.last_result_is_heap = false;
|
|
22745
22790
|
const prev_forwarded = status.forwarded_param_inits;
|
|
22746
22791
|
const consumed = emit_stmt_from_nir$1(child, index, node.statements, status);
|
|
22747
22792
|
index += consumed - 1;
|
|
@@ -23918,7 +23963,6 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
|
|
|
23918
23963
|
}
|
|
23919
23964
|
hoist_field_overrides(node.value, build_node, status, "", node.name);
|
|
23920
23965
|
status.last_result_is_heap = false;
|
|
23921
|
-
const prev_heap = status.last_result_is_heap;
|
|
23922
23966
|
if (node.value?.node_type === "value") {
|
|
23923
23967
|
const inlined = status.top_level_consts?.get(node.value.value);
|
|
23924
23968
|
if (inlined?.value) node.value = fold_string_const(inlined, status.top_level_consts) ?? inlined.value;
|
|
@@ -23940,7 +23984,6 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
|
|
|
23940
23984
|
consolidate_temp_anchors$1(status, node.value, node.type.name);
|
|
23941
23985
|
}
|
|
23942
23986
|
}
|
|
23943
|
-
status.last_result_is_heap = prev_heap;
|
|
23944
23987
|
}
|
|
23945
23988
|
if (node.func_params) {
|
|
23946
23989
|
if (status.function_return_label) {
|
|
@@ -24828,8 +24871,12 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
|
|
|
24828
24871
|
emit_init_value$1(node.value, nir_init, status);
|
|
24829
24872
|
ensure_newline(status);
|
|
24830
24873
|
status.int_dest_hint = void 0;
|
|
24831
|
-
if (size === 16 && node.type.name === "string")
|
|
24832
|
-
|
|
24874
|
+
if (size === 16 && node.type.name === "string") {
|
|
24875
|
+
const force_heap_init = (status.force_heap_strings?.has(node.name) ?? false) && raw !== "null";
|
|
24876
|
+
if (force_heap_init) emit_strdup_string(status);
|
|
24877
|
+
emit_pair_store_x29(status, offset);
|
|
24878
|
+
if (force_heap_init) mark_heap_string(status, node.name);
|
|
24879
|
+
} else emit_var_store(status, "x0", node.name, size);
|
|
24833
24880
|
} else if (is_float_type$1(node.type.name)) {
|
|
24834
24881
|
const label = `_float_const_${decl_const_counter++}`;
|
|
24835
24882
|
emit_data(status, `${label}: .double ${raw}\n.p2align 2\n`);
|
|
@@ -31099,6 +31146,10 @@ function build_auto_free(status) {
|
|
|
31099
31146
|
function free_scoped_declarations(status, decls, persist_string_field_records = false) {
|
|
31100
31147
|
let commented = false;
|
|
31101
31148
|
if (status.heap_string_fields?.size) for (const dec of decls) {
|
|
31149
|
+
if (dec.moved_value_struct_param) {
|
|
31150
|
+
const dec_struct = status.structs.find((s) => s.name === mono_type_name(dec.type) && !s.is_simple_type && !s.is_generic);
|
|
31151
|
+
if (dec_struct && has_destroy(dec_struct)) continue;
|
|
31152
|
+
}
|
|
31102
31153
|
const prefix = `${dec.name}.`;
|
|
31103
31154
|
const keys = Array.from(status.heap_string_fields).filter((key) => key.startsWith(prefix));
|
|
31104
31155
|
if (!keys.length) continue;
|
|
@@ -31130,6 +31181,18 @@ function free_scoped_declarations(status, decls, persist_string_field_records =
|
|
|
31130
31181
|
}
|
|
31131
31182
|
if (persist_string_field_records && dec.force_owned_string === true) continue;
|
|
31132
31183
|
if (status.moved?.has(dec.name)) continue;
|
|
31184
|
+
if (dec.moved_value_struct_param) {
|
|
31185
|
+
const mono_name = mono_type_name(dec.type);
|
|
31186
|
+
const param_struct = status.structs.find((s) => s.name === mono_name && !s.is_simple_type && !s.is_generic);
|
|
31187
|
+
if (param_struct && struct_needs_destroy(param_struct, status)) {
|
|
31188
|
+
if (!commented) {
|
|
31189
|
+
status.code += "\n// Auto-free\n";
|
|
31190
|
+
commented = true;
|
|
31191
|
+
}
|
|
31192
|
+
emit_struct_destroys(status, param_struct, `(*${c_function_name(dec.name)})`);
|
|
31193
|
+
}
|
|
31194
|
+
continue;
|
|
31195
|
+
}
|
|
31133
31196
|
const cname = c_function_name(dec.name);
|
|
31134
31197
|
if (dec.type.name === "func") {
|
|
31135
31198
|
if (!commented) {
|
|
@@ -33620,13 +33683,15 @@ function build_assignment_node$1(node, status, nir_rhs, nir_swap) {
|
|
|
33620
33683
|
}
|
|
33621
33684
|
}
|
|
33622
33685
|
}
|
|
33623
|
-
|
|
33686
|
+
const assign_rhs_is_moved_value = node.right_value.node_type === "value" && node.right_value.is_moved;
|
|
33687
|
+
const assign_rhs_is_fresh_call = !node.swap && node.right_value.node_type === "func_call";
|
|
33688
|
+
if (!node.operator && node.left_value.node_type === "access" && node.left_value.access.node_type === "access_field" && (assign_rhs_is_moved_value || assign_rhs_is_fresh_call)) {
|
|
33624
33689
|
const access_lhs = node.left_value;
|
|
33625
33690
|
const field_type = access_lhs.access.type;
|
|
33626
33691
|
const field_resolved = field_type?.name && !field_type.is_view && !field_type.is_array ? resolve_struct_type(field_type, status) : void 0;
|
|
33627
33692
|
const field_struct = field_resolved && !field_resolved.is_class ? field_resolved : void 0;
|
|
33628
33693
|
if (field_struct) {
|
|
33629
|
-
splice_decl_from_c_scopes(status, node.right_value.value);
|
|
33694
|
+
if (assign_rhs_is_moved_value) splice_decl_from_c_scopes(status, node.right_value.value);
|
|
33630
33695
|
const saved_len = begin_code_scratch(status);
|
|
33631
33696
|
build_node$1(node.left_value, status);
|
|
33632
33697
|
const field_access = end_code_scratch(status, saved_len);
|
|
@@ -36845,9 +36910,16 @@ function build_struct_functions(node, status, skip_init = false) {
|
|
|
36845
36910
|
for (const param of func.params) {
|
|
36846
36911
|
if (param.is_self_param) continue;
|
|
36847
36912
|
const param_struct = status.structs.find((s) => s.name === param.type.name);
|
|
36848
|
-
if (param.is_moved
|
|
36913
|
+
if (!param.is_moved) continue;
|
|
36914
|
+
if (param_struct?.is_class && !moved_param_is_consumed(func, param.name, param_struct.name, status.structs)) {
|
|
36849
36915
|
const pname = c_function_name(param.name);
|
|
36850
36916
|
status.scoped_declarations.push(new DeclarationNode(param.start, "private", "move", pname, param.type));
|
|
36917
|
+
} else if (param_struct && !param_struct.is_class && !param_struct.is_simple_type && !param_struct.is_generic && !param.type.is_nullable && struct_needs_destroy(param_struct, status) && !moved_param_is_consumed(func, param.name, param_struct.name, status.structs)) {
|
|
36918
|
+
const pname = c_function_name(param.name);
|
|
36919
|
+
const decl = new DeclarationNode(param.start, "private", "move", pname, param.type);
|
|
36920
|
+
decl.moved_value_struct_param = true;
|
|
36921
|
+
decl.string_fields_via_pointer = true;
|
|
36922
|
+
status.scoped_declarations.push(decl);
|
|
36851
36923
|
}
|
|
36852
36924
|
}
|
|
36853
36925
|
const saved_sig = begin_code_scratch(status);
|
|
@@ -37734,6 +37806,12 @@ function build_function_node(node, status) {
|
|
|
37734
37806
|
if (param_trait && !param_struct?.is_class) decl.trait_class_trait = param.type.name;
|
|
37735
37807
|
status.scoped_declarations.push(decl);
|
|
37736
37808
|
}
|
|
37809
|
+
if (param.is_moved && !moved_owning_param && param_struct && !param_struct.is_class && !param_struct.is_generic && !param.type.is_nullable && node.name !== "main" && struct_needs_destroy(param_struct, status) && !moved_param_is_consumed(node, param.name, param_struct.name, status.structs)) {
|
|
37810
|
+
const decl = new DeclarationNode(param.start, "private", "move", pname, param.type);
|
|
37811
|
+
decl.moved_value_struct_param = true;
|
|
37812
|
+
decl.string_fields_via_pointer = true;
|
|
37813
|
+
status.scoped_declarations.push(decl);
|
|
37814
|
+
}
|
|
37737
37815
|
const is_free_function = (!node.scope || node.scope.node_type === "root" || node.scope.node_type === "func") && !node.params.some((p) => p.is_self_param);
|
|
37738
37816
|
const fn_name = node.name;
|
|
37739
37817
|
const fn_label = emission_label(node);
|
|
@@ -41741,7 +41819,9 @@ function build_assignment_node(node, status, nir_rhs, nir_swap) {
|
|
|
41741
41819
|
const explicit_move_source = !node.swap && !!rhs_value && rhs_value.is_moved && size === 16 && rhs_value.type?.name === "string" && !rhs_value.type?.is_view ? rhs_value.value : void 0;
|
|
41742
41820
|
const rhs_is_borrow_reception = !node.swap && size === 16 && is_string_borrow(node.right_value);
|
|
41743
41821
|
const rhs_is_field_read = !node.swap && size === 16 && node.right_value.node_type === "access" && node.right_value.access.node_type === "access_field";
|
|
41744
|
-
const
|
|
41822
|
+
const rhs_var_name = !node.swap && size === 16 && node.right_value.node_type === "value" && !node.right_value.type?.is_view ? node.right_value.value : void 0;
|
|
41823
|
+
const rhs_is_unrecorded_var_read = rhs_var_name !== void 0 && !status.heap_strings?.has(rhs_var_name);
|
|
41824
|
+
const borrow_needs_dup = (rhs_is_borrow_reception || rhs_is_field_read || rhs_is_unrecorded_var_read) && !!status.force_heap_strings?.has(name);
|
|
41745
41825
|
const move_source_owns_heap = move_source !== void 0 && !!status.heap_strings?.has(move_source);
|
|
41746
41826
|
if (rhs_is_view_into_owned) {
|
|
41747
41827
|
if (!status.heap_strings) status.heap_strings = /* @__PURE__ */ new Set();
|
|
@@ -41751,7 +41831,7 @@ function build_assignment_node(node, status, nir_rhs, nir_swap) {
|
|
|
41751
41831
|
if (move_source !== void 0 && move_source_owns_heap) status.heap_strings?.delete(move_source);
|
|
41752
41832
|
if (!status.heap_strings) status.heap_strings = /* @__PURE__ */ new Set();
|
|
41753
41833
|
status.heap_strings.add(name);
|
|
41754
|
-
} else if (rhs_is_field_read) status.last_result_is_heap = false;
|
|
41834
|
+
} else if (rhs_is_field_read || rhs_is_unrecorded_var_read) status.last_result_is_heap = false;
|
|
41755
41835
|
else if (status.last_result_is_heap) {
|
|
41756
41836
|
if (!status.heap_strings) status.heap_strings = /* @__PURE__ */ new Set();
|
|
41757
41837
|
status.heap_strings.add(name);
|
|
@@ -41969,7 +42049,9 @@ function build_assignment_node(node, status, nir_rhs, nir_swap) {
|
|
|
41969
42049
|
get_base_address(access, status, "x0");
|
|
41970
42050
|
emit_asm(status, `str x0, [sp, #-16]!\n`);
|
|
41971
42051
|
}
|
|
41972
|
-
|
|
42052
|
+
const field_rhs_is_moved_value = node.right_value.node_type === "value" && node.right_value.is_moved;
|
|
42053
|
+
const field_rhs_is_fresh_call = !node.swap && node.right_value.node_type === "func_call";
|
|
42054
|
+
if (!node.operator && (field_rhs_is_moved_value || field_rhs_is_fresh_call)) {
|
|
41973
42055
|
const field_resolved = resolve_struct_type(field_type, status);
|
|
41974
42056
|
const field_target_var = access.target.node_type === "value" ? access.target.value : "";
|
|
41975
42057
|
const first_init_write = is_first_init_field_write(field_target_var, field_name, status);
|
|
@@ -63202,6 +63284,7 @@ function generate_harness(tests, benches) {
|
|
|
63202
63284
|
}).join("\n");
|
|
63203
63285
|
let main = "\nimport System\nimport System::Test\n";
|
|
63204
63286
|
main += "\npub func main = () {\n";
|
|
63287
|
+
main += `\tConsole.unbuffered()\n`;
|
|
63205
63288
|
main += " var Tester t = Tester()\n";
|
|
63206
63289
|
main += " var int64 audit_mark = 0\n";
|
|
63207
63290
|
main += " var int64 audit_leaked = 0\n";
|
|
@@ -63334,7 +63417,7 @@ async function run_test_file(entry_path, lib_path, arch, audit = false, audit_ru
|
|
|
63334
63417
|
result.ms = performance.now() - start;
|
|
63335
63418
|
return result;
|
|
63336
63419
|
}
|
|
63337
|
-
const audit_obj = path.join(buildDir, "
|
|
63420
|
+
const audit_obj = path.join(buildDir, path.basename(entry_path, ".nm") + "_audit.o");
|
|
63338
63421
|
await execFile_async("clang", [
|
|
63339
63422
|
"-c",
|
|
63340
63423
|
runtime_src,
|
|
@@ -63345,8 +63428,11 @@ async function run_test_file(entry_path, lib_path, arch, audit = false, audit_ru
|
|
|
63345
63428
|
const ext = arch === "aarch64" ? ".s" : platform === "macos" || platform === "ios" ? ".m" : ".c";
|
|
63346
63429
|
const codefile = path.join(buildDir, path.basename(entry_path, ".nm") + ext);
|
|
63347
63430
|
const outfile = path.join(buildDir, path.basename(entry_path, ".nm"));
|
|
63348
|
-
|
|
63349
|
-
fs.writeFileSync(
|
|
63431
|
+
const header_name = path.basename(entry_path, ".nm") + ".main.h";
|
|
63432
|
+
fs.writeFileSync(path.join(buildDir, header_name), buildResult.headers ?? "");
|
|
63433
|
+
let code_text = buildResult.code;
|
|
63434
|
+
if (arch !== "aarch64") code_text = code_text.replace("#include \"main.h\"", `#include "${header_name}"`);
|
|
63435
|
+
fs.writeFileSync(codefile, code_text);
|
|
63350
63436
|
let companionfile;
|
|
63351
63437
|
if (buildResult.companion) {
|
|
63352
63438
|
const comp_ext = platform === "macos" || platform === "ios" ? ".m" : ".c";
|
|
@@ -63372,20 +63458,48 @@ async function run_test_file(entry_path, lib_path, arch, audit = false, audit_ru
|
|
|
63372
63458
|
result.ms = performance.now() - start;
|
|
63373
63459
|
return result;
|
|
63374
63460
|
}
|
|
63461
|
+
const run_out = outfile + ".out";
|
|
63462
|
+
const run_err = outfile + ".err";
|
|
63375
63463
|
let runStdout = "";
|
|
63464
|
+
let runStderr = "";
|
|
63376
63465
|
let crashed;
|
|
63466
|
+
const out_fd = fs.openSync(run_out, "w");
|
|
63467
|
+
const err_fd = fs.openSync(run_err, "w");
|
|
63377
63468
|
try {
|
|
63378
|
-
|
|
63379
|
-
encoding: "utf8",
|
|
63469
|
+
const child = spawn(outfile, [], {
|
|
63380
63470
|
timeout: 3e4,
|
|
63381
|
-
|
|
63471
|
+
stdio: [
|
|
63472
|
+
"ignore",
|
|
63473
|
+
out_fd,
|
|
63474
|
+
err_fd
|
|
63475
|
+
],
|
|
63382
63476
|
cwd: cwd ?? process.cwd()
|
|
63383
|
-
})
|
|
63384
|
-
|
|
63385
|
-
|
|
63386
|
-
|
|
63387
|
-
|
|
63477
|
+
});
|
|
63478
|
+
const run = await new Promise((resolve) => {
|
|
63479
|
+
child.on("error", () => resolve({
|
|
63480
|
+
code: null,
|
|
63481
|
+
signal: null
|
|
63482
|
+
}));
|
|
63483
|
+
child.on("close", (code, signal) => resolve({
|
|
63484
|
+
code,
|
|
63485
|
+
signal
|
|
63486
|
+
}));
|
|
63487
|
+
});
|
|
63488
|
+
if (run.signal === "SIGTERM") crashed = "test binary timed out after 30s";
|
|
63489
|
+
else if (run.signal !== null) crashed = `test binary exited abnormally (signal ${run.signal})`;
|
|
63490
|
+
else if (run.code === null) crashed = "test binary could not be run";
|
|
63491
|
+
else if (run.code !== 0) crashed = `test binary exited abnormally (signal ${run.code})`;
|
|
63492
|
+
} finally {
|
|
63493
|
+
fs.closeSync(out_fd);
|
|
63494
|
+
fs.closeSync(err_fd);
|
|
63495
|
+
}
|
|
63496
|
+
runStdout = fs.readFileSync(run_out, "utf8");
|
|
63497
|
+
try {
|
|
63498
|
+
runStderr = fs.readFileSync(run_err, "utf8");
|
|
63499
|
+
} catch {
|
|
63500
|
+
runStderr = "";
|
|
63388
63501
|
}
|
|
63502
|
+
if (crashed && runStderr.trim()) crashed += `\n${runStderr.trim()}`;
|
|
63389
63503
|
const records = parse_records(runStdout);
|
|
63390
63504
|
result.tests = records.tests;
|
|
63391
63505
|
result.fails = records.fails;
|
package/package.json
CHANGED
package/src/test.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { execFile } from "node:child_process";
|
|
1
|
+
import { execFile, spawn } from "node:child_process";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
@@ -220,6 +220,11 @@ export function generate_harness(tests: TestFunction[], benches: BenchFunction[]
|
|
|
220
220
|
// reports as a leaking test instead of leaving the file's exit-time
|
|
221
221
|
// total unattributed.
|
|
222
222
|
main += "\npub func main = () {\n";
|
|
223
|
+
// Unbuffered stdout: a binary that crashes mid-suite still leaves every
|
|
224
|
+
// record it printed (the CLI reads them from the run's .out file), and a
|
|
225
|
+
// crash is diagnosed with the tests that PASSED before it instead of
|
|
226
|
+
// "(crashed before records)".
|
|
227
|
+
main += `\tConsole.unbuffered()\n`;
|
|
223
228
|
main += "\tvar Tester t = Tester()\n";
|
|
224
229
|
main += "\tvar int64 audit_mark = 0\n";
|
|
225
230
|
main += "\tvar int64 audit_leaked = 0\n";
|
|
@@ -398,7 +403,11 @@ export async function run_test_file(
|
|
|
398
403
|
result.ms = performance.now() - start;
|
|
399
404
|
return result;
|
|
400
405
|
}
|
|
401
|
-
|
|
406
|
+
// Per-file audit object: two concurrent workers compiling to the SHARED
|
|
407
|
+
// `audit_runtime.o` raced — a link could read the object mid-rewrite
|
|
408
|
+
// (concurrent `clang -c` open the output with O_TRUNC) and fail, or link
|
|
409
|
+
// a stale slab. The runtime is tiny; recompiling per file costs ~0.1s.
|
|
410
|
+
const audit_obj = path.join(buildDir, path.basename(entry_path, ".nm") + "_audit.o");
|
|
402
411
|
await execFile_async("clang", ["-c", runtime_src, "-o", audit_obj]);
|
|
403
412
|
const platform = default_platform();
|
|
404
413
|
// Match the run/build command's emission: ObjC-bearing sources (GUI code
|
|
@@ -408,10 +417,19 @@ export async function run_test_file(
|
|
|
408
417
|
const ext = arch === "aarch64" ? ".s" : platform === "macos" || platform === "ios" ? ".m" : ".c";
|
|
409
418
|
const codefile = path.join(buildDir, path.basename(entry_path, ".nm") + ext);
|
|
410
419
|
const outfile = path.join(buildDir, path.basename(entry_path, ".nm"));
|
|
411
|
-
// The C backend's generated source does `#include "main.h"`, so
|
|
412
|
-
// header next to the code. (aarch64 inlines everything into the
|
|
413
|
-
|
|
414
|
-
|
|
420
|
+
// The C backend's generated source does `#include "main.h"`, so the
|
|
421
|
+
// header lands next to the code. (aarch64 inlines everything into the
|
|
422
|
+
// .s file and ignores it.) Per-file header name: every test file shares
|
|
423
|
+
// one build folder, and two concurrent C builds writing the SAME
|
|
424
|
+
// `main.h` raced — a compile could read another file's header between
|
|
425
|
+
// the write and the clang read and fail (or mis-declare) spuriously.
|
|
426
|
+
const header_name = path.basename(entry_path, ".nm") + ".main.h";
|
|
427
|
+
fs.writeFileSync(path.join(buildDir, header_name), buildResult.headers ?? "");
|
|
428
|
+
let code_text: string = buildResult.code;
|
|
429
|
+
if (arch !== "aarch64") {
|
|
430
|
+
code_text = code_text.replace('#include "main.h"', `#include "${header_name}"`);
|
|
431
|
+
}
|
|
432
|
+
fs.writeFileSync(codefile, code_text);
|
|
415
433
|
|
|
416
434
|
// Companion C (raw-block functions + the pool/fiber/file-scope C infra):
|
|
417
435
|
// `run`/`build` compile and link it next to the main TU. The aarch64 .s
|
|
@@ -458,30 +476,63 @@ export async function run_test_file(
|
|
|
458
476
|
return result;
|
|
459
477
|
}
|
|
460
478
|
|
|
461
|
-
// Run the binary
|
|
479
|
+
// Run the binary with its stdout/stderr redirected to per-file FILES
|
|
480
|
+
// (next to the binary, doubling as debugging artifacts) instead of node
|
|
481
|
+
// pipes, then read them. (Why not pipes: this runner's parse/build
|
|
482
|
+
// phases block the event loop for tens of seconds at a time. A binary
|
|
483
|
+
// that finished inside such a block exited 0 long before its records
|
|
484
|
+
// were read, but the 30s execFile timeout that became due during the
|
|
485
|
+
// block still fired at loop-resume — and the kill path destroyed the
|
|
486
|
+
// child's unread pipe, silently dropping EVERY record: the file
|
|
487
|
+
// reported "(0 tests)" and passed. Records on disk are immune to
|
|
488
|
+
// anything the parent does after the fact.) The documented CWD
|
|
489
|
+
// contract: the binary runs in the `--in` root (the folder `nomen test`
|
|
490
|
+
// scanned for *.test.nm files), so fixture paths in tests resolve
|
|
491
|
+
// against that folder regardless of where the CLI was invoked from. A
|
|
462
492
|
// non-zero exit (crash, abort) still surfaces whatever records were
|
|
463
|
-
// emitted before the crash
|
|
464
|
-
//
|
|
465
|
-
//
|
|
466
|
-
|
|
467
|
-
|
|
493
|
+
// emitted before the crash (the harness runs its stdout unbuffered, so
|
|
494
|
+
// a crashing binary keeps the records it printed); a timeout is treated
|
|
495
|
+
// as a crash.
|
|
496
|
+
const run_out = outfile + ".out";
|
|
497
|
+
const run_err = outfile + ".err";
|
|
468
498
|
let runStdout = "";
|
|
499
|
+
let runStderr = "";
|
|
469
500
|
let crashed: string | undefined;
|
|
501
|
+
const out_fd = fs.openSync(run_out, "w");
|
|
502
|
+
const err_fd = fs.openSync(run_err, "w");
|
|
470
503
|
try {
|
|
471
|
-
const
|
|
472
|
-
encoding: "utf8",
|
|
504
|
+
const child = spawn(outfile, [], {
|
|
473
505
|
timeout: 30_000,
|
|
474
|
-
|
|
506
|
+
stdio: ["ignore", out_fd, err_fd],
|
|
475
507
|
cwd: cwd ?? process.cwd(),
|
|
476
508
|
});
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
509
|
+
const run = await new Promise<{ code: number | null; signal: string | null }>((resolve) => {
|
|
510
|
+
child.on("error", () => resolve({ code: null, signal: null }));
|
|
511
|
+
child.on("close", (code, signal) => resolve({ code, signal }));
|
|
512
|
+
});
|
|
513
|
+
if (run.signal === "SIGTERM") {
|
|
481
514
|
crashed = "test binary timed out after 30s";
|
|
482
|
-
} else {
|
|
483
|
-
crashed = `test binary exited abnormally (signal ${
|
|
515
|
+
} else if (run.signal !== null) {
|
|
516
|
+
crashed = `test binary exited abnormally (signal ${run.signal})`;
|
|
517
|
+
} else if (run.code === null) {
|
|
518
|
+
crashed = "test binary could not be run";
|
|
519
|
+
} else if (run.code !== 0) {
|
|
520
|
+
crashed = `test binary exited abnormally (signal ${run.code})`;
|
|
484
521
|
}
|
|
522
|
+
} finally {
|
|
523
|
+
fs.closeSync(out_fd);
|
|
524
|
+
fs.closeSync(err_fd);
|
|
525
|
+
}
|
|
526
|
+
runStdout = fs.readFileSync(run_out, "utf8");
|
|
527
|
+
try {
|
|
528
|
+
runStderr = fs.readFileSync(run_err, "utf8");
|
|
529
|
+
} catch {
|
|
530
|
+
runStderr = "";
|
|
531
|
+
}
|
|
532
|
+
if (crashed && runStderr.trim()) {
|
|
533
|
+
// A crashed binary's own diagnosis (a libmalloc abort message, a
|
|
534
|
+
// panic dump) goes to stderr — surface it with the crash record.
|
|
535
|
+
crashed += `\n${runStderr.trim()}`;
|
|
485
536
|
}
|
|
486
537
|
|
|
487
538
|
const records = parse_records(runStdout);
|