nomen-lang 0.6.12 → 0.6.13
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 +29 -0
- package/dist/index.mjs +481 -64
- package/package.json +1 -1
package/core/System/Buffer.nm
CHANGED
|
@@ -93,6 +93,35 @@ pub struct Buffer<T> {
|
|
|
93
93
|
```
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
// Byte-width primitives (uint8 elements): the width-matched family the
|
|
97
|
+
// NEON planner's `.16b` descriptor keys on — one 16-byte group carries
|
|
98
|
+
// 16 lanes. `ldrb`/`strb` zero-extend/clobber exactly one byte.
|
|
99
|
+
internal inline func load_u8 = (self, int i: i >= 0 && i < self.cap, out uint8) {
|
|
100
|
+
```
|
|
101
|
+
#arch: c
|
|
102
|
+
return ((unsigned char*)(unsigned long long)self->data)[i];
|
|
103
|
+
```
|
|
104
|
+
```
|
|
105
|
+
#arch: aarch64
|
|
106
|
+
ldr x0, [x19, #8]
|
|
107
|
+
add x0, x0, x1
|
|
108
|
+
ldrb w0, [x0]
|
|
109
|
+
```
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
internal inline func store_u8 = (ref self, int i: i >= 0 && i < self.cap, uint8 val) {
|
|
113
|
+
```
|
|
114
|
+
#arch: c
|
|
115
|
+
((unsigned char*)(unsigned long long)self->data)[i] = val;
|
|
116
|
+
```
|
|
117
|
+
```
|
|
118
|
+
#arch: aarch64
|
|
119
|
+
ldr x0, [x19, #8]
|
|
120
|
+
add x0, x0, x1
|
|
121
|
+
strb w2, [x0]
|
|
122
|
+
```
|
|
123
|
+
}
|
|
124
|
+
|
|
96
125
|
internal func alloc_int = (ref self, int size, out int: out >= size) {
|
|
97
126
|
self.cap = size
|
|
98
127
|
self.data = calloc(size, 8)
|
package/dist/index.mjs
CHANGED
|
@@ -4815,6 +4815,7 @@ function instr_defs(instr) {
|
|
|
4815
4815
|
}
|
|
4816
4816
|
return defs;
|
|
4817
4817
|
}
|
|
4818
|
+
let store_store_kill_on = process.env.NOMEN_SS_KILL !== "0";
|
|
4818
4819
|
function optimize_frame_slots(code) {
|
|
4819
4820
|
const lines = code.split("\n");
|
|
4820
4821
|
const out = [];
|
|
@@ -4839,14 +4840,26 @@ function optimize_frame_slots(code) {
|
|
|
4839
4840
|
};
|
|
4840
4841
|
/** A register is REDEFINED: pends sourced from it must reach memory now
|
|
4841
4842
|
* (deferring would store the new value); availability held in it dies.
|
|
4842
|
-
* Width siblings share fate (w/x and s/d views of one register).
|
|
4843
|
-
|
|
4843
|
+
* Width siblings share fate (w/x and s/d views of one register).
|
|
4844
|
+
* EXCEPTION (the store-store kill): when the block's NEXT access to
|
|
4845
|
+
* the pending slot is another STORE of the same family (no read, no
|
|
4846
|
+
* label/branch/call between), the orphaned store is unobservable —
|
|
4847
|
+
* the later store overwrites it before anything could read — and
|
|
4848
|
+
* drops instead of materializing. mul_to's carry-flag residue
|
|
4849
|
+
* (`str x0, [x29, #144]` twice per iteration of the hottest pidigits
|
|
4850
|
+
* loop, the value living in registers between) is exactly this
|
|
4851
|
+
* shape. */
|
|
4852
|
+
const clobber = (reg, at_idx = -1) => {
|
|
4844
4853
|
if (!reg) return;
|
|
4845
4854
|
const sib = sibling_reg$1(reg);
|
|
4846
4855
|
zextW.delete(reg.startsWith("w") ? `x${reg.slice(1)}` : reg);
|
|
4847
4856
|
if (sib && sib.startsWith("x")) zextW.delete(sib);
|
|
4848
4857
|
for (const [off, s] of [...slots.entries()].sort((a, b) => a[0] - b[0])) {
|
|
4849
4858
|
if (s.pendText !== void 0 && s.pendReg && (s.pendReg === reg || sib !== null && s.pendReg === sib)) {
|
|
4859
|
+
if (store_store_kill_on && at_idx >= 0 && s.pendIdx !== void 0 && no_slot_read_between(off, s.pendIdx + 1, at_idx) && next_slot_access_is_store(off, at_idx + 1)) {
|
|
4860
|
+
slots.delete(off);
|
|
4861
|
+
continue;
|
|
4862
|
+
}
|
|
4850
4863
|
out.push(s.pendText);
|
|
4851
4864
|
slots.delete(off);
|
|
4852
4865
|
continue;
|
|
@@ -4857,7 +4870,44 @@ function optimize_frame_slots(code) {
|
|
|
4857
4870
|
}
|
|
4858
4871
|
}
|
|
4859
4872
|
};
|
|
4860
|
-
|
|
4873
|
+
/** Whether any instruction in [from, to) READS slot `off` (or touches
|
|
4874
|
+
* it in a way the store-store reasoning cannot see through). */
|
|
4875
|
+
const no_slot_read_between = (off, from, to) => {
|
|
4876
|
+
for (let j = from; j < to && j < lines.length; j++) {
|
|
4877
|
+
const t = (lines[j] ?? "").replace(/\/\/.*$/, "").trim();
|
|
4878
|
+
if (!t) continue;
|
|
4879
|
+
const m = t.match(/^(str|stur|ldr|ldur|strb|ldrb|strh|ldrh)\s+\S+,\s*\[x29,\s*#(\d+)\]$/);
|
|
4880
|
+
if (m && parseInt(m[2], 10) === off && m[1].startsWith("ld")) return false;
|
|
4881
|
+
if (/^(ldp|stp)\b/.test(t) && t.includes(`#${off}`)) return false;
|
|
4882
|
+
if (/^add\b[^,]*,\s*x29\b/.test(t)) return false;
|
|
4883
|
+
if (/^(bl|blr|br|svc|ret)\b/.test(t)) return false;
|
|
4884
|
+
if (/^[A-Za-z_.$][\w.$]*:/.test(t)) return false;
|
|
4885
|
+
}
|
|
4886
|
+
return true;
|
|
4887
|
+
};
|
|
4888
|
+
const X29_SLOT_ANY = /\[x29,\s*#(\d+)\]/;
|
|
4889
|
+
/** Whether the next access to `off` in the current block (from
|
|
4890
|
+
* `from` to the next label/branch/call boundary) is a STORE. Loads,
|
|
4891
|
+
* block boundaries, escape-prone address builds, and anything
|
|
4892
|
+
* unparseable answer false (the conservative materialize). */
|
|
4893
|
+
const next_slot_access_is_store = (off, from) => {
|
|
4894
|
+
for (let j = from; j < lines.length; j++) {
|
|
4895
|
+
const t = (lines[j] ?? "").replace(/\/\/.*$/, "").trim();
|
|
4896
|
+
if (!t) continue;
|
|
4897
|
+
if (/^[A-Za-z_.$][\w.$]*:/.test(t)) return false;
|
|
4898
|
+
if (/^b/.test(t)) return false;
|
|
4899
|
+
if (/^(blr|br|svc|ret)\b/.test(t)) return false;
|
|
4900
|
+
if (/^add\b[^,]*,\s*x29\b/.test(t)) return false;
|
|
4901
|
+
if (/^(ldp|stp)\b/.test(t)) {
|
|
4902
|
+
const pm = t.match(X29_SLOT_ANY);
|
|
4903
|
+
return pm ? parseInt(pm[1], 10) === off && t.startsWith("stp") : false;
|
|
4904
|
+
}
|
|
4905
|
+
const m = t.match(/^(str|stur|ldr|ldur|strb|ldrb|strh|ldrh)\s+\S+,\s*\[x29,\s*#(\d+)\]$/);
|
|
4906
|
+
if (m) return parseInt(m[2], 10) === off && m[1].startsWith("str");
|
|
4907
|
+
}
|
|
4908
|
+
return false;
|
|
4909
|
+
};
|
|
4910
|
+
function process_frame_access(instr, off, i) {
|
|
4861
4911
|
const data_reg = instr.operands[0];
|
|
4862
4912
|
if (data_reg.kind !== "reg") {
|
|
4863
4913
|
out.push(instr.text);
|
|
@@ -4880,7 +4930,7 @@ function optimize_frame_slots(code) {
|
|
|
4880
4930
|
slots.set(off, fresh);
|
|
4881
4931
|
return;
|
|
4882
4932
|
}
|
|
4883
|
-
clobber(data_reg.name);
|
|
4933
|
+
clobber(data_reg.name, i);
|
|
4884
4934
|
}
|
|
4885
4935
|
let s = slots.get(off);
|
|
4886
4936
|
if (!s) {
|
|
@@ -4892,7 +4942,8 @@ function optimize_frame_slots(code) {
|
|
|
4892
4942
|
slots.set(off, {
|
|
4893
4943
|
pendKey: key,
|
|
4894
4944
|
pendText: instr.text,
|
|
4895
|
-
pendReg: data_reg.name
|
|
4945
|
+
pendReg: data_reg.name,
|
|
4946
|
+
pendIdx: i
|
|
4896
4947
|
});
|
|
4897
4948
|
return;
|
|
4898
4949
|
}
|
|
@@ -4952,7 +5003,7 @@ function optimize_frame_slots(code) {
|
|
|
4952
5003
|
const off = Number(instr.operands[2].value);
|
|
4953
5004
|
const next = parse_asm_instruction(lines[i + 1] ?? "", i + 2);
|
|
4954
5005
|
if (next && (next.op === "ldr" || next.op === "str") && next.operands.length === 2 && next.operands[1].kind === "mem" && next.operands[1].base === scratch && !next.operands[1].offset && !next.operands[1].scale && !next.operands[1].writeback) {
|
|
4955
|
-
clobber(scratch);
|
|
5006
|
+
clobber(scratch, i);
|
|
4956
5007
|
out.push(text);
|
|
4957
5008
|
const new_text = next.text.replace(/\[[^\]]*\]/, `[x29, #${off}]`);
|
|
4958
5009
|
process_frame_access({
|
|
@@ -4966,11 +5017,11 @@ function optimize_frame_slots(code) {
|
|
|
4966
5017
|
value: BigInt(off)
|
|
4967
5018
|
}
|
|
4968
5019
|
}]
|
|
4969
|
-
}, off);
|
|
5020
|
+
}, off, i);
|
|
4970
5021
|
i++;
|
|
4971
5022
|
continue;
|
|
4972
5023
|
}
|
|
4973
|
-
clobber(scratch);
|
|
5024
|
+
clobber(scratch, i);
|
|
4974
5025
|
flush_all();
|
|
4975
5026
|
clear_all();
|
|
4976
5027
|
out.push(text);
|
|
@@ -4991,7 +5042,7 @@ function optimize_frame_slots(code) {
|
|
|
4991
5042
|
continue;
|
|
4992
5043
|
}
|
|
4993
5044
|
if (mem && mem.kind === "mem" && mem.base === "x29" && mem.offset?.kind === "imm" && !mem.writeback) {
|
|
4994
|
-
process_frame_access(instr, Number(mem.offset.value));
|
|
5045
|
+
process_frame_access(instr, Number(mem.offset.value), i);
|
|
4995
5046
|
continue;
|
|
4996
5047
|
}
|
|
4997
5048
|
if (mem && mem.kind === "mem" && (mem.base === "x29" || mem.base === "fp") && (!mem.offset || mem.offset.kind === "reg" || mem.writeback)) {
|
|
@@ -5001,7 +5052,7 @@ function optimize_frame_slots(code) {
|
|
|
5001
5052
|
continue;
|
|
5002
5053
|
}
|
|
5003
5054
|
if (op === "bl" || op === "blr" || op === "br" || op === "svc") {
|
|
5004
|
-
for (const r of CALLER_SAVED) clobber(r);
|
|
5055
|
+
for (const r of CALLER_SAVED) clobber(r, i);
|
|
5005
5056
|
out.push(text);
|
|
5006
5057
|
continue;
|
|
5007
5058
|
}
|
|
@@ -5010,7 +5061,7 @@ function optimize_frame_slots(code) {
|
|
|
5010
5061
|
out.push(text);
|
|
5011
5062
|
continue;
|
|
5012
5063
|
}
|
|
5013
|
-
for (const d of instr_defs(instr)) clobber(d);
|
|
5064
|
+
for (const d of instr_defs(instr)) clobber(d, i);
|
|
5014
5065
|
const load_dst = instr.operands[0];
|
|
5015
5066
|
if ((op === "ldrb" || op === "ldrh" || op === "ldr") && load_dst.kind === "reg" && /^w\d+$/.test(load_dst.name)) zextW.set(`x${load_dst.name.slice(1)}`, op === "ldrb" ? 8 : op === "ldrh" ? 16 : 32);
|
|
5016
5067
|
out.push(text);
|
|
@@ -25251,11 +25302,15 @@ function lane_reg_of(name, plan) {
|
|
|
25251
25302
|
const idx = temps.indexOf(name);
|
|
25252
25303
|
return TEMP_REGS[idx] ?? "v4";
|
|
25253
25304
|
}
|
|
25254
|
-
function emit_lane_expr(e, status, plan, buffer_regs, idx, depth) {
|
|
25305
|
+
function emit_lane_expr(e, status, plan, buffer_regs, shifted_regs, idx, depth) {
|
|
25255
25306
|
switch (e.k) {
|
|
25256
|
-
case "load":
|
|
25257
|
-
|
|
25307
|
+
case "load": {
|
|
25308
|
+
const key = `${e.buffer} ${e.shift}`;
|
|
25309
|
+
const shifted = shifted_regs.get(key);
|
|
25310
|
+
if (shifted) emit_asm(status, `ldr q0, [${shifted}, ${idx}, lsl #4]\n`);
|
|
25311
|
+
else emit_asm(status, `ldr q0, [${buffer_regs.get(e.buffer) ?? "x11"}, ${idx}, lsl #4]\n`);
|
|
25258
25312
|
return;
|
|
25313
|
+
}
|
|
25259
25314
|
case "lit":
|
|
25260
25315
|
case "scalar":
|
|
25261
25316
|
if (plan.elem.float) {
|
|
@@ -25264,7 +25319,7 @@ function emit_lane_expr(e, status, plan, buffer_regs, idx, depth) {
|
|
|
25264
25319
|
} else {
|
|
25265
25320
|
build_node(e.node, status);
|
|
25266
25321
|
ensure_newline(status);
|
|
25267
|
-
emit_asm(status, `dup v0.${plan.elem.arr}, ${plan.elem.arr === "
|
|
25322
|
+
emit_asm(status, `dup v0.${plan.elem.arr}, ${plan.elem.arr === "2d" ? "x0" : "w0"}\n`);
|
|
25268
25323
|
}
|
|
25269
25324
|
return;
|
|
25270
25325
|
case "temp":
|
|
@@ -25277,11 +25332,11 @@ function emit_lane_expr(e, status, plan, buffer_regs, idx, depth) {
|
|
|
25277
25332
|
const right_mul = left_mul ? null : mul_of(e.right);
|
|
25278
25333
|
const mul = left_mul ?? right_mul;
|
|
25279
25334
|
if (mul && (e.op === "+" || !left_mul)) {
|
|
25280
|
-
emit_lane_expr(left_mul ? e.right : e.left, status, plan, buffer_regs, idx, depth + 1);
|
|
25335
|
+
emit_lane_expr(left_mul ? e.right : e.left, status, plan, buffer_regs, shifted_regs, idx, depth + 1);
|
|
25281
25336
|
emit_asm(status, `mov v8.16b, v0.16b\n`);
|
|
25282
|
-
emit_lane_expr(mul.left, status, plan, buffer_regs, idx, depth + 1);
|
|
25337
|
+
emit_lane_expr(mul.left, status, plan, buffer_regs, shifted_regs, idx, depth + 1);
|
|
25283
25338
|
emit_asm(status, `mov ${spill_reg(depth + 1)}.16b, v0.16b\n`);
|
|
25284
|
-
emit_lane_expr(mul.right, status, plan, buffer_regs, idx, depth + 1);
|
|
25339
|
+
emit_lane_expr(mul.right, status, plan, buffer_regs, shifted_regs, idx, depth + 1);
|
|
25285
25340
|
emit_asm(status, `mov v1.16b, v0.16b\n`);
|
|
25286
25341
|
emit_asm(status, `mov v0.16b, ${spill_reg(depth + 1)}.16b\n`);
|
|
25287
25342
|
emit_asm(status, `${e.op === "+" ? "fmla" : "fmls"} v8.2d, v0.2d, v1.2d\n`);
|
|
@@ -25289,9 +25344,9 @@ function emit_lane_expr(e, status, plan, buffer_regs, idx, depth) {
|
|
|
25289
25344
|
return;
|
|
25290
25345
|
}
|
|
25291
25346
|
}
|
|
25292
|
-
emit_lane_expr(e.left, status, plan, buffer_regs, idx, depth + 1);
|
|
25347
|
+
emit_lane_expr(e.left, status, plan, buffer_regs, shifted_regs, idx, depth + 1);
|
|
25293
25348
|
emit_asm(status, `mov ${spill_reg(depth)}.16b, v0.16b\n`);
|
|
25294
|
-
emit_lane_expr(e.right, status, plan, buffer_regs, idx, depth + 1);
|
|
25349
|
+
emit_lane_expr(e.right, status, plan, buffer_regs, shifted_regs, idx, depth + 1);
|
|
25295
25350
|
emit_asm(status, `mov v1.16b, v0.16b\n`);
|
|
25296
25351
|
emit_asm(status, `mov v0.16b, ${spill_reg(depth)}.16b\n`);
|
|
25297
25352
|
const { mnemonic, arr } = lane_op(e.op, plan.elem);
|
|
@@ -25346,19 +25401,19 @@ function lane_op(op, elem) {
|
|
|
25346
25401
|
};
|
|
25347
25402
|
}
|
|
25348
25403
|
}
|
|
25349
|
-
function emit_lane_stmt(lane, status, plan, buffer_regs, idx, acc_regs) {
|
|
25404
|
+
function emit_lane_stmt(lane, status, plan, buffer_regs, shifted_regs, idx, acc_regs) {
|
|
25350
25405
|
if (lane.kind === "temp_def") {
|
|
25351
|
-
emit_lane_expr(lane.value, status, plan, buffer_regs, idx, 0);
|
|
25406
|
+
emit_lane_expr(lane.value, status, plan, buffer_regs, shifted_regs, idx, 0);
|
|
25352
25407
|
emit_asm(status, `mov ${lane_reg_of(lane.name, plan)}.16b, v0.16b\n`);
|
|
25353
25408
|
return;
|
|
25354
25409
|
}
|
|
25355
25410
|
if (lane.kind === "reduction") {
|
|
25356
|
-
emit_lane_expr(lane.operand, status, plan, buffer_regs, idx, 0);
|
|
25411
|
+
emit_lane_expr(lane.operand, status, plan, buffer_regs, shifted_regs, idx, 0);
|
|
25357
25412
|
const acc = acc_regs.get(lane.name) ?? "v2";
|
|
25358
25413
|
emit_asm(status, `${lane.op === "+" ? plan.elem.float ? "fadd" : "add" : "fmul"} ${acc}.${plan.elem.arr}, ${acc}.${plan.elem.arr}, v0.${plan.elem.arr}\n`);
|
|
25359
25414
|
return;
|
|
25360
25415
|
}
|
|
25361
|
-
emit_lane_expr(lane.value, status, plan, buffer_regs, idx, 0);
|
|
25416
|
+
emit_lane_expr(lane.value, status, plan, buffer_regs, shifted_regs, idx, 0);
|
|
25362
25417
|
emit_asm(status, `str q0, [${buffer_regs.get(lane.buffer) ?? "x11"}, ${idx}, lsl #4]\n`);
|
|
25363
25418
|
}
|
|
25364
25419
|
/**
|
|
@@ -25391,6 +25446,37 @@ function emit_neon_vector_loop(plan, status) {
|
|
|
25391
25446
|
emit_asm(status, `ldr ${reg}, [x9, #8]\n`);
|
|
25392
25447
|
buffer_regs.set(b.name, reg);
|
|
25393
25448
|
});
|
|
25449
|
+
const SHIFT_PTR_REGS = [
|
|
25450
|
+
"x15",
|
|
25451
|
+
"x16",
|
|
25452
|
+
"x17"
|
|
25453
|
+
];
|
|
25454
|
+
const shifted_regs = /* @__PURE__ */ new Map();
|
|
25455
|
+
{
|
|
25456
|
+
const elem_size = 16 / plan.elem.group_elems;
|
|
25457
|
+
let spi = 0;
|
|
25458
|
+
const seen = /* @__PURE__ */ new Set();
|
|
25459
|
+
const visit = (e) => {
|
|
25460
|
+
if (!e) return;
|
|
25461
|
+
if (e.k === "load" && e.shift > 0) {
|
|
25462
|
+
const key = `${e.buffer} ${e.shift}`;
|
|
25463
|
+
if (!seen.has(key)) {
|
|
25464
|
+
seen.add(key);
|
|
25465
|
+
if (spi >= SHIFT_PTR_REGS.length) return;
|
|
25466
|
+
const base = buffer_regs.get(e.buffer) ?? "x11";
|
|
25467
|
+
const reg = SHIFT_PTR_REGS[spi++];
|
|
25468
|
+
emit_asm(status, `add ${reg}, ${base}, #${e.shift * elem_size}\n`);
|
|
25469
|
+
shifted_regs.set(key, reg);
|
|
25470
|
+
}
|
|
25471
|
+
} else if (e.k === "op") {
|
|
25472
|
+
visit(e.left);
|
|
25473
|
+
visit(e.right);
|
|
25474
|
+
}
|
|
25475
|
+
};
|
|
25476
|
+
for (const lane of plan.lanes) if (lane.kind === "store") visit(lane.value);
|
|
25477
|
+
else if (lane.kind === "temp_def") visit(lane.value);
|
|
25478
|
+
else if (lane.kind === "reduction") visit(lane.operand);
|
|
25479
|
+
}
|
|
25394
25480
|
const acc_regs = /* @__PURE__ */ new Map();
|
|
25395
25481
|
plan.reductions.forEach((r, i) => {
|
|
25396
25482
|
const reg = REDUCTION_REGS[i] ?? "v2";
|
|
@@ -25400,12 +25486,13 @@ function emit_neon_vector_loop(plan, status) {
|
|
|
25400
25486
|
} else {
|
|
25401
25487
|
build_node(r.init_node, status);
|
|
25402
25488
|
ensure_newline(status);
|
|
25403
|
-
emit_asm(status, `dup ${reg}.${plan.elem.arr}, ${plan.elem.arr === "
|
|
25489
|
+
emit_asm(status, `dup ${reg}.${plan.elem.arr}, ${plan.elem.arr === "2d" ? "x0" : "w0"}\n`);
|
|
25404
25490
|
}
|
|
25405
25491
|
acc_regs.set(r.name, reg);
|
|
25406
25492
|
});
|
|
25407
25493
|
build_node(plan.bound_node, status);
|
|
25408
25494
|
ensure_newline(status);
|
|
25495
|
+
if (plan.max_shift > 0) emit_asm(status, `sub x0, x0, #${plan.max_shift}\n`);
|
|
25409
25496
|
emit_asm(status, `mov x9, x0\n`);
|
|
25410
25497
|
emit_asm(status, `asr x9, x9, #${shift}\n`);
|
|
25411
25498
|
emit_asm(status, `bic x9, x9, #1\n`);
|
|
@@ -25414,9 +25501,9 @@ function emit_neon_vector_loop(plan, status) {
|
|
|
25414
25501
|
emit_asm(status, `${label}:\n`);
|
|
25415
25502
|
emit_asm(status, `cmp x10, x9\n`);
|
|
25416
25503
|
emit_asm(status, `b.hs ${label}_end\n`);
|
|
25417
|
-
for (const lane of plan.lanes) emit_lane_stmt(lane, status, plan, buffer_regs, "x10", acc_regs);
|
|
25504
|
+
for (const lane of plan.lanes) emit_lane_stmt(lane, status, plan, buffer_regs, shifted_regs, "x10", acc_regs);
|
|
25418
25505
|
emit_asm(status, `add x14, x10, #1\n`);
|
|
25419
|
-
for (const lane of plan.lanes) emit_lane_stmt(lane, status, plan, buffer_regs, "x14", acc_regs);
|
|
25506
|
+
for (const lane of plan.lanes) emit_lane_stmt(lane, status, plan, buffer_regs, shifted_regs, "x14", acc_regs);
|
|
25420
25507
|
emit_asm(status, `add x10, x10, #2\n`);
|
|
25421
25508
|
emit_asm(status, `b ${label}\n`);
|
|
25422
25509
|
emit_asm(status, `${label}_end:\n`);
|
|
@@ -27223,8 +27310,9 @@ function flag_form_enabled() {
|
|
|
27223
27310
|
*
|
|
27224
27311
|
* Element kinds (tranche 2): the method pair determines the descriptor —
|
|
27225
27312
|
* `load_float`/`store_float` (f64, `.2d`), `load_int`/`store_int` (8-byte
|
|
27226
|
-
* int, `.2d`), `
|
|
27227
|
-
*
|
|
27313
|
+
* int, `.2d`), `load_u32`/`store_u32` (4-byte int, `.4s`),
|
|
27314
|
+
* `load_u8`/`store_u8` (byte, `.16b` — 16 lanes per group). All accesses in
|
|
27315
|
+
* one loop must agree on the descriptor; lanes per group follow the width.
|
|
27228
27316
|
*
|
|
27229
27317
|
* Soundness model (why this is legal):
|
|
27230
27318
|
*
|
|
@@ -27233,12 +27321,24 @@ function flag_form_enabled() {
|
|
|
27233
27321
|
* a vector iteration touches the same element set the scalar iterations
|
|
27234
27322
|
* it replaces would. Lane k reads element base+k before lane k writes
|
|
27235
27323
|
* element base+k; no lane reads an element another lane writes, whatever
|
|
27236
|
-
* buffers alias.
|
|
27237
|
-
*
|
|
27324
|
+
* buffers alias.
|
|
27325
|
+
* - SHIFTED READS (per-element event-order rule): `load_T(i + c)` with a
|
|
27326
|
+
* LITERAL c >= 1 is admitted. Scalar iteration k reads base+k+c, which
|
|
27327
|
+
* (c > 0) is written — if at all — at iteration k+c, AFTER the read; the
|
|
27328
|
+
* vector group loads every lane before any lane stores, so the group
|
|
27329
|
+
* sees exactly the scalar pre-group values (reads of elements the group
|
|
27330
|
+
* itself writes land on their pre-group contents, matching scalar
|
|
27331
|
+
* order). c <= 0 reads elements an EARLIER same-group lane wrote — the
|
|
27332
|
+
* scalar loop would see the fresh value, the vector lanes would not —
|
|
27333
|
+
* refused. STORES stay exact-induction. The trip limit shrinks by the
|
|
27334
|
+
* max shift (max lane read = base+group-1+max_shift stays < bound);
|
|
27335
|
+
* the scalar tail runs the original loop verbatim, so its own guard
|
|
27336
|
+
* still covers the remainder.
|
|
27238
27337
|
* - TRIP SEMANTICS: the vector loop runs whole 16-byte groups (unrolled to
|
|
27239
|
-
* two groups per iteration); the limit is floor(N / group)
|
|
27240
|
-
* to a multiple of the unroll — floor semantics keep every
|
|
27241
|
-
* < N for ANY signed bound (negative →
|
|
27338
|
+
* two groups per iteration); the limit is floor((N - max_shift) / group)
|
|
27339
|
+
* rounded down to a multiple of the unroll — floor semantics keep every
|
|
27340
|
+
* lane index (+ its read shift) < N for ANY signed bound (negative →
|
|
27341
|
+
* loop skipped). The ORIGINAL
|
|
27242
27342
|
* scalar loop is emitted unchanged afterwards as the tail (the induction
|
|
27243
27343
|
* is synced to the vector loop's exit counter first), covering the
|
|
27244
27344
|
* remainder — including the sub-unroll and negative-N cases where the
|
|
@@ -27272,6 +27372,9 @@ function flag_form_enabled() {
|
|
|
27272
27372
|
/** Max distinct Buffers per plan — the preheader pins each data pointer in
|
|
27273
27373
|
* a dedicated scratch register (x11..x13). */
|
|
27274
27374
|
const MAX_BUFFERS = 3;
|
|
27375
|
+
/** Adjusted read-pointer registers the emitter can materialize (one per
|
|
27376
|
+
* distinct (buffer, shift > 0) pair -- x15/x16/x17, call-free bodies). */
|
|
27377
|
+
const MAX_SHIFTED_PTRS = 3;
|
|
27275
27378
|
/** Max per-lane temps (v4..v7). */
|
|
27276
27379
|
const MAX_TEMPS = 4;
|
|
27277
27380
|
/** Max binary-op nesting depth in lane expressions (spill regs v14→v11). */
|
|
@@ -27305,10 +27408,18 @@ const ELEM_DESCS = [
|
|
|
27305
27408
|
arr: "4s",
|
|
27306
27409
|
group_elems: 4,
|
|
27307
27410
|
shift: 2
|
|
27411
|
+
},
|
|
27412
|
+
{
|
|
27413
|
+
load: "load_u8",
|
|
27414
|
+
store: "store_u8",
|
|
27415
|
+
float: false,
|
|
27416
|
+
arr: "16b",
|
|
27417
|
+
group_elems: 16,
|
|
27418
|
+
shift: 4
|
|
27308
27419
|
}
|
|
27309
27420
|
];
|
|
27310
27421
|
function class_of_elem(elem) {
|
|
27311
|
-
return elem.float ? "float" : elem.group_elems === 2 ? "e8" : "e4";
|
|
27422
|
+
return elem.float ? "float" : elem.group_elems === 2 ? "e8" : elem.group_elems === 4 ? "e4" : "e1";
|
|
27312
27423
|
}
|
|
27313
27424
|
function class_of_type_name(name) {
|
|
27314
27425
|
if (!name) return null;
|
|
@@ -27317,6 +27428,7 @@ function class_of_type_name(name) {
|
|
|
27317
27428
|
const size = aarch64_size(name);
|
|
27318
27429
|
if (size === 8) return "e8";
|
|
27319
27430
|
if (size === 4) return "e4";
|
|
27431
|
+
if (size === 1) return "e1";
|
|
27320
27432
|
} catch {}
|
|
27321
27433
|
return null;
|
|
27322
27434
|
}
|
|
@@ -27568,7 +27680,15 @@ function alloc_temps_of(s, walk) {
|
|
|
27568
27680
|
if (has_allocations(d.value)) return null;
|
|
27569
27681
|
const cls = class_of_type_name(d.type?.name);
|
|
27570
27682
|
if (!cls) return null;
|
|
27571
|
-
if (walk.elem && cls !== class_of_elem(walk.elem))
|
|
27683
|
+
if (walk.elem && cls !== class_of_elem(walk.elem)) {
|
|
27684
|
+
const nir = ast_to_nir_shallow(d.value);
|
|
27685
|
+
if (!nir || shifted_index_of(nir, walk.induction) === null) return null;
|
|
27686
|
+
out.push({
|
|
27687
|
+
name: d.name,
|
|
27688
|
+
value: d.value
|
|
27689
|
+
});
|
|
27690
|
+
continue;
|
|
27691
|
+
}
|
|
27572
27692
|
walk.temp_classes.set(d.name, cls);
|
|
27573
27693
|
out.push({
|
|
27574
27694
|
name: d.name,
|
|
@@ -27577,6 +27697,24 @@ function alloc_temps_of(s, walk) {
|
|
|
27577
27697
|
}
|
|
27578
27698
|
return out;
|
|
27579
27699
|
}
|
|
27700
|
+
/** `i + c` with i the induction and c an int literal >= 1 -- the shifted
|
|
27701
|
+
* read index (event-order rule: scalar iteration k reads base+k+c before
|
|
27702
|
+
* iteration k+c writes it; the vector group loads all lanes before any
|
|
27703
|
+
* stores, so the group sees exactly the scalar pre-group values). */
|
|
27704
|
+
function shifted_index_of(e, induction) {
|
|
27705
|
+
if (e.kind !== "binary") return null;
|
|
27706
|
+
if (e.node.op !== "+") return null;
|
|
27707
|
+
const lit_of = (x) => {
|
|
27708
|
+
if (x.kind !== "leaf" || x.name) return null;
|
|
27709
|
+
const v = node_value(x);
|
|
27710
|
+
if (v === null || !/^\d+$/.test(v)) return null;
|
|
27711
|
+
return Number(v);
|
|
27712
|
+
};
|
|
27713
|
+
const base_of = (x) => x.kind === "leaf" && x.name === induction;
|
|
27714
|
+
const c = base_of(e.left) ? lit_of(e.right) : base_of(e.right) ? lit_of(e.left) : null;
|
|
27715
|
+
if (c === null || c < 1) return null;
|
|
27716
|
+
return c;
|
|
27717
|
+
}
|
|
27580
27718
|
function lane_expr(e, walk, induction, status, depth) {
|
|
27581
27719
|
if (depth > walk.max_depth) walk.max_depth = depth;
|
|
27582
27720
|
if (depth > MAX_OP_DEPTH) return null;
|
|
@@ -27587,13 +27725,34 @@ function lane_expr(e, walk, induction, status, depth) {
|
|
|
27587
27725
|
if (e.receiver.kind !== "leaf" || !e.receiver.name) return null;
|
|
27588
27726
|
if (e.facts.args.length !== 1) return null;
|
|
27589
27727
|
const idx = e.facts.args[0];
|
|
27590
|
-
|
|
27728
|
+
let shift = 0;
|
|
27729
|
+
if (idx.kind === "leaf") {
|
|
27730
|
+
if (idx.name !== induction) {
|
|
27731
|
+
const aliased = walk.induction_alias.get(idx.name);
|
|
27732
|
+
if (aliased === void 0) return null;
|
|
27733
|
+
shift = aliased;
|
|
27734
|
+
}
|
|
27735
|
+
} else if (idx.kind === "binary") {
|
|
27736
|
+
const c = shifted_index_of(idx, induction);
|
|
27737
|
+
if (c === null) return null;
|
|
27738
|
+
shift = c;
|
|
27739
|
+
} else return null;
|
|
27591
27740
|
if (!is_buffer_type_name(type_from_value_node$1(e.receiver.node)?.name ?? "")) return null;
|
|
27592
27741
|
if (buffer_index_of(walk, e.receiver.name, e.receiver.node) === null) return null;
|
|
27742
|
+
if (shift > 0) {
|
|
27743
|
+
const key = `${e.receiver.name} ${shift}`;
|
|
27744
|
+
if (!walk.shifted.has(key)) walk.shifted.set(key, {
|
|
27745
|
+
buffer: e.receiver.name,
|
|
27746
|
+
node: e.receiver.node,
|
|
27747
|
+
shift
|
|
27748
|
+
});
|
|
27749
|
+
if (walk.shifted.size > MAX_SHIFTED_PTRS) return null;
|
|
27750
|
+
}
|
|
27593
27751
|
return {
|
|
27594
27752
|
k: "load",
|
|
27595
27753
|
buffer: e.receiver.name,
|
|
27596
|
-
node: e.receiver.node
|
|
27754
|
+
node: e.receiver.node,
|
|
27755
|
+
shift
|
|
27597
27756
|
};
|
|
27598
27757
|
}
|
|
27599
27758
|
case "leaf": {
|
|
@@ -27790,6 +27949,26 @@ function extract_bound$1(bound, induction) {
|
|
|
27790
27949
|
name: null
|
|
27791
27950
|
};
|
|
27792
27951
|
}
|
|
27952
|
+
if (bound.kind === "binary") {
|
|
27953
|
+
const op_node = bound.node;
|
|
27954
|
+
if (op_node.op !== "+" && op_node.op !== "-") return null;
|
|
27955
|
+
const lit_of = (x) => x.kind === "leaf" && !x.name && node_value(x) !== null && INT_LIT_RE.test(node_value(x));
|
|
27956
|
+
const base_name_of = (x) => {
|
|
27957
|
+
if (x.kind === "path") return x.receiver.kind === "leaf" && x.receiver.name ? x.receiver.name : null;
|
|
27958
|
+
if (x.kind === "leaf") return x.name ?? null;
|
|
27959
|
+
return null;
|
|
27960
|
+
};
|
|
27961
|
+
let base = null;
|
|
27962
|
+
if (op_node.op === "+") {
|
|
27963
|
+
if (lit_of(bound.left)) base = base_name_of(bound.right);
|
|
27964
|
+
else if (lit_of(bound.right)) base = base_name_of(bound.left);
|
|
27965
|
+
} else if (lit_of(bound.right)) base = base_name_of(bound.left);
|
|
27966
|
+
if (base === null || base === induction) return null;
|
|
27967
|
+
return {
|
|
27968
|
+
node: bound.node,
|
|
27969
|
+
name: base
|
|
27970
|
+
};
|
|
27971
|
+
}
|
|
27793
27972
|
if (bound.kind !== "path") return null;
|
|
27794
27973
|
const root = bound.receiver;
|
|
27795
27974
|
if (root.kind !== "leaf" || !root.name || root.name === induction) return null;
|
|
@@ -27844,6 +28023,9 @@ function plan_common(lanes_raw, bound_node, bound_name, induction, index, list,
|
|
|
27844
28023
|
lanes: [],
|
|
27845
28024
|
buffers: [],
|
|
27846
28025
|
buffer_index: /* @__PURE__ */ new Map(),
|
|
28026
|
+
shifted: /* @__PURE__ */ new Map(),
|
|
28027
|
+
induction_alias: /* @__PURE__ */ new Map(),
|
|
28028
|
+
induction,
|
|
27847
28029
|
temps: [],
|
|
27848
28030
|
defs,
|
|
27849
28031
|
defed_so_far: /* @__PURE__ */ new Set(),
|
|
@@ -27859,9 +28041,13 @@ function plan_common(lanes_raw, bound_node, bound_name, induction, index, list,
|
|
|
27859
28041
|
}
|
|
27860
28042
|
continue;
|
|
27861
28043
|
}
|
|
28044
|
+
if (process.env.NOMEN_DBG_NEON) console.error(`NEON: [${status.current_function_name}] refuse: stmt kind ${s.kind}`);
|
|
28045
|
+
return null;
|
|
28046
|
+
}
|
|
28047
|
+
if (bound_name !== null && defs.has(bound_name)) {
|
|
28048
|
+
if (process.env.NOMEN_DBG_NEON) console.error(`NEON: [${status.current_function_name}] refuse: bound ${bound_name} defined in loop`);
|
|
27862
28049
|
return null;
|
|
27863
28050
|
}
|
|
27864
|
-
if (bound_name !== null && defs.has(bound_name)) return null;
|
|
27865
28051
|
const reduction_inits = /* @__PURE__ */ new Map();
|
|
27866
28052
|
for (const name of reduction_candidates.keys()) {
|
|
27867
28053
|
const scan = scan_init(list, index, name);
|
|
@@ -27872,6 +28058,9 @@ function plan_common(lanes_raw, bound_node, bound_name, induction, index, list,
|
|
|
27872
28058
|
lanes: [],
|
|
27873
28059
|
buffers: [],
|
|
27874
28060
|
buffer_index: /* @__PURE__ */ new Map(),
|
|
28061
|
+
shifted: /* @__PURE__ */ new Map(),
|
|
28062
|
+
induction_alias: /* @__PURE__ */ new Map(),
|
|
28063
|
+
induction,
|
|
27875
28064
|
temps: [],
|
|
27876
28065
|
defs,
|
|
27877
28066
|
defed_so_far: /* @__PURE__ */ new Set(),
|
|
@@ -27951,8 +28140,27 @@ function plan_common(lanes_raw, bound_node, bound_name, induction, index, list,
|
|
|
27951
28140
|
for (const t of temps) {
|
|
27952
28141
|
const nir = ast_to_nir_shallow(t.value);
|
|
27953
28142
|
if (!nir) return null;
|
|
28143
|
+
const add = shifted_index_of(nir, induction);
|
|
28144
|
+
if (add !== null) {
|
|
28145
|
+
walk.induction_alias.set(t.name, add);
|
|
28146
|
+
continue;
|
|
28147
|
+
}
|
|
27954
28148
|
const tvalue = lane_expr(nir, walk, induction, status, 0);
|
|
27955
|
-
if (!tvalue)
|
|
28149
|
+
if (!tvalue) {
|
|
28150
|
+
if (process.env.NOMEN_DBG_NEON) {
|
|
28151
|
+
const dump = (x, d) => {
|
|
28152
|
+
if (!x || d > 3) return "?";
|
|
28153
|
+
const nt = x.node?.node_type ?? "?";
|
|
28154
|
+
const op = x.node?.op ?? x.node?.operator ?? "";
|
|
28155
|
+
const val = x.node?.node_type === "value" ? `=${x.node.value}` : "";
|
|
28156
|
+
const name = x.name ? `@${x.name}` : "";
|
|
28157
|
+
const kids = x.left || x.right ? `(${dump(x.left, d + 1)} ${dump(x.right, d + 1)})` : x.inner !== void 0 ? `(${dump(x.inner, d + 1)})` : x.facts ? `(${x.args?.map((a) => dump(a, d + 1)).join(",")})` : "";
|
|
28158
|
+
return `<${x.kind} ${nt}${op ? ":" + op : ""}${val}${name}${kids}>`;
|
|
28159
|
+
};
|
|
28160
|
+
console.error(`NEON: temp refused t=${t.name} ${dump(nir, 0)}`);
|
|
28161
|
+
}
|
|
28162
|
+
return null;
|
|
28163
|
+
}
|
|
27956
28164
|
walk.defed_so_far.add(t.name);
|
|
27957
28165
|
walk.temps.push(t.name);
|
|
27958
28166
|
walk.lanes.push({
|
|
@@ -27970,17 +28178,41 @@ function plan_common(lanes_raw, bound_node, bound_name, induction, index, list,
|
|
|
27970
28178
|
value
|
|
27971
28179
|
});
|
|
27972
28180
|
}
|
|
27973
|
-
if (walk.temps.length > MAX_TEMPS)
|
|
27974
|
-
|
|
28181
|
+
if (walk.temps.length > MAX_TEMPS) {
|
|
28182
|
+
if (process.env.NOMEN_DBG_NEON) console.error(`NEON: [${status.current_function_name}] refuse: too many temps`);
|
|
28183
|
+
return null;
|
|
28184
|
+
}
|
|
28185
|
+
if (!walk.lanes.some((l) => l.kind === "store") && reductions.length === 0) {
|
|
28186
|
+
if (process.env.NOMEN_DBG_NEON) console.error(`NEON: [${status.current_function_name}] refuse: no store lane`);
|
|
28187
|
+
return null;
|
|
28188
|
+
}
|
|
27975
28189
|
if (!walk.elem) return null;
|
|
27976
28190
|
const want = class_of_elem(walk.elem);
|
|
27977
|
-
for (const cls of walk.temp_classes.values()) if (cls !== want)
|
|
28191
|
+
for (const cls of walk.temp_classes.values()) if (cls !== want) {
|
|
28192
|
+
if (process.env.NOMEN_DBG_NEON) console.error(`NEON: [${status.current_function_name}] refuse: temp class ${cls} != ${want}`);
|
|
28193
|
+
return null;
|
|
28194
|
+
}
|
|
27978
28195
|
if (reductions.length > MAX_REDUCTIONS) return null;
|
|
27979
28196
|
if (walk.temps.length > 0) {
|
|
27980
28197
|
const after = /* @__PURE__ */ new Set();
|
|
27981
28198
|
for (let k = index + 1; k < list.length; k++) stmt_reads(list[k], after);
|
|
27982
|
-
for (const t of walk.temps) if (after.has(t))
|
|
28199
|
+
for (const t of walk.temps) if (after.has(t)) {
|
|
28200
|
+
if (process.env.NOMEN_DBG_NEON) console.error(`NEON: [${status.current_function_name}] refuse: temp ${t} read after loop`);
|
|
28201
|
+
return null;
|
|
28202
|
+
}
|
|
27983
28203
|
}
|
|
28204
|
+
let max_shift = 0;
|
|
28205
|
+
const scan_shift = (e) => {
|
|
28206
|
+
if (!e) return;
|
|
28207
|
+
if (e.k === "load" && e.shift > max_shift) max_shift = e.shift;
|
|
28208
|
+
if (e.k === "op") {
|
|
28209
|
+
scan_shift(e.left);
|
|
28210
|
+
scan_shift(e.right);
|
|
28211
|
+
}
|
|
28212
|
+
};
|
|
28213
|
+
for (const lane of walk.lanes) if (lane.kind === "store") scan_shift(lane.value);
|
|
28214
|
+
else if (lane.kind === "temp_def") scan_shift(lane.value);
|
|
28215
|
+
else if (lane.kind === "reduction") scan_shift(lane.operand);
|
|
27984
28216
|
return {
|
|
27985
28217
|
induction,
|
|
27986
28218
|
bound_node,
|
|
@@ -27988,7 +28220,8 @@ function plan_common(lanes_raw, bound_node, bound_name, induction, index, list,
|
|
|
27988
28220
|
buffers: walk.buffers,
|
|
27989
28221
|
elem: walk.elem,
|
|
27990
28222
|
reductions,
|
|
27991
|
-
fma_contract: allow_reductions
|
|
28223
|
+
fma_contract: allow_reductions,
|
|
28224
|
+
max_shift
|
|
27992
28225
|
};
|
|
27993
28226
|
}
|
|
27994
28227
|
/**
|
|
@@ -28016,6 +28249,7 @@ function plan_vector_loop(nstmt, index, list, status) {
|
|
|
28016
28249
|
}
|
|
28017
28250
|
const init = scan_init(list, index, induction);
|
|
28018
28251
|
if (!init.ok || !init.init_node || !is_zero_ast_node(init.init_node)) return null;
|
|
28252
|
+
if (process.env.NOMEN_DBG_NEON) console.error(`NEON: [${status.current_function_name}] loop@${index} reaching plan_common`);
|
|
28019
28253
|
return plan_common(lanes_raw, bound.node, bound.name, induction, index, list, status, status.fast_math === true);
|
|
28020
28254
|
}
|
|
28021
28255
|
/**
|
|
@@ -29648,6 +29882,12 @@ const active_splices = /* @__PURE__ */ new Set();
|
|
|
29648
29882
|
function inline_splice_active(struct_name, func_name) {
|
|
29649
29883
|
return active_splices.has(`${struct_name}.${func_name}`);
|
|
29650
29884
|
}
|
|
29885
|
+
/** How many inline splices are currently open (build_access_node passes
|
|
29886
|
+
* this to is_auto_inline_method as the admission depth, so the emit-time
|
|
29887
|
+
* chain cap agrees with the plan-time recursion). */
|
|
29888
|
+
function inline_splice_depth() {
|
|
29889
|
+
return active_splices.size;
|
|
29890
|
+
}
|
|
29651
29891
|
function begin_inline_splice(struct_name, func_name) {
|
|
29652
29892
|
active_splices.add(`${struct_name}.${func_name}`);
|
|
29653
29893
|
}
|
|
@@ -29751,6 +29991,11 @@ function build_inline_method(struct_node, func, status) {
|
|
|
29751
29991
|
const old_return_buffer_offset = status.return_buffer_stack_offset;
|
|
29752
29992
|
const old_function_return_type = status.function_return_type;
|
|
29753
29993
|
const old_register_allocations = status.register_allocations;
|
|
29994
|
+
const old_nir_site_allocs = status.nir_site_allocs;
|
|
29995
|
+
const old_int_dest_hint = status.int_dest_hint;
|
|
29996
|
+
const old_float_dest_hint = status.float_dest_hint;
|
|
29997
|
+
status.int_dest_hint = void 0;
|
|
29998
|
+
status.float_dest_hint = void 0;
|
|
29754
29999
|
const old_buffer_data_cache = status.buffer_data_cache;
|
|
29755
30000
|
const old_buffer_fold_cache = status.buffer_fold_cache;
|
|
29756
30001
|
const old_array_ptr_cache = status.array_ptr_cache;
|
|
@@ -29900,6 +30145,9 @@ function build_inline_method(struct_node, func, status) {
|
|
|
29900
30145
|
status.return_buffer_stack_offset = old_return_buffer_offset;
|
|
29901
30146
|
status.function_return_type = old_function_return_type;
|
|
29902
30147
|
status.register_allocations = old_register_allocations;
|
|
30148
|
+
status.nir_site_allocs = old_nir_site_allocs;
|
|
30149
|
+
status.int_dest_hint = old_int_dest_hint;
|
|
30150
|
+
status.float_dest_hint = old_float_dest_hint;
|
|
29903
30151
|
status.buffer_data_cache = old_buffer_data_cache;
|
|
29904
30152
|
status.buffer_fold_cache = old_buffer_fold_cache;
|
|
29905
30153
|
status.array_ptr_cache = old_array_ptr_cache;
|
|
@@ -29930,6 +30178,11 @@ function build_inline_function(func, status) {
|
|
|
29930
30178
|
const old_return_buffer_offset = status.return_buffer_stack_offset;
|
|
29931
30179
|
const old_function_return_type = status.function_return_type;
|
|
29932
30180
|
const old_register_allocations = status.register_allocations;
|
|
30181
|
+
const old_nir_site_allocs = status.nir_site_allocs;
|
|
30182
|
+
const old_int_dest_hint = status.int_dest_hint;
|
|
30183
|
+
const old_float_dest_hint = status.float_dest_hint;
|
|
30184
|
+
status.int_dest_hint = void 0;
|
|
30185
|
+
status.float_dest_hint = void 0;
|
|
29933
30186
|
const old_buffer_data_cache = status.buffer_data_cache;
|
|
29934
30187
|
const old_buffer_fold_cache = status.buffer_fold_cache;
|
|
29935
30188
|
const old_array_ptr_cache = status.array_ptr_cache;
|
|
@@ -30060,6 +30313,9 @@ function build_inline_function(func, status) {
|
|
|
30060
30313
|
status.return_buffer_stack_offset = old_return_buffer_offset;
|
|
30061
30314
|
status.function_return_type = old_function_return_type;
|
|
30062
30315
|
status.register_allocations = old_register_allocations;
|
|
30316
|
+
status.nir_site_allocs = old_nir_site_allocs;
|
|
30317
|
+
status.int_dest_hint = old_int_dest_hint;
|
|
30318
|
+
status.float_dest_hint = old_float_dest_hint;
|
|
30063
30319
|
status.buffer_data_cache = old_buffer_data_cache;
|
|
30064
30320
|
status.buffer_fold_cache = old_buffer_fold_cache;
|
|
30065
30321
|
status.array_ptr_cache = old_array_ptr_cache;
|
|
@@ -44267,6 +44523,8 @@ function load_nursery_struct_address(target, status) {
|
|
|
44267
44523
|
const MAX_STATEMENTS = 15;
|
|
44268
44524
|
const MAX_AUTO_METHOD_STATEMENTS = 3;
|
|
44269
44525
|
const MAX_AUTO_METHOD_PARAMS = 3;
|
|
44526
|
+
const MAX_AUTO_CALLING_STATEMENTS = 10;
|
|
44527
|
+
const MAX_AUTO_SPLICE_DEPTH = 2;
|
|
44270
44528
|
/**
|
|
44271
44529
|
* ensure/grow/clear-shaped methods (ASM_PLAN_7 tranche 7): small real
|
|
44272
44530
|
* bodies the method-inline path can splice WITHOUT the explicit `inline`
|
|
@@ -44278,11 +44536,19 @@ const MAX_AUTO_METHOD_PARAMS = 3;
|
|
|
44278
44536
|
* scalar-or-void return. The standalone body is still emitted — trait
|
|
44279
44537
|
* dispatch, method values, and overflow-arg call sites keep taking the
|
|
44280
44538
|
* `bl`, so nothing here may skip emission.
|
|
44539
|
+
*
|
|
44540
|
+
* `depth` is the number of inline splices already open at the dispatch
|
|
44541
|
+
* site (build_access_node passes `active_splices.size`); the admission
|
|
44542
|
+
* recursion for a candidate's callees runs at `depth + 1`, so a chain
|
|
44543
|
+
* deeper than MAX_AUTO_SPLICE_DEPTH is refused everywhere — the plan-time
|
|
44544
|
+
* and emit-time verdicts then agree.
|
|
44281
44545
|
*/
|
|
44282
|
-
function is_auto_inline_method(func) {
|
|
44546
|
+
function is_auto_inline_method(func, depth = 0) {
|
|
44283
44547
|
if (!func.has_body) return false;
|
|
44284
44548
|
if (func.is_inline) return false;
|
|
44285
|
-
|
|
44549
|
+
const calls = body_calls(func.statements);
|
|
44550
|
+
const max_statements = calls || depth > 0 ? MAX_AUTO_CALLING_STATEMENTS : MAX_AUTO_METHOD_STATEMENTS;
|
|
44551
|
+
if (func.statements.length === 0 || func.statements.length > max_statements) return false;
|
|
44286
44552
|
if (func.returns_move) return false;
|
|
44287
44553
|
if (func.statements.some((s) => s.node_type === "raw")) return false;
|
|
44288
44554
|
if (func.params.length > MAX_AUTO_METHOD_PARAMS) return false;
|
|
@@ -44305,22 +44571,55 @@ function is_auto_inline_method(func) {
|
|
|
44305
44571
|
if (func.return_type?.is_array || func.return_type?.is_view) return false;
|
|
44306
44572
|
if (func.return_type?.is_nullable) return false;
|
|
44307
44573
|
if (func.return_type?.name && !SIMPLE_TYPES.includes(func.return_type.name)) return false;
|
|
44308
|
-
if (
|
|
44309
|
-
return
|
|
44574
|
+
if (!calls) return true;
|
|
44575
|
+
if (depth >= MAX_AUTO_SPLICE_DEPTH) return false;
|
|
44576
|
+
return body_calls_splice_through(func.statements, depth);
|
|
44310
44577
|
}
|
|
44311
|
-
|
|
44312
|
-
function body_has_call(node) {
|
|
44578
|
+
function body_calls(node) {
|
|
44313
44579
|
if (!node) return false;
|
|
44314
44580
|
if (Array.isArray(node)) {
|
|
44315
|
-
for (const item of node) if (
|
|
44581
|
+
for (const item of node) if (body_calls(item)) return true;
|
|
44316
44582
|
return false;
|
|
44317
44583
|
}
|
|
44318
44584
|
if (typeof node !== "object") return false;
|
|
44319
44585
|
const nt = node.node_type;
|
|
44320
44586
|
if (nt === "access_func" || nt === "func_call") return true;
|
|
44321
|
-
for (const child of child_nodes(node)) if (
|
|
44587
|
+
for (const child of child_nodes(node)) if (body_calls(child)) return true;
|
|
44322
44588
|
return false;
|
|
44323
44589
|
}
|
|
44590
|
+
/**
|
|
44591
|
+
* The call-bearing cost model: every call in the subtree must either be
|
|
44592
|
+
* an `extern` (a `bl` whether the host splices or not — the expansion
|
|
44593
|
+
* changes nothing about the call count) or splice through — the callee is
|
|
44594
|
+
* a user-`inline` method (the splice path already handles call-bearing
|
|
44595
|
+
* user bodies) or itself admitted as an auto candidate at `depth + 1`.
|
|
44596
|
+
* Anything else keeps the host on the real `bl` path: expanding a call
|
|
44597
|
+
* whose callee cannot splice replaces one `bl` with the same `bl` plus
|
|
44598
|
+
* parking, which is the shape the tranche-7 receipts measured as a loss.
|
|
44599
|
+
*
|
|
44600
|
+
* `resolved_function` is the checker's stamp — present on every call the
|
|
44601
|
+
* checker accepted, and clone_node preserves it. A call without one is
|
|
44602
|
+
* refused (conservative).
|
|
44603
|
+
*/
|
|
44604
|
+
function body_calls_splice_through(node, depth) {
|
|
44605
|
+
if (!node) return true;
|
|
44606
|
+
if (Array.isArray(node)) {
|
|
44607
|
+
for (const item of node) if (!body_calls_splice_through(item, depth)) return false;
|
|
44608
|
+
return true;
|
|
44609
|
+
}
|
|
44610
|
+
if (typeof node !== "object") return true;
|
|
44611
|
+
const any_node = node;
|
|
44612
|
+
const nt = any_node.node_type;
|
|
44613
|
+
if (nt === "access_func" || nt === "func_call") {
|
|
44614
|
+
const callee = any_node.resolved_function;
|
|
44615
|
+
if (!callee) return false;
|
|
44616
|
+
if (callee.is_extern) return true;
|
|
44617
|
+
if (callee.is_inline) return true;
|
|
44618
|
+
return is_auto_inline_method(callee, depth + 1);
|
|
44619
|
+
}
|
|
44620
|
+
for (const child of child_nodes(node)) if (!body_calls_splice_through(child, depth)) return false;
|
|
44621
|
+
return true;
|
|
44622
|
+
}
|
|
44324
44623
|
function scan_inline_candidates(root) {
|
|
44325
44624
|
const counts = /* @__PURE__ */ new Map();
|
|
44326
44625
|
const defs = /* @__PURE__ */ new Map();
|
|
@@ -46120,7 +46419,7 @@ function build_access_method(node, access_func, status) {
|
|
|
46120
46419
|
}
|
|
46121
46420
|
}
|
|
46122
46421
|
const target_struct = status.structs.find((s) => s.name === mono_struct_name);
|
|
46123
|
-
const inline_func = target_struct?.functions.find((f) => (f.is_inline || is_auto_inline_method(f)) && f.name === access_func.name && (access_func.mangled_name ? mangled_label(f, mono_struct_name) === access_func.mangled_name : true));
|
|
46422
|
+
const inline_func = target_struct?.functions.find((f) => (f.is_inline || is_auto_inline_method(f, inline_splice_depth())) && f.name === access_func.name && (access_func.mangled_name ? mangled_label(f, mono_struct_name) === access_func.mangled_name : true));
|
|
46124
46423
|
const trait_target = status.traits.find((t) => t.name === target_type.name);
|
|
46125
46424
|
const trait_func = trait_target?.functions.find((f) => f.name === access_func.name);
|
|
46126
46425
|
if (trait_target && trait_func) {
|
|
@@ -50448,6 +50747,17 @@ function expr_to_string(node, status) {
|
|
|
50448
50747
|
}
|
|
50449
50748
|
}
|
|
50450
50749
|
}
|
|
50750
|
+
if (node.node_type === "op") {
|
|
50751
|
+
const op = node;
|
|
50752
|
+
if (op.op === "+" || op.op === "-") {
|
|
50753
|
+
const l = op.left_value ? expr_to_string(op.left_value, status) : void 0;
|
|
50754
|
+
const r = op.right_value ? expr_to_string(op.right_value, status) : void 0;
|
|
50755
|
+
if (l && r && !/^[+-]?\d+$/.test(l)) {
|
|
50756
|
+
if (/^[+-]?\d+$/.test(r)) return `${l} ${op.op} ${r}`;
|
|
50757
|
+
if (/^[A-Za-z_]\w*(\.\w+)*$/.test(r)) return `${l} ${op.op} ${r}`;
|
|
50758
|
+
}
|
|
50759
|
+
}
|
|
50760
|
+
}
|
|
50451
50761
|
}
|
|
50452
50762
|
/**
|
|
50453
50763
|
* Rewrite a `<base>.<field>` path through the assumed parallel-length
|
|
@@ -50688,6 +50998,19 @@ function apply_bounds(condition, status, is_loop = false) {
|
|
|
50688
50998
|
var_decl.path_bounds.set(bound.var_name, path_entry);
|
|
50689
50999
|
}
|
|
50690
51000
|
}
|
|
51001
|
+
const expr_mirror_target = /^\w+(\.\w+)+$/.test(bound.expr) ? bound.expr.split(".")[0] : void 0;
|
|
51002
|
+
let mirror_entry;
|
|
51003
|
+
if (expr_mirror_target && bound.expr !== bound.var_name) {
|
|
51004
|
+
const expr_decl = status.values.findLast((v) => v.name === expr_mirror_target);
|
|
51005
|
+
if (expr_decl) {
|
|
51006
|
+
if (!expr_decl.path_bounds) expr_decl.path_bounds = /* @__PURE__ */ new Map();
|
|
51007
|
+
mirror_entry = expr_decl.path_bounds.get(bound.expr);
|
|
51008
|
+
if (!mirror_entry) {
|
|
51009
|
+
mirror_entry = {};
|
|
51010
|
+
expr_decl.path_bounds.set(bound.expr, mirror_entry);
|
|
51011
|
+
}
|
|
51012
|
+
}
|
|
51013
|
+
}
|
|
50691
51014
|
const push_unique = (arr, expr) => {
|
|
50692
51015
|
const out = arr ?? [];
|
|
50693
51016
|
if (!out.includes(expr)) out.push(expr);
|
|
@@ -50700,9 +51023,11 @@ function apply_bounds(condition, status, is_loop = false) {
|
|
|
50700
51023
|
if (inclusive) {
|
|
50701
51024
|
var_decl.upper_bound_inclusive_exprs = push_unique(var_decl.upper_bound_inclusive_exprs, bound.expr);
|
|
50702
51025
|
if (path_entry) path_entry.upper_inclusive = push_unique(path_entry.upper_inclusive, bound.expr);
|
|
51026
|
+
if (mirror_entry && bound.var_name) mirror_entry.lower_inclusive = push_unique(mirror_entry.lower_inclusive, bound.var_name);
|
|
50703
51027
|
} else {
|
|
50704
51028
|
var_decl.upper_bound_exprs = push_unique(var_decl.upper_bound_exprs, bound.expr);
|
|
50705
51029
|
if (path_entry) path_entry.upper = push_unique(path_entry.upper, bound.expr);
|
|
51030
|
+
if (mirror_entry && bound.var_name) mirror_entry.lower = push_unique(mirror_entry.lower, bound.var_name);
|
|
50706
51031
|
}
|
|
50707
51032
|
var_decl.upper_bound_expr = bound.expr;
|
|
50708
51033
|
const num = numeric_interval(string_to_node(bound.expr), status);
|
|
@@ -50722,16 +51047,21 @@ function apply_bounds(condition, status, is_loop = false) {
|
|
|
50722
51047
|
if (inclusive) {
|
|
50723
51048
|
var_decl.lower_bound_inclusive_exprs = push_unique(var_decl.lower_bound_inclusive_exprs, bound.expr);
|
|
50724
51049
|
if (path_entry) path_entry.lower_inclusive = push_unique(path_entry.lower_inclusive, bound.expr);
|
|
51050
|
+
if (mirror_entry && bound.var_name) mirror_entry.upper_inclusive = push_unique(mirror_entry.upper_inclusive, bound.var_name);
|
|
50725
51051
|
} else {
|
|
50726
51052
|
var_decl.lower_bound_exprs = push_unique(var_decl.lower_bound_exprs, bound.expr);
|
|
50727
51053
|
if (path_entry) path_entry.lower = push_unique(path_entry.lower, bound.expr);
|
|
51054
|
+
if (mirror_entry && bound.var_name) mirror_entry.upper = push_unique(mirror_entry.upper, bound.var_name);
|
|
50728
51055
|
}
|
|
50729
51056
|
var_decl.lower_bound_expr = bound.expr;
|
|
50730
51057
|
const num = numeric_interval(string_to_node(bound.expr), status);
|
|
50731
51058
|
if (num) {
|
|
50732
51059
|
const lo = bound.op === ">=" ? num.lower : num.upper;
|
|
50733
51060
|
if (var_decl.range_lower === void 0 || lo > var_decl.range_lower) var_decl.range_lower = lo;
|
|
50734
|
-
if (path_entry
|
|
51061
|
+
if (path_entry) {
|
|
51062
|
+
if (is_loop) path_entry.range_lower = lo;
|
|
51063
|
+
else if (path_entry.range_lower === void 0 || lo > path_entry.range_lower) path_entry.range_lower = lo;
|
|
51064
|
+
}
|
|
50735
51065
|
}
|
|
50736
51066
|
}
|
|
50737
51067
|
apply_inverse_numeric_bound(bound, var_decl, status);
|
|
@@ -50858,6 +51188,39 @@ function apply_shifted_bound(condition, status, is_loop = false) {
|
|
|
50858
51188
|
}
|
|
50859
51189
|
}
|
|
50860
51190
|
/**
|
|
51191
|
+
* Sweep every stored fact that REFERENCES `token` (any variable reassigned
|
|
51192
|
+
* or shadowed, or bound through a `ref` argument): offset facts are only as
|
|
51193
|
+
* fresh as their offset operand, and facts live on the BOUNDED variable's
|
|
51194
|
+
* entry — a mutation of the offset variable would otherwise leave a stale
|
|
51195
|
+
* `j < n - d` in place after `d` changed. Entries whose OWN name matches
|
|
51196
|
+
* are skipped (their facts die by entry shadowing on the reassignment
|
|
51197
|
+
* itself).
|
|
51198
|
+
*/
|
|
51199
|
+
function invalidate_token_facts(token, status) {
|
|
51200
|
+
const re = new RegExp(`\\b${token}\\b`);
|
|
51201
|
+
const scrub = (arr) => {
|
|
51202
|
+
if (!arr) return arr;
|
|
51203
|
+
const out = arr.filter((e) => !re.test(e));
|
|
51204
|
+
return out.length === arr.length ? arr : out.length > 0 ? out : void 0;
|
|
51205
|
+
};
|
|
51206
|
+
for (const v of status.values) {
|
|
51207
|
+
if (v.name === token) continue;
|
|
51208
|
+
if (!(v.upper_bound_exprs && v.upper_bound_exprs.some((e) => re.test(e)) || v.lower_bound_exprs && v.lower_bound_exprs.some((e) => re.test(e)) || v.upper_bound_inclusive_exprs && v.upper_bound_inclusive_exprs.some((e) => re.test(e)) || v.lower_bound_inclusive_exprs && v.lower_bound_inclusive_exprs.some((e) => re.test(e)))) continue;
|
|
51209
|
+
v.upper_bound_exprs = scrub(v.upper_bound_exprs);
|
|
51210
|
+
v.lower_bound_exprs = scrub(v.lower_bound_exprs);
|
|
51211
|
+
v.upper_bound_inclusive_exprs = scrub(v.upper_bound_inclusive_exprs);
|
|
51212
|
+
v.lower_bound_inclusive_exprs = scrub(v.lower_bound_inclusive_exprs);
|
|
51213
|
+
if (v.upper_bound_expr && re.test(v.upper_bound_expr)) v.upper_bound_expr = void 0;
|
|
51214
|
+
if (v.lower_bound_expr && re.test(v.lower_bound_expr)) v.lower_bound_expr = void 0;
|
|
51215
|
+
if (v.path_bounds) for (const pb of v.path_bounds.values()) {
|
|
51216
|
+
pb.upper = scrub(pb.upper);
|
|
51217
|
+
pb.upper_inclusive = scrub(pb.upper_inclusive);
|
|
51218
|
+
pb.lower = scrub(pb.lower);
|
|
51219
|
+
pb.lower_inclusive = scrub(pb.lower_inclusive);
|
|
51220
|
+
}
|
|
51221
|
+
}
|
|
51222
|
+
}
|
|
51223
|
+
/**
|
|
50861
51224
|
* Substitute `out` and parameter names in a return-constraint expression with
|
|
50862
51225
|
* caller-side expressions. Used to translate a function's `out TYPE: contract`
|
|
50863
51226
|
* into bounds on the caller's LHS variable.
|
|
@@ -51455,8 +51818,13 @@ function evaluate_operation(op, status) {
|
|
|
51455
51818
|
if (left_decl.upper_bound_exprs.some((u) => upper_implies(u, target_str))) return true;
|
|
51456
51819
|
for (const u of left_decl.upper_bound_exprs) {
|
|
51457
51820
|
const v = parse_offset_expr(u);
|
|
51458
|
-
if (v && v.offset
|
|
51821
|
+
if (v && v.offset <= 0 && u !== target_str) {
|
|
51459
51822
|
const vdecl = status.values.findLast((vv) => vv.name === v.base);
|
|
51823
|
+
const vpath = v.base.includes(".") ? status.values.findLast((vv) => vv.name === v.base.split(".")[0])?.path_bounds?.get(v.base) : void 0;
|
|
51824
|
+
if (vpath) {
|
|
51825
|
+
if (vpath.upper?.includes(target_str)) return true;
|
|
51826
|
+
if (vpath.upper_inclusive?.includes(target_str)) return true;
|
|
51827
|
+
}
|
|
51460
51828
|
if (vdecl?.upper_bound_exprs?.includes(target_str)) return true;
|
|
51461
51829
|
if (vdecl?.upper_bound_inclusive_exprs?.includes(target_str) && op.op === "<=") return true;
|
|
51462
51830
|
if (op.op === "<" && vdecl?.upper_bound_inclusive_exprs?.includes(target_str)) return true;
|
|
@@ -51468,8 +51836,13 @@ function evaluate_operation(op, status) {
|
|
|
51468
51836
|
if (left_decl.upper_bound_inclusive_exprs.some((u) => upper_implies(u, target_str))) return true;
|
|
51469
51837
|
for (const u of left_decl.upper_bound_inclusive_exprs) {
|
|
51470
51838
|
const v = parse_offset_expr(u);
|
|
51471
|
-
if (v && v.offset
|
|
51839
|
+
if (v && v.offset <= 0 && u !== target_str) {
|
|
51472
51840
|
const vdecl = status.values.findLast((vv) => vv.name === v.base);
|
|
51841
|
+
const vpath = v.base.includes(".") ? status.values.findLast((vv) => vv.name === v.base.split(".")[0])?.path_bounds?.get(v.base) : void 0;
|
|
51842
|
+
if (vpath) {
|
|
51843
|
+
if (vpath.upper?.includes(target_str)) return true;
|
|
51844
|
+
if (vpath.upper_inclusive?.includes(target_str)) return true;
|
|
51845
|
+
}
|
|
51473
51846
|
if (vdecl?.upper_bound_exprs?.includes(target_str)) return true;
|
|
51474
51847
|
if (vdecl?.upper_bound_inclusive_exprs?.includes(target_str)) return true;
|
|
51475
51848
|
}
|
|
@@ -52341,6 +52714,7 @@ function check_declaration_node(decl, status) {
|
|
|
52341
52714
|
});
|
|
52342
52715
|
if (decl.value) {
|
|
52343
52716
|
move_closure_source(decl.value, status);
|
|
52717
|
+
invalidate_token_facts(decl.name, status);
|
|
52344
52718
|
track_assignment_bounds(decl.name, decl.value, status);
|
|
52345
52719
|
}
|
|
52346
52720
|
} else {
|
|
@@ -52465,7 +52839,10 @@ function check_declaration_node(decl, status) {
|
|
|
52465
52839
|
is_global: !in_function(status),
|
|
52466
52840
|
trait_slot_conformer: trait_slot_conformer_of_decl(declaration, decl.type, decl.value, status)
|
|
52467
52841
|
});
|
|
52468
|
-
if (decl.value)
|
|
52842
|
+
if (decl.value) {
|
|
52843
|
+
invalidate_token_facts(decl.name, status);
|
|
52844
|
+
track_assignment_bounds(decl.name, decl.value, status);
|
|
52845
|
+
}
|
|
52469
52846
|
if (status.pending_return_bounds) {
|
|
52470
52847
|
const pending = status.pending_return_bounds.get(decl.name);
|
|
52471
52848
|
if (pending) {
|
|
@@ -56689,6 +57066,7 @@ function check_function_call(node, status, func, target_type, self_value, self_p
|
|
|
56689
57066
|
const param_type = type_from_value_node(param, status);
|
|
56690
57067
|
const param_value = value_from_value_node(param);
|
|
56691
57068
|
const has_ref_keyword = node.ref_param_indices?.includes(i) ?? false;
|
|
57069
|
+
if (has_ref_keyword && param.node_type === "value") invalidate_token_facts(param.value, status);
|
|
56692
57070
|
if (func && func_param && func.has_body && (!func.scope || func.scope.node_type !== "struct") && !func.params.some((p) => p.is_self_param) && !func.address_escaped && !func_param.is_self_param && !func_param.is_variadic && !func_param.type.is_ref && !func_param.type.is_nullable && func_param.type.name && !is_owning_struct_type_requiring_move(func_param.type, status)) {
|
|
56693
57071
|
const owned_struct = status.structs.find((st) => st.name === func_param.type.name && !st.is_simple_type && !st.is_class);
|
|
56694
57072
|
if (owned_struct && !(owned_struct.traits ?? []).length && direct_string_fields(owned_struct).length > 0 && !struct_needs_destroy(owned_struct, status)) {
|
|
@@ -56837,18 +57215,56 @@ function check_function_call(node, status, func, target_type, self_value, self_p
|
|
|
56837
57215
|
if (base_path) base_name = base_path.split(".")[0];
|
|
56838
57216
|
}
|
|
56839
57217
|
}
|
|
57218
|
+
let offset_token;
|
|
57219
|
+
let offset_token_sign = 1;
|
|
57220
|
+
if (!base_name && !base_path) {
|
|
57221
|
+
const tok = (n) => n.node_type === "value" && /^[A-Za-z_]\w*$/.test(n.value) ? n.value : void 0;
|
|
57222
|
+
if (pop.left_value.node_type === "value" && tok(pop.right_value)) {
|
|
57223
|
+
base_name = pop.left_value.value;
|
|
57224
|
+
offset_token = tok(pop.right_value);
|
|
57225
|
+
offset_token_sign = pop.op === "-" ? -1 : 1;
|
|
57226
|
+
} else if (pop.right_value.node_type === "value" && tok(pop.left_value)) {
|
|
57227
|
+
base_name = pop.right_value.value;
|
|
57228
|
+
offset_token = tok(pop.left_value);
|
|
57229
|
+
offset_token_sign = pop.op === "-" ? -1 : 1;
|
|
57230
|
+
}
|
|
57231
|
+
}
|
|
56840
57232
|
if (base_name) {
|
|
56841
57233
|
const decl = status.values.findLast((v) => v.name === base_name);
|
|
56842
57234
|
if (decl) {
|
|
56843
57235
|
const shift = (exprs) => exprs?.map((e) => shift_offset_expr(e, c));
|
|
56844
57236
|
range_lower = decl.range_lower !== void 0 ? decl.range_lower + c : void 0;
|
|
56845
57237
|
range_upper = decl.range_upper !== void 0 ? decl.range_upper + c : void 0;
|
|
56846
|
-
|
|
56847
|
-
|
|
56848
|
-
|
|
56849
|
-
|
|
56850
|
-
|
|
56851
|
-
|
|
57238
|
+
if (offset_token) {
|
|
57239
|
+
range_upper = void 0;
|
|
57240
|
+
range_lower = void 0;
|
|
57241
|
+
if (offset_token_sign > 0 && decl.range_lower !== void 0) {
|
|
57242
|
+
const tok_decl = status.values.findLast((v) => v.name === offset_token);
|
|
57243
|
+
if (tok_decl?.range_lower !== void 0 && tok_decl.range_lower >= 0) range_lower = decl.range_lower;
|
|
57244
|
+
}
|
|
57245
|
+
const cancel = (exprs) => {
|
|
57246
|
+
if (!exprs) return void 0;
|
|
57247
|
+
const out = [];
|
|
57248
|
+
for (const e of exprs) {
|
|
57249
|
+
const m = e.match(new RegExp(`^(\\w+(?:\\.\\w+)*)\\s*([+-])\\s*${offset_token}$`));
|
|
57250
|
+
if (m && m[2] === (offset_token_sign > 0 ? "-" : "+")) out.push(m[1]);
|
|
57251
|
+
}
|
|
57252
|
+
return out.length > 0 ? out : void 0;
|
|
57253
|
+
};
|
|
57254
|
+
upper_bound_exprs = cancel(decl.upper_bound_exprs);
|
|
57255
|
+
lower_bound_exprs = cancel(decl.lower_bound_exprs);
|
|
57256
|
+
upper_bound_inclusive_exprs = cancel(decl.upper_bound_inclusive_exprs);
|
|
57257
|
+
lower_bound_inclusive_exprs = cancel(decl.lower_bound_inclusive_exprs);
|
|
57258
|
+
upper_bound_expr = void 0;
|
|
57259
|
+
lower_bound_expr = void 0;
|
|
57260
|
+
} else {
|
|
57261
|
+
upper_bound_exprs = shift(decl.upper_bound_exprs);
|
|
57262
|
+
lower_bound_exprs = shift(decl.lower_bound_exprs);
|
|
57263
|
+
upper_bound_inclusive_exprs = shift(decl.upper_bound_inclusive_exprs);
|
|
57264
|
+
lower_bound_inclusive_exprs = shift(decl.lower_bound_inclusive_exprs);
|
|
57265
|
+
upper_bound_expr = decl.upper_bound_expr ? shift_offset_expr(decl.upper_bound_expr, c) : void 0;
|
|
57266
|
+
lower_bound_expr = decl.lower_bound_expr ? shift_offset_expr(decl.lower_bound_expr, c) : void 0;
|
|
57267
|
+
}
|
|
56852
57268
|
alias_of = decl.alias_of ? shift_offset_expr(decl.alias_of, c) : base_path !== void 0 ? shift_offset_expr(base_path, c) : void 0;
|
|
56853
57269
|
}
|
|
56854
57270
|
}
|
|
@@ -58251,6 +58667,7 @@ function check_assignment_node(assign, status) {
|
|
|
58251
58667
|
const self_snapshot = left_value_name === left_value.name && (!is_compound && is_self_shifted(assign.right_value, left_value.name) || is_trackable_compound) ? snapshot_bounds(left_value.name, status) : void 0;
|
|
58252
58668
|
left_value.range_lower = void 0;
|
|
58253
58669
|
left_value.range_upper = void 0;
|
|
58670
|
+
invalidate_token_facts(left_value.name, status);
|
|
58254
58671
|
left_value.upper_bound_expr = void 0;
|
|
58255
58672
|
left_value.lower_bound_expr = void 0;
|
|
58256
58673
|
left_value.upper_bound_exprs = void 0;
|