nomen-lang 0.6.10 → 0.6.12
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 +158 -34
- 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
|
}
|
|
@@ -20867,10 +20884,12 @@ function build_function_node$1(node, status) {
|
|
|
20867
20884
|
if (moved_param_save_slots.size > 0 && node.name !== "main") {
|
|
20868
20885
|
const need_guard = return_is_class;
|
|
20869
20886
|
const need_save = !!node.return_type?.name;
|
|
20887
|
+
const returns_fat_pair = node.return_type?.name === "string" && !node.return_type.is_array;
|
|
20870
20888
|
let return_save;
|
|
20871
20889
|
if (need_guard || need_save) {
|
|
20872
|
-
return_save = allocate_stack_space(status, 8);
|
|
20873
|
-
emit_asm(status, `
|
|
20890
|
+
return_save = allocate_stack_space(status, returns_fat_pair ? 16 : 8);
|
|
20891
|
+
if (returns_fat_pair) emit_asm(status, `stp x0, x1, [x29, #${return_save}]\n`);
|
|
20892
|
+
else emit_asm(status, `str x0, [x29, #${return_save}]\n`);
|
|
20874
20893
|
}
|
|
20875
20894
|
for (const [name, info] of moved_param_save_slots) {
|
|
20876
20895
|
if (moved_set?.has(name)) continue;
|
|
@@ -20881,12 +20900,25 @@ function build_function_node$1(node, status) {
|
|
|
20881
20900
|
emit_asm(status, `cmp x0, x1\n`);
|
|
20882
20901
|
emit_asm(status, `beq ${keep_prefix}_${name}\n`);
|
|
20883
20902
|
}
|
|
20903
|
+
if (info.value_struct_destroy_name) {
|
|
20904
|
+
const vstruct = status.structs.find((s) => s.name === info.value_struct_destroy_name);
|
|
20905
|
+
release_heap_string_fields(status, name, info.value_struct_destroy_name);
|
|
20906
|
+
if (vstruct && has_destroy(vstruct)) {
|
|
20907
|
+
emit_asm(status, `ldr x0, [x29, #${info.offset}]\n`);
|
|
20908
|
+
emit_asm(status, `bl ${info.value_struct_destroy_name}_destroy\n`);
|
|
20909
|
+
} else if (vstruct) emit_field_destroys(status, vstruct, name, void 0, void 0, false);
|
|
20910
|
+
if (need_guard) emit_asm(status, `${keep_prefix}_${name}:\n`);
|
|
20911
|
+
continue;
|
|
20912
|
+
}
|
|
20884
20913
|
emit_destroy_for_anchor_slot(status, info.offset, info.type_name, info.type_args, info.is_nullable);
|
|
20885
20914
|
emit_asm(status, `ldr x0, [x29, #${info.offset}]\n`);
|
|
20886
20915
|
emit_free(status);
|
|
20887
20916
|
if (need_guard) emit_asm(status, `${keep_prefix}_${name}:\n`);
|
|
20888
20917
|
}
|
|
20889
|
-
if (need_guard || need_save)
|
|
20918
|
+
if (need_guard || need_save) {
|
|
20919
|
+
if (returns_fat_pair) emit_asm(status, `ldp x0, x1, [x29, #${return_save}]\n`);
|
|
20920
|
+
else emit_asm(status, `ldr x0, [x29, #${return_save}]\n`);
|
|
20921
|
+
}
|
|
20890
20922
|
}
|
|
20891
20923
|
const total_stack = Math.ceil((status.stack_size || 0) / 16) * 16;
|
|
20892
20924
|
status.code = status.code.replace(`sub sp, sp, #${stack_placeholder}`, total_stack > 0 ? `sub sp, sp, #${total_stack}` : `// no stack needed`);
|
|
@@ -21091,6 +21123,7 @@ function init_nullable_field_default(node, field, base_reg, status) {
|
|
|
21091
21123
|
* sites record it (record_ctor_heap_default_fields) so scope exit frees it.
|
|
21092
21124
|
*/
|
|
21093
21125
|
function init_expr_field_default(node, field, offset, base_reg, status) {
|
|
21126
|
+
status.last_result_is_heap = false;
|
|
21094
21127
|
build_node(field.value, status);
|
|
21095
21128
|
ensure_newline(status);
|
|
21096
21129
|
const t = field.type;
|
|
@@ -22216,7 +22249,10 @@ function build_struct_functions$1(node, status) {
|
|
|
22216
22249
|
const moved_param_save_slots = /* @__PURE__ */ new Map();
|
|
22217
22250
|
for (const param of func.params) {
|
|
22218
22251
|
if (!param.is_moved || param.is_self_param) continue;
|
|
22219
|
-
|
|
22252
|
+
const is_class = !!status.structs.find((s) => s.name === param.type.name && s.is_class);
|
|
22253
|
+
const vstruct = !is_class ? resolve_struct_type(param.type, status) : void 0;
|
|
22254
|
+
const is_owning_value_struct = !!vstruct && !vstruct.is_class && !param.type.is_nullable && struct_needs_destroy(vstruct, status);
|
|
22255
|
+
if (!is_class && !is_owning_value_struct) continue;
|
|
22220
22256
|
const reg = status.function_param_regs.get(param.name);
|
|
22221
22257
|
if (reg) {
|
|
22222
22258
|
const save_offset = allocate_stack_space(status, 8);
|
|
@@ -22225,7 +22261,8 @@ function build_struct_functions$1(node, status) {
|
|
|
22225
22261
|
offset: save_offset,
|
|
22226
22262
|
type_name: param.type.name,
|
|
22227
22263
|
type_args: param.type.type_args,
|
|
22228
|
-
is_nullable: param.type.is_nullable
|
|
22264
|
+
is_nullable: param.type.is_nullable,
|
|
22265
|
+
value_struct_destroy_name: is_owning_value_struct ? vstruct.name : void 0
|
|
22229
22266
|
});
|
|
22230
22267
|
} else {
|
|
22231
22268
|
const offset = status.stack_offsets.get(param.name);
|
|
@@ -22233,7 +22270,8 @@ function build_struct_functions$1(node, status) {
|
|
|
22233
22270
|
offset,
|
|
22234
22271
|
type_name: param.type.name,
|
|
22235
22272
|
type_args: param.type.type_args,
|
|
22236
|
-
is_nullable: param.type.is_nullable
|
|
22273
|
+
is_nullable: param.type.is_nullable,
|
|
22274
|
+
value_struct_destroy_name: is_owning_value_struct ? vstruct.name : void 0
|
|
22237
22275
|
});
|
|
22238
22276
|
}
|
|
22239
22277
|
}
|
|
@@ -22273,11 +22311,13 @@ function build_struct_functions$1(node, status) {
|
|
|
22273
22311
|
if (moved_param_save_slots.size > 0) {
|
|
22274
22312
|
const ret_is_class = !!func.return_type?.name && !!status.structs.find((s) => s.name === func.return_type.name && s.is_class);
|
|
22275
22313
|
const need_save = !!func.return_type?.name;
|
|
22314
|
+
const returns_fat_pair = func.return_type?.name === "string" && !func.return_type.is_array;
|
|
22276
22315
|
const keep_prefix = `.Lkeep_mparam_${func_label.replace(/[^\w]/g, "_")}`;
|
|
22277
22316
|
let return_save;
|
|
22278
22317
|
if (ret_is_class || need_save) {
|
|
22279
|
-
return_save = allocate_stack_space(status, 8);
|
|
22280
|
-
emit_asm(status, `
|
|
22318
|
+
return_save = allocate_stack_space(status, returns_fat_pair ? 16 : 8);
|
|
22319
|
+
if (returns_fat_pair) emit_asm(status, `stp x0, x1, [x29, #${return_save}]\n`);
|
|
22320
|
+
else emit_asm(status, `str x0, [x29, #${return_save}]\n`);
|
|
22281
22321
|
}
|
|
22282
22322
|
for (const [name, info] of moved_param_save_slots) {
|
|
22283
22323
|
if (status.moved?.has(name) && !moved_before.has(name)) continue;
|
|
@@ -22288,12 +22328,25 @@ function build_struct_functions$1(node, status) {
|
|
|
22288
22328
|
emit_asm(status, `cmp x0, x1\n`);
|
|
22289
22329
|
emit_asm(status, `beq ${keep_prefix}_${name}\n`);
|
|
22290
22330
|
}
|
|
22331
|
+
if (info.value_struct_destroy_name) {
|
|
22332
|
+
const vstruct = status.structs.find((s) => s.name === info.value_struct_destroy_name);
|
|
22333
|
+
release_heap_string_fields(status, name, info.value_struct_destroy_name);
|
|
22334
|
+
if (vstruct && has_destroy(vstruct)) {
|
|
22335
|
+
emit_asm(status, `ldr x0, [x29, #${info.offset}]\n`);
|
|
22336
|
+
emit_asm(status, `bl ${info.value_struct_destroy_name}_destroy\n`);
|
|
22337
|
+
} else if (vstruct) emit_field_destroys(status, vstruct, name, void 0, void 0, false);
|
|
22338
|
+
if (ret_is_class) emit_asm(status, `${keep_prefix}_${name}:\n`);
|
|
22339
|
+
continue;
|
|
22340
|
+
}
|
|
22291
22341
|
emit_destroy_for_anchor_slot(status, info.offset, info.type_name, info.type_args, info.is_nullable);
|
|
22292
22342
|
emit_asm(status, `ldr x0, [x29, #${info.offset}]\n`);
|
|
22293
22343
|
emit_free(status);
|
|
22294
22344
|
if (ret_is_class) emit_asm(status, `${keep_prefix}_${name}:\n`);
|
|
22295
22345
|
}
|
|
22296
|
-
if (ret_is_class || need_save)
|
|
22346
|
+
if (ret_is_class || need_save) {
|
|
22347
|
+
if (returns_fat_pair) emit_asm(status, `ldp x0, x1, [x29, #${return_save}]\n`);
|
|
22348
|
+
else emit_asm(status, `ldr x0, [x29, #${return_save}]\n`);
|
|
22349
|
+
}
|
|
22297
22350
|
}
|
|
22298
22351
|
const total_stack = Math.ceil((status.stack_size || 0) / 16) * 16;
|
|
22299
22352
|
status.code = status.code.replace(`sub sp, sp, #${stack_placeholder}`, total_stack > 0 ? `sub sp, sp, #${total_stack}` : `// no stack needed`);
|
|
@@ -22733,6 +22786,7 @@ function build_block_node$1(node, status) {
|
|
|
22733
22786
|
const prologue = status.module_init_statements;
|
|
22734
22787
|
status.module_init_statements = void 0;
|
|
22735
22788
|
for (const stmt of prologue) {
|
|
22789
|
+
status.last_result_is_heap = false;
|
|
22736
22790
|
emit_allocations$1(stmt, status);
|
|
22737
22791
|
emit_stmt_from_nir$1(stmt, prologue.indexOf(stmt), prologue, status);
|
|
22738
22792
|
}
|
|
@@ -22742,6 +22796,7 @@ function build_block_node$1(node, status) {
|
|
|
22742
22796
|
if (module_init_set.has(child)) continue;
|
|
22743
22797
|
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
22798
|
if (child.node_type !== "while") emit_allocations$1(child, status);
|
|
22799
|
+
status.last_result_is_heap = false;
|
|
22745
22800
|
const prev_forwarded = status.forwarded_param_inits;
|
|
22746
22801
|
const consumed = emit_stmt_from_nir$1(child, index, node.statements, status);
|
|
22747
22802
|
index += consumed - 1;
|
|
@@ -23918,7 +23973,6 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
|
|
|
23918
23973
|
}
|
|
23919
23974
|
hoist_field_overrides(node.value, build_node, status, "", node.name);
|
|
23920
23975
|
status.last_result_is_heap = false;
|
|
23921
|
-
const prev_heap = status.last_result_is_heap;
|
|
23922
23976
|
if (node.value?.node_type === "value") {
|
|
23923
23977
|
const inlined = status.top_level_consts?.get(node.value.value);
|
|
23924
23978
|
if (inlined?.value) node.value = fold_string_const(inlined, status.top_level_consts) ?? inlined.value;
|
|
@@ -23940,7 +23994,6 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
|
|
|
23940
23994
|
consolidate_temp_anchors$1(status, node.value, node.type.name);
|
|
23941
23995
|
}
|
|
23942
23996
|
}
|
|
23943
|
-
status.last_result_is_heap = prev_heap;
|
|
23944
23997
|
}
|
|
23945
23998
|
if (node.func_params) {
|
|
23946
23999
|
if (status.function_return_label) {
|
|
@@ -24828,8 +24881,12 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
|
|
|
24828
24881
|
emit_init_value$1(node.value, nir_init, status);
|
|
24829
24882
|
ensure_newline(status);
|
|
24830
24883
|
status.int_dest_hint = void 0;
|
|
24831
|
-
if (size === 16 && node.type.name === "string")
|
|
24832
|
-
|
|
24884
|
+
if (size === 16 && node.type.name === "string") {
|
|
24885
|
+
const force_heap_init = (status.force_heap_strings?.has(node.name) ?? false) && raw !== "null";
|
|
24886
|
+
if (force_heap_init) emit_strdup_string(status);
|
|
24887
|
+
emit_pair_store_x29(status, offset);
|
|
24888
|
+
if (force_heap_init) mark_heap_string(status, node.name);
|
|
24889
|
+
} else emit_var_store(status, "x0", node.name, size);
|
|
24833
24890
|
} else if (is_float_type$1(node.type.name)) {
|
|
24834
24891
|
const label = `_float_const_${decl_const_counter++}`;
|
|
24835
24892
|
emit_data(status, `${label}: .double ${raw}\n.p2align 2\n`);
|
|
@@ -31099,6 +31156,10 @@ function build_auto_free(status) {
|
|
|
31099
31156
|
function free_scoped_declarations(status, decls, persist_string_field_records = false) {
|
|
31100
31157
|
let commented = false;
|
|
31101
31158
|
if (status.heap_string_fields?.size) for (const dec of decls) {
|
|
31159
|
+
if (dec.moved_value_struct_param) {
|
|
31160
|
+
const dec_struct = status.structs.find((s) => s.name === mono_type_name(dec.type) && !s.is_simple_type && !s.is_generic);
|
|
31161
|
+
if (dec_struct && has_destroy(dec_struct)) continue;
|
|
31162
|
+
}
|
|
31102
31163
|
const prefix = `${dec.name}.`;
|
|
31103
31164
|
const keys = Array.from(status.heap_string_fields).filter((key) => key.startsWith(prefix));
|
|
31104
31165
|
if (!keys.length) continue;
|
|
@@ -31130,6 +31191,18 @@ function free_scoped_declarations(status, decls, persist_string_field_records =
|
|
|
31130
31191
|
}
|
|
31131
31192
|
if (persist_string_field_records && dec.force_owned_string === true) continue;
|
|
31132
31193
|
if (status.moved?.has(dec.name)) continue;
|
|
31194
|
+
if (dec.moved_value_struct_param) {
|
|
31195
|
+
const mono_name = mono_type_name(dec.type);
|
|
31196
|
+
const param_struct = status.structs.find((s) => s.name === mono_name && !s.is_simple_type && !s.is_generic);
|
|
31197
|
+
if (param_struct && struct_needs_destroy(param_struct, status)) {
|
|
31198
|
+
if (!commented) {
|
|
31199
|
+
status.code += "\n// Auto-free\n";
|
|
31200
|
+
commented = true;
|
|
31201
|
+
}
|
|
31202
|
+
emit_struct_destroys(status, param_struct, `(*${c_function_name(dec.name)})`);
|
|
31203
|
+
}
|
|
31204
|
+
continue;
|
|
31205
|
+
}
|
|
31133
31206
|
const cname = c_function_name(dec.name);
|
|
31134
31207
|
if (dec.type.name === "func") {
|
|
31135
31208
|
if (!commented) {
|
|
@@ -33620,13 +33693,15 @@ function build_assignment_node$1(node, status, nir_rhs, nir_swap) {
|
|
|
33620
33693
|
}
|
|
33621
33694
|
}
|
|
33622
33695
|
}
|
|
33623
|
-
|
|
33696
|
+
const assign_rhs_is_moved_value = node.right_value.node_type === "value" && node.right_value.is_moved;
|
|
33697
|
+
const assign_rhs_is_fresh_call = !node.swap && node.right_value.node_type === "func_call";
|
|
33698
|
+
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
33699
|
const access_lhs = node.left_value;
|
|
33625
33700
|
const field_type = access_lhs.access.type;
|
|
33626
33701
|
const field_resolved = field_type?.name && !field_type.is_view && !field_type.is_array ? resolve_struct_type(field_type, status) : void 0;
|
|
33627
33702
|
const field_struct = field_resolved && !field_resolved.is_class ? field_resolved : void 0;
|
|
33628
33703
|
if (field_struct) {
|
|
33629
|
-
splice_decl_from_c_scopes(status, node.right_value.value);
|
|
33704
|
+
if (assign_rhs_is_moved_value) splice_decl_from_c_scopes(status, node.right_value.value);
|
|
33630
33705
|
const saved_len = begin_code_scratch(status);
|
|
33631
33706
|
build_node$1(node.left_value, status);
|
|
33632
33707
|
const field_access = end_code_scratch(status, saved_len);
|
|
@@ -36845,9 +36920,16 @@ function build_struct_functions(node, status, skip_init = false) {
|
|
|
36845
36920
|
for (const param of func.params) {
|
|
36846
36921
|
if (param.is_self_param) continue;
|
|
36847
36922
|
const param_struct = status.structs.find((s) => s.name === param.type.name);
|
|
36848
|
-
if (param.is_moved
|
|
36923
|
+
if (!param.is_moved) continue;
|
|
36924
|
+
if (param_struct?.is_class && !moved_param_is_consumed(func, param.name, param_struct.name, status.structs)) {
|
|
36849
36925
|
const pname = c_function_name(param.name);
|
|
36850
36926
|
status.scoped_declarations.push(new DeclarationNode(param.start, "private", "move", pname, param.type));
|
|
36927
|
+
} 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)) {
|
|
36928
|
+
const pname = c_function_name(param.name);
|
|
36929
|
+
const decl = new DeclarationNode(param.start, "private", "move", pname, param.type);
|
|
36930
|
+
decl.moved_value_struct_param = true;
|
|
36931
|
+
decl.string_fields_via_pointer = true;
|
|
36932
|
+
status.scoped_declarations.push(decl);
|
|
36851
36933
|
}
|
|
36852
36934
|
}
|
|
36853
36935
|
const saved_sig = begin_code_scratch(status);
|
|
@@ -37734,6 +37816,12 @@ function build_function_node(node, status) {
|
|
|
37734
37816
|
if (param_trait && !param_struct?.is_class) decl.trait_class_trait = param.type.name;
|
|
37735
37817
|
status.scoped_declarations.push(decl);
|
|
37736
37818
|
}
|
|
37819
|
+
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)) {
|
|
37820
|
+
const decl = new DeclarationNode(param.start, "private", "move", pname, param.type);
|
|
37821
|
+
decl.moved_value_struct_param = true;
|
|
37822
|
+
decl.string_fields_via_pointer = true;
|
|
37823
|
+
status.scoped_declarations.push(decl);
|
|
37824
|
+
}
|
|
37737
37825
|
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
37826
|
const fn_name = node.name;
|
|
37739
37827
|
const fn_label = emission_label(node);
|
|
@@ -41741,7 +41829,9 @@ function build_assignment_node(node, status, nir_rhs, nir_swap) {
|
|
|
41741
41829
|
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
41830
|
const rhs_is_borrow_reception = !node.swap && size === 16 && is_string_borrow(node.right_value);
|
|
41743
41831
|
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
|
|
41832
|
+
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;
|
|
41833
|
+
const rhs_is_unrecorded_var_read = rhs_var_name !== void 0 && !status.heap_strings?.has(rhs_var_name);
|
|
41834
|
+
const borrow_needs_dup = (rhs_is_borrow_reception || rhs_is_field_read || rhs_is_unrecorded_var_read) && !!status.force_heap_strings?.has(name);
|
|
41745
41835
|
const move_source_owns_heap = move_source !== void 0 && !!status.heap_strings?.has(move_source);
|
|
41746
41836
|
if (rhs_is_view_into_owned) {
|
|
41747
41837
|
if (!status.heap_strings) status.heap_strings = /* @__PURE__ */ new Set();
|
|
@@ -41751,7 +41841,7 @@ function build_assignment_node(node, status, nir_rhs, nir_swap) {
|
|
|
41751
41841
|
if (move_source !== void 0 && move_source_owns_heap) status.heap_strings?.delete(move_source);
|
|
41752
41842
|
if (!status.heap_strings) status.heap_strings = /* @__PURE__ */ new Set();
|
|
41753
41843
|
status.heap_strings.add(name);
|
|
41754
|
-
} else if (rhs_is_field_read) status.last_result_is_heap = false;
|
|
41844
|
+
} else if (rhs_is_field_read || rhs_is_unrecorded_var_read) status.last_result_is_heap = false;
|
|
41755
41845
|
else if (status.last_result_is_heap) {
|
|
41756
41846
|
if (!status.heap_strings) status.heap_strings = /* @__PURE__ */ new Set();
|
|
41757
41847
|
status.heap_strings.add(name);
|
|
@@ -41969,7 +42059,9 @@ function build_assignment_node(node, status, nir_rhs, nir_swap) {
|
|
|
41969
42059
|
get_base_address(access, status, "x0");
|
|
41970
42060
|
emit_asm(status, `str x0, [sp, #-16]!\n`);
|
|
41971
42061
|
}
|
|
41972
|
-
|
|
42062
|
+
const field_rhs_is_moved_value = node.right_value.node_type === "value" && node.right_value.is_moved;
|
|
42063
|
+
const field_rhs_is_fresh_call = !node.swap && node.right_value.node_type === "func_call";
|
|
42064
|
+
if (!node.operator && (field_rhs_is_moved_value || field_rhs_is_fresh_call)) {
|
|
41973
42065
|
const field_resolved = resolve_struct_type(field_type, status);
|
|
41974
42066
|
const field_target_var = access.target.node_type === "value" ? access.target.value : "";
|
|
41975
42067
|
const first_init_write = is_first_init_field_write(field_target_var, field_name, status);
|
|
@@ -63202,6 +63294,7 @@ function generate_harness(tests, benches) {
|
|
|
63202
63294
|
}).join("\n");
|
|
63203
63295
|
let main = "\nimport System\nimport System::Test\n";
|
|
63204
63296
|
main += "\npub func main = () {\n";
|
|
63297
|
+
main += `\tConsole.unbuffered()\n`;
|
|
63205
63298
|
main += " var Tester t = Tester()\n";
|
|
63206
63299
|
main += " var int64 audit_mark = 0\n";
|
|
63207
63300
|
main += " var int64 audit_leaked = 0\n";
|
|
@@ -63334,7 +63427,7 @@ async function run_test_file(entry_path, lib_path, arch, audit = false, audit_ru
|
|
|
63334
63427
|
result.ms = performance.now() - start;
|
|
63335
63428
|
return result;
|
|
63336
63429
|
}
|
|
63337
|
-
const audit_obj = path.join(buildDir, "
|
|
63430
|
+
const audit_obj = path.join(buildDir, path.basename(entry_path, ".nm") + "_audit.o");
|
|
63338
63431
|
await execFile_async("clang", [
|
|
63339
63432
|
"-c",
|
|
63340
63433
|
runtime_src,
|
|
@@ -63345,8 +63438,11 @@ async function run_test_file(entry_path, lib_path, arch, audit = false, audit_ru
|
|
|
63345
63438
|
const ext = arch === "aarch64" ? ".s" : platform === "macos" || platform === "ios" ? ".m" : ".c";
|
|
63346
63439
|
const codefile = path.join(buildDir, path.basename(entry_path, ".nm") + ext);
|
|
63347
63440
|
const outfile = path.join(buildDir, path.basename(entry_path, ".nm"));
|
|
63348
|
-
|
|
63349
|
-
fs.writeFileSync(
|
|
63441
|
+
const header_name = path.basename(entry_path, ".nm") + ".main.h";
|
|
63442
|
+
fs.writeFileSync(path.join(buildDir, header_name), buildResult.headers ?? "");
|
|
63443
|
+
let code_text = buildResult.code;
|
|
63444
|
+
if (arch !== "aarch64") code_text = code_text.replace("#include \"main.h\"", `#include "${header_name}"`);
|
|
63445
|
+
fs.writeFileSync(codefile, code_text);
|
|
63350
63446
|
let companionfile;
|
|
63351
63447
|
if (buildResult.companion) {
|
|
63352
63448
|
const comp_ext = platform === "macos" || platform === "ios" ? ".m" : ".c";
|
|
@@ -63372,20 +63468,48 @@ async function run_test_file(entry_path, lib_path, arch, audit = false, audit_ru
|
|
|
63372
63468
|
result.ms = performance.now() - start;
|
|
63373
63469
|
return result;
|
|
63374
63470
|
}
|
|
63471
|
+
const run_out = outfile + ".out";
|
|
63472
|
+
const run_err = outfile + ".err";
|
|
63375
63473
|
let runStdout = "";
|
|
63474
|
+
let runStderr = "";
|
|
63376
63475
|
let crashed;
|
|
63476
|
+
const out_fd = fs.openSync(run_out, "w");
|
|
63477
|
+
const err_fd = fs.openSync(run_err, "w");
|
|
63377
63478
|
try {
|
|
63378
|
-
|
|
63379
|
-
encoding: "utf8",
|
|
63479
|
+
const child = spawn(outfile, [], {
|
|
63380
63480
|
timeout: 3e4,
|
|
63381
|
-
|
|
63481
|
+
stdio: [
|
|
63482
|
+
"ignore",
|
|
63483
|
+
out_fd,
|
|
63484
|
+
err_fd
|
|
63485
|
+
],
|
|
63382
63486
|
cwd: cwd ?? process.cwd()
|
|
63383
|
-
})
|
|
63384
|
-
|
|
63385
|
-
|
|
63386
|
-
|
|
63387
|
-
|
|
63487
|
+
});
|
|
63488
|
+
const run = await new Promise((resolve) => {
|
|
63489
|
+
child.on("error", () => resolve({
|
|
63490
|
+
code: null,
|
|
63491
|
+
signal: null
|
|
63492
|
+
}));
|
|
63493
|
+
child.on("close", (code, signal) => resolve({
|
|
63494
|
+
code,
|
|
63495
|
+
signal
|
|
63496
|
+
}));
|
|
63497
|
+
});
|
|
63498
|
+
if (run.signal === "SIGTERM") crashed = "test binary timed out after 30s";
|
|
63499
|
+
else if (run.signal !== null) crashed = `test binary exited abnormally (signal ${run.signal})`;
|
|
63500
|
+
else if (run.code === null) crashed = "test binary could not be run";
|
|
63501
|
+
else if (run.code !== 0) crashed = `test binary exited abnormally (signal ${run.code})`;
|
|
63502
|
+
} finally {
|
|
63503
|
+
fs.closeSync(out_fd);
|
|
63504
|
+
fs.closeSync(err_fd);
|
|
63505
|
+
}
|
|
63506
|
+
runStdout = fs.readFileSync(run_out, "utf8");
|
|
63507
|
+
try {
|
|
63508
|
+
runStderr = fs.readFileSync(run_err, "utf8");
|
|
63509
|
+
} catch {
|
|
63510
|
+
runStderr = "";
|
|
63388
63511
|
}
|
|
63512
|
+
if (crashed && runStderr.trim()) crashed += `\n${runStderr.trim()}`;
|
|
63389
63513
|
const records = parse_records(runStdout);
|
|
63390
63514
|
result.tests = records.tests;
|
|
63391
63515
|
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);
|