nomen-lang 0.7.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/System/Buffer.nm +32 -0
- package/core/System/ClassBuffer.nm +24 -0
- package/core/System/Controls/Alert.nm +326 -0
- package/core/System/Controls/App.nm +147 -0
- package/core/System/Controls/Button.nm +119 -1
- package/core/System/Controls/CheckBox.nm +24 -0
- package/core/System/Controls/ColorPicker.nm +307 -0
- package/core/System/Controls/ComboBox.nm +430 -0
- package/core/System/Controls/Container.nm +837 -9
- package/core/System/Controls/Control.nm +443 -0
- package/core/System/Controls/DatePicker.nm +288 -0
- package/core/System/Controls/Form.nm +70 -0
- package/core/System/Controls/GroupBox.nm +267 -0
- package/core/System/Controls/ImageView.nm +311 -0
- package/core/System/Controls/LevelIndicator.nm +305 -0
- package/core/System/Controls/ListView.nm +633 -0
- package/core/System/Controls/Menu.nm +428 -0
- package/core/System/Controls/NumberInput.nm +56 -0
- package/core/System/Controls/OtpInput.nm +50 -0
- package/core/System/Controls/OutlineView.nm +598 -0
- package/core/System/Controls/Pagination.nm +142 -0
- package/core/System/Controls/Popover.nm +296 -0
- package/core/System/Controls/Progress.nm +348 -0
- package/core/System/Controls/Radio.nm +292 -0
- package/core/System/Controls/Scrollable.nm +584 -0
- package/core/System/Controls/SearchField.nm +326 -0
- package/core/System/Controls/SecureField.nm +344 -0
- package/core/System/Controls/Segmented.nm +380 -0
- package/core/System/Controls/Select.nm +340 -0
- package/core/System/Controls/Separator.nm +198 -0
- package/core/System/Controls/Sheet.nm +329 -0
- package/core/System/Controls/Slider.nm +315 -0
- package/core/System/Controls/Spinner.nm +287 -0
- package/core/System/Controls/SplitView.nm +509 -0
- package/core/System/Controls/Stepper.nm +349 -0
- package/core/System/Controls/Switch.nm +259 -0
- package/core/System/Controls/Table.nm +833 -0
- package/core/System/Controls/Tabs.nm +500 -0
- package/core/System/Controls/Text.nm +108 -11
- package/core/System/Controls/TextBox.nm +112 -1
- package/core/System/Controls/TextView.nm +329 -0
- package/core/System/Controls/Toggle.nm +346 -0
- package/core/System/Controls/Toolbar.nm +501 -0
- package/core/System/Controls/Tooltip.nm +76 -0
- package/core/System/Controls/Window.nm +1549 -91
- package/core/System/List.nm +34 -0
- package/core/System/Map.nm +21 -4
- package/core/System/Set.nm +14 -0
- package/dist/index.mjs +2556 -656
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -2367,11 +2367,7 @@ function parse_operand(tok) {
|
|
|
2367
2367
|
if (/^[A-Za-z_.$][\w.$]*@[A-Za-z0-9_]+$/.test(trimmed)) return "labelish";
|
|
2368
2368
|
return null;
|
|
2369
2369
|
}
|
|
2370
|
-
|
|
2371
|
-
* Validate emitted assembly. Returns every problem found (empty array when
|
|
2372
|
-
* clean). Never throws — malformed input IS the input here.
|
|
2373
|
-
*/
|
|
2374
|
-
function validate_asm(code) {
|
|
2370
|
+
function validate_asm(code, options = {}) {
|
|
2375
2371
|
const errors = [];
|
|
2376
2372
|
const lines = code.split("\n");
|
|
2377
2373
|
const labels = /* @__PURE__ */ new Set();
|
|
@@ -2457,7 +2453,7 @@ function validate_asm(code) {
|
|
|
2457
2453
|
if ((op === "b" || op.startsWith("b.") || op === "cbz" || op === "cbnz") && labelish_count > 0) {
|
|
2458
2454
|
const target = operands.find((o) => o.kind === "label").name;
|
|
2459
2455
|
const num = /^(\d+)([fb])$/.exec(target);
|
|
2460
|
-
if (!(num ? (numeric_labels.get(num[1]) ?? []).some((line) => num[2] === "f" ? line > i : line < i) : labels.has(target))) err(`branch to undefined label '${target}'`);
|
|
2456
|
+
if (!(num ? (numeric_labels.get(num[1]) ?? []).some((line) => num[2] === "f" ? line > i : line < i) : labels.has(target)) && !options.allow_external_branch_targets) err(`branch to undefined label '${target}'`);
|
|
2461
2457
|
}
|
|
2462
2458
|
}
|
|
2463
2459
|
return errors;
|
|
@@ -2795,8 +2791,8 @@ function describe$1(operands) {
|
|
|
2795
2791
|
* function entry (bl-target / globl / boundary-heuristic labels). Validation
|
|
2796
2792
|
* errors are returned alongside — callers should check them first.
|
|
2797
2793
|
*/
|
|
2798
|
-
function lift_functions(code) {
|
|
2799
|
-
const errors = validate_asm(code);
|
|
2794
|
+
function lift_functions(code, options = {}) {
|
|
2795
|
+
const errors = validate_asm(code, options);
|
|
2800
2796
|
const lines = [];
|
|
2801
2797
|
const functions = [];
|
|
2802
2798
|
const lines_split = code.split("\n");
|
|
@@ -4318,20 +4314,42 @@ function convert_loop_invariant_branches(code) {
|
|
|
4318
4314
|
* ldp/stp pairs) → the x17 scratch: `mov x17, #big` (+ `add x17, x29, x17`
|
|
4319
4315
|
* for the pair forms, which have no register-offset encoding).
|
|
4320
4316
|
*
|
|
4317
|
+
* The same limits bite RECEIVER-relative field accesses: `[xN, #big]` where
|
|
4318
|
+
* xN holds a struct pointer (x19 in methods), and `add xD, xN, #big` for the
|
|
4319
|
+
* address-of form. A struct past ~4-7 KB of fields crosses the encodings the
|
|
4320
|
+
* moment any field sits far enough into the body (scaled x-loads reach 32760,
|
|
4321
|
+
* but sub-word scaled forms and `add` top out at 4095, ldur/stur at 255, and
|
|
4322
|
+
* ldp/stp at 504). These rewrite exactly like the frame forms, except the
|
|
4323
|
+
* base register is a live receiver that must survive:
|
|
4324
|
+
*
|
|
4325
|
+
* - `add xD, xN, #big` with xD != xN → the destination stages its own
|
|
4326
|
+
* address (`mov xD, #big; add xD, xN, xD`); with xD == xN the offset goes
|
|
4327
|
+
* through x17 (staging into the destination would destroy the base).
|
|
4328
|
+
* - loads whose destination is an x-register distinct from the base stage
|
|
4329
|
+
* their own address; everything else goes through x17, including pair
|
|
4330
|
+
* forms (`mov x17, #big; add x17, xN, x17; ldp a, b, [x17]`).
|
|
4331
|
+
* - Scaled accesses rewrite only past their TRUE encodable range, so in-range
|
|
4332
|
+
* code (e.g. `str x3, [x19, #4104]`) stays single-instruction.
|
|
4333
|
+
*
|
|
4321
4334
|
* x17 is safe as the universal scratch here because this pass runs BEFORE the
|
|
4322
4335
|
* optimization pipeline: the passes that claim x16/x17 (loop promotion,
|
|
4323
4336
|
* select registers, pointer walks) validate their claim against the function
|
|
4324
4337
|
* text and refuse when x17 already appears — losing an optimization on
|
|
4325
4338
|
* large-frame functions, which is the correct trade. Raw `#arch: aarch64`
|
|
4326
4339
|
* bodies in the core library never hold a live value in x17 across an
|
|
4327
|
-
* out-of-range
|
|
4328
|
-
*
|
|
4329
|
-
*
|
|
4340
|
+
* out-of-range access (the corpus has none: such accesses did not assemble
|
|
4341
|
+
* before this pass existed). x17 is never live across a `bl` by the backend's
|
|
4342
|
+
* own convention, and the shims contain no calls. The receiver register is
|
|
4343
|
+
* never x17 (emitters keep receivers in x0–x16/x19–x28), so staging into x17
|
|
4344
|
+
* cannot clobber the base; a base-x17 line is left untouched rather than
|
|
4345
|
+
* mis-staged.
|
|
4330
4346
|
*/
|
|
4331
4347
|
/** First scratch that no emitter or raw body relies on across statements. */
|
|
4332
4348
|
const SCRATCH = "x17";
|
|
4333
|
-
/** The imm12 ceiling shared by `add imm` and the scaled load/store
|
|
4349
|
+
/** The imm12 ceiling shared by `add imm` and the byte-scaled load/store. */
|
|
4334
4350
|
const IMM12_MAX = 4095;
|
|
4351
|
+
/** simm9 ceiling (non-negative side) for the unscaled ldur/stur family. */
|
|
4352
|
+
const SIMM9_MAX = 255;
|
|
4335
4353
|
/** mov (movz/movk expanded) that materializes any non-negative constant. */
|
|
4336
4354
|
function emit_mov_imm(reg, imm) {
|
|
4337
4355
|
if (imm <= 65535) return [`mov ${reg}, #${imm}`];
|
|
@@ -4366,6 +4384,44 @@ const SP_ADJ_RE = /^(sub|add) sp, sp, #(\d+)$/;
|
|
|
4366
4384
|
/** True for full 64-bit integer registers usable as their own address scratch
|
|
4367
4385
|
* (xzr excluded: `ldr xzr` still performs the load, and xzr can't stage). */
|
|
4368
4386
|
const IS_X_REG = /^x([0-9]|[12][0-9]|3[01])$/;
|
|
4387
|
+
const X_BASE = "(x(?:[0-9]|[12][0-9]|3[01]))";
|
|
4388
|
+
const MEM_RECV_RE = new RegExp(`^(ldr|ldrb|ldrh|ldrsb|ldrsh|ldrsw|ldur|ldurb|ldurh|ldursb|ldursh|ldursw|str|strb|strh|stur|sturb|sturh) ([a-z][a-z0-9]*), \\[${X_BASE}, #(\\d+)\\](\\s*\\/\\/.*)?$`);
|
|
4389
|
+
const PAIR_RECV_RE = new RegExp(`^(ldp|stp) ([^,]+), ([^,]+), \\[${X_BASE}, #(\\d+)\\](\\s*\\/\\/.*)?$`);
|
|
4390
|
+
const ADD_RECV_RE = new RegExp(`^add ${X_BASE}, ${X_BASE}, #(\\d+)(\\s*\\/\\/.*)?$`);
|
|
4391
|
+
/** Register-offset spelling of the unscaled ldur/stur family: the unscaled
|
|
4392
|
+
* encodings have no register-offset form, so an out-of-range unscaled access
|
|
4393
|
+
* rewrites to its scaled mnemonic (identical semantics — the immediate is
|
|
4394
|
+
* non-negative, so the missing sign-extension reach is unused). */
|
|
4395
|
+
const SCALED_OF = {
|
|
4396
|
+
ldur: "ldr",
|
|
4397
|
+
stur: "str",
|
|
4398
|
+
ldurb: "ldrb",
|
|
4399
|
+
sturb: "strb",
|
|
4400
|
+
ldurh: "ldrh",
|
|
4401
|
+
sturh: "strh",
|
|
4402
|
+
ldursb: "ldrsb",
|
|
4403
|
+
ldursh: "ldrsh",
|
|
4404
|
+
ldursw: "ldrsw"
|
|
4405
|
+
};
|
|
4406
|
+
/** Largest non-negative immediate a SCALED load/store encodes: imm12 scaled
|
|
4407
|
+
* by the access width (byte 4095 … quad 65520). The width comes from the
|
|
4408
|
+
* mnemonic's suffix where present, else the destination register's class.
|
|
4409
|
+
*/
|
|
4410
|
+
function scaled_imm_max(op, reg) {
|
|
4411
|
+
if (op.endsWith("b")) return IMM12_MAX;
|
|
4412
|
+
if (op.endsWith("h")) return IMM12_MAX * 2;
|
|
4413
|
+
if (op === "ldrsw") return IMM12_MAX * 4;
|
|
4414
|
+
if (reg.startsWith("q")) return IMM12_MAX * 16;
|
|
4415
|
+
if (reg.startsWith("x") || reg.startsWith("d")) return IMM12_MAX * 8;
|
|
4416
|
+
if (reg.startsWith("w") || reg.startsWith("s")) return IMM12_MAX * 4;
|
|
4417
|
+
if (reg.startsWith("h")) return IMM12_MAX * 2;
|
|
4418
|
+
return IMM12_MAX;
|
|
4419
|
+
}
|
|
4420
|
+
/** Largest non-negative immediate a pair form encodes: simm7 scaled by the
|
|
4421
|
+
* element size (8-byte pairs 504, 16-byte pairs 1008). */
|
|
4422
|
+
function pair_imm_max(reg) {
|
|
4423
|
+
return 63 * (reg.startsWith("q") ? 16 : reg.startsWith("w") || reg.startsWith("s") ? 4 : 8);
|
|
4424
|
+
}
|
|
4369
4425
|
function rewrite_large_frame_offsets(asm) {
|
|
4370
4426
|
const lines = asm.split("\n");
|
|
4371
4427
|
const out = [];
|
|
@@ -4403,6 +4459,42 @@ function rewrite_large_frame_offsets(asm) {
|
|
|
4403
4459
|
out.push(`${op} ${a}, ${b}, [${SCRATCH}]${comment ?? ""}`);
|
|
4404
4460
|
continue;
|
|
4405
4461
|
}
|
|
4462
|
+
const add_recv = ADD_RECV_RE.exec(line.trim());
|
|
4463
|
+
if (add_recv && Number(add_recv[3]) > IMM12_MAX) {
|
|
4464
|
+
const [, dest, base, imm_s, comment] = add_recv;
|
|
4465
|
+
const imm = Number(imm_s);
|
|
4466
|
+
if (base !== SCRATCH) {
|
|
4467
|
+
const stage = dest !== base ? dest : SCRATCH;
|
|
4468
|
+
for (const l of emit_mov_imm(stage, imm)) out.push(l);
|
|
4469
|
+
out.push(`add ${dest}, ${base}, ${stage}${comment ?? ""}`);
|
|
4470
|
+
continue;
|
|
4471
|
+
}
|
|
4472
|
+
}
|
|
4473
|
+
const mem_recv = MEM_RECV_RE.exec(line.trim());
|
|
4474
|
+
if (mem_recv) {
|
|
4475
|
+
const [, op, reg, base, imm_s, comment] = mem_recv;
|
|
4476
|
+
const imm = Number(imm_s);
|
|
4477
|
+
if (imm > (op.startsWith("ldur") || op.startsWith("stur") ? SIMM9_MAX : scaled_imm_max(op, reg)) && base !== SCRATCH) {
|
|
4478
|
+
const is_load = op.startsWith("ldr");
|
|
4479
|
+
const emit_op = SCALED_OF[op] ?? op;
|
|
4480
|
+
if (is_load && IS_X_REG.test(reg) && reg !== base) {
|
|
4481
|
+
for (const l of emit_mov_imm(reg, imm)) out.push(l);
|
|
4482
|
+
out.push(`${emit_op} ${reg}, [${base}, ${reg}]${comment ?? ""}`);
|
|
4483
|
+
} else {
|
|
4484
|
+
for (const l of emit_mov_imm(SCRATCH, imm)) out.push(l);
|
|
4485
|
+
out.push(`${emit_op} ${reg}, [${base}, ${SCRATCH}]${comment ?? ""}`);
|
|
4486
|
+
}
|
|
4487
|
+
continue;
|
|
4488
|
+
}
|
|
4489
|
+
}
|
|
4490
|
+
const pair_recv = PAIR_RECV_RE.exec(line.trim());
|
|
4491
|
+
if (pair_recv && Number(pair_recv[5]) > pair_imm_max(pair_recv[2]) && pair_recv[4] !== SCRATCH) {
|
|
4492
|
+
const [, op, a, b, base, imm_s, comment] = pair_recv;
|
|
4493
|
+
for (const l of emit_mov_imm(SCRATCH, Number(imm_s))) out.push(l);
|
|
4494
|
+
out.push(`add ${SCRATCH}, ${base}, ${SCRATCH}`);
|
|
4495
|
+
out.push(`${op} ${a}, ${b}, [${SCRATCH}]${comment ?? ""}`);
|
|
4496
|
+
continue;
|
|
4497
|
+
}
|
|
4406
4498
|
out.push(line);
|
|
4407
4499
|
}
|
|
4408
4500
|
return out.join("\n");
|
|
@@ -6526,6 +6618,31 @@ var AccessFunctionCallNode = class extends BaseNode {
|
|
|
6526
6618
|
*/
|
|
6527
6619
|
is_nursery_spawn;
|
|
6528
6620
|
/**
|
|
6621
|
+
* Set during checking when this `.on_<event>(closure)` call was
|
|
6622
|
+
* checker-interpreted as a handler registration (docs/HANDLERS.md): the
|
|
6623
|
+
* closure body was hoisted into a hidden method on the enclosing struct
|
|
6624
|
+
* and the site checked into a dispatch arm on the host's synthesized
|
|
6625
|
+
* `handle_clicks`. The registration itself emits NO code — both backends
|
|
6626
|
+
* return as soon as they see the stamp.
|
|
6627
|
+
*/
|
|
6628
|
+
is_handler_registration;
|
|
6629
|
+
/**
|
|
6630
|
+
* Set during checking when this `.on_<event>(closure)` call was
|
|
6631
|
+
* checker-interpreted as a COMPOSITE EVENT registration (docs/HANDLERS.md
|
|
6632
|
+
* Stage 3): the closure was hoisted into a hidden method on the app and
|
|
6633
|
+
* the params were REWRITTEN to the enrollment call — `receiver.on_<event>(id)`
|
|
6634
|
+
* — which resolves as the composite's own declared event API. The stamp
|
|
6635
|
+
* makes the re-entered ordinary resolution skip the desugar hook.
|
|
6636
|
+
*/
|
|
6637
|
+
is_handler_enrollment;
|
|
6638
|
+
/**
|
|
6639
|
+
* Set during checking when a `ref T x = container.at(i)` binding was
|
|
6640
|
+
* rewritten to the element-borrow primitive (`slot_ptr` — docs/HANDLERS.md
|
|
6641
|
+
* Stage 4): the call RETURNS the slot pointer, so the ref-binding build
|
|
6642
|
+
* emits it bare (no `&`).
|
|
6643
|
+
*/
|
|
6644
|
+
is_element_slot_load;
|
|
6645
|
+
/**
|
|
6529
6646
|
* RETIRED with the pre-migration launch dispatch: `.start()`/`.detach()`
|
|
6530
6647
|
* are ordinary methods on the library `Thread` class now (docs/ASYNC.md).
|
|
6531
6648
|
* The flags remain only because monomorphized clones can carry them from
|
|
@@ -6949,23 +7066,19 @@ function find_enum(name, status) {
|
|
|
6949
7066
|
/**
|
|
6950
7067
|
* Whether `name` is a monomorphized mv-container struct whose
|
|
6951
7068
|
* `<Mono>_copy` is safe to call from the mv-`Buffer.load_T`
|
|
6952
|
-
* specialization — `List_<T>` / `Buffer_<T>`
|
|
6953
|
-
* These own
|
|
6954
|
-
* yields an independent slab, so a returned
|
|
6955
|
-
* deep-copied or the caller's unconditional
|
|
6956
|
-
* double-frees the slot's slab.
|
|
7069
|
+
* specialization — `List_<T>` / `Buffer_<T>` / `Map_<K,V>` / `Set_<T>`
|
|
7070
|
+
* (and their bare generics). These own heap slabs their `#destroy` frees
|
|
7071
|
+
* unconditionally, and `copy` yields an independent slab, so a returned
|
|
7072
|
+
* element's field must be deep-copied or the caller's unconditional
|
|
7073
|
+
* container-field teardown double-frees the slot's slab.
|
|
6957
7074
|
*
|
|
6958
|
-
* `Map`/`Set`
|
|
6959
|
-
*
|
|
6960
|
-
*
|
|
6961
|
-
*
|
|
6962
|
-
* interaction still open — see FOLLOWUP.md). A `Map`/`Set` FIELD reached
|
|
6963
|
-
* through a container element's `load` therefore stays shallow (the
|
|
6964
|
-
* pre-existing bounded gap); an EXPLICIT `.copy()` on the mv struct or
|
|
6965
|
-
* the Map/Set itself works on both backends.
|
|
7075
|
+
* `Map`/`Set` compose their `copy` from member `Buffer.copy` calls (and a
|
|
7076
|
+
* zero-pair `Map()`/`Set()` ctor); the aarch64 deferred-ctor marshalling
|
|
7077
|
+
* that crashed that composition is fixed (see build_constructor_params),
|
|
7078
|
+
* so they ride the same deep copy.
|
|
6966
7079
|
*/
|
|
6967
7080
|
function is_load_deep_copy_container(name) {
|
|
6968
|
-
return name === "List" || name === "Buffer" || name.startsWith("List_") || name.startsWith("Buffer_");
|
|
7081
|
+
return name === "List" || name === "Buffer" || name === "Map" || name === "Set" || name.startsWith("List_") || name.startsWith("Buffer_") || name.startsWith("Map_") || name.startsWith("Set_");
|
|
6969
7082
|
}
|
|
6970
7083
|
function mono_type_name(type_or_name, type_args) {
|
|
6971
7084
|
const name = typeof type_or_name === "string" ? type_or_name : type_or_name.name;
|
|
@@ -8779,6 +8892,89 @@ function check_raw_arch_coverage(func, arch, platform) {
|
|
|
8779
8892
|
has_other_arch
|
|
8780
8893
|
};
|
|
8781
8894
|
}
|
|
8895
|
+
function find_use_c_mixed_body(func) {
|
|
8896
|
+
let has_use_c = false;
|
|
8897
|
+
const drops = [];
|
|
8898
|
+
for (const stmt of func.statements) {
|
|
8899
|
+
if (stmt.node_type !== "raw") {
|
|
8900
|
+
drops.push({
|
|
8901
|
+
dropped: "statement",
|
|
8902
|
+
start: stmt.start
|
|
8903
|
+
});
|
|
8904
|
+
continue;
|
|
8905
|
+
}
|
|
8906
|
+
let arches = null;
|
|
8907
|
+
for (const line of stmt.value.split("\n")) {
|
|
8908
|
+
const trimmed = line.trim();
|
|
8909
|
+
if (trimmed.startsWith("#arch:")) arches = trimmed.substring(6).split(",").map((a) => a.trim()).filter((a) => a.length > 0);
|
|
8910
|
+
else if (trimmed.length > 0) break;
|
|
8911
|
+
}
|
|
8912
|
+
if (!arches) {
|
|
8913
|
+
drops.push({
|
|
8914
|
+
dropped: "raw asm",
|
|
8915
|
+
start: stmt.start
|
|
8916
|
+
});
|
|
8917
|
+
continue;
|
|
8918
|
+
}
|
|
8919
|
+
if (arches.includes("aarch64")) {
|
|
8920
|
+
drops.push({
|
|
8921
|
+
dropped: "raw asm",
|
|
8922
|
+
start: stmt.start
|
|
8923
|
+
});
|
|
8924
|
+
continue;
|
|
8925
|
+
}
|
|
8926
|
+
if (arches.includes("aarch64_use_c")) {
|
|
8927
|
+
has_use_c = true;
|
|
8928
|
+
continue;
|
|
8929
|
+
}
|
|
8930
|
+
}
|
|
8931
|
+
if (!has_use_c) return find_nested_use_c_block(func.statements);
|
|
8932
|
+
return drops[0] ?? null;
|
|
8933
|
+
}
|
|
8934
|
+
/**
|
|
8935
|
+
* Descend into nested control-flow statements and report the first
|
|
8936
|
+
* block-scope `aarch64_use_c` raw block. Nested functions are boundaries —
|
|
8937
|
+
* their bodies get their own check pass (their own find_use_c_mixed_body
|
|
8938
|
+
* run). `#scope: file` blocks are exempt: file-scope C hoists to the
|
|
8939
|
+
* companion's file scope and emits wherever the carrier function sits (the
|
|
8940
|
+
* blessed pattern for delegate IMPs and registries).
|
|
8941
|
+
*/
|
|
8942
|
+
function find_nested_use_c_block(statements) {
|
|
8943
|
+
let found = null;
|
|
8944
|
+
const visit = (value) => {
|
|
8945
|
+
if (found || !value || typeof value !== "object") return;
|
|
8946
|
+
if (Array.isArray(value)) {
|
|
8947
|
+
for (const item of value) visit(item);
|
|
8948
|
+
return;
|
|
8949
|
+
}
|
|
8950
|
+
const n = value;
|
|
8951
|
+
if (typeof n.node_type !== "string") return;
|
|
8952
|
+
if (n.node_type === "func" || n.node_type === "struct" || n.node_type === "trait") return;
|
|
8953
|
+
if (n.node_type === "raw") {
|
|
8954
|
+
const raw = n;
|
|
8955
|
+
let arches = null;
|
|
8956
|
+
let scope_file = false;
|
|
8957
|
+
for (const line of raw.value.split("\n")) {
|
|
8958
|
+
const trimmed = line.trim();
|
|
8959
|
+
if (trimmed.startsWith("#scope:")) scope_file = trimmed.substring(7).trim() === "file";
|
|
8960
|
+
else if (trimmed.startsWith("#arch:")) arches = trimmed.substring(6).split(",").map((a) => a.trim()).filter((a) => a.length > 0);
|
|
8961
|
+
else if (trimmed.length > 0) break;
|
|
8962
|
+
}
|
|
8963
|
+
if (arches?.includes("aarch64_use_c") && !scope_file) found = {
|
|
8964
|
+
dropped: "nested block",
|
|
8965
|
+
start: n.start
|
|
8966
|
+
};
|
|
8967
|
+
return;
|
|
8968
|
+
}
|
|
8969
|
+
const record = value;
|
|
8970
|
+
for (const key of Object.keys(record)) {
|
|
8971
|
+
if (key === "parent" || key === "scope" || key === "node_type") continue;
|
|
8972
|
+
visit(record[key]);
|
|
8973
|
+
}
|
|
8974
|
+
};
|
|
8975
|
+
for (const stmt of statements) visit(stmt);
|
|
8976
|
+
return found;
|
|
8977
|
+
}
|
|
8782
8978
|
//#endregion
|
|
8783
8979
|
//#region ../src/nodes/AssignmentNode.ts
|
|
8784
8980
|
var AssignmentNode = class extends BaseNode {
|
|
@@ -9317,6 +9513,24 @@ function struct_needs_destroy(struct, table) {
|
|
|
9317
9513
|
return has_destroy(struct) || has_struct_fields_with_destroy(struct, table) || struct_has_owning_enum_field(struct, table) || struct_has_owned_func_field(struct) || owns_strings_via_destroy(struct) && struct_has_direct_string_fields(struct) || struct_has_trait_declared_destroy_fields(struct, table);
|
|
9318
9514
|
}
|
|
9319
9515
|
/**
|
|
9516
|
+
* Whether a container-element load may replace this field's shallow bytes
|
|
9517
|
+
* with `<Mono>_copy(&field)` (the deep-copy half of mv load_T): any
|
|
9518
|
+
* monomorphized, non-class struct that owns resources (needs destroy at
|
|
9519
|
+
* scope exit) AND has a `copy` method. The containers (`List`/`Buffer`/
|
|
9520
|
+
* `Map`/`Set`) always qualify (their library `copy` yields an independent
|
|
9521
|
+
* slab), and nested mv user structs qualify once every field passed the
|
|
9522
|
+
* auto-derive copy gate (their synthesized `copy` deep-copies member
|
|
9523
|
+
* containers/strings). A flat value struct byte-copies soundly
|
|
9524
|
+
* (needs_destroy false) and stays untouched; an owning struct WITHOUT a
|
|
9525
|
+
* copy (Box/class/nullable/func fields) stays shallow — the recorded gap
|
|
9526
|
+
* in FOLLOWUP.md. The borrow-carrier reclaim walkers use the SAME
|
|
9527
|
+
* predicate to free exactly the deep-copied fields — keep all call sites
|
|
9528
|
+
* on this one definition.
|
|
9529
|
+
*/
|
|
9530
|
+
function field_load_deep_copy(struct, table) {
|
|
9531
|
+
return !struct.is_class && !struct.is_generic && struct_needs_destroy(struct, table) && struct.functions.some((f) => f.name === "copy");
|
|
9532
|
+
}
|
|
9533
|
+
/**
|
|
9320
9534
|
* Whether a class or OWNING struct has a `func`-typed field. Such a field may
|
|
9321
9535
|
* hold a capturing-closure descriptor (`owned = 1`, a heap env + descriptor),
|
|
9322
9536
|
* which the scope-exit / generated destroy must reclaim. A non-mv value
|
|
@@ -9328,6 +9542,81 @@ function struct_has_owned_func_field(struct) {
|
|
|
9328
9542
|
return struct.fields.some((f) => !f.type.is_ref && f.type.name === "func" && !f.type.is_array);
|
|
9329
9543
|
}
|
|
9330
9544
|
/**
|
|
9545
|
+
* Whether a container-element bitwise load (`.at(i)` / `.first()` / `load_T`
|
|
9546
|
+
* — the borrow accessor family) aliases destroy-worthy state that neither
|
|
9547
|
+
* the mv `load_T` deep-copy nor the binding's string strdup repair
|
|
9548
|
+
* privatizes. Such a binding must be classified as a PURE BORROW — no
|
|
9549
|
+
* scope-exit destroy and no strdup repair: the slot keeps sole ownership of
|
|
9550
|
+
* everything the load aliased (the recorded `List<Own>.at()`
|
|
9551
|
+
* double-release: a `#destroy` over a non-string field — an AppKit handle —
|
|
9552
|
+
* ran on the borrowed copy AND on the slot, and a nested `#destroy` struct
|
|
9553
|
+
* without a copy method did the same one level down).
|
|
9554
|
+
*
|
|
9555
|
+
* Privatized by the load + binding repair, and therefore fine to keep
|
|
9556
|
+
* destroy-tracked: direct `string` fields (strdup'd at the binding),
|
|
9557
|
+
* non-nullable `Box<T>` fields (cloned by load_T), nested structs whose
|
|
9558
|
+
* `copy` deep-copies (field_load_deep_copy), and enum fields whose payloads
|
|
9559
|
+
* are all deep-copyable (strdup'd in place). NOT privatizable, and
|
|
9560
|
+
* therefore aliasing: a user `#destroy` body (release semantics — a bitwise
|
|
9561
|
+
* copy cannot clone the resource), a nested destroy-worthy struct without a
|
|
9562
|
+
* copy method, a nullable Box/class field (the load's clone arm skips
|
|
9563
|
+
* nullable fields), an enum field whose payloads reference classes/traits,
|
|
9564
|
+
* an owned func field (the shared closure descriptor), and trait-declared
|
|
9565
|
+
* destroy fields (outside the strdup repair's field walk).
|
|
9566
|
+
*
|
|
9567
|
+
* The accessor-name set is kept in sync with
|
|
9568
|
+
* `is_container_borrow_accessor_name` (string_return_analysis.ts), which
|
|
9569
|
+
* cannot be imported here (it pulls the build_c utils graph into a module
|
|
9570
|
+
* the backends' gather passes import directly).
|
|
9571
|
+
*/
|
|
9572
|
+
function element_load_aliases_destroy_state(struct, table) {
|
|
9573
|
+
if (has_destroy(struct)) return true;
|
|
9574
|
+
if (struct_has_owned_func_field(struct)) return true;
|
|
9575
|
+
if (struct_has_trait_declared_destroy_fields(struct, table)) return true;
|
|
9576
|
+
for (const field of struct.fields) {
|
|
9577
|
+
if (field.type.is_ref || field.type.is_view || field.type.is_array) continue;
|
|
9578
|
+
const field_enum = table.enums?.find((e) => e.name === field.type.name && e.has_associated_data);
|
|
9579
|
+
if (field_enum) {
|
|
9580
|
+
for (const c of field_enum.cases) for (const p of c.params) {
|
|
9581
|
+
if (table.traits?.some((t) => t.name === p.type.name)) return true;
|
|
9582
|
+
if (table.structs.find((s) => s.name === p.type.name)?.is_class) return true;
|
|
9583
|
+
}
|
|
9584
|
+
continue;
|
|
9585
|
+
}
|
|
9586
|
+
const field_struct = resolve_struct_type(field.type, table);
|
|
9587
|
+
if (!field_struct) continue;
|
|
9588
|
+
if (field_struct.is_class) {
|
|
9589
|
+
if (is_box_type(field_struct.name, table.structs) && !field.type.is_nullable) continue;
|
|
9590
|
+
return true;
|
|
9591
|
+
}
|
|
9592
|
+
if (struct_needs_destroy(field_struct, table) && !field_load_deep_copy(field_struct, table)) return true;
|
|
9593
|
+
}
|
|
9594
|
+
return false;
|
|
9595
|
+
}
|
|
9596
|
+
/**
|
|
9597
|
+
* Whether a DECLARATION is the `List<Own>.at()` pure borrow: bound (non-`mv`,
|
|
9598
|
+
* non-`ref`) to a container borrow accessor call (`at`/`at_or_panic`/`first`/
|
|
9599
|
+
* `slice`/`get_or`/`get_or_panic`/`load`, without `owned_return`) whose
|
|
9600
|
+
* value-struct element aliases destroy-worthy state. Both backends gate the
|
|
9601
|
+
* binding's scope-exit destroy and the string strdup repair on this — see
|
|
9602
|
+
* element_load_aliases_destroy_state.
|
|
9603
|
+
*/
|
|
9604
|
+
function is_pure_borrow_element_load(decl, table) {
|
|
9605
|
+
const value = decl.value;
|
|
9606
|
+
if (!value || value.node_type !== "access") return false;
|
|
9607
|
+
const access = value.access;
|
|
9608
|
+
if (!access || access.node_type !== "access_func") return false;
|
|
9609
|
+
const func = access;
|
|
9610
|
+
if (func.owned_return) return false;
|
|
9611
|
+
const name = func.name ?? "";
|
|
9612
|
+
if (!(name === "at" || name === "at_or_panic" || name === "first" || name === "slice" || name === "get_or" || name === "get_or_panic" || name === "load")) return false;
|
|
9613
|
+
const decl_type = decl.type;
|
|
9614
|
+
if (!decl_type) return false;
|
|
9615
|
+
const struct_type = resolve_struct_type(decl_type, table);
|
|
9616
|
+
if (!struct_type || struct_type.is_class || struct_type.is_generic) return false;
|
|
9617
|
+
return element_load_aliases_destroy_state(struct_type, table);
|
|
9618
|
+
}
|
|
9619
|
+
/**
|
|
9331
9620
|
* Whether a struct needs an auto-generated `<Struct>_destroy` function.
|
|
9332
9621
|
* Broader than `struct_needs_destroy`: also returns true for structs with
|
|
9333
9622
|
* `string` fields, because a Buffer<T> for such a struct deep-copies the
|
|
@@ -14054,6 +14343,7 @@ function visit$1(node, result, structs) {
|
|
|
14054
14343
|
if (a.left_value.node_type === "value") {
|
|
14055
14344
|
if (is_fresh_heap_string(a.right_value)) result.add(a.left_value.value);
|
|
14056
14345
|
else if (!a.operator && is_owned_string_var_rhs(a.right_value)) result.add(a.left_value.value);
|
|
14346
|
+
else if (!a.operator && is_moved_string_var_rhs(a.right_value)) result.add(a.left_value.value);
|
|
14057
14347
|
}
|
|
14058
14348
|
visit$1(a.right_value, result, structs);
|
|
14059
14349
|
break;
|
|
@@ -14110,6 +14400,13 @@ function is_fresh_heap_string(node) {
|
|
|
14110
14400
|
}
|
|
14111
14401
|
return false;
|
|
14112
14402
|
}
|
|
14403
|
+
function is_moved_string_var_rhs(node) {
|
|
14404
|
+
if (!node || node.node_type !== "value") return false;
|
|
14405
|
+
const vn = node;
|
|
14406
|
+
if (!vn.is_moved) return false;
|
|
14407
|
+
const type = vn.type;
|
|
14408
|
+
return !!type && type.name === "string" && !type.is_view && !type.is_array;
|
|
14409
|
+
}
|
|
14113
14410
|
function is_owned_string_var_rhs(node) {
|
|
14114
14411
|
if (!node || node.node_type !== "value") return false;
|
|
14115
14412
|
const vn = node;
|
|
@@ -14393,49 +14690,6 @@ function build_foreign$1(node, status, struct_node) {
|
|
|
14393
14690
|
emit_asm(status, `\n`);
|
|
14394
14691
|
}
|
|
14395
14692
|
//#endregion
|
|
14396
|
-
//#region ../src/build_aarch64/utils/raw_reload.ts
|
|
14397
|
-
/** Width-aware slot reload — mirrors the prologue's store width so the
|
|
14398
|
-
* register holds exactly the value a body read of the slot would produce
|
|
14399
|
-
* (sub-word stores truncate; a same-width load zero-extends like the
|
|
14400
|
-
* emitters' own slot reads). */
|
|
14401
|
-
function raw_slot_reload_line(reg, offset, size) {
|
|
14402
|
-
const wreg = reg.replace("x", "w");
|
|
14403
|
-
if (size === 1) return `ldrb ${wreg}, [x29, #${offset}]`;
|
|
14404
|
-
if (size === 2) return `ldrh ${wreg}, [x29, #${offset}]`;
|
|
14405
|
-
if (size === 4) return `ldr ${wreg}, [x29, #${offset}]`;
|
|
14406
|
-
return `ldr ${reg}, [x29, #${offset}]`;
|
|
14407
|
-
}
|
|
14408
|
-
/** Capture status.code.length as the entry point and install the plan. */
|
|
14409
|
-
function install_raw_reload_plan(status, lines) {
|
|
14410
|
-
const plan = {
|
|
14411
|
-
entry_marker: asm_code_len(status),
|
|
14412
|
-
lines
|
|
14413
|
-
};
|
|
14414
|
-
status.raw_param_reloads = plan;
|
|
14415
|
-
return plan;
|
|
14416
|
-
}
|
|
14417
|
-
/**
|
|
14418
|
-
* The reload lines a raw block needs: none at the function's entry point
|
|
14419
|
-
* (the registers still hold the entry values), otherwise the lines for the
|
|
14420
|
-
* ABI registers the block's text references. Registers are matched against
|
|
14421
|
-
* comment-stripped instruction text only — a mention in a `//` comment, a
|
|
14422
|
-
* label, or a data directive doesn't force a reload (same discipline as
|
|
14423
|
-
* build_inline_method's count_x19_reads).
|
|
14424
|
-
*/
|
|
14425
|
-
function raw_reload_lines_for(code, plan, current_length) {
|
|
14426
|
-
if (!plan || plan.lines.length === 0 || current_length === plan.entry_marker) return [];
|
|
14427
|
-
const referenced = /* @__PURE__ */ new Set();
|
|
14428
|
-
for (const raw_line of code.split("\n")) {
|
|
14429
|
-
const comment = raw_line.indexOf("//");
|
|
14430
|
-
const line = (comment === -1 ? raw_line : raw_line.slice(0, comment)).trim();
|
|
14431
|
-
if (!line || line.endsWith(":") || line.startsWith(".")) continue;
|
|
14432
|
-
for (const match of line.matchAll(/\b[wx][0-9]\b/g)) referenced.add(match[0]);
|
|
14433
|
-
}
|
|
14434
|
-
const out = [];
|
|
14435
|
-
for (const entry of plan.lines) if (referenced.has(entry.reg) || referenced.has(entry.reg.replace("x", "w"))) out.push(entry.asm);
|
|
14436
|
-
return out;
|
|
14437
|
-
}
|
|
14438
|
-
//#endregion
|
|
14439
14693
|
//#region ../src/build_aarch64/utils/aarch64_size.ts
|
|
14440
14694
|
/**
|
|
14441
14695
|
* Storage width of a value of `type` in bytes, derived from the
|
|
@@ -14792,6 +15046,382 @@ function emit_enum_payload_binding_load(status, base_reg, payload_off, slot, typ
|
|
|
14792
15046
|
}
|
|
14793
15047
|
}
|
|
14794
15048
|
//#endregion
|
|
15049
|
+
//#region ../src/build_aarch64/utils/c_companion.ts
|
|
15050
|
+
/**
|
|
15051
|
+
* The label a `aarch64_use_c` function's C body is emitted under — the same
|
|
15052
|
+
* shape the aarch64 side derives for thunks and call sites.
|
|
15053
|
+
*/
|
|
15054
|
+
function companion_function_label(func, struct_name, status) {
|
|
15055
|
+
if (struct_name) {
|
|
15056
|
+
const struct = status.structs.find((s) => s.name === struct_name);
|
|
15057
|
+
if (struct && is_overloaded(struct, func.name)) return mangled_label(func, struct_name);
|
|
15058
|
+
return `${struct_name}_${func.name.replace(/#/g, "")}`;
|
|
15059
|
+
}
|
|
15060
|
+
return func.name === "main" ? "_nomen_main" : func.name;
|
|
15061
|
+
}
|
|
15062
|
+
/**
|
|
15063
|
+
* Mach-O reserves leading-`L` symbols for assembler-local labels: clang's
|
|
15064
|
+
* assembler refuses `bl L…` to an undefined symbol (local labels can't be
|
|
15065
|
+
* external), and clang's compiler drops L-prefixed `__asm__` bindings from
|
|
15066
|
+
* the object symbol table entirely. A struct named `LevelIndicator` (or any
|
|
15067
|
+
* L-leading function) with `aarch64_use_c` methods therefore binds its
|
|
15068
|
+
* companion symbols under a `_`-prefixed alias — and build.ts rewrites the
|
|
15069
|
+
* asm-side references to match.
|
|
15070
|
+
*/
|
|
15071
|
+
function sanitize_companion_label(label) {
|
|
15072
|
+
return label.startsWith("L") ? `_${label}` : label;
|
|
15073
|
+
}
|
|
15074
|
+
/**
|
|
15075
|
+
* Generate the complete C companion file: includes, struct typedefs, and
|
|
15076
|
+
* function definitions for every collected `aarch64_use_c` function.
|
|
15077
|
+
*/
|
|
15078
|
+
function generate_companion(functions, status) {
|
|
15079
|
+
let out = "";
|
|
15080
|
+
if (functions.length > 0 && (status.platform === "macos" || status.platform === "ios")) {
|
|
15081
|
+
out += `#import <Foundation/Foundation.h>\n`;
|
|
15082
|
+
out += `#include <objc/runtime.h>\n`;
|
|
15083
|
+
out += `#include <objc/message.h>\n`;
|
|
15084
|
+
if (status.platform === "macos") out += `#import <Cocoa/Cocoa.h>\n`;
|
|
15085
|
+
else out += `#import <UIKit/UIKit.h>\n`;
|
|
15086
|
+
}
|
|
15087
|
+
out += `#include <stdint.h>\n`;
|
|
15088
|
+
out += `#include <stdlib.h>\n`;
|
|
15089
|
+
out += `typedef struct { void* ptr; long len; } nomen_view;\n`;
|
|
15090
|
+
out += `typedef struct { char* ptr; long len; } nomen_string;\n`;
|
|
15091
|
+
out += `#include <stdio.h>\n`;
|
|
15092
|
+
out += `#include <string.h>\n`;
|
|
15093
|
+
out += `#include <regex.h>\n`;
|
|
15094
|
+
if (status.file_scope_c?.includes("pthread") || status.emit_mode === "system") out += `#include <pthread.h>\n`;
|
|
15095
|
+
out += "\n";
|
|
15096
|
+
if (status.audit) {
|
|
15097
|
+
out += `void *nomen_malloc_wrap(unsigned long);\n`;
|
|
15098
|
+
out += `void *nomen_calloc_wrap(unsigned long, unsigned long);\n`;
|
|
15099
|
+
out += `void *nomen_realloc_wrap(void *, unsigned long);\n`;
|
|
15100
|
+
out += `void nomen_free_wrap(void *);\n`;
|
|
15101
|
+
out += `void *nomen_strdup_wrap(const char *);\n`;
|
|
15102
|
+
out += `void nomen_audit_check(void);\n`;
|
|
15103
|
+
out += "\n";
|
|
15104
|
+
}
|
|
15105
|
+
if (status.emit_mode === "system") {
|
|
15106
|
+
out += runtime_declarations(POOL_HEADER_C + FIBER_HEADER_C);
|
|
15107
|
+
out += "\n";
|
|
15108
|
+
} else {
|
|
15109
|
+
if (!status.pool_runtime_emitted) {
|
|
15110
|
+
out += POOL_HEADER_C;
|
|
15111
|
+
status.pool_runtime_emitted = true;
|
|
15112
|
+
}
|
|
15113
|
+
if (!status.fiber_runtime_emitted) {
|
|
15114
|
+
out += FIBER_HEADER_C;
|
|
15115
|
+
status.fiber_runtime_emitted = true;
|
|
15116
|
+
}
|
|
15117
|
+
}
|
|
15118
|
+
const enum_nodes = status.enums.filter((e) => !e.is_generic);
|
|
15119
|
+
const struct_nodes = status.structs.filter((s) => !s.is_simple_type && !s.is_generic);
|
|
15120
|
+
const node_kind = /* @__PURE__ */ new Map();
|
|
15121
|
+
for (const e of enum_nodes) node_kind.set(e.name, "enum");
|
|
15122
|
+
for (const st of struct_nodes) node_kind.set(st.name, "struct");
|
|
15123
|
+
for (const b of status.bitsets) node_kind.set(b.name, "bitset");
|
|
15124
|
+
const deps = /* @__PURE__ */ new Map();
|
|
15125
|
+
for (const e of enum_nodes) {
|
|
15126
|
+
const d = /* @__PURE__ */ new Set();
|
|
15127
|
+
for (const c of e.cases) for (const p of c.params) if (node_kind.has(p.type.name) && p.type.name !== e.name) d.add(p.type.name);
|
|
15128
|
+
deps.set(e.name, d);
|
|
15129
|
+
}
|
|
15130
|
+
for (const st of struct_nodes) {
|
|
15131
|
+
const d = /* @__PURE__ */ new Set();
|
|
15132
|
+
for (const f of st.fields) {
|
|
15133
|
+
if (f.type.is_view) continue;
|
|
15134
|
+
const field_type = mono_struct_name(f.type, status);
|
|
15135
|
+
if (status.structs.find((s) => s.name === field_type && !s.is_simple_type)?.is_class) continue;
|
|
15136
|
+
if (node_kind.has(field_type) && field_type !== st.name) d.add(field_type);
|
|
15137
|
+
}
|
|
15138
|
+
deps.set(st.name, d);
|
|
15139
|
+
}
|
|
15140
|
+
const emitted = /* @__PURE__ */ new Set();
|
|
15141
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
15142
|
+
const node_list = [
|
|
15143
|
+
...enum_nodes,
|
|
15144
|
+
...struct_nodes,
|
|
15145
|
+
...status.bitsets
|
|
15146
|
+
];
|
|
15147
|
+
const by_name = new Map(node_list.map((n) => [n.name, n]));
|
|
15148
|
+
const visit = (name) => {
|
|
15149
|
+
if (emitted.has(name) || visiting.has(name)) return;
|
|
15150
|
+
visiting.add(name);
|
|
15151
|
+
for (const dep of deps.get(name) ?? []) visit(dep);
|
|
15152
|
+
visiting.delete(name);
|
|
15153
|
+
emitted.add(name);
|
|
15154
|
+
const node = by_name.get(name);
|
|
15155
|
+
if (!node) return;
|
|
15156
|
+
if (node_kind.get(name) === "enum") out += generate_enum_definition(node, status);
|
|
15157
|
+
else if (node_kind.get(name) === "bitset") out += `typedef unsigned long ${nm(name)};\n`;
|
|
15158
|
+
else out += generate_struct_definition(node, status);
|
|
15159
|
+
};
|
|
15160
|
+
for (const n of node_list) visit(n.name);
|
|
15161
|
+
out += "\n";
|
|
15162
|
+
if (status.file_scope_c) {
|
|
15163
|
+
out += status.file_scope_c;
|
|
15164
|
+
out += "\n";
|
|
15165
|
+
}
|
|
15166
|
+
for (const entry of functions) out += generate_c_function(entry.func, entry.struct_name, entry.raw_code, status, entry.bridge_c_abi);
|
|
15167
|
+
return out;
|
|
15168
|
+
}
|
|
15169
|
+
/**
|
|
15170
|
+
* Prefix a Nomen type's C TYPEDEF name with `nm_`. The companion file `#import`s
|
|
15171
|
+
* the platform frameworks (Foundation/Cocoa/UIKit), which drag in a large set of
|
|
15172
|
+
* system typedefs (e.g. macOS `MacTypes.h` defines `typedef long Size`). If we
|
|
15173
|
+
* emitted Nomen's own `typedef struct Size {...} Size;` the typedef name would
|
|
15174
|
+
* collide; mangling only the typedef (`typedef struct Size {...} nm_Size;`)
|
|
15175
|
+
* sidesteps it while leaving the struct TAG (`Size`) untouched.
|
|
15176
|
+
*
|
|
15177
|
+
* Keeping the original tag matters: generated C (spawn/trampoline infra in
|
|
15178
|
+
* build_spawn_node / build_nursery_spawn) and `#arch: aarch64_use_c` raw bodies
|
|
15179
|
+
* reference Nomen types as `struct Foo` (the tag), so an unchanged tag means
|
|
15180
|
+
* none of that code needs to know about mangling. Codegen-generated references
|
|
15181
|
+
* here (fields, params, returns) use the mangled typedef instead.
|
|
15182
|
+
*
|
|
15183
|
+
* `nm_` (no leading underscore: the C standard reserves `_`+lowercase
|
|
15184
|
+
* identifiers at file scope). This is purely cosmetic — the aarch64 assembly
|
|
15185
|
+
* never references type names (only function labels, bridged via `__asm__`).
|
|
15186
|
+
*/
|
|
15187
|
+
function nm(name) {
|
|
15188
|
+
return "nm_" + name;
|
|
15189
|
+
}
|
|
15190
|
+
/**
|
|
15191
|
+
* The C type for a field/param/return of the given Nomen type, applying `nm_`
|
|
15192
|
+
* to user-defined struct/enum types and falling back to `c_type` for primitives.
|
|
15193
|
+
* Generic structs lower to opaque 8-byte pointers (their element type lives in
|
|
15194
|
+
* the Nomen `Type`, not in the C layout).
|
|
15195
|
+
*/
|
|
15196
|
+
/**
|
|
15197
|
+
* The C type for a value stored BY VALUE in an enum payload: classes and
|
|
15198
|
+
* traits are heap/shared references (pointers, and never have a companion
|
|
15199
|
+
* typedef), everything else follows `companion_type`.
|
|
15200
|
+
*/
|
|
15201
|
+
function payload_type(typeName, status) {
|
|
15202
|
+
if (status.structs.find((s) => s.name === typeName)?.is_class) return `struct ${typeName} *`;
|
|
15203
|
+
if (status.traits.find((t) => t.name === typeName)) return `struct ${typeName} *`;
|
|
15204
|
+
return companion_type(typeName, status);
|
|
15205
|
+
}
|
|
15206
|
+
function companion_type(typeName, status) {
|
|
15207
|
+
const struct = status.structs.find((s) => s.name === typeName);
|
|
15208
|
+
if (struct?.is_generic) return "void *";
|
|
15209
|
+
if (struct && !struct.is_simple_type) return nm(typeName);
|
|
15210
|
+
if (!struct && status.enums.find((e) => e.name === typeName)) return nm(typeName);
|
|
15211
|
+
if (status.bitsets.find((b) => b.name === typeName)) return nm(typeName);
|
|
15212
|
+
return c_type(typeName);
|
|
15213
|
+
}
|
|
15214
|
+
/**
|
|
15215
|
+
* Emit an enum's C type definition. Mirrors the C backend's `build_enum_node`
|
|
15216
|
+
* (typedef for simple enums; tag + tagged-union struct for enums with associated
|
|
15217
|
+
* data) but with `nm_`-prefixed TYPEDEF names and no constructor functions (those
|
|
15218
|
+
* are emitted as assembly in the aarch64 path; the companion only needs types so
|
|
15219
|
+
* struct fields / function signatures can reference them). The struct tag keeps
|
|
15220
|
+
* the original Nomen name (see `nm`).
|
|
15221
|
+
*/
|
|
15222
|
+
function generate_enum_definition(node, status) {
|
|
15223
|
+
let out = "";
|
|
15224
|
+
if (node.has_associated_data) {
|
|
15225
|
+
out += `typedef enum { ${node.cases.map((c) => `${node.name}_${c.name}`).join(", ")} } ${nm(node.name)}_tag;\n`;
|
|
15226
|
+
out += `struct ${node.name};\n`;
|
|
15227
|
+
out += `typedef struct ${node.name}\n{\n`;
|
|
15228
|
+
out += `${nm(node.name)}_tag tag;\n`;
|
|
15229
|
+
out += `union {\n`;
|
|
15230
|
+
for (const c of node.cases) out += `struct { ${c.params.map((p) => `${payload_type(p.type.name, status)} ${p.name}`).join("; ")}${c.params.length ? ";" : ""} } _${c.name};\n`;
|
|
15231
|
+
out += `} _data;\n`;
|
|
15232
|
+
out += `} ${nm(node.name)};\n`;
|
|
15233
|
+
} else out += `typedef enum { ${node.cases.map((c) => `${node.name}_${c.name}`).join(", ")} } ${nm(node.name)};\n`;
|
|
15234
|
+
return out;
|
|
15235
|
+
}
|
|
15236
|
+
function generate_struct_definition(struct, status) {
|
|
15237
|
+
let out = `typedef struct ${struct.name}\n{\n`;
|
|
15238
|
+
out += `void *_vt;\n`;
|
|
15239
|
+
for (const field of struct.fields) {
|
|
15240
|
+
const member_type = field.type.is_view ? "nomen_view" : payload_type(mono_struct_name(field.type, status), status);
|
|
15241
|
+
out += `${member_type} ${field.name};\n`;
|
|
15242
|
+
}
|
|
15243
|
+
for (const traitName of struct.traits) {
|
|
15244
|
+
const trait = status.traits.find((t) => t.name === traitName);
|
|
15245
|
+
if (!trait) continue;
|
|
15246
|
+
for (const field of trait.fields.filter((f) => !struct.fields.find((nf) => nf.name === f.name))) {
|
|
15247
|
+
const member_type = field.type.is_view ? "nomen_view" : payload_type(mono_struct_name(field.type, status), status);
|
|
15248
|
+
out += `${member_type} ${field.name};\n`;
|
|
15249
|
+
}
|
|
15250
|
+
}
|
|
15251
|
+
out += `} ${nm(struct.name)};\n`;
|
|
15252
|
+
return out;
|
|
15253
|
+
}
|
|
15254
|
+
function generate_c_function(func, struct_name, raw_code, status, bridge_c_abi = false) {
|
|
15255
|
+
const func_label = companion_function_label(func, struct_name, status);
|
|
15256
|
+
const is_class_init = func.name === "#init" && !!(struct_name && status.structs.find((s) => s.name === struct_name));
|
|
15257
|
+
const return_type = is_class_init ? "void" : func.return_type?.name || "void";
|
|
15258
|
+
const return_struct = is_class_init ? void 0 : status.structs.find((s) => s.name === return_type && !s.is_simple_type);
|
|
15259
|
+
let return_prefix = return_struct ? nm(return_type) : companion_type(return_type, status);
|
|
15260
|
+
return_prefix += ` `;
|
|
15261
|
+
const return_enum_size = return_struct ? void 0 : get_enum_sret_size(return_type, status);
|
|
15262
|
+
const return_struct_size = return_struct ? get_struct_size(return_type, status) : 0;
|
|
15263
|
+
const symbol_label = sanitize_companion_label(return_struct && return_struct_size <= 16 || return_enum_size !== void 0 && return_enum_size <= 16 || bridge_c_abi ? `${func_label}_c` : func_label);
|
|
15264
|
+
const params = func.params;
|
|
15265
|
+
let param_list = "";
|
|
15266
|
+
for (let i = 0; i < params.length; i++) {
|
|
15267
|
+
if (i > 0) param_list += ", ";
|
|
15268
|
+
param_list += generate_c_param(params[i], status);
|
|
15269
|
+
}
|
|
15270
|
+
if (params.length === 0) param_list += `void`;
|
|
15271
|
+
let out = `// ${func_label}\n`;
|
|
15272
|
+
out += `${return_prefix}${symbol_label}(${param_list}) __asm__("${symbol_label}");\n`;
|
|
15273
|
+
out += `${return_prefix}${symbol_label}(${param_list})\n{\n`;
|
|
15274
|
+
if (func.name === "#init" && struct_name) {
|
|
15275
|
+
const ctor_struct = status.structs.find((s) => s.name === struct_name);
|
|
15276
|
+
if (ctor_struct && !ctor_struct.is_simple_type) {
|
|
15277
|
+
if (ctor_struct.traits.length > 0) {
|
|
15278
|
+
out += `extern void *${struct_name}_traits;\n`;
|
|
15279
|
+
out += `self->_vt = (void *)&${struct_name}_traits;\n`;
|
|
15280
|
+
} else if (!ctor_struct.is_class) out += `self->_vt = (void *)1;\n`;
|
|
15281
|
+
for (const field of ctor_struct.fields) {
|
|
15282
|
+
if (!field.value) continue;
|
|
15283
|
+
let seeded;
|
|
15284
|
+
if (field.value.node_type === "func_call" && field.value.params?.length === 0) seeded = emit_zero_ctor_field_default(field.type.name, field.type.type_args, `self->${field.name}`, status, /* @__PURE__ */ new Set());
|
|
15285
|
+
if (!seeded) seeded = emit_ctor_field_default(ctor_struct, field);
|
|
15286
|
+
if (!seeded) {
|
|
15287
|
+
if (!status.build_errors) status.build_errors = [];
|
|
15288
|
+
status.build_errors.push({
|
|
15289
|
+
message: `mv struct '${struct_name}' has a use_c constructor and field '${field.name}' has a non-literal default — assign it in the #init body`,
|
|
15290
|
+
start: func.start
|
|
15291
|
+
});
|
|
15292
|
+
continue;
|
|
15293
|
+
}
|
|
15294
|
+
out += seeded;
|
|
15295
|
+
}
|
|
15296
|
+
}
|
|
15297
|
+
}
|
|
15298
|
+
const self_param = params[0];
|
|
15299
|
+
if (struct_name && self_param?.is_self_param && !self_param?.is_ref && func.name !== "#destroy" && func.name !== "#init") {
|
|
15300
|
+
const struct = status.structs.find((s) => s.name === struct_name);
|
|
15301
|
+
if (struct && !struct.is_simple_type) out += `${nm(struct_name)} _self = *self;\n`;
|
|
15302
|
+
}
|
|
15303
|
+
out += raw_code;
|
|
15304
|
+
out += `\n}\n\n`;
|
|
15305
|
+
return out;
|
|
15306
|
+
}
|
|
15307
|
+
/**
|
|
15308
|
+
* The C statement seeding a defaulted field in a companion constructor, or
|
|
15309
|
+
* `undefined` when the default is not a literal shape. Mirrors the ASM-side
|
|
15310
|
+
* ctor's default seeding for the shapes a `use_c` constructor can express:
|
|
15311
|
+
* string literals borrow static storage (a function-static C literal — never
|
|
15312
|
+
* strdup'd, matching the ASM literal-pair store), scalar/bool literals store
|
|
15313
|
+
* directly. Nullable and computed defaults are rejected — the caller turns
|
|
15314
|
+
* that into a build error naming the field.
|
|
15315
|
+
*/
|
|
15316
|
+
function emit_ctor_field_default(struct_node, field) {
|
|
15317
|
+
const value = field.value?.node_type === "value" ? field.value.value : void 0;
|
|
15318
|
+
if (value === void 0 || typeof value !== "string") return void 0;
|
|
15319
|
+
if (field.type.is_nullable) return void 0;
|
|
15320
|
+
if (field.type.name === "string" && !field.type.is_view) {
|
|
15321
|
+
const length = string_literal_length(value);
|
|
15322
|
+
const label = `_${struct_node.name}_${field.name}_default`;
|
|
15323
|
+
return `static const char ${label}[] = ${escape_asciz(value)};\nself->${field.name} = (nomen_string){ (char *)${label}, ${length} };\n`;
|
|
15324
|
+
}
|
|
15325
|
+
if (value === "true" || value === "false") return `self->${field.name} = ${value === "true" ? 1 : 0};\n`;
|
|
15326
|
+
if (/^-?\d+$/.test(value)) return `self->${field.name} = ${value};\n`;
|
|
15327
|
+
}
|
|
15328
|
+
/**
|
|
15329
|
+
* The zero-arg CONSTRUCTOR-call field default (`var List<ClickBinding>
|
|
15330
|
+
* clicks = List<ClickBinding>()`) in a use_c ctor's companion prologue:
|
|
15331
|
+
* the System containers' zero-arg init is exactly stamp/traits + zeroed
|
|
15332
|
+
* fields (every container field default is a literal zero or a nested
|
|
15333
|
+
* container ctor call), so seed that shape DIRECTLY. A companion→asm call
|
|
15334
|
+
* (`List_T_init(&self->f)`) is not available here — method labels are
|
|
15335
|
+
* file-local outside system builds, so the companion object could not link
|
|
15336
|
+
* the reference. Returns undefined for anything unsupported (a custom
|
|
15337
|
+
* zero-arg init could have side effects; the caller's build error names
|
|
15338
|
+
* the field). `seen` guards the container nesting (List → Buffer).
|
|
15339
|
+
*/
|
|
15340
|
+
function emit_zero_ctor_field_default(type_name, type_args, target, status, seen) {
|
|
15341
|
+
const resolved = type_args?.length && status.structs.some((s) => s.name === mono_type_name(type_name, type_args)) ? mono_type_name(type_name, type_args) : type_name;
|
|
15342
|
+
if (!is_load_deep_copy_container(resolved)) return void 0;
|
|
15343
|
+
const struct = status.structs.find((s) => s.name === resolved);
|
|
15344
|
+
if (!struct || struct.is_generic || struct.is_simple_type || struct.is_class) return void 0;
|
|
15345
|
+
if (struct.functions.some((f) => f.name === "#init" && f.has_body)) return void 0;
|
|
15346
|
+
if (seen.has(resolved)) return "";
|
|
15347
|
+
seen.add(resolved);
|
|
15348
|
+
let out = "";
|
|
15349
|
+
if (struct.traits.length > 0) {
|
|
15350
|
+
out += `extern void *${resolved}_traits;\n`;
|
|
15351
|
+
out += `${target}._vt = (void *)&${resolved}_traits;\n`;
|
|
15352
|
+
} else out += `${target}._vt = (void *)1;\n`;
|
|
15353
|
+
for (const f of struct.fields) {
|
|
15354
|
+
if (f.type.is_nullable) return void 0;
|
|
15355
|
+
const fv = f.value?.node_type === "value" ? f.value.value : void 0;
|
|
15356
|
+
if (f.value?.node_type === "func_call") {
|
|
15357
|
+
const nested = emit_zero_ctor_field_default(f.type.name, f.type.type_args, `${target}.${f.name}`, status, seen);
|
|
15358
|
+
if (nested === void 0) return void 0;
|
|
15359
|
+
out += nested;
|
|
15360
|
+
continue;
|
|
15361
|
+
}
|
|
15362
|
+
if (typeof fv !== "string") return void 0;
|
|
15363
|
+
if (f.type.name === "string" && !f.type.is_view && fv.startsWith("\"")) return;
|
|
15364
|
+
if (fv !== "0") return void 0;
|
|
15365
|
+
out += `${target}.${f.name} = 0;\n`;
|
|
15366
|
+
}
|
|
15367
|
+
return out;
|
|
15368
|
+
}
|
|
15369
|
+
function generate_c_param(param, status) {
|
|
15370
|
+
const struct_type = status.structs.find((s) => s.name === param.type.name);
|
|
15371
|
+
const trait_type = status.traits.find((t) => t.name === param.type.name);
|
|
15372
|
+
const is_struct = (param.is_self_param || struct_type || trait_type) && !struct_type?.is_simple_type;
|
|
15373
|
+
let out = "";
|
|
15374
|
+
if (param.is_variadic) out += `long _${param.name}_len, `;
|
|
15375
|
+
out += companion_type(param.type.name, status);
|
|
15376
|
+
if (is_struct || param.declaration === "var" || param.type.is_ref || param.type.is_array) out += ` *`;
|
|
15377
|
+
else out += ` `;
|
|
15378
|
+
out += param.name;
|
|
15379
|
+
return out;
|
|
15380
|
+
}
|
|
15381
|
+
//#endregion
|
|
15382
|
+
//#region ../src/build_aarch64/utils/raw_reload.ts
|
|
15383
|
+
/** Width-aware slot reload — mirrors the prologue's store width so the
|
|
15384
|
+
* register holds exactly the value a body read of the slot would produce
|
|
15385
|
+
* (sub-word stores truncate; a same-width load zero-extends like the
|
|
15386
|
+
* emitters' own slot reads). */
|
|
15387
|
+
function raw_slot_reload_line(reg, offset, size) {
|
|
15388
|
+
const wreg = reg.replace("x", "w");
|
|
15389
|
+
if (size === 1) return `ldrb ${wreg}, [x29, #${offset}]`;
|
|
15390
|
+
if (size === 2) return `ldrh ${wreg}, [x29, #${offset}]`;
|
|
15391
|
+
if (size === 4) return `ldr ${wreg}, [x29, #${offset}]`;
|
|
15392
|
+
return `ldr ${reg}, [x29, #${offset}]`;
|
|
15393
|
+
}
|
|
15394
|
+
/** Capture status.code.length as the entry point and install the plan. */
|
|
15395
|
+
function install_raw_reload_plan(status, lines) {
|
|
15396
|
+
const plan = {
|
|
15397
|
+
entry_marker: asm_code_len(status),
|
|
15398
|
+
lines
|
|
15399
|
+
};
|
|
15400
|
+
status.raw_param_reloads = plan;
|
|
15401
|
+
return plan;
|
|
15402
|
+
}
|
|
15403
|
+
/**
|
|
15404
|
+
* The reload lines a raw block needs: none at the function's entry point
|
|
15405
|
+
* (the registers still hold the entry values), otherwise the lines for the
|
|
15406
|
+
* ABI registers the block's text references. Registers are matched against
|
|
15407
|
+
* comment-stripped instruction text only — a mention in a `//` comment, a
|
|
15408
|
+
* label, or a data directive doesn't force a reload (same discipline as
|
|
15409
|
+
* build_inline_method's count_x19_reads).
|
|
15410
|
+
*/
|
|
15411
|
+
function raw_reload_lines_for(code, plan, current_length) {
|
|
15412
|
+
if (!plan || plan.lines.length === 0 || current_length === plan.entry_marker) return [];
|
|
15413
|
+
const referenced = /* @__PURE__ */ new Set();
|
|
15414
|
+
for (const raw_line of code.split("\n")) {
|
|
15415
|
+
const comment = raw_line.indexOf("//");
|
|
15416
|
+
const line = (comment === -1 ? raw_line : raw_line.slice(0, comment)).trim();
|
|
15417
|
+
if (!line || line.endsWith(":") || line.startsWith(".")) continue;
|
|
15418
|
+
for (const match of line.matchAll(/\b[wx][0-9]\b/g)) referenced.add(match[0]);
|
|
15419
|
+
}
|
|
15420
|
+
const out = [];
|
|
15421
|
+
for (const entry of plan.lines) if (referenced.has(entry.reg) || referenced.has(entry.reg.replace("x", "w"))) out.push(entry.asm);
|
|
15422
|
+
return out;
|
|
15423
|
+
}
|
|
15424
|
+
//#endregion
|
|
14795
15425
|
//#region ../src/build_aarch64/build_raw_node.ts
|
|
14796
15426
|
function build_raw_node$1(node, status) {
|
|
14797
15427
|
const { should_emit, code, is_c, scope } = parse_raw_directives(node.value, "aarch64", status.platform);
|
|
@@ -14801,8 +15431,11 @@ function build_raw_node$1(node, status) {
|
|
|
14801
15431
|
if (!status.file_scope_c) status.file_scope_c = "";
|
|
14802
15432
|
status.file_scope_c += `${code}\n`;
|
|
14803
15433
|
} else {
|
|
14804
|
-
if (!status.
|
|
14805
|
-
status.
|
|
15434
|
+
if (!status.build_errors) status.build_errors = [];
|
|
15435
|
+
status.build_errors.push({
|
|
15436
|
+
message: `aarch64 cannot splice an inline C raw block into a Nomen body — a C raw block is emitted only as a function's whole body; move this block into its own aarch64_use_c function and pass values as parameters`,
|
|
15437
|
+
start: node.start
|
|
15438
|
+
});
|
|
14806
15439
|
}
|
|
14807
15440
|
} else {
|
|
14808
15441
|
for (const line of raw_reload_lines_for(code, status.raw_param_reloads, asm_code_len(status))) emit_asm(status, `${line}\n`);
|
|
@@ -14853,6 +15486,16 @@ function check_c_fallback(func, struct_name, status) {
|
|
|
14853
15486
|
else raw_code += parsed.code + "\n";
|
|
14854
15487
|
}
|
|
14855
15488
|
}
|
|
15489
|
+
let float_bridge = has_float_abi_divergence(func);
|
|
15490
|
+
if (float_bridge && func.params.some((p) => p.is_variadic)) {
|
|
15491
|
+
if (!status.build_errors) status.build_errors = [];
|
|
15492
|
+
const label = struct_name ? `${struct_name}.${func.name.replace(/#/g, "")}` : func.name;
|
|
15493
|
+
status.build_errors.push({
|
|
15494
|
+
message: `Function ${label} mixes variadic params with floats — no aarch64_use_c ABI thunk for that shape`,
|
|
15495
|
+
start: func.start
|
|
15496
|
+
});
|
|
15497
|
+
float_bridge = false;
|
|
15498
|
+
}
|
|
14856
15499
|
if (is_c_fallback) {
|
|
14857
15500
|
if (file_scope_code) {
|
|
14858
15501
|
if (!status.file_scope_c) status.file_scope_c = "";
|
|
@@ -14862,38 +15505,84 @@ function check_c_fallback(func, struct_name, status) {
|
|
|
14862
15505
|
status.c_companion_functions.push({
|
|
14863
15506
|
func,
|
|
14864
15507
|
struct_name,
|
|
14865
|
-
raw_code
|
|
15508
|
+
raw_code,
|
|
15509
|
+
bridge_c_abi: float_bridge
|
|
14866
15510
|
});
|
|
14867
|
-
const
|
|
15511
|
+
const is_ctor = func.name === "#init" && !!struct_name;
|
|
15512
|
+
const return_type = is_ctor ? "" : func.return_type?.name || "";
|
|
14868
15513
|
const return_struct = status.structs.find((s) => s.name === return_type && !s.is_simple_type && !s.is_class);
|
|
14869
15514
|
const enum_sret_size = get_enum_sret_size(return_type, status);
|
|
15515
|
+
let sret_size;
|
|
14870
15516
|
if (return_struct) {
|
|
14871
15517
|
const struct_size = get_struct_size(return_type, status);
|
|
14872
|
-
if (struct_size <= 16)
|
|
15518
|
+
if (struct_size <= 16) sret_size = struct_size;
|
|
14873
15519
|
} else if (enum_sret_size !== void 0) {
|
|
14874
|
-
if (enum_sret_size <= 16)
|
|
15520
|
+
if (enum_sret_size <= 16) sret_size = enum_sret_size;
|
|
14875
15521
|
}
|
|
15522
|
+
if (float_bridge) emit_c_abi_thunk(func, struct_name, status, is_ctor ? void 0 : sret_size);
|
|
15523
|
+
else if (!is_ctor && sret_size !== void 0) emit_c_abi_thunk(func, struct_name, status, sret_size);
|
|
14876
15524
|
return true;
|
|
14877
15525
|
}
|
|
14878
15526
|
return false;
|
|
14879
15527
|
}
|
|
14880
|
-
|
|
15528
|
+
/**
|
|
15529
|
+
* Does this parameter type ride the C ABI's d-register file? Nomen `float*`
|
|
15530
|
+
* types are 64-bit doubles (`c_type: "double"`).
|
|
15531
|
+
*/
|
|
15532
|
+
function is_float_type_name(name) {
|
|
15533
|
+
return name.startsWith("float") || name.startsWith("ufloat");
|
|
15534
|
+
}
|
|
15535
|
+
/** Any float param, or a float return? (see emit_c_abi_thunk case 2) */
|
|
15536
|
+
function has_float_abi_divergence(func) {
|
|
15537
|
+
if (is_float_type_name(func.return_type?.name || "")) return true;
|
|
15538
|
+
return func.params.some((p) => !p.is_variadic && is_float_type_name(p.type.name));
|
|
15539
|
+
}
|
|
15540
|
+
function emit_c_abi_thunk(func, struct_name, status, struct_size) {
|
|
14881
15541
|
const func_label = get_func_label(func, struct_name, status);
|
|
14882
|
-
const c_label = `${func_label}_c
|
|
14883
|
-
const words = Math.ceil(struct_size / 8);
|
|
15542
|
+
const c_label = sanitize_companion_label(`${func_label}_c`);
|
|
15543
|
+
const words = struct_size !== void 0 ? Math.ceil(struct_size / 8) : 0;
|
|
15544
|
+
const return_is_float = is_float_type_name(func.return_type?.name || "");
|
|
14884
15545
|
emit_asm(status, `.p2align 2\n`);
|
|
14885
15546
|
emit_asm(status, `${func_label}:\n`);
|
|
14886
15547
|
emit_asm(status, `stp x29, x30, [sp, #-16]!\n`);
|
|
14887
|
-
emit_asm(status, `
|
|
14888
|
-
|
|
14889
|
-
|
|
14890
|
-
|
|
14891
|
-
|
|
14892
|
-
|
|
14893
|
-
|
|
14894
|
-
|
|
15548
|
+
emit_asm(status, `mov x29, sp\n`);
|
|
15549
|
+
if (struct_size !== void 0) {
|
|
15550
|
+
emit_asm(status, `sub sp, sp, #16\n`);
|
|
15551
|
+
emit_asm(status, `str x8, [sp, #0]\n`);
|
|
15552
|
+
}
|
|
15553
|
+
let in_slot = 0;
|
|
15554
|
+
let out_x = 0;
|
|
15555
|
+
let out_d = 0;
|
|
15556
|
+
const x_moves = [];
|
|
15557
|
+
const d_moves = [];
|
|
15558
|
+
for (const param of func.params) {
|
|
15559
|
+
if (is_float_type_name(param.type.name)) {
|
|
15560
|
+
d_moves.push({
|
|
15561
|
+
out: out_d++,
|
|
15562
|
+
in: in_slot
|
|
15563
|
+
});
|
|
15564
|
+
in_slot += 1;
|
|
15565
|
+
continue;
|
|
15566
|
+
}
|
|
15567
|
+
const slots = param.type.name === "string" || param.type.is_view ? 2 : 1;
|
|
15568
|
+
x_moves.push({
|
|
15569
|
+
out: out_x,
|
|
15570
|
+
in: in_slot
|
|
15571
|
+
});
|
|
15572
|
+
out_x += slots;
|
|
15573
|
+
in_slot += slots;
|
|
14895
15574
|
}
|
|
14896
|
-
emit_asm(status, `
|
|
15575
|
+
for (const m of d_moves) emit_asm(status, `fmov d${m.out}, x${m.in}\n`);
|
|
15576
|
+
for (let i = x_moves.length - 1; i >= 0; i--) {
|
|
15577
|
+
const { out, in: inc } = x_moves[i];
|
|
15578
|
+
if (out !== inc) emit_asm(status, `mov x${out}, x${inc}\n`);
|
|
15579
|
+
}
|
|
15580
|
+
emit_asm(status, `bl ${c_label}\n`);
|
|
15581
|
+
if (struct_size !== void 0) {
|
|
15582
|
+
emit_asm(status, `ldr x8, [sp, #0]\n`);
|
|
15583
|
+
for (let i = 0; i < words; i++) emit_asm(status, `str ${i === 0 ? "x0" : i === 1 ? "x1" : `x${i}`}, [x8, #${i * 8}]\n`);
|
|
15584
|
+
emit_asm(status, `add sp, sp, #16\n`);
|
|
15585
|
+
} else if (return_is_float) emit_asm(status, `fmov x0, d0\n`);
|
|
14897
15586
|
emit_asm(status, `ldp x29, x30, [sp], #16\n`);
|
|
14898
15587
|
emit_asm(status, `ret\n`);
|
|
14899
15588
|
}
|
|
@@ -16875,9 +17564,11 @@ function is_borrow_accessor_carrier_stamp(decl) {
|
|
|
16875
17564
|
* them; the `_recv_N` carrier skips its destroy entirely (it must not free the
|
|
16876
17565
|
* ALIASED string fields), so each expression use leaked the copied containers.
|
|
16877
17566
|
* Free exactly the fields the load deep-copied — the same predicate — and only
|
|
16878
|
-
* those: plain strings
|
|
16879
|
-
*
|
|
16880
|
-
*
|
|
17567
|
+
* those: plain strings and nested structs stay aliased/untouched (they were
|
|
17568
|
+
* not deep-copied, so freeing them would be an invalid free). Enum-with-data
|
|
17569
|
+
* fields ride the same gate the load's strdup arm used (enum_copy_is_deep) —
|
|
17570
|
+
* their copied payloads are freed here. Mirrors the C backend's
|
|
17571
|
+
* `emit_load_owned_copies_destroy`.
|
|
16881
17572
|
*/
|
|
16882
17573
|
function emit_borrow_carrier_owned_free(status, elem, decl_name) {
|
|
16883
17574
|
let offset = 8;
|
|
@@ -16899,10 +17590,18 @@ function emit_borrow_carrier_owned_free(status, elem, decl_name) {
|
|
|
16899
17590
|
continue;
|
|
16900
17591
|
}
|
|
16901
17592
|
const field_struct = resolve_struct_type(field.type, status);
|
|
16902
|
-
if (field_struct &&
|
|
17593
|
+
if (field_struct && field_load_deep_copy(field_struct, status)) {
|
|
16903
17594
|
emit_var_address(status, "x0", decl_name);
|
|
16904
17595
|
emit_asm(status, `add x0, x0, #${offset}\n`);
|
|
16905
17596
|
emit_asm(status, `bl ${field_struct.name}_destroy\n`);
|
|
17597
|
+
offset += size;
|
|
17598
|
+
continue;
|
|
17599
|
+
}
|
|
17600
|
+
const field_enum = status.enums.find((e) => e.name === field.type.name && e.has_associated_data);
|
|
17601
|
+
if (field_enum && enum_copy_is_deep$1(field_enum, status)) {
|
|
17602
|
+
emit_var_address(status, "x0", decl_name);
|
|
17603
|
+
emit_asm(status, `add x0, x0, #${offset}\n`);
|
|
17604
|
+
emit_enum_payload_frees_at(status, field_enum.name, "x0");
|
|
16906
17605
|
}
|
|
16907
17606
|
}
|
|
16908
17607
|
offset += size;
|
|
@@ -17214,6 +17913,12 @@ function emit_field_destroys_at(status, struct_type, root_slot, acc, root_is_cla
|
|
|
17214
17913
|
const offset = get_field_offset_of_fields(fields, field.name, status);
|
|
17215
17914
|
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);
|
|
17216
17915
|
const field_acc = acc + offset;
|
|
17916
|
+
if (field.type.is_array && field_struct && !field_struct.is_class) {
|
|
17917
|
+
const elem_size = get_struct_size(field_struct.name, status);
|
|
17918
|
+
const arr_len = field.type.length ? parseInt(field.type.length.value || "0") : 0;
|
|
17919
|
+
for (let i = 0; i < arr_len; i++) emit_destroy_for_array_elem(status, field_struct, () => emit_slot_base(status, root_slot, root_is_class, field_acc), i * elem_size);
|
|
17920
|
+
continue;
|
|
17921
|
+
}
|
|
17217
17922
|
if (field_struct) {
|
|
17218
17923
|
if (field_struct.is_class && !field.type.is_ref) {
|
|
17219
17924
|
emit_slot_base(status, root_slot, root_is_class, field_acc);
|
|
@@ -17398,6 +18103,10 @@ function release_records_for_scope_exit(status, decl, moved) {
|
|
|
17398
18103
|
* before.
|
|
17399
18104
|
*/
|
|
17400
18105
|
function emit_moved_local_cleanup(status, decl) {
|
|
18106
|
+
if (decl.type.is_array && decl.type.length) {
|
|
18107
|
+
emit_fixed_array_local_destroys(status, decl);
|
|
18108
|
+
return;
|
|
18109
|
+
}
|
|
17401
18110
|
const owns_flag = status.mv_owns_flag?.get(decl.name);
|
|
17402
18111
|
if (owns_flag !== void 0 && decl.type.name === "string" && !decl.type.is_array && status.heap_strings?.has(decl.name)) {
|
|
17403
18112
|
const skip = `.Lskip_mvowns_${status.label_counter = (status.label_counter ?? 0) + 1}`;
|
|
@@ -17515,6 +18224,31 @@ function emit_heap_array_decl_destroy(status, decl_name) {
|
|
|
17515
18224
|
}
|
|
17516
18225
|
return false;
|
|
17517
18226
|
}
|
|
18227
|
+
/**
|
|
18228
|
+
* A fixed-size array LOCAL of value-struct elements: destroy each element in
|
|
18229
|
+
* place (elem_size stride from the var's frame slot). `resolve_decl_struct`
|
|
18230
|
+
* resolves the ELEMENT struct by name — array-ness is a Type flag — so the
|
|
18231
|
+
* single-struct walk would reclaim element 0's fields only and leak the rest
|
|
18232
|
+
* (one label per extra element on a conformer row). Returns true when the
|
|
18233
|
+
* declaration was an array (handled or nothing to do); the caller `continue`s
|
|
18234
|
+
* so the single-struct path never mistreats the row.
|
|
18235
|
+
*/
|
|
18236
|
+
function emit_fixed_array_local_destroys(status, decl) {
|
|
18237
|
+
if (!decl.type.is_array || !decl.type.length) return false;
|
|
18238
|
+
const arr_len = parseInt(decl.type.length.value || "0");
|
|
18239
|
+
const elem_struct = is_struct_type$4(decl.type.name, status);
|
|
18240
|
+
if (arr_len > 0 && elem_struct && !elem_struct.is_class && (has_destroy(elem_struct) || struct_needs_destroy(elem_struct, status))) {
|
|
18241
|
+
const elem_size = get_struct_size(elem_struct.name, status);
|
|
18242
|
+
const was_moved = status.moved?.has(decl.name) ?? false;
|
|
18243
|
+
status.moved?.delete(decl.name);
|
|
18244
|
+
for (let i = 0; i < arr_len; i++) {
|
|
18245
|
+
emit_var_address(status, "x0", decl.name);
|
|
18246
|
+
emit_destroy_for_decl(status, decl.name, elem_struct.name, i * elem_size, decl.type.type_args, false);
|
|
18247
|
+
}
|
|
18248
|
+
if (was_moved) status.moved?.add(decl.name);
|
|
18249
|
+
}
|
|
18250
|
+
return true;
|
|
18251
|
+
}
|
|
17518
18252
|
function emit_destroy_for_decl(status, decl_name, decl_type_name, addr_offset, type_args, is_nullable) {
|
|
17519
18253
|
const moved = status.moved ?? /* @__PURE__ */ new Set();
|
|
17520
18254
|
release_records_for_scope_exit(status, {
|
|
@@ -17654,6 +18388,13 @@ function emit_field_destroys(status, struct_type, decl_name, base_offset, is_cla
|
|
|
17654
18388
|
continue;
|
|
17655
18389
|
}
|
|
17656
18390
|
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);
|
|
18391
|
+
if (field.type.is_array && field_struct && !field_struct.is_class) {
|
|
18392
|
+
const elem_size = get_struct_size(field_struct.name, status);
|
|
18393
|
+
const arr_len = field.type.length ? parseInt(field.type.length.value || "0") : 0;
|
|
18394
|
+
const actual_base = base_offset !== void 0 ? base_offset + offset : offset;
|
|
18395
|
+
for (let i = 0; i < arr_len; i++) emit_destroy_for_array_elem(status, field_struct, () => emit_base_ptr(status, decl_name, is_class_parent), actual_base + i * elem_size);
|
|
18396
|
+
continue;
|
|
18397
|
+
}
|
|
17657
18398
|
if (field_struct) {
|
|
17658
18399
|
if (field_struct.is_class && !field.type.is_ref) {
|
|
17659
18400
|
if (decl_name) emit_base_ptr(status, decl_name, is_class_parent);
|
|
@@ -17688,7 +18429,7 @@ function emit_field_destroys(status, struct_type, decl_name, base_offset, is_cla
|
|
|
17688
18429
|
emit_asm(status, `bl ${resolve_struct_name(field_struct.name, field.type.type_args, status)}_destroy\n`);
|
|
17689
18430
|
}
|
|
17690
18431
|
}
|
|
17691
|
-
if (field_has_explicit_destroy) emit_nested_field_destroys(status, field_struct, decl_name, base_offset !== void 0 ? base_offset + offset : offset
|
|
18432
|
+
if (field_has_explicit_destroy) emit_nested_field_destroys(status, field_struct, () => emit_base_ptr(status, decl_name, is_class_parent), base_offset !== void 0 ? base_offset + offset : offset);
|
|
17692
18433
|
}
|
|
17693
18434
|
} else if (free_strings && field.type.name === "string" && !field.type.is_array && !field.type.is_ref && !field.type.is_view) {
|
|
17694
18435
|
const actual_offset = base_offset !== void 0 ? base_offset + offset : offset;
|
|
@@ -17697,6 +18438,9 @@ function emit_field_destroys(status, struct_type, decl_name, base_offset, is_cla
|
|
|
17697
18438
|
emit_free(status);
|
|
17698
18439
|
} else if (field.type.name === "func" && !field.type.is_array && (struct_type.is_class || struct_type.is_owning === true)) {
|
|
17699
18440
|
const actual_offset = base_offset !== void 0 ? base_offset + offset : offset;
|
|
18441
|
+
const func_field_borrow = !!decl_name && !!status.func_field_borrow_decls?.has(decl_name);
|
|
18442
|
+
if (func_field_borrow) status.func_field_borrow_decls?.delete(decl_name);
|
|
18443
|
+
if (func_field_borrow) continue;
|
|
17700
18444
|
if (decl_name) emit_base_ptr(status, decl_name, is_class_parent);
|
|
17701
18445
|
const id = status.label_counter = (status.label_counter ?? 0) + 1;
|
|
17702
18446
|
const skip = `.Lfield_closure_done_${id}`;
|
|
@@ -17723,37 +18467,38 @@ function emit_field_destroys(status, struct_type, decl_name, base_offset, is_cla
|
|
|
17723
18467
|
const elem_size = get_struct_size(field.type.name, status);
|
|
17724
18468
|
const arr_len = field.type.length ? parseInt(field.type.length.value || "0") : 0;
|
|
17725
18469
|
const actual_base = base_offset !== void 0 ? base_offset + offset : offset;
|
|
17726
|
-
for (let i = 0; i < arr_len; i++) emit_destroy_for_array_elem(status, elem_struct, decl_name, actual_base + i * elem_size
|
|
18470
|
+
for (let i = 0; i < arr_len; i++) emit_destroy_for_array_elem(status, elem_struct, () => emit_base_ptr(status, decl_name, is_class_parent), actual_base + i * elem_size);
|
|
17727
18471
|
}
|
|
17728
18472
|
}
|
|
17729
18473
|
}
|
|
17730
18474
|
}
|
|
17731
|
-
function emit_nested_field_destroys(status, struct_type,
|
|
18475
|
+
function emit_nested_field_destroys(status, struct_type, emit_base, base_offset) {
|
|
17732
18476
|
for (const field of struct_type.fields) {
|
|
17733
18477
|
const offset = get_field_offset_of_fields(struct_type.fields, field.name, status);
|
|
17734
18478
|
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);
|
|
17735
18479
|
if (field_struct) {
|
|
17736
18480
|
if (has_destroy(field_struct)) {
|
|
17737
|
-
|
|
18481
|
+
emit_base();
|
|
17738
18482
|
emit_asm(status, `add x0, x0, #${base_offset + offset}\n`);
|
|
17739
18483
|
emit_asm(status, `bl ${resolve_struct_name(field_struct.name, field.type.type_args, status)}_destroy\n`);
|
|
17740
18484
|
}
|
|
17741
|
-
emit_nested_field_destroys(status, field_struct,
|
|
18485
|
+
emit_nested_field_destroys(status, field_struct, emit_base, base_offset + offset);
|
|
17742
18486
|
}
|
|
17743
18487
|
}
|
|
17744
18488
|
}
|
|
17745
|
-
function emit_destroy_for_array_elem(status, struct_type,
|
|
17746
|
-
if (has_destroy(struct_type)) {
|
|
17747
|
-
|
|
18489
|
+
function emit_destroy_for_array_elem(status, struct_type, emit_base, elem_offset) {
|
|
18490
|
+
if (has_destroy(struct_type) || struct_type.is_class || struct_needs_auto_destroy(struct_type, status)) {
|
|
18491
|
+
emit_base();
|
|
17748
18492
|
emit_asm(status, `add x0, x0, #${elem_offset}\n`);
|
|
17749
18493
|
emit_asm(status, `bl ${struct_type.name}_destroy\n`);
|
|
18494
|
+
if (struct_type.is_class || struct_type.is_owning === true) return;
|
|
17750
18495
|
}
|
|
17751
18496
|
let offset = 8;
|
|
17752
18497
|
for (const field of struct_type.fields) {
|
|
17753
18498
|
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);
|
|
17754
18499
|
if (field_struct) {
|
|
17755
18500
|
const field_size = get_type_size(field.type, status);
|
|
17756
|
-
emit_nested_field_destroys(status, field_struct,
|
|
18501
|
+
emit_nested_field_destroys(status, field_struct, emit_base, elem_offset + offset);
|
|
17757
18502
|
offset += field_size;
|
|
17758
18503
|
} else offset += get_type_size(field.type, status);
|
|
17759
18504
|
}
|
|
@@ -17924,6 +18669,8 @@ function emit_destroy_for_scope(status, declarations_before) {
|
|
|
17924
18669
|
continue;
|
|
17925
18670
|
}
|
|
17926
18671
|
if (is_field_struct_borrow(decl)) continue;
|
|
18672
|
+
if (is_pure_borrow_element_load(decl, status)) continue;
|
|
18673
|
+
if (emit_fixed_array_local_destroys(status, decl)) continue;
|
|
17927
18674
|
const resolved_decl = resolve_decl_struct(decl, status);
|
|
17928
18675
|
if (!resolved_decl) continue;
|
|
17929
18676
|
const struct_type = resolved_decl.struct_type;
|
|
@@ -18048,6 +18795,8 @@ function emit_destroy_for_scope(status, declarations_before) {
|
|
|
18048
18795
|
continue;
|
|
18049
18796
|
}
|
|
18050
18797
|
if (is_field_struct_borrow(decl)) continue;
|
|
18798
|
+
if (is_pure_borrow_element_load(decl, status)) continue;
|
|
18799
|
+
if (emit_fixed_array_local_destroys(status, decl)) continue;
|
|
18051
18800
|
const resolved_decl = resolve_decl_struct(decl, status);
|
|
18052
18801
|
if (!resolved_decl) continue;
|
|
18053
18802
|
const struct_type = resolved_decl.struct_type;
|
|
@@ -18188,6 +18937,7 @@ function emit_owning_field_strdups_at(status, struct_name, base_reg, base_offset
|
|
|
18188
18937
|
if (field.type.is_ref || field.type.is_view || field.type.is_array) continue;
|
|
18189
18938
|
const field_struct = status.structs.find((s) => s.name === field.type.name && !s.is_simple_type && !s.is_class);
|
|
18190
18939
|
if (!field_struct) continue;
|
|
18940
|
+
if (field_load_deep_copy(field_struct, status)) continue;
|
|
18191
18941
|
if (!(has_destroy(field_struct) ? owns_strings_uniformly(field_struct) : struct_needs_auto_destroy(field_struct, status))) continue;
|
|
18192
18942
|
const nested_off = base_offset + get_field_offset(struct_name, field.name, status);
|
|
18193
18943
|
if (is_nullable_struct_type(field.type, status)) {
|
|
@@ -21107,10 +21857,14 @@ function emit_transfer_owning_fields$1(status, src, fields) {
|
|
|
21107
21857
|
}
|
|
21108
21858
|
/**
|
|
21109
21859
|
* Emit the deep-copy half of mv load_T into the sret buffer (x8): for
|
|
21110
|
-
* each
|
|
21111
|
-
*
|
|
21860
|
+
* each field that `field_load_deep_copy` accepts (the containers, and
|
|
21861
|
+
* nested mv user structs whose auto-derived `copy` is deep), replace the
|
|
21862
|
+
* shallow-copied field with `<Mono>_copy(&slot.field)` (sret → the sret
|
|
21112
21863
|
* buffer's field; self == x8 is safe — the copy reads its input before the
|
|
21113
|
-
* final result write)
|
|
21864
|
+
* final result write); a `Box<T>` field is cloned, and an enum-with-data
|
|
21865
|
+
* field whose payloads are all deep-copyable gets its string payloads
|
|
21866
|
+
* strdup'd in place (the enum_copy_is_deep gate). Mirrors the C backend's
|
|
21867
|
+
* `emit_load_deep_copy_fields`.
|
|
21114
21868
|
* `sret` is the register holding the returned struct's address; `slot` holds
|
|
21115
21869
|
* the source slot's address (the copy reads from the slot, writes to sret).
|
|
21116
21870
|
*/
|
|
@@ -21134,11 +21888,15 @@ function emit_load_deep_copy_fields$1(elem, status, sret, slot) {
|
|
|
21134
21888
|
continue;
|
|
21135
21889
|
}
|
|
21136
21890
|
const field_struct = resolve_struct_type(field.type, status);
|
|
21137
|
-
if (field_struct &&
|
|
21891
|
+
if (field_struct && field_load_deep_copy(field_struct, status)) {
|
|
21138
21892
|
emit_asm(status, `add x8, ${sret}, #${offset}\n`);
|
|
21139
21893
|
emit_asm(status, `add x0, ${slot}, #${offset}\n`);
|
|
21140
21894
|
emit_asm(status, `bl ${field_struct.name}_copy\n`);
|
|
21895
|
+
offset += size;
|
|
21896
|
+
continue;
|
|
21141
21897
|
}
|
|
21898
|
+
const field_enum = status.enums.find((e) => e.name === field.type.name && e.has_associated_data);
|
|
21899
|
+
if (field_enum && enum_copy_is_deep$1(field_enum, status)) emit_enum_payload_strdups_at(status, field_enum.name, sret, offset);
|
|
21142
21900
|
}
|
|
21143
21901
|
offset += size;
|
|
21144
21902
|
}
|
|
@@ -21246,12 +22004,20 @@ function emit_owning_buffer_inline_aarch64(struct_node, func_name, status) {
|
|
|
21246
22004
|
function emit_owning_buffer_standalone_aarch64(node, func_name, status) {
|
|
21247
22005
|
const elem = owning_buffer_element_aarch64(node, status);
|
|
21248
22006
|
if (elem) {
|
|
21249
|
-
if (func_name !== "store" && func_name !== "replace" && func_name !== "shift" && func_name !== "modify" && func_name !== "load") return false;
|
|
22007
|
+
if (func_name !== "store" && func_name !== "replace" && func_name !== "shift" && func_name !== "modify" && func_name !== "load" && func_name !== "alloc") return false;
|
|
22008
|
+
if (func_name === "alloc") {
|
|
22009
|
+
emit_owning_alloc_T(elem, status);
|
|
22010
|
+
return true;
|
|
22011
|
+
}
|
|
21250
22012
|
emit_owning_standalone_struct(elem, func_name, status);
|
|
21251
22013
|
return true;
|
|
21252
22014
|
}
|
|
21253
22015
|
const enum_elem = owning_buffer_enum_element_aarch64(node, status);
|
|
21254
22016
|
if (enum_elem) {
|
|
22017
|
+
if (func_name === "alloc") {
|
|
22018
|
+
emit_enum_alloc_T(enum_elem, status);
|
|
22019
|
+
return true;
|
|
22020
|
+
}
|
|
21255
22021
|
if (func_name === "store") {
|
|
21256
22022
|
emit_enum_store_T(enum_elem, status, "x19");
|
|
21257
22023
|
return true;
|
|
@@ -21271,6 +22037,10 @@ function emit_owning_buffer_standalone_aarch64(node, func_name, status) {
|
|
|
21271
22037
|
return false;
|
|
21272
22038
|
}
|
|
21273
22039
|
if (owning_buffer_is_string_elem_aarch64(node)) {
|
|
22040
|
+
if (func_name === "alloc") {
|
|
22041
|
+
emit_string_alloc_T(status);
|
|
22042
|
+
return true;
|
|
22043
|
+
}
|
|
21274
22044
|
if (func_name === "store") {
|
|
21275
22045
|
emit_string_store_T(status, "x19");
|
|
21276
22046
|
return true;
|
|
@@ -21338,6 +22108,7 @@ function emit_owning_standalone_struct(elem, func_name, status) {
|
|
|
21338
22108
|
emit_asm(status, `madd x4, x1, x22, xzr\n`);
|
|
21339
22109
|
emit_asm(status, `add x20, x20, x4\n`);
|
|
21340
22110
|
emit_asm(status, `mov x21, x2\n`);
|
|
22111
|
+
if (func_name === "store") emit_displaced_owned_reclaim_a64(status, elem, "x20", "x21");
|
|
21341
22112
|
if (func_name === "replace") emit_guarded_slot_destroy(status, elem, "x20");
|
|
21342
22113
|
if (func_name === "store" && string_fields.length) {
|
|
21343
22114
|
emit_asm(status, `sub sp, sp, #${Math.ceil(string_fields.length * 8 / 16) * 16}\n`);
|
|
@@ -21621,6 +22392,7 @@ function emit_owning_store_T(elem, status) {
|
|
|
21621
22392
|
emit_asm(status, `madd x4, x1, x22, xzr\n`);
|
|
21622
22393
|
emit_asm(status, `add x20, x20, x4\n`);
|
|
21623
22394
|
emit_asm(status, `mov x21, x2\n`);
|
|
22395
|
+
emit_displaced_owned_reclaim_a64(status, elem, "x20", "x21");
|
|
21624
22396
|
if (string_fields.length) {
|
|
21625
22397
|
emit_asm(status, `sub sp, sp, #${Math.ceil(string_fields.length * 8 / 16) * 16}\n`);
|
|
21626
22398
|
string_fields.forEach((f, i) => {
|
|
@@ -21692,6 +22464,86 @@ function emit_owning_replace_T(elem, status) {
|
|
|
21692
22464
|
* `#destroy` body would fire on empty cap slots. `slot_reg` must hold the
|
|
21693
22465
|
* slot ADDRESS and is clobbered only by the destroy call itself.
|
|
21694
22466
|
*/
|
|
22467
|
+
/**
|
|
22468
|
+
* Specialized `alloc` for `Buffer<string>`: free each old slot's ptr half
|
|
22469
|
+
* (zeroed slots free NULL — a no-op), free the slab, calloc the exact fresh
|
|
22470
|
+
* slab. x19 = self, x1 = size; returns cap.
|
|
22471
|
+
*/
|
|
22472
|
+
function emit_string_alloc_T(status) {
|
|
22473
|
+
const lbl = (s) => `.Lsalloc_${s}_${status.label_counter = (status.label_counter ?? 0) + 1}`;
|
|
22474
|
+
const fresh = lbl("fresh");
|
|
22475
|
+
const drop = lbl("drop");
|
|
22476
|
+
const loop = lbl("loop");
|
|
22477
|
+
const skip = lbl("skip");
|
|
22478
|
+
emit_asm(status, `stp x20, x21, [sp, #-16]!\n`);
|
|
22479
|
+
emit_asm(status, `str x22, [sp, #-16]!\n`);
|
|
22480
|
+
emit_asm(status, `mov x22, x1\n`);
|
|
22481
|
+
emit_asm(status, `ldr x20, [x19, #8]\n`);
|
|
22482
|
+
emit_asm(status, `cbz x20, ${fresh}\n`);
|
|
22483
|
+
emit_asm(status, `ldr x21, [x19, #16]\n`);
|
|
22484
|
+
emit_asm(status, `cbz x21, ${drop}\n`);
|
|
22485
|
+
emit_asm(status, `${loop}:\n`);
|
|
22486
|
+
emit_asm(status, `sub x21, x21, #1\n`);
|
|
22487
|
+
emit_asm(status, `lsl x9, x21, #4\n`);
|
|
22488
|
+
emit_asm(status, `add x9, x20, x9\n`);
|
|
22489
|
+
emit_asm(status, `ldr x0, [x9]\n`);
|
|
22490
|
+
emit_asm(status, `cbz x0, ${skip}\n`);
|
|
22491
|
+
emit_free(status);
|
|
22492
|
+
emit_asm(status, `${skip}:\n`);
|
|
22493
|
+
emit_asm(status, `cbnz x21, ${loop}\n`);
|
|
22494
|
+
emit_asm(status, `${drop}:\n`);
|
|
22495
|
+
emit_asm(status, `mov x0, x20\n`);
|
|
22496
|
+
emit_free(status);
|
|
22497
|
+
emit_asm(status, `${fresh}:\n`);
|
|
22498
|
+
emit_asm(status, `mov x0, x22\n`);
|
|
22499
|
+
emit_asm(status, `mov x1, #16\n`);
|
|
22500
|
+
emit_asm(status, status.audit ? `bl _nomen_calloc_wrap\n` : `bl _calloc\n`);
|
|
22501
|
+
emit_asm(status, `str x0, [x19, #8]\n`);
|
|
22502
|
+
emit_asm(status, `str x22, [x19, #16]\n`);
|
|
22503
|
+
emit_asm(status, `ldr x0, [x19, #16]\n`);
|
|
22504
|
+
emit_asm(status, `ldr x22, [sp], #16\n`);
|
|
22505
|
+
emit_asm(status, `ldp x20, x21, [sp], #16\n`);
|
|
22506
|
+
}
|
|
22507
|
+
/**
|
|
22508
|
+
* Specialized `alloc` for a Buffer whose element is an enum-with-data: free
|
|
22509
|
+
* each old slot's payloads in place, free the slab, calloc the exact fresh
|
|
22510
|
+
* slab. x19 = self, x1 = size; returns cap.
|
|
22511
|
+
*/
|
|
22512
|
+
function emit_enum_alloc_T(en, status) {
|
|
22513
|
+
const E_SIZE = get_enum_size(en.name, status);
|
|
22514
|
+
const lbl = (s) => `.Lealloc_${s}_${status.label_counter = (status.label_counter ?? 0) + 1}`;
|
|
22515
|
+
const fresh = lbl("fresh");
|
|
22516
|
+
const drop = lbl("drop");
|
|
22517
|
+
const loop = lbl("loop");
|
|
22518
|
+
emit_asm(status, `stp x20, x21, [sp, #-16]!\n`);
|
|
22519
|
+
emit_asm(status, `str x22, [sp, #-16]!\n`);
|
|
22520
|
+
emit_asm(status, `str x23, [sp, #-16]!\n`);
|
|
22521
|
+
emit_asm(status, `mov x22, x1\n`);
|
|
22522
|
+
emit_asm(status, `ldr x20, [x19, #8]\n`);
|
|
22523
|
+
emit_asm(status, `cbz x20, ${fresh}\n`);
|
|
22524
|
+
emit_asm(status, `ldr x21, [x19, #16]\n`);
|
|
22525
|
+
emit_asm(status, `cbz x21, ${drop}\n`);
|
|
22526
|
+
emit_asm(status, `mov x23, x21\n`);
|
|
22527
|
+
emit_asm(status, `${loop}:\n`);
|
|
22528
|
+
emit_asm(status, `sub x23, x23, #1\n`);
|
|
22529
|
+
emit_asm(status, `mov x9, #${E_SIZE}\n`);
|
|
22530
|
+
emit_asm(status, `madd x10, x23, x9, x20\n`);
|
|
22531
|
+
emit_enum_payload_frees_at(status, en.name, "x10", 0);
|
|
22532
|
+
emit_asm(status, `cbnz x23, ${loop}\n`);
|
|
22533
|
+
emit_asm(status, `${drop}:\n`);
|
|
22534
|
+
emit_asm(status, `mov x0, x20\n`);
|
|
22535
|
+
emit_free(status);
|
|
22536
|
+
emit_asm(status, `${fresh}:\n`);
|
|
22537
|
+
emit_asm(status, `mov x0, x22\n`);
|
|
22538
|
+
emit_asm(status, `mov x1, #${E_SIZE}\n`);
|
|
22539
|
+
emit_asm(status, status.audit ? `bl _nomen_calloc_wrap\n` : `bl _calloc\n`);
|
|
22540
|
+
emit_asm(status, `str x0, [x19, #8]\n`);
|
|
22541
|
+
emit_asm(status, `str x22, [x19, #16]\n`);
|
|
22542
|
+
emit_asm(status, `ldr x0, [x19, #16]\n`);
|
|
22543
|
+
emit_asm(status, `ldr x23, [sp], #16\n`);
|
|
22544
|
+
emit_asm(status, `ldr x22, [sp], #16\n`);
|
|
22545
|
+
emit_asm(status, `ldp x20, x21, [sp], #16\n`);
|
|
22546
|
+
}
|
|
21695
22547
|
function emit_guarded_slot_destroy(status, elem, slot_reg) {
|
|
21696
22548
|
const skip = `.Lown_slot_skip_${status.label_counter = (status.label_counter ?? 0) + 1}`;
|
|
21697
22549
|
emit_asm(status, `ldr x9, [${slot_reg}]\n`);
|
|
@@ -21701,6 +22553,279 @@ function emit_guarded_slot_destroy(status, elem, slot_reg) {
|
|
|
21701
22553
|
emit_asm(status, `${skip}:\n`);
|
|
21702
22554
|
}
|
|
21703
22555
|
/**
|
|
22556
|
+
* The container monos whose `<T>_destroy` reclaims the whole container
|
|
22557
|
+
* (slab + per-element owned state): the core value-slot containers. A user
|
|
22558
|
+
* mv struct must NOT take this path (its destroy would free its string
|
|
22559
|
+
* fields too — the string machinery owns those) — such fields recurse.
|
|
22560
|
+
*/
|
|
22561
|
+
const CONTAINER_MONO_PREFIXES_A64 = [
|
|
22562
|
+
"Buffer_",
|
|
22563
|
+
"List_",
|
|
22564
|
+
"Map_",
|
|
22565
|
+
"Set_",
|
|
22566
|
+
"Array_"
|
|
22567
|
+
];
|
|
22568
|
+
function is_container_mono_name_a64(name) {
|
|
22569
|
+
return CONTAINER_MONO_PREFIXES_A64.some((p) => name.startsWith(p));
|
|
22570
|
+
}
|
|
22571
|
+
/** Offsets (relative to the container struct start) of every owned slab
|
|
22572
|
+
* pointer inside a container mono: the `data` words of the Buffer-shaped
|
|
22573
|
+
* structs reachable through nested struct fields. The displaced-reclaim
|
|
22574
|
+
* guard compares ALL of them — a byte-copy round-trip aliases every slab
|
|
22575
|
+
* pointer; any differing pointer means the displaced container's slabs are
|
|
22576
|
+
* exclusively owned. */
|
|
22577
|
+
function container_data_offsets_a64(fs, status, base = 0) {
|
|
22578
|
+
const out = [];
|
|
22579
|
+
let offset = 8;
|
|
22580
|
+
for (const field of fs.fields) {
|
|
22581
|
+
const size = get_type_size(field.type, status);
|
|
22582
|
+
if (field.name === "data") {
|
|
22583
|
+
out.push(base + offset);
|
|
22584
|
+
offset += size;
|
|
22585
|
+
continue;
|
|
22586
|
+
}
|
|
22587
|
+
if (!field.type.is_ref && !field.type.is_view && !field.type.is_array) {
|
|
22588
|
+
if (field.type.name === "string" && !field.type.is_array) {
|
|
22589
|
+
offset += aarch64_size("string");
|
|
22590
|
+
continue;
|
|
22591
|
+
}
|
|
22592
|
+
const sub = resolve_struct_type(field.type, status);
|
|
22593
|
+
if (sub && !sub.is_class && is_container_mono_name_a64(sub.name)) {
|
|
22594
|
+
out.push(...container_data_offsets_a64(sub, status, base + offset));
|
|
22595
|
+
offset += size;
|
|
22596
|
+
continue;
|
|
22597
|
+
}
|
|
22598
|
+
}
|
|
22599
|
+
offset += size;
|
|
22600
|
+
}
|
|
22601
|
+
return out;
|
|
22602
|
+
}
|
|
22603
|
+
/**
|
|
22604
|
+
* Collect the owned POINTER leaves of an element struct whose displaced
|
|
22605
|
+
* copies a store onto an occupied slot must reclaim: containers, Box/class
|
|
22606
|
+
* fields, func descriptors, enum-with-data payloads. Plain string fields are
|
|
22607
|
+
* EXCLUDED (store_T's string machinery owns their displaced-old reclaim, and
|
|
22608
|
+
* a whole-struct destroy here would double-free them); plain nested mv
|
|
22609
|
+
* structs recurse so their pointer leaves are reached.
|
|
22610
|
+
*/
|
|
22611
|
+
function collect_displaced_leaves_a64(elem, status, base = 0) {
|
|
22612
|
+
const leaves = [];
|
|
22613
|
+
let offset = 8;
|
|
22614
|
+
for (const field of elem.fields) {
|
|
22615
|
+
const size = get_type_size(field.type, status);
|
|
22616
|
+
if (!field.type.is_ref && !field.type.is_view && !field.type.is_array) {
|
|
22617
|
+
if (field.type.name === "string" && !field.type.is_array) {
|
|
22618
|
+
offset += aarch64_size("string");
|
|
22619
|
+
continue;
|
|
22620
|
+
}
|
|
22621
|
+
const box_mono = box_field_mono(field.type, status);
|
|
22622
|
+
if (box_mono) {
|
|
22623
|
+
leaves.push({
|
|
22624
|
+
offset: base + offset,
|
|
22625
|
+
kind: "box",
|
|
22626
|
+
destroy: box_mono.name,
|
|
22627
|
+
identities: [0]
|
|
22628
|
+
});
|
|
22629
|
+
offset += size;
|
|
22630
|
+
continue;
|
|
22631
|
+
}
|
|
22632
|
+
const fs = resolve_struct_type(field.type, status);
|
|
22633
|
+
if (fs?.is_class) {
|
|
22634
|
+
leaves.push({
|
|
22635
|
+
offset: base + offset,
|
|
22636
|
+
kind: "class",
|
|
22637
|
+
destroy: fs.name,
|
|
22638
|
+
identities: [0]
|
|
22639
|
+
});
|
|
22640
|
+
offset += size;
|
|
22641
|
+
continue;
|
|
22642
|
+
}
|
|
22643
|
+
if (field.type.name === "func") {
|
|
22644
|
+
leaves.push({
|
|
22645
|
+
offset: base + offset,
|
|
22646
|
+
kind: "func",
|
|
22647
|
+
identities: [0]
|
|
22648
|
+
});
|
|
22649
|
+
offset += size;
|
|
22650
|
+
continue;
|
|
22651
|
+
}
|
|
22652
|
+
const field_enum = status.enums.find((e) => e.name === field.type.name && e.has_associated_data);
|
|
22653
|
+
if (field_enum) {
|
|
22654
|
+
leaves.push({
|
|
22655
|
+
offset: base + offset,
|
|
22656
|
+
kind: "enum",
|
|
22657
|
+
enum_node: field_enum,
|
|
22658
|
+
identities: []
|
|
22659
|
+
});
|
|
22660
|
+
offset += size;
|
|
22661
|
+
continue;
|
|
22662
|
+
}
|
|
22663
|
+
if (fs && !fs.is_class) {
|
|
22664
|
+
if (is_container_mono_name_a64(fs.name)) {
|
|
22665
|
+
leaves.push({
|
|
22666
|
+
offset: base + offset,
|
|
22667
|
+
kind: "container",
|
|
22668
|
+
destroy: fs.name,
|
|
22669
|
+
identities: container_data_offsets_a64(fs, status)
|
|
22670
|
+
});
|
|
22671
|
+
offset += size;
|
|
22672
|
+
continue;
|
|
22673
|
+
}
|
|
22674
|
+
if (struct_needs_destroy(fs, status) || has_string_fields(fs, status)) {
|
|
22675
|
+
leaves.push(...collect_displaced_leaves_a64(fs, status, base + offset));
|
|
22676
|
+
offset += size;
|
|
22677
|
+
continue;
|
|
22678
|
+
}
|
|
22679
|
+
}
|
|
22680
|
+
}
|
|
22681
|
+
offset += size;
|
|
22682
|
+
}
|
|
22683
|
+
return leaves;
|
|
22684
|
+
}
|
|
22685
|
+
/**
|
|
22686
|
+
* Emit the DISPLACED-OWNED reclaim for a store onto an occupied slot (the
|
|
22687
|
+
* residual Buffer hole in FOLLOWUP.md — store's fresh-slot contract leaked
|
|
22688
|
+
* every owned pointer field of the previous occupant except strings, which
|
|
22689
|
+
* the B1 displaced-old free already handles). Runs BEFORE the slot memcpy
|
|
22690
|
+
* (both identity sides are live): per leaf, the slot's field is destroyed IN
|
|
22691
|
+
* PLACE only when the incoming value does not alias it — the load→modify→store
|
|
22692
|
+
* round-trip (a byte-copied alias) keeps the slot's own state, which the
|
|
22693
|
+
* subsequent memcpy then replaces wholesale.
|
|
22694
|
+
*
|
|
22695
|
+
* `slot_reg` holds &slot[i]; `val_reg` holds the incoming value's address.
|
|
22696
|
+
* Both must be callee-saved (the destroys call).
|
|
22697
|
+
*/
|
|
22698
|
+
function emit_displaced_owned_reclaim_a64(status, elem, slot_reg, val_reg) {
|
|
22699
|
+
const leaves = collect_displaced_leaves_a64(elem, status);
|
|
22700
|
+
for (const leaf of leaves) {
|
|
22701
|
+
const next = `.Ldisp_done_${status.label_counter = (status.label_counter ?? 0) + 1}`;
|
|
22702
|
+
if (leaf.kind === "container") {
|
|
22703
|
+
if (leaf.identities.length) {
|
|
22704
|
+
emit_asm(status, `ldr x9, [${slot_reg}, #${leaf.offset + leaf.identities[0]}]\n`);
|
|
22705
|
+
emit_asm(status, `cbz x9, ${next}\n`);
|
|
22706
|
+
}
|
|
22707
|
+
for (const id of leaf.identities) {
|
|
22708
|
+
emit_asm(status, `ldr x9, [${slot_reg}, #${leaf.offset + id}]\n`);
|
|
22709
|
+
emit_asm(status, `ldr x10, [${val_reg}, #${leaf.offset + id}]\n`);
|
|
22710
|
+
emit_asm(status, `cmp x9, x10\n`);
|
|
22711
|
+
emit_asm(status, `b.eq ${next}\n`);
|
|
22712
|
+
}
|
|
22713
|
+
if (slot_reg === "x0") {
|
|
22714
|
+
if (leaf.offset !== 0) emit_asm(status, `add x0, x0, #${leaf.offset}\n`);
|
|
22715
|
+
} else emit_asm(status, `add x0, ${slot_reg}, #${leaf.offset}\n`);
|
|
22716
|
+
emit_asm(status, `bl ${leaf.destroy}_destroy\n`);
|
|
22717
|
+
} else if (leaf.kind === "box" || leaf.kind === "class") {
|
|
22718
|
+
emit_asm(status, `ldr x9, [${slot_reg}, #${leaf.offset}]\n`);
|
|
22719
|
+
emit_asm(status, `cbz x9, ${next}\n`);
|
|
22720
|
+
emit_asm(status, `ldr x10, [${val_reg}, #${leaf.offset}]\n`);
|
|
22721
|
+
emit_asm(status, `cmp x9, x10\n`);
|
|
22722
|
+
emit_asm(status, `b.eq ${next}\n`);
|
|
22723
|
+
emit_asm(status, `mov x0, x9\n`);
|
|
22724
|
+
emit_asm(status, `bl ${leaf.destroy}_destroy\n`);
|
|
22725
|
+
emit_asm(status, `ldr x0, [${slot_reg}, #${leaf.offset}]\n`);
|
|
22726
|
+
emit_free(status);
|
|
22727
|
+
} else if (leaf.kind === "func") {
|
|
22728
|
+
const no_destroy = `.Ldisp_noown_${status.label_counter = (status.label_counter ?? 0) + 1}`;
|
|
22729
|
+
emit_asm(status, `ldr x9, [${slot_reg}, #${leaf.offset}]\n`);
|
|
22730
|
+
emit_asm(status, `cbz x9, ${next}\n`);
|
|
22731
|
+
emit_asm(status, `ldr x10, [${val_reg}, #${leaf.offset}]\n`);
|
|
22732
|
+
emit_asm(status, `cmp x9, x10\n`);
|
|
22733
|
+
emit_asm(status, `b.eq ${next}\n`);
|
|
22734
|
+
emit_asm(status, `ldr w11, [x9, #16]\n`);
|
|
22735
|
+
emit_asm(status, `cbz w11, ${next}\n`);
|
|
22736
|
+
emit_asm(status, `str x9, [sp, #-16]!\n`);
|
|
22737
|
+
emit_asm(status, `ldr x10, [x9, #24]\n`);
|
|
22738
|
+
emit_asm(status, `cbz x10, ${no_destroy}\n`);
|
|
22739
|
+
emit_asm(status, `ldr x0, [x9, #8]\n`);
|
|
22740
|
+
emit_asm(status, `blr x10\n`);
|
|
22741
|
+
emit_asm(status, `${no_destroy}:\n`);
|
|
22742
|
+
emit_asm(status, `ldr x0, [sp]\n`);
|
|
22743
|
+
emit_asm(status, `ldr x0, [x0, #8]\n`);
|
|
22744
|
+
emit_free(status);
|
|
22745
|
+
emit_asm(status, `ldr x0, [sp], #16\n`);
|
|
22746
|
+
emit_free(status);
|
|
22747
|
+
} else if (leaf.kind === "enum" && leaf.enum_node) {
|
|
22748
|
+
const en = leaf.enum_node;
|
|
22749
|
+
const owning_cases = en.cases.filter((c) => c.params.some((p) => p.type.name === "string" || enum_payload_is_reference_a64(p, status)));
|
|
22750
|
+
if (!owning_cases.length) continue;
|
|
22751
|
+
emit_asm(status, `ldr w9, [${slot_reg}, #${leaf.offset}]\n`);
|
|
22752
|
+
for (const c of owning_cases) {
|
|
22753
|
+
const idx = get_enum_case_index(en.name, c.name, status);
|
|
22754
|
+
const case_next = `.Ldisp_case_${status.label_counter = (status.label_counter ?? 0) + 1}`;
|
|
22755
|
+
emit_asm(status, `cmp w9, #${idx}\n`);
|
|
22756
|
+
emit_asm(status, `b.ne ${case_next}\n`);
|
|
22757
|
+
emit_asm(status, `ldr w10, [${val_reg}, #${leaf.offset}]\n`);
|
|
22758
|
+
emit_asm(status, `cmp w10, #${idx}\n`);
|
|
22759
|
+
emit_asm(status, `b.ne ${next}_reclaim\n`);
|
|
22760
|
+
for (const p of c.params) {
|
|
22761
|
+
if (p.type.name !== "string" && !enum_payload_is_reference_a64(p, status)) continue;
|
|
22762
|
+
const poff = get_enum_payload_offset(en.name, c.name, p.name, status);
|
|
22763
|
+
emit_asm(status, `ldr x9, [${slot_reg}, #${leaf.offset + poff}]\n`);
|
|
22764
|
+
emit_asm(status, `ldr x10, [${val_reg}, #${leaf.offset + poff}]\n`);
|
|
22765
|
+
emit_asm(status, `cmp x9, x10\n`);
|
|
22766
|
+
emit_asm(status, `b.eq ${next}\n`);
|
|
22767
|
+
}
|
|
22768
|
+
emit_asm(status, `${case_next}:\n`);
|
|
22769
|
+
}
|
|
22770
|
+
emit_asm(status, `b ${next}_reclaim\n`);
|
|
22771
|
+
emit_asm(status, `${next}_reclaim:\n`);
|
|
22772
|
+
emit_enum_payload_frees_at(status, en.name, slot_reg, leaf.offset);
|
|
22773
|
+
}
|
|
22774
|
+
emit_asm(status, `${next}:\n`);
|
|
22775
|
+
}
|
|
22776
|
+
}
|
|
22777
|
+
/** The aarch64-side payload reference check (mirrors the C reclaim's). */
|
|
22778
|
+
function enum_payload_is_reference_a64(p, status) {
|
|
22779
|
+
return status.structs.some((s) => s.name === p.type.name && s.is_class) || status.traits.some((t) => t.name === p.type.name);
|
|
22780
|
+
}
|
|
22781
|
+
/**
|
|
22782
|
+
* Specialized `alloc` for a Buffer whose element type owns heap data:
|
|
22783
|
+
* per-element destroy of the OLD slab (guarded on the constructed stamp —
|
|
22784
|
+
* grown-but-unwritten and moved-out slots are zeroed), free the slab, then
|
|
22785
|
+
* calloc the exact fresh one. The raw body dropped the old pointer — the
|
|
22786
|
+
* second Buffer hole in FOLLOWUP.md. x19 = self, x1 = size; returns cap.
|
|
22787
|
+
*/
|
|
22788
|
+
function emit_owning_alloc_T(elem, status) {
|
|
22789
|
+
const T_SIZE = get_struct_size(elem.name, status);
|
|
22790
|
+
const lbl = (s) => `.Lalloc_${s}_${status.label_counter = (status.label_counter ?? 0) + 1}`;
|
|
22791
|
+
const fresh = lbl("fresh");
|
|
22792
|
+
const drop = lbl("drop");
|
|
22793
|
+
const loop = lbl("loop");
|
|
22794
|
+
const skip = lbl("skip");
|
|
22795
|
+
const done = lbl("done");
|
|
22796
|
+
emit_asm(status, `stp x20, x21, [sp, #-16]!\n`);
|
|
22797
|
+
emit_asm(status, `str x22, [sp, #-16]!\n`);
|
|
22798
|
+
emit_asm(status, `mov x22, x1\n`);
|
|
22799
|
+
emit_asm(status, `ldr x20, [x19, #8]\n`);
|
|
22800
|
+
emit_asm(status, `cbz x20, ${fresh}\n`);
|
|
22801
|
+
emit_asm(status, `ldr x21, [x19, #16]\n`);
|
|
22802
|
+
emit_asm(status, `cbz x21, ${drop}\n`);
|
|
22803
|
+
emit_asm(status, `${loop}:\n`);
|
|
22804
|
+
emit_asm(status, `mov x9, #${T_SIZE}\n`);
|
|
22805
|
+
emit_asm(status, `madd x10, x21, x9, x20\n`);
|
|
22806
|
+
emit_asm(status, `sub x10, x10, x9\n`);
|
|
22807
|
+
emit_asm(status, `ldr x9, [x10]\n`);
|
|
22808
|
+
emit_asm(status, `cbz x9, ${skip}\n`);
|
|
22809
|
+
emit_asm(status, `mov x0, x10\n`);
|
|
22810
|
+
emit_asm(status, `bl ${elem.name}_destroy\n`);
|
|
22811
|
+
emit_asm(status, `${skip}:\n`);
|
|
22812
|
+
emit_asm(status, `sub x21, x21, #1\n`);
|
|
22813
|
+
emit_asm(status, `cbnz x21, ${loop}\n`);
|
|
22814
|
+
emit_asm(status, `${drop}:\n`);
|
|
22815
|
+
emit_asm(status, `mov x0, x20\n`);
|
|
22816
|
+
emit_free(status);
|
|
22817
|
+
emit_asm(status, `${fresh}:\n`);
|
|
22818
|
+
emit_asm(status, `mov x0, x22\n`);
|
|
22819
|
+
emit_asm(status, `mov x1, #${T_SIZE}\n`);
|
|
22820
|
+
emit_asm(status, status.audit ? `bl _nomen_calloc_wrap\n` : `bl _calloc\n`);
|
|
22821
|
+
emit_asm(status, `str x0, [x19, #8]\n`);
|
|
22822
|
+
emit_asm(status, `str x22, [x19, #16]\n`);
|
|
22823
|
+
emit_asm(status, `ldr x0, [x19, #16]\n`);
|
|
22824
|
+
emit_asm(status, `${done}:\n`);
|
|
22825
|
+
emit_asm(status, `ldr x22, [sp], #16\n`);
|
|
22826
|
+
emit_asm(status, `ldp x20, x21, [sp], #16\n`);
|
|
22827
|
+
}
|
|
22828
|
+
/**
|
|
21704
22829
|
* Emit a specialized #destroy for a Buffer whose element type is a mv
|
|
21705
22830
|
* value struct. Per-element T_destroy, then free the slab. Called from
|
|
21706
22831
|
* build_struct_node instead of build_destroy_function when the element owns.
|
|
@@ -23362,7 +24487,7 @@ function build_function_node$1(node, status) {
|
|
|
23362
24487
|
const old_code = status.code;
|
|
23363
24488
|
status.code = "";
|
|
23364
24489
|
node.return_type = resolve_mono_type(node.return_type, status);
|
|
23365
|
-
const return_struct = !node.return_type.is_view && !node.return_type.is_array && (!!status.structs.find((s) => s.name === node.return_type.name && !s.is_simple_type && !s.is_class) || get_enum_sret_size(node.return_type.name, status) !== void 0);
|
|
24490
|
+
const return_struct = !node.return_type.is_view && !node.return_type.is_array && !node.return_type.is_pointer && (!!status.structs.find((s) => s.name === node.return_type.name && !s.is_simple_type && !s.is_class) || get_enum_sret_size(node.return_type.name, status) !== void 0);
|
|
23366
24491
|
const nullable_scalar_ret = is_nullable_scalar_type(node.return_type, status) && !node.return_type.is_ref;
|
|
23367
24492
|
const uses_sret = return_struct || nullable_scalar_ret;
|
|
23368
24493
|
const old_struct_return_buffer = status.struct_return_buffer;
|
|
@@ -24514,18 +25639,40 @@ function build_init_function(node, status) {
|
|
|
24514
25639
|
}
|
|
24515
25640
|
if (field.type.is_array && field.type.length && (field.type.length.start ?? -1) >= 0) {
|
|
24516
25641
|
const elem_is_fat_string = field.type.name === "string";
|
|
24517
|
-
const
|
|
25642
|
+
const elem_struct = elem_is_fat_string ? void 0 : status.structs.find((s) => s.name === field.type.name && !s.is_simple_type && !s.is_class && !s.is_generic);
|
|
25643
|
+
const element_size = elem_is_fat_string ? 16 : elem_struct ? get_struct_size(field.type.name, status) : aarch64_size(field.type.name);
|
|
24518
25644
|
const length = parseInt(field.type.length.value || "0");
|
|
24519
|
-
|
|
24520
|
-
|
|
24521
|
-
|
|
24522
|
-
|
|
24523
|
-
|
|
24524
|
-
|
|
24525
|
-
|
|
24526
|
-
|
|
25645
|
+
const detach_fields = elem_struct && owns_strings_uniformly(elem_struct) ? layout_fields(elem_struct, status).filter((f) => !f.type.is_ref && !f.type.is_view && !f.type.is_array && f.type.name === "string") : [];
|
|
25646
|
+
const elem_field_offsets = detach_fields.map((f) => get_field_offset_of_fields(layout_fields(elem_struct, status), f.name, status));
|
|
25647
|
+
const copy_elements = () => {
|
|
25648
|
+
for (let e = 0; e < length; e++) {
|
|
25649
|
+
const byte_offset = e * element_size;
|
|
25650
|
+
if (elem_is_fat_string) {
|
|
25651
|
+
emit_asm(status, `ldp x9, x10, [${src_reg}, #${byte_offset}]\n`);
|
|
25652
|
+
emit_asm(status, `stp x9, x10, [x19, #${offset + byte_offset}]\n`);
|
|
25653
|
+
} else if (elem_struct) {
|
|
25654
|
+
const words = Math.ceil(element_size / 8);
|
|
25655
|
+
for (let w = 0; w < words; w++) {
|
|
25656
|
+
emit_asm(status, `ldr x9, [${src_reg}, #${byte_offset + w * 8}]\n`);
|
|
25657
|
+
emit_asm(status, `str x9, [x19, #${offset + byte_offset + w * 8}]\n`);
|
|
25658
|
+
}
|
|
25659
|
+
for (const sfo of elem_field_offsets) {
|
|
25660
|
+
const slot_off = offset + byte_offset + sfo;
|
|
25661
|
+
emit_asm(status, `ldr x0, [x19, #${slot_off}]\n`);
|
|
25662
|
+
const skip = `.arr_elem_strdup_skip_${status.label_counter = (status.label_counter ?? 0) + 1}`;
|
|
25663
|
+
emit_asm(status, `cbz x0, ${skip}\n`);
|
|
25664
|
+
emit_strdup(status);
|
|
25665
|
+
emit_asm(status, `str x0, [x19, #${slot_off}]\n`);
|
|
25666
|
+
emit_asm(status, `${skip}:\n`);
|
|
25667
|
+
}
|
|
25668
|
+
} else {
|
|
25669
|
+
emit_asm(status, load_element(src_reg, byte_offset, element_size));
|
|
25670
|
+
emit_asm(status, store_element("x19", offset + byte_offset, element_size));
|
|
25671
|
+
}
|
|
24527
25672
|
}
|
|
24528
|
-
}
|
|
25673
|
+
};
|
|
25674
|
+
if (detach_fields.length > 0) with_live_arg_regs_spilled(status, param_regs.slice(slot - 1), copy_elements);
|
|
25675
|
+
else copy_elements();
|
|
24529
25676
|
} else if (!field.type.is_ref && status.enums.find((e) => e.name === field.type.name && e.has_associated_data)) {
|
|
24530
25677
|
const enum_name = field.type.name;
|
|
24531
25678
|
const enum_size = get_enum_size(enum_name, status);
|
|
@@ -24580,7 +25727,13 @@ function build_init_function(node, status) {
|
|
|
24580
25727
|
}
|
|
24581
25728
|
emit_asm(status, `mov x1, #0\n`);
|
|
24582
25729
|
} else if (/^(\+|-)*\d+$/.test(val)) emit_asm(status, `ldr x1, =${val}\n`);
|
|
24583
|
-
else if (
|
|
25730
|
+
else if (/^(\+|-)*\d+\.\d+$/.test(val)) {
|
|
25731
|
+
const label = `_fdefault_${func_name}_${field.name}`;
|
|
25732
|
+
const data = `${label}: .double ${val}\n.p2align 3\n`;
|
|
25733
|
+
status.function_data = (status.function_data ?? "") + data;
|
|
25734
|
+
emit_asm(status, `adr x1, ${label}\n`);
|
|
25735
|
+
emit_asm(status, `ldr x1, [x1]\n`);
|
|
25736
|
+
} else if (val.startsWith("\"")) {
|
|
24584
25737
|
const label = `_str_${func_name}_${field.name}`;
|
|
24585
25738
|
status.strings.set(label, val);
|
|
24586
25739
|
emit_asm(status, `adr x1, ${label}\n`);
|
|
@@ -24678,6 +25831,10 @@ function build_init_function(node, status) {
|
|
|
24678
25831
|
emit_asm(status, `ldr x19, [sp], #16\n`);
|
|
24679
25832
|
emit_asm(status, `ldp x29, x30, [sp], #16\n`);
|
|
24680
25833
|
emit_asm(status, `ret\n`);
|
|
25834
|
+
if (status.function_data) {
|
|
25835
|
+
emit_asm(status, status.function_data);
|
|
25836
|
+
status.function_data = void 0;
|
|
25837
|
+
}
|
|
24681
25838
|
status.stack_size = old_stack_size;
|
|
24682
25839
|
status.stack_offsets = old_stack_offsets;
|
|
24683
25840
|
}
|
|
@@ -24708,6 +25865,8 @@ function build_custom_init_function(node, func, status) {
|
|
|
24708
25865
|
status.register_allocations = void 0;
|
|
24709
25866
|
status.nir_site_allocs = void 0;
|
|
24710
25867
|
status.callee_saved_regs_used = void 0;
|
|
25868
|
+
const old_struct_param_slots = status.function_struct_param_slots;
|
|
25869
|
+
status.function_struct_param_slots = /* @__PURE__ */ new Set();
|
|
24711
25870
|
status.scoped_declarations = [];
|
|
24712
25871
|
status.heap_strings = /* @__PURE__ */ new Set();
|
|
24713
25872
|
status.stack_size = 0;
|
|
@@ -24826,6 +25985,9 @@ function build_custom_init_function(node, func, status) {
|
|
|
24826
25985
|
const size = aarch64_size(param.type.name);
|
|
24827
25986
|
const offset = allocate_stack_space(status, size, size);
|
|
24828
25987
|
status.stack_offsets.set(param.name, offset);
|
|
25988
|
+
if (!param.type.is_ref && !param.type.is_array && !param.is_variadic) {
|
|
25989
|
+
if (!!status.structs.find((s) => s.name === param.type.name && !s.is_simple_type) || !!status.traits.find((t) => t.name === param.type.name) || !!status.enums.find((e) => e.name === param.type.name && e.has_associated_data)) status.function_struct_param_slots.add(param.name);
|
|
25990
|
+
}
|
|
24829
25991
|
if (param_idx < 8) {
|
|
24830
25992
|
const reg = param_regs[param_idx];
|
|
24831
25993
|
if (size === 1) emit_asm(status, `strb ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
|
|
@@ -24886,7 +26048,13 @@ function build_custom_init_function(node, func, status) {
|
|
|
24886
26048
|
}
|
|
24887
26049
|
emit_asm(status, `mov x1, #0\n`);
|
|
24888
26050
|
} else if (/^(\+|-)*\d+$/.test(val)) emit_asm(status, `ldr x1, =${val}\n`);
|
|
24889
|
-
else if (
|
|
26051
|
+
else if (/^(\+|-)*\d+\.\d+$/.test(val)) {
|
|
26052
|
+
const label = `_fdefault_${func_name}_${field.name}`;
|
|
26053
|
+
const data = `${label}: .double ${val}\n.p2align 3\n`;
|
|
26054
|
+
status.function_data = (status.function_data ?? "") + data;
|
|
26055
|
+
emit_asm(status, `adr x1, ${label}\n`);
|
|
26056
|
+
emit_asm(status, `ldr x1, [x1]\n`);
|
|
26057
|
+
} else if (val.startsWith("\"")) {
|
|
24890
26058
|
const label = `_str_${func_name}_${field.name}`;
|
|
24891
26059
|
status.strings.set(label, val);
|
|
24892
26060
|
emit_asm(status, `adr x1, ${label}\n`);
|
|
@@ -24979,6 +26147,7 @@ function build_custom_init_function(node, func, status) {
|
|
|
24979
26147
|
status.init_assigned_fields = old_init_assigned_fields;
|
|
24980
26148
|
status.init_default_reclaimed_fields = old_init_default_reclaimed;
|
|
24981
26149
|
status.heap_string_fields = old_init_heap_string_fields;
|
|
26150
|
+
status.function_struct_param_slots = old_struct_param_slots;
|
|
24982
26151
|
}
|
|
24983
26152
|
const init_claims = status.callee_saved_regs_used;
|
|
24984
26153
|
const init_loop_regs = init_claims ? [...init_claims].sort() : [];
|
|
@@ -25004,6 +26173,10 @@ function build_custom_init_function(node, func, status) {
|
|
|
25004
26173
|
emit_asm(status, `ldr x19, [sp], #16\n`);
|
|
25005
26174
|
emit_asm(status, `ldp x29, x30, [sp], #16\n`);
|
|
25006
26175
|
emit_asm(status, `ret\n`);
|
|
26176
|
+
if (status.function_data) {
|
|
26177
|
+
emit_asm(status, status.function_data);
|
|
26178
|
+
status.function_data = void 0;
|
|
26179
|
+
}
|
|
25007
26180
|
status.scoped_declarations = old_scoped_declarations;
|
|
25008
26181
|
status.heap_strings = old_heap_strings;
|
|
25009
26182
|
status.trait_class_frames = old_trait_class_frames;
|
|
@@ -25036,6 +26209,8 @@ function build_struct_functions$1(node, status) {
|
|
|
25036
26209
|
}
|
|
25037
26210
|
const old_scoped_declarations = status.scoped_declarations;
|
|
25038
26211
|
const old_heap_strings = status.heap_strings;
|
|
26212
|
+
const old_variable_types = status.variable_types;
|
|
26213
|
+
status.variable_types = /* @__PURE__ */ new Map();
|
|
25039
26214
|
const old_trait_class_frames = status.trait_class_frames;
|
|
25040
26215
|
status.trait_class_frames = void 0;
|
|
25041
26216
|
const old_moved = status.moved;
|
|
@@ -25122,7 +26297,7 @@ function build_struct_functions$1(node, status) {
|
|
|
25122
26297
|
status.function_view_params = /* @__PURE__ */ new Set();
|
|
25123
26298
|
status.function_param_types = /* @__PURE__ */ new Map();
|
|
25124
26299
|
status.struct_return_buffer = void 0;
|
|
25125
|
-
const return_struct = !func.return_type?.is_array && (!!status.structs.find((s) => s.name === func.return_type?.name && !s.is_simple_type && !s.is_class) || get_enum_sret_size(func.return_type?.name, status) !== void 0);
|
|
26300
|
+
const return_struct = !func.return_type?.is_array && !func.return_type?.is_pointer && (!!status.structs.find((s) => s.name === func.return_type?.name && !s.is_simple_type && !s.is_class) || get_enum_sret_size(func.return_type?.name, status) !== void 0);
|
|
25126
26301
|
const nullable_scalar_ret = !!func.return_type && is_nullable_scalar_type(func.return_type, status) && !func.return_type.is_ref;
|
|
25127
26302
|
const uses_sret = return_struct || nullable_scalar_ret;
|
|
25128
26303
|
let return_buffer_stack_offset;
|
|
@@ -25454,6 +26629,7 @@ function build_struct_functions$1(node, status) {
|
|
|
25454
26629
|
emit_asm(status, `ret\n`);
|
|
25455
26630
|
status.scoped_declarations = old_scoped_declarations;
|
|
25456
26631
|
status.heap_strings = old_heap_strings;
|
|
26632
|
+
status.variable_types = old_variable_types;
|
|
25457
26633
|
status.trait_class_frames = old_trait_class_frames;
|
|
25458
26634
|
status.moved = old_moved;
|
|
25459
26635
|
status.mv_owns_flag = old_mv_owns_flag;
|
|
@@ -25505,6 +26681,8 @@ function build_trait_functions(node, status) {
|
|
|
25505
26681
|
status.emitted_trait_funcs.add(trait_func_label);
|
|
25506
26682
|
const old_scoped_declarations = status.scoped_declarations;
|
|
25507
26683
|
const old_heap_strings = status.heap_strings;
|
|
26684
|
+
const old_variable_types = status.variable_types;
|
|
26685
|
+
status.variable_types = /* @__PURE__ */ new Map();
|
|
25508
26686
|
const old_heap_string_arrays = status.heap_string_arrays;
|
|
25509
26687
|
status.heap_string_arrays = void 0;
|
|
25510
26688
|
const old_heap_class_arrays = status.heap_class_arrays;
|
|
@@ -25694,6 +26872,7 @@ function build_trait_functions(node, status) {
|
|
|
25694
26872
|
emit_asm(status, `ret\n`);
|
|
25695
26873
|
status.scoped_declarations = old_scoped_declarations;
|
|
25696
26874
|
status.heap_strings = old_heap_strings;
|
|
26875
|
+
status.variable_types = old_variable_types;
|
|
25697
26876
|
status.heap_string_arrays = old_heap_string_arrays;
|
|
25698
26877
|
status.heap_class_arrays = old_heap_class_arrays;
|
|
25699
26878
|
status.heap_array_vars = old_heap_array_vars;
|
|
@@ -27616,6 +28795,23 @@ function scaled_load_instr(name, size) {
|
|
|
27616
28795
|
}
|
|
27617
28796
|
//#endregion
|
|
27618
28797
|
//#region ../src/build_aarch64/naked_inline.ts
|
|
28798
|
+
/**
|
|
28799
|
+
* Storage width of a built-in scalar type name: 1/2/4 for the sub-word ints,
|
|
28800
|
+
* bool, and char; 8 for everything else (words, floats-as-bits, references).
|
|
28801
|
+
* `null` for anything that is not a built-in scalar (structs, enums, strings,
|
|
28802
|
+
* views) — callers keep their existing behavior for those.
|
|
28803
|
+
*/
|
|
28804
|
+
function scalar_byte_width(type_name) {
|
|
28805
|
+
const info = get_built_in_type(type_name);
|
|
28806
|
+
if (!info || info.bytes === void 0) return 8;
|
|
28807
|
+
if (info.bytes === 1 || info.bytes === 2 || info.bytes === 4) return info.bytes;
|
|
28808
|
+
return 8;
|
|
28809
|
+
}
|
|
28810
|
+
/** The W-register alias of an X register ("x19" → "w19") — sub-word loads
|
|
28811
|
+
* zero-extend into the full register, so the caller sees a clean scalar. */
|
|
28812
|
+
function w_alias(reg) {
|
|
28813
|
+
return reg.startsWith("x") ? `w${reg.slice(1)}` : reg;
|
|
28814
|
+
}
|
|
27619
28815
|
const NAKED_MAX_STATEMENTS = 4;
|
|
27620
28816
|
const NAKED_SCRATCH = ["x9", "x10"];
|
|
27621
28817
|
/** Byte size of a pointer element — must be a power of two ≤ 8 to be naked. */
|
|
@@ -27759,7 +28955,11 @@ function emit_naked_expr(node, dst, ctx) {
|
|
|
27759
28955
|
if (next === null) return false;
|
|
27760
28956
|
struct_name = next;
|
|
27761
28957
|
}
|
|
27762
|
-
|
|
28958
|
+
const width = scalar_byte_width(struct_name);
|
|
28959
|
+
if (width === 1) ctx.push(`ldrb ${w_alias(dst)}, [x19, #${offset}]`);
|
|
28960
|
+
else if (width === 2) ctx.push(`ldrh ${w_alias(dst)}, [x19, #${offset}]`);
|
|
28961
|
+
else if (width === 4) ctx.push(`ldr ${w_alias(dst)}, [x19, #${offset}]`);
|
|
28962
|
+
else ctx.push(`ldr ${dst}, [x19, #${offset}]`);
|
|
27763
28963
|
return true;
|
|
27764
28964
|
}
|
|
27765
28965
|
if (nt === "index") {
|
|
@@ -29256,10 +30456,10 @@ function find_var_size$2(name, status) {
|
|
|
29256
30456
|
*/
|
|
29257
30457
|
function current_return_is_string(status) {
|
|
29258
30458
|
const rt = status.function_return_type;
|
|
29259
|
-
if (rt) return rt.name === "string" && !rt.is_view;
|
|
30459
|
+
if (rt) return rt.name === "string" && !rt.is_view && !rt.is_pointer;
|
|
29260
30460
|
if (status.current_struct && status.current_function_name) {
|
|
29261
30461
|
const frt = status.current_struct.functions.find((f) => f.name === status.current_function_name)?.return_type;
|
|
29262
|
-
return !!frt && frt.name === "string" && !frt.is_view;
|
|
30462
|
+
return !!frt && frt.name === "string" && !frt.is_view && !frt.is_pointer;
|
|
29263
30463
|
}
|
|
29264
30464
|
return false;
|
|
29265
30465
|
}
|
|
@@ -29329,6 +30529,7 @@ function build_return_node$1(node, status, nir_value) {
|
|
|
29329
30529
|
continue;
|
|
29330
30530
|
}
|
|
29331
30531
|
if (is_field_struct_borrow(decl)) continue;
|
|
30532
|
+
if (is_pure_borrow_element_load(decl, status)) continue;
|
|
29332
30533
|
emit_destroy_for_decl(status, decl.name, decl.type.name, void 0, decl.type.type_args, decl.type.is_nullable);
|
|
29333
30534
|
}
|
|
29334
30535
|
emit_heap_slots_cleanup_for_return(status);
|
|
@@ -29349,6 +30550,7 @@ function build_return_node$1(node, status, nir_value) {
|
|
|
29349
30550
|
continue;
|
|
29350
30551
|
}
|
|
29351
30552
|
if (is_field_struct_borrow(decl)) continue;
|
|
30553
|
+
if (is_pure_borrow_element_load(decl, status)) continue;
|
|
29352
30554
|
emit_destroy_for_decl(status, decl.name, decl.type.name, void 0, decl.type.type_args, decl.type.is_nullable);
|
|
29353
30555
|
}
|
|
29354
30556
|
emit_heap_slots_cleanup_for_return(status);
|
|
@@ -29456,6 +30658,7 @@ function build_return_node$1(node, status, nir_value) {
|
|
|
29456
30658
|
continue;
|
|
29457
30659
|
}
|
|
29458
30660
|
if (is_field_struct_borrow(decl)) continue;
|
|
30661
|
+
if (is_pure_borrow_element_load(decl, status)) continue;
|
|
29459
30662
|
emit_destroy_for_decl(status, decl.name, decl.type.name, void 0, decl.type.type_args, decl.type.is_nullable);
|
|
29460
30663
|
}
|
|
29461
30664
|
emit_heap_slots_cleanup_for_return(status);
|
|
@@ -32290,6 +33493,7 @@ function build_cast_node$1(node, status) {
|
|
|
32290
33493
|
const is_from_float = is_float_type$1(from);
|
|
32291
33494
|
const is_to_float = is_float_type$1(to);
|
|
32292
33495
|
if (is_from_float && !is_to_float) {
|
|
33496
|
+
emit_asm(status, `fmov d0, x0\n`);
|
|
32293
33497
|
emit_asm(status, `fcvtzs x0, d0\n`);
|
|
32294
33498
|
emit_asm(status, `fmov d0, x0\n`);
|
|
32295
33499
|
return;
|
|
@@ -32800,7 +34004,8 @@ const OWNING_BUFFER_METHODS = /* @__PURE__ */ new Set([
|
|
|
32800
34004
|
"modify",
|
|
32801
34005
|
"shift",
|
|
32802
34006
|
"destroy",
|
|
32803
|
-
"#destroy"
|
|
34007
|
+
"#destroy",
|
|
34008
|
+
"alloc"
|
|
32804
34009
|
]);
|
|
32805
34010
|
/**
|
|
32806
34011
|
* Emit the transfer half of mv store_T/replace_T: the slot bytes were
|
|
@@ -32829,18 +34034,16 @@ function emit_transfer_owning_fields(elem, src, status, prefix = "") {
|
|
|
32829
34034
|
}
|
|
32830
34035
|
}
|
|
32831
34036
|
/**
|
|
32832
|
-
* Emit the deep-copy half of mv load_T: for each field
|
|
32833
|
-
*
|
|
32834
|
-
*
|
|
32835
|
-
*
|
|
32836
|
-
*
|
|
32837
|
-
*
|
|
32838
|
-
*
|
|
32839
|
-
*
|
|
32840
|
-
*
|
|
32841
|
-
*
|
|
32842
|
-
* without a copyable shape (class fields, nested mv user structs without
|
|
32843
|
-
* a synthesized copy) stay shallow — recorded in FOLLOWUP.md.
|
|
34037
|
+
* Emit the deep-copy half of mv load_T: for each field that
|
|
34038
|
+
* `field_load_deep_copy` accepts (a `List<T>`/`Buffer<T>`/`Map`/`Set` field
|
|
34039
|
+
* whose `<Mono>_copy` deep-copies the slots or member buffers, a nested mv
|
|
34040
|
+
* user struct whose auto-derived `copy` is deep, or a `Box<T>` wrapper that
|
|
34041
|
+
* is cloned), overwrite the shallow-copied bytes with an owned copy; enum-
|
|
34042
|
+
* with-data fields whose payloads are all deep-copyable ride `<E>_copy`.
|
|
34043
|
+
* A struct local's CONTAINER fields are reclaimed unconditionally at scope
|
|
34044
|
+
* exit, so a shallow return would double-free the slot's slab. Fields
|
|
34045
|
+
* without a copyable shape (class fields, nested mv structs that need
|
|
34046
|
+
* destroy but have no `copy`) stay shallow — recorded in FOLLOWUP.md.
|
|
32844
34047
|
*/
|
|
32845
34048
|
function emit_load_deep_copy_fields(elem, dst, status) {
|
|
32846
34049
|
for (const field of elem.fields) {
|
|
@@ -32854,7 +34057,7 @@ function emit_load_deep_copy_fields(elem, dst, status) {
|
|
|
32854
34057
|
continue;
|
|
32855
34058
|
}
|
|
32856
34059
|
const field_struct = resolve_struct_type(field.type, status);
|
|
32857
|
-
if (field_struct &&
|
|
34060
|
+
if (field_struct && field_load_deep_copy(field_struct, status)) {
|
|
32858
34061
|
status.code += `${dst}.${field.name} = ${field_struct.name}_copy(&${dst}.${field.name});\n`;
|
|
32859
34062
|
continue;
|
|
32860
34063
|
}
|
|
@@ -32884,7 +34087,7 @@ function emit_load_owned_copies_destroy(status, elem, var_expr) {
|
|
|
32884
34087
|
continue;
|
|
32885
34088
|
}
|
|
32886
34089
|
const field_struct = resolve_struct_type(field.type, status);
|
|
32887
|
-
if (field_struct &&
|
|
34090
|
+
if (field_struct && field_load_deep_copy(field_struct, status)) {
|
|
32888
34091
|
status.code += `${field_struct.name}_destroy(&${var_expr}.${field.name});\n`;
|
|
32889
34092
|
continue;
|
|
32890
34093
|
}
|
|
@@ -32982,6 +34185,17 @@ function emit_owning_buffer_enum_body(func_name, elem, status) {
|
|
|
32982
34185
|
}
|
|
32983
34186
|
return true;
|
|
32984
34187
|
}
|
|
34188
|
+
if (func_name === "alloc") {
|
|
34189
|
+
status.code += `if (self->data) {\n`;
|
|
34190
|
+
status.code += `${E}* _old_slots = ${slots};\n`;
|
|
34191
|
+
status.code += `for (int _i = 0; _i < self->cap; _i++) { ${E}_free_payloads(&_old_slots[_i]); }\n`;
|
|
34192
|
+
status.code += `free(_old_slots);\n`;
|
|
34193
|
+
status.code += `}\n`;
|
|
34194
|
+
status.code += `self->cap = size;\n`;
|
|
34195
|
+
status.code += `self->data = (unsigned long long)calloc(size, sizeof(${E}));\n`;
|
|
34196
|
+
status.code += `return self->cap;\n`;
|
|
34197
|
+
return true;
|
|
34198
|
+
}
|
|
32985
34199
|
if (func_name === "destroy") {
|
|
32986
34200
|
status.code += `if (self->data) {\n`;
|
|
32987
34201
|
status.code += `${E}* _slots = ${slots};\n`;
|
|
@@ -33032,6 +34246,17 @@ function emit_owning_buffer_string_body(func_name, status) {
|
|
|
33032
34246
|
status.code += `}\n`;
|
|
33033
34247
|
return true;
|
|
33034
34248
|
}
|
|
34249
|
+
if (func_name === "alloc") {
|
|
34250
|
+
status.code += `if (self->data) {\n`;
|
|
34251
|
+
status.code += `nomen_string* _old_slots = (nomen_string*)self->data;\n`;
|
|
34252
|
+
status.code += `for (int _i = 0; _i < self->cap; _i++) { free(_old_slots[_i].ptr); }\n`;
|
|
34253
|
+
status.code += `free(_old_slots);\n`;
|
|
34254
|
+
status.code += `}\n`;
|
|
34255
|
+
status.code += `self->cap = size;\n`;
|
|
34256
|
+
status.code += `self->data = (unsigned long long)calloc(size, sizeof(nomen_string));\n`;
|
|
34257
|
+
status.code += `return self->cap;\n`;
|
|
34258
|
+
return true;
|
|
34259
|
+
}
|
|
33035
34260
|
if (func_name === "destroy") {
|
|
33036
34261
|
status.code += `if (self->data) {\n`;
|
|
33037
34262
|
status.code += `nomen_string* _slots = (nomen_string*)self->data;\n`;
|
|
@@ -33060,6 +34285,7 @@ function emit_owning_buffer_body(func_name, elem, status) {
|
|
|
33060
34285
|
if (func_name === "store") {
|
|
33061
34286
|
status.code += `${Tptr}_slots = ${Tcast}(unsigned long long)self->data;\n`;
|
|
33062
34287
|
status.code += `struct ${elem.name} _old = _slots[i];\n`;
|
|
34288
|
+
emit_reclaim_displaced_owned(elem, "_slots[i]", "val", status);
|
|
33063
34289
|
status.code += `_slots[i] = (*val);\n`;
|
|
33064
34290
|
emit_deep_copy_fields(elem, "_slots[i]", "val", status, "_old");
|
|
33065
34291
|
emit_transfer_owning_fields(elem, "val", status);
|
|
@@ -33072,6 +34298,17 @@ function emit_owning_buffer_body(func_name, elem, status) {
|
|
|
33072
34298
|
status.code += `return _loaded;\n`;
|
|
33073
34299
|
return true;
|
|
33074
34300
|
}
|
|
34301
|
+
if (func_name === "alloc") {
|
|
34302
|
+
status.code += `if (self->data) {\n`;
|
|
34303
|
+
status.code += `${Tptr}_old_slots = ${Tcast}(unsigned long long)self->data;\n`;
|
|
34304
|
+
status.code += `for (int _i = 0; _i < self->cap; _i++) { if (_old_slots[_i]._vt) { ${elem.name}_destroy(&_old_slots[_i]); } }\n`;
|
|
34305
|
+
status.code += `free(_old_slots);\n`;
|
|
34306
|
+
status.code += `}\n`;
|
|
34307
|
+
status.code += `self->cap = size;\n`;
|
|
34308
|
+
status.code += `self->data = (unsigned long long)calloc(size, sizeof(struct ${elem.name}));\n`;
|
|
34309
|
+
status.code += `return self->cap;\n`;
|
|
34310
|
+
return true;
|
|
34311
|
+
}
|
|
33075
34312
|
if (func_name === "modify") {
|
|
33076
34313
|
status.code += `${Tptr}_slots = ${Tcast}(unsigned long long)self->data;\n`;
|
|
33077
34314
|
status.code += `struct ${elem.name} _old = _slots[i];\n`;
|
|
@@ -33114,6 +34351,130 @@ function emit_owning_buffer_body(func_name, elem, status) {
|
|
|
33114
34351
|
return false;
|
|
33115
34352
|
}
|
|
33116
34353
|
/**
|
|
34354
|
+
* The container monos whose `<T>_destroy` reclaims the whole container
|
|
34355
|
+
* (slab + per-element owned state): the core value-slot containers. A user
|
|
34356
|
+
* mv struct must NOT take this path (its destroy would free its string
|
|
34357
|
+
* fields too — the string machinery owns those) — such fields recurse.
|
|
34358
|
+
*/
|
|
34359
|
+
const CONTAINER_MONO_PREFIXES = [
|
|
34360
|
+
"Buffer_",
|
|
34361
|
+
"List_",
|
|
34362
|
+
"Map_",
|
|
34363
|
+
"Set_",
|
|
34364
|
+
"Array_"
|
|
34365
|
+
];
|
|
34366
|
+
let c_disp_closure_counter = 0;
|
|
34367
|
+
function is_container_mono_name(name) {
|
|
34368
|
+
return CONTAINER_MONO_PREFIXES.some((p) => name.startsWith(p));
|
|
34369
|
+
}
|
|
34370
|
+
/**
|
|
34371
|
+
* Member paths (relative to the container struct start) of every owned slab
|
|
34372
|
+
* pointer inside a container mono: the `data` words of the Buffer-shaped
|
|
34373
|
+
* structs reachable through nested struct fields (`Buffer` → `.data`,
|
|
34374
|
+
* `List` → `.items.data`, `Map` → `.keys.data` / `.values.data` / …). The
|
|
34375
|
+
* store_T displaced-reclaim guard compares ALL of them: the incoming value
|
|
34376
|
+
* aliases the displaced container only when every slab pointer is shared
|
|
34377
|
+
* (a byte-copy round-trip); any differing pointer means the displaced
|
|
34378
|
+
* container's slabs are exclusively owned and can be reclaimed.
|
|
34379
|
+
*/
|
|
34380
|
+
function container_data_paths(struct, status, prefix = "") {
|
|
34381
|
+
const paths = [];
|
|
34382
|
+
for (const field of struct.fields) {
|
|
34383
|
+
if (field.type.is_ref || field.type.is_view || field.type.is_array) continue;
|
|
34384
|
+
if (field.type.name === "string" && !field.type.is_array) continue;
|
|
34385
|
+
if (field.name === "data") {
|
|
34386
|
+
paths.push(`${prefix}.${field.name}`);
|
|
34387
|
+
continue;
|
|
34388
|
+
}
|
|
34389
|
+
const fs = resolve_struct_type(field.type, status);
|
|
34390
|
+
if (fs && !fs.is_class && is_container_mono_name(fs.name)) paths.push(...container_data_paths(fs, status, `${prefix}.${field.name}`));
|
|
34391
|
+
}
|
|
34392
|
+
return paths;
|
|
34393
|
+
}
|
|
34394
|
+
/**
|
|
34395
|
+
* Reclaim the DISPLACED slot value's owned pointer state on a store onto an
|
|
34396
|
+
* occupied slot — the residual Buffer hole (FOLLOWUP.md): store_T's
|
|
34397
|
+
* fresh-slot contract leaked every owned field of the previous occupant
|
|
34398
|
+
* except the plain string fields the B1 displaced-old reclaim already
|
|
34399
|
+
* handles. Runs BEFORE the slot overwrite (the guard reads both the slot's
|
|
34400
|
+
* and the incoming value's identity pointers live), per owned leaf:
|
|
34401
|
+
*
|
|
34402
|
+
* - container fields: `<T>_destroy` in place when NO slab pointer aliases
|
|
34403
|
+
* the incoming value's (a byte-copy round-trip keeps the slot's own);
|
|
34404
|
+
* - Box / class fields: destroy + free the displaced wrapper when the
|
|
34405
|
+
* incoming pointer differs;
|
|
34406
|
+
* - func fields: the free-if-owned arm when the descriptor differs;
|
|
34407
|
+
* - enum-with-data fields: `<E>_free_payloads` when the tag differs, or
|
|
34408
|
+
* the same case's payload pointers all differ;
|
|
34409
|
+
* - nested mv struct fields: recurse (their strings stay out — the string
|
|
34410
|
+
* machinery owns them).
|
|
34411
|
+
*/
|
|
34412
|
+
function emit_reclaim_displaced_owned(elem, slot_path, val_path, status) {
|
|
34413
|
+
for (const field of elem.fields) {
|
|
34414
|
+
if (field.type.is_ref || field.type.is_view || field.type.is_array) continue;
|
|
34415
|
+
if (field.type.name === "string" && !field.type.is_array) continue;
|
|
34416
|
+
const slot_f = `${slot_path}.${field.name}`;
|
|
34417
|
+
const val_f = `${val_path}${arrow(val_path)}${field.name}`;
|
|
34418
|
+
const box_mono = box_field_mono(field.type, status);
|
|
34419
|
+
if (box_mono) {
|
|
34420
|
+
status.code += `if (${slot_f} && ${slot_f} != ${val_f}) { ${box_mono.name}_destroy(${slot_f}); free(${slot_f}); }\n`;
|
|
34421
|
+
continue;
|
|
34422
|
+
}
|
|
34423
|
+
const fs = resolve_struct_type(field.type, status);
|
|
34424
|
+
if (fs?.is_class) {
|
|
34425
|
+
status.code += `if (${slot_f} && ${slot_f} != ${val_f}) { ${fs.name}_destroy(${slot_f}); free(${slot_f}); }\n`;
|
|
34426
|
+
continue;
|
|
34427
|
+
}
|
|
34428
|
+
if (field.type.name === "func") {
|
|
34429
|
+
const c = `_disp_closure_${c_disp_closure_counter++}`;
|
|
34430
|
+
status.code += `if (${slot_f} && ${slot_f} != ${val_f}) {\n`;
|
|
34431
|
+
status.code += `struct nomen_closure *${c} = (struct nomen_closure *)${slot_f};\n`;
|
|
34432
|
+
status.code += `if (${c}->owned) { if (${c}->destroy_env) ${c}->destroy_env(${c}->env); free(${c}->env); free(${c}); }\n`;
|
|
34433
|
+
status.code += `}\n`;
|
|
34434
|
+
continue;
|
|
34435
|
+
}
|
|
34436
|
+
const field_enum = status.enums.find((e) => e.name === field.type.name && e.has_associated_data);
|
|
34437
|
+
if (field_enum) {
|
|
34438
|
+
emit_displaced_enum_reclaim(field_enum, slot_f, val_f, status);
|
|
34439
|
+
continue;
|
|
34440
|
+
}
|
|
34441
|
+
if (fs && !fs.is_class) {
|
|
34442
|
+
if (is_container_mono_name(fs.name)) {
|
|
34443
|
+
const data_paths = container_data_paths(fs, status);
|
|
34444
|
+
if (!data_paths.length) continue;
|
|
34445
|
+
const guard = [`${slot_f}${data_paths[0]}`, ...data_paths.map((p) => `${slot_f}${p} != ${val_f}${p}`)].join(" && ");
|
|
34446
|
+
status.code += `if (${guard}) { ${fs.name}_destroy(&${slot_f}); }\n`;
|
|
34447
|
+
} else if (struct_needs_destroy(fs, status) || has_string_fields(fs, status)) emit_reclaim_displaced_owned(fs, slot_f, val_f, status);
|
|
34448
|
+
}
|
|
34449
|
+
}
|
|
34450
|
+
}
|
|
34451
|
+
/**
|
|
34452
|
+
* The enum-with-data arm of the displaced reclaim: the payload compare must
|
|
34453
|
+
* be tag-scoped (payloads of different cases never alias — distinct
|
|
34454
|
+
* allocations), so per owning case: a differing tag reclaims outright; the
|
|
34455
|
+
* same tag reclaims only when every payload pointer differs (the round-trip
|
|
34456
|
+
* identity keeps the slot's payloads).
|
|
34457
|
+
*/
|
|
34458
|
+
function emit_displaced_enum_reclaim(elem, slot_f, val_f, status) {
|
|
34459
|
+
const owning_cases = elem.cases.filter((c) => c.params.some((p) => p.type.name === "string" || payload_is_reference$3(p.type, status)));
|
|
34460
|
+
if (!owning_cases.length) return;
|
|
34461
|
+
const reclaim = `${elem.name}_free_payloads(&${slot_f});`;
|
|
34462
|
+
const guards = owning_cases.map((c) => {
|
|
34463
|
+
const compares = c.params.filter((p) => p.type.name === "string" || payload_is_reference$3(p.type, status)).map((p) => {
|
|
34464
|
+
const slot_p = `${slot_f}._data._${c.name}.${p.name}`;
|
|
34465
|
+
const val_p = `${val_f}._data._${c.name}.${p.name}`;
|
|
34466
|
+
return p.type.name === "string" ? `${slot_p}.ptr != ${val_p}.ptr` : `${slot_p} != ${val_p}`;
|
|
34467
|
+
});
|
|
34468
|
+
return `else if (${slot_f}.tag == ${elem.name}_${c.name}) { if (${compares.join(" && ")}) { ${reclaim} } }`;
|
|
34469
|
+
});
|
|
34470
|
+
status.code += `if (${slot_f}.tag != ${val_f}.tag) { ${reclaim} }\n`;
|
|
34471
|
+
status.code += `${guards.join(" ")}\n`;
|
|
34472
|
+
}
|
|
34473
|
+
/** The C-side payload reference check (mirrors the enum reclaim's shape). */
|
|
34474
|
+
function payload_is_reference$3(type, status) {
|
|
34475
|
+
return status.structs.some((s) => s.name === type.name && s.is_class) || status.traits.some((t) => t.name === type.name);
|
|
34476
|
+
}
|
|
34477
|
+
/**
|
|
33117
34478
|
* For each DIRECT string field on the element struct, overwrite the
|
|
33118
34479
|
* shallow-copied pointer in `dst` with a fresh strdup of `src`'s field. This
|
|
33119
34480
|
* breaks the pointer aliasing between the source and the slot so each can be
|
|
@@ -33434,7 +34795,7 @@ function free_scoped_declarations(status, decls, persist_string_field_records =
|
|
|
33434
34795
|
status.code += `(${cast}_get_trait_func((void *)&${cname}, ${traitIndex}, ${funcIndex}))(&${cname});\n`;
|
|
33435
34796
|
}
|
|
33436
34797
|
}
|
|
33437
|
-
const is_destructured_field_access = dec.value?.node_type === "access" && dec.value.access.node_type === "access_field" && !dec.value.is_moved && dec.force_owned_string !== true || is_borrow_accessor_carrier(dec);
|
|
34798
|
+
const is_destructured_field_access = dec.value?.node_type === "access" && dec.value.access.node_type === "access_field" && !dec.value.is_moved && dec.force_owned_string !== true || is_borrow_accessor_carrier(dec) || is_pure_borrow_element_load(dec, status);
|
|
33438
34799
|
const is_borrowed_string = is_string_borrow(dec.value) && !status.c_owned_borrow_inits?.has(dec) || !!status.string_borrow_vars?.has(dec.name);
|
|
33439
34800
|
const value_is_heap_string = dec.type.name === "string" && (dec.value?.node_type === "access" && dec.value.access.node_type === "access_func" || dec.value?.node_type === "func_call" || !!dec.value && !dec.type.is_view && is_view_value(dec.value, status));
|
|
33440
34801
|
const dec_value = dec.value;
|
|
@@ -33604,6 +34965,7 @@ function emit_struct_destroys(status, struct, var_expr) {
|
|
|
33604
34965
|
continue;
|
|
33605
34966
|
}
|
|
33606
34967
|
if ((struct.is_class === true || struct.is_owning === true) && field.type.name === "func" && !field.type.is_array) {
|
|
34968
|
+
if (status.func_field_borrow_decls?.delete(var_expr)) continue;
|
|
33607
34969
|
const f = `((struct nomen_closure *)${var_expr}.${field.name})`;
|
|
33608
34970
|
status.code += `if (${f} && ${f}->owned) { if (${f}->destroy_env) ${f}->destroy_env(${f}->env); free(${f}->env); free(${f}); }\n`;
|
|
33609
34971
|
continue;
|
|
@@ -33611,6 +34973,15 @@ function emit_struct_destroys(status, struct, var_expr) {
|
|
|
33611
34973
|
const field_struct = resolve_struct_type(field.type, status);
|
|
33612
34974
|
if (!field_struct) continue;
|
|
33613
34975
|
const field_expr = `${var_expr}.${field.name}`;
|
|
34976
|
+
if (field.type.is_array && !field_struct.is_class) {
|
|
34977
|
+
const arr_len = field.type.length ? parseInt(field.type.length.value || "0") : 0;
|
|
34978
|
+
if (arr_len > 0 && (has_destroy(field_struct) || struct_needs_destroy(field_struct, status) || struct_needs_auto_destroy(field_struct, status))) {
|
|
34979
|
+
status.code += `for (long _i = 0; _i < ${arr_len}; _i++) {\n`;
|
|
34980
|
+
emit_struct_destroys(status, field_struct, `${field_expr}[_i]`);
|
|
34981
|
+
status.code += `}\n`;
|
|
34982
|
+
}
|
|
34983
|
+
continue;
|
|
34984
|
+
}
|
|
33614
34985
|
if (field_struct.is_class) status.code += `if (${field_expr}) { ${field_struct.name}_destroy(${field_expr}); free(${field_expr}); }\n`;
|
|
33615
34986
|
else if (is_nullable_struct_type(field.type, status)) {
|
|
33616
34987
|
if (struct_needs_destroy(field_struct, status)) {
|
|
@@ -33658,6 +35029,15 @@ function capture_destroys(status, struct, var_expr, accessor) {
|
|
|
33658
35029
|
const field_struct = resolve_struct_type(field.type, status);
|
|
33659
35030
|
if (!field_struct) continue;
|
|
33660
35031
|
const field_expr = `${var_expr}${accessor}${field.name}`;
|
|
35032
|
+
if (field.type.is_array && !field_struct.is_class) {
|
|
35033
|
+
const arr_len = field.type.length ? parseInt(field.type.length.value || "0") : 0;
|
|
35034
|
+
if (arr_len > 0 && (has_destroy(field_struct) || struct_needs_destroy(field_struct, status) || struct_needs_auto_destroy(field_struct, status))) {
|
|
35035
|
+
status.code += `for (long _i = 0; _i < ${arr_len}; _i++) { `;
|
|
35036
|
+
emit_struct_destroys(status, field_struct, `${field_expr}[_i]`);
|
|
35037
|
+
status.code += `} `;
|
|
35038
|
+
}
|
|
35039
|
+
continue;
|
|
35040
|
+
}
|
|
33661
35041
|
if (field_struct.is_class) status.code += `if (${field_expr}) { ${field_struct.name}_destroy(${field_expr}); free(${field_expr}); } `;
|
|
33662
35042
|
else if (is_nullable_struct_type(field.type, status)) {
|
|
33663
35043
|
if (struct_needs_destroy(field_struct, status)) {
|
|
@@ -35027,7 +36407,11 @@ function build_assignment_node$1(node, status, nir_rhs, nir_swap) {
|
|
|
35027
36407
|
} else if (!node.swap && rhs.is_moved) {
|
|
35028
36408
|
const src_name = rhs.value;
|
|
35029
36409
|
if (string_var_owns_heap(status, src_name)) {
|
|
35030
|
-
|
|
36410
|
+
const src_flag = status.c_mv_owns_flags?.get(src_name);
|
|
36411
|
+
if (src_flag !== void 0) {
|
|
36412
|
+
if (!status.pending_mv_owns_sets) status.pending_mv_owns_sets = [];
|
|
36413
|
+
status.pending_mv_owns_sets.push(`${src_flag} = 1;`);
|
|
36414
|
+
} else splice_decl_from_c_scopes(status, src_name);
|
|
35031
36415
|
if (!status.heap_strings) status.heap_strings = /* @__PURE__ */ new Set();
|
|
35032
36416
|
status.heap_strings.add(lhs_name);
|
|
35033
36417
|
} else {
|
|
@@ -35893,11 +37277,13 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
|
|
|
35893
37277
|
status.code += `;\n`;
|
|
35894
37278
|
return;
|
|
35895
37279
|
}
|
|
35896
|
-
|
|
37280
|
+
const value_struct_ref_alias = node.is_ref_binding === true && !node.type.is_ref && !!status.structs.find((s) => s.name === (mono_name ?? node.type.name) && !s.is_class && !s.is_simple_type && !s.is_generic);
|
|
37281
|
+
if ((node.type.is_ref || value_struct_ref_alias) && !node.type.is_array) {
|
|
35897
37282
|
if (mono_struct) status.code += `struct ${mono_name} *${safe_name}`;
|
|
35898
37283
|
else status.code += `${c_type(node.type.name)} *${safe_name}`;
|
|
35899
37284
|
if (node.value) {
|
|
35900
|
-
status.code += ` = &`;
|
|
37285
|
+
if (!(node.value.node_type === "access" && node.value.access.is_element_slot_load === true)) status.code += ` = &`;
|
|
37286
|
+
else status.code += ` = `;
|
|
35901
37287
|
build_node$1(node.value, status);
|
|
35902
37288
|
}
|
|
35903
37289
|
status.code += `;\n`;
|
|
@@ -36215,7 +37601,7 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
|
|
|
36215
37601
|
const binding_struct = status.structs.find((s) => s.name === node.type?.name && !s.is_simple_type && !s.is_class);
|
|
36216
37602
|
const init_access = node.value?.node_type === "access" ? node.value.access : void 0;
|
|
36217
37603
|
const is_borrow_accessor_init = init_access?.node_type === "access_func" && is_container_borrow_accessor_name(init_access.name ?? "");
|
|
36218
|
-
if (binding_struct && owns_strings_uniformly(binding_struct) && is_borrow_accessor_init && !is_borrow_accessor_carrier(node)) {
|
|
37604
|
+
if (binding_struct && owns_strings_uniformly(binding_struct) && is_borrow_accessor_init && !is_borrow_accessor_carrier(node) && !is_pure_borrow_element_load(node, status)) {
|
|
36219
37605
|
if (!status.code.endsWith(";\n")) status.code += `;\n`;
|
|
36220
37606
|
const emit_strdups = (s, prefix, guard) => {
|
|
36221
37607
|
const lines = [];
|
|
@@ -36229,6 +37615,7 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
|
|
|
36229
37615
|
}
|
|
36230
37616
|
const field_struct = resolve_struct_type(field.type, status);
|
|
36231
37617
|
if (!field_struct || field_struct.is_class) continue;
|
|
37618
|
+
if (field_load_deep_copy(field_struct, status)) continue;
|
|
36232
37619
|
if (!owns_strings_uniformly(field_struct)) continue;
|
|
36233
37620
|
const nested_guard = is_nullable_struct_type(field.type, status) ? `${lhs}_has` : void 0;
|
|
36234
37621
|
lines.push(...emit_strdups(field_struct, path, nested_guard));
|
|
@@ -36242,6 +37629,10 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
|
|
|
36242
37629
|
};
|
|
36243
37630
|
for (const line of emit_strdups(binding_struct, "")) status.code += line;
|
|
36244
37631
|
}
|
|
37632
|
+
if (binding_struct && is_borrow_accessor_init && !is_borrow_accessor_carrier(node)) {
|
|
37633
|
+
if (!status.func_field_borrow_decls) status.func_field_borrow_decls = /* @__PURE__ */ new Set();
|
|
37634
|
+
status.func_field_borrow_decls.add(safe_name);
|
|
37635
|
+
}
|
|
36245
37636
|
}
|
|
36246
37637
|
const override_ctor_call = node.value?.node_type === "func_call" && node.value.field_overrides?.length && node.type?.name && node.value.name === node.type.name ? node.value : void 0;
|
|
36247
37638
|
if (override_ctor_call) {
|
|
@@ -36896,10 +38287,10 @@ function build_return_node(node, status, nir_value) {
|
|
|
36896
38287
|
const ret_type = status.function_return_type || node.type;
|
|
36897
38288
|
const mono_ret_name = mono_type_name(ret_type);
|
|
36898
38289
|
const return_struct = status.structs.find((s) => s.name === mono_ret_name && !s.is_simple_type);
|
|
36899
|
-
const is_struct = !!return_struct;
|
|
38290
|
+
const is_struct = !!return_struct && !ret_type.is_pointer;
|
|
36900
38291
|
const return_is_class = !!return_struct?.is_class;
|
|
36901
38292
|
const return_trait = status.traits.find((t) => t.name === ret_type.name);
|
|
36902
|
-
const type_prefix = ret_type.is_array ? `struct Array_${ret_type.name}* ` : ret_type.is_view ? "nomen_view " : is_struct ? return_is_class ? `struct ${mono_ret_name}* ` : `struct ${mono_ret_name} ` : return_trait ? `struct ${ret_type.name}* ` : c_type(ret_type.name || "int");
|
|
38293
|
+
const type_prefix = ret_type.is_array ? `struct Array_${ret_type.name}* ` : ret_type.is_pointer ? `${status.traits.find((t) => t.name === ret_type.name) ? `struct ${ret_type.name}` : pointer_element_c_type(ret_type, status)}* ` : ret_type.is_view ? "nomen_view " : is_struct ? return_is_class ? `struct ${mono_ret_name}* ` : `struct ${mono_ret_name} ` : return_trait ? `struct ${ret_type.name}* ` : c_type(ret_type.name || "int");
|
|
36903
38294
|
if (node.value.node_type === "match" || node.value.node_type === "switch" || node.value.node_type === "if") {
|
|
36904
38295
|
const join_branch_values = collect_expression_branch_values(node.value);
|
|
36905
38296
|
const first_branch_type = join_branch_values.length ? type_from_value_node$1(join_branch_values[0])?.name : void 0;
|
|
@@ -36937,10 +38328,10 @@ function build_return_node(node, status, nir_value) {
|
|
|
36937
38328
|
}
|
|
36938
38329
|
status.code += `${type_prefix} _return_val = `;
|
|
36939
38330
|
if (return_trait && !ret_type.is_view) status.code += `(struct ${ret_type.name} *)`;
|
|
36940
|
-
let returns_borrowed_string = ret_type.name === "string" && !ret_type.is_view && node.value.node_type === "access";
|
|
36941
|
-
const returns_string_literal = ret_type.name === "string" && !ret_type.is_view && node.value.node_type === "value" && node.value.value.length >= 2 && node.value.value.startsWith("\"") && node.value.value.endsWith("\"");
|
|
38331
|
+
let returns_borrowed_string = ret_type.name === "string" && !ret_type.is_view && !ret_type.is_pointer && node.value.node_type === "access";
|
|
38332
|
+
const returns_string_literal = ret_type.name === "string" && !ret_type.is_view && !ret_type.is_pointer && node.value.node_type === "value" && node.value.value.length >= 2 && node.value.value.startsWith("\"") && node.value.value.endsWith("\"");
|
|
36942
38333
|
let returns_borrow_var = false;
|
|
36943
|
-
if (ret_type.name === "string" && !ret_type.is_view && node.value.node_type === "value" && !returns_string_literal) {
|
|
38334
|
+
if (ret_type.name === "string" && !ret_type.is_view && !ret_type.is_pointer && node.value.node_type === "value" && !returns_string_literal) {
|
|
36944
38335
|
const var_name = node.value.value;
|
|
36945
38336
|
if (!returned_value_decl) returns_borrow_var = true;
|
|
36946
38337
|
else if (is_string_borrow(returned_value_decl.value) || !!status.string_borrow_vars?.has(var_name)) returns_borrow_var = true;
|
|
@@ -37618,6 +39009,8 @@ function build_struct_node(node, status) {
|
|
|
37618
39009
|
status.init_default_reclaimed_fields = /* @__PURE__ */ new Set();
|
|
37619
39010
|
const old_init_heap_string_fields = status.heap_string_fields;
|
|
37620
39011
|
status.heap_string_fields = void 0;
|
|
39012
|
+
const old_init_variable_types = status.variable_types;
|
|
39013
|
+
status.variable_types = /* @__PURE__ */ new Map();
|
|
37621
39014
|
for (const field of node.fields) if (field.value) {
|
|
37622
39015
|
status.init_assigned_fields?.add(`self.${field.name}`);
|
|
37623
39016
|
if (field.type.is_view && !field.type.is_array && !is_nullable_struct_type(field.type, status)) {
|
|
@@ -37707,6 +39100,7 @@ function build_struct_node(node, status) {
|
|
|
37707
39100
|
status.init_assigned_fields = old_init_assigned_fields;
|
|
37708
39101
|
status.init_default_reclaimed_fields = old_init_default_reclaimed;
|
|
37709
39102
|
status.heap_string_fields = old_init_heap_string_fields;
|
|
39103
|
+
status.variable_types = old_init_variable_types;
|
|
37710
39104
|
status.function_return_type = old_return_type;
|
|
37711
39105
|
}
|
|
37712
39106
|
build_struct_functions(node, status, true);
|
|
@@ -37724,8 +39118,21 @@ function build_struct_node(node, status) {
|
|
|
37724
39118
|
build_node$1(field.value, status);
|
|
37725
39119
|
status.code += `; ${object_name}${accessor}${field.name} = (nomen_view){ _p.ptr, _p.len }; }\n`;
|
|
37726
39120
|
} else status.code += `${object_name}${accessor}${field.name} = (nomen_view){0};\n`;
|
|
37727
|
-
} else if (field.type.storage_kind === "stack_array" && field.type.length)
|
|
37728
|
-
|
|
39121
|
+
} else if (field.type.storage_kind === "stack_array" && field.type.length) {
|
|
39122
|
+
status.code += `memcpy(${object_name}${accessor}${field.name}, ${field.name}, sizeof(${object_name}${accessor}${field.name}));\n`;
|
|
39123
|
+
const elem_struct = status.structs.find((s) => s.name === field.type.name && !s.is_simple_type && !s.is_class && !s.is_generic);
|
|
39124
|
+
if (elem_struct && owns_strings_uniformly(elem_struct)) {
|
|
39125
|
+
const arr_len = parseInt(field.type.length.value || "0");
|
|
39126
|
+
for (const sf of class_destroy_fields(elem_struct, status)) {
|
|
39127
|
+
if (sf.type.is_ref || sf.type.is_view || sf.type.is_array) continue;
|
|
39128
|
+
if (sf.type.name !== "string") continue;
|
|
39129
|
+
for (let e = 0; e < arr_len; e++) {
|
|
39130
|
+
const slot = `${object_name}${accessor}${field.name}[${e}].${sf.name}`;
|
|
39131
|
+
status.code += `${slot}.ptr = ${slot}.ptr ? nomen_str_dup(${slot}).ptr : ${slot}.ptr;\n`;
|
|
39132
|
+
}
|
|
39133
|
+
}
|
|
39134
|
+
}
|
|
39135
|
+
} else if ((is_nullable_struct_type(field.type, status) || is_nullable_scalar_type(field.type, status)) && field.value) {
|
|
37729
39136
|
if (field.value.node_type === "value" && field.value.value === "null") status.code += `${object_name}${accessor}${has_flag_name(field.name)} = 0;\n`;
|
|
37730
39137
|
else {
|
|
37731
39138
|
status.code += `${object_name}${accessor}${field.name} = `;
|
|
@@ -37869,6 +39276,8 @@ function build_struct_functions(node, status, skip_init = false) {
|
|
|
37869
39276
|
const old_ref_class_params = status.ref_class_params;
|
|
37870
39277
|
const old_ref_class_param_types = status.ref_class_param_types;
|
|
37871
39278
|
const old_scoped_declarations = status.scoped_declarations;
|
|
39279
|
+
const old_variable_types = status.variable_types;
|
|
39280
|
+
status.variable_types = /* @__PURE__ */ new Map();
|
|
37872
39281
|
const old_borrow_only = status.c_borrow_only_strings;
|
|
37873
39282
|
const old_force_heap = status.force_heap_strings;
|
|
37874
39283
|
const old_heap_array_vars = status.heap_array_vars;
|
|
@@ -37931,7 +39340,10 @@ function build_struct_functions(node, status, skip_init = false) {
|
|
|
37931
39340
|
const returns_array_data = (func.return_type.type_args?.length ?? 0) > 0 && return_type === "Array";
|
|
37932
39341
|
const emit_label = is_overloaded(node, func.name) ? mangled_label(func, node.name) : `${node.name}_${func.name.replace(/#/g, "")}`;
|
|
37933
39342
|
if (func.return_type.is_array) status.code += `void* ${emit_label}(`;
|
|
37934
|
-
else if (
|
|
39343
|
+
else if (func.return_type.is_pointer) {
|
|
39344
|
+
const elem_c = status.traits.find((t) => t.name === func.return_type.name) ? `struct ${func.return_type.name}` : pointer_element_c_type(func.return_type, status);
|
|
39345
|
+
status.code += `${elem_c}* ${emit_label}(`;
|
|
39346
|
+
} else if (returns_array_data) {
|
|
37935
39347
|
const elem_type = c_type(func.return_type.type_args[0].name);
|
|
37936
39348
|
status.code += `${elem_type}* ${emit_label}(`;
|
|
37937
39349
|
} else if (return_type.startsWith("Array_")) status.code += `void* ${emit_label}(`;
|
|
@@ -37996,6 +39408,7 @@ function build_struct_functions(node, status, skip_init = false) {
|
|
|
37996
39408
|
status.current_function_name = old_function_name;
|
|
37997
39409
|
status.current_function = old_current_function;
|
|
37998
39410
|
status.function_view_params = old_view_params;
|
|
39411
|
+
status.variable_types = old_variable_types;
|
|
37999
39412
|
}
|
|
38000
39413
|
status.current_struct = old_current_struct;
|
|
38001
39414
|
for (let traitName of node.traits) {
|
|
@@ -38066,6 +39479,11 @@ function build_auto_destroy(node, status) {
|
|
|
38066
39479
|
const field_struct = status.structs.find((s) => s.name === mono_name && !s.is_simple_type);
|
|
38067
39480
|
if (process.env.NOMEN_DEBUG_DESTROY) console.error("AUTODESTROY-LOOKUP:", node.name, field.name, mono_name, "found:", !!field_struct, "nstructs:", status.structs.length);
|
|
38068
39481
|
if (!field_struct) continue;
|
|
39482
|
+
if (field.type.is_array && !field_struct.is_class) {
|
|
39483
|
+
const arr_len = field.type.length ? parseInt(field.type.length.value || "0") : 0;
|
|
39484
|
+
if (arr_len > 0 && (field_struct.functions.find((f) => f.name === "#destroy") || struct_needs_auto_destroy(field_struct, status))) status.code += `for (long _i = 0; _i < ${arr_len}; _i++) { ${field_struct.name}_destroy(&self->${field.name}[_i]); }\n`;
|
|
39485
|
+
continue;
|
|
39486
|
+
}
|
|
38069
39487
|
if (field_struct.is_class) {
|
|
38070
39488
|
const field_has_destroy = !!field_struct.functions.find((f) => f.name === "#destroy");
|
|
38071
39489
|
const field_is_owned = field.declaration === "mv" || node.is_owning === true;
|
|
@@ -39013,7 +40431,7 @@ function emit_boxed_value_struct_c(status, arg, box_struct) {
|
|
|
39013
40431
|
}
|
|
39014
40432
|
const unrecorded = string_fields.filter((f) => !recorded.has(f.name));
|
|
39015
40433
|
if (arg.node_type === "func_call" && arg.name === box_struct.name) {
|
|
39016
|
-
status.code += `(void *)({ struct ${box_struct.name} *_box = (struct ${box_struct.name} *)
|
|
40434
|
+
status.code += `(void *)({ struct ${box_struct.name} *_box = (struct ${box_struct.name} *)malloc(sizeof(struct ${box_struct.name})); *_box = `;
|
|
39017
40435
|
build_node$1(arg, status);
|
|
39018
40436
|
status.code += `; `;
|
|
39019
40437
|
for (const f of unrecorded) status.code += `_box->${f.name} = nomen_str_dup(_box->${f.name}); `;
|
|
@@ -39028,7 +40446,7 @@ function emit_boxed_value_struct_c(status, arg, box_struct) {
|
|
|
39028
40446
|
const addr_text = end_code_scratch(status, saved_arg);
|
|
39029
40447
|
status.code += `(void *)({ struct ${box_struct.name} *_src = (struct ${box_struct.name} *)${addr_text}; `;
|
|
39030
40448
|
for (const f of unrecorded) status.code += `_src->${f.name} = nomen_str_dup(_src->${f.name}); `;
|
|
39031
|
-
status.code += `struct ${box_struct.name} *_box = (struct ${box_struct.name} *)
|
|
40449
|
+
status.code += `struct ${box_struct.name} *_box = (struct ${box_struct.name} *)malloc(sizeof(struct ${box_struct.name})); `;
|
|
39032
40450
|
status.code += `*_box = *_src; `;
|
|
39033
40451
|
status.code += `(void *)_box; })`;
|
|
39034
40452
|
}
|
|
@@ -39837,6 +41255,7 @@ function build_access_node$1(node, status) {
|
|
|
39837
41255
|
}
|
|
39838
41256
|
case "access_func": {
|
|
39839
41257
|
const access_func = node.access;
|
|
41258
|
+
if (access_func.is_handler_registration) return;
|
|
39840
41259
|
if (access_func.is_nursery_spawn) {
|
|
39841
41260
|
build_nursery_spawn$1(access_func, nursery_pointer_expr(node.target, status), status);
|
|
39842
41261
|
return;
|
|
@@ -40599,7 +42018,7 @@ void **_get_trait_func(void **obj, int trait_index, int func_index)
|
|
|
40599
42018
|
let range = Array.from({ length }, (_, i) => i);
|
|
40600
42019
|
let declaration = `nomen_string _string_interpolate_${length}(nomen_string pattern, ${range.map((n) => `nomen_string arg${n + 1}`).join(", ")})`;
|
|
40601
42020
|
status.headers += `${declaration};\n`;
|
|
40602
|
-
if (status.emit_mode !== "user") status.code += `${declaration}
|
|
42021
|
+
if (status.emit_mode !== "user" || length > 7) status.code += `${declaration}
|
|
40603
42022
|
{
|
|
40604
42023
|
int length = snprintf(NULL, 0, pattern.ptr, ${range.map((n) => `arg${n + 1}.ptr`).join(", ")});
|
|
40605
42024
|
char *str = malloc(length + 1);
|
|
@@ -42374,6 +43793,17 @@ function emit_struct_constructor_to_slot(fc, slot_addr, status) {
|
|
|
42374
43793
|
function build_constructor_params(fc, param_regs, status) {
|
|
42375
43794
|
mark_tuple_literal_move_owners(fc, status, (name) => mark_local_moved_flag(status, name));
|
|
42376
43795
|
const variadic_idx = fc.variadic_param_index;
|
|
43796
|
+
if (variadic_idx === void 0 && fc.params.length === 0) {
|
|
43797
|
+
const resolved = fc.resolved_function ?? status.structs.find((s) => s.name === fc.name)?.functions.find((f) => f.name === "#init");
|
|
43798
|
+
const variadic_param = resolved?.params?.find((p) => p.is_variadic);
|
|
43799
|
+
if (resolved && variadic_param && resolved.params.filter((p) => !p.is_self_param && !p.is_variadic).length === 0) {
|
|
43800
|
+
const elem_type_name = variadic_param.type.tuple_types?.length ? variadic_param.type.tuple_types[0].name : variadic_param.type.name || "int";
|
|
43801
|
+
const arr_offset = allocate_stack_space(status, 8 + (status.structs.find((s) => s.name === elem_type_name && !s.is_simple_type) ? get_struct_size(elem_type_name, status) : elem_type_name === "string" ? 16 : 8), 16);
|
|
43802
|
+
emit_asm(status, `str xzr, [x29, #${arr_offset}]\n`);
|
|
43803
|
+
emit_asm(status, `mov x1, #0\n`);
|
|
43804
|
+
emit_asm(status, `add x2, x29, #${arr_offset + 8}\n`);
|
|
43805
|
+
}
|
|
43806
|
+
}
|
|
42377
43807
|
if (variadic_idx !== void 0 && fc.params[variadic_idx] && fc.params[variadic_idx].node_type === "array") {
|
|
42378
43808
|
const arr = fc.params[variadic_idx];
|
|
42379
43809
|
const elem_type_name = arr.type.name || "int";
|
|
@@ -42906,6 +44336,26 @@ function build_declaration_node(node, status, nir_init, nir_swap) {
|
|
|
42906
44336
|
}
|
|
42907
44337
|
return;
|
|
42908
44338
|
}
|
|
44339
|
+
const value_struct_ref_alias = node.is_ref_binding === true && !node.type.is_ref && !!status.structs.find((s) => s.name === node.type.name && !s.is_class && !s.is_simple_type && !s.is_generic);
|
|
44340
|
+
if (node.type.is_ref || value_struct_ref_alias) {
|
|
44341
|
+
const offset = allocate_stack_space(status, 8);
|
|
44342
|
+
status.stack_offsets.set(node.name, offset);
|
|
44343
|
+
if (node.value) {
|
|
44344
|
+
const init_is_slot_load = node.value.node_type === "access" && node.value.access.is_element_slot_load === true;
|
|
44345
|
+
if (node.value.node_type === "value") emit_var_address(status, "x0", node.value.value);
|
|
44346
|
+
else if (init_is_slot_load) {
|
|
44347
|
+
build_node(node.value, status);
|
|
44348
|
+
ensure_newline(status);
|
|
44349
|
+
} else {
|
|
44350
|
+
emit_init_value(node.value, nir_init, status);
|
|
44351
|
+
ensure_newline(status);
|
|
44352
|
+
}
|
|
44353
|
+
emit_var_store(status, "x0", node.name, 8);
|
|
44354
|
+
}
|
|
44355
|
+
if (!status.function_ref_params) status.function_ref_params = /* @__PURE__ */ new Set();
|
|
44356
|
+
status.function_ref_params.add(node.name);
|
|
44357
|
+
return;
|
|
44358
|
+
}
|
|
42909
44359
|
if (node.type.is_pointer) {
|
|
42910
44360
|
const offset = allocate_stack_space(status, 8);
|
|
42911
44361
|
status.stack_offsets.set(node.name, offset);
|
|
@@ -43150,7 +44600,8 @@ function build_declaration_node(node, status, nir_init, nir_swap) {
|
|
|
43150
44600
|
status.alias_owns_flag?.set(node.name, flag_offset);
|
|
43151
44601
|
}
|
|
43152
44602
|
}
|
|
43153
|
-
|
|
44603
|
+
const pure_borrow_element_load = !is_borrowed_class_ref && is_pure_borrow_element_load(node, status);
|
|
44604
|
+
if (!is_borrowed_class_ref && !is_field_struct_borrow(node) && !pure_borrow_element_load && struct_type && struct_needs_destroy(struct_type, status)) track_struct_decl(status, node.name, node.type.name, node.type.type_args, node.type.is_nullable);
|
|
43154
44605
|
if (status.enums.find((e) => e.name === node.type.name && e.has_associated_data)) {
|
|
43155
44606
|
const enum_size = get_enum_size(node.type.name, status);
|
|
43156
44607
|
if (status.function_return_label) {
|
|
@@ -43602,9 +45053,13 @@ function build_declaration_node(node, status, nir_init, nir_swap) {
|
|
|
43602
45053
|
status.stack_offsets.set(node.name, offset);
|
|
43603
45054
|
} else emit_data(status, `${node.name}: .space 8\n`);
|
|
43604
45055
|
if (node.value) {
|
|
43605
|
-
|
|
45056
|
+
const init_is_slot_load = node.value.node_type === "access" && node.value.access.is_element_slot_load === true;
|
|
45057
|
+
if (node.value.node_type === "value" && !init_is_slot_load) {
|
|
43606
45058
|
const src_name = node.value.value;
|
|
43607
45059
|
emit_var_address(status, "x0", src_name);
|
|
45060
|
+
} else if (init_is_slot_load) {
|
|
45061
|
+
build_node(node.value, status);
|
|
45062
|
+
ensure_newline(status);
|
|
43608
45063
|
} else {
|
|
43609
45064
|
emit_init_value(node.value, nir_init, status);
|
|
43610
45065
|
ensure_newline(status);
|
|
@@ -43788,13 +45243,18 @@ function build_declaration_node(node, status, nir_init, nir_swap) {
|
|
|
43788
45243
|
const init_access = node.value?.node_type === "access" ? node.value.access : void 0;
|
|
43789
45244
|
const is_borrow_accessor_init = init_access?.node_type === "access_func" && is_container_borrow_accessor_name(init_access.name ?? "");
|
|
43790
45245
|
const is_compiler_carrier = is_compiler_temp_name$2(node.name);
|
|
43791
|
-
|
|
45246
|
+
const pure_borrow_init = is_borrow_accessor_init && is_pure_borrow_element_load(node, status);
|
|
45247
|
+
if (binding_struct && owns_strings_uniformly(binding_struct) && is_borrow_accessor_init && !pure_borrow_init) {
|
|
43792
45248
|
if (is_compiler_carrier) node.borrow_accessor_carrier = true;
|
|
43793
45249
|
else {
|
|
43794
45250
|
emit_var_address(status, "x2", node.name);
|
|
43795
45251
|
emit_owning_field_strdups_at(status, binding_struct.name, "x2");
|
|
43796
45252
|
}
|
|
43797
45253
|
}
|
|
45254
|
+
if (binding_struct && is_borrow_accessor_init && !is_compiler_carrier) {
|
|
45255
|
+
if (!status.func_field_borrow_decls) status.func_field_borrow_decls = /* @__PURE__ */ new Set();
|
|
45256
|
+
status.func_field_borrow_decls.add(node.name);
|
|
45257
|
+
}
|
|
43798
45258
|
}
|
|
43799
45259
|
if (node.value.is_moved) mark_moved_if_struct(node.value, status);
|
|
43800
45260
|
else if (node.value.node_type === "anon_struct") {
|
|
@@ -45062,9 +46522,9 @@ function build_assignment_node(node, status, nir_rhs, nir_swap) {
|
|
|
45062
46522
|
if (self_param.value !== name) mark_moved_if_struct(self_param, status);
|
|
45063
46523
|
}
|
|
45064
46524
|
}
|
|
45065
|
-
const
|
|
45066
|
-
const lhs_type_name =
|
|
45067
|
-
if (rhs_is_struct && !rhs_struct && !paramReg && !is_local_ref_var(name, status) && lhs_type_name) emit_destroy_for_decl(status, name, lhs_type_name, void 0,
|
|
46525
|
+
const lhs_decl_type = status.scoped_declarations.find((d) => d.name === name)?.type ?? status.variable_types?.get(name);
|
|
46526
|
+
const lhs_type_name = lhs_decl_type?.name ?? "";
|
|
46527
|
+
if (rhs_is_struct && !rhs_struct && !paramReg && !is_local_ref_var(name, status) && lhs_type_name) emit_destroy_for_decl(status, name, lhs_type_name, void 0, lhs_decl_type?.type_args, lhs_decl_type?.is_nullable);
|
|
45068
46528
|
get_source_address(node.right_value, status, nir_rhs);
|
|
45069
46529
|
ensure_newline(status);
|
|
45070
46530
|
if (paramReg && is_mutable_param(name, status)) {
|
|
@@ -45378,7 +46838,12 @@ function build_assignment_node(node, status, nir_rhs, nir_swap) {
|
|
|
45378
46838
|
else if (status.last_result_is_heap) {
|
|
45379
46839
|
if (!status.heap_strings) status.heap_strings = /* @__PURE__ */ new Set();
|
|
45380
46840
|
status.heap_strings.add(name);
|
|
45381
|
-
} else if (explicit_move_source !== void 0 && status.heap_strings?.
|
|
46841
|
+
} else if (explicit_move_source !== void 0 && status.heap_strings?.has(explicit_move_source)) {
|
|
46842
|
+
if (!status.moved) status.moved = /* @__PURE__ */ new Set();
|
|
46843
|
+
status.moved.add(explicit_move_source);
|
|
46844
|
+
mark_local_moved_flag(status, explicit_move_source);
|
|
46845
|
+
status.heap_strings.add(name);
|
|
46846
|
+
}
|
|
45382
46847
|
if (lhs_is_heap && size === 16 && !status.heap_strings?.has(name)) {
|
|
45383
46848
|
emit_strdup_string(status);
|
|
45384
46849
|
if (!status.heap_strings) status.heap_strings = /* @__PURE__ */ new Set();
|
|
@@ -48391,6 +49856,7 @@ function build_access_node(node, status) {
|
|
|
48391
49856
|
break;
|
|
48392
49857
|
case "access_func": {
|
|
48393
49858
|
const access_func = node.access;
|
|
49859
|
+
if (access_func.is_handler_registration) return;
|
|
48394
49860
|
if (access_func.is_nursery_spawn) {
|
|
48395
49861
|
build_nursery_spawn(node, access_func, status);
|
|
48396
49862
|
return;
|
|
@@ -49501,7 +50967,7 @@ function build_access_method(node, access_func, status) {
|
|
|
49501
50967
|
const method_ret_type = access_callee_method?.return_type ?? access_func.type;
|
|
49502
50968
|
const resolved_ret_type = resolve_mono_type(method_ret_type, status);
|
|
49503
50969
|
const ret_type_name = resolved_ret_type.name || access_func.type.name;
|
|
49504
|
-
const return_struct = !access_func.type.is_view && !access_func.type.is_array && !resolved_ret_type.is_view && !resolved_ret_type.is_array && (!!status.structs.find((s) => s.name === ret_type_name && !s.is_simple_type && !s.is_class) || get_enum_sret_size(ret_type_name, status) !== void 0);
|
|
50970
|
+
const return_struct = !access_func.type.is_view && !access_func.type.is_array && !resolved_ret_type.is_view && !resolved_ret_type.is_array && !resolved_ret_type.is_pointer && !access_func.type.is_pointer && (!!status.structs.find((s) => s.name === ret_type_name && !s.is_simple_type && !s.is_class) || get_enum_sret_size(ret_type_name, status) !== void 0);
|
|
49505
50971
|
if (process.env.NOMEN_DBG_SRET) __require("node:fs").appendFileSync("/var/folders/j7/hblq8b2n0t5258cz04tslkg80000gp/T/opencode/nom_asm/dbg.log", `access method=${method_name} ret=${access_func.type.name} mono=${mono_struct_name} return_struct=${return_struct}\n`);
|
|
49506
50972
|
const nullable_scalar_ret = is_nullable_scalar_type(method_ret_type, status) && !method_ret_type.is_ref;
|
|
49507
50973
|
let temp_addr = "";
|
|
@@ -49569,7 +51035,7 @@ function build_access_method(node, access_func, status) {
|
|
|
49569
51035
|
emit_asm(status, `ldr x0, [x0]\n`);
|
|
49570
51036
|
emit_asm(status, `add x0, x0, #8\n`);
|
|
49571
51037
|
} else if (target_type.is_array && (status.function_array_params?.has(name) || status.function_variadic_params?.has(name)) && !is_local_ref_var(name, status)) emit_asm(status, `ldr x0, [x0]\n`);
|
|
49572
|
-
else if (target_is_simple && !target_type.is_array && (target_type.name !== "string" || has_stack_offset) && !is_local_ref_var(name, status)
|
|
51038
|
+
else if (target_is_simple && !target_type.is_array && (target_type.name !== "string" || has_stack_offset) && !method_self_is_ref && (!is_local_ref_var(name, status) || target_type.name !== "string")) {
|
|
49573
51039
|
const size = aarch64_size(target_type.name);
|
|
49574
51040
|
const signed = is_signed_type(target_type.name);
|
|
49575
51041
|
if (size === 1) emit_asm(status, signed ? `ldrsb x0, [x0]\n` : `ldrb w0, [x0]\n`);
|
|
@@ -50044,269 +51510,6 @@ function resolve_access_type(node, status) {
|
|
|
50044
51510
|
return struct.fields.find((f) => f.name === field_name)?.type || null;
|
|
50045
51511
|
}
|
|
50046
51512
|
//#endregion
|
|
50047
|
-
//#region ../src/build_aarch64/utils/c_companion.ts
|
|
50048
|
-
/**
|
|
50049
|
-
* Generate the complete C companion file: includes, struct typedefs, and
|
|
50050
|
-
* function definitions for every collected `aarch64_use_c` function.
|
|
50051
|
-
*/
|
|
50052
|
-
function generate_companion(functions, status) {
|
|
50053
|
-
let out = "";
|
|
50054
|
-
if (functions.length > 0 && (status.platform === "macos" || status.platform === "ios")) {
|
|
50055
|
-
out += `#import <Foundation/Foundation.h>\n`;
|
|
50056
|
-
out += `#include <objc/runtime.h>\n`;
|
|
50057
|
-
out += `#include <objc/message.h>\n`;
|
|
50058
|
-
if (status.platform === "macos") out += `#import <Cocoa/Cocoa.h>\n`;
|
|
50059
|
-
else out += `#import <UIKit/UIKit.h>\n`;
|
|
50060
|
-
}
|
|
50061
|
-
out += `#include <stdint.h>\n`;
|
|
50062
|
-
out += `#include <stdlib.h>\n`;
|
|
50063
|
-
out += `typedef struct { void* ptr; long len; } nomen_view;\n`;
|
|
50064
|
-
out += `typedef struct { char* ptr; long len; } nomen_string;\n`;
|
|
50065
|
-
out += `#include <stdio.h>\n`;
|
|
50066
|
-
out += `#include <string.h>\n`;
|
|
50067
|
-
out += `#include <regex.h>\n`;
|
|
50068
|
-
if (status.file_scope_c?.includes("pthread") || status.emit_mode === "system") out += `#include <pthread.h>\n`;
|
|
50069
|
-
out += "\n";
|
|
50070
|
-
if (status.audit) {
|
|
50071
|
-
out += `void *nomen_malloc_wrap(unsigned long);\n`;
|
|
50072
|
-
out += `void *nomen_calloc_wrap(unsigned long, unsigned long);\n`;
|
|
50073
|
-
out += `void *nomen_realloc_wrap(void *, unsigned long);\n`;
|
|
50074
|
-
out += `void nomen_free_wrap(void *);\n`;
|
|
50075
|
-
out += `void *nomen_strdup_wrap(const char *);\n`;
|
|
50076
|
-
out += `void nomen_audit_check(void);\n`;
|
|
50077
|
-
out += "\n";
|
|
50078
|
-
}
|
|
50079
|
-
if (status.emit_mode === "system") {
|
|
50080
|
-
out += runtime_declarations(POOL_HEADER_C + FIBER_HEADER_C);
|
|
50081
|
-
out += "\n";
|
|
50082
|
-
} else {
|
|
50083
|
-
if (!status.pool_runtime_emitted) {
|
|
50084
|
-
out += POOL_HEADER_C;
|
|
50085
|
-
status.pool_runtime_emitted = true;
|
|
50086
|
-
}
|
|
50087
|
-
if (!status.fiber_runtime_emitted) {
|
|
50088
|
-
out += FIBER_HEADER_C;
|
|
50089
|
-
status.fiber_runtime_emitted = true;
|
|
50090
|
-
}
|
|
50091
|
-
}
|
|
50092
|
-
const enum_nodes = status.enums.filter((e) => !e.is_generic);
|
|
50093
|
-
const struct_nodes = status.structs.filter((s) => !s.is_simple_type && !s.is_generic);
|
|
50094
|
-
const node_kind = /* @__PURE__ */ new Map();
|
|
50095
|
-
for (const e of enum_nodes) node_kind.set(e.name, "enum");
|
|
50096
|
-
for (const st of struct_nodes) node_kind.set(st.name, "struct");
|
|
50097
|
-
for (const b of status.bitsets) node_kind.set(b.name, "bitset");
|
|
50098
|
-
const deps = /* @__PURE__ */ new Map();
|
|
50099
|
-
for (const e of enum_nodes) {
|
|
50100
|
-
const d = /* @__PURE__ */ new Set();
|
|
50101
|
-
for (const c of e.cases) for (const p of c.params) if (node_kind.has(p.type.name) && p.type.name !== e.name) d.add(p.type.name);
|
|
50102
|
-
deps.set(e.name, d);
|
|
50103
|
-
}
|
|
50104
|
-
for (const st of struct_nodes) {
|
|
50105
|
-
const d = /* @__PURE__ */ new Set();
|
|
50106
|
-
for (const f of st.fields) {
|
|
50107
|
-
if (f.type.is_view) continue;
|
|
50108
|
-
if (node_kind.has(f.type.name) && f.type.name !== st.name) d.add(f.type.name);
|
|
50109
|
-
}
|
|
50110
|
-
deps.set(st.name, d);
|
|
50111
|
-
}
|
|
50112
|
-
const emitted = /* @__PURE__ */ new Set();
|
|
50113
|
-
const visiting = /* @__PURE__ */ new Set();
|
|
50114
|
-
const node_list = [
|
|
50115
|
-
...enum_nodes,
|
|
50116
|
-
...struct_nodes,
|
|
50117
|
-
...status.bitsets
|
|
50118
|
-
];
|
|
50119
|
-
const by_name = new Map(node_list.map((n) => [n.name, n]));
|
|
50120
|
-
const visit = (name) => {
|
|
50121
|
-
if (emitted.has(name) || visiting.has(name)) return;
|
|
50122
|
-
visiting.add(name);
|
|
50123
|
-
for (const dep of deps.get(name) ?? []) visit(dep);
|
|
50124
|
-
visiting.delete(name);
|
|
50125
|
-
emitted.add(name);
|
|
50126
|
-
const node = by_name.get(name);
|
|
50127
|
-
if (!node) return;
|
|
50128
|
-
if (node_kind.get(name) === "enum") out += generate_enum_definition(node, status);
|
|
50129
|
-
else if (node_kind.get(name) === "bitset") out += `typedef unsigned long ${nm(name)};\n`;
|
|
50130
|
-
else out += generate_struct_definition(node, status);
|
|
50131
|
-
};
|
|
50132
|
-
for (const n of node_list) visit(n.name);
|
|
50133
|
-
out += "\n";
|
|
50134
|
-
if (status.file_scope_c) {
|
|
50135
|
-
out += status.file_scope_c;
|
|
50136
|
-
out += "\n";
|
|
50137
|
-
}
|
|
50138
|
-
for (const entry of functions) out += generate_c_function(entry.func, entry.struct_name, entry.raw_code, status);
|
|
50139
|
-
return out;
|
|
50140
|
-
}
|
|
50141
|
-
/**
|
|
50142
|
-
* Prefix a Nomen type's C TYPEDEF name with `nm_`. The companion file `#import`s
|
|
50143
|
-
* the platform frameworks (Foundation/Cocoa/UIKit), which drag in a large set of
|
|
50144
|
-
* system typedefs (e.g. macOS `MacTypes.h` defines `typedef long Size`). If we
|
|
50145
|
-
* emitted Nomen's own `typedef struct Size {...} Size;` the typedef name would
|
|
50146
|
-
* collide; mangling only the typedef (`typedef struct Size {...} nm_Size;`)
|
|
50147
|
-
* sidesteps it while leaving the struct TAG (`Size`) untouched.
|
|
50148
|
-
*
|
|
50149
|
-
* Keeping the original tag matters: generated C (spawn/trampoline infra in
|
|
50150
|
-
* build_spawn_node / build_nursery_spawn) and `#arch: aarch64_use_c` raw bodies
|
|
50151
|
-
* reference Nomen types as `struct Foo` (the tag), so an unchanged tag means
|
|
50152
|
-
* none of that code needs to know about mangling. Codegen-generated references
|
|
50153
|
-
* here (fields, params, returns) use the mangled typedef instead.
|
|
50154
|
-
*
|
|
50155
|
-
* `nm_` (no leading underscore: the C standard reserves `_`+lowercase
|
|
50156
|
-
* identifiers at file scope). This is purely cosmetic — the aarch64 assembly
|
|
50157
|
-
* never references type names (only function labels, bridged via `__asm__`).
|
|
50158
|
-
*/
|
|
50159
|
-
function nm(name) {
|
|
50160
|
-
return "nm_" + name;
|
|
50161
|
-
}
|
|
50162
|
-
/**
|
|
50163
|
-
* The C type for a field/param/return of the given Nomen type, applying `nm_`
|
|
50164
|
-
* to user-defined struct/enum types and falling back to `c_type` for primitives.
|
|
50165
|
-
* Generic structs lower to opaque 8-byte pointers (their element type lives in
|
|
50166
|
-
* the Nomen `Type`, not in the C layout).
|
|
50167
|
-
*/
|
|
50168
|
-
/**
|
|
50169
|
-
* The C type for a value stored BY VALUE in an enum payload: classes and
|
|
50170
|
-
* traits are heap/shared references (pointers, and never have a companion
|
|
50171
|
-
* typedef), everything else follows `companion_type`.
|
|
50172
|
-
*/
|
|
50173
|
-
function payload_type(typeName, status) {
|
|
50174
|
-
if (status.structs.find((s) => s.name === typeName)?.is_class) return `struct ${typeName} *`;
|
|
50175
|
-
if (status.traits.find((t) => t.name === typeName)) return `struct ${typeName} *`;
|
|
50176
|
-
return companion_type(typeName, status);
|
|
50177
|
-
}
|
|
50178
|
-
function companion_type(typeName, status) {
|
|
50179
|
-
const struct = status.structs.find((s) => s.name === typeName);
|
|
50180
|
-
if (struct?.is_generic) return "void *";
|
|
50181
|
-
if (struct && !struct.is_simple_type) return nm(typeName);
|
|
50182
|
-
if (!struct && status.enums.find((e) => e.name === typeName)) return nm(typeName);
|
|
50183
|
-
if (status.bitsets.find((b) => b.name === typeName)) return nm(typeName);
|
|
50184
|
-
return c_type(typeName);
|
|
50185
|
-
}
|
|
50186
|
-
/**
|
|
50187
|
-
* Emit an enum's C type definition. Mirrors the C backend's `build_enum_node`
|
|
50188
|
-
* (typedef for simple enums; tag + tagged-union struct for enums with associated
|
|
50189
|
-
* data) but with `nm_`-prefixed TYPEDEF names and no constructor functions (those
|
|
50190
|
-
* are emitted as assembly in the aarch64 path; the companion only needs types so
|
|
50191
|
-
* struct fields / function signatures can reference them). The struct tag keeps
|
|
50192
|
-
* the original Nomen name (see `nm`).
|
|
50193
|
-
*/
|
|
50194
|
-
function generate_enum_definition(node, status) {
|
|
50195
|
-
let out = "";
|
|
50196
|
-
if (node.has_associated_data) {
|
|
50197
|
-
out += `typedef enum { ${node.cases.map((c) => `${node.name}_${c.name}`).join(", ")} } ${nm(node.name)}_tag;\n`;
|
|
50198
|
-
out += `struct ${node.name};\n`;
|
|
50199
|
-
out += `typedef struct ${node.name}\n{\n`;
|
|
50200
|
-
out += `${nm(node.name)}_tag tag;\n`;
|
|
50201
|
-
out += `union {\n`;
|
|
50202
|
-
for (const c of node.cases) out += `struct { ${c.params.map((p) => `${payload_type(p.type.name, status)} ${p.name}`).join("; ")}${c.params.length ? ";" : ""} } _${c.name};\n`;
|
|
50203
|
-
out += `} _data;\n`;
|
|
50204
|
-
out += `} ${nm(node.name)};\n`;
|
|
50205
|
-
} else out += `typedef enum { ${node.cases.map((c) => `${node.name}_${c.name}`).join(", ")} } ${nm(node.name)};\n`;
|
|
50206
|
-
return out;
|
|
50207
|
-
}
|
|
50208
|
-
function generate_struct_definition(struct, status) {
|
|
50209
|
-
let out = `typedef struct ${struct.name}\n{\n`;
|
|
50210
|
-
out += `void *_vt;\n`;
|
|
50211
|
-
for (const field of struct.fields) out += `${field.type.is_view ? "nomen_view" : payload_type(field.type.name, status)} ${field.name};\n`;
|
|
50212
|
-
for (const traitName of struct.traits) {
|
|
50213
|
-
const trait = status.traits.find((t) => t.name === traitName);
|
|
50214
|
-
if (!trait) continue;
|
|
50215
|
-
for (const field of trait.fields.filter((f) => !struct.fields.find((nf) => nf.name === f.name))) out += `${field.type.is_view ? "nomen_view" : payload_type(field.type.name, status)} ${field.name};\n`;
|
|
50216
|
-
}
|
|
50217
|
-
out += `} ${nm(struct.name)};\n`;
|
|
50218
|
-
return out;
|
|
50219
|
-
}
|
|
50220
|
-
function generate_c_function(func, struct_name, raw_code, status) {
|
|
50221
|
-
let func_label;
|
|
50222
|
-
if (struct_name) {
|
|
50223
|
-
const struct = status.structs.find((s) => s.name === struct_name);
|
|
50224
|
-
if (struct && is_overloaded(struct, func.name)) func_label = mangled_label(func, struct_name);
|
|
50225
|
-
else func_label = `${struct_name}_${func.name.replace(/#/g, "")}`;
|
|
50226
|
-
} else func_label = func.name === "main" ? "_nomen_main" : func.name;
|
|
50227
|
-
const is_class_init = func.name === "#init" && !!(struct_name && status.structs.find((s) => s.name === struct_name));
|
|
50228
|
-
const return_type = is_class_init ? "void" : func.return_type?.name || "void";
|
|
50229
|
-
const return_struct = is_class_init ? void 0 : status.structs.find((s) => s.name === return_type && !s.is_simple_type);
|
|
50230
|
-
let return_prefix = return_struct ? nm(return_type) : companion_type(return_type, status);
|
|
50231
|
-
return_prefix += ` `;
|
|
50232
|
-
const return_enum_size = return_struct ? void 0 : get_enum_sret_size(return_type, status);
|
|
50233
|
-
const return_struct_size = return_struct ? get_struct_size(return_type, status) : 0;
|
|
50234
|
-
const symbol_label = return_struct && return_struct_size <= 16 || return_enum_size !== void 0 && return_enum_size <= 16 ? `${func_label}_c` : func_label;
|
|
50235
|
-
const params = func.params;
|
|
50236
|
-
let param_list = "";
|
|
50237
|
-
for (let i = 0; i < params.length; i++) {
|
|
50238
|
-
if (i > 0) param_list += ", ";
|
|
50239
|
-
param_list += generate_c_param(params[i], status);
|
|
50240
|
-
}
|
|
50241
|
-
if (params.length === 0) param_list += `void`;
|
|
50242
|
-
let out = `// ${func_label}\n`;
|
|
50243
|
-
out += `${return_prefix}${symbol_label}(${param_list}) __asm__("${symbol_label}");\n`;
|
|
50244
|
-
out += `${return_prefix}${symbol_label}(${param_list})\n{\n`;
|
|
50245
|
-
if (func.name === "#init" && struct_name) {
|
|
50246
|
-
const ctor_struct = status.structs.find((s) => s.name === struct_name);
|
|
50247
|
-
if (ctor_struct && !ctor_struct.is_simple_type) {
|
|
50248
|
-
if (ctor_struct.traits.length > 0) {
|
|
50249
|
-
out += `extern void *${struct_name}_traits;\n`;
|
|
50250
|
-
out += `self->_vt = (void *)&${struct_name}_traits;\n`;
|
|
50251
|
-
} else if (!ctor_struct.is_class) out += `self->_vt = (void *)1;\n`;
|
|
50252
|
-
for (const field of ctor_struct.fields) {
|
|
50253
|
-
if (!field.value) continue;
|
|
50254
|
-
const seeded = emit_ctor_field_default(ctor_struct, field);
|
|
50255
|
-
if (!seeded) {
|
|
50256
|
-
if (!status.build_errors) status.build_errors = [];
|
|
50257
|
-
status.build_errors.push({
|
|
50258
|
-
message: `mv struct '${struct_name}' has a use_c constructor and field '${field.name}' has a non-literal default — assign it in the #init body`,
|
|
50259
|
-
start: func.start
|
|
50260
|
-
});
|
|
50261
|
-
continue;
|
|
50262
|
-
}
|
|
50263
|
-
out += seeded;
|
|
50264
|
-
}
|
|
50265
|
-
}
|
|
50266
|
-
}
|
|
50267
|
-
const self_param = params[0];
|
|
50268
|
-
if (struct_name && self_param?.is_self_param && !self_param?.is_ref && func.name !== "#destroy" && func.name !== "#init") {
|
|
50269
|
-
const struct = status.structs.find((s) => s.name === struct_name);
|
|
50270
|
-
if (struct && !struct.is_simple_type) out += `${nm(struct_name)} _self = *self;\n`;
|
|
50271
|
-
}
|
|
50272
|
-
out += raw_code;
|
|
50273
|
-
out += `\n}\n\n`;
|
|
50274
|
-
return out;
|
|
50275
|
-
}
|
|
50276
|
-
/**
|
|
50277
|
-
* The C statement seeding a defaulted field in a companion constructor, or
|
|
50278
|
-
* `undefined` when the default is not a literal shape. Mirrors the ASM-side
|
|
50279
|
-
* ctor's default seeding for the shapes a `use_c` constructor can express:
|
|
50280
|
-
* string literals borrow static storage (a function-static C literal — never
|
|
50281
|
-
* strdup'd, matching the ASM literal-pair store), scalar/bool literals store
|
|
50282
|
-
* directly. Nullable and computed defaults are rejected — the caller turns
|
|
50283
|
-
* that into a build error naming the field.
|
|
50284
|
-
*/
|
|
50285
|
-
function emit_ctor_field_default(struct_node, field) {
|
|
50286
|
-
const value = field.value?.node_type === "value" ? field.value.value : void 0;
|
|
50287
|
-
if (value === void 0 || typeof value !== "string") return void 0;
|
|
50288
|
-
if (field.type.is_nullable) return void 0;
|
|
50289
|
-
if (field.type.name === "string" && !field.type.is_view) {
|
|
50290
|
-
const length = string_literal_length(value);
|
|
50291
|
-
const label = `_${struct_node.name}_${field.name}_default`;
|
|
50292
|
-
return `static const char ${label}[] = ${escape_asciz(value)};\nself->${field.name} = (nomen_string){ (char *)${label}, ${length} };\n`;
|
|
50293
|
-
}
|
|
50294
|
-
if (value === "true" || value === "false") return `self->${field.name} = ${value === "true" ? 1 : 0};\n`;
|
|
50295
|
-
if (/^-?\d+$/.test(value)) return `self->${field.name} = ${value};\n`;
|
|
50296
|
-
}
|
|
50297
|
-
function generate_c_param(param, status) {
|
|
50298
|
-
const struct_type = status.structs.find((s) => s.name === param.type.name);
|
|
50299
|
-
const trait_type = status.traits.find((t) => t.name === param.type.name);
|
|
50300
|
-
const is_struct = (param.is_self_param || struct_type || trait_type) && !struct_type?.is_simple_type;
|
|
50301
|
-
let out = "";
|
|
50302
|
-
if (param.is_variadic) out += `long _${param.name}_len, `;
|
|
50303
|
-
out += companion_type(param.type.name, status);
|
|
50304
|
-
if (is_struct || param.declaration === "var" || param.type.is_ref || param.type.is_array) out += ` *`;
|
|
50305
|
-
else out += ` `;
|
|
50306
|
-
out += param.name;
|
|
50307
|
-
return out;
|
|
50308
|
-
}
|
|
50309
|
-
//#endregion
|
|
50310
51513
|
//#region ../src/build_aarch64/utils/scan_heap_returns.ts
|
|
50311
51514
|
const KNOWN_HEAP_RETURNING = /* @__PURE__ */ new Set([]);
|
|
50312
51515
|
function scan_heap_returning_functions(root) {
|
|
@@ -50532,7 +51735,7 @@ function fold_asm_constants(asm) {
|
|
|
50532
51735
|
i++;
|
|
50533
51736
|
continue;
|
|
50534
51737
|
}
|
|
50535
|
-
const m = /^(\w
|
|
51738
|
+
const m = /^([A-Za-z.][\w.]*)\s*(.*)$/.exec(line);
|
|
50536
51739
|
if (!m) {
|
|
50537
51740
|
clear();
|
|
50538
51741
|
out.push(raw_line);
|
|
@@ -51161,7 +52364,9 @@ function build(root, options = {}) {
|
|
|
51161
52364
|
for (const [label, value] of status.float_literals) status.code += `${label}: .double ${value}\n`;
|
|
51162
52365
|
}
|
|
51163
52366
|
const emit_mode = status.emit_mode ?? "all";
|
|
51164
|
-
|
|
52367
|
+
let interpolate_lengths;
|
|
52368
|
+
if (emit_mode === "user") interpolate_lengths = Array.from(status.interpolate_string_counts).filter((n) => n > 7);
|
|
52369
|
+
else if (emit_mode === "system") interpolate_lengths = Array.from(/* @__PURE__ */ new Set([
|
|
51165
52370
|
...status.interpolate_string_counts,
|
|
51166
52371
|
1,
|
|
51167
52372
|
2,
|
|
@@ -51170,15 +52375,21 @@ function build(root, options = {}) {
|
|
|
51170
52375
|
5,
|
|
51171
52376
|
6,
|
|
51172
52377
|
7
|
|
51173
|
-
])).sort((a, b) => a - b)
|
|
52378
|
+
])).sort((a, b) => a - b);
|
|
52379
|
+
else interpolate_lengths = Array.from(status.interpolate_string_counts);
|
|
51174
52380
|
for (const length of interpolate_lengths) {
|
|
51175
52381
|
status.code += `\n.p2align 2\n`;
|
|
51176
52382
|
if (emit_mode === "system") status.code += `.globl _string_interpolate_${length}\n`;
|
|
51177
52383
|
status.code += `_string_interpolate_${length}:\n`;
|
|
51178
52384
|
status.code += `stp x29, x30, [sp, #-16]!\n`;
|
|
51179
52385
|
status.code += `mov x29, sp\n`;
|
|
51180
|
-
|
|
51181
|
-
|
|
52386
|
+
const args_area = length * 8;
|
|
52387
|
+
const len_off = args_area;
|
|
52388
|
+
const buf_off = args_area + 8;
|
|
52389
|
+
const pat_off = args_area + 16;
|
|
52390
|
+
const frame = Math.ceil((args_area + 24) / 16) * 16;
|
|
52391
|
+
status.code += `sub sp, sp, #${frame}\n`;
|
|
52392
|
+
status.code += `str x0, [sp, #${pat_off}]\n`;
|
|
51182
52393
|
const pairRegs = [
|
|
51183
52394
|
["x2", "x3"],
|
|
51184
52395
|
["x4", "x5"],
|
|
@@ -51191,7 +52402,7 @@ function build(root, options = {}) {
|
|
|
51191
52402
|
}
|
|
51192
52403
|
status.code += `mov x0, xzr\n`;
|
|
51193
52404
|
status.code += `mov x1, xzr\n`;
|
|
51194
|
-
status.code += `ldr x2, [sp,
|
|
52405
|
+
status.code += `ldr x2, [sp, #${pat_off}]\n`;
|
|
51195
52406
|
const variadicRegs = [
|
|
51196
52407
|
"x3",
|
|
51197
52408
|
"x4",
|
|
@@ -51202,17 +52413,17 @@ function build(root, options = {}) {
|
|
|
51202
52413
|
for (let i = 0; i < length && i < variadicRegs.length; i++) status.code += `ldr ${variadicRegs[i]}, [sp, #${i * 8}]\n`;
|
|
51203
52414
|
status.code += `bl _snprintf\n`;
|
|
51204
52415
|
status.code += `add x0, x0, #1\n`;
|
|
51205
|
-
status.code += `str x0, [sp,
|
|
52416
|
+
status.code += `str x0, [sp, #${len_off}]\n`;
|
|
51206
52417
|
emit_malloc(status);
|
|
51207
|
-
status.code += `str x0, [sp,
|
|
51208
|
-
status.code += `ldr x0, [sp,
|
|
51209
|
-
status.code += `ldr x1, [sp,
|
|
51210
|
-
status.code += `ldr x2, [sp,
|
|
52418
|
+
status.code += `str x0, [sp, #${buf_off}]\n`;
|
|
52419
|
+
status.code += `ldr x0, [sp, #${buf_off}]\n`;
|
|
52420
|
+
status.code += `ldr x1, [sp, #${len_off}]\n`;
|
|
52421
|
+
status.code += `ldr x2, [sp, #${pat_off}]\n`;
|
|
51211
52422
|
for (let i = 0; i < length && i < variadicRegs.length; i++) status.code += `ldr ${variadicRegs[i]}, [sp, #${i * 8}]\n`;
|
|
51212
52423
|
status.code += `bl _snprintf\n`;
|
|
51213
52424
|
status.code += `mov x1, x0\n`;
|
|
51214
|
-
status.code += `ldr x0, [sp,
|
|
51215
|
-
status.code += `add sp, sp,
|
|
52425
|
+
status.code += `ldr x0, [sp, #${buf_off}]\n`;
|
|
52426
|
+
status.code += `add sp, sp, #${frame}\n`;
|
|
51216
52427
|
status.code += `ldp x29, x30, [sp], #16\n`;
|
|
51217
52428
|
status.code += `ret\n`;
|
|
51218
52429
|
}
|
|
@@ -51239,7 +52450,7 @@ function build(root, options = {}) {
|
|
|
51239
52450
|
status.code = status.code.replaceAll("bl _strdup\n", "bl _nomen_strdup_wrap\n");
|
|
51240
52451
|
status.code = status.code.replaceAll("bl _free\n", "bl _nomen_free_wrap\n");
|
|
51241
52452
|
}
|
|
51242
|
-
const { result: lifted } = lift_functions(status.code);
|
|
52453
|
+
const { result: lifted } = lift_functions(status.code, { allow_external_branch_targets: status.emit_mode === "user" });
|
|
51243
52454
|
const lift_errors = lifted.errors;
|
|
51244
52455
|
if (lift_errors.length > 0) {
|
|
51245
52456
|
if (!status.build_errors) status.build_errors = [];
|
|
@@ -51275,6 +52486,10 @@ function build(root, options = {}) {
|
|
|
51275
52486
|
if (options.arch === "aarch64") ensure_weak_runtime_a64(status);
|
|
51276
52487
|
let companion;
|
|
51277
52488
|
if (status.c_companion_functions && status.c_companion_functions.length > 0 || status.file_scope_c) companion = generate_companion(status.c_companion_functions ?? [], status);
|
|
52489
|
+
if (options.arch === "aarch64" && companion && status.c_companion_functions) for (const entry of status.c_companion_functions) {
|
|
52490
|
+
const label = companion_function_label(entry.func, entry.struct_name, status);
|
|
52491
|
+
if (label.startsWith("L")) status.code = status.code.replace(new RegExp(`\\b${label}`, "g"), `_${label}`);
|
|
52492
|
+
}
|
|
51278
52493
|
if (companion && options.audit) companion = wrap_c_allocators(companion);
|
|
51279
52494
|
if (status.headers.includes("struct Point") && !status.headers.includes("typedef struct Point") && process.env.NOMEN_DBG) console.error("DBG build.ts: headers has forward Point but no typedef. len=", status.headers.length);
|
|
51280
52495
|
return {
|
|
@@ -52894,6 +54109,10 @@ function clone_node(node) {
|
|
|
52894
54109
|
case "access_field": {
|
|
52895
54110
|
const n = node;
|
|
52896
54111
|
const c = new AccessFieldNode(n.start, n.name, n.type ? clone_type$2(n.type) : void 0);
|
|
54112
|
+
c.is_destructure = n.is_destructure;
|
|
54113
|
+
c.destructure_index = n.destructure_index;
|
|
54114
|
+
c.is_destructure_rename = n.is_destructure_rename;
|
|
54115
|
+
c.destructure_move = n.destructure_move;
|
|
52897
54116
|
c.allocations = n.allocations?.map(clone_node);
|
|
52898
54117
|
return c;
|
|
52899
54118
|
}
|
|
@@ -52918,6 +54137,8 @@ function clone_node(node) {
|
|
|
52918
54137
|
} : void 0;
|
|
52919
54138
|
c.inferred_array_length = n.inferred_array_length;
|
|
52920
54139
|
c.is_nursery_spawn = n.is_nursery_spawn;
|
|
54140
|
+
c.is_handler_registration = n.is_handler_registration;
|
|
54141
|
+
c.is_handler_enrollment = n.is_handler_enrollment;
|
|
52921
54142
|
c.is_thread_start = n.is_thread_start;
|
|
52922
54143
|
c.is_thread_detach = n.is_thread_detach;
|
|
52923
54144
|
c.is_fiber_start = n.is_fiber_start;
|
|
@@ -53689,7 +54910,9 @@ function type_from_value_node(node, status) {
|
|
|
53689
54910
|
t.is_nullable = true;
|
|
53690
54911
|
return t;
|
|
53691
54912
|
}
|
|
53692
|
-
|
|
54913
|
+
const t = type_from_value(vn.value, status);
|
|
54914
|
+
if (t.name) return t;
|
|
54915
|
+
return vn.type?.name ? vn.type : t;
|
|
53693
54916
|
}
|
|
53694
54917
|
case "access": return type_from_value_node(node.access, status);
|
|
53695
54918
|
case "array": return node.type;
|
|
@@ -53751,8 +54974,7 @@ function borrow_depth_of(node, status) {
|
|
|
53751
54974
|
return Math.max(base?.borrow_depth ?? 0, status.scope_depth);
|
|
53752
54975
|
}
|
|
53753
54976
|
} else if (access.access.node_type === "access_func") {
|
|
53754
|
-
if (is_borrowed_return(type_from_value_node(access, status), status)) {
|
|
53755
|
-
if (access.access.owned_return) return void 0;
|
|
54977
|
+
if (is_borrowed_return(type_from_value_node(access, status), status) && !access.access.owned_return || is_element_load_pure_borrow(access, status)) {
|
|
53756
54978
|
if (access.target.node_type === "value") {
|
|
53757
54979
|
const recv = status.values.findLast((v) => v.name === access.target.value);
|
|
53758
54980
|
if (recv) return recv.borrow_depth ?? recv.decl_depth ?? status.scope_depth;
|
|
@@ -53784,8 +55006,7 @@ function borrow_owner_of(node, status) {
|
|
|
53784
55006
|
if (t?.name && is_owning_ref_type(t.name, status)) return ultimate_owner(access.target, status);
|
|
53785
55007
|
if (t?.is_view) return ultimate_owner(access.target, status);
|
|
53786
55008
|
} else if (access.access.node_type === "access_func") {
|
|
53787
|
-
if (is_borrowed_return(type_from_value_node(access, status), status)) {
|
|
53788
|
-
if (access.access.owned_return) return void 0;
|
|
55009
|
+
if (is_borrowed_return(type_from_value_node(access, status), status) && !access.access.owned_return || is_element_load_pure_borrow(access, status)) {
|
|
53789
55010
|
if (access.target.node_type === "value") {
|
|
53790
55011
|
const recv = status.values.findLast((v) => v.name === access.target.value);
|
|
53791
55012
|
if (recv) return recv.borrowed_from ?? recv.name;
|
|
@@ -53811,6 +55032,26 @@ function is_borrowed_return(t, status) {
|
|
|
53811
55032
|
return !!t.name && is_owning_ref_type(t.name, status);
|
|
53812
55033
|
}
|
|
53813
55034
|
/**
|
|
55035
|
+
* The `List<Own>.at()` pure borrow (FOLLOWUP.md): a container borrow accessor
|
|
55036
|
+
* (`list.at(i)` — the accessor family, non-`mv out`) whose value-struct
|
|
55037
|
+
* element aliases destroy-worthy state the load cannot privatize. The result
|
|
55038
|
+
* is a borrow of the slot's resources — rooted at the container exactly like
|
|
55039
|
+
* a class-typed method return, so a `ref self` call on the container
|
|
55040
|
+
* invalidates it, it cannot escape its scope, and it cannot transfer through
|
|
55041
|
+
* `mv`. Deliberately name-scoped to the accessor family: any method may
|
|
55042
|
+
* return a `#destroy`-bearing struct by value as an OWNED transfer.
|
|
55043
|
+
*/
|
|
55044
|
+
function is_element_load_pure_borrow(access, status) {
|
|
55045
|
+
const func_access = access.access;
|
|
55046
|
+
if (func_access.owned_return) return false;
|
|
55047
|
+
if (!is_container_borrow_accessor_name(func_access.name ?? "")) return false;
|
|
55048
|
+
const t = type_from_value_node(access, status);
|
|
55049
|
+
if (!t?.name || t.is_view || t.is_array) return false;
|
|
55050
|
+
const elem = status.structs.find((s) => s.name === t.name && !s.is_simple_type && !s.is_generic);
|
|
55051
|
+
if (!elem || elem.is_class) return false;
|
|
55052
|
+
return element_load_aliases_destroy_state(elem, status);
|
|
55053
|
+
}
|
|
55054
|
+
/**
|
|
53814
55055
|
* Resolve an access target down to its ultimate mv variable name. Recurses
|
|
53815
55056
|
* through field paths so `z.animals` → `z` (and `a.b.c` → `a`).
|
|
53816
55057
|
*/
|
|
@@ -55770,6 +57011,7 @@ function sanitize_type_name(t) {
|
|
|
55770
57011
|
function clone_type$1(t) {
|
|
55771
57012
|
const new_t = new Type(t.name, t.is_static, t.is_array, t.length);
|
|
55772
57013
|
new_t.is_ref = t.is_ref;
|
|
57014
|
+
new_t.is_view = t.is_view;
|
|
55773
57015
|
new_t.is_nullable = t.is_nullable;
|
|
55774
57016
|
new_t.is_return_type = t.is_return_type;
|
|
55775
57017
|
new_t.type_args = t.type_args?.map(clone_type$1);
|
|
@@ -55826,6 +57068,178 @@ function materialize_tuple_type(type, status) {
|
|
|
55826
57068
|
return new_type;
|
|
55827
57069
|
}
|
|
55828
57070
|
//#endregion
|
|
57071
|
+
//#region ../src/check/utils/view_fields.ts
|
|
57072
|
+
/**
|
|
57073
|
+
* Whether the named struct (or any value struct embedded in its fields,
|
|
57074
|
+
* recursively) declares a `view T` field. A struct with view fields carries
|
|
57075
|
+
* non-mv borrows inside its bytes, which drives the borrow rules:
|
|
57076
|
+
* copies are sound (a pair copy aliases nothing owned), but escapes
|
|
57077
|
+
* (returning, outer-scope assignment) are checked against where those
|
|
57078
|
+
* borrows were taken.
|
|
57079
|
+
*/
|
|
57080
|
+
function struct_has_view_fields(type_name, status) {
|
|
57081
|
+
if (!type_name) return false;
|
|
57082
|
+
const struct = status.structs.find((s) => s.name === type_name && !s.is_simple_type);
|
|
57083
|
+
return !!struct && has_view_fields(struct, status, /* @__PURE__ */ new Set());
|
|
57084
|
+
}
|
|
57085
|
+
function has_view_fields(struct, status, visited) {
|
|
57086
|
+
if (visited.has(struct.name)) return false;
|
|
57087
|
+
visited.add(struct.name);
|
|
57088
|
+
for (const field of struct.fields) {
|
|
57089
|
+
if (field.type.is_view) return true;
|
|
57090
|
+
if (field.type.is_ref || field.type.is_array) continue;
|
|
57091
|
+
const field_struct = status.structs.find((s) => s.name === field.type.name && !s.is_simple_type && !s.is_class);
|
|
57092
|
+
if (field_struct && has_view_fields(field_struct, status, visited)) return true;
|
|
57093
|
+
}
|
|
57094
|
+
return false;
|
|
57095
|
+
}
|
|
57096
|
+
/**
|
|
57097
|
+
* The root variable an access chain bottoms out at (`line.text` → "line",
|
|
57098
|
+
* `a.b.c` → "a", bare `x` → "x"). Returns undefined when the chain does not
|
|
57099
|
+
* bottom out in a plain name (e.g. a call receiver).
|
|
57100
|
+
*/
|
|
57101
|
+
function root_var_of(node) {
|
|
57102
|
+
if (node.node_type === "value") {
|
|
57103
|
+
const name = node.value;
|
|
57104
|
+
return name === "null" ? void 0 : name;
|
|
57105
|
+
}
|
|
57106
|
+
if (node.node_type === "access") return root_var_of(node.target);
|
|
57107
|
+
}
|
|
57108
|
+
/**
|
|
57109
|
+
* Where a view value stored into a field borrows from.
|
|
57110
|
+
*
|
|
57111
|
+
* - A `.slice()`-style view expression: borrow_depth/borrow_owner give the
|
|
57112
|
+
* scope depth and owner variable (the slice's receiver).
|
|
57113
|
+
* - A bare variable of owned type (`view string f = doc`): borrowing it is
|
|
57114
|
+
* the identity on fat strings, but it creates a real dependency — record
|
|
57115
|
+
* the source's own declaration depth and name so reassignment of the
|
|
57116
|
+
* source invalidates the field.
|
|
57117
|
+
* - A literal / const: static storage that outlives everything — no borrow.
|
|
57118
|
+
*
|
|
57119
|
+
* Returns undefined for sources that impose no dependency.
|
|
57120
|
+
*/
|
|
57121
|
+
function view_source_borrow_info(node, status) {
|
|
57122
|
+
if (node.node_type === "value" && node.value.startsWith("\"")) return void 0;
|
|
57123
|
+
const depth = borrow_depth_of(node, status);
|
|
57124
|
+
const owner = borrow_owner_of(node, status);
|
|
57125
|
+
if (depth !== void 0 || owner !== void 0) return {
|
|
57126
|
+
depth: depth ?? status.scope_depth,
|
|
57127
|
+
owner
|
|
57128
|
+
};
|
|
57129
|
+
if (node.node_type === "value") {
|
|
57130
|
+
const decl = status.values.findLast((v) => v.name === node.value);
|
|
57131
|
+
if (decl && !decl.is_null) return {
|
|
57132
|
+
depth: decl.decl_depth ?? 0,
|
|
57133
|
+
owner: decl.name
|
|
57134
|
+
};
|
|
57135
|
+
const alloc = status.allocations.findLast((d) => d.name === node.value);
|
|
57136
|
+
if (alloc?.value) return view_source_borrow_info(alloc.value, status);
|
|
57137
|
+
}
|
|
57138
|
+
}
|
|
57139
|
+
/**
|
|
57140
|
+
* Borrow info contributed by a constructor call's `view T` arguments (e.g.
|
|
57141
|
+
* `Line(doc.slice(0, 5), 0, 5)`): merges every view-typed argument's borrow
|
|
57142
|
+
* into one entry per distinct owner (the tightest depth wins). Returns
|
|
57143
|
+
* undefined when the call passes no dependent views.
|
|
57144
|
+
*/
|
|
57145
|
+
function ctor_call_view_borrow(call, status) {
|
|
57146
|
+
const indices = call.view_param_indices ?? [];
|
|
57147
|
+
let merged;
|
|
57148
|
+
for (const i of indices) {
|
|
57149
|
+
let arg = call.params[i];
|
|
57150
|
+
if (!arg) continue;
|
|
57151
|
+
if (arg.node_type === "value") {
|
|
57152
|
+
const name = arg.value;
|
|
57153
|
+
const hoisted = call.allocations?.findLast((d) => d.name === name) ?? status.allocations.findLast((d) => d.name === name);
|
|
57154
|
+
if (hoisted?.value) arg = hoisted.value;
|
|
57155
|
+
}
|
|
57156
|
+
const info = view_source_borrow_info(arg, status);
|
|
57157
|
+
if (!info) continue;
|
|
57158
|
+
if (!merged) merged = /* @__PURE__ */ new Map();
|
|
57159
|
+
merge_borrow_info(merged, info);
|
|
57160
|
+
}
|
|
57161
|
+
return merged;
|
|
57162
|
+
}
|
|
57163
|
+
/** Fold one borrow into the per-owner map, keeping the tightest depth. */
|
|
57164
|
+
function merge_borrow_info(map, info) {
|
|
57165
|
+
const key = info.owner ?? "";
|
|
57166
|
+
const existing = map.get(key);
|
|
57167
|
+
if (!existing) {
|
|
57168
|
+
map.set(key, { ...info });
|
|
57169
|
+
return;
|
|
57170
|
+
}
|
|
57171
|
+
if (info.depth !== void 0 && (existing.depth === void 0 || info.depth < existing.depth)) existing.depth = info.depth;
|
|
57172
|
+
}
|
|
57173
|
+
/**
|
|
57174
|
+
* Record that view values were stored into fields of the struct variable
|
|
57175
|
+
* `root`: the instance now carries borrows rooted at each owner with the
|
|
57176
|
+
* given scope depths. Depth tracking keeps the tightest (deepest-rooted)
|
|
57177
|
+
* borrow so escape checks stay conservative across multiple fields. Also
|
|
57178
|
+
* clears any stale invalidation for the root — the fresh store re-points a
|
|
57179
|
+
* field (approximation: any view-field store refreshes the whole instance).
|
|
57180
|
+
*/
|
|
57181
|
+
function merge_view_borrows_into_var(root, infos, status) {
|
|
57182
|
+
if (!root || !infos || infos.size === 0) return;
|
|
57183
|
+
const sv = status.values.findLast((v) => v.name === root);
|
|
57184
|
+
if (!sv) return;
|
|
57185
|
+
sv.has_view_borrows = true;
|
|
57186
|
+
if (!sv.view_field_owners) sv.view_field_owners = /* @__PURE__ */ new Set();
|
|
57187
|
+
for (const [owner, info] of infos) {
|
|
57188
|
+
if (owner) sv.view_field_owners.add(owner);
|
|
57189
|
+
if (info.depth !== void 0) sv.borrow_depth = sv.borrow_depth === void 0 ? info.depth : Math.min(sv.borrow_depth, info.depth);
|
|
57190
|
+
}
|
|
57191
|
+
status.invalidated_view_structs?.delete(root);
|
|
57192
|
+
}
|
|
57193
|
+
/**
|
|
57194
|
+
* Propagate view-field borrow state when a struct value is copied
|
|
57195
|
+
* (`b = a`, `var Line b = a`): the copy aliases the same sources, so it
|
|
57196
|
+
* depends on exactly the same owners and inherits the tightest depth.
|
|
57197
|
+
*/
|
|
57198
|
+
function propagate_view_borrows(target, source) {
|
|
57199
|
+
if (!source.has_view_borrows) return;
|
|
57200
|
+
target.has_view_borrows = true;
|
|
57201
|
+
if (source.view_field_owners?.size) {
|
|
57202
|
+
if (!target.view_field_owners) target.view_field_owners = /* @__PURE__ */ new Set();
|
|
57203
|
+
for (const owner of source.view_field_owners) target.view_field_owners.add(owner);
|
|
57204
|
+
}
|
|
57205
|
+
if (source.borrow_depth !== void 0 && (target.borrow_depth === void 0 || source.borrow_depth < target.borrow_depth)) target.borrow_depth = source.borrow_depth;
|
|
57206
|
+
}
|
|
57207
|
+
/**
|
|
57208
|
+
* Whether every recorded view-field borrow of the value roots at `self`
|
|
57209
|
+
* (the receiver) — the same re-rooting convention that lets a `slice`
|
|
57210
|
+
* method return its view: the caller re-roots the borrow at the call-site
|
|
57211
|
+
* receiver.
|
|
57212
|
+
*/
|
|
57213
|
+
function view_borrows_root_at_self(sv) {
|
|
57214
|
+
const owners = sv.view_field_owners;
|
|
57215
|
+
return !!owners?.size && [...owners].every((o) => o === "self");
|
|
57216
|
+
}
|
|
57217
|
+
/**
|
|
57218
|
+
* Whether a value of this type can carry a non-mv view borrow: the type
|
|
57219
|
+
* itself is a `view`, a `ref` (a borrow by definition — keep the existing
|
|
57220
|
+
* taint), a struct declaring (transitively) `view T` fields, or a generic
|
|
57221
|
+
* instantiation with such an argument. Anything else (primitives, owned
|
|
57222
|
+
* strings, containers of owned values) owns its storage outright, so a call
|
|
57223
|
+
* producing it does not propagate its view arguments' borrows — tainting it
|
|
57224
|
+
* is a false positive (e.g. returning `slice_string(view)` from a function
|
|
57225
|
+
* returning owned `string`). Callers pass an unset/unknown type through as
|
|
57226
|
+
* carrying, preserving today's behavior where nothing is known.
|
|
57227
|
+
*/
|
|
57228
|
+
function type_can_carry_view_borrow(type, status) {
|
|
57229
|
+
if (!type || !type.name) return true;
|
|
57230
|
+
if (type.is_view || type.is_ref) return true;
|
|
57231
|
+
if (struct_has_view_fields(type.name, status)) return true;
|
|
57232
|
+
return (type.type_args ?? []).some((t) => type_can_carry_view_borrow(t, status));
|
|
57233
|
+
}
|
|
57234
|
+
/**
|
|
57235
|
+
* Whether reading view fields of `root` is currently rejected because their
|
|
57236
|
+
* source was mutated (reassigned / ref-mutated).
|
|
57237
|
+
*/
|
|
57238
|
+
function view_fields_invalidated(root, status) {
|
|
57239
|
+
if (!root) return false;
|
|
57240
|
+
return !!status.invalidated_view_structs?.has(root);
|
|
57241
|
+
}
|
|
57242
|
+
//#endregion
|
|
55829
57243
|
//#region ../src/check/utils/anon_enum.ts
|
|
55830
57244
|
/**
|
|
55831
57245
|
* Canonical, order-independent name for an auto-generated anonymous enum
|
|
@@ -55863,6 +57277,8 @@ function contains_unresolved_param(t, status) {
|
|
|
55863
57277
|
*/
|
|
55864
57278
|
function get_or_create_anon_enum(cases, status) {
|
|
55865
57279
|
if (cases.some((c) => c.types.some((t) => contains_unresolved_param(t, status)))) return null;
|
|
57280
|
+
for (const c of cases) for (const t of c.types) if (t.is_view) add_error(status, `enum case payloads cannot be 'view' — materialize the value ('.to_string()') or re-slice from the source on use`, t.start ?? 0);
|
|
57281
|
+
else if (t.name && struct_has_view_fields(t.name, status)) add_error(status, `enum case payloads cannot be '${t.name}' — structs with 'view' fields are scope-local; store the fields (the views) instead`, t.start ?? 0);
|
|
55866
57282
|
const sorted = [...cases].sort((a, b) => a.name.localeCompare(b.name));
|
|
55867
57283
|
const name = anon_enum_name(sorted);
|
|
55868
57284
|
const root = status.stack[0];
|
|
@@ -56084,22 +57500,27 @@ function spawn_arg_region_class(arg_type, moved, status) {
|
|
|
56084
57500
|
* (`nursery_frozen_roots`): `check_async_block_node` pushes one, block
|
|
56085
57501
|
* clones SHARE the innermost set (the freeze lasts to the block exit, not
|
|
56086
57502
|
* to the construction's branch exit), and the set dies with the block's
|
|
56087
|
-
* cloned status — the enclosing scope never sees it.
|
|
57503
|
+
* cloned status — the enclosing scope never sees it. Each entry records the
|
|
57504
|
+
* spawn that lent the root (the task label), so the write gate can name it.
|
|
56088
57505
|
*/
|
|
56089
|
-
function freeze_root(root, status) {
|
|
57506
|
+
function freeze_root(root, status, task_label) {
|
|
56090
57507
|
if (!status.nursery_frozen_roots?.length) return;
|
|
56091
|
-
status.nursery_frozen_roots[status.nursery_frozen_roots.length - 1].
|
|
57508
|
+
status.nursery_frozen_roots[status.nursery_frozen_roots.length - 1].set(root, task_label);
|
|
56092
57509
|
}
|
|
56093
57510
|
/**
|
|
56094
|
-
* The R3 write gate: `'m' is borrowed by
|
|
56095
|
-
* exits`. Called from every mutation path with the ROOT
|
|
56096
|
-
* Returns true when the name is frozen (the error has been
|
|
57511
|
+
* The R3 write gate: `'m' is borrowed by the 'worker' spawn until this
|
|
57512
|
+
* 'async' block exits`. Called from every mutation path with the ROOT
|
|
57513
|
+
* variable's name. Returns true when the name is frozen (the error has been
|
|
57514
|
+
* added). The message names the spawn that lent the root — the disclosure
|
|
57515
|
+
* is retroactive (the lend itself rides the unspelled read-only default
|
|
57516
|
+
* flavor), so it must at least be precise.
|
|
56097
57517
|
*/
|
|
56098
57518
|
function enforce_root_not_frozen(name, status, start) {
|
|
56099
57519
|
const frames = status.nursery_frozen_roots;
|
|
56100
57520
|
if (!frames?.length) return false;
|
|
56101
|
-
|
|
56102
|
-
|
|
57521
|
+
const lender = frames[frames.length - 1].get(name);
|
|
57522
|
+
if (lender !== void 0) {
|
|
57523
|
+
add_error(status, `'${name}' is borrowed by ${lender} until this 'async' block exits — it is read-only here (mv a copy in, or mv it in at the spawn)`, start);
|
|
56103
57524
|
return true;
|
|
56104
57525
|
}
|
|
56105
57526
|
return false;
|
|
@@ -56109,7 +57530,7 @@ function enforce_root_not_frozen(name, status, start) {
|
|
|
56109
57530
|
* (shared class/trait since tranche 1; mv value structs and views since
|
|
56110
57531
|
* R1). Called from the Sendable validation site.
|
|
56111
57532
|
*/
|
|
56112
|
-
function enforce_spawn_arg_region(param, arg_type, status, is_ref_borrow = false) {
|
|
57533
|
+
function enforce_spawn_arg_region(param, arg_type, status, is_ref_borrow = false, task_label = "a task") {
|
|
56113
57534
|
if ((status.nursery_depth ?? 0) === 0) return;
|
|
56114
57535
|
if ((is_ref_borrow ? "borrow" : spawn_arg_region_class(arg_type, false, status)) !== "borrow") return;
|
|
56115
57536
|
const join_depth = innermost_nursery_depth(status);
|
|
@@ -56127,7 +57548,7 @@ function enforce_spawn_arg_region(param, arg_type, status, is_ref_borrow = false
|
|
|
56127
57548
|
else add_error(status, `this task shares a temporary ${arg_type.name ?? ""} that dies before the 'async' join — bind it to a local in the 'async' block's scope and pass the local`, param.start);
|
|
56128
57549
|
return;
|
|
56129
57550
|
}
|
|
56130
|
-
if (root !== void 0 && arg_type.name !== "Task") freeze_root(root, status);
|
|
57551
|
+
if (root !== void 0 && arg_type.name !== "Task") freeze_root(root, status, task_label);
|
|
56131
57552
|
}
|
|
56132
57553
|
/**
|
|
56133
57554
|
* N2 / R4 — a detached task owns its captures. Called from the access-func
|
|
@@ -56208,178 +57629,6 @@ function boxing_owns_error() {
|
|
|
56208
57629
|
return `boxing owns its instance — mv the value, or copy it ('.copy()')`;
|
|
56209
57630
|
}
|
|
56210
57631
|
//#endregion
|
|
56211
|
-
//#region ../src/check/utils/view_fields.ts
|
|
56212
|
-
/**
|
|
56213
|
-
* Whether the named struct (or any value struct embedded in its fields,
|
|
56214
|
-
* recursively) declares a `view T` field. A struct with view fields carries
|
|
56215
|
-
* non-mv borrows inside its bytes, which drives the borrow rules:
|
|
56216
|
-
* copies are sound (a pair copy aliases nothing owned), but escapes
|
|
56217
|
-
* (returning, outer-scope assignment) are checked against where those
|
|
56218
|
-
* borrows were taken.
|
|
56219
|
-
*/
|
|
56220
|
-
function struct_has_view_fields(type_name, status) {
|
|
56221
|
-
if (!type_name) return false;
|
|
56222
|
-
const struct = status.structs.find((s) => s.name === type_name && !s.is_simple_type);
|
|
56223
|
-
return !!struct && has_view_fields(struct, status, /* @__PURE__ */ new Set());
|
|
56224
|
-
}
|
|
56225
|
-
function has_view_fields(struct, status, visited) {
|
|
56226
|
-
if (visited.has(struct.name)) return false;
|
|
56227
|
-
visited.add(struct.name);
|
|
56228
|
-
for (const field of struct.fields) {
|
|
56229
|
-
if (field.type.is_view) return true;
|
|
56230
|
-
if (field.type.is_ref || field.type.is_array) continue;
|
|
56231
|
-
const field_struct = status.structs.find((s) => s.name === field.type.name && !s.is_simple_type && !s.is_class);
|
|
56232
|
-
if (field_struct && has_view_fields(field_struct, status, visited)) return true;
|
|
56233
|
-
}
|
|
56234
|
-
return false;
|
|
56235
|
-
}
|
|
56236
|
-
/**
|
|
56237
|
-
* The root variable an access chain bottoms out at (`line.text` → "line",
|
|
56238
|
-
* `a.b.c` → "a", bare `x` → "x"). Returns undefined when the chain does not
|
|
56239
|
-
* bottom out in a plain name (e.g. a call receiver).
|
|
56240
|
-
*/
|
|
56241
|
-
function root_var_of(node) {
|
|
56242
|
-
if (node.node_type === "value") {
|
|
56243
|
-
const name = node.value;
|
|
56244
|
-
return name === "null" ? void 0 : name;
|
|
56245
|
-
}
|
|
56246
|
-
if (node.node_type === "access") return root_var_of(node.target);
|
|
56247
|
-
}
|
|
56248
|
-
/**
|
|
56249
|
-
* Where a view value stored into a field borrows from.
|
|
56250
|
-
*
|
|
56251
|
-
* - A `.slice()`-style view expression: borrow_depth/borrow_owner give the
|
|
56252
|
-
* scope depth and owner variable (the slice's receiver).
|
|
56253
|
-
* - A bare variable of owned type (`view string f = doc`): borrowing it is
|
|
56254
|
-
* the identity on fat strings, but it creates a real dependency — record
|
|
56255
|
-
* the source's own declaration depth and name so reassignment of the
|
|
56256
|
-
* source invalidates the field.
|
|
56257
|
-
* - A literal / const: static storage that outlives everything — no borrow.
|
|
56258
|
-
*
|
|
56259
|
-
* Returns undefined for sources that impose no dependency.
|
|
56260
|
-
*/
|
|
56261
|
-
function view_source_borrow_info(node, status) {
|
|
56262
|
-
if (node.node_type === "value" && node.value.startsWith("\"")) return void 0;
|
|
56263
|
-
const depth = borrow_depth_of(node, status);
|
|
56264
|
-
const owner = borrow_owner_of(node, status);
|
|
56265
|
-
if (depth !== void 0 || owner !== void 0) return {
|
|
56266
|
-
depth: depth ?? status.scope_depth,
|
|
56267
|
-
owner
|
|
56268
|
-
};
|
|
56269
|
-
if (node.node_type === "value") {
|
|
56270
|
-
const decl = status.values.findLast((v) => v.name === node.value);
|
|
56271
|
-
if (decl && !decl.is_null) return {
|
|
56272
|
-
depth: decl.decl_depth ?? 0,
|
|
56273
|
-
owner: decl.name
|
|
56274
|
-
};
|
|
56275
|
-
const alloc = status.allocations.findLast((d) => d.name === node.value);
|
|
56276
|
-
if (alloc?.value) return view_source_borrow_info(alloc.value, status);
|
|
56277
|
-
}
|
|
56278
|
-
}
|
|
56279
|
-
/**
|
|
56280
|
-
* Borrow info contributed by a constructor call's `view T` arguments (e.g.
|
|
56281
|
-
* `Line(doc.slice(0, 5), 0, 5)`): merges every view-typed argument's borrow
|
|
56282
|
-
* into one entry per distinct owner (the tightest depth wins). Returns
|
|
56283
|
-
* undefined when the call passes no dependent views.
|
|
56284
|
-
*/
|
|
56285
|
-
function ctor_call_view_borrow(call, status) {
|
|
56286
|
-
const indices = call.view_param_indices ?? [];
|
|
56287
|
-
let merged;
|
|
56288
|
-
for (const i of indices) {
|
|
56289
|
-
let arg = call.params[i];
|
|
56290
|
-
if (!arg) continue;
|
|
56291
|
-
if (arg.node_type === "value") {
|
|
56292
|
-
const name = arg.value;
|
|
56293
|
-
const hoisted = call.allocations?.findLast((d) => d.name === name) ?? status.allocations.findLast((d) => d.name === name);
|
|
56294
|
-
if (hoisted?.value) arg = hoisted.value;
|
|
56295
|
-
}
|
|
56296
|
-
const info = view_source_borrow_info(arg, status);
|
|
56297
|
-
if (!info) continue;
|
|
56298
|
-
if (!merged) merged = /* @__PURE__ */ new Map();
|
|
56299
|
-
merge_borrow_info(merged, info);
|
|
56300
|
-
}
|
|
56301
|
-
return merged;
|
|
56302
|
-
}
|
|
56303
|
-
/** Fold one borrow into the per-owner map, keeping the tightest depth. */
|
|
56304
|
-
function merge_borrow_info(map, info) {
|
|
56305
|
-
const key = info.owner ?? "";
|
|
56306
|
-
const existing = map.get(key);
|
|
56307
|
-
if (!existing) {
|
|
56308
|
-
map.set(key, { ...info });
|
|
56309
|
-
return;
|
|
56310
|
-
}
|
|
56311
|
-
if (info.depth !== void 0 && (existing.depth === void 0 || info.depth < existing.depth)) existing.depth = info.depth;
|
|
56312
|
-
}
|
|
56313
|
-
/**
|
|
56314
|
-
* Record that view values were stored into fields of the struct variable
|
|
56315
|
-
* `root`: the instance now carries borrows rooted at each owner with the
|
|
56316
|
-
* given scope depths. Depth tracking keeps the tightest (deepest-rooted)
|
|
56317
|
-
* borrow so escape checks stay conservative across multiple fields. Also
|
|
56318
|
-
* clears any stale invalidation for the root — the fresh store re-points a
|
|
56319
|
-
* field (approximation: any view-field store refreshes the whole instance).
|
|
56320
|
-
*/
|
|
56321
|
-
function merge_view_borrows_into_var(root, infos, status) {
|
|
56322
|
-
if (!root || !infos || infos.size === 0) return;
|
|
56323
|
-
const sv = status.values.findLast((v) => v.name === root);
|
|
56324
|
-
if (!sv) return;
|
|
56325
|
-
sv.has_view_borrows = true;
|
|
56326
|
-
if (!sv.view_field_owners) sv.view_field_owners = /* @__PURE__ */ new Set();
|
|
56327
|
-
for (const [owner, info] of infos) {
|
|
56328
|
-
if (owner) sv.view_field_owners.add(owner);
|
|
56329
|
-
if (info.depth !== void 0) sv.borrow_depth = sv.borrow_depth === void 0 ? info.depth : Math.min(sv.borrow_depth, info.depth);
|
|
56330
|
-
}
|
|
56331
|
-
status.invalidated_view_structs?.delete(root);
|
|
56332
|
-
}
|
|
56333
|
-
/**
|
|
56334
|
-
* Propagate view-field borrow state when a struct value is copied
|
|
56335
|
-
* (`b = a`, `var Line b = a`): the copy aliases the same sources, so it
|
|
56336
|
-
* depends on exactly the same owners and inherits the tightest depth.
|
|
56337
|
-
*/
|
|
56338
|
-
function propagate_view_borrows(target, source) {
|
|
56339
|
-
if (!source.has_view_borrows) return;
|
|
56340
|
-
target.has_view_borrows = true;
|
|
56341
|
-
if (source.view_field_owners?.size) {
|
|
56342
|
-
if (!target.view_field_owners) target.view_field_owners = /* @__PURE__ */ new Set();
|
|
56343
|
-
for (const owner of source.view_field_owners) target.view_field_owners.add(owner);
|
|
56344
|
-
}
|
|
56345
|
-
if (source.borrow_depth !== void 0 && (target.borrow_depth === void 0 || source.borrow_depth < target.borrow_depth)) target.borrow_depth = source.borrow_depth;
|
|
56346
|
-
}
|
|
56347
|
-
/**
|
|
56348
|
-
* Whether every recorded view-field borrow of the value roots at `self`
|
|
56349
|
-
* (the receiver) — the same re-rooting convention that lets a `slice`
|
|
56350
|
-
* method return its view: the caller re-roots the borrow at the call-site
|
|
56351
|
-
* receiver.
|
|
56352
|
-
*/
|
|
56353
|
-
function view_borrows_root_at_self(sv) {
|
|
56354
|
-
const owners = sv.view_field_owners;
|
|
56355
|
-
return !!owners?.size && [...owners].every((o) => o === "self");
|
|
56356
|
-
}
|
|
56357
|
-
/**
|
|
56358
|
-
* Whether a value of this type can carry a non-mv view borrow: the type
|
|
56359
|
-
* itself is a `view`, a `ref` (a borrow by definition — keep the existing
|
|
56360
|
-
* taint), a struct declaring (transitively) `view T` fields, or a generic
|
|
56361
|
-
* instantiation with such an argument. Anything else (primitives, owned
|
|
56362
|
-
* strings, containers of owned values) owns its storage outright, so a call
|
|
56363
|
-
* producing it does not propagate its view arguments' borrows — tainting it
|
|
56364
|
-
* is a false positive (e.g. returning `slice_string(view)` from a function
|
|
56365
|
-
* returning owned `string`). Callers pass an unset/unknown type through as
|
|
56366
|
-
* carrying, preserving today's behavior where nothing is known.
|
|
56367
|
-
*/
|
|
56368
|
-
function type_can_carry_view_borrow(type, status) {
|
|
56369
|
-
if (!type || !type.name) return true;
|
|
56370
|
-
if (type.is_view || type.is_ref) return true;
|
|
56371
|
-
if (struct_has_view_fields(type.name, status)) return true;
|
|
56372
|
-
return (type.type_args ?? []).some((t) => type_can_carry_view_borrow(t, status));
|
|
56373
|
-
}
|
|
56374
|
-
/**
|
|
56375
|
-
* Whether reading view fields of `root` is currently rejected because their
|
|
56376
|
-
* source was mutated (reassigned / ref-mutated).
|
|
56377
|
-
*/
|
|
56378
|
-
function view_fields_invalidated(root, status) {
|
|
56379
|
-
if (!root) return false;
|
|
56380
|
-
return !!status.invalidated_view_structs?.has(root);
|
|
56381
|
-
}
|
|
56382
|
-
//#endregion
|
|
56383
57632
|
//#region ../src/check/check_declaration_node.ts
|
|
56384
57633
|
/**
|
|
56385
57634
|
* Record the may-be-null state of a freshly-declared struct's nullable
|
|
@@ -56563,9 +57812,16 @@ function check_declaration_node(decl, status) {
|
|
|
56563
57812
|
if (!is_view_keyword && !declared_type_is_view && decl.type.is_view && decl.value) add_error(status, `binding a view requires the 'view' keyword (e.g. 'view name = ...' or 'var view name = ...')`, decl.value.start);
|
|
56564
57813
|
const ref_src = is_ref_keyword ? ref_alias_source_name(decl.value) : void 0;
|
|
56565
57814
|
if (is_ref_keyword) {
|
|
56566
|
-
|
|
56567
|
-
if (
|
|
56568
|
-
|
|
57815
|
+
const slot_load = element_slot_load(decl.value, status);
|
|
57816
|
+
if (slot_load) {
|
|
57817
|
+
slot_load.name = "slot_ptr";
|
|
57818
|
+
slot_load.params = [slot_load.params[0]];
|
|
57819
|
+
slot_load.is_element_slot_load = true;
|
|
57820
|
+
} else {
|
|
57821
|
+
if (decl.type.name && !is_class_type$1(decl.type.name, status)) add_error(status, `'ref' binds an alias to a heap-resident instance (a Box<T>); use 'view' for a slice of '${decl.type.name}'`, decl.start);
|
|
57822
|
+
if (decl.value && !ref_src && decl.value.node_type === "value") add_error(status, `'ref' requires an existing binding to alias, got an owned value`, decl.value.start);
|
|
57823
|
+
if (decl.value && !ref_src && decl.value.node_type !== "value") add_error(status, `'ref' requires an existing binding to alias, got an owned value`, decl.value.start);
|
|
57824
|
+
}
|
|
56569
57825
|
if (decl.value && decl.value.is_moved) add_error(status, `'ref' binds an alias — 'mv' cannot transfer through it`, decl.value.start);
|
|
56570
57826
|
}
|
|
56571
57827
|
if (declaration === "mv" && !decl.swap && decl.value?.node_type === "value" && !decl.value.is_moved) decl.value.is_moved = true;
|
|
@@ -56592,7 +57848,10 @@ function check_declaration_node(decl, status) {
|
|
|
56592
57848
|
if (!decl.value.is_moved && !temp_rhs) add_error(status, `cannot copy '${field_type.name}' out of field '${field_name}' by value — it owns heap resources; use 'mv ... swap <replacement>'${field_type.name === "string" ? ", or .copy() for a deep copy" : ""}`, decl.value.start);
|
|
56593
57849
|
else if (!decl.swap) {
|
|
56594
57850
|
if (!access_field.destructure_move && !temp_rhs) add_error(status, `mv out of a field requires a swap to revalidate it`, decl.value.start);
|
|
56595
|
-
else {
|
|
57851
|
+
else if (temp_rhs && access_field.destructure_move && field_type.name === "string" && !field_type.is_array && !field_type.is_view) {
|
|
57852
|
+
decl.value.is_moved = void 0;
|
|
57853
|
+
access_field.destructure_move = void 0;
|
|
57854
|
+
} else {
|
|
56596
57855
|
decl.value.is_moved = true;
|
|
56597
57856
|
decl.swap = synthesized_swap_value(field_type, decl.value.start);
|
|
56598
57857
|
if (check_node(decl.swap, status)) check_type_and_value_match(field_type, type_from_value_node(decl.swap, status), void 0, status, decl.swap.start, "swap");
|
|
@@ -56803,6 +58062,23 @@ function ref_alias_source_name(value) {
|
|
|
56803
58062
|
return value_from_value_node(value) || void 0;
|
|
56804
58063
|
}
|
|
56805
58064
|
}
|
|
58065
|
+
/**
|
|
58066
|
+
* The ELEMENT BORROW shape (docs/HANDLERS.md Stage 4): a ref-binding
|
|
58067
|
+
* initializer that is `container.at(i)` — a container element read whose
|
|
58068
|
+
* container exposes the `slot_ptr` primitive (the `List<T>` mono). Returns
|
|
58069
|
+
* the inner AccessFunctionCallNode to rewrite, or undefined when the
|
|
58070
|
+
* initializer isn't an element load.
|
|
58071
|
+
*/
|
|
58072
|
+
function element_slot_load(value, status) {
|
|
58073
|
+
if (!value || value.node_type !== "access") return void 0;
|
|
58074
|
+
const access = value;
|
|
58075
|
+
if (access.access.node_type !== "access_func") return void 0;
|
|
58076
|
+
const call = access.access;
|
|
58077
|
+
if (call.name !== "at" || call.params.length !== 1) return void 0;
|
|
58078
|
+
const target_type = type_from_value_node(access.target, status);
|
|
58079
|
+
if (!status.structs.find((s) => s.name === target_type.name || s.name === mono_type_name(target_type))?.functions.some((f) => f.name === "slot_ptr")) return void 0;
|
|
58080
|
+
return call;
|
|
58081
|
+
}
|
|
56806
58082
|
//#endregion
|
|
56807
58083
|
//#region ../src/check/utils/check_init_fields.ts
|
|
56808
58084
|
const checked_structs = /* @__PURE__ */ new WeakSet();
|
|
@@ -57563,6 +58839,28 @@ function struct_copy_ctor_args(struct) {
|
|
|
57563
58839
|
if (!auto) return null;
|
|
57564
58840
|
return auto.params.filter((p) => !p.is_self_param).map((p) => p.name);
|
|
57565
58841
|
}
|
|
58842
|
+
/**
|
|
58843
|
+
* The element type args of a mv-container type (`List<T>` / `Buffer<T>` /
|
|
58844
|
+
* `Set<T>` / `Map<K,V>`), in either spelling: the generic annotation
|
|
58845
|
+
* (`Map<string, int>` — type_args on the type, the shape seen by the derive
|
|
58846
|
+
* pre-pass which runs before per-field mono rewriting) or the monomorphized
|
|
58847
|
+
* name (`Map_string_int` — the registered mono carries the flattened args).
|
|
58848
|
+
* Undefined for non-container types.
|
|
58849
|
+
*/
|
|
58850
|
+
function container_element_args(type, status) {
|
|
58851
|
+
if (!type.name || type.is_array || type.is_ref || type.is_view || type.is_nullable) return;
|
|
58852
|
+
if (type.type_args?.length) {
|
|
58853
|
+
if ([
|
|
58854
|
+
"List",
|
|
58855
|
+
"Buffer",
|
|
58856
|
+
"Set",
|
|
58857
|
+
"Map"
|
|
58858
|
+
].includes(type.name)) return type.type_args;
|
|
58859
|
+
return;
|
|
58860
|
+
}
|
|
58861
|
+
const mono = status.structs.find((s) => s.name === type.name && !s.is_generic);
|
|
58862
|
+
if (mono && is_load_deep_copy_container(mono.name) && mono.source_type_args?.length) return mono.source_type_args;
|
|
58863
|
+
}
|
|
57566
58864
|
/** Whether `type` has (or will get) a deep-copy story: a struct with a
|
|
57567
58865
|
* synthesized `copy`, a `List<T>` whose element is deep-copyable (the
|
|
57568
58866
|
* library `List.copy` deep-copies slots via the mv store contract), or
|
|
@@ -57575,6 +58873,8 @@ function field_copyable(type, status, visited) {
|
|
|
57575
58873
|
if (visited?.has(type.name)) return false;
|
|
57576
58874
|
if (is_box_type(type.name, status.structs)) return false;
|
|
57577
58875
|
if (type.name === "List" && type.type_args?.length === 1) return element_copyable(type.type_args[0], status);
|
|
58876
|
+
const elem_args = container_element_args(type, status);
|
|
58877
|
+
if (elem_args) return elem_args.every((arg) => element_copyable(arg, status));
|
|
57578
58878
|
const nested_struct = status.structs.find((s) => s.name === type.name);
|
|
57579
58879
|
if (nested_struct) {
|
|
57580
58880
|
if (nested_struct.is_simple_type) return true;
|
|
@@ -57593,6 +58893,8 @@ function element_copyable(type, status) {
|
|
|
57593
58893
|
if (!type.name || type.is_array || type.is_ref || type.is_view || type.is_nullable) return false;
|
|
57594
58894
|
if (type.name === "string") return true;
|
|
57595
58895
|
if (type.name === "List" && type.type_args?.length === 1) return element_copyable(type.type_args[0], status);
|
|
58896
|
+
const elem_args = container_element_args(type, status);
|
|
58897
|
+
if (elem_args) return elem_args.every((arg) => element_copyable(arg, status));
|
|
57596
58898
|
const nested = status.structs.find((s) => s.name === type.name);
|
|
57597
58899
|
if (nested) {
|
|
57598
58900
|
if (nested.is_simple_type) return true;
|
|
@@ -57609,6 +58911,8 @@ function element_copyable(type, status) {
|
|
|
57609
58911
|
function field_copy_call(type, status) {
|
|
57610
58912
|
if (!type.name || type.is_array || type.is_ref || type.is_view || type.is_nullable) return false;
|
|
57611
58913
|
if (type.name === "List" && type.type_args?.length === 1) return element_copyable(type.type_args[0], status);
|
|
58914
|
+
const elem_args = container_element_args(type, status);
|
|
58915
|
+
if (elem_args) return elem_args.every((arg) => element_copyable(arg, status));
|
|
57612
58916
|
const nested_struct = status.structs.find((s) => s.name === type.name);
|
|
57613
58917
|
return !!nested_struct && !nested_struct.is_simple_type && struct_is_copyable(nested_struct, status);
|
|
57614
58918
|
}
|
|
@@ -58139,7 +59443,7 @@ function check_function_parameter_node(param, status, is_library_scope = false)
|
|
|
58139
59443
|
}
|
|
58140
59444
|
if (status.values.some((v) => v.name === param.name && v.declaration !== "const" && !(is_library_scope && v.is_global))) add_error(status, `Parameter already declared: ${param.name}`, param.start);
|
|
58141
59445
|
status.values.push({
|
|
58142
|
-
declaration: param.declaration === "var" || is_pass_by_value_owning_struct(param.type, status) ? "var" : param.declaration,
|
|
59446
|
+
declaration: param.declaration === "var" || param.is_variadic || is_pass_by_value_owning_struct(param.type, status) ? "var" : param.declaration,
|
|
58143
59447
|
name: param.name,
|
|
58144
59448
|
type: param.type,
|
|
58145
59449
|
is_set: true,
|
|
@@ -58272,6 +59576,8 @@ function check_function_node(func, status) {
|
|
|
58272
59576
|
}
|
|
58273
59577
|
if (func.return_type.name && !foreign_supported_type(func.return_type)) add_error(status, `foreign return type '${func.return_type.name}' is unsupported (scalars and string only)`, func.return_type_start ?? func.start);
|
|
58274
59578
|
}
|
|
59579
|
+
const mixed_body = find_use_c_mixed_body(func);
|
|
59580
|
+
if (mixed_body) add_error(status, mixed_body.dropped === "statement" ? `Function '${func.name}' mixes '#arch: aarch64_use_c' blocks with Nomen statements — the aarch64 backend replaces the whole body with the raw blocks and would drop the statements` : mixed_body.dropped === "raw asm" ? `Function '${func.name}' mixes '#arch: aarch64_use_c' blocks with inline raw code — the aarch64 backend replaces the whole body with the raw blocks and would drop the assembly` : `Function '${func.name}' has a '#arch: aarch64_use_c' block nested inside control flow — the aarch64 backend emits the body as assembly and silently drops block-scope C; move the block into its own aarch64_use_c function and pass values as parameters`, mixed_body.start);
|
|
58275
59581
|
if (is_generic_func(func)) {
|
|
58276
59582
|
func.is_generic = true;
|
|
58277
59583
|
return;
|
|
@@ -58294,6 +59600,10 @@ function check_function_node(func, status) {
|
|
|
58294
59600
|
if (func.return_type.is_pointer && !func.is_library) add_error(function_status, `'ptr' return types are reserved for the System library — raw pointer manipulation is not available to user code`, func.return_type_start ?? func.start);
|
|
58295
59601
|
}
|
|
58296
59602
|
instantiate_generic_type(func.return_type, function_status);
|
|
59603
|
+
if (func.name !== "#init" && !func.return_type.is_view && !func.return_type.is_ref && struct_has_view_fields(func.return_type.name, function_status)) {
|
|
59604
|
+
add_error(function_status, `cannot return a '${func.return_type.name}' — structs with 'view' fields are scope-local; return the fields (the views) instead`, func.return_type_start ?? func.start);
|
|
59605
|
+
func.view_bundle_return_rejected = true;
|
|
59606
|
+
}
|
|
58297
59607
|
}
|
|
58298
59608
|
if (func.is_foreign) return;
|
|
58299
59609
|
check_block_node(func, function_status);
|
|
@@ -58524,6 +59834,8 @@ function synthesize_lambda_name(func, status) {
|
|
|
58524
59834
|
*/
|
|
58525
59835
|
function validate_spawn_args_sendable(call, status, reason) {
|
|
58526
59836
|
const callee_params = call.resolved_function?.params?.filter((p) => !p.is_self_param);
|
|
59837
|
+
const callee_name = call.resolved_function?.name;
|
|
59838
|
+
const task_label = callee_name ? `the '${callee_name}' spawn` : "a spawn";
|
|
58527
59839
|
for (let i = 0; i < call.params.length; i++) {
|
|
58528
59840
|
const param = call.params[i];
|
|
58529
59841
|
let arg_type = type_from_value_node(param, status);
|
|
@@ -58536,7 +59848,7 @@ function validate_spawn_args_sendable(call, status, reason) {
|
|
|
58536
59848
|
if (status.structs.find((st_) => st_.name === arg_type.name && !st_.is_class && !st_.is_simple_type)) {
|
|
58537
59849
|
if (!call.region_borrowed_args) call.region_borrowed_args = [];
|
|
58538
59850
|
if (!call.region_borrowed_args.includes(i)) call.region_borrowed_args.push(i);
|
|
58539
|
-
enforce_spawn_arg_region(param, arg_type, status, true);
|
|
59851
|
+
enforce_spawn_arg_region(param, arg_type, status, true, task_label);
|
|
58540
59852
|
continue;
|
|
58541
59853
|
}
|
|
58542
59854
|
}
|
|
@@ -58550,15 +59862,15 @@ function validate_spawn_args_sendable(call, status, reason) {
|
|
|
58550
59862
|
}
|
|
58551
59863
|
if (!call.region_borrowed_args) call.region_borrowed_args = [];
|
|
58552
59864
|
if (!call.region_borrowed_args.includes(i)) call.region_borrowed_args.push(i);
|
|
58553
|
-
if (inside_nursery) enforce_spawn_arg_region(param, arg_type, status);
|
|
59865
|
+
if (inside_nursery) enforce_spawn_arg_region(param, arg_type, status, false, task_label);
|
|
58554
59866
|
continue;
|
|
58555
59867
|
}
|
|
58556
59868
|
if (inside_nursery && arg_type.is_view && !arg_type.is_array) {
|
|
58557
|
-
enforce_spawn_arg_region(param, arg_type, status);
|
|
59869
|
+
enforce_spawn_arg_region(param, arg_type, status, false, task_label);
|
|
58558
59870
|
continue;
|
|
58559
59871
|
}
|
|
58560
59872
|
if (is_sendable_type(arg_type.name, status)) {
|
|
58561
|
-
enforce_spawn_arg_region(param, arg_type, status);
|
|
59873
|
+
enforce_spawn_arg_region(param, arg_type, status, false, task_label);
|
|
58562
59874
|
continue;
|
|
58563
59875
|
}
|
|
58564
59876
|
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; mv it in (a `mv` parameter), share it as a checked region borrow inside an 'async' block, or mediate it through Mutex/Channel/Atomic<T>"}`, param.start);
|
|
@@ -58822,14 +60134,21 @@ function monomorphize(generic_struct, type_args, status) {
|
|
|
58822
60134
|
const mono_struct = new StructNode(generic_struct.start, generic_struct.visibility, mono_name, generic_struct.traits, mono_fields, []);
|
|
58823
60135
|
mono_struct.source_type_args = flat_args;
|
|
58824
60136
|
mono_struct.is_class = generic_struct.is_class;
|
|
58825
|
-
|
|
60137
|
+
const derived_owning = mono_fields.some((f) => type_owns_heap(f.type, status));
|
|
60138
|
+
mono_struct.is_owning = generic_struct.is_owning || derived_owning;
|
|
60139
|
+
if (derived_owning && !generic_struct.is_owning && !generic_struct.is_class) {
|
|
60140
|
+
const param_list = generic_struct.type_params.join(", ");
|
|
60141
|
+
add_error(status, `'${generic_struct.name}<${param_list}>' instantiates to an owning type — a substituted field owns heap memory --> mark the generic \`mv struct ${generic_struct.name}<${param_list}>\``, generic_struct.start);
|
|
60142
|
+
}
|
|
58826
60143
|
mono_struct.is_library = generic_struct.is_library;
|
|
58827
60144
|
mono_struct.trait_args = (generic_struct.trait_args ?? []).map((args) => args?.map((t) => substitute_type(t, substitution)));
|
|
58828
60145
|
status.structs.push(mono_struct);
|
|
58829
60146
|
status.types.push(mono_name);
|
|
58830
60147
|
const cloned_methods = [];
|
|
60148
|
+
const has_ref_type_arg = (generic_struct.name === "Map" || generic_struct.name === "Set") && flat_args.some((arg) => status.traits.some((tr) => tr.name === arg.name) || status.structs.some((s) => s.name === arg.name && s.is_class));
|
|
58831
60149
|
for (const func of generic_struct.functions) {
|
|
58832
60150
|
if (func.name === "#init") continue;
|
|
60151
|
+
if (func.name === "copy" && has_ref_type_arg) continue;
|
|
58833
60152
|
const cloned = clone_node(func);
|
|
58834
60153
|
substitute_raw_types(cloned, substitution, status.structs, status.traits, {
|
|
58835
60154
|
enums: status.enums,
|
|
@@ -61097,6 +62416,7 @@ function check_function_call(node, status, func, target_type, self_value, self_p
|
|
|
61097
62416
|
if ((func_param.type.name ? status.structs.find((st) => st.name === func_param.type.name && !st.is_class && !st.is_simple_type) : void 0) || func_param.type.name === "string") param.type = func_param.type;
|
|
61098
62417
|
}
|
|
61099
62418
|
const param_type = type_from_value_node(param, status);
|
|
62419
|
+
if (param_type && !param_type.is_view && !param_type.is_ref && param_type.name && struct_has_view_fields(param_type.name, status)) add_error(status, `cannot pass a '${param_type.name}' as an argument — structs with 'view' fields are scope-local; pass the fields (the views) instead`, param.start);
|
|
61100
62420
|
const param_value = value_from_value_node(param);
|
|
61101
62421
|
const has_ref_keyword = node.ref_param_indices?.includes(i) ?? false;
|
|
61102
62422
|
if (has_ref_keyword && param.node_type === "value") invalidate_token_facts(param.value, status);
|
|
@@ -61161,14 +62481,12 @@ function check_function_call(node, status, func, target_type, self_value, self_p
|
|
|
61161
62481
|
} else if (!!func_param && !!status.traits.find((t) => t.name === func_param.type.name) && func_param.is_moved && is_value_struct_conformer(field_type.name, func_param.type.name, status)) add_error(status, `boxing owns its instance — mv the value, or copy it ('.copy()')`, param.start);
|
|
61162
62482
|
}
|
|
61163
62483
|
if (func_param.is_moved && !has_mov_keyword && !node.swap_params?.has(i) && param_is_owning_struct && param.node_type === "access" && param.access.node_type === "access_field") {
|
|
61164
|
-
const
|
|
61165
|
-
|
|
61166
|
-
if (!(read_root.node_type === "access" && read_root.access.node_type === "access_func" || !!root_name && (root_name.startsWith("_recv_") || root_name.startsWith("_tuple_dst_")))) {
|
|
61167
|
-
const field_name = param.access.name;
|
|
61168
|
-
add_error(status, `cannot copy field '${field_name}' into parameter '${func_param.name}' by value — it owns heap resources; use 'mv ... swap <replacement>' or .copy()`, param.start);
|
|
61169
|
-
}
|
|
62484
|
+
const field_name = param.access.name;
|
|
62485
|
+
add_error(status, `cannot copy field '${field_name}' into parameter '${func_param.name}' by value — it owns heap resources; use 'mv ... swap <replacement>' or .copy()`, param.start);
|
|
61170
62486
|
}
|
|
61171
|
-
|
|
62487
|
+
const param_struct = func_param.type.name ? status.structs.find((s) => s.name === func_param.type.name && !s.is_simple_type && !s.is_generic) : void 0;
|
|
62488
|
+
const param_takes_ownership = func_param.type.name && is_owning_ref_type(func_param.type.name, status) || !!param_struct && !param_struct.is_class && param_struct.is_owning === true;
|
|
62489
|
+
if (func_param.is_moved && !node.swap_params?.has(i) && param_takes_ownership) {
|
|
61172
62490
|
if (borrow_depth_of(param, status) !== void 0) add_error(status, `Cannot mv a borrowed value into mv parameter '${func_param.name}' — it would create shared ownership (the destination frees the pointer on destroy, leaving the borrow's source dangling). Pass an owned value: a fresh constructor, an owned local, or a 'mv out T' accessor result (e.g. .pop() or items.move_T(i)).`, param.start);
|
|
61173
62491
|
}
|
|
61174
62492
|
const swap_expr = node.swap_params?.get(i);
|
|
@@ -61410,8 +62728,7 @@ function check_function_call(node, status, func, target_type, self_value, self_p
|
|
|
61410
62728
|
if (param.node_type === "access" && maybe_mark_borrow_to_string_arg(param, func_param, func, i, node, status)) continue;
|
|
61411
62729
|
const callee_param_is_trait = !!func_param && !!status.traits.find((t) => t.name === func_param.type.name);
|
|
61412
62730
|
if (callee_param_is_trait && !func_param.type.is_array && is_value_struct_conformer(param_type.name, func_param.type.name, status)) {
|
|
61413
|
-
if (func_param.
|
|
61414
|
-
else if (func_param.is_moved) {
|
|
62731
|
+
if (func_param.is_moved) {
|
|
61415
62732
|
if (!node.move_owned_param_indices) node.move_owned_param_indices = [];
|
|
61416
62733
|
if (!node.move_owned_param_indices.includes(i)) node.move_owned_param_indices.push(i);
|
|
61417
62734
|
}
|
|
@@ -61635,6 +62952,516 @@ function is_library_regex_func(func, status) {
|
|
|
61635
62952
|
return !!regex_struct && regex_struct.functions.includes(func);
|
|
61636
62953
|
}
|
|
61637
62954
|
//#endregion
|
|
62955
|
+
//#region ../src/check/utils/handlers.ts
|
|
62956
|
+
/**
|
|
62957
|
+
* Handler registrations: `.on_<event>(closure)` desugars into owner-dispatched
|
|
62958
|
+
* methods (docs/HANDLERS.md, Stage 1 — the root desugar).
|
|
62959
|
+
*
|
|
62960
|
+
* `self.<field>.on_click(func () { ... })` on a control receiver is
|
|
62961
|
+
* checker-interpreted (the name-keyed precedent is the Fiber.start_on
|
|
62962
|
+
* rewrite): no code executes at the site. The closure body is HOISTED into a
|
|
62963
|
+
* hidden method on the enclosing struct — `#on_click_N = (ref self, ref
|
|
62964
|
+
* Window win) { body }` — an ordinary method both backends already lower, and
|
|
62965
|
+
* the registration checks into a dispatch ARM appended to the host's
|
|
62966
|
+
* synthesized `handle_events` method:
|
|
62967
|
+
*
|
|
62968
|
+
* ```
|
|
62969
|
+
* func handle_events = (ref self, ref Window win) {
|
|
62970
|
+
* nomen_window_clicks_init(self.<field>.handle)
|
|
62971
|
+
* if self.<field>.take_click() { self.#on_click_N(ref win) }
|
|
62972
|
+
* }
|
|
62973
|
+
* ```
|
|
62974
|
+
*
|
|
62975
|
+
* `App` declares a default-empty `handle_events`; `Window.run` calls
|
|
62976
|
+
* `app.handle_events(ref win)` before `frame`, so the borrow of `self` is
|
|
62977
|
+
* taken by the dispatcher at dispatch time — the closure captures nothing
|
|
62978
|
+
* (HANDLERS.md, "The insight"). The arm's handle expression re-evaluates the
|
|
62979
|
+
* remembered field path against final fields each frame; nothing is captured,
|
|
62980
|
+
* so there is no staleness.
|
|
62981
|
+
*
|
|
62982
|
+
* Checker rules (one clear error each, HANDLERS.md "Checker rules"):
|
|
62983
|
+
* - the receiver must be a `self`-rooted field path (the arm re-evaluates
|
|
62984
|
+
* it, so a local receiver has nothing to re-evaluate);
|
|
62985
|
+
* - the closure takes no parameters (v1);
|
|
62986
|
+
* - no upvalue captures — the hoisted method has no env;
|
|
62987
|
+
* - host reachability — dispatch reaches hosts through static field paths
|
|
62988
|
+
* from the run root, so the enclosing struct must conform to `App` or be
|
|
62989
|
+
* a (transitive) field of one (library composites are trusted).
|
|
62990
|
+
*/
|
|
62991
|
+
/** The reserved member-name family on control receivers (HANDLERS.md). */
|
|
62992
|
+
const HANDLER_EVENT = /^on_[a-z][a-z0-9_]*$/;
|
|
62993
|
+
/** The one pump slot's name on handler hosts. */
|
|
62994
|
+
const PUMP_NAME = "handle_events";
|
|
62995
|
+
/**
|
|
62996
|
+
* Per-check synthesis state, keyed by the host struct (one pump per host).
|
|
62997
|
+
* Module-level (not on CheckStatus) because `clone_status` copies a fixed
|
|
62998
|
+
* field list — state recorded inside a branch clone (a registration nested
|
|
62999
|
+
* in an `if`) must survive the clone's discard. The per-check entry in
|
|
63000
|
+
* `pending_pumps` dies with the check that created it: `finalize` deletes by
|
|
63001
|
+
* root identity, and an aborted compile (parse errors, no check pass) never
|
|
63002
|
+
* creates an entry.
|
|
63003
|
+
*/
|
|
63004
|
+
const dispatcher_states = /* @__PURE__ */ new WeakMap();
|
|
63005
|
+
/** Hosts whose pump awaits its final check, per compile root. */
|
|
63006
|
+
const pending_hosts = /* @__PURE__ */ new Map();
|
|
63007
|
+
/** Per-app up-call synthesis state (see host_states for why module-level). */
|
|
63008
|
+
const run_handler_states = /* @__PURE__ */ new WeakMap();
|
|
63009
|
+
/** Apps whose synthesized `run_handler` awaits its check (see pending_hosts). */
|
|
63010
|
+
const pending_run_handlers = /* @__PURE__ */ new Map();
|
|
63011
|
+
/** Per-app tick-forward state (see host_states for why module-level). */
|
|
63012
|
+
const tick_states = /* @__PURE__ */ new WeakMap();
|
|
63013
|
+
/** Apps whose pump awaits its synthesized tick forwards (see pending_hosts). */
|
|
63014
|
+
const pending_tick_apps = /* @__PURE__ */ new Map();
|
|
63015
|
+
/**
|
|
63016
|
+
* The `.on_<event>(closure)` hook, called from check_access_function_node
|
|
63017
|
+
* BEFORE ordinary method resolution. Returns:
|
|
63018
|
+
* - `undefined` — not a handler registration (name family or receiver does
|
|
63019
|
+
* not match); the call resolves ordinarily;
|
|
63020
|
+
* - `false` — a handler registration that failed a checker rule (an error
|
|
63021
|
+
* has been added);
|
|
63022
|
+
* - `true` — desugared: the site is stamped `is_handler_registration` (the
|
|
63023
|
+
* backends emit nothing for it) and the arm is synthesized.
|
|
63024
|
+
*/
|
|
63025
|
+
function try_check_handler_registration(target_type, target, node, status) {
|
|
63026
|
+
if (node.is_handler_enrollment) return void 0;
|
|
63027
|
+
if (!HANDLER_EVENT.test(node.name)) return void 0;
|
|
63028
|
+
const receiver = status.structs.find((s) => s.name === target_type.name);
|
|
63029
|
+
if (!receiver) return void 0;
|
|
63030
|
+
const fail = (message) => {
|
|
63031
|
+
add_error(status, message, node.start);
|
|
63032
|
+
return false;
|
|
63033
|
+
};
|
|
63034
|
+
const composite = try_check_composite_event(receiver, target_type, target, node, status, fail);
|
|
63035
|
+
if (composite !== void 0) return composite;
|
|
63036
|
+
return try_check_control_drain(receiver, target_type, target, node, status, fail);
|
|
63037
|
+
}
|
|
63038
|
+
/**
|
|
63039
|
+
* The control-carried event path (docs/HANDLERS.md, "Registered control
|
|
63040
|
+
* events"): the event comes from the CONTROL's own source (the click ring,
|
|
63041
|
+
* the window's first responder, the control's text), the synthesized arm
|
|
63042
|
+
* polls that source's drain primitive, and the hidden method hoists onto
|
|
63043
|
+
* the App conformer the way the click desugar does. Config comes from
|
|
63044
|
+
* and everything here is event-agnostic.
|
|
63045
|
+
*/
|
|
63046
|
+
function try_check_control_drain(receiver, target_type, target, node, status, fail) {
|
|
63047
|
+
const drain = `take_${node.name.slice(3)}`;
|
|
63048
|
+
if (!receiver.functions.some((f) => f.name === drain && f.params.length === 1)) return fail(`'${receiver.name}' doesn't emit ${node.name} events (no ${drain} drain)`);
|
|
63049
|
+
if (node.params.length !== 1 || node.params[0].node_type !== "func") return fail(`.${node.name} expects one closure argument — .${node.name}(func () { ... })`);
|
|
63050
|
+
const closure = node.params[0];
|
|
63051
|
+
if (closure.params.length > 0) return fail("handler closures take no parameters — the body sees `self` and the dispatch `win`");
|
|
63052
|
+
const fields = self_rooted_field_path(target);
|
|
63053
|
+
if (!fields) return fail(`.${node.name} needs a self-rooted field receiver — store the control in a field and register from self.<field>.${node.name}(...): the dispatch arm re-evaluates the path, so a local receiver has nothing to re-evaluate`);
|
|
63054
|
+
const host = status.stack.findLast((n) => n.node_type === "func")?.scope;
|
|
63055
|
+
if (!host || host.node_type !== "struct") return fail(`.${node.name} registers a handler on the enclosing struct — it cannot appear outside one`);
|
|
63056
|
+
if (!host.traits.includes("App") && !host.is_library && !reachable_from_an_app(host, status)) return fail(`handlers dispatch from the run root — '${host.name}' is not owned by the app`);
|
|
63057
|
+
let state = dispatcher_states.get(host);
|
|
63058
|
+
if (!state) {
|
|
63059
|
+
if (host.functions.some((f) => f.name === PUMP_NAME)) return fail(`'${host.name}' already declares ${PUMP_NAME} — the .${node.name} desugar synthesizes it (the name is reserved on handler hosts)`);
|
|
63060
|
+
const func = new FunctionNode(node.start, "pub", PUMP_NAME, new Type(""), [ref_self_param(host), win_param()]);
|
|
63061
|
+
host.functions.push(func);
|
|
63062
|
+
state = {
|
|
63063
|
+
func,
|
|
63064
|
+
next_index: /* @__PURE__ */ new Map()
|
|
63065
|
+
};
|
|
63066
|
+
dispatcher_states.set(host, state);
|
|
63067
|
+
const root = status.stack[0];
|
|
63068
|
+
let hosts = pending_hosts.get(root);
|
|
63069
|
+
if (!hosts) {
|
|
63070
|
+
hosts = [];
|
|
63071
|
+
pending_hosts.set(root, hosts);
|
|
63072
|
+
}
|
|
63073
|
+
hosts.push(host);
|
|
63074
|
+
}
|
|
63075
|
+
let index_state = state.next_index.get(node.name);
|
|
63076
|
+
if (index_state === void 0) index_state = 1;
|
|
63077
|
+
else index_state += 1;
|
|
63078
|
+
state.next_index.set(node.name, index_state);
|
|
63079
|
+
const index = index_state;
|
|
63080
|
+
const hidden = `#${node.name}_${index}`;
|
|
63081
|
+
const hoisted = new FunctionNode(closure.start, "private", `#${node.name}_${index}`, new Type(""), [ref_self_param(host), win_param()], closure.statements);
|
|
63082
|
+
host.functions.push(hoisted);
|
|
63083
|
+
hoisted.is_closure = true;
|
|
63084
|
+
check_function_node(hoisted, status);
|
|
63085
|
+
const upvalues = hoisted.captures ?? [];
|
|
63086
|
+
hoisted.is_closure = void 0;
|
|
63087
|
+
hoisted.captures = void 0;
|
|
63088
|
+
if (upvalues.length) return fail("handler closures see `self` and the dispatch `win` — copy what you need into fields");
|
|
63089
|
+
state.func.statements.push(arm_dispatch_statement(host, fields, drain, hidden, node.start));
|
|
63090
|
+
node.is_handler_registration = true;
|
|
63091
|
+
node.type = new Type("");
|
|
63092
|
+
return true;
|
|
63093
|
+
}
|
|
63094
|
+
/**
|
|
63095
|
+
* Composite event registration (docs/HANDLERS.md Stage 3 — id up-calls).
|
|
63096
|
+
* `self.<field>.on_<event>(closure)` on a composite host — a struct that
|
|
63097
|
+
* DECLARES the event API — enrolls the app handler for the composite's
|
|
63098
|
+
* up-call:
|
|
63099
|
+
*
|
|
63100
|
+
* - the closure hoists into a hidden method on the app —
|
|
63101
|
+
* `#on_<event>_N = (ref self, ref Window win) { body }`;
|
|
63102
|
+
* - the app's synthesized `run_handler` gains the dispatch arm
|
|
63103
|
+
* `if id == N { self.#on_<event>_N(ref win) }` — the concrete method the
|
|
63104
|
+
* composite's `owner.run_handler(id, ref win)` callback hops into;
|
|
63105
|
+
* - the registration site REWRITES to the enrollment call
|
|
63106
|
+
* `receiver.on_<event>(N)` — the composite's own declared API stores the
|
|
63107
|
+
* id (the one thing tier 2 stores: an int, never a handler);
|
|
63108
|
+
* - the app's dispatch pump gains the per-frame owner forward
|
|
63109
|
+
* `self.<fields>.tick(ref self, ref win)` at finalize — registration is
|
|
63110
|
+
* register-and-forget, the app never writes the forwarding call.
|
|
63111
|
+
*
|
|
63112
|
+
* The composite opts in by declaration: the event exists when the receiver
|
|
63113
|
+
* declares `func on_<event> = (ref self, int id)`. Without it the name
|
|
63114
|
+
* resolves ordinarily ("Function not found") — the family is not reserved
|
|
63115
|
+
* on composites, only on controls.
|
|
63116
|
+
*
|
|
63117
|
+
* The closure rules match the click desugar (no parameters, no upvalue
|
|
63118
|
+
* captures, self-rooted field receiver), and the enclosing host must be the
|
|
63119
|
+
* App conformer — the up-call dispatches through the app's `run_handler`.
|
|
63120
|
+
* Returns undefined when this isn't a declared composite event.
|
|
63121
|
+
*/
|
|
63122
|
+
function try_check_composite_event(receiver, target_type, target, node, status, fail) {
|
|
63123
|
+
if (!receiver.functions.find((f) => f.name === node.name && f.params.length === 2 && f.params[0].is_self_param && !f.params[1].is_self_param && f.params[1].type.name === "int" && !f.params[1].type.is_ref)) return void 0;
|
|
63124
|
+
if (node.params.length !== 1 || node.params[0].node_type !== "func") return fail(`.${node.name} expects one closure argument — .${node.name}(func () { ... })`);
|
|
63125
|
+
const closure = node.params[0];
|
|
63126
|
+
if (closure.params.length > 0) return fail("handler closures take no parameters — the body sees `self` and the dispatch `win`");
|
|
63127
|
+
const fields = self_rooted_field_path(target);
|
|
63128
|
+
if (!fields) return fail(`.${node.name} needs a self-rooted field receiver — store the composite in a field and register from self.<field>.${node.name}(...)`);
|
|
63129
|
+
const host = status.stack.findLast((n) => n.node_type === "func")?.scope;
|
|
63130
|
+
if (!host || host.node_type !== "struct") return fail(`.${node.name} registers a handler on the enclosing struct — it cannot appear outside one`);
|
|
63131
|
+
if (!host.traits.includes("App")) return fail(`up-call handlers dispatch through the app's run_handler — register .${node.name} from the app, not from '${host.name}'`);
|
|
63132
|
+
if (host.functions.some((f) => f.name === "run_handler")) return fail(`'${host.name}' already declares run_handler — the .${node.name} desugar synthesizes it (the name is reserved on the app)`);
|
|
63133
|
+
const root = status.stack[0];
|
|
63134
|
+
let state = run_handler_states.get(host);
|
|
63135
|
+
if (!state) {
|
|
63136
|
+
const run_handler = new FunctionNode(node.start, "pub", "run_handler", new Type(""), [
|
|
63137
|
+
ref_self_param(host),
|
|
63138
|
+
new ParameterNode(node.start, "id", new Type("int")),
|
|
63139
|
+
win_param()
|
|
63140
|
+
]);
|
|
63141
|
+
host.functions.push(run_handler);
|
|
63142
|
+
state = {
|
|
63143
|
+
run_handler,
|
|
63144
|
+
next_id: 1
|
|
63145
|
+
};
|
|
63146
|
+
run_handler_states.set(host, state);
|
|
63147
|
+
let apps = pending_run_handlers.get(root);
|
|
63148
|
+
if (!apps) {
|
|
63149
|
+
apps = [];
|
|
63150
|
+
pending_run_handlers.set(root, apps);
|
|
63151
|
+
}
|
|
63152
|
+
apps.push(host);
|
|
63153
|
+
}
|
|
63154
|
+
const id = state.next_id++;
|
|
63155
|
+
const hoisted = new FunctionNode(closure.start, "private", `#up_${node.name}_${id}`, new Type(""), [ref_self_param(host), win_param()], closure.statements);
|
|
63156
|
+
host.functions.push(hoisted);
|
|
63157
|
+
hoisted.is_closure = true;
|
|
63158
|
+
check_function_node(hoisted, status);
|
|
63159
|
+
const upvalues = hoisted.captures ?? [];
|
|
63160
|
+
hoisted.is_closure = void 0;
|
|
63161
|
+
hoisted.captures = void 0;
|
|
63162
|
+
if (upvalues.length) return fail("handler closures see `self` and the dispatch `win` — copy what you need into fields");
|
|
63163
|
+
state.run_handler.statements.push(up_call_arm(host, node.name, id, node.start));
|
|
63164
|
+
let ticks = tick_states.get(host);
|
|
63165
|
+
if (!ticks) {
|
|
63166
|
+
ticks = {
|
|
63167
|
+
entries: [],
|
|
63168
|
+
seen: /* @__PURE__ */ new Set()
|
|
63169
|
+
};
|
|
63170
|
+
tick_states.set(host, ticks);
|
|
63171
|
+
const tick_root = status.stack[0];
|
|
63172
|
+
let apps = pending_tick_apps.get(tick_root);
|
|
63173
|
+
if (!apps) {
|
|
63174
|
+
apps = [];
|
|
63175
|
+
pending_tick_apps.set(tick_root, apps);
|
|
63176
|
+
}
|
|
63177
|
+
apps.push(host);
|
|
63178
|
+
}
|
|
63179
|
+
const path_key = fields.join(".");
|
|
63180
|
+
if (!ticks.seen.has(path_key)) {
|
|
63181
|
+
ticks.seen.add(path_key);
|
|
63182
|
+
ticks.entries.push({
|
|
63183
|
+
fields,
|
|
63184
|
+
composite: receiver
|
|
63185
|
+
});
|
|
63186
|
+
}
|
|
63187
|
+
node.is_handler_enrollment = true;
|
|
63188
|
+
node.params = [new ValueNode(node.start, String(id), new Type("int"))];
|
|
63189
|
+
return check_access_function_node(target_type, target, node, status);
|
|
63190
|
+
}
|
|
63191
|
+
/**
|
|
63192
|
+
* `if id == N { self.#on_<event>_N(ref win) }` — one arm of the
|
|
63193
|
+
* synthesized `run_handler`.
|
|
63194
|
+
*/
|
|
63195
|
+
function up_call_arm(host, event, id, start) {
|
|
63196
|
+
const condition = new OperationNode(start, "==", new ValueNode(start, "id", new Type("int")), new ValueNode(start, String(id), new Type("int")));
|
|
63197
|
+
const dispatch = new AccessFunctionCallNode(start, `#up_${event}_${id}`);
|
|
63198
|
+
dispatch.ref_param_indices = [0];
|
|
63199
|
+
dispatch.params = [new ValueNode(start, "win")];
|
|
63200
|
+
return new IfElseNode(start, condition, new BranchNode(start, [new AccessNode(start, new ValueNode(start, "self", new Type(host.name)), dispatch)]));
|
|
63201
|
+
}
|
|
63202
|
+
/**
|
|
63203
|
+
* Check every host's synthesized `handle_events`, after first synthesizing
|
|
63204
|
+
* the FORWARDING wiring (Stage 2, HANDLERS.md "Handler hosts beyond the
|
|
63205
|
+
* root"): a struct with registrations is a handler host — not just the App.
|
|
63206
|
+
* Dispatch reaches non-root hosts down static field paths: every struct on a
|
|
63207
|
+
* path from an App conformer to a host gets a `handle_events` (its own arms
|
|
63208
|
+
* if it registers, pure forwarding if it doesn't) that forwards to each
|
|
63209
|
+
* host-reaching field — `self.<field>.handle_events(ref win)` — direct
|
|
63210
|
+
* calls, no trait objects. Dispatch order is children-first, then own arms
|
|
63211
|
+
* (most specific first, HANDLERS.md "user-authored composites").
|
|
63212
|
+
*
|
|
63213
|
+
* Runs once, after the whole tree has been checked: the arms reference the
|
|
63214
|
+
* hidden `#on_click_N` methods (checked at their registration sites), the
|
|
63215
|
+
* drain methods (`take_click` et al) and the `Window` type are resolved, and
|
|
63216
|
+
* the full field graph (gathered upfront) decides reachability. Checking the
|
|
63217
|
+
* bodies also stamps every node for the build.
|
|
63218
|
+
*/
|
|
63219
|
+
function finalize_handler_hosts(status) {
|
|
63220
|
+
const root = status.stack[0];
|
|
63221
|
+
const hosts = pending_hosts.get(root);
|
|
63222
|
+
const up_call_apps = pending_run_handlers.get(root);
|
|
63223
|
+
const tick_apps = pending_tick_apps.get(root);
|
|
63224
|
+
if (!hosts && !up_call_apps && !tick_apps) return;
|
|
63225
|
+
pending_hosts.delete(root);
|
|
63226
|
+
pending_tick_apps.delete(root);
|
|
63227
|
+
const host_set = new Set(hosts ?? []);
|
|
63228
|
+
const to_check = [];
|
|
63229
|
+
for (const host of hosts ?? []) {
|
|
63230
|
+
const state = dispatcher_states.get(host);
|
|
63231
|
+
if (state) to_check.push(state.func);
|
|
63232
|
+
}
|
|
63233
|
+
const reaches_memo = /* @__PURE__ */ new Map();
|
|
63234
|
+
const reaches_host = (s, stack) => {
|
|
63235
|
+
if (host_set.has(s)) return true;
|
|
63236
|
+
const memo = reaches_memo.get(s);
|
|
63237
|
+
if (memo !== void 0) return memo;
|
|
63238
|
+
if (stack.has(s)) return false;
|
|
63239
|
+
stack.add(s);
|
|
63240
|
+
let result = false;
|
|
63241
|
+
for (const field of s.fields) {
|
|
63242
|
+
for (const t of reachable_field_targets(field, status)) if (reaches_host(t, stack)) {
|
|
63243
|
+
result = true;
|
|
63244
|
+
break;
|
|
63245
|
+
}
|
|
63246
|
+
if (result) break;
|
|
63247
|
+
}
|
|
63248
|
+
stack.delete(s);
|
|
63249
|
+
reaches_memo.set(s, result);
|
|
63250
|
+
return result;
|
|
63251
|
+
};
|
|
63252
|
+
for (const s of status.structs) {
|
|
63253
|
+
const forwards = [];
|
|
63254
|
+
for (const f of s.fields) {
|
|
63255
|
+
const t = forwardable_field_type(f, status);
|
|
63256
|
+
if (t && reaches_host(t, /* @__PURE__ */ new Set())) forwards.push(forward_statement(s, f.name));
|
|
63257
|
+
}
|
|
63258
|
+
for (const f of s.fields) {
|
|
63259
|
+
const elem = forwardable_list_element(f, status);
|
|
63260
|
+
if (elem && reaches_host(elem, /* @__PURE__ */ new Set())) forwards.push(...list_forward_loop(s, f.name, elem, status));
|
|
63261
|
+
}
|
|
63262
|
+
if (forwards.length === 0) continue;
|
|
63263
|
+
const is_app = s.traits.includes("App");
|
|
63264
|
+
const is_host = host_set.has(s);
|
|
63265
|
+
if (!is_app && !is_host && !reachable_from_an_app(s, status)) continue;
|
|
63266
|
+
let state = dispatcher_states.get(s);
|
|
63267
|
+
if (!state) {
|
|
63268
|
+
if (s.functions.some((f) => f.name === PUMP_NAME)) continue;
|
|
63269
|
+
const pump = new FunctionNode(-1, "pub", PUMP_NAME, new Type(""), [ref_self_param(s), win_param()]);
|
|
63270
|
+
s.functions.push(pump);
|
|
63271
|
+
state = {
|
|
63272
|
+
func: pump,
|
|
63273
|
+
next_index: /* @__PURE__ */ new Map()
|
|
63274
|
+
};
|
|
63275
|
+
dispatcher_states.set(s, state);
|
|
63276
|
+
to_check.push(pump);
|
|
63277
|
+
}
|
|
63278
|
+
state.func.statements.unshift(...forwards);
|
|
63279
|
+
}
|
|
63280
|
+
for (const app of tick_apps ?? []) {
|
|
63281
|
+
const ticks = tick_states.get(app);
|
|
63282
|
+
if (!ticks) continue;
|
|
63283
|
+
let state = dispatcher_states.get(app);
|
|
63284
|
+
if (!state) {
|
|
63285
|
+
if (app.functions.some((f) => f.name === PUMP_NAME)) continue;
|
|
63286
|
+
const pump = new FunctionNode(-1, "pub", PUMP_NAME, new Type(""), [ref_self_param(app), win_param()]);
|
|
63287
|
+
app.functions.push(pump);
|
|
63288
|
+
state = {
|
|
63289
|
+
func: pump,
|
|
63290
|
+
next_index: /* @__PURE__ */ new Map()
|
|
63291
|
+
};
|
|
63292
|
+
dispatcher_states.set(app, state);
|
|
63293
|
+
to_check.push(pump);
|
|
63294
|
+
}
|
|
63295
|
+
for (const entry of ticks.entries) {
|
|
63296
|
+
if (!entry.composite.functions.some((f) => f.name === "tick" && f.params.length === 3 && f.params[0].is_self_param)) continue;
|
|
63297
|
+
const tick_call = new AccessFunctionCallNode(-1, "tick");
|
|
63298
|
+
tick_call.ref_param_indices = [0, 1];
|
|
63299
|
+
tick_call.params = [new ValueNode(-1, "self", new Type(app.name)), new ValueNode(-1, "win")];
|
|
63300
|
+
state.func.statements.push(new AccessNode(-1, arm_receiver(app, entry.fields, -1), tick_call));
|
|
63301
|
+
}
|
|
63302
|
+
}
|
|
63303
|
+
for (const hc of to_check) check_function_node(hc, status);
|
|
63304
|
+
if (up_call_apps) {
|
|
63305
|
+
pending_run_handlers.delete(root);
|
|
63306
|
+
for (const app of up_call_apps) {
|
|
63307
|
+
const state = run_handler_states.get(app);
|
|
63308
|
+
if (!state) continue;
|
|
63309
|
+
check_function_node(state.run_handler, status);
|
|
63310
|
+
}
|
|
63311
|
+
}
|
|
63312
|
+
}
|
|
63313
|
+
/**
|
|
63314
|
+
* The field types dispatch may FORWARD through: direct, owned, concrete
|
|
63315
|
+
* struct fields. Boxes/containers/monos/generics are element-dispatch
|
|
63316
|
+
* territory (B5, Stage 4) and stop the walk; nullable/borrowed/array fields
|
|
63317
|
+
* have no single static target.
|
|
63318
|
+
*/
|
|
63319
|
+
function forwardable_field_type(field, status) {
|
|
63320
|
+
const type = field.type;
|
|
63321
|
+
if (!type.name || type.is_ref || type.is_view || type.is_array || type.is_nullable) return;
|
|
63322
|
+
if (is_box_type(type.name, status.structs)) return void 0;
|
|
63323
|
+
const s = status.structs.find((st) => st.name === type.name);
|
|
63324
|
+
if (!s) return void 0;
|
|
63325
|
+
if (s.source_type_args?.length || s.type_params.length > 0) return void 0;
|
|
63326
|
+
return s;
|
|
63327
|
+
}
|
|
63328
|
+
/**
|
|
63329
|
+
* The struct types a field makes REACHABLE for host-dispatch purposes: the
|
|
63330
|
+
* direct struct field target, plus (Stage 4) the element struct of a
|
|
63331
|
+
* `List<Row>` field — dynamic trees are dispatchable, so a composite type
|
|
63332
|
+
* hosted in a list is owned by the app exactly like a direct field.
|
|
63333
|
+
*/
|
|
63334
|
+
function reachable_field_targets(field, status) {
|
|
63335
|
+
const targets = [];
|
|
63336
|
+
const direct = forwardable_field_type(field, status);
|
|
63337
|
+
if (direct) targets.push(direct);
|
|
63338
|
+
const elem = forwardable_list_element(field, status);
|
|
63339
|
+
if (elem) targets.push(elem);
|
|
63340
|
+
return targets;
|
|
63341
|
+
}
|
|
63342
|
+
/** Whether some App conformer reaches `target` down forwardable field paths. */
|
|
63343
|
+
function reachable_from_an_app(target, status) {
|
|
63344
|
+
for (const s of status.structs) {
|
|
63345
|
+
if (!s.traits.includes("App")) continue;
|
|
63346
|
+
if (path_exists(s, target, status, /* @__PURE__ */ new Set())) return true;
|
|
63347
|
+
}
|
|
63348
|
+
return false;
|
|
63349
|
+
}
|
|
63350
|
+
function path_exists(s, target, status, stack) {
|
|
63351
|
+
if (s === target) return true;
|
|
63352
|
+
if (stack.has(s)) return false;
|
|
63353
|
+
stack.add(s);
|
|
63354
|
+
for (const field of s.fields) for (const t of reachable_field_targets(field, status)) if (path_exists(t, target, status, stack)) return true;
|
|
63355
|
+
stack.delete(s);
|
|
63356
|
+
return false;
|
|
63357
|
+
}
|
|
63358
|
+
/** `self.<field>.handle_events(ref win)` — one hop down the ownership path. */
|
|
63359
|
+
function forward_statement(host, field) {
|
|
63360
|
+
const receiver = new AccessNode(-1, new ValueNode(-1, "self", new Type(host.name)), new AccessFieldNode(-1, field));
|
|
63361
|
+
const dispatch = new AccessFunctionCallNode(-1, "handle_events");
|
|
63362
|
+
dispatch.ref_param_indices = [0];
|
|
63363
|
+
dispatch.params = [new ValueNode(-1, "win")];
|
|
63364
|
+
return new AccessNode(-1, receiver, dispatch);
|
|
63365
|
+
}
|
|
63366
|
+
/**
|
|
63367
|
+
* The element struct a `List<Row>` field dispatches to, when the field is a
|
|
63368
|
+
* forwardable dynamic tree (either spelling of the mono). Containers of
|
|
63369
|
+
* trait/erased elements stay B5 territory — concrete rows only.
|
|
63370
|
+
*/
|
|
63371
|
+
function forwardable_list_element(field, status) {
|
|
63372
|
+
const t = field.type;
|
|
63373
|
+
if (t.is_ref || t.is_view || t.is_array || t.is_nullable) return void 0;
|
|
63374
|
+
let elem_name;
|
|
63375
|
+
if (t.name === "List" && t.type_args?.length === 1) elem_name = t.type_args[0].name;
|
|
63376
|
+
else if (t.name.startsWith("List_")) elem_name = status.structs.find((s) => s.name === t.name && s.source_type_args?.length)?.source_type_args?.[0]?.name;
|
|
63377
|
+
if (!elem_name) return void 0;
|
|
63378
|
+
return status.structs.find((s) => s.name === elem_name && !s.is_generic);
|
|
63379
|
+
}
|
|
63380
|
+
/**
|
|
63381
|
+
* The dynamic-tree dispatch body for one `List<Row>` field (docs/HANDLERS.md
|
|
63382
|
+
* Stage 4):
|
|
63383
|
+
*
|
|
63384
|
+
* ```
|
|
63385
|
+
* var int _rows_i_N = 0
|
|
63386
|
+
* while _rows_i_N < self.rows.length; _rows_i_N += 1 {
|
|
63387
|
+
* ref Row _row = self.rows.at(_rows_i_N)
|
|
63388
|
+
* _row.handle_events(ref win)
|
|
63389
|
+
* }
|
|
63390
|
+
* ```
|
|
63391
|
+
*
|
|
63392
|
+
* The element borrow binds the SLOT (the checker rewrites `.at` to
|
|
63393
|
+
* `slot_ptr`), so each row's own arms run in place — state persists across
|
|
63394
|
+
* frames. The bounded-loop condition discharges `.at`'s bounds for the
|
|
63395
|
+
* borrow, and any list mutation (`ref self` on the list) invalidates the
|
|
63396
|
+
* borrow through the ordinary rules.
|
|
63397
|
+
*/
|
|
63398
|
+
function list_forward_loop(host, field, elem, status) {
|
|
63399
|
+
const start = -1;
|
|
63400
|
+
const index = `_${field}_i_${status.var_name_counter.value++}`;
|
|
63401
|
+
const index_value = () => new ValueNode(start, index, new Type("int"));
|
|
63402
|
+
const list_access = () => new AccessNode(start, new ValueNode(start, "self", new Type(host.name)), new AccessFieldNode(start, field));
|
|
63403
|
+
const declaration = new DeclarationNode(start, "private", "var", index, new Type("int"), new ValueNode(start, "0", new Type("int")));
|
|
63404
|
+
const element_borrow = new DeclarationNode(start, "private", "ref", `_${field}_row`, new Type(elem.name), new AccessNode(start, list_access(), new AccessFunctionCallNode(start, "at", new Type(""), [index_value()])));
|
|
63405
|
+
element_borrow.is_ref_binding = true;
|
|
63406
|
+
const dispatch = new AccessFunctionCallNode(start, "handle_events");
|
|
63407
|
+
dispatch.ref_param_indices = [0];
|
|
63408
|
+
dispatch.params = [new ValueNode(start, "win")];
|
|
63409
|
+
const call = new AccessNode(start, new ValueNode(start, `_${field}_row`, new Type(elem.name)), dispatch);
|
|
63410
|
+
const condition = new OperationNode(start, "<", index_value(), new AccessNode(start, list_access(), new AccessFieldNode(start, "length")));
|
|
63411
|
+
const update = new AssignmentNode(start, index_value(), new ValueNode(start, "1", new Type("int")), "+=");
|
|
63412
|
+
return [declaration, new WhileLoopNode(start, condition, [element_borrow, call], update)];
|
|
63413
|
+
}
|
|
63414
|
+
/** `ref self` — the hoisted method's receiver (the host instance). */
|
|
63415
|
+
function ref_self_param(host) {
|
|
63416
|
+
const param = new ParameterNode(-1, "self", new Type(host.name));
|
|
63417
|
+
param.is_self_param = true;
|
|
63418
|
+
param.is_ref = true;
|
|
63419
|
+
param.declaration = "var";
|
|
63420
|
+
return param;
|
|
63421
|
+
}
|
|
63422
|
+
/** `ref Window win` — the dispatch window, lent by the run loop per frame. */
|
|
63423
|
+
function win_param() {
|
|
63424
|
+
const type = new Type("Window");
|
|
63425
|
+
type.is_ref = true;
|
|
63426
|
+
return new ParameterNode(-1, "win", type);
|
|
63427
|
+
}
|
|
63428
|
+
/**
|
|
63429
|
+
* The registration receiver's field path when it is `self`-rooted:
|
|
63430
|
+
* `self.add_btn` → `["add_btn"]`, `self.pager.prev` → `["pager", "prev"]`.
|
|
63431
|
+
* Undefined for any other shape (a bare local, a call result, …).
|
|
63432
|
+
*/
|
|
63433
|
+
function self_rooted_field_path(target) {
|
|
63434
|
+
const fields = [];
|
|
63435
|
+
let current = target;
|
|
63436
|
+
while (current.node_type === "access") {
|
|
63437
|
+
const access = current;
|
|
63438
|
+
if (access.access.node_type !== "access_field") return void 0;
|
|
63439
|
+
fields.unshift(access.access.name);
|
|
63440
|
+
current = access.target;
|
|
63441
|
+
}
|
|
63442
|
+
if (current.node_type !== "value" || current.value !== "self") return;
|
|
63443
|
+
if (fields.length === 0) return void 0;
|
|
63444
|
+
return fields;
|
|
63445
|
+
}
|
|
63446
|
+
/** The receiver path re-emitted for the arm: `self.a.b` (handle expression). */
|
|
63447
|
+
function arm_receiver(host, fields, start) {
|
|
63448
|
+
let receiver = new ValueNode(start, "self", new Type(host.name));
|
|
63449
|
+
for (const field of fields) receiver = new AccessNode(start, receiver, new AccessFieldNode(start, field));
|
|
63450
|
+
return receiver;
|
|
63451
|
+
}
|
|
63452
|
+
/**
|
|
63453
|
+
* `if <drain>(self.<path>.handle) { self.<hidden>(ref win) }` — one arm of
|
|
63454
|
+
* the synthesized dispatcher.
|
|
63455
|
+
*/
|
|
63456
|
+
function arm_dispatch_statement(host, fields, drain, hidden, start) {
|
|
63457
|
+
const condition = new AccessFunctionCallNode(start, drain);
|
|
63458
|
+
const condition_call = new AccessNode(start, arm_receiver(host, fields, start), condition);
|
|
63459
|
+
const dispatch = new AccessFunctionCallNode(start, hidden);
|
|
63460
|
+
dispatch.ref_param_indices = [0];
|
|
63461
|
+
dispatch.params = [new ValueNode(start, "win")];
|
|
63462
|
+
return new IfElseNode(start, condition_call, new BranchNode(start, [new AccessNode(start, new ValueNode(start, "self", new Type(host.name)), dispatch)]));
|
|
63463
|
+
}
|
|
63464
|
+
//#endregion
|
|
61638
63465
|
//#region ../src/check/check_access_node.ts
|
|
61639
63466
|
function check_access_node(node, status) {
|
|
61640
63467
|
if (!check_node(node.target, status)) return false;
|
|
@@ -61902,6 +63729,8 @@ function check_access_field_node(target_type, target, node, status) {
|
|
|
61902
63729
|
return true;
|
|
61903
63730
|
}
|
|
61904
63731
|
function check_access_function_node(target_type, target, node, status) {
|
|
63732
|
+
const handler = try_check_handler_registration(target_type, target, node, status);
|
|
63733
|
+
if (handler !== void 0) return handler;
|
|
61905
63734
|
if (target_type.name === "Nursery" && node.name === "start") return check_nursery_spawn(node, status);
|
|
61906
63735
|
if (target_type.name === "Fiber" && node.name === "start_on") {
|
|
61907
63736
|
if (!check_spawn_start_on(target, target_type, node, status)) return false;
|
|
@@ -62022,6 +63851,13 @@ function check_access_function_node(target_type, target, node, status) {
|
|
|
62022
63851
|
}
|
|
62023
63852
|
}
|
|
62024
63853
|
if (!func) {
|
|
63854
|
+
if (node.name === "copy" && node.params.length === 0) {
|
|
63855
|
+
const container_is = (prefix) => target_type.name === prefix || target_type.name.startsWith(`${prefix}_`) || !!struct?.name.startsWith(`${prefix}_`);
|
|
63856
|
+
if ((container_is("Map") || container_is("Set") ? target_type.type_args ?? struct?.source_type_args : void 0)?.some((arg) => arg.name && is_owning_ref_type(arg.name, status))) {
|
|
63857
|
+
add_error(status, container_is("Map") ? "Map.copy() is not available when the key or value type is a class or trait — its store is a ClassBuffer with no copy, so the copy would share the stored instances (a double free). Keep one mv map and index into it, or rebuild the map with fresh instances." : "Set.copy() is not available when the element type is a class or trait — its store is a ClassBuffer with no copy, so the copy would share the stored instances (a double free). Keep one mv set and index into it, or rebuild the set with fresh instances.", node.start);
|
|
63858
|
+
return false;
|
|
63859
|
+
}
|
|
63860
|
+
}
|
|
62025
63861
|
add_error(status, `Function not found: ${target_type.name}.${node.name}`, node.start);
|
|
62026
63862
|
return false;
|
|
62027
63863
|
}
|
|
@@ -62724,8 +64560,10 @@ function check_assignment_node(assign, status) {
|
|
|
62724
64560
|
add_error(status, `Unknown variable: ${left_value_name}`, assign.left_value.start);
|
|
62725
64561
|
return false;
|
|
62726
64562
|
} else if (left_value.is_ref_binding && assign.left_value.node_type === "value") {
|
|
62727
|
-
|
|
62728
|
-
|
|
64563
|
+
left_value.class_alias_of = void 0;
|
|
64564
|
+
left_value.ref_source = void 0;
|
|
64565
|
+
left_value.is_ref_binding = void 0;
|
|
64566
|
+
left_value.declaration = "var";
|
|
62729
64567
|
} else if (assign.left_value.node_type === "access" && left_value.is_ref_binding) {
|
|
62730
64568
|
if (left_value.ref_source) enforce_root_not_frozen(left_value.ref_source, status, assign.left_value.start);
|
|
62731
64569
|
if (!(left_value.ref_source && is_var_binding(left_value.ref_source, status))) {
|
|
@@ -62818,7 +64656,8 @@ function check_assignment_node(assign, status) {
|
|
|
62818
64656
|
} else check_type_and_value_match(type_from_value_node(assign.left_value, status), type_from_value_node(assign.right_value, status), value_from_value_node(assign.right_value), status, assign.right_value.start, "assignment");
|
|
62819
64657
|
if (assign.left_value.node_type === "access" && assign.left_value.access.node_type === "access_field") {
|
|
62820
64658
|
const lhs_access = assign.left_value;
|
|
62821
|
-
|
|
64659
|
+
const lhs_field = lhs_access.access;
|
|
64660
|
+
if (lhs_field.type?.is_view) {
|
|
62822
64661
|
const root = root_var_of(lhs_access.target);
|
|
62823
64662
|
const rhs = assign.right_value;
|
|
62824
64663
|
let infos;
|
|
@@ -62829,6 +64668,18 @@ function check_assignment_node(assign, status) {
|
|
|
62829
64668
|
const info = view_source_borrow_info(rhs, status);
|
|
62830
64669
|
if (info) merge_view_borrows_into_var(root, /* @__PURE__ */ new Map([[info.owner ?? "", info]]), status);
|
|
62831
64670
|
else if (!infos?.size && root && view_fields_invalidated(root, status)) status.invalidated_view_structs?.delete(root);
|
|
64671
|
+
} else if (lhs_field.type?.name && struct_has_view_fields(lhs_field.type.name, status)) {
|
|
64672
|
+
const root = root_var_of(lhs_access.target);
|
|
64673
|
+
const rhs_name = value_from_value_node(assign.right_value);
|
|
64674
|
+
const rhs_sv = rhs_name !== void 0 ? status.values.findLast((v) => v.name === rhs_name) : void 0;
|
|
64675
|
+
if (root && rhs_sv?.has_view_borrows) {
|
|
64676
|
+
const infos = /* @__PURE__ */ new Map();
|
|
64677
|
+
for (const owner of rhs_sv.view_field_owners ?? []) infos.set(owner, {
|
|
64678
|
+
owner,
|
|
64679
|
+
depth: rhs_sv.borrow_depth
|
|
64680
|
+
});
|
|
64681
|
+
merge_view_borrows_into_var(root, infos, status);
|
|
64682
|
+
}
|
|
62832
64683
|
}
|
|
62833
64684
|
}
|
|
62834
64685
|
const lhs_is_owning_field_store = assign.left_value.node_type === "access" && assign.left_value.access.node_type === "access_field" && lhs_field_access_owning(assign.left_value, status);
|
|
@@ -63045,7 +64896,7 @@ function check_async_block_node(node, status) {
|
|
|
63045
64896
|
const block_status = clone_status(status);
|
|
63046
64897
|
block_status.nursery_depth = (status.nursery_depth ?? 0) + 1;
|
|
63047
64898
|
block_status.nursery_scope_depths = [...status.nursery_scope_depths ?? [], status.scope_depth + 1];
|
|
63048
|
-
block_status.nursery_frozen_roots = [...status.nursery_frozen_roots ?? [], /* @__PURE__ */ new
|
|
64899
|
+
block_status.nursery_frozen_roots = [...status.nursery_frozen_roots ?? [], /* @__PURE__ */ new Map()];
|
|
63049
64900
|
if (node.nursery_name) block_status.values.push({
|
|
63050
64901
|
declaration: "var",
|
|
63051
64902
|
name: node.nursery_name,
|
|
@@ -63154,6 +65005,8 @@ function check_enum_node(node, status) {
|
|
|
63154
65005
|
for (let i = 0; i < node.cases.length; i++) for (let j = i + 1; j < node.cases.length; j++) if (node.cases[i].name === node.cases[j].name) add_error(status, `Duplicate enum case: ${node.cases[j].name}`, node.cases[j].params[0]?.start || node.start);
|
|
63155
65006
|
for (const tp of node.type_params) if (node.type_params.filter((t) => t === tp).length > 1) add_error(status, `Duplicate type parameter: ${tp}`, node.start);
|
|
63156
65007
|
for (let c of node.cases) for (let p of c.params) {
|
|
65008
|
+
if (p.type.is_view) add_error(status, `enum case payloads cannot be 'view' — materialize the value ('.to_string()') or re-slice from the source on use`, p.start || node.start);
|
|
65009
|
+
else if (p.type.name && struct_has_view_fields(p.type.name, status)) add_error(status, `enum case payloads cannot be '${p.type.name}' — structs with 'view' fields are scope-local; store the fields (the views) instead`, p.start || node.start);
|
|
63157
65010
|
if (node.type_params.includes(p.type.name)) continue;
|
|
63158
65011
|
if (!status.types.includes(p.type.name)) add_error(status, `Unknown type: ${p.type.name}`, p.start);
|
|
63159
65012
|
}
|
|
@@ -63969,6 +65822,7 @@ function check_return_node(ret, status) {
|
|
|
63969
65822
|
ret.type = new Type("void");
|
|
63970
65823
|
return;
|
|
63971
65824
|
}
|
|
65825
|
+
if (func?.return_type && !func.return_type.is_view && func.return_type.name && struct_has_view_fields(func.return_type.name, status) && !func.view_bundle_return_rejected) add_error(status, `cannot return a '${func.return_type.name}' — structs with 'view' fields are scope-local; return the fields (the views) instead`, ret.value.start);
|
|
63972
65826
|
const old_expected_type = status.expected_type;
|
|
63973
65827
|
if (func?.return_type?.name && func.return_type.name !== "?") status.expected_type = func.return_type;
|
|
63974
65828
|
const old_allow_null = status.allow_null_value;
|
|
@@ -63983,8 +65837,11 @@ function check_return_node(ret, status) {
|
|
|
63983
65837
|
ret.type = type_from_value_node(ret.value, status);
|
|
63984
65838
|
if (ret.value.node_type === "access" && ret.value.access.node_type === "access_field") {
|
|
63985
65839
|
const af = ret.value.access;
|
|
63986
|
-
const
|
|
63987
|
-
|
|
65840
|
+
const base_target = ret.value.target;
|
|
65841
|
+
const base_type = type_from_value_node(base_target, status);
|
|
65842
|
+
const base_name = base_target.node_type === "value" ? base_target.value : "";
|
|
65843
|
+
const base_is_binding = !!base_name && status.values.some((v) => v.name === base_name);
|
|
65844
|
+
if (base_type.name && base_is_binding) {
|
|
63988
65845
|
const field = status.structs.findLast((s) => s.name === mono_type_name(base_type) && !s.is_generic)?.fields.find((f) => f.name === af.name);
|
|
63989
65846
|
if (field?.type.name && !field.type.is_array && !field.type.is_view && !field.type.is_ref && (is_class_type(field.type.name, status) || is_owning_struct_type_requiring_move(new Type(mono_type_name(field.type)), status))) add_error(status, `cannot copy '${field.type.name}' out of field '${af.name}' by value — it owns heap resources; use 'mv ... swap <replacement>'${field.type.name === "string" ? ", or .copy() for a deep copy" : ""}`, ret.value.start);
|
|
63990
65847
|
}
|
|
@@ -64199,8 +66056,9 @@ function check_value_node(node, status) {
|
|
|
64199
66056
|
return true;
|
|
64200
66057
|
}
|
|
64201
66058
|
if (node.value.startsWith(".") && node.value.length > 1 && !node.value.startsWith("..")) return check_enum_shorthand(node, status);
|
|
64202
|
-
|
|
64203
|
-
if (
|
|
66059
|
+
const resolved = type_from_value(node.value, status);
|
|
66060
|
+
if (resolved.name) node.type = resolved;
|
|
66061
|
+
else if (!node.type?.name) {
|
|
64204
66062
|
add_error(status, `Unknown value: ${node.value}`, node.start);
|
|
64205
66063
|
return false;
|
|
64206
66064
|
}
|
|
@@ -64611,6 +66469,7 @@ function check(root, options) {
|
|
|
64611
66469
|
};
|
|
64612
66470
|
stamp_literal_element_moves(root);
|
|
64613
66471
|
check_node(root, status);
|
|
66472
|
+
finalize_handler_hosts(status);
|
|
64614
66473
|
unwrap_unsafe_blocks(root);
|
|
64615
66474
|
if (!status.errors.length) emit_warnings(root, status);
|
|
64616
66475
|
return {
|
|
@@ -66229,7 +68088,7 @@ function parse_function(visibility, status, name_override, is_inline, is_foreign
|
|
|
66229
68088
|
let name = name_override || consume(status);
|
|
66230
68089
|
if (!name_override && name === "#") {
|
|
66231
68090
|
const next = consume(status);
|
|
66232
|
-
if (next === "init" || next === "destroy" || next.startsWith("op_")) name = `#${next}`;
|
|
68091
|
+
if (next === "init" || next === "destroy" || next === "spawn" || next === "weak" || next === "handle" || next.startsWith("op_")) name = `#${next}`;
|
|
66233
68092
|
}
|
|
66234
68093
|
const op_internal = {
|
|
66235
68094
|
"#op_add": "add",
|
|
@@ -66296,7 +68155,7 @@ function parse_function(visibility, status, name_override, is_inline, is_foreign
|
|
|
66296
68155
|
parse_statement(status);
|
|
66297
68156
|
expect("}", status);
|
|
66298
68157
|
status.stack.pop();
|
|
66299
|
-
if (name !== "#init" && name !== "#destroy" && name !== "#spawn") check_missing_return(func, status);
|
|
68158
|
+
if (name !== "#init" && name !== "#destroy" && name !== "#spawn" && name !== "#weak" && name !== "#handle") check_missing_return(func, status);
|
|
66300
68159
|
}
|
|
66301
68160
|
switch (parent.node_type) {
|
|
66302
68161
|
case "root":
|
|
@@ -66498,6 +68357,10 @@ function parse_struct(visibility, status, is_owning = false) {
|
|
|
66498
68357
|
struct.functions.unshift(func);
|
|
66499
68358
|
}
|
|
66500
68359
|
add_to_parent(struct, "Struct", status);
|
|
68360
|
+
if (struct.functions.some((f) => f.name === "#handle")) {
|
|
68361
|
+
struct.functions = struct.functions.filter((f) => f.name !== "#handle");
|
|
68362
|
+
add_to_parent(synthesize_handle_struct(struct, start, visibility), "Struct", status);
|
|
68363
|
+
}
|
|
66501
68364
|
}
|
|
66502
68365
|
status.namespace = old_namespace;
|
|
66503
68366
|
}
|
|
@@ -66514,6 +68377,34 @@ function parse_optional_type_param_bound(status) {
|
|
|
66514
68377
|
while (accept("+", status)) bounds.push(consume(status));
|
|
66515
68378
|
return bounds;
|
|
66516
68379
|
}
|
|
68380
|
+
function synthesize_handle_struct(struct, start, visibility) {
|
|
68381
|
+
if (struct.type_params.length > 0) throw new Error(`#handle is not supported on generic struct '${struct.name}' yet`);
|
|
68382
|
+
const handle = new StructNode(start, visibility, `${struct.name}Handle`);
|
|
68383
|
+
handle.is_owning = false;
|
|
68384
|
+
handle.fields.push(new DeclarationNode(start, "internal", "var", "handle", new Type("uint64")));
|
|
68385
|
+
const init = new FunctionNode(start, visibility, "#init", new Type(handle.name));
|
|
68386
|
+
const self_param = new ParameterNode(start, "self", new Type(handle.name));
|
|
68387
|
+
self_param.is_self_param = true;
|
|
68388
|
+
self_param.declaration = "var";
|
|
68389
|
+
init.params = [self_param, new ParameterNode(start, "from", new Type(struct.name))];
|
|
68390
|
+
init.is_static = false;
|
|
68391
|
+
init.has_body = true;
|
|
68392
|
+
init.statements = [new AssignmentNode(start, new AccessNode(start, new ValueNode(start, "self", new Type(handle.name)), new AccessFieldNode(start, "handle", new Type("uint64"))), new AccessNode(start, new ValueNode(start, "from", new Type(struct.name)), new AccessFieldNode(start, "handle", new Type("uint64"))))];
|
|
68393
|
+
handle.functions.push(init);
|
|
68394
|
+
for (const func of struct.functions) {
|
|
68395
|
+
if (func.visibility !== "pub") continue;
|
|
68396
|
+
if (func.name.startsWith("#")) continue;
|
|
68397
|
+
const receiver = func.params[0];
|
|
68398
|
+
if (!receiver || !receiver.is_self_param) continue;
|
|
68399
|
+
if (receiver.is_ref) continue;
|
|
68400
|
+
const clone = clone_node(func);
|
|
68401
|
+
clone.start = start;
|
|
68402
|
+
clone.visibility = "pub";
|
|
68403
|
+
clone.params[0].type = new Type(handle.name);
|
|
68404
|
+
handle.functions.push(clone);
|
|
68405
|
+
}
|
|
68406
|
+
return handle;
|
|
68407
|
+
}
|
|
66517
68408
|
//#endregion
|
|
66518
68409
|
//#region ../src/parse/utils/class_retired_error.ts
|
|
66519
68410
|
/**
|
|
@@ -66813,6 +68704,11 @@ function parse_visibility(visibility, status) {
|
|
|
66813
68704
|
consume(status);
|
|
66814
68705
|
consume(status);
|
|
66815
68706
|
parse_function(visibility, status, "#weak");
|
|
68707
|
+
} else if (next2 === "handle") {
|
|
68708
|
+
consume(status);
|
|
68709
|
+
consume(status);
|
|
68710
|
+
consume(status);
|
|
68711
|
+
parse_function(visibility, status, "#handle");
|
|
66816
68712
|
} else {
|
|
66817
68713
|
add_error(status, `Expected #init, #destroy, #spawn or #weak after pub`, get_index(status));
|
|
66818
68714
|
consume(status);
|
|
@@ -66974,8 +68870,12 @@ function parse_statement(status) {
|
|
|
66974
68870
|
consume(status);
|
|
66975
68871
|
consume(status);
|
|
66976
68872
|
parse_destroy(default_visibility(status), status, "#weak");
|
|
68873
|
+
} else if (next === "handle") {
|
|
68874
|
+
consume(status);
|
|
68875
|
+
consume(status);
|
|
68876
|
+
parse_destroy(default_visibility(status), status, "#handle");
|
|
66977
68877
|
} else {
|
|
66978
|
-
add_error(status, `Expected #init, #destroy, #spawn or #
|
|
68878
|
+
add_error(status, `Expected #init, #destroy, #spawn, #weak or #handle`, get_index(status));
|
|
66979
68879
|
consume(status);
|
|
66980
68880
|
}
|
|
66981
68881
|
break;
|