nomen-lang 0.5.0 → 0.6.1
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 +116 -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 +176 -58
- 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 +1201 -1101
- package/package.json +1 -1
- package/src/test.ts +22 -4
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,8 @@ 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;
|
|
19245
|
+
const old_variable_types = status.variable_types;
|
|
19136
19246
|
status.function_param_regs = /* @__PURE__ */ new Map();
|
|
19137
19247
|
status.function_param_vars = /* @__PURE__ */ new Set();
|
|
19138
19248
|
status.function_array_params = /* @__PURE__ */ new Set();
|
|
@@ -19140,6 +19250,8 @@ function build_function_node$1(node, status) {
|
|
|
19140
19250
|
status.ref_class_slots = /* @__PURE__ */ new Map();
|
|
19141
19251
|
status.function_struct_param_slots = /* @__PURE__ */ new Set();
|
|
19142
19252
|
status.function_param_types = /* @__PURE__ */ new Map();
|
|
19253
|
+
status.function_local_types = /* @__PURE__ */ new Map();
|
|
19254
|
+
status.variable_types = /* @__PURE__ */ new Map();
|
|
19143
19255
|
const old_variadic_params_aarch64 = status.function_variadic_params;
|
|
19144
19256
|
const old_view_params = status.function_view_params;
|
|
19145
19257
|
status.function_variadic_params = /* @__PURE__ */ new Set();
|
|
@@ -19387,6 +19499,8 @@ function build_function_node$1(node, status) {
|
|
|
19387
19499
|
}
|
|
19388
19500
|
const old_force_heap = status.force_heap_strings;
|
|
19389
19501
|
status.force_heap_strings = scan_force_heap_strings(node.statements, status.structs);
|
|
19502
|
+
const old_reassigned_vars = status.reassigned_vars;
|
|
19503
|
+
status.reassigned_vars = scan_reassigned_vars(node.statements);
|
|
19390
19504
|
const old_buffer_data_cache = status.buffer_data_cache;
|
|
19391
19505
|
status.buffer_data_cache = void 0;
|
|
19392
19506
|
const old_array_ptr_cache = status.array_ptr_cache;
|
|
@@ -19421,6 +19535,7 @@ function build_function_node$1(node, status) {
|
|
|
19421
19535
|
status.buffer_data_cache = old_buffer_data_cache;
|
|
19422
19536
|
status.array_ptr_cache = old_array_ptr_cache;
|
|
19423
19537
|
status.force_heap_strings = old_force_heap;
|
|
19538
|
+
status.reassigned_vars = old_reassigned_vars;
|
|
19424
19539
|
const loop_regs_used = status.callee_saved_regs_used ? [...status.callee_saved_regs_used].sort() : [];
|
|
19425
19540
|
status.callee_saved_regs_used = old_callee_saved_regs;
|
|
19426
19541
|
status.register_allocations = old_register_allocations;
|
|
@@ -19544,6 +19659,8 @@ function build_function_node$1(node, status) {
|
|
|
19544
19659
|
status.ref_class_slots = old_ref_class_slots;
|
|
19545
19660
|
status.function_struct_param_slots = old_struct_param_slots;
|
|
19546
19661
|
status.function_param_types = old_function_param_types;
|
|
19662
|
+
status.function_local_types = old_function_local_types;
|
|
19663
|
+
status.variable_types = old_variable_types;
|
|
19547
19664
|
status.function_variadic_params = old_variadic_params_aarch64;
|
|
19548
19665
|
status.function_view_params = old_view_params;
|
|
19549
19666
|
status.function_return_label = old_return_label;
|
|
@@ -19788,10 +19905,7 @@ function resolve_global_const_value(name, status) {
|
|
|
19788
19905
|
function build_struct_node$1(node, status) {
|
|
19789
19906
|
if (node.is_generic) return;
|
|
19790
19907
|
const is_nested = !!status.function_return_label;
|
|
19791
|
-
|
|
19792
|
-
ensure_concurrency_runtime_a64(status);
|
|
19793
|
-
status.used_fibers = true;
|
|
19794
|
-
}
|
|
19908
|
+
for (const func of node.functions) ensure_runtime_for_method_a64(func, status);
|
|
19795
19909
|
let old_code;
|
|
19796
19910
|
if (is_nested) {
|
|
19797
19911
|
old_code = status.code;
|
|
@@ -20172,14 +20286,18 @@ function build_init_function(node, status) {
|
|
|
20172
20286
|
}
|
|
20173
20287
|
emit_typed_store(status, "x1", "x19", offset, get_type_size(field.type, status));
|
|
20174
20288
|
} else if (field.value.node_type === "func_call") {
|
|
20175
|
-
|
|
20289
|
+
const field_struct = status.structs.find((s) => s.name === field.type.name && !s.is_simple_type);
|
|
20290
|
+
if (field_struct) {
|
|
20176
20291
|
build_node(field.value, status);
|
|
20177
20292
|
ensure_newline(status);
|
|
20178
|
-
|
|
20179
|
-
|
|
20180
|
-
|
|
20181
|
-
|
|
20182
|
-
|
|
20293
|
+
if (field_struct.is_class) emit_typed_store(status, "x0", "x19", offset, 8);
|
|
20294
|
+
else {
|
|
20295
|
+
const field_size = get_struct_size(field.type.name, status);
|
|
20296
|
+
const words = Math.ceil(field_size / 8);
|
|
20297
|
+
for (let w = 0; w < words; w++) {
|
|
20298
|
+
emit_asm(status, `ldr x9, [x0, #${w * 8}]\n`);
|
|
20299
|
+
emit_asm(status, `str x9, [x19, #${offset + w * 8}]\n`);
|
|
20300
|
+
}
|
|
20183
20301
|
}
|
|
20184
20302
|
}
|
|
20185
20303
|
} else if (field.value.node_type === "func" && field.type.name === "func") {
|
|
@@ -20390,14 +20508,18 @@ function build_custom_init_function(node, func, status) {
|
|
|
20390
20508
|
}
|
|
20391
20509
|
emit_typed_store(status, "x1", "x19", offset, get_type_size(field.type, status));
|
|
20392
20510
|
} else if (field.value.node_type === "func_call") {
|
|
20393
|
-
|
|
20511
|
+
const field_struct = status.structs.find((s) => s.name === field.type.name && !s.is_simple_type);
|
|
20512
|
+
if (field_struct) {
|
|
20394
20513
|
build_node(field.value, status);
|
|
20395
20514
|
ensure_newline(status);
|
|
20396
|
-
|
|
20397
|
-
|
|
20398
|
-
|
|
20399
|
-
|
|
20400
|
-
|
|
20515
|
+
if (field_struct.is_class) emit_typed_store(status, "x0", "x19", offset, 8);
|
|
20516
|
+
else {
|
|
20517
|
+
const field_size = get_struct_size(field.type.name, status);
|
|
20518
|
+
const words = Math.ceil(field_size / 8);
|
|
20519
|
+
for (let w = 0; w < words; w++) {
|
|
20520
|
+
emit_asm(status, `ldr x9, [x0, #${w * 8}]\n`);
|
|
20521
|
+
emit_asm(status, `str x9, [x19, #${offset + w * 8}]\n`);
|
|
20522
|
+
}
|
|
20401
20523
|
}
|
|
20402
20524
|
}
|
|
20403
20525
|
}
|
|
@@ -20451,6 +20573,7 @@ function build_custom_init_function(node, func, status) {
|
|
|
20451
20573
|
}
|
|
20452
20574
|
function build_struct_functions$1(node, status) {
|
|
20453
20575
|
for (const func of node.functions) {
|
|
20576
|
+
if (func.name === "#spawn") continue;
|
|
20454
20577
|
if (func.name === "#init" && !func.has_body) continue;
|
|
20455
20578
|
if (func.name === "#init" && func.has_body) {
|
|
20456
20579
|
if (!check_c_fallback(func, node.name, status)) build_custom_init_function(node, func, status);
|
|
@@ -20481,6 +20604,7 @@ function build_struct_functions$1(node, status) {
|
|
|
20481
20604
|
const old_param_vars = status.function_param_vars;
|
|
20482
20605
|
const old_return_label = status.function_return_label;
|
|
20483
20606
|
const old_force_heap = status.force_heap_strings;
|
|
20607
|
+
const old_reassigned_vars = status.reassigned_vars;
|
|
20484
20608
|
const old_function_name = status.current_function_name;
|
|
20485
20609
|
const old_register_allocations = status.register_allocations;
|
|
20486
20610
|
const old_nir_site_allocs = status.nir_site_allocs;
|
|
@@ -20572,7 +20696,7 @@ function build_struct_functions$1(node, status) {
|
|
|
20572
20696
|
slot_idx += 2;
|
|
20573
20697
|
continue;
|
|
20574
20698
|
}
|
|
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) {
|
|
20699
|
+
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
20700
|
const saved_reg = callee_saved[callee_idx++];
|
|
20577
20701
|
if (saved_reg !== "x19" || !needs_x19) emit_asm(status, `str ${saved_reg}, [sp, #-16]!\n`);
|
|
20578
20702
|
if (slot_idx < 8) emit_asm(status, `mov ${saved_reg}, ${param_regs[slot_idx]}\n`);
|
|
@@ -20650,7 +20774,7 @@ function build_struct_functions$1(node, status) {
|
|
|
20650
20774
|
second_slot_idx += 2;
|
|
20651
20775
|
continue;
|
|
20652
20776
|
}
|
|
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))) {
|
|
20777
|
+
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
20778
|
const is_ref = param.type.is_ref;
|
|
20655
20779
|
const size = is_ref ? 8 : aarch64_size(param.type.name);
|
|
20656
20780
|
const offset = allocate_stack_space(status, size, is_ref ? 8 : size);
|
|
@@ -20735,6 +20859,7 @@ function build_struct_functions$1(node, status) {
|
|
|
20735
20859
|
}
|
|
20736
20860
|
}
|
|
20737
20861
|
status.force_heap_strings = scan_force_heap_strings(func.statements, status.structs);
|
|
20862
|
+
status.reassigned_vars = scan_reassigned_vars(func.statements);
|
|
20738
20863
|
status.buffer_data_cache = void 0;
|
|
20739
20864
|
status.array_ptr_cache = void 0;
|
|
20740
20865
|
const moved_before = new Set(status.moved ?? []);
|
|
@@ -20820,6 +20945,7 @@ function build_struct_functions$1(node, status) {
|
|
|
20820
20945
|
status.function_view_params = old_view_params;
|
|
20821
20946
|
status.function_return_label = old_return_label;
|
|
20822
20947
|
status.force_heap_strings = old_force_heap;
|
|
20948
|
+
status.reassigned_vars = old_reassigned_vars;
|
|
20823
20949
|
status.struct_return_buffer = void 0;
|
|
20824
20950
|
status.function_return_type = void 0;
|
|
20825
20951
|
status.current_function_name = old_function_name;
|
|
@@ -22070,6 +22196,7 @@ function resolve_string_value(node, status) {
|
|
|
22070
22196
|
if (node.node_type === "value") {
|
|
22071
22197
|
const val = node.value;
|
|
22072
22198
|
if (val.startsWith("\"") && val.endsWith("\"")) return val;
|
|
22199
|
+
if (status.reassigned_vars?.has(val)) return null;
|
|
22073
22200
|
const decl = status.scoped_declarations.find((d) => d.name === val);
|
|
22074
22201
|
if (decl && decl.value) return resolve_string_value(decl.value, status);
|
|
22075
22202
|
}
|
|
@@ -22122,6 +22249,10 @@ function nir_array_element(nir, arr, i) {
|
|
|
22122
22249
|
return nir?.kind === "call" && nir.node === arr ? nir.facts.args[i] : void 0;
|
|
22123
22250
|
}
|
|
22124
22251
|
function build_declaration_node$1(node, status, nir_init, nir_swap) {
|
|
22252
|
+
if (node.name && node.type?.name) {
|
|
22253
|
+
if (!status.function_local_types) status.function_local_types = /* @__PURE__ */ new Map();
|
|
22254
|
+
status.function_local_types.set(node.name, node.type.name);
|
|
22255
|
+
}
|
|
22125
22256
|
hoist_field_overrides(node.value, build_node, status, "", node.name);
|
|
22126
22257
|
status.last_result_is_heap = false;
|
|
22127
22258
|
const prev_heap = status.last_result_is_heap;
|
|
@@ -23807,6 +23938,7 @@ function promote_loop_locals(status, scoped_declarations, sources, options) {
|
|
|
23807
23938
|
if (!ptype.name || !SCALAR_TYPES.includes(ptype.name) || ptype.is_array || ptype.is_view || ptype.is_ref || ptype.is_nullable) continue;
|
|
23808
23939
|
type_name = ptype.name;
|
|
23809
23940
|
}
|
|
23941
|
+
if (status.function_local_types?.get(name) === "string") continue;
|
|
23810
23942
|
}
|
|
23811
23943
|
let offset = status.stack_offsets?.get(name);
|
|
23812
23944
|
if (offset === void 0) {
|
|
@@ -24250,18 +24382,24 @@ function build_if_else_node$1(node, status, nir) {
|
|
|
24250
24382
|
const pre_cache = status.buffer_data_cache;
|
|
24251
24383
|
const pre_array_cache = status.array_ptr_cache;
|
|
24252
24384
|
if (node.else_branch) {
|
|
24385
|
+
const then_frame = enter_scope_frame(status);
|
|
24253
24386
|
status.buffer_data_cache = new Map(pre_cache);
|
|
24254
24387
|
status.array_ptr_cache = new Map(pre_array_cache);
|
|
24255
24388
|
build_block_with_cursor$1(node.if_branch, nir?.then_branch, status);
|
|
24389
|
+
exit_scope_frame(status, then_frame);
|
|
24256
24390
|
emit_asm(status, `b end_${label}\n`);
|
|
24257
24391
|
emit_asm(status, `else_${label}:\n`);
|
|
24392
|
+
const else_frame = enter_scope_frame(status);
|
|
24258
24393
|
status.buffer_data_cache = new Map(pre_cache);
|
|
24259
24394
|
status.array_ptr_cache = new Map(pre_array_cache);
|
|
24260
24395
|
build_block_with_cursor$1(node.else_branch, nir?.else_branch, status);
|
|
24396
|
+
exit_scope_frame(status, else_frame);
|
|
24261
24397
|
} else if (node.if_branch) {
|
|
24398
|
+
const then_frame = enter_scope_frame(status);
|
|
24262
24399
|
status.buffer_data_cache = new Map(pre_cache);
|
|
24263
24400
|
status.array_ptr_cache = new Map(pre_array_cache);
|
|
24264
24401
|
build_block_with_cursor$1(node.if_branch, nir?.then_branch, status);
|
|
24402
|
+
exit_scope_frame(status, then_frame);
|
|
24265
24403
|
}
|
|
24266
24404
|
status.buffer_data_cache = pre_cache;
|
|
24267
24405
|
status.array_ptr_cache = pre_array_cache;
|
|
@@ -24483,6 +24621,7 @@ function build_return_node$1(node, status, nir_value) {
|
|
|
24483
24621
|
emit_asm(status, `ldr x8, [x29, #${status.return_buffer_stack_offset}]\n`);
|
|
24484
24622
|
emit_asm(status, `str xzr, [x8, #${struct_size}]\n`);
|
|
24485
24623
|
}
|
|
24624
|
+
emit_nursery_joins_on_return_a64(status);
|
|
24486
24625
|
const finalized = status.moved ?? /* @__PURE__ */ new Set();
|
|
24487
24626
|
for (const decl of all_scope_frames(status).flat()) {
|
|
24488
24627
|
if (finalized.has(decl.name)) {
|
|
@@ -24502,6 +24641,7 @@ function build_return_node$1(node, status, nir_value) {
|
|
|
24502
24641
|
emit_asm(status, `mov x0, #0\n`);
|
|
24503
24642
|
emit_var_store(status, "x0", status.return_assign, size);
|
|
24504
24643
|
} else if (status.function_return_label) {
|
|
24644
|
+
emit_nursery_joins_on_return_a64(status);
|
|
24505
24645
|
const finalized = status.moved ?? /* @__PURE__ */ new Set();
|
|
24506
24646
|
for (const decl of all_scope_frames(status).flat()) {
|
|
24507
24647
|
if (finalized.has(decl.name)) {
|
|
@@ -24620,7 +24760,7 @@ function build_return_node$1(node, status, nir_value) {
|
|
|
24620
24760
|
const raw_value = node.value.value;
|
|
24621
24761
|
const is_str_lit = raw_value.length >= 2 && raw_value.startsWith("\"") && raw_value.endsWith("\"");
|
|
24622
24762
|
const is_numeric = /^(\+|-)?\d+$/.test(raw_value);
|
|
24623
|
-
if (!is_str_lit && !is_numeric && !status.heap_strings?.has(raw_value)) needs_borrow_strdup = true;
|
|
24763
|
+
if (!is_str_lit && !is_numeric && raw_value !== "null" && !status.heap_strings?.has(raw_value)) needs_borrow_strdup = true;
|
|
24624
24764
|
} else if (is_container_borrow_access(node.value)) needs_borrow_strdup = true;
|
|
24625
24765
|
else if (node.value.node_type === "access" && node.value.access.node_type === "access_field") needs_borrow_strdup = true;
|
|
24626
24766
|
else if ((node.value.node_type === "match" || node.value.node_type === "switch") && !return_join_owned_string) needs_borrow_strdup = true;
|
|
@@ -24731,6 +24871,7 @@ function build_return_node$1(node, status, nir_value) {
|
|
|
24731
24871
|
if (returns_fat_pair) emit_asm(status, `stp x0, x1, [sp, #-16]!\n`);
|
|
24732
24872
|
else if (returns_float) emit_asm(status, `str d0, [sp, #-16]!\n`);
|
|
24733
24873
|
else emit_asm(status, `str x0, [sp, #-16]!\n`);
|
|
24874
|
+
emit_nursery_joins_on_return_a64(status);
|
|
24734
24875
|
for (const decl of all_scope_frames(status).flat()) {
|
|
24735
24876
|
if (finalized.has(decl.name)) {
|
|
24736
24877
|
release_heap_string_fields(status, decl.name, decl.type.name);
|
|
@@ -26485,7 +26626,7 @@ function emit_stmt_dispatch(child, index, statements, status) {
|
|
|
26485
26626
|
const eval_node = nstmt.expr.node;
|
|
26486
26627
|
if (eval_node.node_type === "access") {
|
|
26487
26628
|
const inner = eval_node.access;
|
|
26488
|
-
if (inner.node_type === "access_func" &&
|
|
26629
|
+
if (inner.node_type === "access_func" && inner.is_nursery_spawn) inner.is_statement = true;
|
|
26489
26630
|
}
|
|
26490
26631
|
const restore_eval = apply_forward_use(ctx.use_sites, nstmt.expr.node);
|
|
26491
26632
|
try {
|
|
@@ -26891,7 +27032,7 @@ function emit_expr_from_nir$1(expr, status) {
|
|
|
26891
27032
|
function build_async_block_node$1(node, status, nir) {
|
|
26892
27033
|
const id = status.spawn_counter ?? 0;
|
|
26893
27034
|
status.spawn_counter = id + 1;
|
|
26894
|
-
|
|
27035
|
+
ensure_concurrency_runtime_a64(status);
|
|
26895
27036
|
const futures_off = allocate_stack_space(status, 8, 8);
|
|
26896
27037
|
const count_off = allocate_stack_space(status, 8, 8);
|
|
26897
27038
|
const cap_off = allocate_stack_space(status, 8, 8);
|
|
@@ -26913,28 +27054,6 @@ function build_async_block_node$1(node, status, nir) {
|
|
|
26913
27054
|
let deadline_off;
|
|
26914
27055
|
if (node.timeout) {
|
|
26915
27056
|
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
27057
|
emit_asm(status, `// Compute deadline: now + timeout_ms\n`);
|
|
26939
27058
|
build_node(node.timeout, status);
|
|
26940
27059
|
ensure_newline(status);
|
|
@@ -26955,11 +27074,57 @@ function build_async_block_node$1(node, status, nir) {
|
|
|
26955
27074
|
emit_asm(status, `add x0, x1, x0\n`);
|
|
26956
27075
|
emit_asm(status, `str x0, [x29, #${deadline_off}]\n`);
|
|
26957
27076
|
}
|
|
26958
|
-
|
|
26959
|
-
|
|
26960
|
-
|
|
26961
|
-
|
|
26962
|
-
|
|
27077
|
+
if (!status.nursery_offsets) status.nursery_offsets = /* @__PURE__ */ new Map();
|
|
27078
|
+
status.nursery_offsets.set(id, {
|
|
27079
|
+
futures_off,
|
|
27080
|
+
count_off,
|
|
27081
|
+
cap_off,
|
|
27082
|
+
deadline_off
|
|
27083
|
+
});
|
|
27084
|
+
if (!status.nursery_meta) status.nursery_meta = /* @__PURE__ */ new Map();
|
|
27085
|
+
status.nursery_meta.set(id, {
|
|
27086
|
+
mode: node.mode ?? "all",
|
|
27087
|
+
has_deadline: !!node.timeout
|
|
27088
|
+
});
|
|
27089
|
+
status.nursery_stack ??= [];
|
|
27090
|
+
status.nursery_stack.push(id);
|
|
27091
|
+
const old_scoped_declarations = status.scoped_declarations;
|
|
27092
|
+
status.scoped_declarations = [];
|
|
27093
|
+
build_block_with_cursor$1(node, nir?.body, status);
|
|
27094
|
+
status.scoped_declarations = old_scoped_declarations;
|
|
27095
|
+
status.nursery_stack.pop();
|
|
27096
|
+
emit_asm(status, `// nursery ${id}: join all futures\n`);
|
|
27097
|
+
emit_nursery_join_a64(status, id, `__nursery_${id}`);
|
|
27098
|
+
status.nursery_offsets.delete(id);
|
|
27099
|
+
status.nursery_meta?.delete(id);
|
|
27100
|
+
}
|
|
27101
|
+
/**
|
|
27102
|
+
* Emit one nursery's join sequence in assembly: the race-mode wait (if any),
|
|
27103
|
+
* the wait+release loop over every registered future, and the futures-list
|
|
27104
|
+
* free. Shared by the block exit and the RETURN path — a `return` inside an
|
|
27105
|
+
* `async { }` block must route through the join (the structured-concurrency
|
|
27106
|
+
* contract). The nursery's per-invocation state lives at this function's
|
|
27107
|
+
* stack frame (offsets from x29), so a return site inside the block reaches
|
|
27108
|
+
* the same state. `label_prefix` must be unique per emission: the block exit
|
|
27109
|
+
* uses `__nursery_<id>`, a return site `__nursery_<id>_retjoin<N>` (the
|
|
27110
|
+
* block-exit labels would collide with a second loop in the same function).
|
|
27111
|
+
* Clobbers x0-x3, x20, x22, x23 (backend scratch — nothing live across a
|
|
27112
|
+
* join).
|
|
27113
|
+
*/
|
|
27114
|
+
function emit_nursery_join_a64(status, id, label_prefix) {
|
|
27115
|
+
const offsets = status.nursery_offsets?.get(id);
|
|
27116
|
+
if (!offsets) return;
|
|
27117
|
+
const { futures_off, count_off, deadline_off } = offsets;
|
|
27118
|
+
const is_race = (status.nursery_meta?.get(id) ?? {
|
|
27119
|
+
mode: "all",
|
|
27120
|
+
has_deadline: false
|
|
27121
|
+
}).mode === "race";
|
|
27122
|
+
const loop_start = `${label_prefix}_join_start`;
|
|
27123
|
+
const loop_end = `${label_prefix}_join_end`;
|
|
27124
|
+
const loop_release = `${label_prefix}_release`;
|
|
27125
|
+
const no_list = `${label_prefix}_no_list`;
|
|
27126
|
+
emit_asm(status, `ldr x20, [x29, #${futures_off}]\n`);
|
|
27127
|
+
emit_asm(status, `ldr w22, [x29, #${count_off}]\n`);
|
|
26963
27128
|
if (is_race) {
|
|
26964
27129
|
emit_asm(status, `mov x0, x20\n`);
|
|
26965
27130
|
emit_asm(status, `mov x1, x22\n`);
|
|
@@ -26967,6 +27132,7 @@ function build_async_block_node$1(node, status, nir) {
|
|
|
26967
27132
|
else emit_asm(status, `mov x2, #0\n`);
|
|
26968
27133
|
emit_asm(status, `bl ___nomen_nursery_race_wait\n`);
|
|
26969
27134
|
}
|
|
27135
|
+
emit_asm(status, `mov x23, xzr\n`);
|
|
26970
27136
|
emit_asm(status, `${loop_start}:\n`);
|
|
26971
27137
|
emit_asm(status, `cmp x23, x22\n`);
|
|
26972
27138
|
emit_asm(status, `b.ge ${loop_end}\n`);
|
|
@@ -26991,9 +27157,28 @@ function build_async_block_node$1(node, status, nir) {
|
|
|
26991
27157
|
emit_asm(status, `b ${loop_start}\n`);
|
|
26992
27158
|
emit_asm(status, `${loop_end}:\n`);
|
|
26993
27159
|
emit_asm(status, `ldr x0, [x29, #${futures_off}]\n`);
|
|
26994
|
-
emit_asm(status, `cbz x0,
|
|
27160
|
+
emit_asm(status, `cbz x0, ${no_list}\n`);
|
|
26995
27161
|
emit_asm(status, `bl _free\n`);
|
|
26996
|
-
emit_asm(status,
|
|
27162
|
+
emit_asm(status, `${no_list}:\n`);
|
|
27163
|
+
}
|
|
27164
|
+
let retjoin_counter = 0;
|
|
27165
|
+
/**
|
|
27166
|
+
* Emit the join sequence for every nursery the current `return` must route
|
|
27167
|
+
* through — innermost first. Only nurseries belonging to the CURRENT
|
|
27168
|
+
* function (stack depth >= the function's entry depth) apply: a lambda
|
|
27169
|
+
* defined inside an `async { }` block inherits the block's id on
|
|
27170
|
+
* nursery_stack (for lexical spawn captures), but its `return` exits the
|
|
27171
|
+
* lambda, whose frame has no nursery state. The return value must already
|
|
27172
|
+
* be parked (the join's `bl`s clobber x0/x1).
|
|
27173
|
+
*/
|
|
27174
|
+
function emit_nursery_joins_on_return_a64(status) {
|
|
27175
|
+
if (!status.nursery_stack?.length) return;
|
|
27176
|
+
const base = status.function_nursery_depth ?? 0;
|
|
27177
|
+
for (let i = status.nursery_stack.length - 1; i >= base; i--) {
|
|
27178
|
+
const id = status.nursery_stack[i];
|
|
27179
|
+
emit_asm(status, `// return inside async block: join nursery ${id} before leaving\n`);
|
|
27180
|
+
emit_nursery_join_a64(status, id, `__nursery_${id}_retjoin${retjoin_counter++}`);
|
|
27181
|
+
}
|
|
26997
27182
|
}
|
|
26998
27183
|
//#endregion
|
|
26999
27184
|
//#region ../src/build_aarch64/build_break_node.ts
|
|
@@ -27482,6 +27667,7 @@ function build_inline_method(struct_node, func, status) {
|
|
|
27482
27667
|
const old_param_vars = status.function_param_vars;
|
|
27483
27668
|
status.stack_offsets = /* @__PURE__ */ new Map();
|
|
27484
27669
|
const old_param_types = status.function_param_types;
|
|
27670
|
+
const old_local_types = status.function_local_types;
|
|
27485
27671
|
const old_return_label = status.function_return_label;
|
|
27486
27672
|
const old_ref_params = status.function_ref_params;
|
|
27487
27673
|
const old_struct_return_buffer = status.struct_return_buffer;
|
|
@@ -27548,6 +27734,7 @@ function build_inline_method(struct_node, func, status) {
|
|
|
27548
27734
|
status.function_ref_params = /* @__PURE__ */ new Set();
|
|
27549
27735
|
status.function_param_types = /* @__PURE__ */ new Map();
|
|
27550
27736
|
for (const p of func.params) if (p.type) status.function_param_types.set(p.name, p.type);
|
|
27737
|
+
status.function_local_types = /* @__PURE__ */ new Map();
|
|
27551
27738
|
if (needs_x19) status.function_param_regs.set("self", "x19");
|
|
27552
27739
|
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
27740
|
const saved_stack_slots = [];
|
|
@@ -27628,6 +27815,7 @@ function build_inline_method(struct_node, func, status) {
|
|
|
27628
27815
|
status.function_param_regs = old_param_regs;
|
|
27629
27816
|
status.function_param_vars = old_param_vars;
|
|
27630
27817
|
status.function_param_types = old_param_types;
|
|
27818
|
+
status.function_local_types = old_local_types;
|
|
27631
27819
|
status.function_ref_params = old_ref_params;
|
|
27632
27820
|
status.function_return_label = old_return_label;
|
|
27633
27821
|
status.stack_offsets = old_stack_offsets;
|
|
@@ -27658,6 +27846,7 @@ function build_inline_function(func, status) {
|
|
|
27658
27846
|
const old_param_vars = status.function_param_vars;
|
|
27659
27847
|
status.stack_offsets = /* @__PURE__ */ new Map();
|
|
27660
27848
|
const old_param_types = status.function_param_types;
|
|
27849
|
+
const old_local_types = status.function_local_types;
|
|
27661
27850
|
const old_return_label = status.function_return_label;
|
|
27662
27851
|
const old_ref_params = status.function_ref_params;
|
|
27663
27852
|
const old_struct_return_buffer = status.struct_return_buffer;
|
|
@@ -27716,6 +27905,7 @@ function build_inline_function(func, status) {
|
|
|
27716
27905
|
status.function_ref_params = /* @__PURE__ */ new Set();
|
|
27717
27906
|
status.function_param_types = /* @__PURE__ */ new Map();
|
|
27718
27907
|
for (const p of func.params) if (p.type) status.function_param_types.set(p.name, p.type);
|
|
27908
|
+
status.function_local_types = /* @__PURE__ */ new Map();
|
|
27719
27909
|
const saved_stack_slots = [];
|
|
27720
27910
|
const inline_reloads = [];
|
|
27721
27911
|
const inline_push_slots = [];
|
|
@@ -27785,6 +27975,7 @@ function build_inline_function(func, status) {
|
|
|
27785
27975
|
status.function_param_regs = old_param_regs;
|
|
27786
27976
|
status.function_param_vars = old_param_vars;
|
|
27787
27977
|
status.function_param_types = old_param_types;
|
|
27978
|
+
status.function_local_types = old_local_types;
|
|
27788
27979
|
status.function_ref_params = old_ref_params;
|
|
27789
27980
|
status.function_return_label = old_return_label;
|
|
27790
27981
|
status.stack_offsets = old_stack_offsets;
|
|
@@ -28217,7 +28408,7 @@ function build_function_call_node$1(node, status) {
|
|
|
28217
28408
|
let arg_is_class = false;
|
|
28218
28409
|
if (arg_name) {
|
|
28219
28410
|
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);
|
|
28411
|
+
arg_is_class = !!tn && (!!status.structs.find((s) => s.name === tn && s.is_class) || !!status.traits.find((t) => t.name === tn));
|
|
28221
28412
|
}
|
|
28222
28413
|
const ref_param_slot = arg_name !== void 0 ? status.ref_class_slots?.get(arg_name) : void 0;
|
|
28223
28414
|
if (ref_param_slot !== void 0) emit_ref_class_param_slot(status, ref_param_slot, arg_name, ref_class_param_reload);
|
|
@@ -28286,11 +28477,11 @@ function build_function_call_node$1(node, status) {
|
|
|
28286
28477
|
}
|
|
28287
28478
|
}
|
|
28288
28479
|
}
|
|
28289
|
-
if (is_struct &&
|
|
28480
|
+
if (is_struct && is_struct.is_class) emit_asm(status, `ldr x0, [sp]\n`);
|
|
28481
|
+
else if (is_struct && status.struct_return_buffer) {
|
|
28290
28482
|
if (status.return_buffer_stack_offset !== void 0) emit_asm(status, `ldr x8, [x29, #${status.return_buffer_stack_offset}]\n`);
|
|
28291
28483
|
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) {
|
|
28484
|
+
} else if (is_struct && !is_struct.is_class) {
|
|
28294
28485
|
const temp_addr = `_temp_${temp_counter - 1}`;
|
|
28295
28486
|
emit_asm(status, `add x0, x29, #${status.stack_offsets.get(temp_addr)}\n`);
|
|
28296
28487
|
} else if (!is_struct && node.type?.name && !status.call_x8_preset) {
|
|
@@ -28352,16 +28543,17 @@ function build_function_call_node$1(node, status) {
|
|
|
28352
28543
|
}
|
|
28353
28544
|
}
|
|
28354
28545
|
build_swap_params(node, status);
|
|
28355
|
-
if (is_struct &&
|
|
28356
|
-
|
|
28357
|
-
|
|
28358
|
-
|
|
28359
|
-
|
|
28546
|
+
if (is_struct && is_struct.is_class) emit_asm(status, `ldr x0, [sp], #16\n`);
|
|
28547
|
+
else if (is_struct && status.struct_return_buffer) {
|
|
28548
|
+
if (status.return_buffer_stack_offset !== void 0) emit_asm(status, `ldr x0, [x29, #${status.return_buffer_stack_offset}]\n`);
|
|
28549
|
+
else emit_asm(status, `mov x0, ${status.struct_return_buffer}\n`);
|
|
28550
|
+
} else if (is_struct && !status.struct_return_buffer) {
|
|
28551
|
+
const temp_addr = `_temp_${temp_counter - 1}`;
|
|
28552
|
+
const offset = status.stack_offsets.get(temp_addr);
|
|
28553
|
+
emit_asm(status, `add x0, x29, #${offset}\n`);
|
|
28554
|
+
if (node.field_overrides?.length) {
|
|
28555
|
+
emit_field_overrides(temp_addr, node, build_node, status);
|
|
28360
28556
|
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
28557
|
}
|
|
28366
28558
|
}
|
|
28367
28559
|
if (node.name.startsWith("_string_interpolate_")) {
|
|
@@ -28752,6 +28944,14 @@ struct nomen_future {
|
|
|
28752
28944
|
// The fiber whose task this future is (set at fiber spawn). Cancellation
|
|
28753
28945
|
// uses it to wake a fiber parked on a primitive waitq.
|
|
28754
28946
|
struct nomen_fiber *owning_fiber;
|
|
28947
|
+
// 1 when the result slot holds a FAT string (set at construction, which
|
|
28948
|
+
// knows the wrapped call's return type). The last release frees an
|
|
28949
|
+
// unconsumed string buffer from the cell: result() zeroes the cell when
|
|
28950
|
+
// it moves the value out, and Task's #destroy zeroes after its own
|
|
28951
|
+
// free, so a non-NULL ptr at the last release means "never consumed".
|
|
28952
|
+
// Appended LAST: raw aarch64 bodies read refs/result_slot/owner_args at
|
|
28953
|
+
// fixed offsets (#116/#128/#136), which must not shift.
|
|
28954
|
+
int slot_fat;
|
|
28755
28955
|
};
|
|
28756
28956
|
// Fiber seam (defined in FIBER_HEADER, appended after this block). Declared
|
|
28757
28957
|
// here — after struct nomen_future is complete — so the prototypes bind to
|
|
@@ -28877,6 +29077,19 @@ static void __nomen_future_release(struct nomen_future *f) {
|
|
|
28877
29077
|
pthread_mutex_destroy(&f->mu);
|
|
28878
29078
|
pthread_cond_destroy(&f->cv);
|
|
28879
29079
|
free(f->cancel_flag);
|
|
29080
|
+
// An unconsumed fat-string result: free the buffer the trampoline
|
|
29081
|
+
// stored (race-mode and fire-and-forget tasks never hand it to a
|
|
29082
|
+
// consumer). Every consumer path zeroes the cell first — result()
|
|
29083
|
+
// on move-out, Task's #destroy after its own free — so a non-NULL
|
|
29084
|
+
// ptr here is unambiguous and the free happens exactly once.
|
|
29085
|
+
if (f->slot_fat && f->result_slot) {
|
|
29086
|
+
unsigned long long *_s = (unsigned long long *)f->result_slot;
|
|
29087
|
+
if (_s[0]) {
|
|
29088
|
+
free((void *)_s[0]);
|
|
29089
|
+
_s[0] = 0;
|
|
29090
|
+
_s[1] = 0;
|
|
29091
|
+
}
|
|
29092
|
+
}
|
|
28880
29093
|
free(f->result_slot);
|
|
28881
29094
|
// The task closure is owned by the future: disposing it here (the
|
|
28882
29095
|
// last release, ordered after every use — trampoline, Task destroy,
|
|
@@ -29013,7 +29226,11 @@ static void __nomen_future_cancel(struct nomen_future *f) {
|
|
|
29013
29226
|
// schedule() only queues PARKED fibers, so a double wake is harmless.
|
|
29014
29227
|
if (f->owning_fiber) __nomen_fiber_schedule(f->owning_fiber);
|
|
29015
29228
|
}
|
|
29229
|
+
// Non-blocking completion peek (Task.is_done): 1 once the future's task has
|
|
29230
|
+
// finished, 0 while it may still be running (and for a NULL — a consumed or
|
|
29231
|
+
// never-launched handle).
|
|
29016
29232
|
static int __nomen_future_is_done(struct nomen_future *f) {
|
|
29233
|
+
if (!f) return 0;
|
|
29017
29234
|
pthread_mutex_lock(&f->mu);
|
|
29018
29235
|
int d = f->done;
|
|
29019
29236
|
pthread_mutex_unlock(&f->mu);
|
|
@@ -29885,95 +30102,6 @@ static int __nomen_deadlock_check_worker(void) {
|
|
|
29885
30102
|
}
|
|
29886
30103
|
`;
|
|
29887
30104
|
/**
|
|
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
30105
|
* Resolve each spawn argument's C type. Classes/traits are pointers;
|
|
29978
30106
|
* primitives and by-value structs use c_type's output directly. Generic
|
|
29979
30107
|
* instantiations (e.g. Channel<uint64>) use the monomorphized C name
|
|
@@ -30058,98 +30186,41 @@ function find_spawn_callee(name, status) {
|
|
|
30058
30186
|
visit(status.root);
|
|
30059
30187
|
return found;
|
|
30060
30188
|
}
|
|
30061
|
-
//#endregion
|
|
30062
|
-
//#region ../src/build_c/build_fiber_spawn.ts
|
|
30063
30189
|
/**
|
|
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.
|
|
30190
|
+
* Whether any raw `#arch` body under this node
|
|
30191
|
+
* references the concurrency runtime (`__nomen_*` symbols — pool, futures,
|
|
30192
|
+
* fiber scheduler, nursery tracking, netpoller). The runtime is the
|
|
30193
|
+
* LIBRARY's dependency: a method whose raw body drives it pulls it in,
|
|
30194
|
+
* keyed on body CONTENT — never on a type name.
|
|
30086
30195
|
*/
|
|
30196
|
+
function raw_bodies_reference_runtime(node) {
|
|
30197
|
+
if (!node || typeof node !== "object") return false;
|
|
30198
|
+
if (node.node_type === "raw") {
|
|
30199
|
+
const raw = node.value;
|
|
30200
|
+
return typeof raw === "string" && raw.includes("__nomen_");
|
|
30201
|
+
}
|
|
30202
|
+
for (const key of Object.keys(node)) {
|
|
30203
|
+
if (key === "parent" || key === "scope") continue;
|
|
30204
|
+
const v = node[key];
|
|
30205
|
+
if (Array.isArray(v)) {
|
|
30206
|
+
for (const item of v) if (raw_bodies_reference_runtime(item)) return true;
|
|
30207
|
+
} else if (v && typeof v === "object" && "node_type" in v) {
|
|
30208
|
+
if (raw_bodies_reference_runtime(v)) return true;
|
|
30209
|
+
}
|
|
30210
|
+
}
|
|
30211
|
+
return false;
|
|
30212
|
+
}
|
|
30087
30213
|
/**
|
|
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).
|
|
30214
|
+
* Pull the concurrency runtime in when this struct method's raw bodies
|
|
30215
|
+
* reference it (deduped by the ensure flags). Replaces the old per-type-name
|
|
30216
|
+
* gates (CONCURRENCY_TYPES / the aarch64 Fiber||Thread check): the runtime
|
|
30217
|
+
* dependency now belongs to the library code itself.
|
|
30097
30218
|
*/
|
|
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`;
|
|
30219
|
+
function ensure_runtime_for_method(func, status) {
|
|
30220
|
+
for (const stmt of func.statements ?? []) if (raw_bodies_reference_runtime(stmt)) {
|
|
30221
|
+
ensure_concurrency_runtime(status);
|
|
30222
|
+
return;
|
|
30146
30223
|
}
|
|
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
30224
|
}
|
|
30154
30225
|
//#endregion
|
|
30155
30226
|
//#region ../src/build_c/build_nursery_spawn.ts
|
|
@@ -30174,7 +30245,7 @@ function build_nursery_spawn$1(node, nursery_ptr, status) {
|
|
|
30174
30245
|
const fire_and_forget = !!node.is_statement;
|
|
30175
30246
|
const refs = fire_and_forget ? 2 : 3;
|
|
30176
30247
|
ensure_concurrency_runtime(status);
|
|
30177
|
-
const t_arg = spawn_result_type_arg$
|
|
30248
|
+
const t_arg = spawn_result_type_arg$1(node.function_return_type);
|
|
30178
30249
|
const mono_thread = mono_type_name("Thread", [t_arg]);
|
|
30179
30250
|
const mono_task_name = mono_type_name("Task", [t_arg]);
|
|
30180
30251
|
const arg = node.params[0];
|
|
@@ -30211,7 +30282,7 @@ function build_nursery_spawn$1(node, nursery_ptr, status) {
|
|
|
30211
30282
|
}
|
|
30212
30283
|
/** The launch's T as a Type: the stamped wrapped-call return type, with
|
|
30213
30284
|
* void coerced to uint64 (Task's result-slot convention). */
|
|
30214
|
-
function spawn_result_type_arg$
|
|
30285
|
+
function spawn_result_type_arg$1(return_type) {
|
|
30215
30286
|
return return_type?.name && return_type.name !== "void" && return_type.name !== "?" ? new Type(return_type.name) : new Type("uint64");
|
|
30216
30287
|
}
|
|
30217
30288
|
//#endregion
|
|
@@ -31512,7 +31583,8 @@ function build_assignment_node$1(node, status, nir_rhs, nir_swap) {
|
|
|
31512
31583
|
const lhs_decl = status.scoped_declarations.find((d) => d.name === lhs_name);
|
|
31513
31584
|
const lhs_in_class_vars = !!status.class_vars?.has(lhs_name);
|
|
31514
31585
|
const lhs_type = lhs_decl?.type || status.variable_types?.get(lhs_name);
|
|
31515
|
-
const
|
|
31586
|
+
const lhs_mono_name = lhs_type ? mono_type_name(lhs_type) : "";
|
|
31587
|
+
const lhs_struct = lhs_mono_name ? status.structs.find((s) => s.name === lhs_mono_name) : null;
|
|
31516
31588
|
const lhs_is_string = lhs_type?.name === "string";
|
|
31517
31589
|
const lhs_is_class = !!lhs_struct?.is_class || lhs_in_class_vars;
|
|
31518
31590
|
const lhs_trait_class = lhs_scope_hit ? lhs_scope_hit.frame[lhs_scope_hit.index].trait_class_trait : void 0;
|
|
@@ -31580,11 +31652,12 @@ function build_assignment_node$1(node, status, nir_rhs, nir_swap) {
|
|
|
31580
31652
|
}
|
|
31581
31653
|
} else if (lhs_is_class && lhs_struct && !lhs_is_alias && (lhs_decl || lhs_in_class_vars)) {
|
|
31582
31654
|
const temp = `_deferred_${status.label_counter = (status.label_counter ?? 0) + 1}`;
|
|
31583
|
-
|
|
31655
|
+
const destroy_name = lhs_struct.name.startsWith(lhs_type.name) ? lhs_struct.name : lhs_type.name;
|
|
31656
|
+
status.code += `struct ${destroy_name}* ${temp} = ${lhs_name};\n`;
|
|
31584
31657
|
if (!status.deferred_frees) status.deferred_frees = [];
|
|
31585
31658
|
status.deferred_frees.push({
|
|
31586
31659
|
temp,
|
|
31587
|
-
struct_name:
|
|
31660
|
+
struct_name: destroy_name,
|
|
31588
31661
|
is_nullable: !!lhs_type?.is_nullable
|
|
31589
31662
|
});
|
|
31590
31663
|
} else if (lhs_is_class && lhs_is_alias && status.c_alias_owns_flags?.has(lhs_name)) {
|
|
@@ -31930,11 +32003,15 @@ function string_var_owns_heap(status, name) {
|
|
|
31930
32003
|
function build_async_block_node(node, status, nir) {
|
|
31931
32004
|
const id = status.spawn_counter ?? 0;
|
|
31932
32005
|
status.spawn_counter = id + 1;
|
|
31933
|
-
|
|
32006
|
+
ensure_concurrency_runtime(status);
|
|
31934
32007
|
const futures_name = `__nomen_nursery_${id}_futures`;
|
|
31935
32008
|
const count_name = `__nomen_nursery_${id}_count`;
|
|
31936
32009
|
const cap_name = `__nomen_nursery_${id}_cap`;
|
|
31937
|
-
|
|
32010
|
+
if (!status.nursery_meta) status.nursery_meta = /* @__PURE__ */ new Map();
|
|
32011
|
+
status.nursery_meta.set(id, {
|
|
32012
|
+
mode: node.mode ?? "all",
|
|
32013
|
+
has_deadline: !!node.timeout
|
|
32014
|
+
});
|
|
31938
32015
|
status.code += `{\n`;
|
|
31939
32016
|
status.code += `\tstruct nomen_future **${futures_name} = NULL;\n`;
|
|
31940
32017
|
status.code += `\tint ${count_name} = 0;\n`;
|
|
@@ -31966,7 +32043,35 @@ function build_async_block_node(node, status, nir) {
|
|
|
31966
32043
|
status.nursery_stack.push(id);
|
|
31967
32044
|
build_block_with_cursor(node, nir?.body, status);
|
|
31968
32045
|
status.nursery_stack.pop();
|
|
31969
|
-
|
|
32046
|
+
emit_nursery_join_c(status, id);
|
|
32047
|
+
build_auto_free(status);
|
|
32048
|
+
status.code += `}\n`;
|
|
32049
|
+
leave_c_scope(status);
|
|
32050
|
+
status.scoped_declarations = old_scoped_declarations;
|
|
32051
|
+
status.deferred_frees = old_deferred_frees;
|
|
32052
|
+
}
|
|
32053
|
+
/**
|
|
32054
|
+
* Emit one nursery's join sequence: the race-mode wait (if any), the
|
|
32055
|
+
* wait+release loop over every registered future, and the futures-list
|
|
32056
|
+
* free. Shared by the block exit and the RETURN path — a `return` inside an
|
|
32057
|
+
* `async { }` block must route through the join (the structured-concurrency
|
|
32058
|
+
* contract: the block's scoped resources must not die under still-running
|
|
32059
|
+
* tasks, and the per-future release must not be skipped). The names are
|
|
32060
|
+
* deterministic per nursery id, so a return site inside the block can
|
|
32061
|
+
* re-emit the same sequence textually (the block's tracking locals are
|
|
32062
|
+
* still in C scope there).
|
|
32063
|
+
*/
|
|
32064
|
+
function emit_nursery_join_c(status, id, is_return_path = false) {
|
|
32065
|
+
const meta = status.nursery_meta?.get(id) ?? {
|
|
32066
|
+
mode: "all",
|
|
32067
|
+
has_deadline: false
|
|
32068
|
+
};
|
|
32069
|
+
const futures_name = `__nomen_nursery_${id}_futures`;
|
|
32070
|
+
const count_name = `__nomen_nursery_${id}_count`;
|
|
32071
|
+
const idx_name = `__nomen_nursery_${id}_i`;
|
|
32072
|
+
const deadline_var = meta.has_deadline ? `_deadline_ms_${id}` : "";
|
|
32073
|
+
if (is_return_path) status.code += `\t// return inside async block: join nursery ${id} before leaving\n`;
|
|
32074
|
+
const is_race = meta.mode === "race";
|
|
31970
32075
|
if (is_race) status.code += `\t__nomen_nursery_race_wait((struct nomen_future **)${futures_name}, ${count_name}, ${deadline_var ? deadline_var : "0"});\n`;
|
|
31971
32076
|
status.code += `\tfor (int ${idx_name} = 0; ${idx_name} < ${count_name}; ${idx_name}++) {\n`;
|
|
31972
32077
|
status.code += `\t\tstruct nomen_future *_f = (struct nomen_future *)${futures_name}[${idx_name}];\n`;
|
|
@@ -31983,11 +32088,19 @@ function build_async_block_node(node, status, nir) {
|
|
|
31983
32088
|
status.code += `\t\t__nomen_future_release(_f);\n`;
|
|
31984
32089
|
status.code += `\t}\n`;
|
|
31985
32090
|
status.code += `\tfree(${futures_name});\n`;
|
|
31986
|
-
|
|
31987
|
-
|
|
31988
|
-
|
|
31989
|
-
|
|
31990
|
-
|
|
32091
|
+
}
|
|
32092
|
+
/**
|
|
32093
|
+
* Emit the join sequence for every nursery the current `return` must route
|
|
32094
|
+
* through — innermost first. Only nurseries belonging to the CURRENT
|
|
32095
|
+
* function (stack depth >= the function's entry depth) apply: a lambda
|
|
32096
|
+
* defined inside an `async { }` block inherits the block's id on
|
|
32097
|
+
* nursery_stack (for lexical spawn captures), but its `return` exits the
|
|
32098
|
+
* lambda, whose frame has no nursery state.
|
|
32099
|
+
*/
|
|
32100
|
+
function emit_nursery_joins_on_return_c(status) {
|
|
32101
|
+
if (!status.nursery_stack?.length) return;
|
|
32102
|
+
const base = status.function_nursery_depth ?? 0;
|
|
32103
|
+
for (let i = status.nursery_stack.length - 1; i >= base; i--) emit_nursery_join_c(status, status.nursery_stack[i], true);
|
|
31991
32104
|
}
|
|
31992
32105
|
//#endregion
|
|
31993
32106
|
//#region ../src/build_c/build_array_values_node.ts
|
|
@@ -32945,6 +33058,7 @@ function build_return_node(node, status, nir_value) {
|
|
|
32945
33058
|
const ret_is_null = returns_nullable_struct && (!node.value || node.value.node_type === "value" && node.value.value === "null");
|
|
32946
33059
|
if (returns_nullable_struct) {
|
|
32947
33060
|
if (ret_is_null) {
|
|
33061
|
+
emit_nursery_joins_on_return_c(status);
|
|
32948
33062
|
reclaim_all_c_scopes(status);
|
|
32949
33063
|
status.code += `*${ret_has} = 0;\n`;
|
|
32950
33064
|
status.code += `return (struct ${status.function_return_type.name}){0};\n`;
|
|
@@ -32953,6 +33067,7 @@ function build_return_node(node, status, nir_value) {
|
|
|
32953
33067
|
status.code += `*${ret_has} = 1;\n`;
|
|
32954
33068
|
}
|
|
32955
33069
|
if (!node.value) {
|
|
33070
|
+
emit_nursery_joins_on_return_c(status);
|
|
32956
33071
|
reclaim_all_c_scopes(status);
|
|
32957
33072
|
if (status.return_assign) status.code += `${status.return_assign} = 0;\n`;
|
|
32958
33073
|
else if (status.current_function_name?.toLocaleLowerCase() === "main") status.code += `return 0;\n`;
|
|
@@ -33005,6 +33120,7 @@ function build_return_node(node, status, nir_value) {
|
|
|
33005
33120
|
status.code += `_return_val->length = ${return_array_len};\n`;
|
|
33006
33121
|
status.code += `${elem_c_type}* _return_data = (${elem_c_type}*)((char*)_return_val + sizeof(struct ${array_struct}));\n`;
|
|
33007
33122
|
status.code += `for (long _i = 0; _i < ${return_array_len}; _i++) _return_data[_i] = ${return_array_var}[_i];\n`;
|
|
33123
|
+
emit_nursery_joins_on_return_c(status);
|
|
33008
33124
|
reclaim_all_c_scopes(status);
|
|
33009
33125
|
status.code += `return _return_val;\n`;
|
|
33010
33126
|
return;
|
|
@@ -33020,6 +33136,7 @@ function build_return_node(node, status, nir_value) {
|
|
|
33020
33136
|
emit_allocations(node.value, status);
|
|
33021
33137
|
emit_return_value(node.value, nir_value, status);
|
|
33022
33138
|
status.code += `;\n`;
|
|
33139
|
+
emit_nursery_joins_on_return_c(status);
|
|
33023
33140
|
reclaim_all_c_scopes(status);
|
|
33024
33141
|
status.code += `return;\n`;
|
|
33025
33142
|
} else {
|
|
@@ -33042,6 +33159,7 @@ function build_return_node(node, status, nir_value) {
|
|
|
33042
33159
|
build_node$1(node.value, status);
|
|
33043
33160
|
status.join_needs_owned_string = old_join_owned;
|
|
33044
33161
|
status.return_assign = old_return_assign;
|
|
33162
|
+
emit_nursery_joins_on_return_c(status);
|
|
33045
33163
|
reclaim_all_c_scopes(status);
|
|
33046
33164
|
if (string_join) status.code += any_branch_owned ? `return _return_val;\n` : `return nomen_str_dup(_return_val);\n`;
|
|
33047
33165
|
else status.code += `return _return_val;\n`;
|
|
@@ -33100,6 +33218,7 @@ function build_return_node(node, status, nir_value) {
|
|
|
33100
33218
|
status.code += `;\n`;
|
|
33101
33219
|
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
33220
|
if (has_field_overrides(node.value)) emit_field_overrides("_return_val", node.value, build_node$1, status, "", ";\n");
|
|
33221
|
+
emit_nursery_joins_on_return_c(status);
|
|
33103
33222
|
reclaim_all_c_scopes(status);
|
|
33104
33223
|
status.code += `return _return_val;\n`;
|
|
33105
33224
|
}
|
|
@@ -33382,7 +33501,7 @@ function emit_stmt_from_nir(child, index, statements, status) {
|
|
|
33382
33501
|
const eval_node = nstmt.expr.node;
|
|
33383
33502
|
if (eval_node.node_type === "access") {
|
|
33384
33503
|
const inner = eval_node.access;
|
|
33385
|
-
if (inner.node_type === "access_func" &&
|
|
33504
|
+
if (inner.node_type === "access_func" && inner.is_nursery_spawn) inner.is_statement = true;
|
|
33386
33505
|
}
|
|
33387
33506
|
emit_expr_from_nir(nstmt.expr, status);
|
|
33388
33507
|
with_semicolon_tail(status, eval_node);
|
|
@@ -33911,22 +34030,9 @@ function scan_borrow_only_strings(body) {
|
|
|
33911
34030
|
}
|
|
33912
34031
|
//#endregion
|
|
33913
34032
|
//#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
34033
|
function build_struct_node(node, status) {
|
|
33925
34034
|
if (node.is_generic) return;
|
|
33926
|
-
|
|
33927
|
-
ensure_concurrency_runtime(status);
|
|
33928
|
-
status.used_fibers = true;
|
|
33929
|
-
}
|
|
34035
|
+
for (const func of node.functions) ensure_runtime_for_method(func, status);
|
|
33930
34036
|
if (node.is_simple_type) {
|
|
33931
34037
|
status.code += `// Struct ${node.name}\n`;
|
|
33932
34038
|
build_struct_functions(node, status);
|
|
@@ -34165,6 +34271,7 @@ function build_struct_functions(node, status, skip_init = false) {
|
|
|
34165
34271
|
const old_current_struct = status.current_struct;
|
|
34166
34272
|
status.current_struct = node;
|
|
34167
34273
|
for (let func of node.functions) {
|
|
34274
|
+
if (func.name === "#spawn") continue;
|
|
34168
34275
|
if (func.name === "#init" && !func.has_body) continue;
|
|
34169
34276
|
if (func.name === "#init" && skip_init) continue;
|
|
34170
34277
|
if (func.is_extern) {
|
|
@@ -35022,11 +35129,14 @@ function build_function_node(node, status) {
|
|
|
35022
35129
|
status.function_view_params = /* @__PURE__ */ new Set();
|
|
35023
35130
|
const old_return_type = status.function_return_type;
|
|
35024
35131
|
status.function_return_type = node.return_type;
|
|
35132
|
+
const old_variable_types = status.variable_types;
|
|
35133
|
+
status.variable_types = /* @__PURE__ */ new Map();
|
|
35025
35134
|
const old_nullable_ret_has = status.nullable_ret_has_param;
|
|
35026
35135
|
if (is_nullable_struct_type(node.return_type, status)) status.nullable_ret_has_param = "_ret_has";
|
|
35027
35136
|
else status.nullable_ret_has_param = void 0;
|
|
35028
35137
|
const old_function_name = status.current_function_name;
|
|
35029
35138
|
status.current_function_name = emission_label(node);
|
|
35139
|
+
status.function_nursery_depth = status.nursery_stack?.length ?? 0;
|
|
35030
35140
|
const old_current_function = status.current_function;
|
|
35031
35141
|
status.current_function = node;
|
|
35032
35142
|
for (let param of node.params) {
|
|
@@ -35076,6 +35186,7 @@ function build_function_node(node, status) {
|
|
|
35076
35186
|
status.function_variadic_params = old_variadic_params;
|
|
35077
35187
|
status.function_view_params = old_view_params;
|
|
35078
35188
|
status.function_return_type = old_return_type;
|
|
35189
|
+
status.variable_types = old_variable_types;
|
|
35079
35190
|
status.nullable_ret_has_param = old_nullable_ret_has;
|
|
35080
35191
|
status.current_function_name = old_function_name;
|
|
35081
35192
|
status.current_function = old_current_function;
|
|
@@ -35645,22 +35756,6 @@ function build_access_node$1(node, status) {
|
|
|
35645
35756
|
}
|
|
35646
35757
|
case "access_func": {
|
|
35647
35758
|
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
35759
|
if (access_func.is_nursery_spawn) {
|
|
35665
35760
|
build_nursery_spawn$1(access_func, nursery_pointer_expr(node.target, status), status);
|
|
35666
35761
|
return;
|
|
@@ -35965,10 +36060,35 @@ function build_access_node$1(node, status) {
|
|
|
35965
36060
|
const label = access_func.mangled_name || trait_default_label || `${mono_struct_name}_${access_func.name.replace(/#/g, "")}`;
|
|
35966
36061
|
const array_wrap = array_receiver_wrap(node, method_type, target_method, mono_struct_name, status);
|
|
35967
36062
|
if (array_wrap) status.code += array_wrap.prefix;
|
|
36063
|
+
const ctor_target_node = node.target.node_type === "func_call" ? node.target : void 0;
|
|
36064
|
+
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));
|
|
36065
|
+
let ctor_temp_free;
|
|
36066
|
+
let ctor_temp_val;
|
|
36067
|
+
if (ctor_temp_receiver) {
|
|
36068
|
+
const id = status.label_counter = (status.label_counter ?? 0) + 1;
|
|
36069
|
+
ctor_temp_free = `_ctorr_${id}`;
|
|
36070
|
+
const ctor_mono = mono_type_name(type_from_value_node$1(node.target));
|
|
36071
|
+
status.code += `({ struct ${ctor_mono} *${ctor_temp_free} = `;
|
|
36072
|
+
{
|
|
36073
|
+
const saved_suppress = status.suppress_dereference;
|
|
36074
|
+
status.suppress_dereference = true;
|
|
36075
|
+
build_node$1(node.target, status);
|
|
36076
|
+
status.suppress_dereference = saved_suppress;
|
|
36077
|
+
}
|
|
36078
|
+
status.code += `; `;
|
|
36079
|
+
const ret = (target_method ?? trait_default_func)?.return_type;
|
|
36080
|
+
if (ret?.name && ret.name !== "void") {
|
|
36081
|
+
const mono_ret = mono_type_name(access_func.type);
|
|
36082
|
+
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);
|
|
36083
|
+
ctor_temp_val = `_ctorv_${id}`;
|
|
36084
|
+
status.code += `${ret_c} ${ctor_temp_val} = `;
|
|
36085
|
+
}
|
|
36086
|
+
}
|
|
35968
36087
|
if (lambda_ret_tmp) status.code += `${lambda_ret_c} ${lambda_ret_tmp} = `;
|
|
35969
36088
|
status.code += `${label}(`;
|
|
35970
36089
|
if (!access_func.is_static) {
|
|
35971
|
-
if (
|
|
36090
|
+
if (ctor_temp_free) status.code += ctor_temp_free;
|
|
36091
|
+
else if (array_wrap) status.code += array_wrap.self_expr;
|
|
35972
36092
|
else {
|
|
35973
36093
|
const method_self_is_ref = !!target_method?.params?.some((p) => p.is_self_param && (p.is_ref || p.type?.is_ref));
|
|
35974
36094
|
const method_self = target_method?.params?.find((p) => p.is_self_param);
|
|
@@ -36037,6 +36157,11 @@ function build_access_node$1(node, status) {
|
|
|
36037
36157
|
}
|
|
36038
36158
|
}
|
|
36039
36159
|
status.code += ")";
|
|
36160
|
+
if (ctor_temp_free) {
|
|
36161
|
+
status.code += `; free(${ctor_temp_free});`;
|
|
36162
|
+
if (ctor_temp_val) status.code += ` ${ctor_temp_val};`;
|
|
36163
|
+
status.code += ` })`;
|
|
36164
|
+
}
|
|
36040
36165
|
if (lambda_arg_temps.size > 0) {
|
|
36041
36166
|
for (const tmp of lambda_arg_temps.values()) status.code += `; ${closure_dispose_arm(tmp)}`;
|
|
36042
36167
|
if (lambda_ret_tmp) status.code += `; ${lambda_ret_tmp}`;
|
|
@@ -36758,7 +36883,7 @@ function build_node$1(node, status, with_semicolon = false) {
|
|
|
36758
36883
|
const access = node;
|
|
36759
36884
|
if (with_semicolon && access.access.node_type === "access_func") {
|
|
36760
36885
|
const afn = access.access;
|
|
36761
|
-
if (afn.is_nursery_spawn
|
|
36886
|
+
if (afn.is_nursery_spawn) afn.is_statement = true;
|
|
36762
36887
|
}
|
|
36763
36888
|
build_access_node$1(access, status);
|
|
36764
36889
|
break;
|
|
@@ -36861,9 +36986,10 @@ function build_node$1(node, status, with_semicolon = false) {
|
|
|
36861
36986
|
//#endregion
|
|
36862
36987
|
//#region ../src/build_c/build_magic_ctor.ts
|
|
36863
36988
|
/**
|
|
36864
|
-
* The
|
|
36865
|
-
*
|
|
36866
|
-
*
|
|
36989
|
+
* The `#spawn` construction (the deferred-call special form — the library
|
|
36990
|
+
* `Thread(fn(args))` / `Fiber(fn(args))`, or any class declaring its own
|
|
36991
|
+
* `#spawn` hook; docs/ASYNC.md, "The construction special form") — packs
|
|
36992
|
+
* the wrapped call's
|
|
36867
36993
|
* arguments EAGERLY into a task environment, allocates the result slot,
|
|
36868
36994
|
* cancel flag, and future, and builds the task closure — a heap descriptor
|
|
36869
36995
|
* over that environment (the same ABI every lambda lowers to; the runtime's
|
|
@@ -37058,6 +37184,7 @@ function build_magic_ctor_node$1(node, status) {
|
|
|
37058
37184
|
status.code += `\t_future->done = 0;\n`;
|
|
37059
37185
|
status.code += `\t_future->cancel_flag = _cancel_ptr;\n`;
|
|
37060
37186
|
status.code += `\t_future->result_slot = _result_ptr;\n`;
|
|
37187
|
+
status.code += `\t_future->slot_fat = ${slot_c_type === "nomen_string" ? 1 : 0};\n`;
|
|
37061
37188
|
status.code += `\t_future->fiber_waiters = NULL;\n`;
|
|
37062
37189
|
status.code += `\t_future->owning_fiber = NULL;\n`;
|
|
37063
37190
|
status.code += `\t_future->refs = 1;\n`;
|
|
@@ -37075,10 +37202,29 @@ function build_magic_ctor_node$1(node, status) {
|
|
|
37075
37202
|
status.code += `\t_self->cancel_flag = (unsigned long long)_cancel_ptr;\n`;
|
|
37076
37203
|
status.code += `\t_self->future = (unsigned long long)_future;\n`;
|
|
37077
37204
|
if (info.has_started) status.code += `\t_self->started = 0;\n`;
|
|
37205
|
+
emit_nursery_capture_c(status, kind, (field, expr) => {
|
|
37206
|
+
status.code += `\t_self->${field} = ${expr};\n`;
|
|
37207
|
+
});
|
|
37078
37208
|
status.code += `\t_self;\n`;
|
|
37079
37209
|
status.code += `})\n`;
|
|
37080
37210
|
}
|
|
37081
37211
|
/**
|
|
37212
|
+
* LEXICAL NURSERY CAPTURE (ASYNC.md): a construction lexically
|
|
37213
|
+
* inside an `async` block stores the block's tracking-slot pointers in the
|
|
37214
|
+
* instance, so `.start()` — an ordinary method on the spawn class —
|
|
37215
|
+
* registers the future with the scope that CREATED the deferred call rather
|
|
37216
|
+
* than wherever the launch happens. Library spawn classes only: Thread/Fiber
|
|
37217
|
+
* carry the three capture fields (the generalized Awaitable flavor's field
|
|
37218
|
+
* contract predates them and keeps its historical emission).
|
|
37219
|
+
*/
|
|
37220
|
+
function emit_nursery_capture_c(status, kind, emit_field) {
|
|
37221
|
+
const nursery_id = status.nursery_stack?.at(-1);
|
|
37222
|
+
if (nursery_id === void 0 || kind !== "Thread" && kind !== "Fiber") return;
|
|
37223
|
+
emit_field("nursery_futures", `(unsigned long long)&__nomen_nursery_${nursery_id}_futures`);
|
|
37224
|
+
emit_field("nursery_count", `(unsigned long long)&__nomen_nursery_${nursery_id}_count`);
|
|
37225
|
+
emit_field("nursery_cap", `(unsigned long long)&__nomen_nursery_${nursery_id}_cap`);
|
|
37226
|
+
}
|
|
37227
|
+
/**
|
|
37082
37228
|
* The Phase 3c function-value construction (`Thread(() => work(n))` /
|
|
37083
37229
|
* `Thread(job)` with a zero-argument func-typed local): the task closure is
|
|
37084
37230
|
* an ADAPTER over the given closure. The adapter env carries the user
|
|
@@ -37152,6 +37298,7 @@ function build_fn_value_ctor(node, status, id, kind, info) {
|
|
|
37152
37298
|
status.code += `\t_future->done = 0;\n`;
|
|
37153
37299
|
status.code += `\t_future->cancel_flag = _cancel_ptr;\n`;
|
|
37154
37300
|
status.code += `\t_future->result_slot = _result_ptr;\n`;
|
|
37301
|
+
status.code += `\t_future->slot_fat = ${dup_result ? 1 : 0};\n`;
|
|
37155
37302
|
status.code += `\t_future->fiber_waiters = NULL;\n`;
|
|
37156
37303
|
status.code += `\t_future->owning_fiber = NULL;\n`;
|
|
37157
37304
|
status.code += `\t_future->refs = 1;\n`;
|
|
@@ -37173,6 +37320,9 @@ function build_fn_value_ctor(node, status, id, kind, info) {
|
|
|
37173
37320
|
status.code += `\t_self->cancel_flag = (unsigned long long)_cancel_ptr;\n`;
|
|
37174
37321
|
status.code += `\t_self->future = (unsigned long long)_future;\n`;
|
|
37175
37322
|
if (info.has_started) status.code += `\t_self->started = 0;\n`;
|
|
37323
|
+
emit_nursery_capture_c(status, kind, (field, expr) => {
|
|
37324
|
+
status.code += `\t_self->${field} = ${expr};\n`;
|
|
37325
|
+
});
|
|
37176
37326
|
status.code += `\t_self;\n`;
|
|
37177
37327
|
status.code += `})\n`;
|
|
37178
37328
|
}
|
|
@@ -37193,10 +37343,10 @@ function is_struct_ret(node, status) {
|
|
|
37193
37343
|
//#endregion
|
|
37194
37344
|
//#region ../src/build_aarch64/build_magic_ctor.ts
|
|
37195
37345
|
/**
|
|
37196
|
-
* The spawn
|
|
37197
|
-
* `Thread(fn(args))` / `Fiber(fn(args))`, or
|
|
37198
|
-
*
|
|
37199
|
-
*
|
|
37346
|
+
* The `#spawn` construction (the deferred-call special form — the library
|
|
37347
|
+
* `Thread(fn(args))` / `Fiber(fn(args))`, or any class declaring its own
|
|
37348
|
+
* `#spawn` hook; docs/ASYNC.md, "The construction special form"),
|
|
37349
|
+
* aarch64 backend: the per-site
|
|
37200
37350
|
* trampoline and a constructor helper are emitted as C in the companion
|
|
37201
37351
|
* file; the assembly stages the wrapped call's arguments and calls the
|
|
37202
37352
|
* helper, which packs the env, allocates the future machinery, builds the
|
|
@@ -37218,10 +37368,14 @@ function build_magic_ctor_node(node, status) {
|
|
|
37218
37368
|
const tramp_name = `__nomen_spawn_${id}_trampoline`;
|
|
37219
37369
|
const desc_name = `__nomen_spawn_${id}_descriptor`;
|
|
37220
37370
|
const struct_name = `__nomen_spawn_${id}_args`;
|
|
37371
|
+
const nursery_id = status.nursery_stack?.at(-1);
|
|
37372
|
+
const nursery_off = nursery_id !== void 0 ? status.nursery_offsets?.get(nursery_id) : void 0;
|
|
37373
|
+
const capture = nursery_capture_a64(status, kind);
|
|
37374
|
+
const arg_types = spawn_arg_types(call, status);
|
|
37221
37375
|
const arg_c_types = [];
|
|
37222
37376
|
const fat_string_args = [];
|
|
37223
37377
|
for (let i = 0; i < call.params.length; i++) {
|
|
37224
|
-
const mono_name = mono_type_name(
|
|
37378
|
+
const mono_name = mono_type_name(arg_types[i]);
|
|
37225
37379
|
const is_class = !!status.structs.find((s) => s.name === mono_name && s.is_class);
|
|
37226
37380
|
const is_trait = !!status.traits.find((t) => t.name === mono_name);
|
|
37227
37381
|
arg_c_types.push(is_class || is_trait ? `struct ${mono_name} *` : `${c_type(mono_name)}`);
|
|
@@ -37277,6 +37431,10 @@ function build_magic_ctor_node(node, status) {
|
|
|
37277
37431
|
if (i > 0) c += ", ";
|
|
37278
37432
|
c += `${arg_c_types[i]} arg${i}`;
|
|
37279
37433
|
}
|
|
37434
|
+
if (capture) {
|
|
37435
|
+
if (arg_c_types.length > 0) c += ", ";
|
|
37436
|
+
c += capture.helper_params;
|
|
37437
|
+
}
|
|
37280
37438
|
c += `) {\n`;
|
|
37281
37439
|
c += `\tstruct ${struct_name} *a = (struct ${struct_name} *)malloc(sizeof(struct ${struct_name}));\n`;
|
|
37282
37440
|
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 +37449,7 @@ function build_magic_ctor_node(node, status) {
|
|
|
37291
37449
|
c += `\tf->refs = 1;\n`;
|
|
37292
37450
|
c += `\tf->cancel_flag = a->cancel_flag;\n`;
|
|
37293
37451
|
c += `\tf->result_slot = a->result_slot;\n`;
|
|
37452
|
+
c += `\tf->slot_fat = ${slot_c_type === "nomen_string" ? 1 : 0};\n`;
|
|
37294
37453
|
c += `\tf->fiber_waiters = NULL;\n`;
|
|
37295
37454
|
c += `\tf->owning_fiber = 0;\n`;
|
|
37296
37455
|
c += `\ta->future = f;\n`;
|
|
@@ -37307,6 +37466,7 @@ function build_magic_ctor_node(node, status) {
|
|
|
37307
37466
|
c += `\tself->cancel_flag = (unsigned long long)a->cancel_flag;\n`;
|
|
37308
37467
|
c += `\tself->future = (unsigned long long)f;\n`;
|
|
37309
37468
|
if (info.has_started) c += `\tself->started = 0;\n`;
|
|
37469
|
+
if (capture) c += capture.field_stores.join("");
|
|
37310
37470
|
c += `\treturn self;\n`;
|
|
37311
37471
|
c += `}\n`;
|
|
37312
37472
|
if (!status.file_scope_c) status.file_scope_c = "";
|
|
@@ -37318,6 +37478,8 @@ function build_magic_ctor_node(node, status) {
|
|
|
37318
37478
|
arg_slot.push(total_arg_slots);
|
|
37319
37479
|
total_arg_slots += fat_string_args[i] ? 2 : 1;
|
|
37320
37480
|
}
|
|
37481
|
+
const capture_base_slot = total_arg_slots;
|
|
37482
|
+
if (capture) total_arg_slots += capture.arg_count;
|
|
37321
37483
|
if (total_arg_slots === 0) emit_asm(status, `bl _${helper_name}\n`);
|
|
37322
37484
|
else {
|
|
37323
37485
|
const args_base = allocate_stack_space(status, total_arg_slots * 8, 16);
|
|
@@ -37328,6 +37490,17 @@ function build_magic_ctor_node(node, status) {
|
|
|
37328
37490
|
emit_asm(status, `str x0, [x29, #${args_base + arg_slot[i] * 8}]\n`);
|
|
37329
37491
|
if (fat_string_args[i]) emit_asm(status, `str x1, [x29, #${args_base + (arg_slot[i] + 1) * 8}]\n`);
|
|
37330
37492
|
}
|
|
37493
|
+
if (capture && nursery_off) {
|
|
37494
|
+
const addrs = [
|
|
37495
|
+
nursery_off.futures_off,
|
|
37496
|
+
nursery_off.count_off,
|
|
37497
|
+
nursery_off.cap_off
|
|
37498
|
+
];
|
|
37499
|
+
for (let k = 0; k < addrs.length; k++) {
|
|
37500
|
+
emit_asm(status, `add x9, x29, #${addrs[k]}\n`);
|
|
37501
|
+
emit_asm(status, `str x9, [x29, #${args_base + (capture_base_slot + k) * 8}]\n`);
|
|
37502
|
+
}
|
|
37503
|
+
}
|
|
37331
37504
|
const NUM_REG_ARGS = 8;
|
|
37332
37505
|
const overflow_count = Math.max(0, total_arg_slots - NUM_REG_ARGS);
|
|
37333
37506
|
if (overflow_count > 0) {
|
|
@@ -37402,7 +37575,8 @@ function build_fn_value_ctor_a64(node, status, id, kind, info) {
|
|
|
37402
37575
|
c += `\t__nomen_future_release(a->future);\n`;
|
|
37403
37576
|
c += `}\n`;
|
|
37404
37577
|
c += `static struct nomen_closure ${desc_name} = { (void *)${tramp_name}, NULL, 0, NULL };\n`;
|
|
37405
|
-
|
|
37578
|
+
const fnval_capture = nursery_capture_a64(status, kind);
|
|
37579
|
+
c += `void *${helper_name}(struct nomen_closure *fn${fnval_capture ? `, ${fnval_capture.helper_params}` : ""}) {\n`;
|
|
37406
37580
|
c += `\tstruct ${struct_name} *a = (struct ${struct_name} *)malloc(sizeof(struct ${struct_name}));\n`;
|
|
37407
37581
|
c += `\ta->fn = fn;\n`;
|
|
37408
37582
|
c += `\ta->result_slot = (${slot_c_type} *)${returns_value ? `malloc(sizeof(${slot_c_type}))` : "malloc(16)"};\n`;
|
|
@@ -37416,6 +37590,7 @@ function build_fn_value_ctor_a64(node, status, id, kind, info) {
|
|
|
37416
37590
|
c += `\tf->refs = 1;\n`;
|
|
37417
37591
|
c += `\tf->cancel_flag = a->cancel_flag;\n`;
|
|
37418
37592
|
c += `\tf->result_slot = a->result_slot;\n`;
|
|
37593
|
+
c += `\tf->slot_fat = ${dup_result ? 1 : 0};\n`;
|
|
37419
37594
|
c += `\tf->fiber_waiters = NULL;\n`;
|
|
37420
37595
|
c += `\tf->owning_fiber = 0;\n`;
|
|
37421
37596
|
c += `\ta->future = f;\n`;
|
|
@@ -37432,6 +37607,7 @@ function build_fn_value_ctor_a64(node, status, id, kind, info) {
|
|
|
37432
37607
|
c += `\tself->cancel_flag = (unsigned long long)a->cancel_flag;\n`;
|
|
37433
37608
|
c += `\tself->future = (unsigned long long)f;\n`;
|
|
37434
37609
|
if (info.has_started) c += `\tself->started = 0;\n`;
|
|
37610
|
+
if (fnval_capture) c += fnval_capture.field_stores.join("");
|
|
37435
37611
|
c += `\treturn self;\n`;
|
|
37436
37612
|
c += `}\n`;
|
|
37437
37613
|
if (!status.file_scope_c) status.file_scope_c = "";
|
|
@@ -37447,8 +37623,41 @@ function build_fn_value_ctor_a64(node, status, id, kind, info) {
|
|
|
37447
37623
|
if (!status.moved) status.moved = /* @__PURE__ */ new Set();
|
|
37448
37624
|
status.moved.add(name);
|
|
37449
37625
|
}
|
|
37626
|
+
if (fnval_capture) {
|
|
37627
|
+
const fnval_nursery_id = status.nursery_stack?.at(-1);
|
|
37628
|
+
const fnval_off = fnval_nursery_id !== void 0 ? status.nursery_offsets?.get(fnval_nursery_id) : void 0;
|
|
37629
|
+
if (fnval_off) {
|
|
37630
|
+
const fnval_park = allocate_stack_space(status, 8, 8);
|
|
37631
|
+
emit_asm(status, `str x0, [x29, #${fnval_park}]\n`);
|
|
37632
|
+
emit_asm(status, `add x1, x29, #${fnval_off.futures_off}\n`);
|
|
37633
|
+
emit_asm(status, `add x2, x29, #${fnval_off.count_off}\n`);
|
|
37634
|
+
emit_asm(status, `add x3, x29, #${fnval_off.cap_off}\n`);
|
|
37635
|
+
emit_asm(status, `ldr x0, [x29, #${fnval_park}]\n`);
|
|
37636
|
+
}
|
|
37637
|
+
}
|
|
37450
37638
|
emit_asm(status, `bl _${helper_name}\n`);
|
|
37451
37639
|
}
|
|
37640
|
+
/**
|
|
37641
|
+
* LEXICAL NURSERY CAPTURE (ASYNC.md), aarch64 backend. The
|
|
37642
|
+
* construction helper is companion C and cannot address the enclosing async
|
|
37643
|
+
* block's frame, so the assembly passes the three tracking-slot addresses as
|
|
37644
|
+
* trailing helper arguments and the helper stores them into the instance's
|
|
37645
|
+
* capture fields — the lexical-at-construction counterpart of the C
|
|
37646
|
+
* backend's emit_nursery_capture_c. Applies to Thread/Fiber only (they
|
|
37647
|
+
* carry the capture fields).
|
|
37648
|
+
*/
|
|
37649
|
+
function nursery_capture_a64(status, kind) {
|
|
37650
|
+
if (status.nursery_stack?.at(-1) === void 0 || kind !== "Thread" && kind !== "Fiber") return void 0;
|
|
37651
|
+
return {
|
|
37652
|
+
helper_params: "unsigned long long nursery_futures, unsigned long long nursery_count, unsigned long long nursery_cap",
|
|
37653
|
+
field_stores: [
|
|
37654
|
+
`\tself->nursery_futures = nursery_futures;\n`,
|
|
37655
|
+
`\tself->nursery_count = nursery_count;\n`,
|
|
37656
|
+
`\tself->nursery_cap = nursery_cap;\n`
|
|
37657
|
+
],
|
|
37658
|
+
arg_count: 3
|
|
37659
|
+
};
|
|
37660
|
+
}
|
|
37452
37661
|
//#endregion
|
|
37453
37662
|
//#region ../src/build_aarch64/build_panic_node.ts
|
|
37454
37663
|
function build_panic_node(node, status) {
|
|
@@ -37576,7 +37785,7 @@ function build_value_node(node, status) {
|
|
|
37576
37785
|
if (paramReg !== "x0") emit_asm(status, `mov x0, ${paramReg}`);
|
|
37577
37786
|
} 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
37787
|
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)) {
|
|
37788
|
+
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
37789
|
if (paramReg !== "x0") emit_asm(status, `mov x0, ${paramReg}`);
|
|
37581
37790
|
} else emit_asm(status, deref_load_instr(paramReg, param_type_name));
|
|
37582
37791
|
} else if (paramReg !== "x0") emit_asm(status, `mov x0, ${paramReg}`);
|
|
@@ -37628,7 +37837,7 @@ function build_value_node(node, status) {
|
|
|
37628
37837
|
if (status.function_ref_params?.has(value) || status.function_ref_params?.has(original_value)) {
|
|
37629
37838
|
if (alloc_reg !== "x0") emit_asm(status, `mov x0, ${alloc_reg}\n`);
|
|
37630
37839
|
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));
|
|
37840
|
+
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
37841
|
} else if (alloc_reg !== "x0") {
|
|
37633
37842
|
if (alloc_reg.startsWith("d")) emit_asm(status, `fmov x0, ${alloc_reg}`);
|
|
37634
37843
|
else emit_asm(status, `mov x0, ${alloc_reg}`);
|
|
@@ -37836,7 +38045,7 @@ function build_node(node, status, with_semicolon = false) {
|
|
|
37836
38045
|
const access = node;
|
|
37837
38046
|
if (with_semicolon && access.access.node_type === "access_func") {
|
|
37838
38047
|
const afn = access.access;
|
|
37839
|
-
if (afn.is_nursery_spawn
|
|
38048
|
+
if (afn.is_nursery_spawn) afn.is_statement = true;
|
|
37840
38049
|
}
|
|
37841
38050
|
build_access_node(access, status);
|
|
37842
38051
|
break;
|
|
@@ -39757,7 +39966,7 @@ function build_operand(node, target_reg, status) {
|
|
|
39757
39966
|
if (paramReg) {
|
|
39758
39967
|
if (status.function_param_vars?.has(value) || status.function_ref_params?.has(value)) {
|
|
39759
39968
|
const param_type_name = node.type?.name;
|
|
39760
|
-
if (param_type_name && status.structs.find((s) => s.name === param_type_name && s.is_class)) {
|
|
39969
|
+
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
39970
|
if (paramReg !== target_reg) emit_asm(status, `mov ${target_reg}, ${paramReg}`);
|
|
39762
39971
|
} else emit_asm(status, `ldr ${target_reg}, [${paramReg}]`);
|
|
39763
39972
|
} else if (paramReg !== target_reg) emit_asm(status, `mov ${target_reg}, ${paramReg}`);
|
|
@@ -40990,113 +41199,6 @@ function array_licm_enabled() {
|
|
|
40990
41199
|
return array_licm_on;
|
|
40991
41200
|
}
|
|
40992
41201
|
//#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
41202
|
//#region ../src/build_aarch64/build_nursery_spawn.ts
|
|
41101
41203
|
/**
|
|
41102
41204
|
* Build a `name.start(Thread(fn(args)))` escape-hatch call for aarch64
|
|
@@ -41680,22 +41782,6 @@ function build_access_node(node, status) {
|
|
|
41680
41782
|
break;
|
|
41681
41783
|
case "access_func": {
|
|
41682
41784
|
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
41785
|
if (access_func.is_nursery_spawn) {
|
|
41700
41786
|
build_nursery_spawn(node, access_func, status);
|
|
41701
41787
|
return;
|
|
@@ -41741,6 +41827,46 @@ function get_base_target(node) {
|
|
|
41741
41827
|
if (node.target.node_type === "access") return get_base_target(node.target);
|
|
41742
41828
|
return node.target;
|
|
41743
41829
|
}
|
|
41830
|
+
/**
|
|
41831
|
+
* Resolve the concrete type of an access target (a local/param/`self` value or
|
|
41832
|
+
* a nested access), consulting the containing struct's field declarations when
|
|
41833
|
+
* a cached type is absent — an inferred `move` class field
|
|
41834
|
+
* (`pub move rules = RuleSet()`) carries no cached AccessFieldNode.type.
|
|
41835
|
+
*/
|
|
41836
|
+
function resolve_access_target_type(target, status) {
|
|
41837
|
+
if (target.node_type === "value") {
|
|
41838
|
+
const name = target.value;
|
|
41839
|
+
if (name === "self" && status.current_struct) return new Type(status.current_struct.name);
|
|
41840
|
+
if (status.variable_types?.has(name)) return status.variable_types.get(name);
|
|
41841
|
+
const decl = status.scoped_declarations.findLast((d) => d.name === name);
|
|
41842
|
+
if (decl?.type?.name) return decl.type;
|
|
41843
|
+
return type_from_value_node$1(target);
|
|
41844
|
+
}
|
|
41845
|
+
if (target.node_type === "access") return type_from_value_node$1(target) ?? resolve_access_type(target, status) ?? void 0;
|
|
41846
|
+
return type_from_value_node$1(target);
|
|
41847
|
+
}
|
|
41848
|
+
/**
|
|
41849
|
+
* Whether reading the final field of `node` must dereference an intermediate
|
|
41850
|
+
* pointer. The summed-offset-from-root load (`compute_field_offset` + the
|
|
41851
|
+
* base address) is only valid while every hop is an INLINE value struct. A
|
|
41852
|
+
* class-typed field — or a class-typed root value — stores a POINTER, so the
|
|
41853
|
+
* chain has to be built recursively (each pointer hop loaded) before the
|
|
41854
|
+
* final field is read. Without this, `state.rules.blocks.length` (State/
|
|
41855
|
+
* BlockParserState is a class) summed 8+8+8 and loaded from `state + 24`.
|
|
41856
|
+
*/
|
|
41857
|
+
function access_chain_crosses_class(node, status) {
|
|
41858
|
+
const base_type = resolve_access_target_type(get_base_target(node), status);
|
|
41859
|
+
if (base_type?.name && status.structs.find((s) => s.name === base_type.name && s.is_class)) return true;
|
|
41860
|
+
let cursor = node;
|
|
41861
|
+
while (cursor.node_type === "access") {
|
|
41862
|
+
const acc = cursor;
|
|
41863
|
+
if (acc.access.node_type !== "access_field") return false;
|
|
41864
|
+
const ft = resolve_field_type(acc.access, resolve_access_target_type(acc.target, status)?.name, status);
|
|
41865
|
+
if (ft?.name && status.structs.find((s) => s.name === ft.name && s.is_class)) return true;
|
|
41866
|
+
cursor = acc.target;
|
|
41867
|
+
}
|
|
41868
|
+
return false;
|
|
41869
|
+
}
|
|
41744
41870
|
function get_param_reg(name, status) {
|
|
41745
41871
|
return status.function_param_regs?.get(name);
|
|
41746
41872
|
}
|
|
@@ -42140,9 +42266,11 @@ function build_access_field(node, status) {
|
|
|
42140
42266
|
emit_string_length(node.target, status);
|
|
42141
42267
|
return;
|
|
42142
42268
|
}
|
|
42143
|
-
|
|
42269
|
+
let offset = compute_field_offset(node, status);
|
|
42144
42270
|
const base = get_base_target(node);
|
|
42145
|
-
const
|
|
42271
|
+
const target_access_field = node.target.node_type === "access" && node.target.access.node_type === "access_field" ? node.target.access : void 0;
|
|
42272
|
+
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;
|
|
42273
|
+
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
42274
|
if (node.target.node_type === "access" && node.target.access.node_type === "access_func") {
|
|
42147
42275
|
build_node(node.target, status);
|
|
42148
42276
|
ensure_newline(status);
|
|
@@ -42173,8 +42301,13 @@ function build_access_field(node, status) {
|
|
|
42173
42301
|
build_node(node.target, status);
|
|
42174
42302
|
ensure_newline(status);
|
|
42175
42303
|
const final_offset = get_field_offset(target_type?.name || "", access_field.name, status);
|
|
42176
|
-
const
|
|
42177
|
-
|
|
42304
|
+
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);
|
|
42305
|
+
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 || "";
|
|
42306
|
+
if (!!field_type && !class_access_field_type?.is_ref && !class_access_field_type?.is_nullable && is_struct_type(field_type, status)) {
|
|
42307
|
+
if (final_offset > 0) emit_asm(status, `add x0, x0, #${final_offset}\n`);
|
|
42308
|
+
return;
|
|
42309
|
+
}
|
|
42310
|
+
if (field_type === "string" || class_access_field_type?.is_view) {
|
|
42178
42311
|
emit_string_pair_load_at(status, "x0", final_offset);
|
|
42179
42312
|
return;
|
|
42180
42313
|
}
|
|
@@ -42204,14 +42337,14 @@ function build_access_field(node, status) {
|
|
|
42204
42337
|
if (paramReg !== "x0") emit_asm(status, `mov x0, ${paramReg}\n`);
|
|
42205
42338
|
} else emit_var_load(status, "x0", name, 8);
|
|
42206
42339
|
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 || "";
|
|
42340
|
+
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);
|
|
42341
|
+
const resolved_field_type = field_type_obj?.type_args?.length ? mono_type_name(field_type_obj) : field_type_obj?.name || "";
|
|
42209
42342
|
if (!!resolved_field_type && !field_type_obj?.is_ref && !field_type_obj?.is_nullable && is_struct_type(resolved_field_type, status)) {
|
|
42210
42343
|
if (final_offset > 0) emit_asm(status, `add x0, x0, #${final_offset}\n`);
|
|
42211
42344
|
return;
|
|
42212
42345
|
}
|
|
42213
|
-
const field_type =
|
|
42214
|
-
if (field_type === "string" ||
|
|
42346
|
+
const field_type = resolved_field_type;
|
|
42347
|
+
if (field_type === "string" || field_type_obj?.is_view) {
|
|
42215
42348
|
emit_string_pair_load_at(status, "x0", final_offset);
|
|
42216
42349
|
return;
|
|
42217
42350
|
}
|
|
@@ -42222,16 +42355,25 @@ function build_access_field(node, status) {
|
|
|
42222
42355
|
else emit_asm(status, `ldr x0, [x0, #${final_offset}]\n`);
|
|
42223
42356
|
return;
|
|
42224
42357
|
}
|
|
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);
|
|
42358
|
+
let target_built = false;
|
|
42359
|
+
if (node.target.node_type === "access" && access_chain_crosses_class(node, status)) {
|
|
42360
|
+
build_node(node.target, status);
|
|
42234
42361
|
ensure_newline(status);
|
|
42362
|
+
offset = get_field_offset(target_type?.name || "", access_field.name, status);
|
|
42363
|
+
target_built = true;
|
|
42364
|
+
}
|
|
42365
|
+
if (!target_built) {
|
|
42366
|
+
if (base.node_type === "value") {
|
|
42367
|
+
const name = base.value;
|
|
42368
|
+
const paramReg = get_param_reg(name, status);
|
|
42369
|
+
if (paramReg) {
|
|
42370
|
+
if (paramReg !== "x0") emit_asm(status, `mov x0, ${paramReg}\n`);
|
|
42371
|
+
} else if (is_local_ref_var(name, status)) emit_deref_var_address(status, "x0", name);
|
|
42372
|
+
else emit_var_address(status, "x0", name);
|
|
42373
|
+
} else {
|
|
42374
|
+
build_node(base, status);
|
|
42375
|
+
ensure_newline(status);
|
|
42376
|
+
}
|
|
42235
42377
|
}
|
|
42236
42378
|
const field_type_obj = resolve_field_type(access_field, target_type?.name, status);
|
|
42237
42379
|
const resolved_field_type = field_type_obj?.name || "";
|
|
@@ -42758,20 +42900,25 @@ function build_access_method(node, access_func, status) {
|
|
|
42758
42900
|
} else if (!target_is_simple && node.target.node_type === "access") {
|
|
42759
42901
|
const access_target = node.target;
|
|
42760
42902
|
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);
|
|
42903
|
+
if (access_chain_crosses_class(access_target, status)) {
|
|
42904
|
+
build_node(access_target, status);
|
|
42772
42905
|
ensure_newline(status);
|
|
42906
|
+
} else {
|
|
42907
|
+
const offset = compute_field_offset(access_target, status);
|
|
42908
|
+
const base = get_base_target(access_target);
|
|
42909
|
+
if (base.node_type === "value") {
|
|
42910
|
+
const name = base.value;
|
|
42911
|
+
const paramReg = get_param_reg(name, status);
|
|
42912
|
+
if (paramReg) {
|
|
42913
|
+
if (paramReg !== "x0") emit_asm(status, `mov x0, ${paramReg}\n`);
|
|
42914
|
+
} else if (is_local_ref_var(name, status)) emit_deref_var_address(status, "x0", name);
|
|
42915
|
+
else emit_var_address(status, "x0", name);
|
|
42916
|
+
} else {
|
|
42917
|
+
build_node(base, status);
|
|
42918
|
+
ensure_newline(status);
|
|
42919
|
+
}
|
|
42920
|
+
if (offset > 0) emit_asm(status, `add x0, x0, #${offset}\n`);
|
|
42773
42921
|
}
|
|
42774
|
-
if (offset > 0) emit_asm(status, `add x0, x0, #${offset}\n`);
|
|
42775
42922
|
if (target_type.is_array && target_type.is_array_heap) {
|
|
42776
42923
|
emit_asm(status, `ldr x0, [x0]\n`);
|
|
42777
42924
|
emit_asm(status, `add x0, x0, #8\n`);
|
|
@@ -42793,6 +42940,13 @@ function build_access_method(node, access_func, status) {
|
|
|
42793
42940
|
trait_recv_slot = allocate_stack_space(status, 8, 8);
|
|
42794
42941
|
emit_asm(status, `str x0, [x29, #${trait_recv_slot}]\n`);
|
|
42795
42942
|
}
|
|
42943
|
+
const ctor_temp_node = node.target.node_type === "func_call" ? node.target : void 0;
|
|
42944
|
+
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));
|
|
42945
|
+
let ctor_recv_slot;
|
|
42946
|
+
if (ctor_temp_receiver && !access_func.is_static && !receiver_is_string) {
|
|
42947
|
+
ctor_recv_slot = allocate_stack_space(status, 8, 8);
|
|
42948
|
+
emit_asm(status, `str x0, [x29, #${ctor_recv_slot}]\n`);
|
|
42949
|
+
}
|
|
42796
42950
|
const raw_needs_self = !access_func.is_static && access_func.params.length > 0 && !receiver_is_string;
|
|
42797
42951
|
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
42952
|
const elide_self_save = raw_needs_self && !!inline_func0 && naked_inline_skips_self(inline_func0, status.platform);
|
|
@@ -43010,12 +43164,20 @@ function build_access_method(node, access_func, status) {
|
|
|
43010
43164
|
}
|
|
43011
43165
|
if (outgoing_size > 0) emit_asm(status, `add sp, sp, #${outgoing_size}\n`);
|
|
43012
43166
|
emit_dispose_lambda_args_a64(status, lambda_arg_slots);
|
|
43167
|
+
if (ctor_recv_slot !== void 0) {
|
|
43168
|
+
emit_asm(status, `str x0, [sp, #-16]!\n`);
|
|
43169
|
+
emit_asm(status, `str x1, [sp, #-16]!\n`);
|
|
43170
|
+
emit_asm(status, `ldr x0, [x29, #${ctor_recv_slot}]\n`);
|
|
43171
|
+
emit_free(status);
|
|
43172
|
+
emit_asm(status, `ldr x1, [sp], #16\n`);
|
|
43173
|
+
emit_asm(status, `ldr x0, [sp], #16\n`);
|
|
43174
|
+
}
|
|
43013
43175
|
if (frees_string_receiver) {
|
|
43014
43176
|
emit_asm(status, `ldr x9, [sp]\n`);
|
|
43015
43177
|
emit_asm(status, `str x0, [sp, #-16]!\n`);
|
|
43016
43178
|
emit_asm(status, `str x1, [sp, #-16]!\n`);
|
|
43017
43179
|
emit_asm(status, `mov x0, x9\n`);
|
|
43018
|
-
|
|
43180
|
+
emit_free(status);
|
|
43019
43181
|
emit_asm(status, `ldr x1, [sp], #16\n`);
|
|
43020
43182
|
emit_asm(status, `ldr x0, [sp], #16\n`);
|
|
43021
43183
|
emit_asm(status, `add sp, sp, #16\n`);
|
|
@@ -43415,15 +43577,22 @@ function scan_spawn_callees(root, result) {
|
|
|
43415
43577
|
if (node.node_type === "spawn") {
|
|
43416
43578
|
spawn_call = node.call;
|
|
43417
43579
|
spawn_ret = node.function_return_type;
|
|
43580
|
+
} else if (node.node_type === "func_call") {
|
|
43581
|
+
if (node.is_thread_ctor || node.is_fiber_ctor) {
|
|
43582
|
+
spawn_call = node.params?.[0];
|
|
43583
|
+
spawn_ret = node.function_return_type;
|
|
43584
|
+
}
|
|
43418
43585
|
} 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) {
|
|
43586
|
+
if (node.access.is_nursery_spawn) {
|
|
43423
43587
|
spawn_call = node.access.params?.[0]?.params?.[0];
|
|
43424
43588
|
spawn_ret = node.access.function_return_type;
|
|
43425
43589
|
}
|
|
43426
43590
|
}
|
|
43591
|
+
if (spawn_call && spawn_call.node_type === "func") {
|
|
43592
|
+
mark_fn_value_callees(spawn_call, spawn_ret, result);
|
|
43593
|
+
spawn_call = void 0;
|
|
43594
|
+
spawn_ret = void 0;
|
|
43595
|
+
}
|
|
43427
43596
|
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
43597
|
if (name_and_ret && name_and_ret[0] && name_and_ret[1].name === "string") result.add(name_and_ret[0]);
|
|
43429
43598
|
for (const key of Object.keys(node)) {
|
|
@@ -43435,6 +43604,34 @@ function scan_spawn_callees(root, result) {
|
|
|
43435
43604
|
};
|
|
43436
43605
|
visit(root);
|
|
43437
43606
|
}
|
|
43607
|
+
/**
|
|
43608
|
+
* Heap-normalize a function-value spawn's lambda (spawn_ret is the
|
|
43609
|
+
* construction's function return type; the lambda's own signature is the
|
|
43610
|
+
* fallback). Two marks:
|
|
43611
|
+
*
|
|
43612
|
+
* 1. The LAMBDA's emission label — a literal or borrowed-variable return
|
|
43613
|
+
* inside the lambda is then strdup'd at the lambda's return site (the
|
|
43614
|
+
* return build classifies through the same label).
|
|
43615
|
+
* 2. Every string-returning call the lambda returns DIRECTLY
|
|
43616
|
+
* (`() => fn(args)`): the call's pair flows to the slot unchanged (a call
|
|
43617
|
+
* result is not re-strdup'd at return), so the callee itself must
|
|
43618
|
+
* normalize — the same mark the direct-call form puts on its callee.
|
|
43619
|
+
*/
|
|
43620
|
+
function mark_fn_value_callees(lambda, spawn_ret, result) {
|
|
43621
|
+
if ((spawn_ret?.name ?? lambda.return_type?.name) !== "string") return;
|
|
43622
|
+
result.add(emission_label(lambda));
|
|
43623
|
+
const visit = (node) => {
|
|
43624
|
+
if (!node || typeof node !== "object") return;
|
|
43625
|
+
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 }));
|
|
43626
|
+
for (const key of Object.keys(node)) {
|
|
43627
|
+
if (key === "parent" || key === "scope") continue;
|
|
43628
|
+
const val = node[key];
|
|
43629
|
+
if (Array.isArray(val)) for (const item of val) visit(item);
|
|
43630
|
+
else if (val && typeof val === "object") visit(val);
|
|
43631
|
+
}
|
|
43632
|
+
};
|
|
43633
|
+
for (const s of lambda.statements ?? []) visit(s);
|
|
43634
|
+
}
|
|
43438
43635
|
function scan_statements(statements, result, struct_name) {
|
|
43439
43636
|
for (const stmt of statements) {
|
|
43440
43637
|
if (stmt.node_type === "struct") {
|
|
@@ -44072,6 +44269,8 @@ function build(root, options = {}) {
|
|
|
44072
44269
|
status.code = reduce_pointer_walks(status.code);
|
|
44073
44270
|
status.code = unroll_loops_x2(status.code);
|
|
44074
44271
|
status.code = eliminate_dead_cycle_moves(status.code);
|
|
44272
|
+
status.code = expand_large_mov_immediates(status.code);
|
|
44273
|
+
status.code = expand_far_adr_addresses(status.code);
|
|
44075
44274
|
if (options.audit) {
|
|
44076
44275
|
status.code = status.code.replaceAll("bl _malloc\n", "bl _nomen_malloc_wrap\n");
|
|
44077
44276
|
status.code = status.code.replaceAll("bl _calloc\n", "bl _nomen_calloc_wrap\n");
|
|
@@ -44235,7 +44434,9 @@ const CALL_KEYWORDS = /* @__PURE__ */ new Set([
|
|
|
44235
44434
|
"trait",
|
|
44236
44435
|
"enum",
|
|
44237
44436
|
"bitset",
|
|
44238
|
-
"cast"
|
|
44437
|
+
"cast",
|
|
44438
|
+
"panic",
|
|
44439
|
+
"todo"
|
|
44239
44440
|
]);
|
|
44240
44441
|
const NO_SPACE_BEFORE = /* @__PURE__ */ new Set([
|
|
44241
44442
|
",",
|
|
@@ -44316,8 +44517,8 @@ function needs_space(pieces, i) {
|
|
|
44316
44517
|
if (NO_SPACE_AFTER.has(previous.text)) return false;
|
|
44317
44518
|
if (previous.text === "out" || previous.text === "in") return current.text !== ".";
|
|
44318
44519
|
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(
|
|
44520
|
+
if (current.text === ".") return !is_value_end(pieces, i - 1);
|
|
44521
|
+
if (current.text === "(" || current.text === "[") return !is_value_end(pieces, i - 1) && !CALL_KEYWORDS.has(previous.text);
|
|
44321
44522
|
if (current.kind === "comment") return current.space_before;
|
|
44322
44523
|
return true;
|
|
44323
44524
|
}
|
|
@@ -44330,13 +44531,20 @@ function is_sign(pieces, i) {
|
|
|
44330
44531
|
if (PREFIX_POSITIONS.has(previous.text)) return true;
|
|
44331
44532
|
return previous.kind === "word" && KEYWORDS.has(previous.text);
|
|
44332
44533
|
}
|
|
44333
|
-
/**
|
|
44334
|
-
|
|
44534
|
+
/**
|
|
44535
|
+
* Can the piece at `i` end a value, so that a following `(`, `[` or `.` binds
|
|
44536
|
+
* to it? A word after `.` is a member name, so it binds even when it spells a
|
|
44537
|
+
* keyword (`old_keys.move(i)`).
|
|
44538
|
+
*/
|
|
44539
|
+
function is_value_end(pieces, i) {
|
|
44540
|
+
const piece = pieces[i];
|
|
44335
44541
|
switch (piece.kind) {
|
|
44336
44542
|
case "number":
|
|
44337
44543
|
case "string":
|
|
44338
44544
|
case "char": return true;
|
|
44339
|
-
case "word":
|
|
44545
|
+
case "word":
|
|
44546
|
+
if (i > 0 && pieces[i - 1].text === ".") return true;
|
|
44547
|
+
return !KEYWORDS.has(piece.text);
|
|
44340
44548
|
case "symbol": return piece.text === ")" || piece.text === "]" || piece.text === ">" || piece.text === "?";
|
|
44341
44549
|
default: return false;
|
|
44342
44550
|
}
|
|
@@ -45055,12 +45263,12 @@ function is_list_open(pieces, i) {
|
|
|
45055
45263
|
const bracket = pieces[i].text;
|
|
45056
45264
|
if (bracket === "(") {
|
|
45057
45265
|
const previous = pieces[i - 1];
|
|
45058
|
-
return !!previous && is_value_end(
|
|
45266
|
+
return !!previous && is_value_end(pieces, i - 1) && previous.text !== "=";
|
|
45059
45267
|
}
|
|
45060
45268
|
if (bracket === "[") {
|
|
45061
45269
|
const previous = pieces[i - 1];
|
|
45062
45270
|
if (previous?.kind === "word" && KEYWORDS.has(previous.text)) return false;
|
|
45063
|
-
return !previous || !is_value_end(
|
|
45271
|
+
return !previous || !is_value_end(pieces, i - 1);
|
|
45064
45272
|
}
|
|
45065
45273
|
return false;
|
|
45066
45274
|
}
|
|
@@ -45133,9 +45341,7 @@ function is_redundant(type, value) {
|
|
|
45133
45341
|
if (value.length > type.length && value[type.length].text === "(") {
|
|
45134
45342
|
if (match_bracket(value, type.length) === value.length - 1) {
|
|
45135
45343
|
const constructor = value.slice(0, type.length);
|
|
45136
|
-
if (
|
|
45137
|
-
if (type.every((piece, index) => piece.text === constructor[index].text)) return true;
|
|
45138
|
-
}
|
|
45344
|
+
if (type.every((piece, index) => piece.text === constructor[index].text)) return true;
|
|
45139
45345
|
}
|
|
45140
45346
|
}
|
|
45141
45347
|
if (value.length === 1 && type.length === 1) return literal_type(value[0]) === type[0].text && SAFE_LITERAL_TYPES.has(type[0].text);
|
|
@@ -48902,8 +49108,17 @@ function check_declaration_node(decl, status) {
|
|
|
48902
49108
|
const src_sv = /^[A-Za-z_][A-Za-z0-9_]*$/.test(src_name ?? "") ? status.values.findLast((v) => v.name === src_name) : void 0;
|
|
48903
49109
|
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
49110
|
}
|
|
48905
|
-
if (!decl.type.name)
|
|
48906
|
-
|
|
49111
|
+
if (!decl.type.name) {
|
|
49112
|
+
decl.type = type_from_value_node(decl.value, status);
|
|
49113
|
+
if (!decl.type.name && status.type_params.length > 0 && decl.value.node_type === "func_call") {
|
|
49114
|
+
const call = decl.value;
|
|
49115
|
+
if (call.type_args?.length) {
|
|
49116
|
+
const constructed = new Type(call.name);
|
|
49117
|
+
constructed.type_args = call.type_args;
|
|
49118
|
+
decl.type = constructed;
|
|
49119
|
+
}
|
|
49120
|
+
}
|
|
49121
|
+
} else if (decl.type.is_array && decl.value.node_type === "array") {
|
|
48907
49122
|
const value_type = type_from_value_node(decl.value, status);
|
|
48908
49123
|
if (value_type.is_array && value_type.length) decl.type.length = value_type.length;
|
|
48909
49124
|
}
|
|
@@ -49473,20 +49688,25 @@ function infer_field_type_from_value(value, status) {
|
|
|
49473
49688
|
* Resolving inferred field types upfront (right after gather_structs registers
|
|
49474
49689
|
* every struct) makes them visible regardless of declaration order.
|
|
49475
49690
|
*
|
|
49476
|
-
* Only
|
|
49477
|
-
* field
|
|
49478
|
-
*
|
|
49479
|
-
*
|
|
49691
|
+
* Only the field-type inference runs for generic structs too: a generic
|
|
49692
|
+
* field's initializer names the constructed type in terms of the struct's own
|
|
49693
|
+
* type params (`var items = Buffer<T>()` → `Buffer<T>`), which is exactly the
|
|
49694
|
+
* annotation it replaces, so recording it upfront is safe and keeps it visible
|
|
49695
|
+
* to the ownership classifier and to monomorphization. The buffer rewrite and
|
|
49696
|
+
* owning-param marking stay non-generic, mirroring check_struct_node. The
|
|
49697
|
+
* inference is structural to avoid re-running the full declaration check and
|
|
49698
|
+
* its side effects; check_struct_node later skips already-resolved fields, so
|
|
49699
|
+
* each field is resolved exactly once.
|
|
49480
49700
|
*/
|
|
49481
49701
|
function resolve_struct_field_types(status) {
|
|
49482
49702
|
for (const struct of status.structs) {
|
|
49483
|
-
if (struct.is_generic) continue;
|
|
49484
49703
|
for (const field of struct.fields) {
|
|
49485
49704
|
if (field.type.name) continue;
|
|
49486
49705
|
if (!field.value) continue;
|
|
49487
49706
|
const t = infer_field_type_from_value(field.value, status);
|
|
49488
49707
|
if (t?.name) field.type = t;
|
|
49489
49708
|
}
|
|
49709
|
+
if (struct.is_generic) continue;
|
|
49490
49710
|
rewrite_generic_buffer_fields(struct, status);
|
|
49491
49711
|
mark_owning_auto_init_params(struct, status);
|
|
49492
49712
|
}
|
|
@@ -49502,8 +49722,9 @@ function resolve_struct_field_types(status) {
|
|
|
49502
49722
|
*
|
|
49503
49723
|
* A trait method with a default body need not be overridden; if it isn't,
|
|
49504
49724
|
* 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
|
|
49725
|
+
* onto the struct by synthesize_generic_trait_defaults). `#init`,
|
|
49726
|
+
* `#destroy` and `#spawn` are lifecycle hooks, not contract methods, so
|
|
49727
|
+
* they're skipped.
|
|
49507
49728
|
*/
|
|
49508
49729
|
function check_trait_conformance(struct, status) {
|
|
49509
49730
|
for (let i = 0; i < struct.traits.length; i++) {
|
|
@@ -49514,7 +49735,7 @@ function check_trait_conformance(struct, status) {
|
|
|
49514
49735
|
const substitution = /* @__PURE__ */ new Map();
|
|
49515
49736
|
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
49737
|
for (const trait_func of trait.functions) {
|
|
49517
|
-
if (trait_func.name === "#init" || trait_func.name === "#destroy") continue;
|
|
49738
|
+
if (trait_func.name === "#init" || trait_func.name === "#destroy" || trait_func.name === "#spawn") continue;
|
|
49518
49739
|
const overrides = struct.functions.filter((f) => f.name === trait_func.name);
|
|
49519
49740
|
if (overrides.length === 0) {
|
|
49520
49741
|
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 +50486,6 @@ function check_function_node(func, status) {
|
|
|
50265
50486
|
}
|
|
50266
50487
|
}
|
|
50267
50488
|
//#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
50489
|
//#region ../src/check/utils/enum_mono.ts
|
|
50317
50490
|
/**
|
|
50318
50491
|
* Monomorphize a generic enum (`enum Result<T, E>`) applied to concrete type
|
|
@@ -50496,39 +50669,37 @@ function synthesize_lambda_name(func, status) {
|
|
|
50496
50669
|
//#endregion
|
|
50497
50670
|
//#region ../src/check/utils/validate_spawn_args_sendable.ts
|
|
50498
50671
|
/**
|
|
50499
|
-
* Validate a spawn's packed arguments (CLOSURE.md Phase 3d
|
|
50672
|
+
* Validate a spawn's packed arguments (CLOSURE.md Phase 3d; the shrunk
|
|
50673
|
+
* `Sendable` — docs/ASYNC.md, "Design decisions").
|
|
50500
50674
|
*
|
|
50501
|
-
*
|
|
50502
|
-
*
|
|
50503
|
-
*
|
|
50504
|
-
*
|
|
50505
|
-
*
|
|
50506
|
-
*
|
|
50507
|
-
*
|
|
50675
|
+
* `Sendable` gates exactly the case that can race: a SHARED class/trait
|
|
50676
|
+
* reference crossing the task boundary (a plain argument — the packed env
|
|
50677
|
+
* aliases the instance). Everything else is exempt:
|
|
50678
|
+
*
|
|
50679
|
+
* - MOVED arguments: the callee's `move T` parameter takes exclusive
|
|
50680
|
+
* ownership, so no one else can touch the instance concurrently;
|
|
50681
|
+
* - scalars, strings, and everything `is_sendable_type` accepts (copied or
|
|
50682
|
+
* safe by value — strings are deep-copied at pack);
|
|
50683
|
+
* - owning VALUE structs whose fields are all safe (the pack deep-copies
|
|
50684
|
+
* them; `is_sendable_type`'s field recursion rejects a struct with an
|
|
50685
|
+
* unsafe class field).
|
|
50686
|
+
*
|
|
50687
|
+
* The nursery-borrow exception (a non-Sendable class aliased into a task
|
|
50688
|
+
* inside `async { }`) is RETIRED: mark the class `Sendable`, move it in
|
|
50689
|
+
* (a `move` parameter), or share it through a `Sendable` primitive
|
|
50690
|
+
* (`Mutex`/`Channel`).
|
|
50508
50691
|
*/
|
|
50509
|
-
function validate_spawn_args_sendable(call, status,
|
|
50510
|
-
const
|
|
50511
|
-
for (
|
|
50692
|
+
function validate_spawn_args_sendable(call, status, reason) {
|
|
50693
|
+
const callee_params = call.resolved_function?.params?.filter((p) => !p.is_self_param);
|
|
50694
|
+
for (let i = 0; i < call.params.length; i++) {
|
|
50695
|
+
const param = call.params[i];
|
|
50512
50696
|
let arg_type = type_from_value_node(param, status);
|
|
50513
50697
|
const stamped = param.type;
|
|
50514
50698
|
if (!arg_type.name && stamped?.name) arg_type = stamped;
|
|
50699
|
+
if (callee_params?.[i]?.is_moved) continue;
|
|
50515
50700
|
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);
|
|
50701
|
+
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
50702
|
}
|
|
50531
|
-
return result;
|
|
50532
50703
|
}
|
|
50533
50704
|
//#endregion
|
|
50534
50705
|
//#region ../src/check/check_function_call_node.ts
|
|
@@ -50548,28 +50719,17 @@ function find_free_function(status, name) {
|
|
|
50548
50719
|
return status.functions.findLast((f) => f.name === name && !is_method(f)) ?? status.functions.findLast((f) => f.name === name);
|
|
50549
50720
|
}
|
|
50550
50721
|
function check_function_call_node(node, status) {
|
|
50551
|
-
const magic_ctor = node.name
|
|
50552
|
-
const thread_shadow_struct = resolve_declared_struct("Thread", status);
|
|
50722
|
+
const magic_ctor = resolve_declared_struct(node.name, status)?.functions.some((f) => f.name === "#spawn") ? node.name : void 0;
|
|
50553
50723
|
const first = node.params[0];
|
|
50554
50724
|
const first_decl = first?.node_type === "value" && typeof first.value === "string" ? status.values.findLast((v) => v.name === first.value) : void 0;
|
|
50555
50725
|
const first_fn = first?.node_type === "value" && typeof first.value === "string" ? status.functions.findLast((f) => f.name === first.value) : void 0;
|
|
50556
50726
|
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
50727
|
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
|
-
}
|
|
50728
|
+
if (magic_ctor && !spawn_ctor_shape && !find_free_function(status, node.name)) {
|
|
50729
|
+
add_error(status, "`#spawn` expects a call or a zero-argument function value (bind arguments in the lambda's captures)", first?.start ?? node.start);
|
|
50730
|
+
return false;
|
|
50570
50731
|
}
|
|
50571
|
-
if (magic_ctor && spawn_ctor_shape
|
|
50572
|
-
if (awaitable_ctor) return check_magic_ctor(node, status, awaitable_ctor.name);
|
|
50732
|
+
if (magic_ctor && spawn_ctor_shape) return check_magic_ctor(node, status, magic_ctor);
|
|
50573
50733
|
let func = find_free_function(status, node.name);
|
|
50574
50734
|
if (func?.is_closure && func.captures?.length) func = void 0;
|
|
50575
50735
|
if (!func) {
|
|
@@ -50734,7 +50894,18 @@ function monomorphize(generic_struct, type_args, status) {
|
|
|
50734
50894
|
const mono_fields = generic_struct.fields.map((field) => {
|
|
50735
50895
|
const resolved_type = substitute_type(field.type, substitution);
|
|
50736
50896
|
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)
|
|
50897
|
+
if (mono_field.value) {
|
|
50898
|
+
if (!mono_field.type.name && mono_field.value.node_type === "func_call") {
|
|
50899
|
+
const call = mono_field.value;
|
|
50900
|
+
const target = status.structs.find((s) => s.name === call.name);
|
|
50901
|
+
if (target?.type_params.length && call.type_args?.length === target.type_params.length) {
|
|
50902
|
+
const constructed = new Type(call.name);
|
|
50903
|
+
constructed.type_args = call.type_args.map((arg) => substitute_type(arg, substitution));
|
|
50904
|
+
mono_field.type = constructed;
|
|
50905
|
+
}
|
|
50906
|
+
}
|
|
50907
|
+
substitute_raw_in_node(mono_field.value, substitution, status.structs, void 0, status.traits, status.enums);
|
|
50908
|
+
}
|
|
50738
50909
|
mono_field.is_library = field.is_library;
|
|
50739
50910
|
mono_field.is_readonly = field.is_readonly;
|
|
50740
50911
|
mono_field.is_view_keyword = field.is_view_keyword;
|
|
@@ -50761,6 +50932,7 @@ function monomorphize(generic_struct, type_args, status) {
|
|
|
50761
50932
|
mono_struct.source_type_args = flat_args;
|
|
50762
50933
|
mono_struct.is_class = generic_struct.is_class;
|
|
50763
50934
|
mono_struct.is_library = generic_struct.is_library;
|
|
50935
|
+
mono_struct.trait_args = (generic_struct.trait_args ?? []).map((args) => args?.map((t) => substitute_type(t, substitution)));
|
|
50764
50936
|
status.structs.push(mono_struct);
|
|
50765
50937
|
status.types.push(mono_name);
|
|
50766
50938
|
const cloned_methods = [];
|
|
@@ -51215,14 +51387,7 @@ function resolve_free_calls_in_node(node, status) {
|
|
|
51215
51387
|
const any_node = node;
|
|
51216
51388
|
if (node.node_type === "func_call" && !any_node.resolved_function) {
|
|
51217
51389
|
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
|
-
}
|
|
51390
|
+
if (resolve_declared_struct(name, status)?.functions.some((f) => f.name === "#spawn")) rederive_magic_ctor_annotations(node, status, name);
|
|
51226
51391
|
const func = find_free_function(status, name);
|
|
51227
51392
|
if (func) set_resolved_function(node, func);
|
|
51228
51393
|
}
|
|
@@ -51281,18 +51446,6 @@ function derive_annotations_for_access_func(access_node, fc, status) {
|
|
|
51281
51446
|
rederive_nursery_spawn_annotations(fc, status);
|
|
51282
51447
|
return;
|
|
51283
51448
|
}
|
|
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
51449
|
const struct = status.structs.find((s) => s.name === receiver_type);
|
|
51297
51450
|
if (!struct) return;
|
|
51298
51451
|
const func = struct.functions.find((f) => f.name === fc.name);
|
|
@@ -51377,7 +51530,7 @@ function check_magic_ctor(node, status, name) {
|
|
|
51377
51530
|
add_error(status, `Spawned call '${call.name}' did not resolve`, node.start);
|
|
51378
51531
|
return false;
|
|
51379
51532
|
}
|
|
51380
|
-
validate_spawn_args_sendable(call, status
|
|
51533
|
+
validate_spawn_args_sendable(call, status);
|
|
51381
51534
|
stamp_spawn_ctor(node, name);
|
|
51382
51535
|
const return_type = call.type;
|
|
51383
51536
|
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 +51555,8 @@ function stamp_spawn_ctor(node, name) {
|
|
|
51402
51555
|
* nothing) — carries T on the type args, which the `.start()` /
|
|
51403
51556
|
* nursery paths read. Also materializes Task<T>: the launch seam a user
|
|
51404
51557
|
* primitive drives is Task's runtime (Task.pool_submit / future_* —
|
|
51405
|
-
* ASYNC.md, "
|
|
51558
|
+
* docs/ASYNC.md, "Extension: a class with its own `#spawn`"), and
|
|
51559
|
+
* statics on the bare
|
|
51406
51560
|
* generic only link once a mono instantiation exists (the same
|
|
51407
51561
|
* materialization a `.start()` launch performs). */
|
|
51408
51562
|
function stamp_spawn_type_args(node, status, name, result_type_arg) {
|
|
@@ -51554,61 +51708,6 @@ function rederive_nursery_spawn_annotations(fc, status) {
|
|
|
51554
51708
|
if (task_struct && task_struct.type_params.length > 0) monomorphize(task_struct, [result_type_arg], status);
|
|
51555
51709
|
}
|
|
51556
51710
|
/**
|
|
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
51711
|
* Substitute type-param names through a function-typed parameter's nested
|
|
51613
51712
|
* signature (`func (T, out T)` on a generic struct method or generic free
|
|
51614
51713
|
* function). The signature hangs off the ParameterNode itself (`func_params`
|
|
@@ -51918,6 +52017,15 @@ function substitute_raw_in_node(node, substitution, structs, deref_params = /* @
|
|
|
51918
52017
|
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
52018
|
} 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
52019
|
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));
|
|
52020
|
+
if (node.node_type === "declare" && !any_node.type?.name && any_node.value?.node_type === "func_call") {
|
|
52021
|
+
const call = any_node.value;
|
|
52022
|
+
const target = structs.find((s) => s.name === call.name);
|
|
52023
|
+
if (target?.type_params.length && call.type_args?.length === target.type_params.length) {
|
|
52024
|
+
const constructed = new Type(call.name);
|
|
52025
|
+
constructed.type_args = call.type_args;
|
|
52026
|
+
any_node.type = constructed;
|
|
52027
|
+
}
|
|
52028
|
+
}
|
|
51921
52029
|
if (node.node_type === "declare" && any_node.type?.name) {
|
|
51922
52030
|
any_node.type = substitute_type(any_node.type, substitution);
|
|
51923
52031
|
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 +53392,11 @@ function check_access_node(node, status) {
|
|
|
53284
53392
|
status.allocations.push(decl);
|
|
53285
53393
|
node.target = new ValueNode(node.target.start, decl.name, target_type);
|
|
53286
53394
|
}
|
|
53395
|
+
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)) {
|
|
53396
|
+
const decl = new DeclarationNode(node.target.start, "private", "var", `_recv_${status.var_name_counter.value++}`, target_type, node.target);
|
|
53397
|
+
status.allocations.push(decl);
|
|
53398
|
+
node.target = new ValueNode(node.target.start, decl.name, target_type);
|
|
53399
|
+
}
|
|
53287
53400
|
if (node.access.node_type === "access_field") {
|
|
53288
53401
|
const af = node.access;
|
|
53289
53402
|
if (af.is_destructure) {
|
|
@@ -53513,9 +53626,11 @@ function check_access_field_node(target_type, target, node, status) {
|
|
|
53513
53626
|
}
|
|
53514
53627
|
function check_access_function_node(target_type, target, node, status) {
|
|
53515
53628
|
if (target_type.name === "Nursery" && node.name === "start") return check_nursery_spawn(node, status);
|
|
53516
|
-
if (target_type.name === "
|
|
53517
|
-
|
|
53518
|
-
|
|
53629
|
+
if (target_type.name === "Fiber" && node.name === "start_on") {
|
|
53630
|
+
if (!check_spawn_start_on(target, target_type, node, status)) return false;
|
|
53631
|
+
node.name = "start";
|
|
53632
|
+
node.params = [];
|
|
53633
|
+
}
|
|
53519
53634
|
if (target_type.is_view) {
|
|
53520
53635
|
const elem_name = target_type.name === "string" ? "char" : target_type.name;
|
|
53521
53636
|
if (node.name === "at" && node.params.length === 1) {
|
|
@@ -53788,77 +53903,32 @@ function check_nursery_spawn(node, status) {
|
|
|
53788
53903
|
return true;
|
|
53789
53904
|
}
|
|
53790
53905
|
/**
|
|
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);
|
|
53906
|
+
* Validate a `Fiber(fn(args)).start_on(buf)` call's stack buffer (see
|
|
53907
|
+
* ASYNC.md): a fixed-size array of at least 16 KB. The caller rewrites the
|
|
53908
|
+
* access to the ordinary Fiber.start afterwards — this function is
|
|
53909
|
+
* validation only.
|
|
53910
|
+
*/
|
|
53911
|
+
function check_spawn_start_on(target, target_type, node, status) {
|
|
53912
|
+
if (node.params.length !== 1) {
|
|
53913
|
+
add_error(status, `start_on expects a stack buffer argument, e.g. Fiber(work(n)).start_on(buf)`, node.start);
|
|
53807
53914
|
return false;
|
|
53808
53915
|
}
|
|
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
|
-
}
|
|
53916
|
+
const buf_type = type_from_value_node(node.params[0], status);
|
|
53917
|
+
if (!buf_type.is_array) {
|
|
53918
|
+
add_error(status, `start_on expects a fixed-size array stack buffer (T[N]), got ${buf_type.name || "<unknown>"}`, node.params[0].start);
|
|
53919
|
+
return false;
|
|
53920
|
+
}
|
|
53921
|
+
const elem_size = buf_type.name === "string" ? 16 : 8;
|
|
53922
|
+
const len_node = buf_type.length;
|
|
53923
|
+
const len = len_node && len_node.node_type === "value" ? parseInt(len_node.value, 10) : NaN;
|
|
53924
|
+
if (Number.isNaN(len)) {
|
|
53925
|
+
add_error(status, "start_on expects a fixed-size array (T[N]); the length must be known at compile time", node.params[0].start);
|
|
53926
|
+
return false;
|
|
53927
|
+
}
|
|
53928
|
+
if (len * elem_size < 16384) {
|
|
53929
|
+
add_error(status, `Fiber stack too small: ${len * elem_size} bytes, minimum is 16384`, node.params[0].start);
|
|
53930
|
+
return false;
|
|
53831
53931
|
}
|
|
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
53932
|
return true;
|
|
53863
53933
|
}
|
|
53864
53934
|
//#endregion
|
|
@@ -55483,7 +55553,13 @@ function check_return_node(ret, status) {
|
|
|
55483
55553
|
} else if (!func.is_arrow_body) {
|
|
55484
55554
|
add_error(status, `Function returns a value but has no 'out' return type`, ret.start);
|
|
55485
55555
|
func.return_type = ret.type;
|
|
55486
|
-
} else
|
|
55556
|
+
} else {
|
|
55557
|
+
func.return_type = ret.type;
|
|
55558
|
+
if (!ret.type?.name) {
|
|
55559
|
+
func.return_type = new Type("void");
|
|
55560
|
+
ret.type = new Type("void");
|
|
55561
|
+
}
|
|
55562
|
+
}
|
|
55487
55563
|
}
|
|
55488
55564
|
}
|
|
55489
55565
|
//#endregion
|
|
@@ -57545,7 +57621,7 @@ function parse_function(visibility, status, name_override, is_inline, is_extern,
|
|
|
57545
57621
|
if (op_internal[name]) name = op_internal[name];
|
|
57546
57622
|
const parent_for_type = status.stack.at(-1);
|
|
57547
57623
|
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);
|
|
57624
|
+
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
57625
|
const func = new FunctionNode(start, visibility, name, return_type);
|
|
57550
57626
|
if (is_inline) func.is_inline = true;
|
|
57551
57627
|
if (is_unsafe) {
|
|
@@ -57565,7 +57641,7 @@ function parse_function(visibility, status, name_override, is_inline, is_extern,
|
|
|
57565
57641
|
if (expect("=", status) && expect("(", status)) {
|
|
57566
57642
|
const parent = status.stack.at(-1);
|
|
57567
57643
|
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)) {
|
|
57644
|
+
if ((name === "#destroy" || name === "#spawn") && parent.node_type === "struct" && !func.params.some((p) => p.is_self_param)) {
|
|
57569
57645
|
const self_param = new ParameterNode(start, "self", new Type(parent.name));
|
|
57570
57646
|
self_param.is_self_param = true;
|
|
57571
57647
|
self_param.declaration = "var";
|
|
@@ -57597,7 +57673,7 @@ function parse_function(visibility, status, name_override, is_inline, is_extern,
|
|
|
57597
57673
|
parse_statement(status);
|
|
57598
57674
|
expect("}", status);
|
|
57599
57675
|
status.stack.pop();
|
|
57600
|
-
if (name !== "#init" && name !== "#destroy") check_missing_return(func, status);
|
|
57676
|
+
if (name !== "#init" && name !== "#destroy" && name !== "#spawn") check_missing_return(func, status);
|
|
57601
57677
|
}
|
|
57602
57678
|
switch (parent.node_type) {
|
|
57603
57679
|
case "root":
|
|
@@ -57782,7 +57858,7 @@ function parse_struct(visibility, status, is_class = false) {
|
|
|
57782
57858
|
parse_statement(status);
|
|
57783
57859
|
expect("}", status);
|
|
57784
57860
|
status.stack.pop();
|
|
57785
|
-
if (!struct.functions.some((f) => f.name === "#init")) {
|
|
57861
|
+
if (!struct.functions.some((f) => f.name === "#init" || f.name === "#spawn")) {
|
|
57786
57862
|
const func = new FunctionNode(-1, visibility, "#init", new Type(struct.name));
|
|
57787
57863
|
func.params = struct.fields.filter((f) => f.visibility !== "private" && !f.value).map((f) => {
|
|
57788
57864
|
const param = new ParameterNode(-1, f.name, f.func_params ? new Type("func") : f.type);
|
|
@@ -58071,8 +58147,13 @@ function parse_visibility(visibility, status) {
|
|
|
58071
58147
|
consume(status);
|
|
58072
58148
|
consume(status);
|
|
58073
58149
|
parse_function(visibility, status, "#destroy");
|
|
58150
|
+
} else if (next2 === "spawn") {
|
|
58151
|
+
consume(status);
|
|
58152
|
+
consume(status);
|
|
58153
|
+
consume(status);
|
|
58154
|
+
parse_function(visibility, status, "#spawn");
|
|
58074
58155
|
} else {
|
|
58075
|
-
add_error(status, `Expected #init or #
|
|
58156
|
+
add_error(status, `Expected #init, #destroy or #spawn after pub`, get_index(status));
|
|
58076
58157
|
consume(status);
|
|
58077
58158
|
}
|
|
58078
58159
|
break;
|
|
@@ -58199,8 +58280,12 @@ function parse_statement(status) {
|
|
|
58199
58280
|
consume(status);
|
|
58200
58281
|
consume(status);
|
|
58201
58282
|
parse_destroy(default_visibility(status), status, "#destroy");
|
|
58283
|
+
} else if (next === "spawn") {
|
|
58284
|
+
consume(status);
|
|
58285
|
+
consume(status);
|
|
58286
|
+
parse_destroy(default_visibility(status), status, "#spawn");
|
|
58202
58287
|
} else {
|
|
58203
|
-
add_error(status, `Expected #init or #
|
|
58288
|
+
add_error(status, `Expected #init, #destroy or #spawn`, get_index(status));
|
|
58204
58289
|
consume(status);
|
|
58205
58290
|
}
|
|
58206
58291
|
break;
|
|
@@ -58442,15 +58527,28 @@ function resolve_linked_types(source, library, file_path) {
|
|
|
58442
58527
|
}
|
|
58443
58528
|
const user_declared_funcs = /* @__PURE__ */ new Set();
|
|
58444
58529
|
for (let i = 0; i < tokens.length - 1; i++) if (tokens[i].value === "func" && /^\w+$/.test(tokens[i + 1].value)) user_declared_funcs.add(tokens[i + 1].value);
|
|
58445
|
-
|
|
58530
|
+
const import_tokens = /* @__PURE__ */ new Set();
|
|
58531
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
58532
|
+
if (tokens[i].value !== "import") continue;
|
|
58533
|
+
import_tokens.add(i);
|
|
58534
|
+
let j = i + 1;
|
|
58535
|
+
if (j < tokens.length && /^\w+$/.test(tokens[j].value)) {
|
|
58536
|
+
import_tokens.add(j);
|
|
58537
|
+
j += 1;
|
|
58538
|
+
while (j + 1 < tokens.length && tokens[j].value === "::" && /^\w+$/.test(tokens[j + 1].value)) {
|
|
58539
|
+
import_tokens.add(j);
|
|
58540
|
+
import_tokens.add(j + 1);
|
|
58541
|
+
j += 2;
|
|
58542
|
+
}
|
|
58543
|
+
}
|
|
58544
|
+
}
|
|
58545
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
58546
|
+
if (import_tokens.has(i)) continue;
|
|
58547
|
+
const token = tokens[i];
|
|
58446
58548
|
if (user_defined.has(token.value)) continue;
|
|
58447
58549
|
if (library.types.has(token.value) && !BASE_TYPES.includes(token.value)) needed.add(token.value);
|
|
58448
58550
|
else if (library.functions.has(token.value) && !user_declared_funcs.has(token.value)) needed.add(token.value);
|
|
58449
58551
|
}
|
|
58450
|
-
if (tokens.some((t) => t.value === "Thread" || t.value === "Fiber" || t.value === "async")) {
|
|
58451
|
-
needed.add("Task");
|
|
58452
|
-
needed.add("Sendable");
|
|
58453
|
-
}
|
|
58454
58552
|
if (tokens.some((t) => t.value === "async")) needed.add("Nursery");
|
|
58455
58553
|
const resolved = resolve_types_with_deps(needed, library, is_library_file ? file_path : void 0);
|
|
58456
58554
|
if (!resolved) return source;
|
|
@@ -59287,7 +59385,8 @@ function run_test_file(entry_path, lib_path, arch, audit = false, audit_runtime)
|
|
|
59287
59385
|
audit_obj
|
|
59288
59386
|
]);
|
|
59289
59387
|
}
|
|
59290
|
-
const
|
|
59388
|
+
const platform = default_platform();
|
|
59389
|
+
const ext = arch === "aarch64" ? ".s" : platform === "macos" || platform === "ios" ? ".m" : ".c";
|
|
59291
59390
|
const codefile = path.join(buildDir, path.basename(entry_path, ".nm") + ext);
|
|
59292
59391
|
const outfile = path.join(buildDir, path.basename(entry_path, ".nm"));
|
|
59293
59392
|
fs.writeFileSync(path.join(buildDir, "main.h"), buildResult.headers ?? "");
|
|
@@ -59298,6 +59397,7 @@ function run_test_file(entry_path, lib_path, arch, audit = false, audit_runtime)
|
|
|
59298
59397
|
codefile
|
|
59299
59398
|
];
|
|
59300
59399
|
if (audit_obj) link_args.push(audit_obj);
|
|
59400
|
+
if (platform === "macos" || platform === "ios") link_args.push("-framework", "CoreGraphics", "-framework", "Foundation", "-framework", "AppKit", "-lobjc");
|
|
59301
59401
|
try {
|
|
59302
59402
|
execFileSync("clang", link_args, {
|
|
59303
59403
|
encoding: "utf8",
|