nomen-lang 0.5.0 → 0.6.0
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/Arena.nm +6 -6
- package/core/System/Buffer.nm +15 -6
- package/core/System/Channel.nm +2 -0
- package/core/System/ClassBuffer.nm +14 -5
- package/core/System/Controls/Button.nm +2 -2
- package/core/System/Controls/CheckBox.nm +2 -2
- package/core/System/Controls/Container.nm +3 -3
- package/core/System/Controls/TextBox.nm +2 -2
- package/core/System/Fiber.nm +85 -5
- package/core/System/Graph.nm +1 -1
- package/core/System/LinkedList.nm +1 -1
- package/core/System/List.nm +3 -3
- package/core/System/Map.nm +2 -2
- package/core/System/Mutex.nm +8 -8
- package/core/System/Option.nm +2 -2
- package/core/System/Random.nm +67 -0
- package/core/System/Result.nm +2 -2
- package/core/System/Set.nm +1 -1
- package/core/System/Spawnable.nm +18 -0
- package/core/System/Stream/File.nm +3 -3
- package/core/System/Stream/Http.nm +5 -5
- package/core/System/Stream/Tcp.nm +6 -6
- package/core/System/String.nm +7 -7
- package/core/System/StringBuilder.nm +1 -1
- package/core/System/Task.nm +42 -8
- package/core/System/Test.nm +20 -11
- package/core/System/Text/CharIndex.nm +8 -8
- package/core/System/Text/Chars.nm +1 -1
- package/core/System/Text/JsonTree.nm +1 -1
- package/core/System/Text/Regex.nm +122 -32
- package/core/System/Text/Utf8.nm +6 -6
- package/core/System/Thread.nm +118 -2
- package/core/System/Tree.nm +1 -1
- package/dist/index.mjs +1166 -1097
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1702,6 +1702,7 @@ const MNEMONICS = {
|
|
|
1702
1702
|
]] },
|
|
1703
1703
|
dup: { shapes: [["f", "f"], ["f", "r"]] },
|
|
1704
1704
|
movz: { shapes: [["r", "i"]] },
|
|
1705
|
+
movk: { shapes: [["r", "i"]] },
|
|
1705
1706
|
mvn: { shapes: [["r", "r"]] },
|
|
1706
1707
|
fmov: { shapes: [
|
|
1707
1708
|
["f", "f"],
|
|
@@ -2108,7 +2109,8 @@ function try_parse_operands(parts) {
|
|
|
2108
2109
|
continue;
|
|
2109
2110
|
}
|
|
2110
2111
|
if (SHIFT_TOKEN_RE.exec(tok) && operands.length > 0) {
|
|
2111
|
-
|
|
2112
|
+
const prev = operands[operands.length - 1];
|
|
2113
|
+
if (prev.kind === "reg" || prev.kind === "imm") continue;
|
|
2112
2114
|
}
|
|
2113
2115
|
return null;
|
|
2114
2116
|
}
|
|
@@ -2159,7 +2161,7 @@ function split_operands(s) {
|
|
|
2159
2161
|
return parts;
|
|
2160
2162
|
}
|
|
2161
2163
|
const IMM_RE = /^#(-?)(0x[0-9a-fA-F]+|\d+)$/;
|
|
2162
|
-
function parse_imm(tok) {
|
|
2164
|
+
function parse_imm$1(tok) {
|
|
2163
2165
|
const m = IMM_RE.exec(tok);
|
|
2164
2166
|
if (!m) return null;
|
|
2165
2167
|
return (m[1] ? -1n : 1n) * (m[2].toLowerCase().startsWith("0x") ? BigInt(m[2]) : BigInt(m[2]));
|
|
@@ -2168,7 +2170,7 @@ function parse_imm(tok) {
|
|
|
2168
2170
|
function parse_pseudo_imm(tok) {
|
|
2169
2171
|
const t = tok.trim();
|
|
2170
2172
|
if (!t.startsWith("=")) return null;
|
|
2171
|
-
const v = parse_imm("#" + t.slice(1));
|
|
2173
|
+
const v = parse_imm$1("#" + t.slice(1));
|
|
2172
2174
|
return v === null && /^=\d+$/.test(t) ? BigInt(t.slice(1)) : v;
|
|
2173
2175
|
}
|
|
2174
2176
|
function parse_reg(tok) {
|
|
@@ -2225,7 +2227,7 @@ function parse_mem(parts, start) {
|
|
|
2225
2227
|
let next = start + 1;
|
|
2226
2228
|
let postOffset;
|
|
2227
2229
|
if (next < parts.length && !parts[next].startsWith("[")) {
|
|
2228
|
-
const v = parse_imm(parts[next]);
|
|
2230
|
+
const v = parse_imm$1(parts[next]);
|
|
2229
2231
|
if (v !== null && !parsed_inner.offset && !parsed_inner.scale) {
|
|
2230
2232
|
postOffset = v;
|
|
2231
2233
|
next++;
|
|
@@ -2248,7 +2250,7 @@ function parse_mem_inner(inner) {
|
|
|
2248
2250
|
const mem = { base };
|
|
2249
2251
|
for (let k = 1; k < comps.length; k++) {
|
|
2250
2252
|
const comp = comps[k];
|
|
2251
|
-
const imm = parse_imm(comp);
|
|
2253
|
+
const imm = parse_imm$1(comp);
|
|
2252
2254
|
if (imm !== null) {
|
|
2253
2255
|
mem.offset = {
|
|
2254
2256
|
kind: "imm",
|
|
@@ -2287,7 +2289,7 @@ function parse_operand(tok) {
|
|
|
2287
2289
|
width: !(cls === "fpr") ? 64 : reg.startsWith("d") ? 64 : /^[vs]\d+$/.test(reg) ? 32 : 128
|
|
2288
2290
|
};
|
|
2289
2291
|
}
|
|
2290
|
-
const imm = parse_imm(trimmed);
|
|
2292
|
+
const imm = parse_imm$1(trimmed);
|
|
2291
2293
|
if (imm !== null) return {
|
|
2292
2294
|
kind: "imm",
|
|
2293
2295
|
value: imm,
|
|
@@ -3734,6 +3736,110 @@ function eliminate_dead_cycle_moves(code) {
|
|
|
3734
3736
|
}
|
|
3735
3737
|
return code;
|
|
3736
3738
|
}
|
|
3739
|
+
//#endregion
|
|
3740
|
+
//#region ../src/build_aarch64/asm_expand_adr.ts
|
|
3741
|
+
/**
|
|
3742
|
+
* Final-address expansion (runs last, after every optimizer that
|
|
3743
|
+
* pattern-matches the `adr` form). `adr` is PC-relative with a ±1 MB reach;
|
|
3744
|
+
* in a large program (allmark's generated spec tests assemble to 1.5-3 MB of
|
|
3745
|
+
* asm) the string and constant labels in `.data` sit more than 1 MB from the
|
|
3746
|
+
* code that uses them, and the assembler rejects every `adr xN, _str_KNN`
|
|
3747
|
+
* with "fixup value out of range".
|
|
3748
|
+
*
|
|
3749
|
+
* Rewrite `adr xN, SYM` into the page-relative pair the vtable install already
|
|
3750
|
+
* uses — `adrp xN, SYM@PAGE` + `add xN, xN, SYM@PAGEOFF` (adrp reaches ±4 GB).
|
|
3751
|
+
*
|
|
3752
|
+
* Only NON-LOCAL symbols are rewritten: a label beginning with `.` (or a
|
|
3753
|
+
* numeric `Nf`/`Nb` local) is intra-function and within reach, so it stays an
|
|
3754
|
+
* `adr` — keeping the emitted text of small programs unchanged.
|
|
3755
|
+
*/
|
|
3756
|
+
const ADR_RE = /^(\s*)adr (x[0-9]+), ([A-Za-z_][A-Za-z0-9_]*)\s*$/;
|
|
3757
|
+
function expand_far_adr_addresses(code) {
|
|
3758
|
+
const lines = code.split("\n");
|
|
3759
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3760
|
+
const m = ADR_RE.exec(lines[i]);
|
|
3761
|
+
if (!m) continue;
|
|
3762
|
+
const [, indent, reg, sym] = m;
|
|
3763
|
+
lines[i] = `${indent}adrp ${reg}, ${sym}@PAGE\n${indent}add ${reg}, ${reg}, ${sym}@PAGEOFF`;
|
|
3764
|
+
}
|
|
3765
|
+
return lines.join("\n");
|
|
3766
|
+
}
|
|
3767
|
+
//#endregion
|
|
3768
|
+
//#region ../src/build_aarch64/asm_expand_mov.ts
|
|
3769
|
+
/**
|
|
3770
|
+
* Large-immediate expansion (final pass, runs after every optimizer so the
|
|
3771
|
+
* passes see the canonical `mov xN, #imm` / `ldr xN, =K` forms).
|
|
3772
|
+
*
|
|
3773
|
+
* Two AArch64 hazards this pass removes:
|
|
3774
|
+
* - The `mov` alias only encodes a 16-bit immediate (movz; movn for small
|
|
3775
|
+
* negatives). An emitter site that hands a bigger constant to the plain
|
|
3776
|
+
* `mov` form — a long string literal's byte length, a big array count, a
|
|
3777
|
+
* large struct size — produces a line the assembler rejects outright.
|
|
3778
|
+
* - `ldr xN, =K` needs a literal pool the assembler must site within ±1 MB
|
|
3779
|
+
* of the load. In a multi-MB program (allmark's generated spec tests) the
|
|
3780
|
+
* pool falls out of range and the assembler reports "fixup value out of
|
|
3781
|
+
* range".
|
|
3782
|
+
*
|
|
3783
|
+
* Both are rewritten to a movz/movk chunk chain (positive) or the
|
|
3784
|
+
* movn + movk form (negative), exactly materializing the 64-bit constant. The
|
|
3785
|
+
* chunks are computed on BigInt so a full-width `uint64` (or a value written
|
|
3786
|
+
* as a hex literal) is preserved — a JS `number` rounds and can spill into a
|
|
3787
|
+
* fifth chunk (`movk …, lsl #64`, which the assembler rejects).
|
|
3788
|
+
*/
|
|
3789
|
+
const MASK64 = (1n << 64n) - 1n;
|
|
3790
|
+
const MASK16 = 65535n;
|
|
3791
|
+
/** Materialize any 64-bit integer constant in `reg`, without a literal pool. */
|
|
3792
|
+
function materialize_immediate(reg, k) {
|
|
3793
|
+
if (k >= 0n && k <= 65535n) return [`mov ${reg}, #${k}`];
|
|
3794
|
+
if (k < 0n && k >= -65536n) return [`movn ${reg}, #${-k - 1n}`];
|
|
3795
|
+
const v = k & MASK64;
|
|
3796
|
+
const chunk = (i) => v >> BigInt(16 * i) & MASK16;
|
|
3797
|
+
if (k >= 0n) {
|
|
3798
|
+
const out = [`movz ${reg}, #${chunk(0)}`];
|
|
3799
|
+
for (let i = 1; i < 4; i++) {
|
|
3800
|
+
const c = chunk(i);
|
|
3801
|
+
if (c !== 0n) out.push(`movk ${reg}, #${c}, lsl #${i * 16}`);
|
|
3802
|
+
}
|
|
3803
|
+
return out;
|
|
3804
|
+
}
|
|
3805
|
+
return [
|
|
3806
|
+
`movn ${reg}, #${~chunk(0) & MASK16}`,
|
|
3807
|
+
`movk ${reg}, #${chunk(1)}, lsl #16`,
|
|
3808
|
+
`movk ${reg}, #${chunk(2)}, lsl #32`,
|
|
3809
|
+
`movk ${reg}, #${chunk(3)}, lsl #48`
|
|
3810
|
+
];
|
|
3811
|
+
}
|
|
3812
|
+
function parse_imm(text) {
|
|
3813
|
+
try {
|
|
3814
|
+
return BigInt(text);
|
|
3815
|
+
} catch {
|
|
3816
|
+
return null;
|
|
3817
|
+
}
|
|
3818
|
+
}
|
|
3819
|
+
const MOV_IMM_RE = /^(\s*)mov (x|w)([0-9]+), #(-?\d+)$/;
|
|
3820
|
+
const LDR_POOL_RE = /^(\s*)ldr (x[0-9]+), =(-?\d+)$/;
|
|
3821
|
+
function expand_large_mov_immediates(code) {
|
|
3822
|
+
const lines = code.split("\n");
|
|
3823
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3824
|
+
const m = MOV_IMM_RE.exec(lines[i]);
|
|
3825
|
+
if (m) {
|
|
3826
|
+
const [, indent, cls, num, imm_text] = m;
|
|
3827
|
+
const k = parse_imm(imm_text);
|
|
3828
|
+
if (k === null) continue;
|
|
3829
|
+
if (k >= -65536n && k <= 65535n) continue;
|
|
3830
|
+
const reg = `${cls}${num}`;
|
|
3831
|
+
lines[i] = materialize_immediate(reg, k).map((line) => `${indent}${line}`).join("\n");
|
|
3832
|
+
continue;
|
|
3833
|
+
}
|
|
3834
|
+
const p = LDR_POOL_RE.exec(lines[i]);
|
|
3835
|
+
if (!p) continue;
|
|
3836
|
+
const [, indent, reg, imm_text] = p;
|
|
3837
|
+
const k = parse_imm(imm_text);
|
|
3838
|
+
if (k === null) continue;
|
|
3839
|
+
lines[i] = materialize_immediate(reg, k).map((line) => `${indent}${line}`).join("\n");
|
|
3840
|
+
}
|
|
3841
|
+
return lines.join("\n");
|
|
3842
|
+
}
|
|
3737
3843
|
const LABEL_LINE_RE$2 = /^([A-Za-z_.$][\w.$]*|\d+):$/;
|
|
3738
3844
|
const NUMERIC_LABEL_RE = /^\d+$/;
|
|
3739
3845
|
const B_ALIAS_RE = /^(eq|ne|lt|le|gt|ge|hs|lo|ls|hi|mi|pl)$/;
|
|
@@ -6309,36 +6415,24 @@ var AccessFunctionCallNode = class extends BaseNode {
|
|
|
6309
6415
|
* escape-hatch call (target type is `Nursery`, method name is `start`).
|
|
6310
6416
|
* The build phase reads `function_return_type` and emits the spawn
|
|
6311
6417
|
* trampoline against the passed Nursery's runtime futures/count pointers.
|
|
6312
|
-
* See ASYNC.md
|
|
6418
|
+
* See ASYNC.md, "Nursery escape hatch".
|
|
6313
6419
|
*/
|
|
6314
6420
|
is_nursery_spawn;
|
|
6315
6421
|
/**
|
|
6316
|
-
*
|
|
6317
|
-
*
|
|
6318
|
-
*
|
|
6319
|
-
*
|
|
6422
|
+
* RETIRED with the pre-migration launch dispatch: `.start()`/`.detach()`
|
|
6423
|
+
* are ordinary methods on the library `Thread` class now (docs/ASYNC.md).
|
|
6424
|
+
* The flags remain only because monomorphized clones can carry them from
|
|
6425
|
+
* older stamps; nothing sets or consumes them on fresh checks.
|
|
6320
6426
|
*/
|
|
6321
6427
|
is_thread_start;
|
|
6322
|
-
/**
|
|
6323
|
-
* Set during checking when this is a `Thread(fn(args)).detach()` call —
|
|
6324
|
-
* the daemon form. The build synthesizes a SpawnNode from the wrapped
|
|
6325
|
-
* call and emits a detached-pthread trampoline: never a pool worker,
|
|
6326
|
-
* nobody joins it, process exit kills it mid-execution by design (the
|
|
6327
|
-
* std::thread::spawn contract). See ASYNC.md, "Daemon tasks".
|
|
6328
|
-
*/
|
|
6428
|
+
/** Retired — see is_thread_start. */
|
|
6329
6429
|
is_thread_detach;
|
|
6330
|
-
/**
|
|
6331
|
-
* Set during checking when this is a `Fiber(fn(args)).start()` call —
|
|
6332
|
-
* the fiber flavor of a direct spawn. The build synthesizes a SpawnNode
|
|
6333
|
-
* from the wrapped call and emits the fiber trampoline (the launch goes
|
|
6334
|
-
* to the fiber scheduler instead of the pool). See ASYNC.md.
|
|
6335
|
-
*/
|
|
6430
|
+
/** Retired — see is_thread_start. */
|
|
6336
6431
|
is_fiber_start;
|
|
6337
6432
|
/**
|
|
6338
|
-
*
|
|
6339
|
-
*
|
|
6340
|
-
*
|
|
6341
|
-
* ASYNC.md.
|
|
6433
|
+
* RETIRED with the pre-migration launch dispatch: `.start_on(buf)` is
|
|
6434
|
+
* checker-validated and rewritten to the ordinary `Fiber.start` method
|
|
6435
|
+
* (docs/ASYNC.md). Kept only for old clone stamps.
|
|
6342
6436
|
*/
|
|
6343
6437
|
is_fiber_start_on;
|
|
6344
6438
|
/**
|
|
@@ -6520,16 +6614,17 @@ var FunctionCallNode = class extends BaseNode {
|
|
|
6520
6614
|
/** Fiber flavor of the magic spawn constructor (see is_thread_ctor). */
|
|
6521
6615
|
is_fiber_ctor;
|
|
6522
6616
|
/**
|
|
6523
|
-
* The
|
|
6524
|
-
*
|
|
6525
|
-
*
|
|
6526
|
-
* spawn
|
|
6527
|
-
*
|
|
6528
|
-
*
|
|
6529
|
-
*
|
|
6530
|
-
*
|
|
6531
|
-
*
|
|
6532
|
-
* the Thread/Fiber
|
|
6617
|
+
* The catch-all spawn-constructor flavor: a `#spawn`-bearing class that
|
|
6618
|
+
* is neither Thread nor Fiber (a user class declaring its own
|
|
6619
|
+
* construction hook — ASYNC.md). The construction is keyed
|
|
6620
|
+
* on the `#spawn` member, never on Awaitable conformance (the old
|
|
6621
|
+
* field-contract sugar retired; see docs/ASYNC.md, "Design decisions"). `X(fn(args))` packs the
|
|
6622
|
+
* wrapped call eagerly and yields a heap `X` instance (monomorphized
|
|
6623
|
+
* `X<T>` when generic) whose fields carry the launch machinery (the
|
|
6624
|
+
* uint64 task / result_slot / cancel_flag / future contract the
|
|
6625
|
+
* Task.* seam drives); the class's own launch methods consume them.
|
|
6626
|
+
* Typed/stamped by check_magic_ctor exactly like the Thread/Fiber
|
|
6627
|
+
* flavors.
|
|
6533
6628
|
*/
|
|
6534
6629
|
is_awaitable_ctor;
|
|
6535
6630
|
/**
|
|
@@ -7351,7 +7446,7 @@ function scan_self_string_field_writes(struct, method) {
|
|
|
7351
7446
|
const scan_method = (func) => {
|
|
7352
7447
|
if (visited.has(func.name)) return;
|
|
7353
7448
|
visited.add(func.name);
|
|
7354
|
-
walk$
|
|
7449
|
+
walk$3(func.statements ?? [], (n) => {
|
|
7355
7450
|
if (n.node_type === "assign") {
|
|
7356
7451
|
const lhs = n.left_value;
|
|
7357
7452
|
if (lhs?.node_type !== "access") return;
|
|
@@ -7395,10 +7490,10 @@ function drop_self_written_string_field_records(status, receiver_name, fields) {
|
|
|
7395
7490
|
* statement arrays) — skipping `parent`/`scope` back-references and NOT
|
|
7396
7491
|
* descending INTO nested `func`/`struct`/`trait` declarations (a nested
|
|
7397
7492
|
* function's body is a separate function, not part of this one's writes). */
|
|
7398
|
-
function walk$
|
|
7493
|
+
function walk$3(value, cb) {
|
|
7399
7494
|
if (!value || typeof value !== "object") return;
|
|
7400
7495
|
if (Array.isArray(value)) {
|
|
7401
|
-
for (const item of value) walk$
|
|
7496
|
+
for (const item of value) walk$3(item, cb);
|
|
7402
7497
|
return;
|
|
7403
7498
|
}
|
|
7404
7499
|
const n = value;
|
|
@@ -7407,7 +7502,7 @@ function walk$2(value, cb) {
|
|
|
7407
7502
|
if (is_boundary) return;
|
|
7408
7503
|
for (const key of Object.keys(value)) {
|
|
7409
7504
|
if (key === "parent" || key === "scope" || key === "node_type") continue;
|
|
7410
|
-
walk$
|
|
7505
|
+
walk$3(value[key], cb);
|
|
7411
7506
|
}
|
|
7412
7507
|
}
|
|
7413
7508
|
//#endregion
|
|
@@ -8983,186 +9078,6 @@ var AsyncBlockNode = class extends BaseNode {
|
|
|
8983
9078
|
}
|
|
8984
9079
|
};
|
|
8985
9080
|
//#endregion
|
|
8986
|
-
//#region ../src/build_aarch64/utils/aarch64_size.ts
|
|
8987
|
-
/**
|
|
8988
|
-
* Storage width of a value of `type` in bytes, derived from the
|
|
8989
|
-
* built-in type table (see BuiltInTypeInfo.bytes). Non-built-in names are
|
|
8990
|
-
* structs/enums/generics — 8 unless overridden by struct layout. `string`
|
|
8991
|
-
* is the fat 16-byte { char* ptr; long len; } pair; in registers it rides
|
|
8992
|
-
* as a consecutive (ptr, len) pair — the same ABI `view T` uses.
|
|
8993
|
-
*/
|
|
8994
|
-
function aarch64_size(type) {
|
|
8995
|
-
if (type === "string") return 16;
|
|
8996
|
-
return get_built_in_type(type)?.bytes ?? 8;
|
|
8997
|
-
}
|
|
8998
|
-
//#endregion
|
|
8999
|
-
//#region ../src/build_aarch64/utils/stack_var.ts
|
|
9000
|
-
function allocate_stack_space(status, size, alignment = 8) {
|
|
9001
|
-
if (!status.stack_size) status.stack_size = 0;
|
|
9002
|
-
const aligned_size = Math.ceil(status.stack_size / alignment) * alignment;
|
|
9003
|
-
status.stack_size = aligned_size + size;
|
|
9004
|
-
return aligned_size;
|
|
9005
|
-
}
|
|
9006
|
-
/**
|
|
9007
|
-
* Load a stack slot into a promoted (callee-saved) register with the slot's
|
|
9008
|
-
* width. A sub-word slot (bool/char/int8 = strb, int16 = strh, int32 = str w)
|
|
9009
|
-
* must be loaded with the matching zero-extending ldrb/ldrh/ldr w — a
|
|
9010
|
-
* full-width `ldr` picks up dirty stack bytes above the slot and a bool can
|
|
9011
|
-
* read as true regardless of its stored value.
|
|
9012
|
-
*/
|
|
9013
|
-
function emit_promoted_load(status, reg, offset, type_name) {
|
|
9014
|
-
const size = aarch64_size(type_name);
|
|
9015
|
-
if (size === 1) emit_asm(status, `ldrb ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
|
|
9016
|
-
else if (size === 2) emit_asm(status, `ldrh ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
|
|
9017
|
-
else if (size === 4) emit_asm(status, `ldr ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
|
|
9018
|
-
else emit_asm(status, `ldr ${reg}, [x29, #${offset}]\n`);
|
|
9019
|
-
}
|
|
9020
|
-
/**
|
|
9021
|
-
* Write a promoted (callee-saved) register back to its stack slot with the
|
|
9022
|
-
* slot's width. A full-width `str` into a sub-word slot clobbers the adjacent
|
|
9023
|
-
* stack bytes — whatever variable lives there gets corrupted.
|
|
9024
|
-
*/
|
|
9025
|
-
function emit_promoted_store(status, reg, offset, type_name) {
|
|
9026
|
-
const size = aarch64_size(type_name);
|
|
9027
|
-
if (size === 1) emit_asm(status, `strb ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
|
|
9028
|
-
else if (size === 2) emit_asm(status, `strh ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
|
|
9029
|
-
else if (size === 4) emit_asm(status, `str ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
|
|
9030
|
-
else emit_asm(status, `str ${reg}, [x29, #${offset}]\n`);
|
|
9031
|
-
}
|
|
9032
|
-
function is_local_ref_var(name, status) {
|
|
9033
|
-
return !!status.function_ref_params?.has(name) && !status.function_param_regs?.has(name);
|
|
9034
|
-
}
|
|
9035
|
-
function emit_var_address(status, reg, name) {
|
|
9036
|
-
if (status.closure_env_offsets?.has(name) && status.closure_env_slot !== void 0) {
|
|
9037
|
-
emit_asm(status, `ldr ${reg}, [x29, #${status.closure_env_slot}]\n`);
|
|
9038
|
-
const off = status.closure_env_offsets.get(name);
|
|
9039
|
-
if (off !== 0) emit_asm(status, `add ${reg}, ${reg}, #${off}\n`);
|
|
9040
|
-
return;
|
|
9041
|
-
}
|
|
9042
|
-
const alloc_reg = status.register_allocations?.get(name);
|
|
9043
|
-
if (alloc_reg) {
|
|
9044
|
-
const offset = status.stack_offsets?.get(name);
|
|
9045
|
-
if (offset !== void 0) {
|
|
9046
|
-
const type_name = status.scoped_declarations.find((d) => d.name === name)?.type?.name || status.variable_types?.get(name)?.name || "";
|
|
9047
|
-
if (type_name) emit_promoted_store(status, alloc_reg, offset, type_name);
|
|
9048
|
-
else emit_asm(status, `str ${alloc_reg}, [x29, #${offset}]\n`);
|
|
9049
|
-
}
|
|
9050
|
-
}
|
|
9051
|
-
const offset = status.stack_offsets?.get(name);
|
|
9052
|
-
if (offset !== void 0) {
|
|
9053
|
-
if (status.function_struct_param_slots?.has(name)) emit_asm(status, `ldr ${reg}, [x29, #${offset}]\n`);
|
|
9054
|
-
else emit_asm(status, `add ${reg}, x29, #${offset}\n`);
|
|
9055
|
-
} else {
|
|
9056
|
-
const param_reg = status.function_param_regs?.get(name);
|
|
9057
|
-
if (param_reg) emit_asm(status, `mov ${reg}, ${param_reg}\n`);
|
|
9058
|
-
else emit_asm(status, `adr ${reg}, ${name}\n`);
|
|
9059
|
-
}
|
|
9060
|
-
}
|
|
9061
|
-
function emit_deref_var_address(status, reg, name) {
|
|
9062
|
-
emit_var_address(status, reg, name);
|
|
9063
|
-
if (is_local_ref_var(name, status)) emit_asm(status, `ldr ${reg}, [${reg}]\n`);
|
|
9064
|
-
}
|
|
9065
|
-
function emit_var_load(status, reg, name, size) {
|
|
9066
|
-
if (name === "null") {
|
|
9067
|
-
emit_asm(status, `mov ${reg}, #0\n`);
|
|
9068
|
-
return;
|
|
9069
|
-
}
|
|
9070
|
-
if (status.closure_env_offsets?.has(name) && status.closure_env_slot !== void 0) {
|
|
9071
|
-
const off = status.closure_env_offsets.get(name);
|
|
9072
|
-
emit_asm(status, `ldr ${reg}, [x29, #${status.closure_env_slot}]\n`);
|
|
9073
|
-
if (size === 16 && reg.startsWith("x")) {
|
|
9074
|
-
emit_asm(status, `ldr x${parseInt(reg.substring(1), 10) + 1}, [${reg}, #${off + 8}]\n`);
|
|
9075
|
-
emit_asm(status, `ldr ${reg}, [${reg}, #${off}]\n`);
|
|
9076
|
-
} else if (size === 1) emit_asm(status, `ldrb ${reg.replace("x", "w")}, [${reg}, #${off}]\n`);
|
|
9077
|
-
else if (size === 2) emit_asm(status, `ldrh ${reg.replace("x", "w")}, [${reg}, #${off}]\n`);
|
|
9078
|
-
else if (size === 4) emit_asm(status, `ldr ${reg.replace("x", "w")}, [${reg}, #${off}]\n`);
|
|
9079
|
-
else emit_asm(status, `ldr ${reg}, [${reg}, #${off}]\n`);
|
|
9080
|
-
return;
|
|
9081
|
-
}
|
|
9082
|
-
const alloc_reg = status.register_allocations?.get(name);
|
|
9083
|
-
if (alloc_reg) {
|
|
9084
|
-
if (alloc_reg.startsWith("d") && reg.startsWith("d")) {
|
|
9085
|
-
if (reg !== alloc_reg) emit_asm(status, `fmov ${reg}, ${alloc_reg}\n`);
|
|
9086
|
-
} else if (alloc_reg.startsWith("d")) emit_asm(status, `fmov ${reg}, ${alloc_reg}\n`);
|
|
9087
|
-
else if (reg !== alloc_reg) emit_asm(status, `mov ${reg}, ${alloc_reg}\n`);
|
|
9088
|
-
return;
|
|
9089
|
-
}
|
|
9090
|
-
const offset = status.stack_offsets?.get(name);
|
|
9091
|
-
if (offset !== void 0) {
|
|
9092
|
-
if (size === 16 && reg.startsWith("x")) {
|
|
9093
|
-
const n = parseInt(reg.substring(1), 10);
|
|
9094
|
-
if (offset + 8 > 504) {
|
|
9095
|
-
emit_asm(status, `ldr ${reg}, [x29, #${offset}]\n`);
|
|
9096
|
-
emit_asm(status, `ldr x${n + 1}, [x29, #${offset + 8}]\n`);
|
|
9097
|
-
} else emit_asm(status, `ldp ${reg}, x${n + 1}, [x29, #${offset}]\n`);
|
|
9098
|
-
return;
|
|
9099
|
-
}
|
|
9100
|
-
if (size === 1) emit_asm(status, `ldrb ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
|
|
9101
|
-
else if (size === 2) emit_asm(status, `ldrh ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
|
|
9102
|
-
else if (size === 4) emit_asm(status, `ldr ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
|
|
9103
|
-
else emit_asm(status, `ldr ${reg}, [x29, #${offset}]\n`);
|
|
9104
|
-
} else {
|
|
9105
|
-
const param_reg = status.function_param_regs?.get(name);
|
|
9106
|
-
if (param_reg) {
|
|
9107
|
-
if (reg !== param_reg) emit_asm(status, `mov ${reg}, ${param_reg}\n`);
|
|
9108
|
-
} else {
|
|
9109
|
-
emit_asm(status, `adr ${reg}, ${name}\n`);
|
|
9110
|
-
if (size === 1) emit_asm(status, `ldrb ${reg.replace("x", "w")}, [${reg}]\n`);
|
|
9111
|
-
else if (size === 2) emit_asm(status, `ldrh ${reg.replace("x", "w")}, [${reg}]\n`);
|
|
9112
|
-
else if (size === 4) emit_asm(status, `ldr ${reg.replace("x", "w")}, [${reg}]\n`);
|
|
9113
|
-
else emit_asm(status, `ldr ${reg}, [${reg}]\n`);
|
|
9114
|
-
}
|
|
9115
|
-
}
|
|
9116
|
-
}
|
|
9117
|
-
function emit_var_store(status, reg, name, size) {
|
|
9118
|
-
if (status.closure_env_offsets?.has(name) && status.closure_env_slot !== void 0) {
|
|
9119
|
-
const off = status.closure_env_offsets.get(name);
|
|
9120
|
-
emit_asm(status, `ldr x9, [x29, #${status.closure_env_slot}]\n`);
|
|
9121
|
-
if (size === 16 && reg.startsWith("x")) {
|
|
9122
|
-
const n = parseInt(reg.substring(1), 10);
|
|
9123
|
-
emit_asm(status, `str ${reg}, [x9, #${off}]\n`);
|
|
9124
|
-
emit_asm(status, `str x${n + 1}, [x9, #${off + 8}]\n`);
|
|
9125
|
-
} else if (size === 1) emit_asm(status, `strb ${reg.replace("x", "w")}, [x9, #${off}]\n`);
|
|
9126
|
-
else if (size === 2) emit_asm(status, `strh ${reg.replace("x", "w")}, [x9, #${off}]\n`);
|
|
9127
|
-
else if (size === 4) emit_asm(status, `str ${reg.replace("x", "w")}, [x9, #${off}]\n`);
|
|
9128
|
-
else emit_asm(status, `str ${reg}, [x9, #${off}]\n`);
|
|
9129
|
-
return;
|
|
9130
|
-
}
|
|
9131
|
-
const alloc_reg = status.register_allocations?.get(name);
|
|
9132
|
-
if (alloc_reg) {
|
|
9133
|
-
if (alloc_reg.startsWith("d") && reg.startsWith("d")) {
|
|
9134
|
-
if (reg !== alloc_reg) emit_asm(status, `fmov ${alloc_reg}, ${reg}\n`);
|
|
9135
|
-
} else if (alloc_reg.startsWith("d")) emit_asm(status, `fmov ${alloc_reg}, ${reg}\n`);
|
|
9136
|
-
else if (reg !== alloc_reg) emit_asm(status, `mov ${alloc_reg}, ${reg}\n`);
|
|
9137
|
-
return;
|
|
9138
|
-
}
|
|
9139
|
-
const offset = status.stack_offsets?.get(name);
|
|
9140
|
-
if (offset !== void 0) {
|
|
9141
|
-
if (size === 16 && reg.startsWith("x")) {
|
|
9142
|
-
const n = parseInt(reg.substring(1), 10);
|
|
9143
|
-
if (offset + 8 > 504) {
|
|
9144
|
-
emit_asm(status, `str ${reg}, [x29, #${offset}]\n`);
|
|
9145
|
-
emit_asm(status, `str x${n + 1}, [x29, #${offset + 8}]\n`);
|
|
9146
|
-
} else emit_asm(status, `stp ${reg}, x${n + 1}, [x29, #${offset}]\n`);
|
|
9147
|
-
return;
|
|
9148
|
-
}
|
|
9149
|
-
if (size === 1) emit_asm(status, `strb ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
|
|
9150
|
-
else if (size === 2) emit_asm(status, `strh ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
|
|
9151
|
-
else if (size === 4) emit_asm(status, `str ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
|
|
9152
|
-
else emit_asm(status, `str ${reg}, [x29, #${offset}]\n`);
|
|
9153
|
-
} else {
|
|
9154
|
-
const param_reg = status.function_param_regs?.get(name);
|
|
9155
|
-
if (param_reg) emit_asm(status, `mov ${param_reg}, ${reg}\n`);
|
|
9156
|
-
else {
|
|
9157
|
-
emit_asm(status, `adr x1, ${name}\n`);
|
|
9158
|
-
if (size === 1) emit_asm(status, `strb ${reg.replace("x", "w")}, [x1]\n`);
|
|
9159
|
-
else if (size === 2) emit_asm(status, `strh ${reg.replace("x", "w")}, [x1]\n`);
|
|
9160
|
-
else if (size === 4) emit_asm(status, `str ${reg.replace("x", "w")}, [x1]\n`);
|
|
9161
|
-
else emit_asm(status, `str ${reg}, [x1]\n`);
|
|
9162
|
-
}
|
|
9163
|
-
}
|
|
9164
|
-
}
|
|
9165
|
-
//#endregion
|
|
9166
9081
|
//#region ../src/build_aarch64/build_spawn_node.ts
|
|
9167
9082
|
/**
|
|
9168
9083
|
* Pool infrastructure emitted as file-scope C on the first spawn.
|
|
@@ -9234,6 +9149,14 @@ struct nomen_future {
|
|
|
9234
9149
|
// The fiber whose task this future is (set at fiber spawn). Cancellation
|
|
9235
9150
|
// uses it to wake a fiber parked on a primitive waitq.
|
|
9236
9151
|
struct nomen_fiber *owning_fiber;
|
|
9152
|
+
// 1 when the result slot holds a FAT string (set at construction, which
|
|
9153
|
+
// knows the wrapped call's return type). The last release frees an
|
|
9154
|
+
// unconsumed string buffer from the cell: result() zeroes the cell when
|
|
9155
|
+
// it moves the value out, and Task's #destroy zeroes after its own
|
|
9156
|
+
// free, so a non-NULL ptr at the last release means "never consumed".
|
|
9157
|
+
// Appended LAST: raw bodies read refs/result_slot/owner_args at fixed
|
|
9158
|
+
// offsets (#116/#128/#136), which must not shift.
|
|
9159
|
+
int slot_fat;
|
|
9237
9160
|
};
|
|
9238
9161
|
// Fiber seam (defined in FIBER_HEADER_C, appended after this block).
|
|
9239
9162
|
// Declared here — after struct nomen_future is complete — so the
|
|
@@ -9343,6 +9266,15 @@ int __nomen_future_timedwait(struct nomen_future *f, long long deadline_ms) {
|
|
|
9343
9266
|
pthread_mutex_unlock(&f->mu);
|
|
9344
9267
|
return 1;
|
|
9345
9268
|
}
|
|
9269
|
+
// Non-blocking completion peek (Task.is_done) — non-static: raw asm
|
|
9270
|
+
// branches into it.
|
|
9271
|
+
int __nomen_future_is_done(struct nomen_future *f) {
|
|
9272
|
+
if (!f) return 0;
|
|
9273
|
+
pthread_mutex_lock(&f->mu);
|
|
9274
|
+
int done = f->done;
|
|
9275
|
+
pthread_mutex_unlock(&f->mu);
|
|
9276
|
+
return done;
|
|
9277
|
+
}
|
|
9346
9278
|
void __nomen_future_release(struct nomen_future *f) {
|
|
9347
9279
|
pthread_mutex_lock(&f->mu);
|
|
9348
9280
|
int last = --f->refs == 0;
|
|
@@ -9351,6 +9283,19 @@ void __nomen_future_release(struct nomen_future *f) {
|
|
|
9351
9283
|
pthread_mutex_destroy(&f->mu);
|
|
9352
9284
|
pthread_cond_destroy(&f->cv);
|
|
9353
9285
|
free(f->cancel_flag);
|
|
9286
|
+
// An unconsumed fat-string result: free the buffer the trampoline
|
|
9287
|
+
// stored (race-mode and fire-and-forget tasks never hand it to a
|
|
9288
|
+
// consumer). Every consumer path zeroes the cell first — result()
|
|
9289
|
+
// on move-out, Task's #destroy after its own free — so a non-NULL
|
|
9290
|
+
// ptr here is unambiguous and the free happens exactly once.
|
|
9291
|
+
if (f->slot_fat && f->result_slot) {
|
|
9292
|
+
unsigned long long *_s = (unsigned long long *)f->result_slot;
|
|
9293
|
+
if (_s[0]) {
|
|
9294
|
+
free((void *)_s[0]);
|
|
9295
|
+
_s[0] = 0;
|
|
9296
|
+
_s[1] = 0;
|
|
9297
|
+
}
|
|
9298
|
+
}
|
|
9354
9299
|
free(f->result_slot);
|
|
9355
9300
|
// The task closure is owned by the future — disposed at the last
|
|
9356
9301
|
// release (see __nomen_closure_dispose above for the lifetime note).
|
|
@@ -9474,13 +9419,9 @@ void __nomen_future_cancel(struct nomen_future *f) {
|
|
|
9474
9419
|
// schedule it so it resumes and observes the flag (idempotent).
|
|
9475
9420
|
if (f->owning_fiber) __nomen_fiber_schedule(f->owning_fiber);
|
|
9476
9421
|
}
|
|
9477
|
-
// Race-mode helpers — see build_c POOL_HEADER for semantics.
|
|
9478
|
-
|
|
9479
|
-
|
|
9480
|
-
int d = f->done;
|
|
9481
|
-
pthread_mutex_unlock(&f->mu);
|
|
9482
|
-
return d;
|
|
9483
|
-
}
|
|
9422
|
+
// Race-mode helpers — see build_c POOL_HEADER for semantics. (The
|
|
9423
|
+
// non-blocking peek itself, __nomen_future_is_done, lives with the future
|
|
9424
|
+
// machinery above — non-static: Task.is_done's raw asm branches into it.)
|
|
9484
9425
|
int __nomen_nursery_race_wait(struct nomen_future **futures, int count, long long deadline_ms) {
|
|
9485
9426
|
if (count <= 0) return 0;
|
|
9486
9427
|
struct timespec sleep_ts = {0, 1000000};
|
|
@@ -9524,7 +9465,8 @@ static void *__nomen_detached_run(void *p) {
|
|
|
9524
9465
|
free(d);
|
|
9525
9466
|
return NULL;
|
|
9526
9467
|
}
|
|
9527
|
-
static
|
|
9468
|
+
// Non-static: raw asm (Thread.nm's #destroy/detach bodies) branches into it.
|
|
9469
|
+
void __nomen_task_detach(struct nomen_closure *task) {
|
|
9528
9470
|
struct nomen_detached_start *d =
|
|
9529
9471
|
(struct nomen_detached_start *)malloc(sizeof(struct nomen_detached_start));
|
|
9530
9472
|
d->task = task;
|
|
@@ -9548,6 +9490,7 @@ static void __nomen_task_detach(struct nomen_closure *task) {
|
|
|
9548
9490
|
* linked program must define them.
|
|
9549
9491
|
*/
|
|
9550
9492
|
function ensure_concurrency_runtime_a64(status) {
|
|
9493
|
+
status.used_fibers = true;
|
|
9551
9494
|
if (status.emit_mode === "system") return;
|
|
9552
9495
|
if (status.pool_runtime_emitted && status.fiber_runtime_emitted) return;
|
|
9553
9496
|
if (!status.pool_runtime_emitted) {
|
|
@@ -10299,116 +10242,6 @@ static int __nomen_deadlock_check_worker(void) {
|
|
|
10299
10242
|
}
|
|
10300
10243
|
`;
|
|
10301
10244
|
/**
|
|
10302
|
-
* Build a `.start()` launch on a `Thread(fn(args))` construction (or a
|
|
10303
|
-
* stored Thread binding) — aarch64 backend (CLOSURE.md Phase 3b).
|
|
10304
|
-
* A companion-C helper reads the packed handles from the receiver's fields,
|
|
10305
|
-
* submits the task closure to the pool, registers the future with the
|
|
10306
|
-
* enclosing nursery (if any), transfers the handles out of the instance,
|
|
10307
|
-
* and returns the Task<T> handle (or NULL, fire-and-forget); the assembly
|
|
10308
|
-
* builds the receiver pointer and calls it.
|
|
10309
|
-
*/
|
|
10310
|
-
function build_thread_start$1(access_func, target, status) {
|
|
10311
|
-
ensure_concurrency_runtime_a64(status);
|
|
10312
|
-
const id = status.spawn_counter ?? 0;
|
|
10313
|
-
status.spawn_counter = id + 1;
|
|
10314
|
-
const helper_name = `nomen_thread_${id}_start`;
|
|
10315
|
-
const t_arg = spawn_result_type_arg$5(access_func.function_return_type);
|
|
10316
|
-
const mono_thread = mono_type_name("Thread", [t_arg]);
|
|
10317
|
-
const mono_task_name = mono_type_name("Task", [t_arg]);
|
|
10318
|
-
const nursery_id = status.nursery_stack?.at(-1);
|
|
10319
|
-
const nursery_off = nursery_id !== void 0 ? status.nursery_offsets?.get(nursery_id) : void 0;
|
|
10320
|
-
const fire_and_forget = !!access_func.is_statement;
|
|
10321
|
-
const target_is_temp = target.node_type === "func_call" && !!(target.is_thread_ctor || target.is_fiber_ctor);
|
|
10322
|
-
const refs = fire_and_forget ? nursery_id !== void 0 ? 2 : 1 : nursery_id !== void 0 ? 3 : 2;
|
|
10323
|
-
let c = `// --- thread start site ${id} ---\n`;
|
|
10324
|
-
c += `void *${helper_name}(void *self`;
|
|
10325
|
-
if (nursery_off) c += `, void **__nomen_nursery_futures, int *__nomen_nursery_count, int *__nomen_nursery_cap`;
|
|
10326
|
-
c += `) {\n`;
|
|
10327
|
-
c += `\textern void *${mono_task_name}_traits[];\n\tstruct ${mono_thread} *t = (struct ${mono_thread} *)self;\n`;
|
|
10328
|
-
c += `\tstruct nomen_future *f = (struct nomen_future *)t->future;\n`;
|
|
10329
|
-
c += `\tvoid *result_ptr = (void *)t->result_slot;\n`;
|
|
10330
|
-
c += `\tunsigned long long *cancel_ptr = (unsigned long long *)t->cancel_flag;\n`;
|
|
10331
|
-
c += `\tstruct nomen_closure *cl = (struct nomen_closure *)t->task;\n`;
|
|
10332
|
-
c += `\tf->refs = ${refs};\n`;
|
|
10333
|
-
c += `\t__nomen_pool_submit(cl);\n`;
|
|
10334
|
-
if (nursery_off) c += `\t__nomen_nursery_track(__nomen_nursery_futures, __nomen_nursery_count, __nomen_nursery_cap, f);\n`;
|
|
10335
|
-
c += `\tt->task = 0;\n`;
|
|
10336
|
-
c += `\tt->future = 0;\n`;
|
|
10337
|
-
c += `\tt->result_slot = 0;\n`;
|
|
10338
|
-
c += `\tt->cancel_flag = 0;\n`;
|
|
10339
|
-
c += `\tt->started = 1;\n`;
|
|
10340
|
-
if (target_is_temp) c += `\tfree(t);\n`;
|
|
10341
|
-
if (fire_and_forget) c += `\treturn (void *)0;\n`;
|
|
10342
|
-
else {
|
|
10343
|
-
c += `\tstruct ${mono_task_name} *task = (struct ${mono_task_name} *)malloc(sizeof(struct ${mono_task_name}));\n`;
|
|
10344
|
-
c += `\ttask->_vt = (void **)${mono_task_name}_traits;\n`;
|
|
10345
|
-
c += `\ttask->handle = 0;\n`;
|
|
10346
|
-
c += `\ttask->done = 0;\n`;
|
|
10347
|
-
c += `\ttask->result_slot = (unsigned long long)result_ptr;\n`;
|
|
10348
|
-
c += `\ttask->cancel_flag = (unsigned long long)cancel_ptr;\n`;
|
|
10349
|
-
c += `\ttask->future = (unsigned long long)f;\n`;
|
|
10350
|
-
c += `\treturn task;\n`;
|
|
10351
|
-
}
|
|
10352
|
-
c += `}\n`;
|
|
10353
|
-
if (!status.file_scope_c) status.file_scope_c = "";
|
|
10354
|
-
status.file_scope_c += c;
|
|
10355
|
-
emit_asm(status, `// thread start site ${id}\n`);
|
|
10356
|
-
build_node(target, status);
|
|
10357
|
-
ensure_newline(status);
|
|
10358
|
-
if (nursery_off) {
|
|
10359
|
-
const park = allocate_stack_space(status, 8, 8);
|
|
10360
|
-
emit_asm(status, `str x0, [x29, #${park}]\n`);
|
|
10361
|
-
emit_asm(status, `add x1, x29, #${nursery_off.futures_off}\n`);
|
|
10362
|
-
emit_asm(status, `add x2, x29, #${nursery_off.count_off}\n`);
|
|
10363
|
-
emit_asm(status, `add x3, x29, #${nursery_off.cap_off}\n`);
|
|
10364
|
-
emit_asm(status, `ldr x0, [x29, #${park}]\n`);
|
|
10365
|
-
}
|
|
10366
|
-
emit_asm(status, `bl _${helper_name}\n`);
|
|
10367
|
-
}
|
|
10368
|
-
/**
|
|
10369
|
-
* Build a `.detach()` launch on a Thread construction — the daemon form
|
|
10370
|
-
* (ASYNC.md, "Daemon tasks"), aarch64 backend. The companion helper
|
|
10371
|
-
* releases the construction's unused future (detaching the closure from
|
|
10372
|
-
* it — the daemon runner owns the closure), hands the task to the detached
|
|
10373
|
-
* launcher, and marks the instance started; the assembly calls it.
|
|
10374
|
-
*/
|
|
10375
|
-
function build_thread_detach$1(access_func, target, status) {
|
|
10376
|
-
ensure_concurrency_runtime_a64(status);
|
|
10377
|
-
const id = status.spawn_counter ?? 0;
|
|
10378
|
-
status.spawn_counter = id + 1;
|
|
10379
|
-
const helper_name = `nomen_detach_${id}_start`;
|
|
10380
|
-
const mono_thread = mono_type_name("Thread", [spawn_result_type_arg$5(access_func.function_return_type)]);
|
|
10381
|
-
const target_is_temp = target.node_type === "func_call" && !!(target.is_thread_ctor || target.is_fiber_ctor);
|
|
10382
|
-
let c = `// --- thread detach site ${id} ---\n`;
|
|
10383
|
-
c += `void *${helper_name}(void *self) {\n`;
|
|
10384
|
-
c += `\tstruct ${mono_thread} *t = (struct ${mono_thread} *)self;\n`;
|
|
10385
|
-
c += `\tstruct nomen_future *unused = (struct nomen_future *)t->future;\n`;
|
|
10386
|
-
c += `\tif (unused) {\n`;
|
|
10387
|
-
c += `\t\tunused->owner_args = NULL;\n`;
|
|
10388
|
-
c += `\t\t__nomen_future_release(unused);\n`;
|
|
10389
|
-
c += `\t}\n`;
|
|
10390
|
-
c += `\t__nomen_task_detach((struct nomen_closure *)t->task);\n`;
|
|
10391
|
-
c += `\tt->task = 0;\n`;
|
|
10392
|
-
c += `\tt->future = 0;\n`;
|
|
10393
|
-
c += `\tt->result_slot = 0;\n`;
|
|
10394
|
-
c += `\tt->cancel_flag = 0;\n`;
|
|
10395
|
-
c += `\tt->started = 1;\n`;
|
|
10396
|
-
if (target_is_temp) c += `\tfree(t);\n`;
|
|
10397
|
-
c += `\treturn (void *)0;\n`;
|
|
10398
|
-
c += `}\n`;
|
|
10399
|
-
if (!status.file_scope_c) status.file_scope_c = "";
|
|
10400
|
-
status.file_scope_c += c;
|
|
10401
|
-
emit_asm(status, `// thread detach site ${id}\n`);
|
|
10402
|
-
build_node(target, status);
|
|
10403
|
-
ensure_newline(status);
|
|
10404
|
-
emit_asm(status, `bl _${helper_name}\n`);
|
|
10405
|
-
}
|
|
10406
|
-
/** The launch's T as a Type: the stamped wrapped-call return type, with
|
|
10407
|
-
* void coerced to uint64 (Task's result-slot convention). */
|
|
10408
|
-
function spawn_result_type_arg$5(return_type) {
|
|
10409
|
-
return return_type?.name && return_type.name !== "void" && return_type.name !== "?" ? new Type(return_type.name) : new Type("uint64");
|
|
10410
|
-
}
|
|
10411
|
-
/**
|
|
10412
10245
|
* Whether a spawn argument rides the fat-string (ptr, len) pair ABI: its
|
|
10413
10246
|
* static type names `string`, or it is a string literal (whose ValueNode.type
|
|
10414
10247
|
* may be unset). Mirrors arg_is_string in build_function_call_node.
|
|
@@ -10419,6 +10252,35 @@ function spawn_arg_is_string(node) {
|
|
|
10419
10252
|
const t = type_from_value_node$1(node);
|
|
10420
10253
|
return t?.name === "string" && !t.is_view && !t.is_array;
|
|
10421
10254
|
}
|
|
10255
|
+
/**
|
|
10256
|
+
* Aarch64 backend: pull the concurrency runtime in
|
|
10257
|
+
* when this struct method's raw bodies reference it — keyed on body
|
|
10258
|
+
* content, never on a type name. See the C backend's
|
|
10259
|
+
* ensure_runtime_for_method.
|
|
10260
|
+
*/
|
|
10261
|
+
function ensure_runtime_for_method_a64(func, status) {
|
|
10262
|
+
for (const stmt of func.statements ?? []) if (raw_bodies_reference_runtime$1(stmt)) {
|
|
10263
|
+
ensure_concurrency_runtime_a64(status);
|
|
10264
|
+
return;
|
|
10265
|
+
}
|
|
10266
|
+
}
|
|
10267
|
+
function raw_bodies_reference_runtime$1(node) {
|
|
10268
|
+
if (!node || typeof node !== "object") return false;
|
|
10269
|
+
if (node.node_type === "raw") {
|
|
10270
|
+
const raw = node.value;
|
|
10271
|
+
return typeof raw === "string" && raw.includes("__nomen_");
|
|
10272
|
+
}
|
|
10273
|
+
for (const key of Object.keys(node)) {
|
|
10274
|
+
if (key === "parent" || key === "scope") continue;
|
|
10275
|
+
const v = node[key];
|
|
10276
|
+
if (Array.isArray(v)) {
|
|
10277
|
+
for (const item of v) if (raw_bodies_reference_runtime$1(item)) return true;
|
|
10278
|
+
} else if (v && typeof v === "object" && "node_type" in v) {
|
|
10279
|
+
if (raw_bodies_reference_runtime$1(v)) return true;
|
|
10280
|
+
}
|
|
10281
|
+
}
|
|
10282
|
+
return false;
|
|
10283
|
+
}
|
|
10422
10284
|
//#endregion
|
|
10423
10285
|
//#region ../src/nir/from_ast.ts
|
|
10424
10286
|
/**
|
|
@@ -11054,14 +10916,14 @@ function array_struct_name(type, status) {
|
|
|
11054
10916
|
//#region ../src/build_common/scan_force_heap_strings.ts
|
|
11055
10917
|
function scan_force_heap_strings(statements, structs) {
|
|
11056
10918
|
const result = /* @__PURE__ */ new Set();
|
|
11057
|
-
walk$
|
|
10919
|
+
walk$2(statements, result, structs);
|
|
11058
10920
|
return result;
|
|
11059
10921
|
}
|
|
11060
|
-
function walk$
|
|
10922
|
+
function walk$2(statements, result, structs) {
|
|
11061
10923
|
if (!statements) return;
|
|
11062
|
-
for (const stmt of statements) visit(stmt, result, structs);
|
|
10924
|
+
for (const stmt of statements) visit$1(stmt, result, structs);
|
|
11063
10925
|
}
|
|
11064
|
-
function visit(node, result, structs) {
|
|
10926
|
+
function visit$1(node, result, structs) {
|
|
11065
10927
|
if (!node) return;
|
|
11066
10928
|
switch (node.node_type) {
|
|
11067
10929
|
case "assign": {
|
|
@@ -11070,7 +10932,7 @@ function visit(node, result, structs) {
|
|
|
11070
10932
|
if (is_fresh_heap_string(a.right_value)) result.add(a.left_value.value);
|
|
11071
10933
|
else if (!a.operator && is_owned_string_var_rhs(a.right_value)) result.add(a.left_value.value);
|
|
11072
10934
|
}
|
|
11073
|
-
visit(a.right_value, result, structs);
|
|
10935
|
+
visit$1(a.right_value, result, structs);
|
|
11074
10936
|
break;
|
|
11075
10937
|
}
|
|
11076
10938
|
case "access": {
|
|
@@ -11083,30 +10945,37 @@ function visit(node, result, structs) {
|
|
|
11083
10945
|
}
|
|
11084
10946
|
case "while":
|
|
11085
10947
|
case "for":
|
|
11086
|
-
walk$
|
|
10948
|
+
walk$2(node.statements, result, structs);
|
|
10949
|
+
break;
|
|
10950
|
+
case "async_block":
|
|
10951
|
+
walk$2(node.statements, result, structs);
|
|
11087
10952
|
break;
|
|
11088
10953
|
case "if": {
|
|
11089
10954
|
const n = node;
|
|
11090
|
-
walk$
|
|
11091
|
-
walk$
|
|
10955
|
+
walk$2(n.if_branch?.statements, result, structs);
|
|
10956
|
+
walk$2(n.else_branch?.statements, result, structs);
|
|
11092
10957
|
break;
|
|
11093
10958
|
}
|
|
11094
10959
|
case "switch": {
|
|
11095
10960
|
const n = node;
|
|
11096
|
-
for (const c of n.cases) walk$
|
|
11097
|
-
walk$
|
|
10961
|
+
for (const c of n.cases) walk$2(c.branch?.statements, result, structs);
|
|
10962
|
+
walk$2(n.else_branch?.statements, result, structs);
|
|
11098
10963
|
break;
|
|
11099
10964
|
}
|
|
11100
10965
|
case "match": {
|
|
11101
10966
|
const m = node;
|
|
11102
|
-
for (const c of m.cases ?? []) walk$
|
|
11103
|
-
walk$
|
|
10967
|
+
for (const c of m.cases ?? []) walk$2(c.branch?.statements, result, structs);
|
|
10968
|
+
walk$2(m.else_branch?.statements, result, structs);
|
|
11104
10969
|
break;
|
|
11105
10970
|
}
|
|
11106
10971
|
}
|
|
11107
10972
|
}
|
|
11108
10973
|
function is_fresh_heap_string(node) {
|
|
11109
10974
|
if (!node) return false;
|
|
10975
|
+
if (node.node_type === "access") {
|
|
10976
|
+
const access = node.access;
|
|
10977
|
+
if (access.node_type === "access_func" && access.name === "to_string") return true;
|
|
10978
|
+
}
|
|
11110
10979
|
if (node.type?.name !== "string") return false;
|
|
11111
10980
|
if (node.node_type === "op") return true;
|
|
11112
10981
|
if (node.node_type === "func_call") return true;
|
|
@@ -11249,6 +11118,61 @@ function receiver_escapes(func, owner_struct_name, state) {
|
|
|
11249
11118
|
return escapes;
|
|
11250
11119
|
}
|
|
11251
11120
|
//#endregion
|
|
11121
|
+
//#region ../src/build_common/scan_reassigned_vars.ts
|
|
11122
|
+
/**
|
|
11123
|
+
* Collect the names reassigned anywhere in a function body (any nested
|
|
11124
|
+
* scope). The aarch64 string-concat constant folder (`resolve_string_value`)
|
|
11125
|
+
* resolves a variable operand to its DECLARATION initializer; that is only
|
|
11126
|
+
* sound while the variable still holds it. Once the name is reassigned, the
|
|
11127
|
+
* fold must be suppressed (`var tag = ""; tag = f(); "</" + tag + ">"` folded
|
|
11128
|
+
* to `"</>"` using the stale `""`).
|
|
11129
|
+
*/
|
|
11130
|
+
function scan_reassigned_vars(statements) {
|
|
11131
|
+
const result = /* @__PURE__ */ new Set();
|
|
11132
|
+
walk$1(statements, result);
|
|
11133
|
+
return result;
|
|
11134
|
+
}
|
|
11135
|
+
function walk$1(statements, result) {
|
|
11136
|
+
if (!statements) return;
|
|
11137
|
+
for (const stmt of statements) visit(stmt, result);
|
|
11138
|
+
}
|
|
11139
|
+
function visit(node, result) {
|
|
11140
|
+
if (!node) return;
|
|
11141
|
+
switch (node.node_type) {
|
|
11142
|
+
case "assign": {
|
|
11143
|
+
const a = node;
|
|
11144
|
+
if (a.left_value.node_type === "value") {
|
|
11145
|
+
const name = a.left_value.value;
|
|
11146
|
+
if (typeof name === "string") result.add(name);
|
|
11147
|
+
} else if (a.left_value.node_type === "access") a.left_value;
|
|
11148
|
+
visit(a.right_value, result);
|
|
11149
|
+
break;
|
|
11150
|
+
}
|
|
11151
|
+
case "while":
|
|
11152
|
+
case "for":
|
|
11153
|
+
walk$1(node.statements, result);
|
|
11154
|
+
break;
|
|
11155
|
+
case "if": {
|
|
11156
|
+
const n = node;
|
|
11157
|
+
walk$1(n.if_branch?.statements, result);
|
|
11158
|
+
walk$1(n.else_branch?.statements, result);
|
|
11159
|
+
break;
|
|
11160
|
+
}
|
|
11161
|
+
case "switch": {
|
|
11162
|
+
const n = node;
|
|
11163
|
+
for (const c of n.cases) walk$1(c.branch?.statements, result);
|
|
11164
|
+
walk$1(n.else_branch?.statements, result);
|
|
11165
|
+
break;
|
|
11166
|
+
}
|
|
11167
|
+
case "match": {
|
|
11168
|
+
const m = node;
|
|
11169
|
+
for (const c of m.cases ?? []) walk$1(c.branch?.statements, result);
|
|
11170
|
+
walk$1(m.else_branch?.statements, result);
|
|
11171
|
+
break;
|
|
11172
|
+
}
|
|
11173
|
+
}
|
|
11174
|
+
}
|
|
11175
|
+
//#endregion
|
|
11252
11176
|
//#region ../src/build_aarch64/build_extern.ts
|
|
11253
11177
|
/**
|
|
11254
11178
|
* Emit an `extern func` for the aarch64 backend: a fixed-shape adapter that
|
|
@@ -11371,6 +11295,19 @@ function raw_reload_lines_for(code, plan, current_length) {
|
|
|
11371
11295
|
return out;
|
|
11372
11296
|
}
|
|
11373
11297
|
//#endregion
|
|
11298
|
+
//#region ../src/build_aarch64/utils/aarch64_size.ts
|
|
11299
|
+
/**
|
|
11300
|
+
* Storage width of a value of `type` in bytes, derived from the
|
|
11301
|
+
* built-in type table (see BuiltInTypeInfo.bytes). Non-built-in names are
|
|
11302
|
+
* structs/enums/generics — 8 unless overridden by struct layout. `string`
|
|
11303
|
+
* is the fat 16-byte { char* ptr; long len; } pair; in registers it rides
|
|
11304
|
+
* as a consecutive (ptr, len) pair — the same ABI `view T` uses.
|
|
11305
|
+
*/
|
|
11306
|
+
function aarch64_size(type) {
|
|
11307
|
+
if (type === "string") return 16;
|
|
11308
|
+
return get_built_in_type(type)?.bytes ?? 8;
|
|
11309
|
+
}
|
|
11310
|
+
//#endregion
|
|
11374
11311
|
//#region ../src/build_aarch64/utils/struct_layout.ts
|
|
11375
11312
|
const VT_SIZE = 8;
|
|
11376
11313
|
/**
|
|
@@ -12595,6 +12532,173 @@ function cset_flag_is_write_only(ctx, name) {
|
|
|
12595
12532
|
return ctx.write_only?.has(name) ?? false;
|
|
12596
12533
|
}
|
|
12597
12534
|
//#endregion
|
|
12535
|
+
//#region ../src/build_aarch64/utils/stack_var.ts
|
|
12536
|
+
function allocate_stack_space(status, size, alignment = 8) {
|
|
12537
|
+
if (!status.stack_size) status.stack_size = 0;
|
|
12538
|
+
const aligned_size = Math.ceil(status.stack_size / alignment) * alignment;
|
|
12539
|
+
status.stack_size = aligned_size + size;
|
|
12540
|
+
return aligned_size;
|
|
12541
|
+
}
|
|
12542
|
+
/**
|
|
12543
|
+
* Load a stack slot into a promoted (callee-saved) register with the slot's
|
|
12544
|
+
* width. A sub-word slot (bool/char/int8 = strb, int16 = strh, int32 = str w)
|
|
12545
|
+
* must be loaded with the matching zero-extending ldrb/ldrh/ldr w — a
|
|
12546
|
+
* full-width `ldr` picks up dirty stack bytes above the slot and a bool can
|
|
12547
|
+
* read as true regardless of its stored value.
|
|
12548
|
+
*/
|
|
12549
|
+
function emit_promoted_load(status, reg, offset, type_name) {
|
|
12550
|
+
const size = aarch64_size(type_name);
|
|
12551
|
+
if (size === 1) emit_asm(status, `ldrb ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
|
|
12552
|
+
else if (size === 2) emit_asm(status, `ldrh ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
|
|
12553
|
+
else if (size === 4) emit_asm(status, `ldr ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
|
|
12554
|
+
else emit_asm(status, `ldr ${reg}, [x29, #${offset}]\n`);
|
|
12555
|
+
}
|
|
12556
|
+
/**
|
|
12557
|
+
* Write a promoted (callee-saved) register back to its stack slot with the
|
|
12558
|
+
* slot's width. A full-width `str` into a sub-word slot clobbers the adjacent
|
|
12559
|
+
* stack bytes — whatever variable lives there gets corrupted.
|
|
12560
|
+
*/
|
|
12561
|
+
function emit_promoted_store(status, reg, offset, type_name) {
|
|
12562
|
+
const size = aarch64_size(type_name);
|
|
12563
|
+
if (size === 1) emit_asm(status, `strb ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
|
|
12564
|
+
else if (size === 2) emit_asm(status, `strh ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
|
|
12565
|
+
else if (size === 4) emit_asm(status, `str ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
|
|
12566
|
+
else emit_asm(status, `str ${reg}, [x29, #${offset}]\n`);
|
|
12567
|
+
}
|
|
12568
|
+
function is_local_ref_var(name, status) {
|
|
12569
|
+
return !!status.function_ref_params?.has(name) && !status.function_param_regs?.has(name);
|
|
12570
|
+
}
|
|
12571
|
+
function emit_var_address(status, reg, name) {
|
|
12572
|
+
if (status.closure_env_offsets?.has(name) && status.closure_env_slot !== void 0) {
|
|
12573
|
+
emit_asm(status, `ldr ${reg}, [x29, #${status.closure_env_slot}]\n`);
|
|
12574
|
+
const off = status.closure_env_offsets.get(name);
|
|
12575
|
+
if (off !== 0) emit_asm(status, `add ${reg}, ${reg}, #${off}\n`);
|
|
12576
|
+
return;
|
|
12577
|
+
}
|
|
12578
|
+
const alloc_reg = status.register_allocations?.get(name);
|
|
12579
|
+
if (alloc_reg) {
|
|
12580
|
+
const offset = status.stack_offsets?.get(name);
|
|
12581
|
+
if (offset !== void 0) {
|
|
12582
|
+
const type_name = status.scoped_declarations.find((d) => d.name === name)?.type?.name || status.variable_types?.get(name)?.name || "";
|
|
12583
|
+
if (type_name) emit_promoted_store(status, alloc_reg, offset, type_name);
|
|
12584
|
+
else emit_asm(status, `str ${alloc_reg}, [x29, #${offset}]\n`);
|
|
12585
|
+
}
|
|
12586
|
+
}
|
|
12587
|
+
const offset = status.stack_offsets?.get(name);
|
|
12588
|
+
if (offset !== void 0) {
|
|
12589
|
+
if (status.function_struct_param_slots?.has(name)) emit_asm(status, `ldr ${reg}, [x29, #${offset}]\n`);
|
|
12590
|
+
else emit_asm(status, `add ${reg}, x29, #${offset}\n`);
|
|
12591
|
+
} else {
|
|
12592
|
+
const param_reg = status.function_param_regs?.get(name);
|
|
12593
|
+
if (param_reg) emit_asm(status, `mov ${reg}, ${param_reg}\n`);
|
|
12594
|
+
else emit_asm(status, `adr ${reg}, ${name}\n`);
|
|
12595
|
+
}
|
|
12596
|
+
}
|
|
12597
|
+
function emit_deref_var_address(status, reg, name) {
|
|
12598
|
+
emit_var_address(status, reg, name);
|
|
12599
|
+
if (is_local_ref_var(name, status)) emit_asm(status, `ldr ${reg}, [${reg}]\n`);
|
|
12600
|
+
}
|
|
12601
|
+
function emit_var_load(status, reg, name, size) {
|
|
12602
|
+
if (name === "null") {
|
|
12603
|
+
emit_asm(status, `mov ${reg}, #0\n`);
|
|
12604
|
+
return;
|
|
12605
|
+
}
|
|
12606
|
+
if (status.closure_env_offsets?.has(name) && status.closure_env_slot !== void 0) {
|
|
12607
|
+
const off = status.closure_env_offsets.get(name);
|
|
12608
|
+
emit_asm(status, `ldr ${reg}, [x29, #${status.closure_env_slot}]\n`);
|
|
12609
|
+
if (size === 16 && reg.startsWith("x")) {
|
|
12610
|
+
emit_asm(status, `ldr x${parseInt(reg.substring(1), 10) + 1}, [${reg}, #${off + 8}]\n`);
|
|
12611
|
+
emit_asm(status, `ldr ${reg}, [${reg}, #${off}]\n`);
|
|
12612
|
+
} else if (size === 1) emit_asm(status, `ldrb ${reg.replace("x", "w")}, [${reg}, #${off}]\n`);
|
|
12613
|
+
else if (size === 2) emit_asm(status, `ldrh ${reg.replace("x", "w")}, [${reg}, #${off}]\n`);
|
|
12614
|
+
else if (size === 4) emit_asm(status, `ldr ${reg.replace("x", "w")}, [${reg}, #${off}]\n`);
|
|
12615
|
+
else emit_asm(status, `ldr ${reg}, [${reg}, #${off}]\n`);
|
|
12616
|
+
return;
|
|
12617
|
+
}
|
|
12618
|
+
const alloc_reg = status.register_allocations?.get(name);
|
|
12619
|
+
if (alloc_reg) {
|
|
12620
|
+
if (alloc_reg.startsWith("d") && reg.startsWith("d")) {
|
|
12621
|
+
if (reg !== alloc_reg) emit_asm(status, `fmov ${reg}, ${alloc_reg}\n`);
|
|
12622
|
+
} else if (alloc_reg.startsWith("d")) emit_asm(status, `fmov ${reg}, ${alloc_reg}\n`);
|
|
12623
|
+
else if (reg !== alloc_reg) emit_asm(status, `mov ${reg}, ${alloc_reg}\n`);
|
|
12624
|
+
return;
|
|
12625
|
+
}
|
|
12626
|
+
const offset = status.stack_offsets?.get(name);
|
|
12627
|
+
if (offset !== void 0) {
|
|
12628
|
+
if (size === 16 && reg.startsWith("x")) {
|
|
12629
|
+
const n = parseInt(reg.substring(1), 10);
|
|
12630
|
+
if (offset + 8 > 504) {
|
|
12631
|
+
emit_asm(status, `ldr ${reg}, [x29, #${offset}]\n`);
|
|
12632
|
+
emit_asm(status, `ldr x${n + 1}, [x29, #${offset + 8}]\n`);
|
|
12633
|
+
} else emit_asm(status, `ldp ${reg}, x${n + 1}, [x29, #${offset}]\n`);
|
|
12634
|
+
return;
|
|
12635
|
+
}
|
|
12636
|
+
if (size === 1) emit_asm(status, `ldrb ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
|
|
12637
|
+
else if (size === 2) emit_asm(status, `ldrh ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
|
|
12638
|
+
else if (size === 4) emit_asm(status, `ldr ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
|
|
12639
|
+
else emit_asm(status, `ldr ${reg}, [x29, #${offset}]\n`);
|
|
12640
|
+
} else {
|
|
12641
|
+
const param_reg = status.function_param_regs?.get(name);
|
|
12642
|
+
if (param_reg) {
|
|
12643
|
+
if (reg !== param_reg) emit_asm(status, `mov ${reg}, ${param_reg}\n`);
|
|
12644
|
+
} else {
|
|
12645
|
+
emit_asm(status, `adr ${reg}, ${name}\n`);
|
|
12646
|
+
if (size === 1) emit_asm(status, `ldrb ${reg.replace("x", "w")}, [${reg}]\n`);
|
|
12647
|
+
else if (size === 2) emit_asm(status, `ldrh ${reg.replace("x", "w")}, [${reg}]\n`);
|
|
12648
|
+
else if (size === 4) emit_asm(status, `ldr ${reg.replace("x", "w")}, [${reg}]\n`);
|
|
12649
|
+
else emit_asm(status, `ldr ${reg}, [${reg}]\n`);
|
|
12650
|
+
}
|
|
12651
|
+
}
|
|
12652
|
+
}
|
|
12653
|
+
function emit_var_store(status, reg, name, size) {
|
|
12654
|
+
if (status.closure_env_offsets?.has(name) && status.closure_env_slot !== void 0) {
|
|
12655
|
+
const off = status.closure_env_offsets.get(name);
|
|
12656
|
+
emit_asm(status, `ldr x9, [x29, #${status.closure_env_slot}]\n`);
|
|
12657
|
+
if (size === 16 && reg.startsWith("x")) {
|
|
12658
|
+
const n = parseInt(reg.substring(1), 10);
|
|
12659
|
+
emit_asm(status, `str ${reg}, [x9, #${off}]\n`);
|
|
12660
|
+
emit_asm(status, `str x${n + 1}, [x9, #${off + 8}]\n`);
|
|
12661
|
+
} else if (size === 1) emit_asm(status, `strb ${reg.replace("x", "w")}, [x9, #${off}]\n`);
|
|
12662
|
+
else if (size === 2) emit_asm(status, `strh ${reg.replace("x", "w")}, [x9, #${off}]\n`);
|
|
12663
|
+
else if (size === 4) emit_asm(status, `str ${reg.replace("x", "w")}, [x9, #${off}]\n`);
|
|
12664
|
+
else emit_asm(status, `str ${reg}, [x9, #${off}]\n`);
|
|
12665
|
+
return;
|
|
12666
|
+
}
|
|
12667
|
+
const alloc_reg = status.register_allocations?.get(name);
|
|
12668
|
+
if (alloc_reg) {
|
|
12669
|
+
if (alloc_reg.startsWith("d") && reg.startsWith("d")) {
|
|
12670
|
+
if (reg !== alloc_reg) emit_asm(status, `fmov ${alloc_reg}, ${reg}\n`);
|
|
12671
|
+
} else if (alloc_reg.startsWith("d")) emit_asm(status, `fmov ${alloc_reg}, ${reg}\n`);
|
|
12672
|
+
else if (reg !== alloc_reg) emit_asm(status, `mov ${alloc_reg}, ${reg}\n`);
|
|
12673
|
+
return;
|
|
12674
|
+
}
|
|
12675
|
+
const offset = status.stack_offsets?.get(name);
|
|
12676
|
+
if (offset !== void 0) {
|
|
12677
|
+
if (size === 16 && reg.startsWith("x")) {
|
|
12678
|
+
const n = parseInt(reg.substring(1), 10);
|
|
12679
|
+
if (offset + 8 > 504) {
|
|
12680
|
+
emit_asm(status, `str ${reg}, [x29, #${offset}]\n`);
|
|
12681
|
+
emit_asm(status, `str x${n + 1}, [x29, #${offset + 8}]\n`);
|
|
12682
|
+
} else emit_asm(status, `stp ${reg}, x${n + 1}, [x29, #${offset}]\n`);
|
|
12683
|
+
return;
|
|
12684
|
+
}
|
|
12685
|
+
if (size === 1) emit_asm(status, `strb ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
|
|
12686
|
+
else if (size === 2) emit_asm(status, `strh ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
|
|
12687
|
+
else if (size === 4) emit_asm(status, `str ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
|
|
12688
|
+
else emit_asm(status, `str ${reg}, [x29, #${offset}]\n`);
|
|
12689
|
+
} else {
|
|
12690
|
+
const param_reg = status.function_param_regs?.get(name);
|
|
12691
|
+
if (param_reg) emit_asm(status, `mov ${param_reg}, ${reg}\n`);
|
|
12692
|
+
else {
|
|
12693
|
+
emit_asm(status, `adr x1, ${name}\n`);
|
|
12694
|
+
if (size === 1) emit_asm(status, `strb ${reg.replace("x", "w")}, [x1]\n`);
|
|
12695
|
+
else if (size === 2) emit_asm(status, `strh ${reg.replace("x", "w")}, [x1]\n`);
|
|
12696
|
+
else if (size === 4) emit_asm(status, `str ${reg.replace("x", "w")}, [x1]\n`);
|
|
12697
|
+
else emit_asm(status, `str ${reg}, [x1]\n`);
|
|
12698
|
+
}
|
|
12699
|
+
}
|
|
12700
|
+
}
|
|
12701
|
+
//#endregion
|
|
12598
12702
|
//#region ../src/build_aarch64/slp_pair.ts
|
|
12599
12703
|
/**
|
|
12600
12704
|
* Field-pair SLP (ASM_PLAN_4 remaining step 1 — the PLAN_3 D-step-2
|
|
@@ -13641,14 +13745,32 @@ function emit_destroy_for_anchor_slot(status, offset, type_name, type_args, is_n
|
|
|
13641
13745
|
emit_field_destroys_from_slot(status, struct_type, offset);
|
|
13642
13746
|
if (skip_label) emit_asm(status, `${skip_label}:\n`);
|
|
13643
13747
|
}
|
|
13748
|
+
/**
|
|
13749
|
+
* Load the address of the sub-object at accumulated offset `acc` from the
|
|
13750
|
+
* object rooted at frame slot `root_slot` into x0. A CLASS root is a pointer
|
|
13751
|
+
* stored in the slot; a VALUE-struct root is inline AT the slot. Nested
|
|
13752
|
+
* value-struct fields keep accumulating offsets from that single base — they
|
|
13753
|
+
* are never re-addressed as if each nested field had its own frame slot. (The
|
|
13754
|
+
* old bug loaded `[x29, root_slot + field_offset]` for a heap class's inline
|
|
13755
|
+
* `List` field, feeding a garbage buffer header to the destroy loop.)
|
|
13756
|
+
*/
|
|
13757
|
+
function emit_slot_base(status, root_slot, root_is_class, acc) {
|
|
13758
|
+
if (root_is_class) emit_asm(status, `ldr x0, [x29, #${root_slot}]\n`);
|
|
13759
|
+
else emit_asm(status, `add x0, x29, #${root_slot}\n`);
|
|
13760
|
+
if (acc !== 0) emit_asm(status, `add x0, x0, #${acc}\n`);
|
|
13761
|
+
}
|
|
13644
13762
|
function emit_field_destroys_from_slot(status, struct_type, base_offset) {
|
|
13763
|
+
emit_field_destroys_at(status, struct_type, base_offset, 0, !!struct_type.is_class);
|
|
13764
|
+
}
|
|
13765
|
+
function emit_field_destroys_at(status, struct_type, root_slot, acc, root_is_class) {
|
|
13645
13766
|
for (const field of struct_type.fields) {
|
|
13646
13767
|
const offset = get_field_offset_of_fields(struct_type.fields, field.name, status);
|
|
13647
13768
|
const field_struct = is_struct_type$4(resolve_struct_name(field.type.name, field.type.type_args, status), status) || is_struct_type$4(field.type.name, status);
|
|
13769
|
+
const field_acc = acc + offset;
|
|
13648
13770
|
if (field_struct) {
|
|
13649
13771
|
if (field_struct.is_class && !field.type.is_ref) {
|
|
13650
|
-
|
|
13651
|
-
emit_asm(status, `ldr x0, [x0
|
|
13772
|
+
emit_slot_base(status, root_slot, root_is_class, field_acc);
|
|
13773
|
+
emit_asm(status, `ldr x0, [x0]\n`);
|
|
13652
13774
|
emit_asm(status, `str x0, [sp, #-16]!\n`);
|
|
13653
13775
|
const skip_label = `.Lskip_defer_${status.label_counter = (status.label_counter ?? 0) + 1}`;
|
|
13654
13776
|
emit_asm(status, `cbz x0, ${skip_label}\n`);
|
|
@@ -13657,32 +13779,17 @@ function emit_field_destroys_from_slot(status, struct_type, base_offset) {
|
|
|
13657
13779
|
emit_asm(status, `ldr x0, [sp], #16\n`);
|
|
13658
13780
|
emit_free(status);
|
|
13659
13781
|
} else if (has_destroy(field_struct)) {
|
|
13660
|
-
|
|
13661
|
-
emit_asm(status, `add x0, x0, #${offset}\n`);
|
|
13782
|
+
emit_slot_base(status, root_slot, root_is_class, field_acc);
|
|
13662
13783
|
emit_asm(status, `bl ${resolve_struct_name(field_struct.name, field.type.type_args, status)}_destroy\n`);
|
|
13663
13784
|
}
|
|
13664
|
-
|
|
13785
|
+
emit_field_destroys_at(status, field_struct, root_slot, field_acc, root_is_class);
|
|
13665
13786
|
} else if (field.type.name === "string" && !field.type.is_array && !field.type.is_ref && !field.type.is_view) {
|
|
13666
|
-
|
|
13667
|
-
emit_asm(status, `ldr x0, [x0
|
|
13787
|
+
emit_slot_base(status, root_slot, root_is_class, field_acc);
|
|
13788
|
+
emit_asm(status, `ldr x0, [x0]\n`);
|
|
13668
13789
|
emit_free(status);
|
|
13669
13790
|
}
|
|
13670
13791
|
}
|
|
13671
13792
|
}
|
|
13672
|
-
function emit_nested_field_destroys_from_slot(status, struct_type, base_offset) {
|
|
13673
|
-
for (const field of struct_type.fields) {
|
|
13674
|
-
const offset = get_field_offset_of_fields(struct_type.fields, field.name, status);
|
|
13675
|
-
const field_struct = is_struct_type$4(resolve_struct_name(field.type.name, field.type.type_args, status), status) || is_struct_type$4(field.type.name, status);
|
|
13676
|
-
if (field_struct) {
|
|
13677
|
-
if (has_destroy(field_struct)) {
|
|
13678
|
-
emit_asm(status, `ldr x0, [x29, #${base_offset}]\n`);
|
|
13679
|
-
emit_asm(status, `add x0, x0, #${offset}\n`);
|
|
13680
|
-
emit_asm(status, `bl ${resolve_struct_name(field_struct.name, field.type.type_args, status)}_destroy\n`);
|
|
13681
|
-
}
|
|
13682
|
-
emit_nested_field_destroys_from_slot(status, field_struct, base_offset + offset);
|
|
13683
|
-
}
|
|
13684
|
-
}
|
|
13685
|
-
}
|
|
13686
13793
|
/**
|
|
13687
13794
|
* Free an anchor slot, running its deferred destroy (if any) first.
|
|
13688
13795
|
*/
|
|
@@ -18963,6 +19070,7 @@ function build_function_node$1(node, status) {
|
|
|
18963
19070
|
if (check_c_fallback(node, void 0, status)) return;
|
|
18964
19071
|
const old_function_name = status.current_function_name;
|
|
18965
19072
|
status.current_function_name = emission_label(node);
|
|
19073
|
+
status.function_nursery_depth = status.nursery_stack?.length ?? 0;
|
|
18966
19074
|
const old_scoped_declarations = status.scoped_declarations;
|
|
18967
19075
|
status.scoped_declarations = [];
|
|
18968
19076
|
status.last_result_is_heap = false;
|
|
@@ -19133,6 +19241,7 @@ function build_function_node$1(node, status) {
|
|
|
19133
19241
|
const old_ref_class_slots = status.ref_class_slots;
|
|
19134
19242
|
const old_struct_param_slots = status.function_struct_param_slots;
|
|
19135
19243
|
const old_function_param_types = status.function_param_types;
|
|
19244
|
+
const old_function_local_types = status.function_local_types;
|
|
19136
19245
|
status.function_param_regs = /* @__PURE__ */ new Map();
|
|
19137
19246
|
status.function_param_vars = /* @__PURE__ */ new Set();
|
|
19138
19247
|
status.function_array_params = /* @__PURE__ */ new Set();
|
|
@@ -19140,6 +19249,7 @@ function build_function_node$1(node, status) {
|
|
|
19140
19249
|
status.ref_class_slots = /* @__PURE__ */ new Map();
|
|
19141
19250
|
status.function_struct_param_slots = /* @__PURE__ */ new Set();
|
|
19142
19251
|
status.function_param_types = /* @__PURE__ */ new Map();
|
|
19252
|
+
status.function_local_types = /* @__PURE__ */ new Map();
|
|
19143
19253
|
const old_variadic_params_aarch64 = status.function_variadic_params;
|
|
19144
19254
|
const old_view_params = status.function_view_params;
|
|
19145
19255
|
status.function_variadic_params = /* @__PURE__ */ new Set();
|
|
@@ -19387,6 +19497,8 @@ function build_function_node$1(node, status) {
|
|
|
19387
19497
|
}
|
|
19388
19498
|
const old_force_heap = status.force_heap_strings;
|
|
19389
19499
|
status.force_heap_strings = scan_force_heap_strings(node.statements, status.structs);
|
|
19500
|
+
const old_reassigned_vars = status.reassigned_vars;
|
|
19501
|
+
status.reassigned_vars = scan_reassigned_vars(node.statements);
|
|
19390
19502
|
const old_buffer_data_cache = status.buffer_data_cache;
|
|
19391
19503
|
status.buffer_data_cache = void 0;
|
|
19392
19504
|
const old_array_ptr_cache = status.array_ptr_cache;
|
|
@@ -19421,6 +19533,7 @@ function build_function_node$1(node, status) {
|
|
|
19421
19533
|
status.buffer_data_cache = old_buffer_data_cache;
|
|
19422
19534
|
status.array_ptr_cache = old_array_ptr_cache;
|
|
19423
19535
|
status.force_heap_strings = old_force_heap;
|
|
19536
|
+
status.reassigned_vars = old_reassigned_vars;
|
|
19424
19537
|
const loop_regs_used = status.callee_saved_regs_used ? [...status.callee_saved_regs_used].sort() : [];
|
|
19425
19538
|
status.callee_saved_regs_used = old_callee_saved_regs;
|
|
19426
19539
|
status.register_allocations = old_register_allocations;
|
|
@@ -19544,6 +19657,7 @@ function build_function_node$1(node, status) {
|
|
|
19544
19657
|
status.ref_class_slots = old_ref_class_slots;
|
|
19545
19658
|
status.function_struct_param_slots = old_struct_param_slots;
|
|
19546
19659
|
status.function_param_types = old_function_param_types;
|
|
19660
|
+
status.function_local_types = old_function_local_types;
|
|
19547
19661
|
status.function_variadic_params = old_variadic_params_aarch64;
|
|
19548
19662
|
status.function_view_params = old_view_params;
|
|
19549
19663
|
status.function_return_label = old_return_label;
|
|
@@ -19788,10 +19902,7 @@ function resolve_global_const_value(name, status) {
|
|
|
19788
19902
|
function build_struct_node$1(node, status) {
|
|
19789
19903
|
if (node.is_generic) return;
|
|
19790
19904
|
const is_nested = !!status.function_return_label;
|
|
19791
|
-
|
|
19792
|
-
ensure_concurrency_runtime_a64(status);
|
|
19793
|
-
status.used_fibers = true;
|
|
19794
|
-
}
|
|
19905
|
+
for (const func of node.functions) ensure_runtime_for_method_a64(func, status);
|
|
19795
19906
|
let old_code;
|
|
19796
19907
|
if (is_nested) {
|
|
19797
19908
|
old_code = status.code;
|
|
@@ -20172,14 +20283,18 @@ function build_init_function(node, status) {
|
|
|
20172
20283
|
}
|
|
20173
20284
|
emit_typed_store(status, "x1", "x19", offset, get_type_size(field.type, status));
|
|
20174
20285
|
} else if (field.value.node_type === "func_call") {
|
|
20175
|
-
|
|
20286
|
+
const field_struct = status.structs.find((s) => s.name === field.type.name && !s.is_simple_type);
|
|
20287
|
+
if (field_struct) {
|
|
20176
20288
|
build_node(field.value, status);
|
|
20177
20289
|
ensure_newline(status);
|
|
20178
|
-
|
|
20179
|
-
|
|
20180
|
-
|
|
20181
|
-
|
|
20182
|
-
|
|
20290
|
+
if (field_struct.is_class) emit_typed_store(status, "x0", "x19", offset, 8);
|
|
20291
|
+
else {
|
|
20292
|
+
const field_size = get_struct_size(field.type.name, status);
|
|
20293
|
+
const words = Math.ceil(field_size / 8);
|
|
20294
|
+
for (let w = 0; w < words; w++) {
|
|
20295
|
+
emit_asm(status, `ldr x9, [x0, #${w * 8}]\n`);
|
|
20296
|
+
emit_asm(status, `str x9, [x19, #${offset + w * 8}]\n`);
|
|
20297
|
+
}
|
|
20183
20298
|
}
|
|
20184
20299
|
}
|
|
20185
20300
|
} else if (field.value.node_type === "func" && field.type.name === "func") {
|
|
@@ -20390,14 +20505,18 @@ function build_custom_init_function(node, func, status) {
|
|
|
20390
20505
|
}
|
|
20391
20506
|
emit_typed_store(status, "x1", "x19", offset, get_type_size(field.type, status));
|
|
20392
20507
|
} else if (field.value.node_type === "func_call") {
|
|
20393
|
-
|
|
20508
|
+
const field_struct = status.structs.find((s) => s.name === field.type.name && !s.is_simple_type);
|
|
20509
|
+
if (field_struct) {
|
|
20394
20510
|
build_node(field.value, status);
|
|
20395
20511
|
ensure_newline(status);
|
|
20396
|
-
|
|
20397
|
-
|
|
20398
|
-
|
|
20399
|
-
|
|
20400
|
-
|
|
20512
|
+
if (field_struct.is_class) emit_typed_store(status, "x0", "x19", offset, 8);
|
|
20513
|
+
else {
|
|
20514
|
+
const field_size = get_struct_size(field.type.name, status);
|
|
20515
|
+
const words = Math.ceil(field_size / 8);
|
|
20516
|
+
for (let w = 0; w < words; w++) {
|
|
20517
|
+
emit_asm(status, `ldr x9, [x0, #${w * 8}]\n`);
|
|
20518
|
+
emit_asm(status, `str x9, [x19, #${offset + w * 8}]\n`);
|
|
20519
|
+
}
|
|
20401
20520
|
}
|
|
20402
20521
|
}
|
|
20403
20522
|
}
|
|
@@ -20451,6 +20570,7 @@ function build_custom_init_function(node, func, status) {
|
|
|
20451
20570
|
}
|
|
20452
20571
|
function build_struct_functions$1(node, status) {
|
|
20453
20572
|
for (const func of node.functions) {
|
|
20573
|
+
if (func.name === "#spawn") continue;
|
|
20454
20574
|
if (func.name === "#init" && !func.has_body) continue;
|
|
20455
20575
|
if (func.name === "#init" && func.has_body) {
|
|
20456
20576
|
if (!check_c_fallback(func, node.name, status)) build_custom_init_function(node, func, status);
|
|
@@ -20481,6 +20601,7 @@ function build_struct_functions$1(node, status) {
|
|
|
20481
20601
|
const old_param_vars = status.function_param_vars;
|
|
20482
20602
|
const old_return_label = status.function_return_label;
|
|
20483
20603
|
const old_force_heap = status.force_heap_strings;
|
|
20604
|
+
const old_reassigned_vars = status.reassigned_vars;
|
|
20484
20605
|
const old_function_name = status.current_function_name;
|
|
20485
20606
|
const old_register_allocations = status.register_allocations;
|
|
20486
20607
|
const old_nir_site_allocs = status.nir_site_allocs;
|
|
@@ -20572,7 +20693,7 @@ function build_struct_functions$1(node, status) {
|
|
|
20572
20693
|
slot_idx += 2;
|
|
20573
20694
|
continue;
|
|
20574
20695
|
}
|
|
20575
|
-
if ((!!status.structs.find((s) => s.name === param.type.name && !s.is_simple_type) || !!status.enums.find((e) => e.name === param.type.name && e.has_associated_data)) && callee_idx < callee_saved.length) {
|
|
20696
|
+
if ((!!status.structs.find((s) => s.name === param.type.name && !s.is_simple_type) || !!status.enums.find((e) => e.name === param.type.name && e.has_associated_data) || !!status.traits.find((t) => t.name === param.type.name)) && callee_idx < callee_saved.length) {
|
|
20576
20697
|
const saved_reg = callee_saved[callee_idx++];
|
|
20577
20698
|
if (saved_reg !== "x19" || !needs_x19) emit_asm(status, `str ${saved_reg}, [sp, #-16]!\n`);
|
|
20578
20699
|
if (slot_idx < 8) emit_asm(status, `mov ${saved_reg}, ${param_regs[slot_idx]}\n`);
|
|
@@ -20650,7 +20771,7 @@ function build_struct_functions$1(node, status) {
|
|
|
20650
20771
|
second_slot_idx += 2;
|
|
20651
20772
|
continue;
|
|
20652
20773
|
}
|
|
20653
|
-
if (!(!!status.structs.find((s) => s.name === param.type.name && !s.is_simple_type) || !!status.enums.find((e) => e.name === param.type.name && e.has_associated_data))) {
|
|
20774
|
+
if (!(!!status.structs.find((s) => s.name === param.type.name && !s.is_simple_type) || !!status.enums.find((e) => e.name === param.type.name && e.has_associated_data) || !!status.traits.find((t) => t.name === param.type.name))) {
|
|
20654
20775
|
const is_ref = param.type.is_ref;
|
|
20655
20776
|
const size = is_ref ? 8 : aarch64_size(param.type.name);
|
|
20656
20777
|
const offset = allocate_stack_space(status, size, is_ref ? 8 : size);
|
|
@@ -20735,6 +20856,7 @@ function build_struct_functions$1(node, status) {
|
|
|
20735
20856
|
}
|
|
20736
20857
|
}
|
|
20737
20858
|
status.force_heap_strings = scan_force_heap_strings(func.statements, status.structs);
|
|
20859
|
+
status.reassigned_vars = scan_reassigned_vars(func.statements);
|
|
20738
20860
|
status.buffer_data_cache = void 0;
|
|
20739
20861
|
status.array_ptr_cache = void 0;
|
|
20740
20862
|
const moved_before = new Set(status.moved ?? []);
|
|
@@ -20820,6 +20942,7 @@ function build_struct_functions$1(node, status) {
|
|
|
20820
20942
|
status.function_view_params = old_view_params;
|
|
20821
20943
|
status.function_return_label = old_return_label;
|
|
20822
20944
|
status.force_heap_strings = old_force_heap;
|
|
20945
|
+
status.reassigned_vars = old_reassigned_vars;
|
|
20823
20946
|
status.struct_return_buffer = void 0;
|
|
20824
20947
|
status.function_return_type = void 0;
|
|
20825
20948
|
status.current_function_name = old_function_name;
|
|
@@ -22070,6 +22193,7 @@ function resolve_string_value(node, status) {
|
|
|
22070
22193
|
if (node.node_type === "value") {
|
|
22071
22194
|
const val = node.value;
|
|
22072
22195
|
if (val.startsWith("\"") && val.endsWith("\"")) return val;
|
|
22196
|
+
if (status.reassigned_vars?.has(val)) return null;
|
|
22073
22197
|
const decl = status.scoped_declarations.find((d) => d.name === val);
|
|
22074
22198
|
if (decl && decl.value) return resolve_string_value(decl.value, status);
|
|
22075
22199
|
}
|
|
@@ -22122,6 +22246,10 @@ function nir_array_element(nir, arr, i) {
|
|
|
22122
22246
|
return nir?.kind === "call" && nir.node === arr ? nir.facts.args[i] : void 0;
|
|
22123
22247
|
}
|
|
22124
22248
|
function build_declaration_node$1(node, status, nir_init, nir_swap) {
|
|
22249
|
+
if (node.name && node.type?.name) {
|
|
22250
|
+
if (!status.function_local_types) status.function_local_types = /* @__PURE__ */ new Map();
|
|
22251
|
+
status.function_local_types.set(node.name, node.type.name);
|
|
22252
|
+
}
|
|
22125
22253
|
hoist_field_overrides(node.value, build_node, status, "", node.name);
|
|
22126
22254
|
status.last_result_is_heap = false;
|
|
22127
22255
|
const prev_heap = status.last_result_is_heap;
|
|
@@ -23807,6 +23935,7 @@ function promote_loop_locals(status, scoped_declarations, sources, options) {
|
|
|
23807
23935
|
if (!ptype.name || !SCALAR_TYPES.includes(ptype.name) || ptype.is_array || ptype.is_view || ptype.is_ref || ptype.is_nullable) continue;
|
|
23808
23936
|
type_name = ptype.name;
|
|
23809
23937
|
}
|
|
23938
|
+
if (status.function_local_types?.get(name) === "string") continue;
|
|
23810
23939
|
}
|
|
23811
23940
|
let offset = status.stack_offsets?.get(name);
|
|
23812
23941
|
if (offset === void 0) {
|
|
@@ -24250,18 +24379,24 @@ function build_if_else_node$1(node, status, nir) {
|
|
|
24250
24379
|
const pre_cache = status.buffer_data_cache;
|
|
24251
24380
|
const pre_array_cache = status.array_ptr_cache;
|
|
24252
24381
|
if (node.else_branch) {
|
|
24382
|
+
const then_frame = enter_scope_frame(status);
|
|
24253
24383
|
status.buffer_data_cache = new Map(pre_cache);
|
|
24254
24384
|
status.array_ptr_cache = new Map(pre_array_cache);
|
|
24255
24385
|
build_block_with_cursor$1(node.if_branch, nir?.then_branch, status);
|
|
24386
|
+
exit_scope_frame(status, then_frame);
|
|
24256
24387
|
emit_asm(status, `b end_${label}\n`);
|
|
24257
24388
|
emit_asm(status, `else_${label}:\n`);
|
|
24389
|
+
const else_frame = enter_scope_frame(status);
|
|
24258
24390
|
status.buffer_data_cache = new Map(pre_cache);
|
|
24259
24391
|
status.array_ptr_cache = new Map(pre_array_cache);
|
|
24260
24392
|
build_block_with_cursor$1(node.else_branch, nir?.else_branch, status);
|
|
24393
|
+
exit_scope_frame(status, else_frame);
|
|
24261
24394
|
} else if (node.if_branch) {
|
|
24395
|
+
const then_frame = enter_scope_frame(status);
|
|
24262
24396
|
status.buffer_data_cache = new Map(pre_cache);
|
|
24263
24397
|
status.array_ptr_cache = new Map(pre_array_cache);
|
|
24264
24398
|
build_block_with_cursor$1(node.if_branch, nir?.then_branch, status);
|
|
24399
|
+
exit_scope_frame(status, then_frame);
|
|
24265
24400
|
}
|
|
24266
24401
|
status.buffer_data_cache = pre_cache;
|
|
24267
24402
|
status.array_ptr_cache = pre_array_cache;
|
|
@@ -24483,6 +24618,7 @@ function build_return_node$1(node, status, nir_value) {
|
|
|
24483
24618
|
emit_asm(status, `ldr x8, [x29, #${status.return_buffer_stack_offset}]\n`);
|
|
24484
24619
|
emit_asm(status, `str xzr, [x8, #${struct_size}]\n`);
|
|
24485
24620
|
}
|
|
24621
|
+
emit_nursery_joins_on_return_a64(status);
|
|
24486
24622
|
const finalized = status.moved ?? /* @__PURE__ */ new Set();
|
|
24487
24623
|
for (const decl of all_scope_frames(status).flat()) {
|
|
24488
24624
|
if (finalized.has(decl.name)) {
|
|
@@ -24502,6 +24638,7 @@ function build_return_node$1(node, status, nir_value) {
|
|
|
24502
24638
|
emit_asm(status, `mov x0, #0\n`);
|
|
24503
24639
|
emit_var_store(status, "x0", status.return_assign, size);
|
|
24504
24640
|
} else if (status.function_return_label) {
|
|
24641
|
+
emit_nursery_joins_on_return_a64(status);
|
|
24505
24642
|
const finalized = status.moved ?? /* @__PURE__ */ new Set();
|
|
24506
24643
|
for (const decl of all_scope_frames(status).flat()) {
|
|
24507
24644
|
if (finalized.has(decl.name)) {
|
|
@@ -24620,7 +24757,7 @@ function build_return_node$1(node, status, nir_value) {
|
|
|
24620
24757
|
const raw_value = node.value.value;
|
|
24621
24758
|
const is_str_lit = raw_value.length >= 2 && raw_value.startsWith("\"") && raw_value.endsWith("\"");
|
|
24622
24759
|
const is_numeric = /^(\+|-)?\d+$/.test(raw_value);
|
|
24623
|
-
if (!is_str_lit && !is_numeric && !status.heap_strings?.has(raw_value)) needs_borrow_strdup = true;
|
|
24760
|
+
if (!is_str_lit && !is_numeric && raw_value !== "null" && !status.heap_strings?.has(raw_value)) needs_borrow_strdup = true;
|
|
24624
24761
|
} else if (is_container_borrow_access(node.value)) needs_borrow_strdup = true;
|
|
24625
24762
|
else if (node.value.node_type === "access" && node.value.access.node_type === "access_field") needs_borrow_strdup = true;
|
|
24626
24763
|
else if ((node.value.node_type === "match" || node.value.node_type === "switch") && !return_join_owned_string) needs_borrow_strdup = true;
|
|
@@ -24731,6 +24868,7 @@ function build_return_node$1(node, status, nir_value) {
|
|
|
24731
24868
|
if (returns_fat_pair) emit_asm(status, `stp x0, x1, [sp, #-16]!\n`);
|
|
24732
24869
|
else if (returns_float) emit_asm(status, `str d0, [sp, #-16]!\n`);
|
|
24733
24870
|
else emit_asm(status, `str x0, [sp, #-16]!\n`);
|
|
24871
|
+
emit_nursery_joins_on_return_a64(status);
|
|
24734
24872
|
for (const decl of all_scope_frames(status).flat()) {
|
|
24735
24873
|
if (finalized.has(decl.name)) {
|
|
24736
24874
|
release_heap_string_fields(status, decl.name, decl.type.name);
|
|
@@ -26485,7 +26623,7 @@ function emit_stmt_dispatch(child, index, statements, status) {
|
|
|
26485
26623
|
const eval_node = nstmt.expr.node;
|
|
26486
26624
|
if (eval_node.node_type === "access") {
|
|
26487
26625
|
const inner = eval_node.access;
|
|
26488
|
-
if (inner.node_type === "access_func" &&
|
|
26626
|
+
if (inner.node_type === "access_func" && inner.is_nursery_spawn) inner.is_statement = true;
|
|
26489
26627
|
}
|
|
26490
26628
|
const restore_eval = apply_forward_use(ctx.use_sites, nstmt.expr.node);
|
|
26491
26629
|
try {
|
|
@@ -26891,7 +27029,7 @@ function emit_expr_from_nir$1(expr, status) {
|
|
|
26891
27029
|
function build_async_block_node$1(node, status, nir) {
|
|
26892
27030
|
const id = status.spawn_counter ?? 0;
|
|
26893
27031
|
status.spawn_counter = id + 1;
|
|
26894
|
-
|
|
27032
|
+
ensure_concurrency_runtime_a64(status);
|
|
26895
27033
|
const futures_off = allocate_stack_space(status, 8, 8);
|
|
26896
27034
|
const count_off = allocate_stack_space(status, 8, 8);
|
|
26897
27035
|
const cap_off = allocate_stack_space(status, 8, 8);
|
|
@@ -26913,28 +27051,6 @@ function build_async_block_node$1(node, status, nir) {
|
|
|
26913
27051
|
let deadline_off;
|
|
26914
27052
|
if (node.timeout) {
|
|
26915
27053
|
deadline_off = allocate_stack_space(status, 8, 8);
|
|
26916
|
-
emit_asm(status, `mov x0, #-1\n`);
|
|
26917
|
-
emit_asm(status, `str x0, [x29, #${deadline_off}]\n`);
|
|
26918
|
-
}
|
|
26919
|
-
if (!status.nursery_offsets) status.nursery_offsets = /* @__PURE__ */ new Map();
|
|
26920
|
-
status.nursery_offsets.set(id, {
|
|
26921
|
-
futures_off,
|
|
26922
|
-
count_off,
|
|
26923
|
-
cap_off,
|
|
26924
|
-
deadline_off
|
|
26925
|
-
});
|
|
26926
|
-
status.nursery_stack ??= [];
|
|
26927
|
-
status.nursery_stack.push(id);
|
|
26928
|
-
const old_scoped_declarations = status.scoped_declarations;
|
|
26929
|
-
status.scoped_declarations = [];
|
|
26930
|
-
build_block_with_cursor$1(node, nir?.body, status);
|
|
26931
|
-
status.scoped_declarations = old_scoped_declarations;
|
|
26932
|
-
status.nursery_stack.pop();
|
|
26933
|
-
status.nursery_offsets.delete(id);
|
|
26934
|
-
emit_asm(status, `// nursery ${id}: join all futures\n`);
|
|
26935
|
-
emit_asm(status, `ldr x20, [x29, #${futures_off}]\n`);
|
|
26936
|
-
emit_asm(status, `ldr w22, [x29, #${count_off}]\n`);
|
|
26937
|
-
if (deadline_off !== void 0) {
|
|
26938
27054
|
emit_asm(status, `// Compute deadline: now + timeout_ms\n`);
|
|
26939
27055
|
build_node(node.timeout, status);
|
|
26940
27056
|
ensure_newline(status);
|
|
@@ -26955,11 +27071,57 @@ function build_async_block_node$1(node, status, nir) {
|
|
|
26955
27071
|
emit_asm(status, `add x0, x1, x0\n`);
|
|
26956
27072
|
emit_asm(status, `str x0, [x29, #${deadline_off}]\n`);
|
|
26957
27073
|
}
|
|
26958
|
-
|
|
26959
|
-
|
|
26960
|
-
|
|
26961
|
-
|
|
26962
|
-
|
|
27074
|
+
if (!status.nursery_offsets) status.nursery_offsets = /* @__PURE__ */ new Map();
|
|
27075
|
+
status.nursery_offsets.set(id, {
|
|
27076
|
+
futures_off,
|
|
27077
|
+
count_off,
|
|
27078
|
+
cap_off,
|
|
27079
|
+
deadline_off
|
|
27080
|
+
});
|
|
27081
|
+
if (!status.nursery_meta) status.nursery_meta = /* @__PURE__ */ new Map();
|
|
27082
|
+
status.nursery_meta.set(id, {
|
|
27083
|
+
mode: node.mode ?? "all",
|
|
27084
|
+
has_deadline: !!node.timeout
|
|
27085
|
+
});
|
|
27086
|
+
status.nursery_stack ??= [];
|
|
27087
|
+
status.nursery_stack.push(id);
|
|
27088
|
+
const old_scoped_declarations = status.scoped_declarations;
|
|
27089
|
+
status.scoped_declarations = [];
|
|
27090
|
+
build_block_with_cursor$1(node, nir?.body, status);
|
|
27091
|
+
status.scoped_declarations = old_scoped_declarations;
|
|
27092
|
+
status.nursery_stack.pop();
|
|
27093
|
+
emit_asm(status, `// nursery ${id}: join all futures\n`);
|
|
27094
|
+
emit_nursery_join_a64(status, id, `__nursery_${id}`);
|
|
27095
|
+
status.nursery_offsets.delete(id);
|
|
27096
|
+
status.nursery_meta?.delete(id);
|
|
27097
|
+
}
|
|
27098
|
+
/**
|
|
27099
|
+
* Emit one nursery's join sequence in assembly: the race-mode wait (if any),
|
|
27100
|
+
* the wait+release loop over every registered future, and the futures-list
|
|
27101
|
+
* free. Shared by the block exit and the RETURN path — a `return` inside an
|
|
27102
|
+
* `async { }` block must route through the join (the structured-concurrency
|
|
27103
|
+
* contract). The nursery's per-invocation state lives at this function's
|
|
27104
|
+
* stack frame (offsets from x29), so a return site inside the block reaches
|
|
27105
|
+
* the same state. `label_prefix` must be unique per emission: the block exit
|
|
27106
|
+
* uses `__nursery_<id>`, a return site `__nursery_<id>_retjoin<N>` (the
|
|
27107
|
+
* block-exit labels would collide with a second loop in the same function).
|
|
27108
|
+
* Clobbers x0-x3, x20, x22, x23 (backend scratch — nothing live across a
|
|
27109
|
+
* join).
|
|
27110
|
+
*/
|
|
27111
|
+
function emit_nursery_join_a64(status, id, label_prefix) {
|
|
27112
|
+
const offsets = status.nursery_offsets?.get(id);
|
|
27113
|
+
if (!offsets) return;
|
|
27114
|
+
const { futures_off, count_off, deadline_off } = offsets;
|
|
27115
|
+
const is_race = (status.nursery_meta?.get(id) ?? {
|
|
27116
|
+
mode: "all",
|
|
27117
|
+
has_deadline: false
|
|
27118
|
+
}).mode === "race";
|
|
27119
|
+
const loop_start = `${label_prefix}_join_start`;
|
|
27120
|
+
const loop_end = `${label_prefix}_join_end`;
|
|
27121
|
+
const loop_release = `${label_prefix}_release`;
|
|
27122
|
+
const no_list = `${label_prefix}_no_list`;
|
|
27123
|
+
emit_asm(status, `ldr x20, [x29, #${futures_off}]\n`);
|
|
27124
|
+
emit_asm(status, `ldr w22, [x29, #${count_off}]\n`);
|
|
26963
27125
|
if (is_race) {
|
|
26964
27126
|
emit_asm(status, `mov x0, x20\n`);
|
|
26965
27127
|
emit_asm(status, `mov x1, x22\n`);
|
|
@@ -26967,6 +27129,7 @@ function build_async_block_node$1(node, status, nir) {
|
|
|
26967
27129
|
else emit_asm(status, `mov x2, #0\n`);
|
|
26968
27130
|
emit_asm(status, `bl ___nomen_nursery_race_wait\n`);
|
|
26969
27131
|
}
|
|
27132
|
+
emit_asm(status, `mov x23, xzr\n`);
|
|
26970
27133
|
emit_asm(status, `${loop_start}:\n`);
|
|
26971
27134
|
emit_asm(status, `cmp x23, x22\n`);
|
|
26972
27135
|
emit_asm(status, `b.ge ${loop_end}\n`);
|
|
@@ -26991,9 +27154,28 @@ function build_async_block_node$1(node, status, nir) {
|
|
|
26991
27154
|
emit_asm(status, `b ${loop_start}\n`);
|
|
26992
27155
|
emit_asm(status, `${loop_end}:\n`);
|
|
26993
27156
|
emit_asm(status, `ldr x0, [x29, #${futures_off}]\n`);
|
|
26994
|
-
emit_asm(status, `cbz x0,
|
|
27157
|
+
emit_asm(status, `cbz x0, ${no_list}\n`);
|
|
26995
27158
|
emit_asm(status, `bl _free\n`);
|
|
26996
|
-
emit_asm(status,
|
|
27159
|
+
emit_asm(status, `${no_list}:\n`);
|
|
27160
|
+
}
|
|
27161
|
+
let retjoin_counter = 0;
|
|
27162
|
+
/**
|
|
27163
|
+
* Emit the join sequence for every nursery the current `return` must route
|
|
27164
|
+
* through — innermost first. Only nurseries belonging to the CURRENT
|
|
27165
|
+
* function (stack depth >= the function's entry depth) apply: a lambda
|
|
27166
|
+
* defined inside an `async { }` block inherits the block's id on
|
|
27167
|
+
* nursery_stack (for lexical spawn captures), but its `return` exits the
|
|
27168
|
+
* lambda, whose frame has no nursery state. The return value must already
|
|
27169
|
+
* be parked (the join's `bl`s clobber x0/x1).
|
|
27170
|
+
*/
|
|
27171
|
+
function emit_nursery_joins_on_return_a64(status) {
|
|
27172
|
+
if (!status.nursery_stack?.length) return;
|
|
27173
|
+
const base = status.function_nursery_depth ?? 0;
|
|
27174
|
+
for (let i = status.nursery_stack.length - 1; i >= base; i--) {
|
|
27175
|
+
const id = status.nursery_stack[i];
|
|
27176
|
+
emit_asm(status, `// return inside async block: join nursery ${id} before leaving\n`);
|
|
27177
|
+
emit_nursery_join_a64(status, id, `__nursery_${id}_retjoin${retjoin_counter++}`);
|
|
27178
|
+
}
|
|
26997
27179
|
}
|
|
26998
27180
|
//#endregion
|
|
26999
27181
|
//#region ../src/build_aarch64/build_break_node.ts
|
|
@@ -27482,6 +27664,7 @@ function build_inline_method(struct_node, func, status) {
|
|
|
27482
27664
|
const old_param_vars = status.function_param_vars;
|
|
27483
27665
|
status.stack_offsets = /* @__PURE__ */ new Map();
|
|
27484
27666
|
const old_param_types = status.function_param_types;
|
|
27667
|
+
const old_local_types = status.function_local_types;
|
|
27485
27668
|
const old_return_label = status.function_return_label;
|
|
27486
27669
|
const old_ref_params = status.function_ref_params;
|
|
27487
27670
|
const old_struct_return_buffer = status.struct_return_buffer;
|
|
@@ -27548,6 +27731,7 @@ function build_inline_method(struct_node, func, status) {
|
|
|
27548
27731
|
status.function_ref_params = /* @__PURE__ */ new Set();
|
|
27549
27732
|
status.function_param_types = /* @__PURE__ */ new Map();
|
|
27550
27733
|
for (const p of func.params) if (p.type) status.function_param_types.set(p.name, p.type);
|
|
27734
|
+
status.function_local_types = /* @__PURE__ */ new Map();
|
|
27551
27735
|
if (needs_x19) status.function_param_regs.set("self", "x19");
|
|
27552
27736
|
if (process.env.AMI_HOMES && func.name === "get") console.log(`HOMES-DBG get: needs_x19=${needs_x19} self_is_var=${self_is_var} code_tail=${JSON.stringify(status.code.slice(-120))}`);
|
|
27553
27737
|
const saved_stack_slots = [];
|
|
@@ -27628,6 +27812,7 @@ function build_inline_method(struct_node, func, status) {
|
|
|
27628
27812
|
status.function_param_regs = old_param_regs;
|
|
27629
27813
|
status.function_param_vars = old_param_vars;
|
|
27630
27814
|
status.function_param_types = old_param_types;
|
|
27815
|
+
status.function_local_types = old_local_types;
|
|
27631
27816
|
status.function_ref_params = old_ref_params;
|
|
27632
27817
|
status.function_return_label = old_return_label;
|
|
27633
27818
|
status.stack_offsets = old_stack_offsets;
|
|
@@ -27658,6 +27843,7 @@ function build_inline_function(func, status) {
|
|
|
27658
27843
|
const old_param_vars = status.function_param_vars;
|
|
27659
27844
|
status.stack_offsets = /* @__PURE__ */ new Map();
|
|
27660
27845
|
const old_param_types = status.function_param_types;
|
|
27846
|
+
const old_local_types = status.function_local_types;
|
|
27661
27847
|
const old_return_label = status.function_return_label;
|
|
27662
27848
|
const old_ref_params = status.function_ref_params;
|
|
27663
27849
|
const old_struct_return_buffer = status.struct_return_buffer;
|
|
@@ -27716,6 +27902,7 @@ function build_inline_function(func, status) {
|
|
|
27716
27902
|
status.function_ref_params = /* @__PURE__ */ new Set();
|
|
27717
27903
|
status.function_param_types = /* @__PURE__ */ new Map();
|
|
27718
27904
|
for (const p of func.params) if (p.type) status.function_param_types.set(p.name, p.type);
|
|
27905
|
+
status.function_local_types = /* @__PURE__ */ new Map();
|
|
27719
27906
|
const saved_stack_slots = [];
|
|
27720
27907
|
const inline_reloads = [];
|
|
27721
27908
|
const inline_push_slots = [];
|
|
@@ -27785,6 +27972,7 @@ function build_inline_function(func, status) {
|
|
|
27785
27972
|
status.function_param_regs = old_param_regs;
|
|
27786
27973
|
status.function_param_vars = old_param_vars;
|
|
27787
27974
|
status.function_param_types = old_param_types;
|
|
27975
|
+
status.function_local_types = old_local_types;
|
|
27788
27976
|
status.function_ref_params = old_ref_params;
|
|
27789
27977
|
status.function_return_label = old_return_label;
|
|
27790
27978
|
status.stack_offsets = old_stack_offsets;
|
|
@@ -28217,7 +28405,7 @@ function build_function_call_node$1(node, status) {
|
|
|
28217
28405
|
let arg_is_class = false;
|
|
28218
28406
|
if (arg_name) {
|
|
28219
28407
|
const tn = arg.type?.name ?? status.variable_types?.get(arg_name)?.name;
|
|
28220
|
-
arg_is_class = !!tn && !!status.structs.find((s) => s.name === tn && s.is_class);
|
|
28408
|
+
arg_is_class = !!tn && (!!status.structs.find((s) => s.name === tn && s.is_class) || !!status.traits.find((t) => t.name === tn));
|
|
28221
28409
|
}
|
|
28222
28410
|
const ref_param_slot = arg_name !== void 0 ? status.ref_class_slots?.get(arg_name) : void 0;
|
|
28223
28411
|
if (ref_param_slot !== void 0) emit_ref_class_param_slot(status, ref_param_slot, arg_name, ref_class_param_reload);
|
|
@@ -28286,11 +28474,11 @@ function build_function_call_node$1(node, status) {
|
|
|
28286
28474
|
}
|
|
28287
28475
|
}
|
|
28288
28476
|
}
|
|
28289
|
-
if (is_struct &&
|
|
28477
|
+
if (is_struct && is_struct.is_class) emit_asm(status, `ldr x0, [sp]\n`);
|
|
28478
|
+
else if (is_struct && status.struct_return_buffer) {
|
|
28290
28479
|
if (status.return_buffer_stack_offset !== void 0) emit_asm(status, `ldr x8, [x29, #${status.return_buffer_stack_offset}]\n`);
|
|
28291
28480
|
emit_asm(status, `mov x0, ${status.struct_return_buffer}\n`);
|
|
28292
|
-
} else if (is_struct && is_struct.is_class)
|
|
28293
|
-
else if (is_struct && !is_struct.is_class) {
|
|
28481
|
+
} else if (is_struct && !is_struct.is_class) {
|
|
28294
28482
|
const temp_addr = `_temp_${temp_counter - 1}`;
|
|
28295
28483
|
emit_asm(status, `add x0, x29, #${status.stack_offsets.get(temp_addr)}\n`);
|
|
28296
28484
|
} else if (!is_struct && node.type?.name && !status.call_x8_preset) {
|
|
@@ -28352,16 +28540,17 @@ function build_function_call_node$1(node, status) {
|
|
|
28352
28540
|
}
|
|
28353
28541
|
}
|
|
28354
28542
|
build_swap_params(node, status);
|
|
28355
|
-
if (is_struct &&
|
|
28356
|
-
|
|
28357
|
-
|
|
28358
|
-
|
|
28359
|
-
|
|
28543
|
+
if (is_struct && is_struct.is_class) emit_asm(status, `ldr x0, [sp], #16\n`);
|
|
28544
|
+
else if (is_struct && status.struct_return_buffer) {
|
|
28545
|
+
if (status.return_buffer_stack_offset !== void 0) emit_asm(status, `ldr x0, [x29, #${status.return_buffer_stack_offset}]\n`);
|
|
28546
|
+
else emit_asm(status, `mov x0, ${status.struct_return_buffer}\n`);
|
|
28547
|
+
} else if (is_struct && !status.struct_return_buffer) {
|
|
28548
|
+
const temp_addr = `_temp_${temp_counter - 1}`;
|
|
28549
|
+
const offset = status.stack_offsets.get(temp_addr);
|
|
28550
|
+
emit_asm(status, `add x0, x29, #${offset}\n`);
|
|
28551
|
+
if (node.field_overrides?.length) {
|
|
28552
|
+
emit_field_overrides(temp_addr, node, build_node, status);
|
|
28360
28553
|
emit_asm(status, `add x0, x29, #${offset}\n`);
|
|
28361
|
-
if (node.field_overrides?.length) {
|
|
28362
|
-
emit_field_overrides(temp_addr, node, build_node, status);
|
|
28363
|
-
emit_asm(status, `add x0, x29, #${offset}\n`);
|
|
28364
|
-
}
|
|
28365
28554
|
}
|
|
28366
28555
|
}
|
|
28367
28556
|
if (node.name.startsWith("_string_interpolate_")) {
|
|
@@ -28752,6 +28941,14 @@ struct nomen_future {
|
|
|
28752
28941
|
// The fiber whose task this future is (set at fiber spawn). Cancellation
|
|
28753
28942
|
// uses it to wake a fiber parked on a primitive waitq.
|
|
28754
28943
|
struct nomen_fiber *owning_fiber;
|
|
28944
|
+
// 1 when the result slot holds a FAT string (set at construction, which
|
|
28945
|
+
// knows the wrapped call's return type). The last release frees an
|
|
28946
|
+
// unconsumed string buffer from the cell: result() zeroes the cell when
|
|
28947
|
+
// it moves the value out, and Task's #destroy zeroes after its own
|
|
28948
|
+
// free, so a non-NULL ptr at the last release means "never consumed".
|
|
28949
|
+
// Appended LAST: raw aarch64 bodies read refs/result_slot/owner_args at
|
|
28950
|
+
// fixed offsets (#116/#128/#136), which must not shift.
|
|
28951
|
+
int slot_fat;
|
|
28755
28952
|
};
|
|
28756
28953
|
// Fiber seam (defined in FIBER_HEADER, appended after this block). Declared
|
|
28757
28954
|
// here — after struct nomen_future is complete — so the prototypes bind to
|
|
@@ -28877,6 +29074,19 @@ static void __nomen_future_release(struct nomen_future *f) {
|
|
|
28877
29074
|
pthread_mutex_destroy(&f->mu);
|
|
28878
29075
|
pthread_cond_destroy(&f->cv);
|
|
28879
29076
|
free(f->cancel_flag);
|
|
29077
|
+
// An unconsumed fat-string result: free the buffer the trampoline
|
|
29078
|
+
// stored (race-mode and fire-and-forget tasks never hand it to a
|
|
29079
|
+
// consumer). Every consumer path zeroes the cell first — result()
|
|
29080
|
+
// on move-out, Task's #destroy after its own free — so a non-NULL
|
|
29081
|
+
// ptr here is unambiguous and the free happens exactly once.
|
|
29082
|
+
if (f->slot_fat && f->result_slot) {
|
|
29083
|
+
unsigned long long *_s = (unsigned long long *)f->result_slot;
|
|
29084
|
+
if (_s[0]) {
|
|
29085
|
+
free((void *)_s[0]);
|
|
29086
|
+
_s[0] = 0;
|
|
29087
|
+
_s[1] = 0;
|
|
29088
|
+
}
|
|
29089
|
+
}
|
|
28880
29090
|
free(f->result_slot);
|
|
28881
29091
|
// The task closure is owned by the future: disposing it here (the
|
|
28882
29092
|
// last release, ordered after every use — trampoline, Task destroy,
|
|
@@ -29013,7 +29223,11 @@ static void __nomen_future_cancel(struct nomen_future *f) {
|
|
|
29013
29223
|
// schedule() only queues PARKED fibers, so a double wake is harmless.
|
|
29014
29224
|
if (f->owning_fiber) __nomen_fiber_schedule(f->owning_fiber);
|
|
29015
29225
|
}
|
|
29226
|
+
// Non-blocking completion peek (Task.is_done): 1 once the future's task has
|
|
29227
|
+
// finished, 0 while it may still be running (and for a NULL — a consumed or
|
|
29228
|
+
// never-launched handle).
|
|
29016
29229
|
static int __nomen_future_is_done(struct nomen_future *f) {
|
|
29230
|
+
if (!f) return 0;
|
|
29017
29231
|
pthread_mutex_lock(&f->mu);
|
|
29018
29232
|
int d = f->done;
|
|
29019
29233
|
pthread_mutex_unlock(&f->mu);
|
|
@@ -29885,95 +30099,6 @@ static int __nomen_deadlock_check_worker(void) {
|
|
|
29885
30099
|
}
|
|
29886
30100
|
`;
|
|
29887
30101
|
/**
|
|
29888
|
-
* Build a `.start()` launch on a `Thread(fn(args))` construction — the
|
|
29889
|
-
* surface form of a direct spawn (CLOSURE.md Phase 3b). The
|
|
29890
|
-
* receiver is the construction expression itself or any Thread-typed
|
|
29891
|
-
* expression (a stored `var t = Thread(...)` binding); its fields carry the
|
|
29892
|
-
* task closure packed eagerly at the construction site, plus the result
|
|
29893
|
-
* slot, cancel flag, and future. This submits the closure to the worker
|
|
29894
|
-
* pool, registers the future with the enclosing nursery (if any), transfers
|
|
29895
|
-
* the handles out of the instance (so its #destroy becomes a no-op), and
|
|
29896
|
-
* yields a Task<T> handle — or nothing, for a fire-and-forget statement.
|
|
29897
|
-
*/
|
|
29898
|
-
function build_thread_start(access_func, target, status) {
|
|
29899
|
-
ensure_concurrency_runtime(status);
|
|
29900
|
-
status.spawn_counter = (status.spawn_counter ?? 0) + 1;
|
|
29901
|
-
const t_arg = spawn_result_type_arg$4(access_func.function_return_type);
|
|
29902
|
-
const mono_thread = mono_type_name("Thread", [t_arg]);
|
|
29903
|
-
const mono_task_name = mono_type_name("Task", [t_arg]);
|
|
29904
|
-
const nursery_id = status.nursery_stack?.at(-1);
|
|
29905
|
-
const fire_and_forget = !!access_func.is_statement;
|
|
29906
|
-
const refs = fire_and_forget ? nursery_id !== void 0 ? 2 : 1 : nursery_id !== void 0 ? 3 : 2;
|
|
29907
|
-
const target_is_temp = target.node_type === "func_call" && !!(target.is_thread_ctor || target.is_fiber_ctor);
|
|
29908
|
-
status.code += `({\n`;
|
|
29909
|
-
status.code += `\tstruct ${mono_thread} *_self = `;
|
|
29910
|
-
build_node$1(target, status);
|
|
29911
|
-
status.code += `;\n`;
|
|
29912
|
-
status.code += `\tstruct nomen_future *_future = (struct nomen_future *)_self->future;\n`;
|
|
29913
|
-
status.code += `\tvoid *_result_ptr = (void *)_self->result_slot;\n`;
|
|
29914
|
-
status.code += `\tunsigned long long *_cancel_ptr = (unsigned long long *)_self->cancel_flag;\n`;
|
|
29915
|
-
status.code += `\tstruct nomen_closure *_closure = (struct nomen_closure *)_self->task;\n`;
|
|
29916
|
-
status.code += `\t_future->refs = ${refs};\n`;
|
|
29917
|
-
status.code += `\t__nomen_pool_submit(_closure);\n`;
|
|
29918
|
-
if (nursery_id !== void 0) status.code += `\t__nomen_nursery_track((void **)&__nomen_nursery_${nursery_id}_futures, &__nomen_nursery_${nursery_id}_count, &__nomen_nursery_${nursery_id}_cap, _future);\n`;
|
|
29919
|
-
status.code += `\t_self->task = 0;\n`;
|
|
29920
|
-
status.code += `\t_self->future = 0;\n`;
|
|
29921
|
-
status.code += `\t_self->result_slot = 0;\n`;
|
|
29922
|
-
status.code += `\t_self->cancel_flag = 0;\n`;
|
|
29923
|
-
status.code += `\t_self->started = 1;\n`;
|
|
29924
|
-
if (target_is_temp) status.code += `\tfree(_self);\n`;
|
|
29925
|
-
if (fire_and_forget) status.code += `\t(void)0;\n`;
|
|
29926
|
-
else {
|
|
29927
|
-
status.code += `\tstruct ${mono_task_name} *_task = (struct ${mono_task_name} *)malloc(sizeof(struct ${mono_task_name}));\n`;
|
|
29928
|
-
status.code += `\t_task->_vt = &_${mono_task_name}_traits;\n`;
|
|
29929
|
-
status.code += `\t_task->handle = 0;\n`;
|
|
29930
|
-
status.code += `\t_task->done = 0;\n`;
|
|
29931
|
-
status.code += `\t_task->result_slot = (unsigned long long)_result_ptr;\n`;
|
|
29932
|
-
status.code += `\t_task->cancel_flag = (unsigned long long)_cancel_ptr;\n`;
|
|
29933
|
-
status.code += `\t_task->future = (unsigned long long)_future;\n`;
|
|
29934
|
-
status.code += `\t_task;\n`;
|
|
29935
|
-
}
|
|
29936
|
-
status.code += `})\n`;
|
|
29937
|
-
}
|
|
29938
|
-
/**
|
|
29939
|
-
* Build a `.detach()` launch on a `Thread(fn(args))` construction — the
|
|
29940
|
-
* daemon form (ASYNC.md, "Daemon tasks"). The packed task closure runs on a
|
|
29941
|
-
* dedicated detached pthread: never a pool worker, never joined, killed by
|
|
29942
|
-
* process exit by design. No future is used: the construction allocated one
|
|
29943
|
-
* (a Thread always carries the full launch machinery), so detach releases
|
|
29944
|
-
* that unused future (first detaching the closure from it — the daemon
|
|
29945
|
-
* runner owns the closure now) and marks the instance started.
|
|
29946
|
-
*/
|
|
29947
|
-
function build_thread_detach(access_func, target, status) {
|
|
29948
|
-
ensure_concurrency_runtime(status);
|
|
29949
|
-
status.spawn_counter = (status.spawn_counter ?? 0) + 1;
|
|
29950
|
-
const mono_thread = mono_type_name("Thread", [spawn_result_type_arg$4(access_func.function_return_type)]);
|
|
29951
|
-
const target_is_temp = target.node_type === "func_call" && !!(target.is_thread_ctor || target.is_fiber_ctor);
|
|
29952
|
-
status.code += `({\n`;
|
|
29953
|
-
status.code += `\tstruct ${mono_thread} *_self = `;
|
|
29954
|
-
build_node$1(target, status);
|
|
29955
|
-
status.code += `;\n`;
|
|
29956
|
-
status.code += `\tstruct nomen_future *_unused_future = (struct nomen_future *)_self->future;\n`;
|
|
29957
|
-
status.code += `\tif (_unused_future) {\n`;
|
|
29958
|
-
status.code += `\t\t_unused_future->owner_args = NULL;\n`;
|
|
29959
|
-
status.code += `\t\t__nomen_future_release(_unused_future);\n`;
|
|
29960
|
-
status.code += `\t}\n`;
|
|
29961
|
-
status.code += `\t__nomen_task_detach((struct nomen_closure *)_self->task);\n`;
|
|
29962
|
-
status.code += `\t_self->task = 0;\n`;
|
|
29963
|
-
status.code += `\t_self->future = 0;\n`;
|
|
29964
|
-
status.code += `\t_self->result_slot = 0;\n`;
|
|
29965
|
-
status.code += `\t_self->cancel_flag = 0;\n`;
|
|
29966
|
-
status.code += `\t_self->started = 1;\n`;
|
|
29967
|
-
if (target_is_temp) status.code += `\tfree(_self);\n`;
|
|
29968
|
-
status.code += `\t(void)0;\n`;
|
|
29969
|
-
status.code += `})\n`;
|
|
29970
|
-
}
|
|
29971
|
-
/** The launch's T as a Type: the stamped wrapped-call return type, with
|
|
29972
|
-
* void coerced to uint64 (Task's result-slot convention). */
|
|
29973
|
-
function spawn_result_type_arg$4(return_type) {
|
|
29974
|
-
return return_type?.name && return_type.name !== "void" && return_type.name !== "?" ? new Type(return_type.name) : new Type("uint64");
|
|
29975
|
-
}
|
|
29976
|
-
/**
|
|
29977
30102
|
* Resolve each spawn argument's C type. Classes/traits are pointers;
|
|
29978
30103
|
* primitives and by-value structs use c_type's output directly. Generic
|
|
29979
30104
|
* instantiations (e.g. Channel<uint64>) use the monomorphized C name
|
|
@@ -30058,98 +30183,41 @@ function find_spawn_callee(name, status) {
|
|
|
30058
30183
|
visit(status.root);
|
|
30059
30184
|
return found;
|
|
30060
30185
|
}
|
|
30061
|
-
//#endregion
|
|
30062
|
-
//#region ../src/build_c/build_fiber_spawn.ts
|
|
30063
30186
|
/**
|
|
30064
|
-
*
|
|
30065
|
-
*
|
|
30066
|
-
*
|
|
30067
|
-
*
|
|
30068
|
-
*
|
|
30069
|
-
* switch is ucontext-based here: portable across every target the C backend
|
|
30070
|
-
* serves. The aarch64 backend's FIBER_HEADER_C is the same scheduler with a
|
|
30071
|
-
* naked-asm switch instead.
|
|
30072
|
-
*
|
|
30073
|
-
* Execution model: a fiber is a heap (or caller-provided) stack plus a
|
|
30074
|
-
* context. `__nomen_fiber_spawn` enqueues it; pool workers drain the fiber
|
|
30075
|
-
* queue between pool tasks, so a parked fiber frees its worker. In
|
|
30076
|
-
* cooperative mode (Fiber.set_cooperative) the spawning thread drains the
|
|
30077
|
-
* queue itself — no worker threads are ever started, which is the
|
|
30078
|
-
* no-thread/embedded path.
|
|
30079
|
-
*
|
|
30080
|
-
* Swap protocol: the runner captures its continuation into a stack-local
|
|
30081
|
-
* `ret` (its address rides the per-thread `__nomen_fiber_ret` pointer) and
|
|
30082
|
-
* switches into `f->ctx`. A suspending fiber saves ITS continuation into
|
|
30083
|
-
* `f->ctx` (overwriting the entry state after the first suspension) and
|
|
30084
|
-
* switches into `__nomen_fiber_ret`. Resume therefore always continues where
|
|
30085
|
-
* the fiber last suspended.
|
|
30187
|
+
* Whether any raw `#arch` body under this node
|
|
30188
|
+
* references the concurrency runtime (`__nomen_*` symbols — pool, futures,
|
|
30189
|
+
* fiber scheduler, nursery tracking, netpoller). The runtime is the
|
|
30190
|
+
* LIBRARY's dependency: a method whose raw body drives it pulls it in,
|
|
30191
|
+
* keyed on body CONTENT — never on a type name.
|
|
30086
30192
|
*/
|
|
30193
|
+
function raw_bodies_reference_runtime(node) {
|
|
30194
|
+
if (!node || typeof node !== "object") return false;
|
|
30195
|
+
if (node.node_type === "raw") {
|
|
30196
|
+
const raw = node.value;
|
|
30197
|
+
return typeof raw === "string" && raw.includes("__nomen_");
|
|
30198
|
+
}
|
|
30199
|
+
for (const key of Object.keys(node)) {
|
|
30200
|
+
if (key === "parent" || key === "scope") continue;
|
|
30201
|
+
const v = node[key];
|
|
30202
|
+
if (Array.isArray(v)) {
|
|
30203
|
+
for (const item of v) if (raw_bodies_reference_runtime(item)) return true;
|
|
30204
|
+
} else if (v && typeof v === "object" && "node_type" in v) {
|
|
30205
|
+
if (raw_bodies_reference_runtime(v)) return true;
|
|
30206
|
+
}
|
|
30207
|
+
}
|
|
30208
|
+
return false;
|
|
30209
|
+
}
|
|
30087
30210
|
/**
|
|
30088
|
-
*
|
|
30089
|
-
*
|
|
30090
|
-
* (
|
|
30091
|
-
*
|
|
30092
|
-
* cancel flag, and future. This launches the closure on the fiber
|
|
30093
|
-
* scheduler (a heap stack, or the caller's fixed-size array buffer for
|
|
30094
|
-
* start_on — validated at check time, >= 16 KB), registers the future with
|
|
30095
|
-
* the enclosing nursery (if any), transfers the handles out of the
|
|
30096
|
-
* instance, and yields Task<T> (or nothing, fire-and-forget).
|
|
30211
|
+
* Pull the concurrency runtime in when this struct method's raw bodies
|
|
30212
|
+
* reference it (deduped by the ensure flags). Replaces the old per-type-name
|
|
30213
|
+
* gates (CONCURRENCY_TYPES / the aarch64 Fiber||Thread check): the runtime
|
|
30214
|
+
* dependency now belongs to the library code itself.
|
|
30097
30215
|
*/
|
|
30098
|
-
function
|
|
30099
|
-
|
|
30100
|
-
|
|
30101
|
-
|
|
30102
|
-
const mono_fiber = mono_type_name("Fiber", [t_arg]);
|
|
30103
|
-
const mono_task_name = mono_type_name("Task", [t_arg]);
|
|
30104
|
-
const nursery_id = status.nursery_stack?.at(-1);
|
|
30105
|
-
const fire_and_forget = !!access_func.is_statement;
|
|
30106
|
-
const target_is_temp = target.node_type === "func_call" && !!(target.is_thread_ctor || target.is_fiber_ctor);
|
|
30107
|
-
const refs = fire_and_forget ? nursery_id !== void 0 ? 2 : 1 : nursery_id !== void 0 ? 3 : 2;
|
|
30108
|
-
status.code += `({\n`;
|
|
30109
|
-
status.code += `\tstruct ${mono_fiber} *_self = `;
|
|
30110
|
-
build_node$1(target, status);
|
|
30111
|
-
status.code += `;\n`;
|
|
30112
|
-
status.code += `\tstruct nomen_future *_future = (struct nomen_future *)_self->future;\n`;
|
|
30113
|
-
status.code += `\tvoid *_result_ptr = (void *)_self->result_slot;\n`;
|
|
30114
|
-
status.code += `\tunsigned long long *_cancel_ptr = (unsigned long long *)_self->cancel_flag;\n`;
|
|
30115
|
-
status.code += `\tstruct nomen_closure *_closure = (struct nomen_closure *)_self->task;\n`;
|
|
30116
|
-
status.code += `\t_future->refs = ${refs};\n`;
|
|
30117
|
-
status.code += `\t__nomen_fiber_spawn${start_on ? "_on" : ""}(_closure, _future`;
|
|
30118
|
-
if (start_on) {
|
|
30119
|
-
const buf_type = type_from_value_node$1(start_on);
|
|
30120
|
-
const elem_size = buf_type.name === "string" ? 16 : 8;
|
|
30121
|
-
const len_node = buf_type.length;
|
|
30122
|
-
const len = len_node && len_node.node_type === "value" ? parseInt(len_node.value, 10) : NaN;
|
|
30123
|
-
const stack_bytes = Number.isNaN(len) ? 0 : len * elem_size;
|
|
30124
|
-
status.code += `, (void *)`;
|
|
30125
|
-
build_node$1(start_on, status);
|
|
30126
|
-
status.code += `, (size_t)${stack_bytes}`;
|
|
30127
|
-
}
|
|
30128
|
-
status.code += `);\n`;
|
|
30129
|
-
if (nursery_id !== void 0) status.code += `\t__nomen_nursery_track((void **)&__nomen_nursery_${nursery_id}_futures, &__nomen_nursery_${nursery_id}_count, &__nomen_nursery_${nursery_id}_cap, _future);\n`;
|
|
30130
|
-
status.code += `\t_self->task = 0;\n`;
|
|
30131
|
-
status.code += `\t_self->future = 0;\n`;
|
|
30132
|
-
status.code += `\t_self->result_slot = 0;\n`;
|
|
30133
|
-
status.code += `\t_self->cancel_flag = 0;\n`;
|
|
30134
|
-
status.code += `\t_self->started = 1;\n`;
|
|
30135
|
-
if (target_is_temp) status.code += `\tfree(_self);\n`;
|
|
30136
|
-
if (fire_and_forget) status.code += `\t(void)0;\n`;
|
|
30137
|
-
else {
|
|
30138
|
-
status.code += `\tstruct ${mono_task_name} *_task = (struct ${mono_task_name} *)malloc(sizeof(struct ${mono_task_name}));\n`;
|
|
30139
|
-
status.code += `\t_task->_vt = &_${mono_task_name}_traits;\n`;
|
|
30140
|
-
status.code += `\t_task->handle = 0;\n`;
|
|
30141
|
-
status.code += `\t_task->done = 0;\n`;
|
|
30142
|
-
status.code += `\t_task->result_slot = (unsigned long long)_result_ptr;\n`;
|
|
30143
|
-
status.code += `\t_task->cancel_flag = (unsigned long long)_cancel_ptr;\n`;
|
|
30144
|
-
status.code += `\t_task->future = (unsigned long long)_future;\n`;
|
|
30145
|
-
status.code += `\t_task;\n`;
|
|
30216
|
+
function ensure_runtime_for_method(func, status) {
|
|
30217
|
+
for (const stmt of func.statements ?? []) if (raw_bodies_reference_runtime(stmt)) {
|
|
30218
|
+
ensure_concurrency_runtime(status);
|
|
30219
|
+
return;
|
|
30146
30220
|
}
|
|
30147
|
-
status.code += `})\n`;
|
|
30148
|
-
}
|
|
30149
|
-
/** The launch's T as a Type: the stamped wrapped-call return type, with
|
|
30150
|
-
* void coerced to uint64 (Task's result-slot convention). */
|
|
30151
|
-
function spawn_result_type_arg$3(return_type) {
|
|
30152
|
-
return return_type?.name && return_type.name !== "void" && return_type.name !== "?" ? new Type(return_type.name) : new Type("uint64");
|
|
30153
30221
|
}
|
|
30154
30222
|
//#endregion
|
|
30155
30223
|
//#region ../src/build_c/build_nursery_spawn.ts
|
|
@@ -30174,7 +30242,7 @@ function build_nursery_spawn$1(node, nursery_ptr, status) {
|
|
|
30174
30242
|
const fire_and_forget = !!node.is_statement;
|
|
30175
30243
|
const refs = fire_and_forget ? 2 : 3;
|
|
30176
30244
|
ensure_concurrency_runtime(status);
|
|
30177
|
-
const t_arg = spawn_result_type_arg$
|
|
30245
|
+
const t_arg = spawn_result_type_arg$1(node.function_return_type);
|
|
30178
30246
|
const mono_thread = mono_type_name("Thread", [t_arg]);
|
|
30179
30247
|
const mono_task_name = mono_type_name("Task", [t_arg]);
|
|
30180
30248
|
const arg = node.params[0];
|
|
@@ -30211,7 +30279,7 @@ function build_nursery_spawn$1(node, nursery_ptr, status) {
|
|
|
30211
30279
|
}
|
|
30212
30280
|
/** The launch's T as a Type: the stamped wrapped-call return type, with
|
|
30213
30281
|
* void coerced to uint64 (Task's result-slot convention). */
|
|
30214
|
-
function spawn_result_type_arg$
|
|
30282
|
+
function spawn_result_type_arg$1(return_type) {
|
|
30215
30283
|
return return_type?.name && return_type.name !== "void" && return_type.name !== "?" ? new Type(return_type.name) : new Type("uint64");
|
|
30216
30284
|
}
|
|
30217
30285
|
//#endregion
|
|
@@ -31512,7 +31580,8 @@ function build_assignment_node$1(node, status, nir_rhs, nir_swap) {
|
|
|
31512
31580
|
const lhs_decl = status.scoped_declarations.find((d) => d.name === lhs_name);
|
|
31513
31581
|
const lhs_in_class_vars = !!status.class_vars?.has(lhs_name);
|
|
31514
31582
|
const lhs_type = lhs_decl?.type || status.variable_types?.get(lhs_name);
|
|
31515
|
-
const
|
|
31583
|
+
const lhs_mono_name = lhs_type ? mono_type_name(lhs_type) : "";
|
|
31584
|
+
const lhs_struct = lhs_mono_name ? status.structs.find((s) => s.name === lhs_mono_name) : null;
|
|
31516
31585
|
const lhs_is_string = lhs_type?.name === "string";
|
|
31517
31586
|
const lhs_is_class = !!lhs_struct?.is_class || lhs_in_class_vars;
|
|
31518
31587
|
const lhs_trait_class = lhs_scope_hit ? lhs_scope_hit.frame[lhs_scope_hit.index].trait_class_trait : void 0;
|
|
@@ -31580,11 +31649,12 @@ function build_assignment_node$1(node, status, nir_rhs, nir_swap) {
|
|
|
31580
31649
|
}
|
|
31581
31650
|
} else if (lhs_is_class && lhs_struct && !lhs_is_alias && (lhs_decl || lhs_in_class_vars)) {
|
|
31582
31651
|
const temp = `_deferred_${status.label_counter = (status.label_counter ?? 0) + 1}`;
|
|
31583
|
-
|
|
31652
|
+
const destroy_name = lhs_struct.name.startsWith(lhs_type.name) ? lhs_struct.name : lhs_type.name;
|
|
31653
|
+
status.code += `struct ${destroy_name}* ${temp} = ${lhs_name};\n`;
|
|
31584
31654
|
if (!status.deferred_frees) status.deferred_frees = [];
|
|
31585
31655
|
status.deferred_frees.push({
|
|
31586
31656
|
temp,
|
|
31587
|
-
struct_name:
|
|
31657
|
+
struct_name: destroy_name,
|
|
31588
31658
|
is_nullable: !!lhs_type?.is_nullable
|
|
31589
31659
|
});
|
|
31590
31660
|
} else if (lhs_is_class && lhs_is_alias && status.c_alias_owns_flags?.has(lhs_name)) {
|
|
@@ -31930,11 +32000,15 @@ function string_var_owns_heap(status, name) {
|
|
|
31930
32000
|
function build_async_block_node(node, status, nir) {
|
|
31931
32001
|
const id = status.spawn_counter ?? 0;
|
|
31932
32002
|
status.spawn_counter = id + 1;
|
|
31933
|
-
|
|
32003
|
+
ensure_concurrency_runtime(status);
|
|
31934
32004
|
const futures_name = `__nomen_nursery_${id}_futures`;
|
|
31935
32005
|
const count_name = `__nomen_nursery_${id}_count`;
|
|
31936
32006
|
const cap_name = `__nomen_nursery_${id}_cap`;
|
|
31937
|
-
|
|
32007
|
+
if (!status.nursery_meta) status.nursery_meta = /* @__PURE__ */ new Map();
|
|
32008
|
+
status.nursery_meta.set(id, {
|
|
32009
|
+
mode: node.mode ?? "all",
|
|
32010
|
+
has_deadline: !!node.timeout
|
|
32011
|
+
});
|
|
31938
32012
|
status.code += `{\n`;
|
|
31939
32013
|
status.code += `\tstruct nomen_future **${futures_name} = NULL;\n`;
|
|
31940
32014
|
status.code += `\tint ${count_name} = 0;\n`;
|
|
@@ -31966,7 +32040,35 @@ function build_async_block_node(node, status, nir) {
|
|
|
31966
32040
|
status.nursery_stack.push(id);
|
|
31967
32041
|
build_block_with_cursor(node, nir?.body, status);
|
|
31968
32042
|
status.nursery_stack.pop();
|
|
31969
|
-
|
|
32043
|
+
emit_nursery_join_c(status, id);
|
|
32044
|
+
build_auto_free(status);
|
|
32045
|
+
status.code += `}\n`;
|
|
32046
|
+
leave_c_scope(status);
|
|
32047
|
+
status.scoped_declarations = old_scoped_declarations;
|
|
32048
|
+
status.deferred_frees = old_deferred_frees;
|
|
32049
|
+
}
|
|
32050
|
+
/**
|
|
32051
|
+
* Emit one nursery's join sequence: the race-mode wait (if any), the
|
|
32052
|
+
* wait+release loop over every registered future, and the futures-list
|
|
32053
|
+
* free. Shared by the block exit and the RETURN path — a `return` inside an
|
|
32054
|
+
* `async { }` block must route through the join (the structured-concurrency
|
|
32055
|
+
* contract: the block's scoped resources must not die under still-running
|
|
32056
|
+
* tasks, and the per-future release must not be skipped). The names are
|
|
32057
|
+
* deterministic per nursery id, so a return site inside the block can
|
|
32058
|
+
* re-emit the same sequence textually (the block's tracking locals are
|
|
32059
|
+
* still in C scope there).
|
|
32060
|
+
*/
|
|
32061
|
+
function emit_nursery_join_c(status, id, is_return_path = false) {
|
|
32062
|
+
const meta = status.nursery_meta?.get(id) ?? {
|
|
32063
|
+
mode: "all",
|
|
32064
|
+
has_deadline: false
|
|
32065
|
+
};
|
|
32066
|
+
const futures_name = `__nomen_nursery_${id}_futures`;
|
|
32067
|
+
const count_name = `__nomen_nursery_${id}_count`;
|
|
32068
|
+
const idx_name = `__nomen_nursery_${id}_i`;
|
|
32069
|
+
const deadline_var = meta.has_deadline ? `_deadline_ms_${id}` : "";
|
|
32070
|
+
if (is_return_path) status.code += `\t// return inside async block: join nursery ${id} before leaving\n`;
|
|
32071
|
+
const is_race = meta.mode === "race";
|
|
31970
32072
|
if (is_race) status.code += `\t__nomen_nursery_race_wait((struct nomen_future **)${futures_name}, ${count_name}, ${deadline_var ? deadline_var : "0"});\n`;
|
|
31971
32073
|
status.code += `\tfor (int ${idx_name} = 0; ${idx_name} < ${count_name}; ${idx_name}++) {\n`;
|
|
31972
32074
|
status.code += `\t\tstruct nomen_future *_f = (struct nomen_future *)${futures_name}[${idx_name}];\n`;
|
|
@@ -31983,11 +32085,19 @@ function build_async_block_node(node, status, nir) {
|
|
|
31983
32085
|
status.code += `\t\t__nomen_future_release(_f);\n`;
|
|
31984
32086
|
status.code += `\t}\n`;
|
|
31985
32087
|
status.code += `\tfree(${futures_name});\n`;
|
|
31986
|
-
|
|
31987
|
-
|
|
31988
|
-
|
|
31989
|
-
|
|
31990
|
-
|
|
32088
|
+
}
|
|
32089
|
+
/**
|
|
32090
|
+
* Emit the join sequence for every nursery the current `return` must route
|
|
32091
|
+
* through — innermost first. Only nurseries belonging to the CURRENT
|
|
32092
|
+
* function (stack depth >= the function's entry depth) apply: a lambda
|
|
32093
|
+
* defined inside an `async { }` block inherits the block's id on
|
|
32094
|
+
* nursery_stack (for lexical spawn captures), but its `return` exits the
|
|
32095
|
+
* lambda, whose frame has no nursery state.
|
|
32096
|
+
*/
|
|
32097
|
+
function emit_nursery_joins_on_return_c(status) {
|
|
32098
|
+
if (!status.nursery_stack?.length) return;
|
|
32099
|
+
const base = status.function_nursery_depth ?? 0;
|
|
32100
|
+
for (let i = status.nursery_stack.length - 1; i >= base; i--) emit_nursery_join_c(status, status.nursery_stack[i], true);
|
|
31991
32101
|
}
|
|
31992
32102
|
//#endregion
|
|
31993
32103
|
//#region ../src/build_c/build_array_values_node.ts
|
|
@@ -32945,6 +33055,7 @@ function build_return_node(node, status, nir_value) {
|
|
|
32945
33055
|
const ret_is_null = returns_nullable_struct && (!node.value || node.value.node_type === "value" && node.value.value === "null");
|
|
32946
33056
|
if (returns_nullable_struct) {
|
|
32947
33057
|
if (ret_is_null) {
|
|
33058
|
+
emit_nursery_joins_on_return_c(status);
|
|
32948
33059
|
reclaim_all_c_scopes(status);
|
|
32949
33060
|
status.code += `*${ret_has} = 0;\n`;
|
|
32950
33061
|
status.code += `return (struct ${status.function_return_type.name}){0};\n`;
|
|
@@ -32953,6 +33064,7 @@ function build_return_node(node, status, nir_value) {
|
|
|
32953
33064
|
status.code += `*${ret_has} = 1;\n`;
|
|
32954
33065
|
}
|
|
32955
33066
|
if (!node.value) {
|
|
33067
|
+
emit_nursery_joins_on_return_c(status);
|
|
32956
33068
|
reclaim_all_c_scopes(status);
|
|
32957
33069
|
if (status.return_assign) status.code += `${status.return_assign} = 0;\n`;
|
|
32958
33070
|
else if (status.current_function_name?.toLocaleLowerCase() === "main") status.code += `return 0;\n`;
|
|
@@ -33005,6 +33117,7 @@ function build_return_node(node, status, nir_value) {
|
|
|
33005
33117
|
status.code += `_return_val->length = ${return_array_len};\n`;
|
|
33006
33118
|
status.code += `${elem_c_type}* _return_data = (${elem_c_type}*)((char*)_return_val + sizeof(struct ${array_struct}));\n`;
|
|
33007
33119
|
status.code += `for (long _i = 0; _i < ${return_array_len}; _i++) _return_data[_i] = ${return_array_var}[_i];\n`;
|
|
33120
|
+
emit_nursery_joins_on_return_c(status);
|
|
33008
33121
|
reclaim_all_c_scopes(status);
|
|
33009
33122
|
status.code += `return _return_val;\n`;
|
|
33010
33123
|
return;
|
|
@@ -33020,6 +33133,7 @@ function build_return_node(node, status, nir_value) {
|
|
|
33020
33133
|
emit_allocations(node.value, status);
|
|
33021
33134
|
emit_return_value(node.value, nir_value, status);
|
|
33022
33135
|
status.code += `;\n`;
|
|
33136
|
+
emit_nursery_joins_on_return_c(status);
|
|
33023
33137
|
reclaim_all_c_scopes(status);
|
|
33024
33138
|
status.code += `return;\n`;
|
|
33025
33139
|
} else {
|
|
@@ -33042,6 +33156,7 @@ function build_return_node(node, status, nir_value) {
|
|
|
33042
33156
|
build_node$1(node.value, status);
|
|
33043
33157
|
status.join_needs_owned_string = old_join_owned;
|
|
33044
33158
|
status.return_assign = old_return_assign;
|
|
33159
|
+
emit_nursery_joins_on_return_c(status);
|
|
33045
33160
|
reclaim_all_c_scopes(status);
|
|
33046
33161
|
if (string_join) status.code += any_branch_owned ? `return _return_val;\n` : `return nomen_str_dup(_return_val);\n`;
|
|
33047
33162
|
else status.code += `return _return_val;\n`;
|
|
@@ -33100,6 +33215,7 @@ function build_return_node(node, status, nir_value) {
|
|
|
33100
33215
|
status.code += `;\n`;
|
|
33101
33216
|
if (node.value.node_type === "access" && node.value.access?.node_type === "access_field" && !returns_string_zero && !returns_struct_zero && !returns_view_value && ret_type.name !== "string" && !ret_type.is_view && !ret_type.is_array && status.enums.find((e) => e.name === ret_type.name && e.has_associated_data)) status.code += `_return_val = ${ret_type.name}_copy(_return_val);\n`;
|
|
33102
33217
|
if (has_field_overrides(node.value)) emit_field_overrides("_return_val", node.value, build_node$1, status, "", ";\n");
|
|
33218
|
+
emit_nursery_joins_on_return_c(status);
|
|
33103
33219
|
reclaim_all_c_scopes(status);
|
|
33104
33220
|
status.code += `return _return_val;\n`;
|
|
33105
33221
|
}
|
|
@@ -33382,7 +33498,7 @@ function emit_stmt_from_nir(child, index, statements, status) {
|
|
|
33382
33498
|
const eval_node = nstmt.expr.node;
|
|
33383
33499
|
if (eval_node.node_type === "access") {
|
|
33384
33500
|
const inner = eval_node.access;
|
|
33385
|
-
if (inner.node_type === "access_func" &&
|
|
33501
|
+
if (inner.node_type === "access_func" && inner.is_nursery_spawn) inner.is_statement = true;
|
|
33386
33502
|
}
|
|
33387
33503
|
emit_expr_from_nir(nstmt.expr, status);
|
|
33388
33504
|
with_semicolon_tail(status, eval_node);
|
|
@@ -33911,22 +34027,9 @@ function scan_borrow_only_strings(body) {
|
|
|
33911
34027
|
}
|
|
33912
34028
|
//#endregion
|
|
33913
34029
|
//#region ../src/build_c/build_struct_node.ts
|
|
33914
|
-
/** System types whose method bodies call into the concurrency runtime. */
|
|
33915
|
-
const CONCURRENCY_TYPES = /* @__PURE__ */ new Set([
|
|
33916
|
-
"Task",
|
|
33917
|
-
"Thread",
|
|
33918
|
-
"Fiber",
|
|
33919
|
-
"Channel",
|
|
33920
|
-
"Mutex",
|
|
33921
|
-
"Nursery",
|
|
33922
|
-
"Tcp"
|
|
33923
|
-
]);
|
|
33924
34030
|
function build_struct_node(node, status) {
|
|
33925
34031
|
if (node.is_generic) return;
|
|
33926
|
-
|
|
33927
|
-
ensure_concurrency_runtime(status);
|
|
33928
|
-
status.used_fibers = true;
|
|
33929
|
-
}
|
|
34032
|
+
for (const func of node.functions) ensure_runtime_for_method(func, status);
|
|
33930
34033
|
if (node.is_simple_type) {
|
|
33931
34034
|
status.code += `// Struct ${node.name}\n`;
|
|
33932
34035
|
build_struct_functions(node, status);
|
|
@@ -34165,6 +34268,7 @@ function build_struct_functions(node, status, skip_init = false) {
|
|
|
34165
34268
|
const old_current_struct = status.current_struct;
|
|
34166
34269
|
status.current_struct = node;
|
|
34167
34270
|
for (let func of node.functions) {
|
|
34271
|
+
if (func.name === "#spawn") continue;
|
|
34168
34272
|
if (func.name === "#init" && !func.has_body) continue;
|
|
34169
34273
|
if (func.name === "#init" && skip_init) continue;
|
|
34170
34274
|
if (func.is_extern) {
|
|
@@ -35027,6 +35131,7 @@ function build_function_node(node, status) {
|
|
|
35027
35131
|
else status.nullable_ret_has_param = void 0;
|
|
35028
35132
|
const old_function_name = status.current_function_name;
|
|
35029
35133
|
status.current_function_name = emission_label(node);
|
|
35134
|
+
status.function_nursery_depth = status.nursery_stack?.length ?? 0;
|
|
35030
35135
|
const old_current_function = status.current_function;
|
|
35031
35136
|
status.current_function = node;
|
|
35032
35137
|
for (let param of node.params) {
|
|
@@ -35645,22 +35750,6 @@ function build_access_node$1(node, status) {
|
|
|
35645
35750
|
}
|
|
35646
35751
|
case "access_func": {
|
|
35647
35752
|
const access_func = node.access;
|
|
35648
|
-
if (node.target.node_type === "value" && node.target.value === "Fiber") {
|
|
35649
|
-
ensure_concurrency_runtime(status);
|
|
35650
|
-
status.used_fibers = true;
|
|
35651
|
-
}
|
|
35652
|
-
if (access_func.is_thread_start) {
|
|
35653
|
-
build_thread_start(access_func, node.target, status);
|
|
35654
|
-
return;
|
|
35655
|
-
}
|
|
35656
|
-
if (access_func.is_thread_detach) {
|
|
35657
|
-
build_thread_detach(access_func, node.target, status);
|
|
35658
|
-
return;
|
|
35659
|
-
}
|
|
35660
|
-
if (access_func.is_fiber_start) {
|
|
35661
|
-
build_fiber_spawn_node$1(access_func, node.target, status, access_func.is_fiber_start_on ? access_func.params[0] : void 0);
|
|
35662
|
-
return;
|
|
35663
|
-
}
|
|
35664
35753
|
if (access_func.is_nursery_spawn) {
|
|
35665
35754
|
build_nursery_spawn$1(access_func, nursery_pointer_expr(node.target, status), status);
|
|
35666
35755
|
return;
|
|
@@ -35965,10 +36054,35 @@ function build_access_node$1(node, status) {
|
|
|
35965
36054
|
const label = access_func.mangled_name || trait_default_label || `${mono_struct_name}_${access_func.name.replace(/#/g, "")}`;
|
|
35966
36055
|
const array_wrap = array_receiver_wrap(node, method_type, target_method, mono_struct_name, status);
|
|
35967
36056
|
if (array_wrap) status.code += array_wrap.prefix;
|
|
36057
|
+
const ctor_target_node = node.target.node_type === "func_call" ? node.target : void 0;
|
|
36058
|
+
const ctor_temp_receiver = !!(ctor_target_node && (ctor_target_node.is_thread_ctor || ctor_target_node.is_fiber_ctor || ctor_target_node.is_awaitable_ctor));
|
|
36059
|
+
let ctor_temp_free;
|
|
36060
|
+
let ctor_temp_val;
|
|
36061
|
+
if (ctor_temp_receiver) {
|
|
36062
|
+
const id = status.label_counter = (status.label_counter ?? 0) + 1;
|
|
36063
|
+
ctor_temp_free = `_ctorr_${id}`;
|
|
36064
|
+
const ctor_mono = mono_type_name(type_from_value_node$1(node.target));
|
|
36065
|
+
status.code += `({ struct ${ctor_mono} *${ctor_temp_free} = `;
|
|
36066
|
+
{
|
|
36067
|
+
const saved_suppress = status.suppress_dereference;
|
|
36068
|
+
status.suppress_dereference = true;
|
|
36069
|
+
build_node$1(node.target, status);
|
|
36070
|
+
status.suppress_dereference = saved_suppress;
|
|
36071
|
+
}
|
|
36072
|
+
status.code += `; `;
|
|
36073
|
+
const ret = (target_method ?? trait_default_func)?.return_type;
|
|
36074
|
+
if (ret?.name && ret.name !== "void") {
|
|
36075
|
+
const mono_ret = mono_type_name(access_func.type);
|
|
36076
|
+
const ret_c = !!status.structs.find((s) => s.name === mono_ret && s.is_class) || !!status.traits.find((t) => t.name === mono_ret) ? `struct ${mono_ret} *` : c_type(mono_ret);
|
|
36077
|
+
ctor_temp_val = `_ctorv_${id}`;
|
|
36078
|
+
status.code += `${ret_c} ${ctor_temp_val} = `;
|
|
36079
|
+
}
|
|
36080
|
+
}
|
|
35968
36081
|
if (lambda_ret_tmp) status.code += `${lambda_ret_c} ${lambda_ret_tmp} = `;
|
|
35969
36082
|
status.code += `${label}(`;
|
|
35970
36083
|
if (!access_func.is_static) {
|
|
35971
|
-
if (
|
|
36084
|
+
if (ctor_temp_free) status.code += ctor_temp_free;
|
|
36085
|
+
else if (array_wrap) status.code += array_wrap.self_expr;
|
|
35972
36086
|
else {
|
|
35973
36087
|
const method_self_is_ref = !!target_method?.params?.some((p) => p.is_self_param && (p.is_ref || p.type?.is_ref));
|
|
35974
36088
|
const method_self = target_method?.params?.find((p) => p.is_self_param);
|
|
@@ -36037,6 +36151,11 @@ function build_access_node$1(node, status) {
|
|
|
36037
36151
|
}
|
|
36038
36152
|
}
|
|
36039
36153
|
status.code += ")";
|
|
36154
|
+
if (ctor_temp_free) {
|
|
36155
|
+
status.code += `; free(${ctor_temp_free});`;
|
|
36156
|
+
if (ctor_temp_val) status.code += ` ${ctor_temp_val};`;
|
|
36157
|
+
status.code += ` })`;
|
|
36158
|
+
}
|
|
36040
36159
|
if (lambda_arg_temps.size > 0) {
|
|
36041
36160
|
for (const tmp of lambda_arg_temps.values()) status.code += `; ${closure_dispose_arm(tmp)}`;
|
|
36042
36161
|
if (lambda_ret_tmp) status.code += `; ${lambda_ret_tmp}`;
|
|
@@ -36758,7 +36877,7 @@ function build_node$1(node, status, with_semicolon = false) {
|
|
|
36758
36877
|
const access = node;
|
|
36759
36878
|
if (with_semicolon && access.access.node_type === "access_func") {
|
|
36760
36879
|
const afn = access.access;
|
|
36761
|
-
if (afn.is_nursery_spawn
|
|
36880
|
+
if (afn.is_nursery_spawn) afn.is_statement = true;
|
|
36762
36881
|
}
|
|
36763
36882
|
build_access_node$1(access, status);
|
|
36764
36883
|
break;
|
|
@@ -36861,9 +36980,10 @@ function build_node$1(node, status, with_semicolon = false) {
|
|
|
36861
36980
|
//#endregion
|
|
36862
36981
|
//#region ../src/build_c/build_magic_ctor.ts
|
|
36863
36982
|
/**
|
|
36864
|
-
* The
|
|
36865
|
-
*
|
|
36866
|
-
*
|
|
36983
|
+
* The `#spawn` construction (the deferred-call special form — the library
|
|
36984
|
+
* `Thread(fn(args))` / `Fiber(fn(args))`, or any class declaring its own
|
|
36985
|
+
* `#spawn` hook; docs/ASYNC.md, "The construction special form") — packs
|
|
36986
|
+
* the wrapped call's
|
|
36867
36987
|
* arguments EAGERLY into a task environment, allocates the result slot,
|
|
36868
36988
|
* cancel flag, and future, and builds the task closure — a heap descriptor
|
|
36869
36989
|
* over that environment (the same ABI every lambda lowers to; the runtime's
|
|
@@ -37058,6 +37178,7 @@ function build_magic_ctor_node$1(node, status) {
|
|
|
37058
37178
|
status.code += `\t_future->done = 0;\n`;
|
|
37059
37179
|
status.code += `\t_future->cancel_flag = _cancel_ptr;\n`;
|
|
37060
37180
|
status.code += `\t_future->result_slot = _result_ptr;\n`;
|
|
37181
|
+
status.code += `\t_future->slot_fat = ${slot_c_type === "nomen_string" ? 1 : 0};\n`;
|
|
37061
37182
|
status.code += `\t_future->fiber_waiters = NULL;\n`;
|
|
37062
37183
|
status.code += `\t_future->owning_fiber = NULL;\n`;
|
|
37063
37184
|
status.code += `\t_future->refs = 1;\n`;
|
|
@@ -37075,10 +37196,29 @@ function build_magic_ctor_node$1(node, status) {
|
|
|
37075
37196
|
status.code += `\t_self->cancel_flag = (unsigned long long)_cancel_ptr;\n`;
|
|
37076
37197
|
status.code += `\t_self->future = (unsigned long long)_future;\n`;
|
|
37077
37198
|
if (info.has_started) status.code += `\t_self->started = 0;\n`;
|
|
37199
|
+
emit_nursery_capture_c(status, kind, (field, expr) => {
|
|
37200
|
+
status.code += `\t_self->${field} = ${expr};\n`;
|
|
37201
|
+
});
|
|
37078
37202
|
status.code += `\t_self;\n`;
|
|
37079
37203
|
status.code += `})\n`;
|
|
37080
37204
|
}
|
|
37081
37205
|
/**
|
|
37206
|
+
* LEXICAL NURSERY CAPTURE (ASYNC.md): a construction lexically
|
|
37207
|
+
* inside an `async` block stores the block's tracking-slot pointers in the
|
|
37208
|
+
* instance, so `.start()` — an ordinary method on the spawn class —
|
|
37209
|
+
* registers the future with the scope that CREATED the deferred call rather
|
|
37210
|
+
* than wherever the launch happens. Library spawn classes only: Thread/Fiber
|
|
37211
|
+
* carry the three capture fields (the generalized Awaitable flavor's field
|
|
37212
|
+
* contract predates them and keeps its historical emission).
|
|
37213
|
+
*/
|
|
37214
|
+
function emit_nursery_capture_c(status, kind, emit_field) {
|
|
37215
|
+
const nursery_id = status.nursery_stack?.at(-1);
|
|
37216
|
+
if (nursery_id === void 0 || kind !== "Thread" && kind !== "Fiber") return;
|
|
37217
|
+
emit_field("nursery_futures", `(unsigned long long)&__nomen_nursery_${nursery_id}_futures`);
|
|
37218
|
+
emit_field("nursery_count", `(unsigned long long)&__nomen_nursery_${nursery_id}_count`);
|
|
37219
|
+
emit_field("nursery_cap", `(unsigned long long)&__nomen_nursery_${nursery_id}_cap`);
|
|
37220
|
+
}
|
|
37221
|
+
/**
|
|
37082
37222
|
* The Phase 3c function-value construction (`Thread(() => work(n))` /
|
|
37083
37223
|
* `Thread(job)` with a zero-argument func-typed local): the task closure is
|
|
37084
37224
|
* an ADAPTER over the given closure. The adapter env carries the user
|
|
@@ -37152,6 +37292,7 @@ function build_fn_value_ctor(node, status, id, kind, info) {
|
|
|
37152
37292
|
status.code += `\t_future->done = 0;\n`;
|
|
37153
37293
|
status.code += `\t_future->cancel_flag = _cancel_ptr;\n`;
|
|
37154
37294
|
status.code += `\t_future->result_slot = _result_ptr;\n`;
|
|
37295
|
+
status.code += `\t_future->slot_fat = ${dup_result ? 1 : 0};\n`;
|
|
37155
37296
|
status.code += `\t_future->fiber_waiters = NULL;\n`;
|
|
37156
37297
|
status.code += `\t_future->owning_fiber = NULL;\n`;
|
|
37157
37298
|
status.code += `\t_future->refs = 1;\n`;
|
|
@@ -37173,6 +37314,9 @@ function build_fn_value_ctor(node, status, id, kind, info) {
|
|
|
37173
37314
|
status.code += `\t_self->cancel_flag = (unsigned long long)_cancel_ptr;\n`;
|
|
37174
37315
|
status.code += `\t_self->future = (unsigned long long)_future;\n`;
|
|
37175
37316
|
if (info.has_started) status.code += `\t_self->started = 0;\n`;
|
|
37317
|
+
emit_nursery_capture_c(status, kind, (field, expr) => {
|
|
37318
|
+
status.code += `\t_self->${field} = ${expr};\n`;
|
|
37319
|
+
});
|
|
37176
37320
|
status.code += `\t_self;\n`;
|
|
37177
37321
|
status.code += `})\n`;
|
|
37178
37322
|
}
|
|
@@ -37193,10 +37337,10 @@ function is_struct_ret(node, status) {
|
|
|
37193
37337
|
//#endregion
|
|
37194
37338
|
//#region ../src/build_aarch64/build_magic_ctor.ts
|
|
37195
37339
|
/**
|
|
37196
|
-
* The spawn
|
|
37197
|
-
* `Thread(fn(args))` / `Fiber(fn(args))`, or
|
|
37198
|
-
*
|
|
37199
|
-
*
|
|
37340
|
+
* The `#spawn` construction (the deferred-call special form — the library
|
|
37341
|
+
* `Thread(fn(args))` / `Fiber(fn(args))`, or any class declaring its own
|
|
37342
|
+
* `#spawn` hook; docs/ASYNC.md, "The construction special form"),
|
|
37343
|
+
* aarch64 backend: the per-site
|
|
37200
37344
|
* trampoline and a constructor helper are emitted as C in the companion
|
|
37201
37345
|
* file; the assembly stages the wrapped call's arguments and calls the
|
|
37202
37346
|
* helper, which packs the env, allocates the future machinery, builds the
|
|
@@ -37218,10 +37362,14 @@ function build_magic_ctor_node(node, status) {
|
|
|
37218
37362
|
const tramp_name = `__nomen_spawn_${id}_trampoline`;
|
|
37219
37363
|
const desc_name = `__nomen_spawn_${id}_descriptor`;
|
|
37220
37364
|
const struct_name = `__nomen_spawn_${id}_args`;
|
|
37365
|
+
const nursery_id = status.nursery_stack?.at(-1);
|
|
37366
|
+
const nursery_off = nursery_id !== void 0 ? status.nursery_offsets?.get(nursery_id) : void 0;
|
|
37367
|
+
const capture = nursery_capture_a64(status, kind);
|
|
37368
|
+
const arg_types = spawn_arg_types(call, status);
|
|
37221
37369
|
const arg_c_types = [];
|
|
37222
37370
|
const fat_string_args = [];
|
|
37223
37371
|
for (let i = 0; i < call.params.length; i++) {
|
|
37224
|
-
const mono_name = mono_type_name(
|
|
37372
|
+
const mono_name = mono_type_name(arg_types[i]);
|
|
37225
37373
|
const is_class = !!status.structs.find((s) => s.name === mono_name && s.is_class);
|
|
37226
37374
|
const is_trait = !!status.traits.find((t) => t.name === mono_name);
|
|
37227
37375
|
arg_c_types.push(is_class || is_trait ? `struct ${mono_name} *` : `${c_type(mono_name)}`);
|
|
@@ -37277,6 +37425,10 @@ function build_magic_ctor_node(node, status) {
|
|
|
37277
37425
|
if (i > 0) c += ", ";
|
|
37278
37426
|
c += `${arg_c_types[i]} arg${i}`;
|
|
37279
37427
|
}
|
|
37428
|
+
if (capture) {
|
|
37429
|
+
if (arg_c_types.length > 0) c += ", ";
|
|
37430
|
+
c += capture.helper_params;
|
|
37431
|
+
}
|
|
37280
37432
|
c += `) {\n`;
|
|
37281
37433
|
c += `\tstruct ${struct_name} *a = (struct ${struct_name} *)malloc(sizeof(struct ${struct_name}));\n`;
|
|
37282
37434
|
for (let i = 0; i < arg_c_types.length; i++) c += `\ta->arg${i} = ${fat_string_args[i] ? "nomen_str_dup(arg" + i + ")" : `arg${i}`};\n`;
|
|
@@ -37291,6 +37443,7 @@ function build_magic_ctor_node(node, status) {
|
|
|
37291
37443
|
c += `\tf->refs = 1;\n`;
|
|
37292
37444
|
c += `\tf->cancel_flag = a->cancel_flag;\n`;
|
|
37293
37445
|
c += `\tf->result_slot = a->result_slot;\n`;
|
|
37446
|
+
c += `\tf->slot_fat = ${slot_c_type === "nomen_string" ? 1 : 0};\n`;
|
|
37294
37447
|
c += `\tf->fiber_waiters = NULL;\n`;
|
|
37295
37448
|
c += `\tf->owning_fiber = 0;\n`;
|
|
37296
37449
|
c += `\ta->future = f;\n`;
|
|
@@ -37307,6 +37460,7 @@ function build_magic_ctor_node(node, status) {
|
|
|
37307
37460
|
c += `\tself->cancel_flag = (unsigned long long)a->cancel_flag;\n`;
|
|
37308
37461
|
c += `\tself->future = (unsigned long long)f;\n`;
|
|
37309
37462
|
if (info.has_started) c += `\tself->started = 0;\n`;
|
|
37463
|
+
if (capture) c += capture.field_stores.join("");
|
|
37310
37464
|
c += `\treturn self;\n`;
|
|
37311
37465
|
c += `}\n`;
|
|
37312
37466
|
if (!status.file_scope_c) status.file_scope_c = "";
|
|
@@ -37318,6 +37472,8 @@ function build_magic_ctor_node(node, status) {
|
|
|
37318
37472
|
arg_slot.push(total_arg_slots);
|
|
37319
37473
|
total_arg_slots += fat_string_args[i] ? 2 : 1;
|
|
37320
37474
|
}
|
|
37475
|
+
const capture_base_slot = total_arg_slots;
|
|
37476
|
+
if (capture) total_arg_slots += capture.arg_count;
|
|
37321
37477
|
if (total_arg_slots === 0) emit_asm(status, `bl _${helper_name}\n`);
|
|
37322
37478
|
else {
|
|
37323
37479
|
const args_base = allocate_stack_space(status, total_arg_slots * 8, 16);
|
|
@@ -37328,6 +37484,17 @@ function build_magic_ctor_node(node, status) {
|
|
|
37328
37484
|
emit_asm(status, `str x0, [x29, #${args_base + arg_slot[i] * 8}]\n`);
|
|
37329
37485
|
if (fat_string_args[i]) emit_asm(status, `str x1, [x29, #${args_base + (arg_slot[i] + 1) * 8}]\n`);
|
|
37330
37486
|
}
|
|
37487
|
+
if (capture && nursery_off) {
|
|
37488
|
+
const addrs = [
|
|
37489
|
+
nursery_off.futures_off,
|
|
37490
|
+
nursery_off.count_off,
|
|
37491
|
+
nursery_off.cap_off
|
|
37492
|
+
];
|
|
37493
|
+
for (let k = 0; k < addrs.length; k++) {
|
|
37494
|
+
emit_asm(status, `add x9, x29, #${addrs[k]}\n`);
|
|
37495
|
+
emit_asm(status, `str x9, [x29, #${args_base + (capture_base_slot + k) * 8}]\n`);
|
|
37496
|
+
}
|
|
37497
|
+
}
|
|
37331
37498
|
const NUM_REG_ARGS = 8;
|
|
37332
37499
|
const overflow_count = Math.max(0, total_arg_slots - NUM_REG_ARGS);
|
|
37333
37500
|
if (overflow_count > 0) {
|
|
@@ -37402,7 +37569,8 @@ function build_fn_value_ctor_a64(node, status, id, kind, info) {
|
|
|
37402
37569
|
c += `\t__nomen_future_release(a->future);\n`;
|
|
37403
37570
|
c += `}\n`;
|
|
37404
37571
|
c += `static struct nomen_closure ${desc_name} = { (void *)${tramp_name}, NULL, 0, NULL };\n`;
|
|
37405
|
-
|
|
37572
|
+
const fnval_capture = nursery_capture_a64(status, kind);
|
|
37573
|
+
c += `void *${helper_name}(struct nomen_closure *fn${fnval_capture ? `, ${fnval_capture.helper_params}` : ""}) {\n`;
|
|
37406
37574
|
c += `\tstruct ${struct_name} *a = (struct ${struct_name} *)malloc(sizeof(struct ${struct_name}));\n`;
|
|
37407
37575
|
c += `\ta->fn = fn;\n`;
|
|
37408
37576
|
c += `\ta->result_slot = (${slot_c_type} *)${returns_value ? `malloc(sizeof(${slot_c_type}))` : "malloc(16)"};\n`;
|
|
@@ -37416,6 +37584,7 @@ function build_fn_value_ctor_a64(node, status, id, kind, info) {
|
|
|
37416
37584
|
c += `\tf->refs = 1;\n`;
|
|
37417
37585
|
c += `\tf->cancel_flag = a->cancel_flag;\n`;
|
|
37418
37586
|
c += `\tf->result_slot = a->result_slot;\n`;
|
|
37587
|
+
c += `\tf->slot_fat = ${dup_result ? 1 : 0};\n`;
|
|
37419
37588
|
c += `\tf->fiber_waiters = NULL;\n`;
|
|
37420
37589
|
c += `\tf->owning_fiber = 0;\n`;
|
|
37421
37590
|
c += `\ta->future = f;\n`;
|
|
@@ -37432,6 +37601,7 @@ function build_fn_value_ctor_a64(node, status, id, kind, info) {
|
|
|
37432
37601
|
c += `\tself->cancel_flag = (unsigned long long)a->cancel_flag;\n`;
|
|
37433
37602
|
c += `\tself->future = (unsigned long long)f;\n`;
|
|
37434
37603
|
if (info.has_started) c += `\tself->started = 0;\n`;
|
|
37604
|
+
if (fnval_capture) c += fnval_capture.field_stores.join("");
|
|
37435
37605
|
c += `\treturn self;\n`;
|
|
37436
37606
|
c += `}\n`;
|
|
37437
37607
|
if (!status.file_scope_c) status.file_scope_c = "";
|
|
@@ -37447,8 +37617,41 @@ function build_fn_value_ctor_a64(node, status, id, kind, info) {
|
|
|
37447
37617
|
if (!status.moved) status.moved = /* @__PURE__ */ new Set();
|
|
37448
37618
|
status.moved.add(name);
|
|
37449
37619
|
}
|
|
37620
|
+
if (fnval_capture) {
|
|
37621
|
+
const fnval_nursery_id = status.nursery_stack?.at(-1);
|
|
37622
|
+
const fnval_off = fnval_nursery_id !== void 0 ? status.nursery_offsets?.get(fnval_nursery_id) : void 0;
|
|
37623
|
+
if (fnval_off) {
|
|
37624
|
+
const fnval_park = allocate_stack_space(status, 8, 8);
|
|
37625
|
+
emit_asm(status, `str x0, [x29, #${fnval_park}]\n`);
|
|
37626
|
+
emit_asm(status, `add x1, x29, #${fnval_off.futures_off}\n`);
|
|
37627
|
+
emit_asm(status, `add x2, x29, #${fnval_off.count_off}\n`);
|
|
37628
|
+
emit_asm(status, `add x3, x29, #${fnval_off.cap_off}\n`);
|
|
37629
|
+
emit_asm(status, `ldr x0, [x29, #${fnval_park}]\n`);
|
|
37630
|
+
}
|
|
37631
|
+
}
|
|
37450
37632
|
emit_asm(status, `bl _${helper_name}\n`);
|
|
37451
37633
|
}
|
|
37634
|
+
/**
|
|
37635
|
+
* LEXICAL NURSERY CAPTURE (ASYNC.md), aarch64 backend. The
|
|
37636
|
+
* construction helper is companion C and cannot address the enclosing async
|
|
37637
|
+
* block's frame, so the assembly passes the three tracking-slot addresses as
|
|
37638
|
+
* trailing helper arguments and the helper stores them into the instance's
|
|
37639
|
+
* capture fields — the lexical-at-construction counterpart of the C
|
|
37640
|
+
* backend's emit_nursery_capture_c. Applies to Thread/Fiber only (they
|
|
37641
|
+
* carry the capture fields).
|
|
37642
|
+
*/
|
|
37643
|
+
function nursery_capture_a64(status, kind) {
|
|
37644
|
+
if (status.nursery_stack?.at(-1) === void 0 || kind !== "Thread" && kind !== "Fiber") return void 0;
|
|
37645
|
+
return {
|
|
37646
|
+
helper_params: "unsigned long long nursery_futures, unsigned long long nursery_count, unsigned long long nursery_cap",
|
|
37647
|
+
field_stores: [
|
|
37648
|
+
`\tself->nursery_futures = nursery_futures;\n`,
|
|
37649
|
+
`\tself->nursery_count = nursery_count;\n`,
|
|
37650
|
+
`\tself->nursery_cap = nursery_cap;\n`
|
|
37651
|
+
],
|
|
37652
|
+
arg_count: 3
|
|
37653
|
+
};
|
|
37654
|
+
}
|
|
37452
37655
|
//#endregion
|
|
37453
37656
|
//#region ../src/build_aarch64/build_panic_node.ts
|
|
37454
37657
|
function build_panic_node(node, status) {
|
|
@@ -37576,7 +37779,7 @@ function build_value_node(node, status) {
|
|
|
37576
37779
|
if (paramReg !== "x0") emit_asm(status, `mov x0, ${paramReg}`);
|
|
37577
37780
|
} else if (status.function_param_vars?.has(original_value) || status.function_param_vars?.has(value) || status.function_ref_params?.has(original_value) || status.function_ref_params?.has(value)) {
|
|
37578
37781
|
const param_type_name = node.type?.name;
|
|
37579
|
-
if (param_type_name && status.structs.find((s) => s.name === param_type_name && s.is_class) || !!status.class_vars?.has(value) || !!status.class_vars?.has(original_value)) {
|
|
37782
|
+
if (param_type_name && status.structs.find((s) => s.name === param_type_name && s.is_class) || !!param_type_name && !!status.traits.find((t) => t.name === param_type_name) || !!status.class_vars?.has(value) || !!status.class_vars?.has(original_value)) {
|
|
37580
37783
|
if (paramReg !== "x0") emit_asm(status, `mov x0, ${paramReg}`);
|
|
37581
37784
|
} else emit_asm(status, deref_load_instr(paramReg, param_type_name));
|
|
37582
37785
|
} else if (paramReg !== "x0") emit_asm(status, `mov x0, ${paramReg}`);
|
|
@@ -37628,7 +37831,7 @@ function build_value_node(node, status) {
|
|
|
37628
37831
|
if (status.function_ref_params?.has(value) || status.function_ref_params?.has(original_value)) {
|
|
37629
37832
|
if (alloc_reg !== "x0") emit_asm(status, `mov x0, ${alloc_reg}\n`);
|
|
37630
37833
|
const param_type_name = node.type?.name;
|
|
37631
|
-
if (!(param_type_name && status.structs.find((s) => s.name === param_type_name && s.is_class) || !!status.class_vars?.has(value) || !!status.class_vars?.has(original_value))) emit_asm(status, deref_load_instr("x0", param_type_name));
|
|
37834
|
+
if (!(param_type_name && status.structs.find((s) => s.name === param_type_name && s.is_class) || !!param_type_name && !!status.traits.find((t) => t.name === param_type_name) || !!status.class_vars?.has(value) || !!status.class_vars?.has(original_value))) emit_asm(status, deref_load_instr("x0", param_type_name));
|
|
37632
37835
|
} else if (alloc_reg !== "x0") {
|
|
37633
37836
|
if (alloc_reg.startsWith("d")) emit_asm(status, `fmov x0, ${alloc_reg}`);
|
|
37634
37837
|
else emit_asm(status, `mov x0, ${alloc_reg}`);
|
|
@@ -37836,7 +38039,7 @@ function build_node(node, status, with_semicolon = false) {
|
|
|
37836
38039
|
const access = node;
|
|
37837
38040
|
if (with_semicolon && access.access.node_type === "access_func") {
|
|
37838
38041
|
const afn = access.access;
|
|
37839
|
-
if (afn.is_nursery_spawn
|
|
38042
|
+
if (afn.is_nursery_spawn) afn.is_statement = true;
|
|
37840
38043
|
}
|
|
37841
38044
|
build_access_node(access, status);
|
|
37842
38045
|
break;
|
|
@@ -39757,7 +39960,7 @@ function build_operand(node, target_reg, status) {
|
|
|
39757
39960
|
if (paramReg) {
|
|
39758
39961
|
if (status.function_param_vars?.has(value) || status.function_ref_params?.has(value)) {
|
|
39759
39962
|
const param_type_name = node.type?.name;
|
|
39760
|
-
if (param_type_name && status.structs.find((s) => s.name === param_type_name && s.is_class)) {
|
|
39963
|
+
if (param_type_name && status.structs.find((s) => s.name === param_type_name && s.is_class) || !!param_type_name && !!status.traits.find((t) => t.name === param_type_name)) {
|
|
39761
39964
|
if (paramReg !== target_reg) emit_asm(status, `mov ${target_reg}, ${paramReg}`);
|
|
39762
39965
|
} else emit_asm(status, `ldr ${target_reg}, [${paramReg}]`);
|
|
39763
39966
|
} else if (paramReg !== target_reg) emit_asm(status, `mov ${target_reg}, ${paramReg}`);
|
|
@@ -40990,113 +41193,6 @@ function array_licm_enabled() {
|
|
|
40990
41193
|
return array_licm_on;
|
|
40991
41194
|
}
|
|
40992
41195
|
//#endregion
|
|
40993
|
-
//#region ../src/build_aarch64/build_fiber_spawn.ts
|
|
40994
|
-
/**
|
|
40995
|
-
* Fiber scheduler runtime (ASYNC.md Phase 1), aarch64 backend.
|
|
40996
|
-
*
|
|
40997
|
-
* Same scheduler as the C backend's FIBER_HEADER (which see), but the
|
|
40998
|
-
* context switch is a naked-asm function compiled into the companion C —
|
|
40999
|
-
* 13 words (x19–x28, FP, LR, SP) per context, ~30 instructions per switch,
|
|
41000
|
-
* no ucontext. The raw-asm-called helpers (__nomen_fiber_yield,
|
|
41001
|
-
* __nomen_fiber_is_active, __nomen_fiber_set_cooperative — reached from
|
|
41002
|
-
* Fiber.nm's `#arch: aarch64` bodies as ___nomen_fiber_*) are non-static.
|
|
41003
|
-
*/
|
|
41004
|
-
/**
|
|
41005
|
-
* Build a `.start()` / `.start_on(buf)` launch on a `Fiber(fn(args))`
|
|
41006
|
-
* construction (or a stored Fiber binding) — aarch64 backend
|
|
41007
|
-
* (CLOSURE.md Phase 3b). A companion-C helper reads the packed
|
|
41008
|
-
* handles from the receiver's fields and launches the task closure on the
|
|
41009
|
-
* fiber scheduler (a heap stack, or the caller's fixed-size array buffer
|
|
41010
|
-
* for start_on — validated at check time, >= 16 KB), registers the future
|
|
41011
|
-
* with the enclosing nursery (if any), transfers the handles out of the
|
|
41012
|
-
* instance, and returns Task<T> (or NULL, fire-and-forget); the assembly
|
|
41013
|
-
* builds the receiver pointer and calls it.
|
|
41014
|
-
*/
|
|
41015
|
-
function build_fiber_spawn_node(access_func, target, status, start_on) {
|
|
41016
|
-
ensure_concurrency_runtime_a64(status);
|
|
41017
|
-
status.used_fibers = true;
|
|
41018
|
-
const id = status.spawn_counter ?? 0;
|
|
41019
|
-
status.spawn_counter = id + 1;
|
|
41020
|
-
const helper_name = `nomen_fiber_${id}_start`;
|
|
41021
|
-
const t_arg = spawn_result_type_arg$1(access_func.function_return_type);
|
|
41022
|
-
const mono_fiber = mono_type_name("Fiber", [t_arg]);
|
|
41023
|
-
const mono_task_name = mono_type_name("Task", [t_arg]);
|
|
41024
|
-
const nursery_id = status.nursery_stack?.at(-1);
|
|
41025
|
-
const nursery_off = nursery_id !== void 0 ? status.nursery_offsets?.get(nursery_id) : void 0;
|
|
41026
|
-
const fire_and_forget = !!access_func.is_statement;
|
|
41027
|
-
const target_is_temp = target.node_type === "func_call" && !!(target.is_thread_ctor || target.is_fiber_ctor);
|
|
41028
|
-
const refs = fire_and_forget ? nursery_id !== void 0 ? 2 : 1 : nursery_id !== void 0 ? 3 : 2;
|
|
41029
|
-
let c = `// --- fiber start site ${id} ---\n`;
|
|
41030
|
-
c += `void *${helper_name}(void *self`;
|
|
41031
|
-
let trailing = "";
|
|
41032
|
-
if (nursery_off) trailing += `void **__nomen_nursery_futures, int *__nomen_nursery_count, int *__nomen_nursery_cap`;
|
|
41033
|
-
if (start_on) trailing += `${trailing ? ", " : ""}void *__nomen_stack, unsigned long long __nomen_stack_size`;
|
|
41034
|
-
if (trailing) c += `, ${trailing}`;
|
|
41035
|
-
c += `) {\n`;
|
|
41036
|
-
c += `\textern void *${mono_task_name}_traits[];\n\tstruct ${mono_fiber} *t = (struct ${mono_fiber} *)self;\n`;
|
|
41037
|
-
c += `\tstruct nomen_future *f = (struct nomen_future *)t->future;\n`;
|
|
41038
|
-
c += `\tvoid *result_ptr = (void *)t->result_slot;\n`;
|
|
41039
|
-
c += `\tunsigned long long *cancel_ptr = (unsigned long long *)t->cancel_flag;\n`;
|
|
41040
|
-
c += `\tstruct nomen_closure *cl = (struct nomen_closure *)t->task;\n`;
|
|
41041
|
-
c += `\tf->refs = ${refs};\n`;
|
|
41042
|
-
c += `\t__nomen_fiber_spawn${start_on ? "_on" : ""}(cl, f${start_on ? ", __nomen_stack, (size_t)__nomen_stack_size" : ""});\n`;
|
|
41043
|
-
if (nursery_off) c += `\t__nomen_nursery_track(__nomen_nursery_futures, __nomen_nursery_count, __nomen_nursery_cap, f);\n`;
|
|
41044
|
-
c += `\tt->task = 0;\n`;
|
|
41045
|
-
c += `\tt->future = 0;\n`;
|
|
41046
|
-
c += `\tt->result_slot = 0;\n`;
|
|
41047
|
-
c += `\tt->cancel_flag = 0;\n`;
|
|
41048
|
-
c += `\tt->started = 1;\n`;
|
|
41049
|
-
if (target_is_temp) c += `\tfree(t);\n`;
|
|
41050
|
-
if (fire_and_forget) c += `\treturn (void *)0;\n`;
|
|
41051
|
-
else {
|
|
41052
|
-
c += `\tstruct ${mono_task_name} *task = (struct ${mono_task_name} *)malloc(sizeof(struct ${mono_task_name}));\n`;
|
|
41053
|
-
c += `\ttask->_vt = (void **)${mono_task_name}_traits;\n`;
|
|
41054
|
-
c += `\ttask->handle = 0;\n`;
|
|
41055
|
-
c += `\ttask->done = 0;\n`;
|
|
41056
|
-
c += `\ttask->result_slot = (unsigned long long)result_ptr;\n`;
|
|
41057
|
-
c += `\ttask->cancel_flag = (unsigned long long)cancel_ptr;\n`;
|
|
41058
|
-
c += `\ttask->future = (unsigned long long)f;\n`;
|
|
41059
|
-
c += `\treturn task;\n`;
|
|
41060
|
-
}
|
|
41061
|
-
c += `}\n`;
|
|
41062
|
-
if (!status.file_scope_c) status.file_scope_c = "";
|
|
41063
|
-
status.file_scope_c += c;
|
|
41064
|
-
emit_asm(status, `// fiber start site ${id}\n`);
|
|
41065
|
-
build_node(target, status);
|
|
41066
|
-
ensure_newline(status);
|
|
41067
|
-
let extra_slots = 0;
|
|
41068
|
-
if (nursery_off) extra_slots += 3;
|
|
41069
|
-
if (start_on) extra_slots += 2;
|
|
41070
|
-
if (extra_slots > 0) {
|
|
41071
|
-
const park = allocate_stack_space(status, 8, 8);
|
|
41072
|
-
emit_asm(status, `str x0, [x29, #${park}]\n`);
|
|
41073
|
-
if (nursery_off) {
|
|
41074
|
-
emit_asm(status, `add x1, x29, #${nursery_off.futures_off}\n`);
|
|
41075
|
-
emit_asm(status, `add x2, x29, #${nursery_off.count_off}\n`);
|
|
41076
|
-
emit_asm(status, `add x3, x29, #${nursery_off.cap_off}\n`);
|
|
41077
|
-
}
|
|
41078
|
-
if (start_on) {
|
|
41079
|
-
emit_asm(status, `// Build stack buffer address\n`);
|
|
41080
|
-
emit_address_of(start_on, status);
|
|
41081
|
-
ensure_newline(status);
|
|
41082
|
-
emit_asm(status, `mov x${nursery_off ? 4 : 1}, x0\n`);
|
|
41083
|
-
const buf_type = type_from_value_node$1(start_on);
|
|
41084
|
-
const elem_size = buf_type.name === "string" ? 16 : 8;
|
|
41085
|
-
const len_node = buf_type.length;
|
|
41086
|
-
const len = len_node && len_node.node_type === "value" ? parseInt(len_node.value, 10) : NaN;
|
|
41087
|
-
emit_asm(status, `// stack size = ${Number.isNaN(len) ? 0 : len} * ${elem_size}\n`);
|
|
41088
|
-
emit_asm(status, `mov x${nursery_off ? 5 : 2}, #${Number.isNaN(len) ? 0 : len * elem_size}\n`);
|
|
41089
|
-
}
|
|
41090
|
-
emit_asm(status, `ldr x0, [x29, #${park}]\n`);
|
|
41091
|
-
}
|
|
41092
|
-
emit_asm(status, `bl _${helper_name}\n`);
|
|
41093
|
-
}
|
|
41094
|
-
/** The launch's T as a Type: the stamped wrapped-call return type, with
|
|
41095
|
-
* void coerced to uint64 (Task's result-slot convention). */
|
|
41096
|
-
function spawn_result_type_arg$1(return_type) {
|
|
41097
|
-
return return_type?.name && return_type.name !== "void" && return_type.name !== "?" ? new Type(return_type.name) : new Type("uint64");
|
|
41098
|
-
}
|
|
41099
|
-
//#endregion
|
|
41100
41196
|
//#region ../src/build_aarch64/build_nursery_spawn.ts
|
|
41101
41197
|
/**
|
|
41102
41198
|
* Build a `name.start(Thread(fn(args)))` escape-hatch call for aarch64
|
|
@@ -41680,22 +41776,6 @@ function build_access_node(node, status) {
|
|
|
41680
41776
|
break;
|
|
41681
41777
|
case "access_func": {
|
|
41682
41778
|
const access_func = node.access;
|
|
41683
|
-
if (node.target.node_type === "value" && node.target.value === "Fiber" && !status.used_fibers) {
|
|
41684
|
-
ensure_concurrency_runtime_a64(status);
|
|
41685
|
-
status.used_fibers = true;
|
|
41686
|
-
}
|
|
41687
|
-
if (access_func.is_thread_start) {
|
|
41688
|
-
build_thread_start$1(access_func, node.target, status);
|
|
41689
|
-
return;
|
|
41690
|
-
}
|
|
41691
|
-
if (access_func.is_thread_detach) {
|
|
41692
|
-
build_thread_detach$1(access_func, node.target, status);
|
|
41693
|
-
return;
|
|
41694
|
-
}
|
|
41695
|
-
if (access_func.is_fiber_start) {
|
|
41696
|
-
build_fiber_spawn_node(access_func, node.target, status, access_func.is_fiber_start_on ? access_func.params[0] : void 0);
|
|
41697
|
-
return;
|
|
41698
|
-
}
|
|
41699
41779
|
if (access_func.is_nursery_spawn) {
|
|
41700
41780
|
build_nursery_spawn(node, access_func, status);
|
|
41701
41781
|
return;
|
|
@@ -41741,6 +41821,46 @@ function get_base_target(node) {
|
|
|
41741
41821
|
if (node.target.node_type === "access") return get_base_target(node.target);
|
|
41742
41822
|
return node.target;
|
|
41743
41823
|
}
|
|
41824
|
+
/**
|
|
41825
|
+
* Resolve the concrete type of an access target (a local/param/`self` value or
|
|
41826
|
+
* a nested access), consulting the containing struct's field declarations when
|
|
41827
|
+
* a cached type is absent — an inferred `move` class field
|
|
41828
|
+
* (`pub move rules = RuleSet()`) carries no cached AccessFieldNode.type.
|
|
41829
|
+
*/
|
|
41830
|
+
function resolve_access_target_type(target, status) {
|
|
41831
|
+
if (target.node_type === "value") {
|
|
41832
|
+
const name = target.value;
|
|
41833
|
+
if (name === "self" && status.current_struct) return new Type(status.current_struct.name);
|
|
41834
|
+
if (status.variable_types?.has(name)) return status.variable_types.get(name);
|
|
41835
|
+
const decl = status.scoped_declarations.findLast((d) => d.name === name);
|
|
41836
|
+
if (decl?.type?.name) return decl.type;
|
|
41837
|
+
return type_from_value_node$1(target);
|
|
41838
|
+
}
|
|
41839
|
+
if (target.node_type === "access") return type_from_value_node$1(target) ?? resolve_access_type(target, status) ?? void 0;
|
|
41840
|
+
return type_from_value_node$1(target);
|
|
41841
|
+
}
|
|
41842
|
+
/**
|
|
41843
|
+
* Whether reading the final field of `node` must dereference an intermediate
|
|
41844
|
+
* pointer. The summed-offset-from-root load (`compute_field_offset` + the
|
|
41845
|
+
* base address) is only valid while every hop is an INLINE value struct. A
|
|
41846
|
+
* class-typed field — or a class-typed root value — stores a POINTER, so the
|
|
41847
|
+
* chain has to be built recursively (each pointer hop loaded) before the
|
|
41848
|
+
* final field is read. Without this, `state.rules.blocks.length` (State/
|
|
41849
|
+
* BlockParserState is a class) summed 8+8+8 and loaded from `state + 24`.
|
|
41850
|
+
*/
|
|
41851
|
+
function access_chain_crosses_class(node, status) {
|
|
41852
|
+
const base_type = resolve_access_target_type(get_base_target(node), status);
|
|
41853
|
+
if (base_type?.name && status.structs.find((s) => s.name === base_type.name && s.is_class)) return true;
|
|
41854
|
+
let cursor = node;
|
|
41855
|
+
while (cursor.node_type === "access") {
|
|
41856
|
+
const acc = cursor;
|
|
41857
|
+
if (acc.access.node_type !== "access_field") return false;
|
|
41858
|
+
const ft = resolve_field_type(acc.access, resolve_access_target_type(acc.target, status)?.name, status);
|
|
41859
|
+
if (ft?.name && status.structs.find((s) => s.name === ft.name && s.is_class)) return true;
|
|
41860
|
+
cursor = acc.target;
|
|
41861
|
+
}
|
|
41862
|
+
return false;
|
|
41863
|
+
}
|
|
41744
41864
|
function get_param_reg(name, status) {
|
|
41745
41865
|
return status.function_param_regs?.get(name);
|
|
41746
41866
|
}
|
|
@@ -42140,9 +42260,11 @@ function build_access_field(node, status) {
|
|
|
42140
42260
|
emit_string_length(node.target, status);
|
|
42141
42261
|
return;
|
|
42142
42262
|
}
|
|
42143
|
-
|
|
42263
|
+
let offset = compute_field_offset(node, status);
|
|
42144
42264
|
const base = get_base_target(node);
|
|
42145
|
-
const
|
|
42265
|
+
const target_access_field = node.target.node_type === "access" && node.target.access.node_type === "access_field" ? node.target.access : void 0;
|
|
42266
|
+
const target_class_field_type = target_access_field ? resolve_field_type(target_access_field, resolve_access_target_type(node.target.target, status)?.name, status) : void 0;
|
|
42267
|
+
const target_is_class_access = !!target_class_field_type?.name && !!status.structs.find((s) => s.name === target_class_field_type.name && s.is_class);
|
|
42146
42268
|
if (node.target.node_type === "access" && node.target.access.node_type === "access_func") {
|
|
42147
42269
|
build_node(node.target, status);
|
|
42148
42270
|
ensure_newline(status);
|
|
@@ -42173,8 +42295,13 @@ function build_access_field(node, status) {
|
|
|
42173
42295
|
build_node(node.target, status);
|
|
42174
42296
|
ensure_newline(status);
|
|
42175
42297
|
const final_offset = get_field_offset(target_type?.name || "", access_field.name, status);
|
|
42176
|
-
const
|
|
42177
|
-
|
|
42298
|
+
const class_access_field_type = status.structs.find((s) => s.name === (target_type?.name || "") && !s.is_simple_type)?.fields.find((f) => f.name === access_field.name)?.type ?? resolve_field_type(access_field, target_type?.name, status);
|
|
42299
|
+
const field_type = class_access_field_type?.type_args?.length ? mono_type_name(class_access_field_type) : class_access_field_type?.name || access_field.type?.name || "";
|
|
42300
|
+
if (!!field_type && !class_access_field_type?.is_ref && !class_access_field_type?.is_nullable && is_struct_type(field_type, status)) {
|
|
42301
|
+
if (final_offset > 0) emit_asm(status, `add x0, x0, #${final_offset}\n`);
|
|
42302
|
+
return;
|
|
42303
|
+
}
|
|
42304
|
+
if (field_type === "string" || class_access_field_type?.is_view) {
|
|
42178
42305
|
emit_string_pair_load_at(status, "x0", final_offset);
|
|
42179
42306
|
return;
|
|
42180
42307
|
}
|
|
@@ -42204,14 +42331,14 @@ function build_access_field(node, status) {
|
|
|
42204
42331
|
if (paramReg !== "x0") emit_asm(status, `mov x0, ${paramReg}\n`);
|
|
42205
42332
|
} else emit_var_load(status, "x0", name, 8);
|
|
42206
42333
|
const final_offset = get_field_offset(target_type?.name || "", access_field.name, status);
|
|
42207
|
-
const field_type_obj = resolve_field_type(access_field, target_type?.name, status);
|
|
42208
|
-
const resolved_field_type = field_type_obj?.name || "";
|
|
42334
|
+
const field_type_obj = status.structs.find((s) => s.name === (target_type?.name || "") && !s.is_simple_type)?.fields.find((f) => f.name === access_field.name)?.type ?? resolve_field_type(access_field, target_type?.name, status);
|
|
42335
|
+
const resolved_field_type = field_type_obj?.type_args?.length ? mono_type_name(field_type_obj) : field_type_obj?.name || "";
|
|
42209
42336
|
if (!!resolved_field_type && !field_type_obj?.is_ref && !field_type_obj?.is_nullable && is_struct_type(resolved_field_type, status)) {
|
|
42210
42337
|
if (final_offset > 0) emit_asm(status, `add x0, x0, #${final_offset}\n`);
|
|
42211
42338
|
return;
|
|
42212
42339
|
}
|
|
42213
|
-
const field_type =
|
|
42214
|
-
if (field_type === "string" ||
|
|
42340
|
+
const field_type = resolved_field_type;
|
|
42341
|
+
if (field_type === "string" || field_type_obj?.is_view) {
|
|
42215
42342
|
emit_string_pair_load_at(status, "x0", final_offset);
|
|
42216
42343
|
return;
|
|
42217
42344
|
}
|
|
@@ -42222,16 +42349,25 @@ function build_access_field(node, status) {
|
|
|
42222
42349
|
else emit_asm(status, `ldr x0, [x0, #${final_offset}]\n`);
|
|
42223
42350
|
return;
|
|
42224
42351
|
}
|
|
42225
|
-
|
|
42226
|
-
|
|
42227
|
-
|
|
42228
|
-
if (paramReg) {
|
|
42229
|
-
if (paramReg !== "x0") emit_asm(status, `mov x0, ${paramReg}\n`);
|
|
42230
|
-
} else if (is_local_ref_var(name, status)) emit_deref_var_address(status, "x0", name);
|
|
42231
|
-
else emit_var_address(status, "x0", name);
|
|
42232
|
-
} else {
|
|
42233
|
-
build_node(base, status);
|
|
42352
|
+
let target_built = false;
|
|
42353
|
+
if (node.target.node_type === "access" && access_chain_crosses_class(node, status)) {
|
|
42354
|
+
build_node(node.target, status);
|
|
42234
42355
|
ensure_newline(status);
|
|
42356
|
+
offset = get_field_offset(target_type?.name || "", access_field.name, status);
|
|
42357
|
+
target_built = true;
|
|
42358
|
+
}
|
|
42359
|
+
if (!target_built) {
|
|
42360
|
+
if (base.node_type === "value") {
|
|
42361
|
+
const name = base.value;
|
|
42362
|
+
const paramReg = get_param_reg(name, status);
|
|
42363
|
+
if (paramReg) {
|
|
42364
|
+
if (paramReg !== "x0") emit_asm(status, `mov x0, ${paramReg}\n`);
|
|
42365
|
+
} else if (is_local_ref_var(name, status)) emit_deref_var_address(status, "x0", name);
|
|
42366
|
+
else emit_var_address(status, "x0", name);
|
|
42367
|
+
} else {
|
|
42368
|
+
build_node(base, status);
|
|
42369
|
+
ensure_newline(status);
|
|
42370
|
+
}
|
|
42235
42371
|
}
|
|
42236
42372
|
const field_type_obj = resolve_field_type(access_field, target_type?.name, status);
|
|
42237
42373
|
const resolved_field_type = field_type_obj?.name || "";
|
|
@@ -42758,20 +42894,25 @@ function build_access_method(node, access_func, status) {
|
|
|
42758
42894
|
} else if (!target_is_simple && node.target.node_type === "access") {
|
|
42759
42895
|
const access_target = node.target;
|
|
42760
42896
|
if (access_target.access.node_type === "access_field") {
|
|
42761
|
-
|
|
42762
|
-
|
|
42763
|
-
if (base.node_type === "value") {
|
|
42764
|
-
const name = base.value;
|
|
42765
|
-
const paramReg = get_param_reg(name, status);
|
|
42766
|
-
if (paramReg) {
|
|
42767
|
-
if (paramReg !== "x0") emit_asm(status, `mov x0, ${paramReg}\n`);
|
|
42768
|
-
} else if (is_local_ref_var(name, status)) emit_deref_var_address(status, "x0", name);
|
|
42769
|
-
else emit_var_address(status, "x0", name);
|
|
42770
|
-
} else {
|
|
42771
|
-
build_node(base, status);
|
|
42897
|
+
if (access_chain_crosses_class(access_target, status)) {
|
|
42898
|
+
build_node(access_target, status);
|
|
42772
42899
|
ensure_newline(status);
|
|
42900
|
+
} else {
|
|
42901
|
+
const offset = compute_field_offset(access_target, status);
|
|
42902
|
+
const base = get_base_target(access_target);
|
|
42903
|
+
if (base.node_type === "value") {
|
|
42904
|
+
const name = base.value;
|
|
42905
|
+
const paramReg = get_param_reg(name, status);
|
|
42906
|
+
if (paramReg) {
|
|
42907
|
+
if (paramReg !== "x0") emit_asm(status, `mov x0, ${paramReg}\n`);
|
|
42908
|
+
} else if (is_local_ref_var(name, status)) emit_deref_var_address(status, "x0", name);
|
|
42909
|
+
else emit_var_address(status, "x0", name);
|
|
42910
|
+
} else {
|
|
42911
|
+
build_node(base, status);
|
|
42912
|
+
ensure_newline(status);
|
|
42913
|
+
}
|
|
42914
|
+
if (offset > 0) emit_asm(status, `add x0, x0, #${offset}\n`);
|
|
42773
42915
|
}
|
|
42774
|
-
if (offset > 0) emit_asm(status, `add x0, x0, #${offset}\n`);
|
|
42775
42916
|
if (target_type.is_array && target_type.is_array_heap) {
|
|
42776
42917
|
emit_asm(status, `ldr x0, [x0]\n`);
|
|
42777
42918
|
emit_asm(status, `add x0, x0, #8\n`);
|
|
@@ -42793,6 +42934,13 @@ function build_access_method(node, access_func, status) {
|
|
|
42793
42934
|
trait_recv_slot = allocate_stack_space(status, 8, 8);
|
|
42794
42935
|
emit_asm(status, `str x0, [x29, #${trait_recv_slot}]\n`);
|
|
42795
42936
|
}
|
|
42937
|
+
const ctor_temp_node = node.target.node_type === "func_call" ? node.target : void 0;
|
|
42938
|
+
const ctor_temp_receiver = !!(ctor_temp_node && (ctor_temp_node.is_thread_ctor || ctor_temp_node.is_fiber_ctor || ctor_temp_node.is_awaitable_ctor));
|
|
42939
|
+
let ctor_recv_slot;
|
|
42940
|
+
if (ctor_temp_receiver && !access_func.is_static && !receiver_is_string) {
|
|
42941
|
+
ctor_recv_slot = allocate_stack_space(status, 8, 8);
|
|
42942
|
+
emit_asm(status, `str x0, [x29, #${ctor_recv_slot}]\n`);
|
|
42943
|
+
}
|
|
42796
42944
|
const raw_needs_self = !access_func.is_static && access_func.params.length > 0 && !receiver_is_string;
|
|
42797
42945
|
const inline_func0 = status.structs.find((s) => s.name === mono_struct_name)?.functions.find((f) => f.is_inline && f.name === access_func.name && (access_func.mangled_name ? mangled_label(f, mono_struct_name) === access_func.mangled_name : true));
|
|
42798
42946
|
const elide_self_save = raw_needs_self && !!inline_func0 && naked_inline_skips_self(inline_func0, status.platform);
|
|
@@ -43010,6 +43158,14 @@ function build_access_method(node, access_func, status) {
|
|
|
43010
43158
|
}
|
|
43011
43159
|
if (outgoing_size > 0) emit_asm(status, `add sp, sp, #${outgoing_size}\n`);
|
|
43012
43160
|
emit_dispose_lambda_args_a64(status, lambda_arg_slots);
|
|
43161
|
+
if (ctor_recv_slot !== void 0) {
|
|
43162
|
+
emit_asm(status, `str x0, [sp, #-16]!\n`);
|
|
43163
|
+
emit_asm(status, `str x1, [sp, #-16]!\n`);
|
|
43164
|
+
emit_asm(status, `ldr x0, [x29, #${ctor_recv_slot}]\n`);
|
|
43165
|
+
emit_free(status);
|
|
43166
|
+
emit_asm(status, `ldr x1, [sp], #16\n`);
|
|
43167
|
+
emit_asm(status, `ldr x0, [sp], #16\n`);
|
|
43168
|
+
}
|
|
43013
43169
|
if (frees_string_receiver) {
|
|
43014
43170
|
emit_asm(status, `ldr x9, [sp]\n`);
|
|
43015
43171
|
emit_asm(status, `str x0, [sp, #-16]!\n`);
|
|
@@ -43415,15 +43571,22 @@ function scan_spawn_callees(root, result) {
|
|
|
43415
43571
|
if (node.node_type === "spawn") {
|
|
43416
43572
|
spawn_call = node.call;
|
|
43417
43573
|
spawn_ret = node.function_return_type;
|
|
43574
|
+
} else if (node.node_type === "func_call") {
|
|
43575
|
+
if (node.is_thread_ctor || node.is_fiber_ctor) {
|
|
43576
|
+
spawn_call = node.params?.[0];
|
|
43577
|
+
spawn_ret = node.function_return_type;
|
|
43578
|
+
}
|
|
43418
43579
|
} else if (node.node_type === "access" && node.access?.node_type === "access_func") {
|
|
43419
|
-
if (node.access.
|
|
43420
|
-
spawn_call = node.target?.params?.[0];
|
|
43421
|
-
spawn_ret = node.access.function_return_type;
|
|
43422
|
-
} else if (node.access.is_nursery_spawn) {
|
|
43580
|
+
if (node.access.is_nursery_spawn) {
|
|
43423
43581
|
spawn_call = node.access.params?.[0]?.params?.[0];
|
|
43424
43582
|
spawn_ret = node.access.function_return_type;
|
|
43425
43583
|
}
|
|
43426
43584
|
}
|
|
43585
|
+
if (spawn_call && spawn_call.node_type === "func") {
|
|
43586
|
+
mark_fn_value_callees(spawn_call, spawn_ret, result);
|
|
43587
|
+
spawn_call = void 0;
|
|
43588
|
+
spawn_ret = void 0;
|
|
43589
|
+
}
|
|
43427
43590
|
const name_and_ret = spawn_call && spawn_call.node_type === "func_call" ? [emission_label(spawn_call.resolved_function ?? { name: spawn_call.name }), spawn_ret ?? {}] : void 0;
|
|
43428
43591
|
if (name_and_ret && name_and_ret[0] && name_and_ret[1].name === "string") result.add(name_and_ret[0]);
|
|
43429
43592
|
for (const key of Object.keys(node)) {
|
|
@@ -43435,6 +43598,34 @@ function scan_spawn_callees(root, result) {
|
|
|
43435
43598
|
};
|
|
43436
43599
|
visit(root);
|
|
43437
43600
|
}
|
|
43601
|
+
/**
|
|
43602
|
+
* Heap-normalize a function-value spawn's lambda (spawn_ret is the
|
|
43603
|
+
* construction's function return type; the lambda's own signature is the
|
|
43604
|
+
* fallback). Two marks:
|
|
43605
|
+
*
|
|
43606
|
+
* 1. The LAMBDA's emission label — a literal or borrowed-variable return
|
|
43607
|
+
* inside the lambda is then strdup'd at the lambda's return site (the
|
|
43608
|
+
* return build classifies through the same label).
|
|
43609
|
+
* 2. Every string-returning call the lambda returns DIRECTLY
|
|
43610
|
+
* (`() => fn(args)`): the call's pair flows to the slot unchanged (a call
|
|
43611
|
+
* result is not re-strdup'd at return), so the callee itself must
|
|
43612
|
+
* normalize — the same mark the direct-call form puts on its callee.
|
|
43613
|
+
*/
|
|
43614
|
+
function mark_fn_value_callees(lambda, spawn_ret, result) {
|
|
43615
|
+
if ((spawn_ret?.name ?? lambda.return_type?.name) !== "string") return;
|
|
43616
|
+
result.add(emission_label(lambda));
|
|
43617
|
+
const visit = (node) => {
|
|
43618
|
+
if (!node || typeof node !== "object") return;
|
|
43619
|
+
if (node.node_type === "return" && node.value?.node_type === "func_call" && node.value.type?.name === "string") result.add(emission_label(node.value.resolved_function ?? { name: node.value.name }));
|
|
43620
|
+
for (const key of Object.keys(node)) {
|
|
43621
|
+
if (key === "parent" || key === "scope") continue;
|
|
43622
|
+
const val = node[key];
|
|
43623
|
+
if (Array.isArray(val)) for (const item of val) visit(item);
|
|
43624
|
+
else if (val && typeof val === "object") visit(val);
|
|
43625
|
+
}
|
|
43626
|
+
};
|
|
43627
|
+
for (const s of lambda.statements ?? []) visit(s);
|
|
43628
|
+
}
|
|
43438
43629
|
function scan_statements(statements, result, struct_name) {
|
|
43439
43630
|
for (const stmt of statements) {
|
|
43440
43631
|
if (stmt.node_type === "struct") {
|
|
@@ -44072,6 +44263,8 @@ function build(root, options = {}) {
|
|
|
44072
44263
|
status.code = reduce_pointer_walks(status.code);
|
|
44073
44264
|
status.code = unroll_loops_x2(status.code);
|
|
44074
44265
|
status.code = eliminate_dead_cycle_moves(status.code);
|
|
44266
|
+
status.code = expand_large_mov_immediates(status.code);
|
|
44267
|
+
status.code = expand_far_adr_addresses(status.code);
|
|
44075
44268
|
if (options.audit) {
|
|
44076
44269
|
status.code = status.code.replaceAll("bl _malloc\n", "bl _nomen_malloc_wrap\n");
|
|
44077
44270
|
status.code = status.code.replaceAll("bl _calloc\n", "bl _nomen_calloc_wrap\n");
|
|
@@ -44235,7 +44428,9 @@ const CALL_KEYWORDS = /* @__PURE__ */ new Set([
|
|
|
44235
44428
|
"trait",
|
|
44236
44429
|
"enum",
|
|
44237
44430
|
"bitset",
|
|
44238
|
-
"cast"
|
|
44431
|
+
"cast",
|
|
44432
|
+
"panic",
|
|
44433
|
+
"todo"
|
|
44239
44434
|
]);
|
|
44240
44435
|
const NO_SPACE_BEFORE = /* @__PURE__ */ new Set([
|
|
44241
44436
|
",",
|
|
@@ -44316,8 +44511,8 @@ function needs_space(pieces, i) {
|
|
|
44316
44511
|
if (NO_SPACE_AFTER.has(previous.text)) return false;
|
|
44317
44512
|
if (previous.text === "out" || previous.text === "in") return current.text !== ".";
|
|
44318
44513
|
if (is_sign(pieces, i - 1)) return false;
|
|
44319
|
-
if (current.text === ".") return !is_value_end(
|
|
44320
|
-
if (current.text === "(" || current.text === "[") return !is_value_end(
|
|
44514
|
+
if (current.text === ".") return !is_value_end(pieces, i - 1);
|
|
44515
|
+
if (current.text === "(" || current.text === "[") return !is_value_end(pieces, i - 1) && !CALL_KEYWORDS.has(previous.text);
|
|
44321
44516
|
if (current.kind === "comment") return current.space_before;
|
|
44322
44517
|
return true;
|
|
44323
44518
|
}
|
|
@@ -44330,13 +44525,20 @@ function is_sign(pieces, i) {
|
|
|
44330
44525
|
if (PREFIX_POSITIONS.has(previous.text)) return true;
|
|
44331
44526
|
return previous.kind === "word" && KEYWORDS.has(previous.text);
|
|
44332
44527
|
}
|
|
44333
|
-
/**
|
|
44334
|
-
|
|
44528
|
+
/**
|
|
44529
|
+
* Can the piece at `i` end a value, so that a following `(`, `[` or `.` binds
|
|
44530
|
+
* to it? A word after `.` is a member name, so it binds even when it spells a
|
|
44531
|
+
* keyword (`old_keys.move(i)`).
|
|
44532
|
+
*/
|
|
44533
|
+
function is_value_end(pieces, i) {
|
|
44534
|
+
const piece = pieces[i];
|
|
44335
44535
|
switch (piece.kind) {
|
|
44336
44536
|
case "number":
|
|
44337
44537
|
case "string":
|
|
44338
44538
|
case "char": return true;
|
|
44339
|
-
case "word":
|
|
44539
|
+
case "word":
|
|
44540
|
+
if (i > 0 && pieces[i - 1].text === ".") return true;
|
|
44541
|
+
return !KEYWORDS.has(piece.text);
|
|
44340
44542
|
case "symbol": return piece.text === ")" || piece.text === "]" || piece.text === ">" || piece.text === "?";
|
|
44341
44543
|
default: return false;
|
|
44342
44544
|
}
|
|
@@ -45055,12 +45257,12 @@ function is_list_open(pieces, i) {
|
|
|
45055
45257
|
const bracket = pieces[i].text;
|
|
45056
45258
|
if (bracket === "(") {
|
|
45057
45259
|
const previous = pieces[i - 1];
|
|
45058
|
-
return !!previous && is_value_end(
|
|
45260
|
+
return !!previous && is_value_end(pieces, i - 1) && previous.text !== "=";
|
|
45059
45261
|
}
|
|
45060
45262
|
if (bracket === "[") {
|
|
45061
45263
|
const previous = pieces[i - 1];
|
|
45062
45264
|
if (previous?.kind === "word" && KEYWORDS.has(previous.text)) return false;
|
|
45063
|
-
return !previous || !is_value_end(
|
|
45265
|
+
return !previous || !is_value_end(pieces, i - 1);
|
|
45064
45266
|
}
|
|
45065
45267
|
return false;
|
|
45066
45268
|
}
|
|
@@ -45133,9 +45335,7 @@ function is_redundant(type, value) {
|
|
|
45133
45335
|
if (value.length > type.length && value[type.length].text === "(") {
|
|
45134
45336
|
if (match_bracket(value, type.length) === value.length - 1) {
|
|
45135
45337
|
const constructor = value.slice(0, type.length);
|
|
45136
|
-
if (
|
|
45137
|
-
if (type.every((piece, index) => piece.text === constructor[index].text)) return true;
|
|
45138
|
-
}
|
|
45338
|
+
if (type.every((piece, index) => piece.text === constructor[index].text)) return true;
|
|
45139
45339
|
}
|
|
45140
45340
|
}
|
|
45141
45341
|
if (value.length === 1 && type.length === 1) return literal_type(value[0]) === type[0].text && SAFE_LITERAL_TYPES.has(type[0].text);
|
|
@@ -48902,8 +49102,17 @@ function check_declaration_node(decl, status) {
|
|
|
48902
49102
|
const src_sv = /^[A-Za-z_][A-Za-z0-9_]*$/.test(src_name ?? "") ? status.values.findLast((v) => v.name === src_name) : void 0;
|
|
48903
49103
|
if (src_sv && !src_sv.trait_slot_conformer && src_sv.type.name === decl.type.name) add_error(status, `a class-backed trait local cannot be copied — reassign through the original binding or declare a new class instance`, decl.value.start);
|
|
48904
49104
|
}
|
|
48905
|
-
if (!decl.type.name)
|
|
48906
|
-
|
|
49105
|
+
if (!decl.type.name) {
|
|
49106
|
+
decl.type = type_from_value_node(decl.value, status);
|
|
49107
|
+
if (!decl.type.name && status.type_params.length > 0 && decl.value.node_type === "func_call") {
|
|
49108
|
+
const call = decl.value;
|
|
49109
|
+
if (call.type_args?.length) {
|
|
49110
|
+
const constructed = new Type(call.name);
|
|
49111
|
+
constructed.type_args = call.type_args;
|
|
49112
|
+
decl.type = constructed;
|
|
49113
|
+
}
|
|
49114
|
+
}
|
|
49115
|
+
} else if (decl.type.is_array && decl.value.node_type === "array") {
|
|
48907
49116
|
const value_type = type_from_value_node(decl.value, status);
|
|
48908
49117
|
if (value_type.is_array && value_type.length) decl.type.length = value_type.length;
|
|
48909
49118
|
}
|
|
@@ -49473,20 +49682,25 @@ function infer_field_type_from_value(value, status) {
|
|
|
49473
49682
|
* Resolving inferred field types upfront (right after gather_structs registers
|
|
49474
49683
|
* every struct) makes them visible regardless of declaration order.
|
|
49475
49684
|
*
|
|
49476
|
-
* Only
|
|
49477
|
-
* field
|
|
49478
|
-
*
|
|
49479
|
-
*
|
|
49685
|
+
* Only the field-type inference runs for generic structs too: a generic
|
|
49686
|
+
* field's initializer names the constructed type in terms of the struct's own
|
|
49687
|
+
* type params (`var items = Buffer<T>()` → `Buffer<T>`), which is exactly the
|
|
49688
|
+
* annotation it replaces, so recording it upfront is safe and keeps it visible
|
|
49689
|
+
* to the ownership classifier and to monomorphization. The buffer rewrite and
|
|
49690
|
+
* owning-param marking stay non-generic, mirroring check_struct_node. The
|
|
49691
|
+
* inference is structural to avoid re-running the full declaration check and
|
|
49692
|
+
* its side effects; check_struct_node later skips already-resolved fields, so
|
|
49693
|
+
* each field is resolved exactly once.
|
|
49480
49694
|
*/
|
|
49481
49695
|
function resolve_struct_field_types(status) {
|
|
49482
49696
|
for (const struct of status.structs) {
|
|
49483
|
-
if (struct.is_generic) continue;
|
|
49484
49697
|
for (const field of struct.fields) {
|
|
49485
49698
|
if (field.type.name) continue;
|
|
49486
49699
|
if (!field.value) continue;
|
|
49487
49700
|
const t = infer_field_type_from_value(field.value, status);
|
|
49488
49701
|
if (t?.name) field.type = t;
|
|
49489
49702
|
}
|
|
49703
|
+
if (struct.is_generic) continue;
|
|
49490
49704
|
rewrite_generic_buffer_fields(struct, status);
|
|
49491
49705
|
mark_owning_auto_init_params(struct, status);
|
|
49492
49706
|
}
|
|
@@ -49502,8 +49716,9 @@ function resolve_struct_field_types(status) {
|
|
|
49502
49716
|
*
|
|
49503
49717
|
* A trait method with a default body need not be overridden; if it isn't,
|
|
49504
49718
|
* the default is inherited (and, for generic traits, already synthesized
|
|
49505
|
-
* onto the struct by synthesize_generic_trait_defaults). `#init
|
|
49506
|
-
* `#destroy` are lifecycle hooks, not contract methods, so
|
|
49719
|
+
* onto the struct by synthesize_generic_trait_defaults). `#init`,
|
|
49720
|
+
* `#destroy` and `#spawn` are lifecycle hooks, not contract methods, so
|
|
49721
|
+
* they're skipped.
|
|
49507
49722
|
*/
|
|
49508
49723
|
function check_trait_conformance(struct, status) {
|
|
49509
49724
|
for (let i = 0; i < struct.traits.length; i++) {
|
|
@@ -49514,7 +49729,7 @@ function check_trait_conformance(struct, status) {
|
|
|
49514
49729
|
const substitution = /* @__PURE__ */ new Map();
|
|
49515
49730
|
if (args && trait.type_params.length === args.length) for (let j = 0; j < trait.type_params.length; j++) substitution.set(trait.type_params[j], args[j].name);
|
|
49516
49731
|
for (const trait_func of trait.functions) {
|
|
49517
|
-
if (trait_func.name === "#init" || trait_func.name === "#destroy") continue;
|
|
49732
|
+
if (trait_func.name === "#init" || trait_func.name === "#destroy" || trait_func.name === "#spawn") continue;
|
|
49518
49733
|
const overrides = struct.functions.filter((f) => f.name === trait_func.name);
|
|
49519
49734
|
if (overrides.length === 0) {
|
|
49520
49735
|
if (!trait_func.has_body) add_error(status, `Type '${struct.name}' does not conform to trait '${trait.name}': missing required method '${trait_func.name}'`, struct.start);
|
|
@@ -50265,54 +50480,6 @@ function check_function_node(func, status) {
|
|
|
50265
50480
|
}
|
|
50266
50481
|
}
|
|
50267
50482
|
//#endregion
|
|
50268
|
-
//#region ../src/check/utils/awaitable_spawn_class.ts
|
|
50269
|
-
/**
|
|
50270
|
-
* The spawn-handle field contract an Awaitable class must declare for the
|
|
50271
|
-
* `X(fn(args))` construction sugar (ASYNC.md, "User-defined async
|
|
50272
|
-
* primitives"). The sugar packs the wrapped call eagerly and stores the
|
|
50273
|
-
* launch machinery in these fields — the same layout Thread/Fiber carry and
|
|
50274
|
-
* their own launch methods consume; a user primitive's launch methods read
|
|
50275
|
-
* the same contract.
|
|
50276
|
-
*/
|
|
50277
|
-
const SPAWN_FIELD_CONTRACT = [
|
|
50278
|
-
"task",
|
|
50279
|
-
"result_slot",
|
|
50280
|
-
"cancel_flag",
|
|
50281
|
-
"future"
|
|
50282
|
-
];
|
|
50283
|
-
/**
|
|
50284
|
-
* Resolve the Awaitable-conforming CLASS a generalized spawn-sugar call
|
|
50285
|
-
* (`X(fn(args))` / `X(() => work(n))`) targets. Undefined for everything
|
|
50286
|
-
* else: non-classes, classes not conforming to the core `Awaitable` trait,
|
|
50287
|
-
* and the reserved library names Thread/Fiber (whose own fast path runs
|
|
50288
|
-
* first and whose user-named shadows mean a plain struct).
|
|
50289
|
-
*
|
|
50290
|
-
* The class must additionally carry the spawn-handle field contract
|
|
50291
|
-
* (`spawn_field_contract_gaps`) and at most one type parameter before the
|
|
50292
|
-
* sugar fires — the callers validate those and report dedicated errors.
|
|
50293
|
-
*/
|
|
50294
|
-
function resolve_awaitable_spawn_class(name, status) {
|
|
50295
|
-
if (name === "Thread" || name === "Fiber") return void 0;
|
|
50296
|
-
const struct = resolve_declared_struct(name, status);
|
|
50297
|
-
if (!struct || !struct.is_class) return void 0;
|
|
50298
|
-
if (!struct.traits.includes("Awaitable")) return void 0;
|
|
50299
|
-
return struct;
|
|
50300
|
-
}
|
|
50301
|
-
/**
|
|
50302
|
-
* The class's spawn-field contract gaps: one human-readable entry per
|
|
50303
|
-
* missing or mistyped handle field. Empty means the class can carry the
|
|
50304
|
-
* packed task machinery.
|
|
50305
|
-
*/
|
|
50306
|
-
function spawn_field_contract_gaps(struct) {
|
|
50307
|
-
const gaps = [];
|
|
50308
|
-
for (const field_name of SPAWN_FIELD_CONTRACT) {
|
|
50309
|
-
const field = struct.fields.find((f) => f.name === field_name);
|
|
50310
|
-
if (!field) gaps.push(`missing field '${field_name}'`);
|
|
50311
|
-
else if (field.type?.name !== "uint64") gaps.push(`field '${field_name}' must be uint64, got '${field.type?.name || "<none>"}'`);
|
|
50312
|
-
}
|
|
50313
|
-
return gaps;
|
|
50314
|
-
}
|
|
50315
|
-
//#endregion
|
|
50316
50483
|
//#region ../src/check/utils/enum_mono.ts
|
|
50317
50484
|
/**
|
|
50318
50485
|
* Monomorphize a generic enum (`enum Result<T, E>`) applied to concrete type
|
|
@@ -50496,39 +50663,37 @@ function synthesize_lambda_name(func, status) {
|
|
|
50496
50663
|
//#endregion
|
|
50497
50664
|
//#region ../src/check/utils/validate_spawn_args_sendable.ts
|
|
50498
50665
|
/**
|
|
50499
|
-
* Validate a spawn's packed arguments (CLOSURE.md Phase 3d
|
|
50666
|
+
* Validate a spawn's packed arguments (CLOSURE.md Phase 3d; the shrunk
|
|
50667
|
+
* `Sendable` — docs/ASYNC.md, "Design decisions").
|
|
50668
|
+
*
|
|
50669
|
+
* `Sendable` gates exactly the case that can race: a SHARED class/trait
|
|
50670
|
+
* reference crossing the task boundary (a plain argument — the packed env
|
|
50671
|
+
* aliases the instance). Everything else is exempt:
|
|
50500
50672
|
*
|
|
50501
|
-
*
|
|
50502
|
-
*
|
|
50503
|
-
*
|
|
50504
|
-
*
|
|
50505
|
-
*
|
|
50506
|
-
*
|
|
50507
|
-
*
|
|
50673
|
+
* - MOVED arguments: the callee's `move T` parameter takes exclusive
|
|
50674
|
+
* ownership, so no one else can touch the instance concurrently;
|
|
50675
|
+
* - scalars, strings, and everything `is_sendable_type` accepts (copied or
|
|
50676
|
+
* safe by value — strings are deep-copied at pack);
|
|
50677
|
+
* - owning VALUE structs whose fields are all safe (the pack deep-copies
|
|
50678
|
+
* them; `is_sendable_type`'s field recursion rejects a struct with an
|
|
50679
|
+
* unsafe class field).
|
|
50680
|
+
*
|
|
50681
|
+
* The nursery-borrow exception (a non-Sendable class aliased into a task
|
|
50682
|
+
* inside `async { }`) is RETIRED: mark the class `Sendable`, move it in
|
|
50683
|
+
* (a `move` parameter), or share it through a `Sendable` primitive
|
|
50684
|
+
* (`Mutex`/`Channel`).
|
|
50508
50685
|
*/
|
|
50509
|
-
function validate_spawn_args_sendable(call, status,
|
|
50510
|
-
const
|
|
50511
|
-
for (
|
|
50686
|
+
function validate_spawn_args_sendable(call, status, reason) {
|
|
50687
|
+
const callee_params = call.resolved_function?.params?.filter((p) => !p.is_self_param);
|
|
50688
|
+
for (let i = 0; i < call.params.length; i++) {
|
|
50689
|
+
const param = call.params[i];
|
|
50512
50690
|
let arg_type = type_from_value_node(param, status);
|
|
50513
50691
|
const stamped = param.type;
|
|
50514
50692
|
if (!arg_type.name && stamped?.name) arg_type = stamped;
|
|
50693
|
+
if (callee_params?.[i]?.is_moved) continue;
|
|
50515
50694
|
if (is_sendable_type(arg_type.name, status)) continue;
|
|
50516
|
-
|
|
50517
|
-
const is_pointer_arg = (() => {
|
|
50518
|
-
const struct = status.structs.find((s) => s.name === arg_type.name);
|
|
50519
|
-
return !!struct && (struct.is_class || struct.traits.length > 0);
|
|
50520
|
-
})();
|
|
50521
|
-
if (allow_borrows && is_pointer_arg && donor) {
|
|
50522
|
-
result.borrow_args = true;
|
|
50523
|
-
continue;
|
|
50524
|
-
}
|
|
50525
|
-
if (allow_borrows && is_pointer_arg && !donor) {
|
|
50526
|
-
add_error(status, `Spawn argument '${arg_type.name}' is not Sendable and may only be borrowed from a named local or parameter (a temporary would die before the task runs)`, param.start);
|
|
50527
|
-
continue;
|
|
50528
|
-
}
|
|
50529
|
-
add_error(status, `Spawn argument of type ${arg_type.name || "<unknown>"} is not Sendable${is_pointer_arg && !allow_borrows ? reason ?? " — a non-Sendable class may only be passed inside a nursery (async { }), where the join bounds the borrow" : ""}`, param.start);
|
|
50695
|
+
add_error(status, `Spawn argument of type ${arg_type.name || "<unknown>"} is not Sendable${reason ?? " — a shared class/trait reference crossing a task boundary must be marked Sendable; move it in (a `move` parameter) or share it through a Sendable primitive (Mutex/Channel)"}`, param.start);
|
|
50530
50696
|
}
|
|
50531
|
-
return result;
|
|
50532
50697
|
}
|
|
50533
50698
|
//#endregion
|
|
50534
50699
|
//#region ../src/check/check_function_call_node.ts
|
|
@@ -50548,28 +50713,17 @@ function find_free_function(status, name) {
|
|
|
50548
50713
|
return status.functions.findLast((f) => f.name === name && !is_method(f)) ?? status.functions.findLast((f) => f.name === name);
|
|
50549
50714
|
}
|
|
50550
50715
|
function check_function_call_node(node, status) {
|
|
50551
|
-
const magic_ctor = node.name
|
|
50552
|
-
const thread_shadow_struct = resolve_declared_struct("Thread", status);
|
|
50716
|
+
const magic_ctor = resolve_declared_struct(node.name, status)?.functions.some((f) => f.name === "#spawn") ? node.name : void 0;
|
|
50553
50717
|
const first = node.params[0];
|
|
50554
50718
|
const first_decl = first?.node_type === "value" && typeof first.value === "string" ? status.values.findLast((v) => v.name === first.value) : void 0;
|
|
50555
50719
|
const first_fn = first?.node_type === "value" && typeof first.value === "string" ? status.functions.findLast((f) => f.name === first.value) : void 0;
|
|
50556
50720
|
const func_value_arg = first?.node_type === "func" || first?.node_type === "value" && typeof first.value === "string" && (first_decl?.func_params !== void 0 || !!first_fn);
|
|
50557
50721
|
const spawn_ctor_shape = node.params.length === 1 && (node.params[0].node_type === "func_call" || func_value_arg) && !find_free_function(status, node.name);
|
|
50558
|
-
|
|
50559
|
-
|
|
50560
|
-
|
|
50561
|
-
if (candidate) {
|
|
50562
|
-
const gaps = spawn_field_contract_gaps(candidate);
|
|
50563
|
-
if (gaps.length === 0 && candidate.type_params.length <= 1) awaitable_ctor = candidate;
|
|
50564
|
-
else {
|
|
50565
|
-
const why = gaps.length ? `it lacks the spawn-field contract (${gaps.join("; ")})` : `it has ${candidate.type_params.length} type parameters (the sugar types the class as C<T> with T the wrapped call's return type)`;
|
|
50566
|
-
add_error(status, `'${node.name}' conforms to Awaitable but cannot take the ${node.name}(fn(args)) construction sugar: ${why}`, node.start);
|
|
50567
|
-
return false;
|
|
50568
|
-
}
|
|
50569
|
-
}
|
|
50722
|
+
if (magic_ctor && !spawn_ctor_shape && !find_free_function(status, node.name)) {
|
|
50723
|
+
add_error(status, "`#spawn` expects a call or a zero-argument function value (bind arguments in the lambda's captures)", first?.start ?? node.start);
|
|
50724
|
+
return false;
|
|
50570
50725
|
}
|
|
50571
|
-
if (magic_ctor && spawn_ctor_shape
|
|
50572
|
-
if (awaitable_ctor) return check_magic_ctor(node, status, awaitable_ctor.name);
|
|
50726
|
+
if (magic_ctor && spawn_ctor_shape) return check_magic_ctor(node, status, magic_ctor);
|
|
50573
50727
|
let func = find_free_function(status, node.name);
|
|
50574
50728
|
if (func?.is_closure && func.captures?.length) func = void 0;
|
|
50575
50729
|
if (!func) {
|
|
@@ -50734,7 +50888,18 @@ function monomorphize(generic_struct, type_args, status) {
|
|
|
50734
50888
|
const mono_fields = generic_struct.fields.map((field) => {
|
|
50735
50889
|
const resolved_type = substitute_type(field.type, substitution);
|
|
50736
50890
|
const mono_field = new DeclarationNode(field.start, field.visibility, field.declaration, field.name, resolved_type, field.value ? clone_node(field.value) : void 0);
|
|
50737
|
-
if (mono_field.value)
|
|
50891
|
+
if (mono_field.value) {
|
|
50892
|
+
if (!mono_field.type.name && mono_field.value.node_type === "func_call") {
|
|
50893
|
+
const call = mono_field.value;
|
|
50894
|
+
const target = status.structs.find((s) => s.name === call.name);
|
|
50895
|
+
if (target?.type_params.length && call.type_args?.length === target.type_params.length) {
|
|
50896
|
+
const constructed = new Type(call.name);
|
|
50897
|
+
constructed.type_args = call.type_args.map((arg) => substitute_type(arg, substitution));
|
|
50898
|
+
mono_field.type = constructed;
|
|
50899
|
+
}
|
|
50900
|
+
}
|
|
50901
|
+
substitute_raw_in_node(mono_field.value, substitution, status.structs, void 0, status.traits, status.enums);
|
|
50902
|
+
}
|
|
50738
50903
|
mono_field.is_library = field.is_library;
|
|
50739
50904
|
mono_field.is_readonly = field.is_readonly;
|
|
50740
50905
|
mono_field.is_view_keyword = field.is_view_keyword;
|
|
@@ -50761,6 +50926,7 @@ function monomorphize(generic_struct, type_args, status) {
|
|
|
50761
50926
|
mono_struct.source_type_args = flat_args;
|
|
50762
50927
|
mono_struct.is_class = generic_struct.is_class;
|
|
50763
50928
|
mono_struct.is_library = generic_struct.is_library;
|
|
50929
|
+
mono_struct.trait_args = (generic_struct.trait_args ?? []).map((args) => args?.map((t) => substitute_type(t, substitution)));
|
|
50764
50930
|
status.structs.push(mono_struct);
|
|
50765
50931
|
status.types.push(mono_name);
|
|
50766
50932
|
const cloned_methods = [];
|
|
@@ -51215,14 +51381,7 @@ function resolve_free_calls_in_node(node, status) {
|
|
|
51215
51381
|
const any_node = node;
|
|
51216
51382
|
if (node.node_type === "func_call" && !any_node.resolved_function) {
|
|
51217
51383
|
const name = node.name;
|
|
51218
|
-
if (name
|
|
51219
|
-
const fiber_struct = resolve_declared_struct("Fiber", status);
|
|
51220
|
-
const magic = name === "Thread" ? "Thread" : fiber_struct?.is_library === true ? "Fiber" : void 0;
|
|
51221
|
-
if (magic) rederive_magic_ctor_annotations(node, status, magic);
|
|
51222
|
-
} else {
|
|
51223
|
-
const candidate = resolve_awaitable_spawn_class(name, status);
|
|
51224
|
-
if (candidate && spawn_field_contract_gaps(candidate).length === 0 && candidate.type_params.length <= 1) rederive_magic_ctor_annotations(node, status, candidate.name);
|
|
51225
|
-
}
|
|
51384
|
+
if (resolve_declared_struct(name, status)?.functions.some((f) => f.name === "#spawn")) rederive_magic_ctor_annotations(node, status, name);
|
|
51226
51385
|
const func = find_free_function(status, name);
|
|
51227
51386
|
if (func) set_resolved_function(node, func);
|
|
51228
51387
|
}
|
|
@@ -51281,18 +51440,6 @@ function derive_annotations_for_access_func(access_node, fc, status) {
|
|
|
51281
51440
|
rederive_nursery_spawn_annotations(fc, status);
|
|
51282
51441
|
return;
|
|
51283
51442
|
}
|
|
51284
|
-
if (receiver_type === "Thread" && fc.name === "start") {
|
|
51285
|
-
rederive_spawn_start_annotations(access_node, fc, status, "Thread", false);
|
|
51286
|
-
return;
|
|
51287
|
-
}
|
|
51288
|
-
if (receiver_type === "Thread" && fc.name === "detach") {
|
|
51289
|
-
rederive_spawn_detach_annotations(access_node, fc, status);
|
|
51290
|
-
return;
|
|
51291
|
-
}
|
|
51292
|
-
if (receiver_type === "Fiber" && (fc.name === "start" || fc.name === "start_on")) {
|
|
51293
|
-
rederive_spawn_start_annotations(access_node, fc, status, "Fiber", fc.name === "start_on");
|
|
51294
|
-
return;
|
|
51295
|
-
}
|
|
51296
51443
|
const struct = status.structs.find((s) => s.name === receiver_type);
|
|
51297
51444
|
if (!struct) return;
|
|
51298
51445
|
const func = struct.functions.find((f) => f.name === fc.name);
|
|
@@ -51377,7 +51524,7 @@ function check_magic_ctor(node, status, name) {
|
|
|
51377
51524
|
add_error(status, `Spawned call '${call.name}' did not resolve`, node.start);
|
|
51378
51525
|
return false;
|
|
51379
51526
|
}
|
|
51380
|
-
validate_spawn_args_sendable(call, status
|
|
51527
|
+
validate_spawn_args_sendable(call, status);
|
|
51381
51528
|
stamp_spawn_ctor(node, name);
|
|
51382
51529
|
const return_type = call.type;
|
|
51383
51530
|
const result_type_arg = return_type && return_type.name && return_type.name !== "void" && return_type.name !== "?" ? new Type(return_type.name) : new Type("uint64");
|
|
@@ -51402,7 +51549,8 @@ function stamp_spawn_ctor(node, name) {
|
|
|
51402
51549
|
* nothing) — carries T on the type args, which the `.start()` /
|
|
51403
51550
|
* nursery paths read. Also materializes Task<T>: the launch seam a user
|
|
51404
51551
|
* primitive drives is Task's runtime (Task.pool_submit / future_* —
|
|
51405
|
-
* ASYNC.md, "
|
|
51552
|
+
* docs/ASYNC.md, "Extension: a class with its own `#spawn`"), and
|
|
51553
|
+
* statics on the bare
|
|
51406
51554
|
* generic only link once a mono instantiation exists (the same
|
|
51407
51555
|
* materialization a `.start()` launch performs). */
|
|
51408
51556
|
function stamp_spawn_type_args(node, status, name, result_type_arg) {
|
|
@@ -51554,61 +51702,6 @@ function rederive_nursery_spawn_annotations(fc, status) {
|
|
|
51554
51702
|
if (task_struct && task_struct.type_params.length > 0) monomorphize(task_struct, [result_type_arg], status);
|
|
51555
51703
|
}
|
|
51556
51704
|
/**
|
|
51557
|
-
* Re-derive the annotations for a `Thread(fn(args)).start()` /
|
|
51558
|
-
* `Fiber(fn(args)).start[_on](...)` call inside a monomorphised body —
|
|
51559
|
-
* mirroring check_spawn_start (check_access_node). The build gates its
|
|
51560
|
-
* spawn/fiber-trampoline emission on is_thread_start / is_fiber_start
|
|
51561
|
-
* (without them the call falls through to method resolution, which finds no
|
|
51562
|
-
* `start`) and the declaration-ownership analysis on owned_return (the Task
|
|
51563
|
-
* a spawn yields is a fresh heap allocation, not a borrow).
|
|
51564
|
-
*/
|
|
51565
|
-
function rederive_spawn_start_annotations(access_node, fc, status, name, _start_on) {
|
|
51566
|
-
const ctor = access_node?.target;
|
|
51567
|
-
const ctor_flagged = ctor?.node_type === "func_call" && (name === "Thread" ? ctor.is_thread_ctor : ctor.is_fiber_ctor);
|
|
51568
|
-
if (name === "Thread") fc.is_thread_start = true;
|
|
51569
|
-
else fc.is_fiber_start = true;
|
|
51570
|
-
fc.owned_return = true;
|
|
51571
|
-
let return_type;
|
|
51572
|
-
if (ctor_flagged) {
|
|
51573
|
-
const call = ctor.params[0];
|
|
51574
|
-
return_type = ctor.function_return_type ?? call.type;
|
|
51575
|
-
if (!return_type?.name) {
|
|
51576
|
-
const func = find_free_function(status, call.name);
|
|
51577
|
-
if (func) return_type = func.return_type;
|
|
51578
|
-
}
|
|
51579
|
-
} else return_type = (access_node?.target?.type)?.type_args?.[0];
|
|
51580
|
-
if (!return_type?.name) return_type = new Type("uint64");
|
|
51581
|
-
const is_void = !return_type.name || return_type.name === "void" || return_type.name === "?";
|
|
51582
|
-
fc.function_return_type = is_void ? new Type("uint64") : return_type;
|
|
51583
|
-
const result_type_arg = is_void ? new Type("uint64") : return_type;
|
|
51584
|
-
if (!fc.type?.name) {
|
|
51585
|
-
const task_type = new Type("Task");
|
|
51586
|
-
task_type.type_args = [result_type_arg];
|
|
51587
|
-
fc.type = task_type;
|
|
51588
|
-
}
|
|
51589
|
-
const task_struct = status.structs.find((s) => s.name === "Task");
|
|
51590
|
-
if (task_struct && task_struct.type_params.length > 0) monomorphize(task_struct, [result_type_arg], status);
|
|
51591
|
-
}
|
|
51592
|
-
/**
|
|
51593
|
-
* Re-derive the annotations for a `Thread(fn(args)).detach()` call inside a
|
|
51594
|
-
* monomorphised body — mirroring check_spawn_detach (check_access_node).
|
|
51595
|
-
* The build gates its detached-pthread emission on is_thread_detach.
|
|
51596
|
-
*/
|
|
51597
|
-
function rederive_spawn_detach_annotations(access_node, fc, status) {
|
|
51598
|
-
fc.is_thread_detach = true;
|
|
51599
|
-
const ctor = access_node?.target;
|
|
51600
|
-
if (ctor?.node_type === "func_call" && ctor.is_thread_ctor && ctor.params[0]) {
|
|
51601
|
-
const call = ctor.params[0];
|
|
51602
|
-
let return_type = ctor.function_return_type ?? call.type;
|
|
51603
|
-
if (!return_type?.name) {
|
|
51604
|
-
const func = find_free_function(status, call.name);
|
|
51605
|
-
if (func) return_type = func.return_type;
|
|
51606
|
-
}
|
|
51607
|
-
fc.function_return_type = return_type;
|
|
51608
|
-
} else fc.function_return_type = (access_node?.target?.type)?.type_args?.[0] ?? new Type("uint64");
|
|
51609
|
-
if (!fc.type?.name) fc.type = new Type("void");
|
|
51610
|
-
}
|
|
51611
|
-
/**
|
|
51612
51705
|
* Substitute type-param names through a function-typed parameter's nested
|
|
51613
51706
|
* signature (`func (T, out T)` on a generic struct method or generic free
|
|
51614
51707
|
* function). The signature hangs off the ParameterNode itself (`func_params`
|
|
@@ -51918,6 +52011,15 @@ function substitute_raw_in_node(node, substitution, structs, deref_params = /* @
|
|
|
51918
52011
|
any_node.name = mono_type_name(route_reference_elem(any_node.name.endsWith(old_suffix) ? any_node.name.slice(0, any_node.name.length - old_suffix.length) : any_node.name, any_node.type_args), any_node.type_args);
|
|
51919
52012
|
} else if (node.node_type === "access_func" && any_node.type_args?.length) any_node.type_args = any_node.type_args.map((t) => substitute_type(t, substitution));
|
|
51920
52013
|
if (node.node_type === "access" && any_node.access?.type_args?.length) any_node.access.type_args = any_node.access.type_args.map((t) => substitute_type(t, substitution));
|
|
52014
|
+
if (node.node_type === "declare" && !any_node.type?.name && any_node.value?.node_type === "func_call") {
|
|
52015
|
+
const call = any_node.value;
|
|
52016
|
+
const target = structs.find((s) => s.name === call.name);
|
|
52017
|
+
if (target?.type_params.length && call.type_args?.length === target.type_params.length) {
|
|
52018
|
+
const constructed = new Type(call.name);
|
|
52019
|
+
constructed.type_args = call.type_args;
|
|
52020
|
+
any_node.type = constructed;
|
|
52021
|
+
}
|
|
52022
|
+
}
|
|
51921
52023
|
if (node.node_type === "declare" && any_node.type?.name) {
|
|
51922
52024
|
any_node.type = substitute_type(any_node.type, substitution);
|
|
51923
52025
|
if (any_node.type.name === "Buffer" && any_node.type.type_args?.length === 1 && is_reference_type_arg(any_node.type.type_args[0], structs, traits)) any_node.type.name = "ClassBuffer";
|
|
@@ -53284,6 +53386,11 @@ function check_access_node(node, status) {
|
|
|
53284
53386
|
status.allocations.push(decl);
|
|
53285
53387
|
node.target = new ValueNode(node.target.start, decl.name, target_type);
|
|
53286
53388
|
}
|
|
53389
|
+
if (node.target.node_type === "access" && node.target.access.node_type === "access_func" && node.target.access.owned_return && !!status.structs.find((s) => (s.name === target_type.name || target_type.type_args?.length && s.name === mono_type_name(target_type)) && s.is_class)) {
|
|
53390
|
+
const decl = new DeclarationNode(node.target.start, "private", "var", `_recv_${status.var_name_counter.value++}`, target_type, node.target);
|
|
53391
|
+
status.allocations.push(decl);
|
|
53392
|
+
node.target = new ValueNode(node.target.start, decl.name, target_type);
|
|
53393
|
+
}
|
|
53287
53394
|
if (node.access.node_type === "access_field") {
|
|
53288
53395
|
const af = node.access;
|
|
53289
53396
|
if (af.is_destructure) {
|
|
@@ -53513,9 +53620,11 @@ function check_access_field_node(target_type, target, node, status) {
|
|
|
53513
53620
|
}
|
|
53514
53621
|
function check_access_function_node(target_type, target, node, status) {
|
|
53515
53622
|
if (target_type.name === "Nursery" && node.name === "start") return check_nursery_spawn(node, status);
|
|
53516
|
-
if (target_type.name === "
|
|
53517
|
-
|
|
53518
|
-
|
|
53623
|
+
if (target_type.name === "Fiber" && node.name === "start_on") {
|
|
53624
|
+
if (!check_spawn_start_on(target, target_type, node, status)) return false;
|
|
53625
|
+
node.name = "start";
|
|
53626
|
+
node.params = [];
|
|
53627
|
+
}
|
|
53519
53628
|
if (target_type.is_view) {
|
|
53520
53629
|
const elem_name = target_type.name === "string" ? "char" : target_type.name;
|
|
53521
53630
|
if (node.name === "at" && node.params.length === 1) {
|
|
@@ -53788,77 +53897,32 @@ function check_nursery_spawn(node, status) {
|
|
|
53788
53897
|
return true;
|
|
53789
53898
|
}
|
|
53790
53899
|
/**
|
|
53791
|
-
*
|
|
53792
|
-
*
|
|
53793
|
-
*
|
|
53794
|
-
*
|
|
53795
|
-
|
|
53796
|
-
|
|
53797
|
-
|
|
53798
|
-
|
|
53799
|
-
*/
|
|
53800
|
-
function check_spawn_start(target, target_type, node, status, kind, start_on) {
|
|
53801
|
-
const ctor = target;
|
|
53802
|
-
const wrapped_call = target.node_type === "func_call" && (kind === "Thread" ? ctor.is_thread_ctor : ctor.is_fiber_ctor) ? ctor.params[0] : void 0;
|
|
53803
|
-
let return_type = target_type.type_args?.[0];
|
|
53804
|
-
if (!return_type?.name && wrapped_call) return_type = wrapped_call.type;
|
|
53805
|
-
if (!return_type?.name) {
|
|
53806
|
-
add_error(status, `${kind}(fn(args)).start expects a spawned call, e.g. ${kind}(work(n)).start()`, node.start);
|
|
53900
|
+
* Validate a `Fiber(fn(args)).start_on(buf)` call's stack buffer (see
|
|
53901
|
+
* ASYNC.md): a fixed-size array of at least 16 KB. The caller rewrites the
|
|
53902
|
+
* access to the ordinary Fiber.start afterwards — this function is
|
|
53903
|
+
* validation only.
|
|
53904
|
+
*/
|
|
53905
|
+
function check_spawn_start_on(target, target_type, node, status) {
|
|
53906
|
+
if (node.params.length !== 1) {
|
|
53907
|
+
add_error(status, `start_on expects a stack buffer argument, e.g. Fiber(work(n)).start_on(buf)`, node.start);
|
|
53807
53908
|
return false;
|
|
53808
53909
|
}
|
|
53809
|
-
const
|
|
53810
|
-
if (
|
|
53811
|
-
|
|
53812
|
-
|
|
53813
|
-
|
|
53814
|
-
|
|
53815
|
-
|
|
53816
|
-
|
|
53817
|
-
|
|
53818
|
-
|
|
53819
|
-
|
|
53820
|
-
|
|
53821
|
-
|
|
53822
|
-
|
|
53823
|
-
|
|
53824
|
-
add_error(status, "start_on expects a fixed-size array (T[N]); the length must be known at compile time", node.params[0].start);
|
|
53825
|
-
return false;
|
|
53826
|
-
}
|
|
53827
|
-
if (len * elem_size < 16384) {
|
|
53828
|
-
add_error(status, `Fiber stack too small: ${len * elem_size} bytes, minimum is 16384`, node.params[0].start);
|
|
53829
|
-
return false;
|
|
53830
|
-
}
|
|
53910
|
+
const buf_type = type_from_value_node(node.params[0], status);
|
|
53911
|
+
if (!buf_type.is_array) {
|
|
53912
|
+
add_error(status, `start_on expects a fixed-size array stack buffer (T[N]), got ${buf_type.name || "<unknown>"}`, node.params[0].start);
|
|
53913
|
+
return false;
|
|
53914
|
+
}
|
|
53915
|
+
const elem_size = buf_type.name === "string" ? 16 : 8;
|
|
53916
|
+
const len_node = buf_type.length;
|
|
53917
|
+
const len = len_node && len_node.node_type === "value" ? parseInt(len_node.value, 10) : NaN;
|
|
53918
|
+
if (Number.isNaN(len)) {
|
|
53919
|
+
add_error(status, "start_on expects a fixed-size array (T[N]); the length must be known at compile time", node.params[0].start);
|
|
53920
|
+
return false;
|
|
53921
|
+
}
|
|
53922
|
+
if (len * elem_size < 16384) {
|
|
53923
|
+
add_error(status, `Fiber stack too small: ${len * elem_size} bytes, minimum is 16384`, node.params[0].start);
|
|
53924
|
+
return false;
|
|
53831
53925
|
}
|
|
53832
|
-
const result_type_arg = return_type.name && return_type.name !== "void" && return_type.name !== "?" ? return_type : new Type("uint64");
|
|
53833
|
-
const task_type = new Type("Task");
|
|
53834
|
-
task_type.type_args = [result_type_arg];
|
|
53835
|
-
node.function_return_type = return_type;
|
|
53836
|
-
node.type = task_type;
|
|
53837
|
-
if (call) call.type = task_type;
|
|
53838
|
-
if (kind === "Thread") node.is_thread_start = true;
|
|
53839
|
-
else node.is_fiber_start = true;
|
|
53840
|
-
node.owned_return = true;
|
|
53841
|
-
const task_struct = status.structs.find((s) => s.name === "Task");
|
|
53842
|
-
if (task_struct && task_struct.type_params.length > 0) monomorphize(task_struct, [result_type_arg], status);
|
|
53843
|
-
return true;
|
|
53844
|
-
}
|
|
53845
|
-
/**
|
|
53846
|
-
* Check a `Thread(fn(args)).detach()` call — the daemon form (see
|
|
53847
|
-
* ASYNC.md, "Daemon tasks"). The wrapped call runs on a dedicated detached
|
|
53848
|
-
* pthread (never a pool worker); nobody joins it, and process exit kills it
|
|
53849
|
-
* mid-execution BY DESIGN — the std::thread::spawn contract. The daemon
|
|
53850
|
-
* owns its own shutdown (a stop channel or flag), not process exit. There
|
|
53851
|
-
* is no handle, no future, and no cancellation: statement form only, the
|
|
53852
|
-
* expression types as void. The receiver is the magic constructor or any
|
|
53853
|
-
* Thread-typed expression (a stored daemon handle).
|
|
53854
|
-
*/
|
|
53855
|
-
function check_spawn_detach(target, target_type, node, status) {
|
|
53856
|
-
const ctor = target;
|
|
53857
|
-
const wrapped_call = target.node_type === "func_call" && ctor.is_thread_ctor ? ctor.params[0] : void 0;
|
|
53858
|
-
if (wrapped_call) validate_spawn_args_sendable(wrapped_call, status, false, " — a detached task must own its arguments (it outlives every scope; only a nursery's join bounds a borrow)");
|
|
53859
|
-
node.function_return_type = wrapped_call?.type ?? target_type.type_args?.[0] ?? new Type("uint64");
|
|
53860
|
-
node.type = new Type("void");
|
|
53861
|
-
node.is_thread_detach = true;
|
|
53862
53926
|
return true;
|
|
53863
53927
|
}
|
|
53864
53928
|
//#endregion
|
|
@@ -57545,7 +57609,7 @@ function parse_function(visibility, status, name_override, is_inline, is_extern,
|
|
|
57545
57609
|
if (op_internal[name]) name = op_internal[name];
|
|
57546
57610
|
const parent_for_type = status.stack.at(-1);
|
|
57547
57611
|
let return_type = new Type("");
|
|
57548
|
-
if (name === "#init" && (parent_for_type?.node_type === "struct" || parent_for_type?.node_type === "extend")) return_type = new Type(parent_for_type.name);
|
|
57612
|
+
if ((name === "#init" || name === "#spawn") && (parent_for_type?.node_type === "struct" || parent_for_type?.node_type === "extend")) return_type = new Type(parent_for_type.name);
|
|
57549
57613
|
const func = new FunctionNode(start, visibility, name, return_type);
|
|
57550
57614
|
if (is_inline) func.is_inline = true;
|
|
57551
57615
|
if (is_unsafe) {
|
|
@@ -57565,7 +57629,7 @@ function parse_function(visibility, status, name_override, is_inline, is_extern,
|
|
|
57565
57629
|
if (expect("=", status) && expect("(", status)) {
|
|
57566
57630
|
const parent = status.stack.at(-1);
|
|
57567
57631
|
if (peek_current(status) !== ")") parse_function_parameter(parent, func, status);
|
|
57568
|
-
if (name === "#destroy" && parent.node_type === "struct" && !func.params.some((p) => p.is_self_param)) {
|
|
57632
|
+
if ((name === "#destroy" || name === "#spawn") && parent.node_type === "struct" && !func.params.some((p) => p.is_self_param)) {
|
|
57569
57633
|
const self_param = new ParameterNode(start, "self", new Type(parent.name));
|
|
57570
57634
|
self_param.is_self_param = true;
|
|
57571
57635
|
self_param.declaration = "var";
|
|
@@ -57597,7 +57661,7 @@ function parse_function(visibility, status, name_override, is_inline, is_extern,
|
|
|
57597
57661
|
parse_statement(status);
|
|
57598
57662
|
expect("}", status);
|
|
57599
57663
|
status.stack.pop();
|
|
57600
|
-
if (name !== "#init" && name !== "#destroy") check_missing_return(func, status);
|
|
57664
|
+
if (name !== "#init" && name !== "#destroy" && name !== "#spawn") check_missing_return(func, status);
|
|
57601
57665
|
}
|
|
57602
57666
|
switch (parent.node_type) {
|
|
57603
57667
|
case "root":
|
|
@@ -57782,7 +57846,7 @@ function parse_struct(visibility, status, is_class = false) {
|
|
|
57782
57846
|
parse_statement(status);
|
|
57783
57847
|
expect("}", status);
|
|
57784
57848
|
status.stack.pop();
|
|
57785
|
-
if (!struct.functions.some((f) => f.name === "#init")) {
|
|
57849
|
+
if (!struct.functions.some((f) => f.name === "#init" || f.name === "#spawn")) {
|
|
57786
57850
|
const func = new FunctionNode(-1, visibility, "#init", new Type(struct.name));
|
|
57787
57851
|
func.params = struct.fields.filter((f) => f.visibility !== "private" && !f.value).map((f) => {
|
|
57788
57852
|
const param = new ParameterNode(-1, f.name, f.func_params ? new Type("func") : f.type);
|
|
@@ -58071,8 +58135,13 @@ function parse_visibility(visibility, status) {
|
|
|
58071
58135
|
consume(status);
|
|
58072
58136
|
consume(status);
|
|
58073
58137
|
parse_function(visibility, status, "#destroy");
|
|
58138
|
+
} else if (next2 === "spawn") {
|
|
58139
|
+
consume(status);
|
|
58140
|
+
consume(status);
|
|
58141
|
+
consume(status);
|
|
58142
|
+
parse_function(visibility, status, "#spawn");
|
|
58074
58143
|
} else {
|
|
58075
|
-
add_error(status, `Expected #init or #
|
|
58144
|
+
add_error(status, `Expected #init, #destroy or #spawn after pub`, get_index(status));
|
|
58076
58145
|
consume(status);
|
|
58077
58146
|
}
|
|
58078
58147
|
break;
|
|
@@ -58199,8 +58268,12 @@ function parse_statement(status) {
|
|
|
58199
58268
|
consume(status);
|
|
58200
58269
|
consume(status);
|
|
58201
58270
|
parse_destroy(default_visibility(status), status, "#destroy");
|
|
58271
|
+
} else if (next === "spawn") {
|
|
58272
|
+
consume(status);
|
|
58273
|
+
consume(status);
|
|
58274
|
+
parse_destroy(default_visibility(status), status, "#spawn");
|
|
58202
58275
|
} else {
|
|
58203
|
-
add_error(status, `Expected #init or #
|
|
58276
|
+
add_error(status, `Expected #init, #destroy or #spawn`, get_index(status));
|
|
58204
58277
|
consume(status);
|
|
58205
58278
|
}
|
|
58206
58279
|
break;
|
|
@@ -58447,10 +58520,6 @@ function resolve_linked_types(source, library, file_path) {
|
|
|
58447
58520
|
if (library.types.has(token.value) && !BASE_TYPES.includes(token.value)) needed.add(token.value);
|
|
58448
58521
|
else if (library.functions.has(token.value) && !user_declared_funcs.has(token.value)) needed.add(token.value);
|
|
58449
58522
|
}
|
|
58450
|
-
if (tokens.some((t) => t.value === "Thread" || t.value === "Fiber" || t.value === "async")) {
|
|
58451
|
-
needed.add("Task");
|
|
58452
|
-
needed.add("Sendable");
|
|
58453
|
-
}
|
|
58454
58523
|
if (tokens.some((t) => t.value === "async")) needed.add("Nursery");
|
|
58455
58524
|
const resolved = resolve_types_with_deps(needed, library, is_library_file ? file_path : void 0);
|
|
58456
58525
|
if (!resolved) return source;
|