nomen-lang 0.6.11 → 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 +497 -70
- 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);
|
|
@@ -20884,10 +20935,12 @@ function build_function_node$1(node, status) {
|
|
|
20884
20935
|
if (moved_param_save_slots.size > 0 && node.name !== "main") {
|
|
20885
20936
|
const need_guard = return_is_class;
|
|
20886
20937
|
const need_save = !!node.return_type?.name;
|
|
20938
|
+
const returns_fat_pair = node.return_type?.name === "string" && !node.return_type.is_array;
|
|
20887
20939
|
let return_save;
|
|
20888
20940
|
if (need_guard || need_save) {
|
|
20889
|
-
return_save = allocate_stack_space(status, 8);
|
|
20890
|
-
emit_asm(status, `
|
|
20941
|
+
return_save = allocate_stack_space(status, returns_fat_pair ? 16 : 8);
|
|
20942
|
+
if (returns_fat_pair) emit_asm(status, `stp x0, x1, [x29, #${return_save}]\n`);
|
|
20943
|
+
else emit_asm(status, `str x0, [x29, #${return_save}]\n`);
|
|
20891
20944
|
}
|
|
20892
20945
|
for (const [name, info] of moved_param_save_slots) {
|
|
20893
20946
|
if (moved_set?.has(name)) continue;
|
|
@@ -20913,7 +20966,10 @@ function build_function_node$1(node, status) {
|
|
|
20913
20966
|
emit_free(status);
|
|
20914
20967
|
if (need_guard) emit_asm(status, `${keep_prefix}_${name}:\n`);
|
|
20915
20968
|
}
|
|
20916
|
-
if (need_guard || need_save)
|
|
20969
|
+
if (need_guard || need_save) {
|
|
20970
|
+
if (returns_fat_pair) emit_asm(status, `ldp x0, x1, [x29, #${return_save}]\n`);
|
|
20971
|
+
else emit_asm(status, `ldr x0, [x29, #${return_save}]\n`);
|
|
20972
|
+
}
|
|
20917
20973
|
}
|
|
20918
20974
|
const total_stack = Math.ceil((status.stack_size || 0) / 16) * 16;
|
|
20919
20975
|
status.code = status.code.replace(`sub sp, sp, #${stack_placeholder}`, total_stack > 0 ? `sub sp, sp, #${total_stack}` : `// no stack needed`);
|
|
@@ -22306,11 +22362,13 @@ function build_struct_functions$1(node, status) {
|
|
|
22306
22362
|
if (moved_param_save_slots.size > 0) {
|
|
22307
22363
|
const ret_is_class = !!func.return_type?.name && !!status.structs.find((s) => s.name === func.return_type.name && s.is_class);
|
|
22308
22364
|
const need_save = !!func.return_type?.name;
|
|
22365
|
+
const returns_fat_pair = func.return_type?.name === "string" && !func.return_type.is_array;
|
|
22309
22366
|
const keep_prefix = `.Lkeep_mparam_${func_label.replace(/[^\w]/g, "_")}`;
|
|
22310
22367
|
let return_save;
|
|
22311
22368
|
if (ret_is_class || need_save) {
|
|
22312
|
-
return_save = allocate_stack_space(status, 8);
|
|
22313
|
-
emit_asm(status, `
|
|
22369
|
+
return_save = allocate_stack_space(status, returns_fat_pair ? 16 : 8);
|
|
22370
|
+
if (returns_fat_pair) emit_asm(status, `stp x0, x1, [x29, #${return_save}]\n`);
|
|
22371
|
+
else emit_asm(status, `str x0, [x29, #${return_save}]\n`);
|
|
22314
22372
|
}
|
|
22315
22373
|
for (const [name, info] of moved_param_save_slots) {
|
|
22316
22374
|
if (status.moved?.has(name) && !moved_before.has(name)) continue;
|
|
@@ -22336,7 +22394,10 @@ function build_struct_functions$1(node, status) {
|
|
|
22336
22394
|
emit_free(status);
|
|
22337
22395
|
if (ret_is_class) emit_asm(status, `${keep_prefix}_${name}:\n`);
|
|
22338
22396
|
}
|
|
22339
|
-
if (ret_is_class || need_save)
|
|
22397
|
+
if (ret_is_class || need_save) {
|
|
22398
|
+
if (returns_fat_pair) emit_asm(status, `ldp x0, x1, [x29, #${return_save}]\n`);
|
|
22399
|
+
else emit_asm(status, `ldr x0, [x29, #${return_save}]\n`);
|
|
22400
|
+
}
|
|
22340
22401
|
}
|
|
22341
22402
|
const total_stack = Math.ceil((status.stack_size || 0) / 16) * 16;
|
|
22342
22403
|
status.code = status.code.replace(`sub sp, sp, #${stack_placeholder}`, total_stack > 0 ? `sub sp, sp, #${total_stack}` : `// no stack needed`);
|
|
@@ -25241,11 +25302,15 @@ function lane_reg_of(name, plan) {
|
|
|
25241
25302
|
const idx = temps.indexOf(name);
|
|
25242
25303
|
return TEMP_REGS[idx] ?? "v4";
|
|
25243
25304
|
}
|
|
25244
|
-
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) {
|
|
25245
25306
|
switch (e.k) {
|
|
25246
|
-
case "load":
|
|
25247
|
-
|
|
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`);
|
|
25248
25312
|
return;
|
|
25313
|
+
}
|
|
25249
25314
|
case "lit":
|
|
25250
25315
|
case "scalar":
|
|
25251
25316
|
if (plan.elem.float) {
|
|
@@ -25254,7 +25319,7 @@ function emit_lane_expr(e, status, plan, buffer_regs, idx, depth) {
|
|
|
25254
25319
|
} else {
|
|
25255
25320
|
build_node(e.node, status);
|
|
25256
25321
|
ensure_newline(status);
|
|
25257
|
-
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`);
|
|
25258
25323
|
}
|
|
25259
25324
|
return;
|
|
25260
25325
|
case "temp":
|
|
@@ -25267,11 +25332,11 @@ function emit_lane_expr(e, status, plan, buffer_regs, idx, depth) {
|
|
|
25267
25332
|
const right_mul = left_mul ? null : mul_of(e.right);
|
|
25268
25333
|
const mul = left_mul ?? right_mul;
|
|
25269
25334
|
if (mul && (e.op === "+" || !left_mul)) {
|
|
25270
|
-
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);
|
|
25271
25336
|
emit_asm(status, `mov v8.16b, v0.16b\n`);
|
|
25272
|
-
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);
|
|
25273
25338
|
emit_asm(status, `mov ${spill_reg(depth + 1)}.16b, v0.16b\n`);
|
|
25274
|
-
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);
|
|
25275
25340
|
emit_asm(status, `mov v1.16b, v0.16b\n`);
|
|
25276
25341
|
emit_asm(status, `mov v0.16b, ${spill_reg(depth + 1)}.16b\n`);
|
|
25277
25342
|
emit_asm(status, `${e.op === "+" ? "fmla" : "fmls"} v8.2d, v0.2d, v1.2d\n`);
|
|
@@ -25279,9 +25344,9 @@ function emit_lane_expr(e, status, plan, buffer_regs, idx, depth) {
|
|
|
25279
25344
|
return;
|
|
25280
25345
|
}
|
|
25281
25346
|
}
|
|
25282
|
-
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);
|
|
25283
25348
|
emit_asm(status, `mov ${spill_reg(depth)}.16b, v0.16b\n`);
|
|
25284
|
-
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);
|
|
25285
25350
|
emit_asm(status, `mov v1.16b, v0.16b\n`);
|
|
25286
25351
|
emit_asm(status, `mov v0.16b, ${spill_reg(depth)}.16b\n`);
|
|
25287
25352
|
const { mnemonic, arr } = lane_op(e.op, plan.elem);
|
|
@@ -25336,19 +25401,19 @@ function lane_op(op, elem) {
|
|
|
25336
25401
|
};
|
|
25337
25402
|
}
|
|
25338
25403
|
}
|
|
25339
|
-
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) {
|
|
25340
25405
|
if (lane.kind === "temp_def") {
|
|
25341
|
-
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);
|
|
25342
25407
|
emit_asm(status, `mov ${lane_reg_of(lane.name, plan)}.16b, v0.16b\n`);
|
|
25343
25408
|
return;
|
|
25344
25409
|
}
|
|
25345
25410
|
if (lane.kind === "reduction") {
|
|
25346
|
-
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);
|
|
25347
25412
|
const acc = acc_regs.get(lane.name) ?? "v2";
|
|
25348
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`);
|
|
25349
25414
|
return;
|
|
25350
25415
|
}
|
|
25351
|
-
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);
|
|
25352
25417
|
emit_asm(status, `str q0, [${buffer_regs.get(lane.buffer) ?? "x11"}, ${idx}, lsl #4]\n`);
|
|
25353
25418
|
}
|
|
25354
25419
|
/**
|
|
@@ -25381,6 +25446,37 @@ function emit_neon_vector_loop(plan, status) {
|
|
|
25381
25446
|
emit_asm(status, `ldr ${reg}, [x9, #8]\n`);
|
|
25382
25447
|
buffer_regs.set(b.name, reg);
|
|
25383
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
|
+
}
|
|
25384
25480
|
const acc_regs = /* @__PURE__ */ new Map();
|
|
25385
25481
|
plan.reductions.forEach((r, i) => {
|
|
25386
25482
|
const reg = REDUCTION_REGS[i] ?? "v2";
|
|
@@ -25390,12 +25486,13 @@ function emit_neon_vector_loop(plan, status) {
|
|
|
25390
25486
|
} else {
|
|
25391
25487
|
build_node(r.init_node, status);
|
|
25392
25488
|
ensure_newline(status);
|
|
25393
|
-
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`);
|
|
25394
25490
|
}
|
|
25395
25491
|
acc_regs.set(r.name, reg);
|
|
25396
25492
|
});
|
|
25397
25493
|
build_node(plan.bound_node, status);
|
|
25398
25494
|
ensure_newline(status);
|
|
25495
|
+
if (plan.max_shift > 0) emit_asm(status, `sub x0, x0, #${plan.max_shift}\n`);
|
|
25399
25496
|
emit_asm(status, `mov x9, x0\n`);
|
|
25400
25497
|
emit_asm(status, `asr x9, x9, #${shift}\n`);
|
|
25401
25498
|
emit_asm(status, `bic x9, x9, #1\n`);
|
|
@@ -25404,9 +25501,9 @@ function emit_neon_vector_loop(plan, status) {
|
|
|
25404
25501
|
emit_asm(status, `${label}:\n`);
|
|
25405
25502
|
emit_asm(status, `cmp x10, x9\n`);
|
|
25406
25503
|
emit_asm(status, `b.hs ${label}_end\n`);
|
|
25407
|
-
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);
|
|
25408
25505
|
emit_asm(status, `add x14, x10, #1\n`);
|
|
25409
|
-
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);
|
|
25410
25507
|
emit_asm(status, `add x10, x10, #2\n`);
|
|
25411
25508
|
emit_asm(status, `b ${label}\n`);
|
|
25412
25509
|
emit_asm(status, `${label}_end:\n`);
|
|
@@ -27213,8 +27310,9 @@ function flag_form_enabled() {
|
|
|
27213
27310
|
*
|
|
27214
27311
|
* Element kinds (tranche 2): the method pair determines the descriptor —
|
|
27215
27312
|
* `load_float`/`store_float` (f64, `.2d`), `load_int`/`store_int` (8-byte
|
|
27216
|
-
* int, `.2d`), `
|
|
27217
|
-
*
|
|
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.
|
|
27218
27316
|
*
|
|
27219
27317
|
* Soundness model (why this is legal):
|
|
27220
27318
|
*
|
|
@@ -27223,12 +27321,24 @@ function flag_form_enabled() {
|
|
|
27223
27321
|
* a vector iteration touches the same element set the scalar iterations
|
|
27224
27322
|
* it replaces would. Lane k reads element base+k before lane k writes
|
|
27225
27323
|
* element base+k; no lane reads an element another lane writes, whatever
|
|
27226
|
-
* buffers alias.
|
|
27227
|
-
*
|
|
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.
|
|
27228
27337
|
* - TRIP SEMANTICS: the vector loop runs whole 16-byte groups (unrolled to
|
|
27229
|
-
* two groups per iteration); the limit is floor(N / group)
|
|
27230
|
-
* to a multiple of the unroll — floor semantics keep every
|
|
27231
|
-
* < 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
|
|
27232
27342
|
* scalar loop is emitted unchanged afterwards as the tail (the induction
|
|
27233
27343
|
* is synced to the vector loop's exit counter first), covering the
|
|
27234
27344
|
* remainder — including the sub-unroll and negative-N cases where the
|
|
@@ -27262,6 +27372,9 @@ function flag_form_enabled() {
|
|
|
27262
27372
|
/** Max distinct Buffers per plan — the preheader pins each data pointer in
|
|
27263
27373
|
* a dedicated scratch register (x11..x13). */
|
|
27264
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;
|
|
27265
27378
|
/** Max per-lane temps (v4..v7). */
|
|
27266
27379
|
const MAX_TEMPS = 4;
|
|
27267
27380
|
/** Max binary-op nesting depth in lane expressions (spill regs v14→v11). */
|
|
@@ -27295,10 +27408,18 @@ const ELEM_DESCS = [
|
|
|
27295
27408
|
arr: "4s",
|
|
27296
27409
|
group_elems: 4,
|
|
27297
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
|
|
27298
27419
|
}
|
|
27299
27420
|
];
|
|
27300
27421
|
function class_of_elem(elem) {
|
|
27301
|
-
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";
|
|
27302
27423
|
}
|
|
27303
27424
|
function class_of_type_name(name) {
|
|
27304
27425
|
if (!name) return null;
|
|
@@ -27307,6 +27428,7 @@ function class_of_type_name(name) {
|
|
|
27307
27428
|
const size = aarch64_size(name);
|
|
27308
27429
|
if (size === 8) return "e8";
|
|
27309
27430
|
if (size === 4) return "e4";
|
|
27431
|
+
if (size === 1) return "e1";
|
|
27310
27432
|
} catch {}
|
|
27311
27433
|
return null;
|
|
27312
27434
|
}
|
|
@@ -27558,7 +27680,15 @@ function alloc_temps_of(s, walk) {
|
|
|
27558
27680
|
if (has_allocations(d.value)) return null;
|
|
27559
27681
|
const cls = class_of_type_name(d.type?.name);
|
|
27560
27682
|
if (!cls) return null;
|
|
27561
|
-
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
|
+
}
|
|
27562
27692
|
walk.temp_classes.set(d.name, cls);
|
|
27563
27693
|
out.push({
|
|
27564
27694
|
name: d.name,
|
|
@@ -27567,6 +27697,24 @@ function alloc_temps_of(s, walk) {
|
|
|
27567
27697
|
}
|
|
27568
27698
|
return out;
|
|
27569
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
|
+
}
|
|
27570
27718
|
function lane_expr(e, walk, induction, status, depth) {
|
|
27571
27719
|
if (depth > walk.max_depth) walk.max_depth = depth;
|
|
27572
27720
|
if (depth > MAX_OP_DEPTH) return null;
|
|
@@ -27577,13 +27725,34 @@ function lane_expr(e, walk, induction, status, depth) {
|
|
|
27577
27725
|
if (e.receiver.kind !== "leaf" || !e.receiver.name) return null;
|
|
27578
27726
|
if (e.facts.args.length !== 1) return null;
|
|
27579
27727
|
const idx = e.facts.args[0];
|
|
27580
|
-
|
|
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;
|
|
27581
27740
|
if (!is_buffer_type_name(type_from_value_node$1(e.receiver.node)?.name ?? "")) return null;
|
|
27582
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
|
+
}
|
|
27583
27751
|
return {
|
|
27584
27752
|
k: "load",
|
|
27585
27753
|
buffer: e.receiver.name,
|
|
27586
|
-
node: e.receiver.node
|
|
27754
|
+
node: e.receiver.node,
|
|
27755
|
+
shift
|
|
27587
27756
|
};
|
|
27588
27757
|
}
|
|
27589
27758
|
case "leaf": {
|
|
@@ -27780,6 +27949,26 @@ function extract_bound$1(bound, induction) {
|
|
|
27780
27949
|
name: null
|
|
27781
27950
|
};
|
|
27782
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
|
+
}
|
|
27783
27972
|
if (bound.kind !== "path") return null;
|
|
27784
27973
|
const root = bound.receiver;
|
|
27785
27974
|
if (root.kind !== "leaf" || !root.name || root.name === induction) return null;
|
|
@@ -27834,6 +28023,9 @@ function plan_common(lanes_raw, bound_node, bound_name, induction, index, list,
|
|
|
27834
28023
|
lanes: [],
|
|
27835
28024
|
buffers: [],
|
|
27836
28025
|
buffer_index: /* @__PURE__ */ new Map(),
|
|
28026
|
+
shifted: /* @__PURE__ */ new Map(),
|
|
28027
|
+
induction_alias: /* @__PURE__ */ new Map(),
|
|
28028
|
+
induction,
|
|
27837
28029
|
temps: [],
|
|
27838
28030
|
defs,
|
|
27839
28031
|
defed_so_far: /* @__PURE__ */ new Set(),
|
|
@@ -27849,9 +28041,13 @@ function plan_common(lanes_raw, bound_node, bound_name, induction, index, list,
|
|
|
27849
28041
|
}
|
|
27850
28042
|
continue;
|
|
27851
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`);
|
|
27852
28049
|
return null;
|
|
27853
28050
|
}
|
|
27854
|
-
if (bound_name !== null && defs.has(bound_name)) return null;
|
|
27855
28051
|
const reduction_inits = /* @__PURE__ */ new Map();
|
|
27856
28052
|
for (const name of reduction_candidates.keys()) {
|
|
27857
28053
|
const scan = scan_init(list, index, name);
|
|
@@ -27862,6 +28058,9 @@ function plan_common(lanes_raw, bound_node, bound_name, induction, index, list,
|
|
|
27862
28058
|
lanes: [],
|
|
27863
28059
|
buffers: [],
|
|
27864
28060
|
buffer_index: /* @__PURE__ */ new Map(),
|
|
28061
|
+
shifted: /* @__PURE__ */ new Map(),
|
|
28062
|
+
induction_alias: /* @__PURE__ */ new Map(),
|
|
28063
|
+
induction,
|
|
27865
28064
|
temps: [],
|
|
27866
28065
|
defs,
|
|
27867
28066
|
defed_so_far: /* @__PURE__ */ new Set(),
|
|
@@ -27941,8 +28140,27 @@ function plan_common(lanes_raw, bound_node, bound_name, induction, index, list,
|
|
|
27941
28140
|
for (const t of temps) {
|
|
27942
28141
|
const nir = ast_to_nir_shallow(t.value);
|
|
27943
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
|
+
}
|
|
27944
28148
|
const tvalue = lane_expr(nir, walk, induction, status, 0);
|
|
27945
|
-
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
|
+
}
|
|
27946
28164
|
walk.defed_so_far.add(t.name);
|
|
27947
28165
|
walk.temps.push(t.name);
|
|
27948
28166
|
walk.lanes.push({
|
|
@@ -27960,17 +28178,41 @@ function plan_common(lanes_raw, bound_node, bound_name, induction, index, list,
|
|
|
27960
28178
|
value
|
|
27961
28179
|
});
|
|
27962
28180
|
}
|
|
27963
|
-
if (walk.temps.length > MAX_TEMPS)
|
|
27964
|
-
|
|
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
|
+
}
|
|
27965
28189
|
if (!walk.elem) return null;
|
|
27966
28190
|
const want = class_of_elem(walk.elem);
|
|
27967
|
-
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
|
+
}
|
|
27968
28195
|
if (reductions.length > MAX_REDUCTIONS) return null;
|
|
27969
28196
|
if (walk.temps.length > 0) {
|
|
27970
28197
|
const after = /* @__PURE__ */ new Set();
|
|
27971
28198
|
for (let k = index + 1; k < list.length; k++) stmt_reads(list[k], after);
|
|
27972
|
-
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
|
+
}
|
|
27973
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);
|
|
27974
28216
|
return {
|
|
27975
28217
|
induction,
|
|
27976
28218
|
bound_node,
|
|
@@ -27978,7 +28220,8 @@ function plan_common(lanes_raw, bound_node, bound_name, induction, index, list,
|
|
|
27978
28220
|
buffers: walk.buffers,
|
|
27979
28221
|
elem: walk.elem,
|
|
27980
28222
|
reductions,
|
|
27981
|
-
fma_contract: allow_reductions
|
|
28223
|
+
fma_contract: allow_reductions,
|
|
28224
|
+
max_shift
|
|
27982
28225
|
};
|
|
27983
28226
|
}
|
|
27984
28227
|
/**
|
|
@@ -28006,6 +28249,7 @@ function plan_vector_loop(nstmt, index, list, status) {
|
|
|
28006
28249
|
}
|
|
28007
28250
|
const init = scan_init(list, index, induction);
|
|
28008
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`);
|
|
28009
28253
|
return plan_common(lanes_raw, bound.node, bound.name, induction, index, list, status, status.fast_math === true);
|
|
28010
28254
|
}
|
|
28011
28255
|
/**
|
|
@@ -29638,6 +29882,12 @@ const active_splices = /* @__PURE__ */ new Set();
|
|
|
29638
29882
|
function inline_splice_active(struct_name, func_name) {
|
|
29639
29883
|
return active_splices.has(`${struct_name}.${func_name}`);
|
|
29640
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
|
+
}
|
|
29641
29891
|
function begin_inline_splice(struct_name, func_name) {
|
|
29642
29892
|
active_splices.add(`${struct_name}.${func_name}`);
|
|
29643
29893
|
}
|
|
@@ -29741,6 +29991,11 @@ function build_inline_method(struct_node, func, status) {
|
|
|
29741
29991
|
const old_return_buffer_offset = status.return_buffer_stack_offset;
|
|
29742
29992
|
const old_function_return_type = status.function_return_type;
|
|
29743
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;
|
|
29744
29999
|
const old_buffer_data_cache = status.buffer_data_cache;
|
|
29745
30000
|
const old_buffer_fold_cache = status.buffer_fold_cache;
|
|
29746
30001
|
const old_array_ptr_cache = status.array_ptr_cache;
|
|
@@ -29890,6 +30145,9 @@ function build_inline_method(struct_node, func, status) {
|
|
|
29890
30145
|
status.return_buffer_stack_offset = old_return_buffer_offset;
|
|
29891
30146
|
status.function_return_type = old_function_return_type;
|
|
29892
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;
|
|
29893
30151
|
status.buffer_data_cache = old_buffer_data_cache;
|
|
29894
30152
|
status.buffer_fold_cache = old_buffer_fold_cache;
|
|
29895
30153
|
status.array_ptr_cache = old_array_ptr_cache;
|
|
@@ -29920,6 +30178,11 @@ function build_inline_function(func, status) {
|
|
|
29920
30178
|
const old_return_buffer_offset = status.return_buffer_stack_offset;
|
|
29921
30179
|
const old_function_return_type = status.function_return_type;
|
|
29922
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;
|
|
29923
30186
|
const old_buffer_data_cache = status.buffer_data_cache;
|
|
29924
30187
|
const old_buffer_fold_cache = status.buffer_fold_cache;
|
|
29925
30188
|
const old_array_ptr_cache = status.array_ptr_cache;
|
|
@@ -30050,6 +30313,9 @@ function build_inline_function(func, status) {
|
|
|
30050
30313
|
status.return_buffer_stack_offset = old_return_buffer_offset;
|
|
30051
30314
|
status.function_return_type = old_function_return_type;
|
|
30052
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;
|
|
30053
30319
|
status.buffer_data_cache = old_buffer_data_cache;
|
|
30054
30320
|
status.buffer_fold_cache = old_buffer_fold_cache;
|
|
30055
30321
|
status.array_ptr_cache = old_array_ptr_cache;
|
|
@@ -44257,6 +44523,8 @@ function load_nursery_struct_address(target, status) {
|
|
|
44257
44523
|
const MAX_STATEMENTS = 15;
|
|
44258
44524
|
const MAX_AUTO_METHOD_STATEMENTS = 3;
|
|
44259
44525
|
const MAX_AUTO_METHOD_PARAMS = 3;
|
|
44526
|
+
const MAX_AUTO_CALLING_STATEMENTS = 10;
|
|
44527
|
+
const MAX_AUTO_SPLICE_DEPTH = 2;
|
|
44260
44528
|
/**
|
|
44261
44529
|
* ensure/grow/clear-shaped methods (ASM_PLAN_7 tranche 7): small real
|
|
44262
44530
|
* bodies the method-inline path can splice WITHOUT the explicit `inline`
|
|
@@ -44268,11 +44536,19 @@ const MAX_AUTO_METHOD_PARAMS = 3;
|
|
|
44268
44536
|
* scalar-or-void return. The standalone body is still emitted — trait
|
|
44269
44537
|
* dispatch, method values, and overflow-arg call sites keep taking the
|
|
44270
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.
|
|
44271
44545
|
*/
|
|
44272
|
-
function is_auto_inline_method(func) {
|
|
44546
|
+
function is_auto_inline_method(func, depth = 0) {
|
|
44273
44547
|
if (!func.has_body) return false;
|
|
44274
44548
|
if (func.is_inline) return false;
|
|
44275
|
-
|
|
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;
|
|
44276
44552
|
if (func.returns_move) return false;
|
|
44277
44553
|
if (func.statements.some((s) => s.node_type === "raw")) return false;
|
|
44278
44554
|
if (func.params.length > MAX_AUTO_METHOD_PARAMS) return false;
|
|
@@ -44295,22 +44571,55 @@ function is_auto_inline_method(func) {
|
|
|
44295
44571
|
if (func.return_type?.is_array || func.return_type?.is_view) return false;
|
|
44296
44572
|
if (func.return_type?.is_nullable) return false;
|
|
44297
44573
|
if (func.return_type?.name && !SIMPLE_TYPES.includes(func.return_type.name)) return false;
|
|
44298
|
-
if (
|
|
44299
|
-
return
|
|
44574
|
+
if (!calls) return true;
|
|
44575
|
+
if (depth >= MAX_AUTO_SPLICE_DEPTH) return false;
|
|
44576
|
+
return body_calls_splice_through(func.statements, depth);
|
|
44300
44577
|
}
|
|
44301
|
-
|
|
44302
|
-
function body_has_call(node) {
|
|
44578
|
+
function body_calls(node) {
|
|
44303
44579
|
if (!node) return false;
|
|
44304
44580
|
if (Array.isArray(node)) {
|
|
44305
|
-
for (const item of node) if (
|
|
44581
|
+
for (const item of node) if (body_calls(item)) return true;
|
|
44306
44582
|
return false;
|
|
44307
44583
|
}
|
|
44308
44584
|
if (typeof node !== "object") return false;
|
|
44309
44585
|
const nt = node.node_type;
|
|
44310
44586
|
if (nt === "access_func" || nt === "func_call") return true;
|
|
44311
|
-
for (const child of child_nodes(node)) if (
|
|
44587
|
+
for (const child of child_nodes(node)) if (body_calls(child)) return true;
|
|
44312
44588
|
return false;
|
|
44313
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
|
+
}
|
|
44314
44623
|
function scan_inline_candidates(root) {
|
|
44315
44624
|
const counts = /* @__PURE__ */ new Map();
|
|
44316
44625
|
const defs = /* @__PURE__ */ new Map();
|
|
@@ -46110,7 +46419,7 @@ function build_access_method(node, access_func, status) {
|
|
|
46110
46419
|
}
|
|
46111
46420
|
}
|
|
46112
46421
|
const target_struct = status.structs.find((s) => s.name === mono_struct_name);
|
|
46113
|
-
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));
|
|
46114
46423
|
const trait_target = status.traits.find((t) => t.name === target_type.name);
|
|
46115
46424
|
const trait_func = trait_target?.functions.find((f) => f.name === access_func.name);
|
|
46116
46425
|
if (trait_target && trait_func) {
|
|
@@ -50438,6 +50747,17 @@ function expr_to_string(node, status) {
|
|
|
50438
50747
|
}
|
|
50439
50748
|
}
|
|
50440
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
|
+
}
|
|
50441
50761
|
}
|
|
50442
50762
|
/**
|
|
50443
50763
|
* Rewrite a `<base>.<field>` path through the assumed parallel-length
|
|
@@ -50678,6 +50998,19 @@ function apply_bounds(condition, status, is_loop = false) {
|
|
|
50678
50998
|
var_decl.path_bounds.set(bound.var_name, path_entry);
|
|
50679
50999
|
}
|
|
50680
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
|
+
}
|
|
50681
51014
|
const push_unique = (arr, expr) => {
|
|
50682
51015
|
const out = arr ?? [];
|
|
50683
51016
|
if (!out.includes(expr)) out.push(expr);
|
|
@@ -50690,9 +51023,11 @@ function apply_bounds(condition, status, is_loop = false) {
|
|
|
50690
51023
|
if (inclusive) {
|
|
50691
51024
|
var_decl.upper_bound_inclusive_exprs = push_unique(var_decl.upper_bound_inclusive_exprs, bound.expr);
|
|
50692
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);
|
|
50693
51027
|
} else {
|
|
50694
51028
|
var_decl.upper_bound_exprs = push_unique(var_decl.upper_bound_exprs, bound.expr);
|
|
50695
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);
|
|
50696
51031
|
}
|
|
50697
51032
|
var_decl.upper_bound_expr = bound.expr;
|
|
50698
51033
|
const num = numeric_interval(string_to_node(bound.expr), status);
|
|
@@ -50712,16 +51047,21 @@ function apply_bounds(condition, status, is_loop = false) {
|
|
|
50712
51047
|
if (inclusive) {
|
|
50713
51048
|
var_decl.lower_bound_inclusive_exprs = push_unique(var_decl.lower_bound_inclusive_exprs, bound.expr);
|
|
50714
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);
|
|
50715
51051
|
} else {
|
|
50716
51052
|
var_decl.lower_bound_exprs = push_unique(var_decl.lower_bound_exprs, bound.expr);
|
|
50717
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);
|
|
50718
51055
|
}
|
|
50719
51056
|
var_decl.lower_bound_expr = bound.expr;
|
|
50720
51057
|
const num = numeric_interval(string_to_node(bound.expr), status);
|
|
50721
51058
|
if (num) {
|
|
50722
51059
|
const lo = bound.op === ">=" ? num.lower : num.upper;
|
|
50723
51060
|
if (var_decl.range_lower === void 0 || lo > var_decl.range_lower) var_decl.range_lower = lo;
|
|
50724
|
-
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
|
+
}
|
|
50725
51065
|
}
|
|
50726
51066
|
}
|
|
50727
51067
|
apply_inverse_numeric_bound(bound, var_decl, status);
|
|
@@ -50848,6 +51188,39 @@ function apply_shifted_bound(condition, status, is_loop = false) {
|
|
|
50848
51188
|
}
|
|
50849
51189
|
}
|
|
50850
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
|
+
/**
|
|
50851
51224
|
* Substitute `out` and parameter names in a return-constraint expression with
|
|
50852
51225
|
* caller-side expressions. Used to translate a function's `out TYPE: contract`
|
|
50853
51226
|
* into bounds on the caller's LHS variable.
|
|
@@ -51445,8 +51818,13 @@ function evaluate_operation(op, status) {
|
|
|
51445
51818
|
if (left_decl.upper_bound_exprs.some((u) => upper_implies(u, target_str))) return true;
|
|
51446
51819
|
for (const u of left_decl.upper_bound_exprs) {
|
|
51447
51820
|
const v = parse_offset_expr(u);
|
|
51448
|
-
if (v && v.offset
|
|
51821
|
+
if (v && v.offset <= 0 && u !== target_str) {
|
|
51449
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
|
+
}
|
|
51450
51828
|
if (vdecl?.upper_bound_exprs?.includes(target_str)) return true;
|
|
51451
51829
|
if (vdecl?.upper_bound_inclusive_exprs?.includes(target_str) && op.op === "<=") return true;
|
|
51452
51830
|
if (op.op === "<" && vdecl?.upper_bound_inclusive_exprs?.includes(target_str)) return true;
|
|
@@ -51458,8 +51836,13 @@ function evaluate_operation(op, status) {
|
|
|
51458
51836
|
if (left_decl.upper_bound_inclusive_exprs.some((u) => upper_implies(u, target_str))) return true;
|
|
51459
51837
|
for (const u of left_decl.upper_bound_inclusive_exprs) {
|
|
51460
51838
|
const v = parse_offset_expr(u);
|
|
51461
|
-
if (v && v.offset
|
|
51839
|
+
if (v && v.offset <= 0 && u !== target_str) {
|
|
51462
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
|
+
}
|
|
51463
51846
|
if (vdecl?.upper_bound_exprs?.includes(target_str)) return true;
|
|
51464
51847
|
if (vdecl?.upper_bound_inclusive_exprs?.includes(target_str)) return true;
|
|
51465
51848
|
}
|
|
@@ -52331,6 +52714,7 @@ function check_declaration_node(decl, status) {
|
|
|
52331
52714
|
});
|
|
52332
52715
|
if (decl.value) {
|
|
52333
52716
|
move_closure_source(decl.value, status);
|
|
52717
|
+
invalidate_token_facts(decl.name, status);
|
|
52334
52718
|
track_assignment_bounds(decl.name, decl.value, status);
|
|
52335
52719
|
}
|
|
52336
52720
|
} else {
|
|
@@ -52455,7 +52839,10 @@ function check_declaration_node(decl, status) {
|
|
|
52455
52839
|
is_global: !in_function(status),
|
|
52456
52840
|
trait_slot_conformer: trait_slot_conformer_of_decl(declaration, decl.type, decl.value, status)
|
|
52457
52841
|
});
|
|
52458
|
-
if (decl.value)
|
|
52842
|
+
if (decl.value) {
|
|
52843
|
+
invalidate_token_facts(decl.name, status);
|
|
52844
|
+
track_assignment_bounds(decl.name, decl.value, status);
|
|
52845
|
+
}
|
|
52459
52846
|
if (status.pending_return_bounds) {
|
|
52460
52847
|
const pending = status.pending_return_bounds.get(decl.name);
|
|
52461
52848
|
if (pending) {
|
|
@@ -56679,6 +57066,7 @@ function check_function_call(node, status, func, target_type, self_value, self_p
|
|
|
56679
57066
|
const param_type = type_from_value_node(param, status);
|
|
56680
57067
|
const param_value = value_from_value_node(param);
|
|
56681
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);
|
|
56682
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)) {
|
|
56683
57071
|
const owned_struct = status.structs.find((st) => st.name === func_param.type.name && !st.is_simple_type && !st.is_class);
|
|
56684
57072
|
if (owned_struct && !(owned_struct.traits ?? []).length && direct_string_fields(owned_struct).length > 0 && !struct_needs_destroy(owned_struct, status)) {
|
|
@@ -56827,18 +57215,56 @@ function check_function_call(node, status, func, target_type, self_value, self_p
|
|
|
56827
57215
|
if (base_path) base_name = base_path.split(".")[0];
|
|
56828
57216
|
}
|
|
56829
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
|
+
}
|
|
56830
57232
|
if (base_name) {
|
|
56831
57233
|
const decl = status.values.findLast((v) => v.name === base_name);
|
|
56832
57234
|
if (decl) {
|
|
56833
57235
|
const shift = (exprs) => exprs?.map((e) => shift_offset_expr(e, c));
|
|
56834
57236
|
range_lower = decl.range_lower !== void 0 ? decl.range_lower + c : void 0;
|
|
56835
57237
|
range_upper = decl.range_upper !== void 0 ? decl.range_upper + c : void 0;
|
|
56836
|
-
|
|
56837
|
-
|
|
56838
|
-
|
|
56839
|
-
|
|
56840
|
-
|
|
56841
|
-
|
|
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
|
+
}
|
|
56842
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;
|
|
56843
57269
|
}
|
|
56844
57270
|
}
|
|
@@ -58241,6 +58667,7 @@ function check_assignment_node(assign, status) {
|
|
|
58241
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;
|
|
58242
58668
|
left_value.range_lower = void 0;
|
|
58243
58669
|
left_value.range_upper = void 0;
|
|
58670
|
+
invalidate_token_facts(left_value.name, status);
|
|
58244
58671
|
left_value.upper_bound_expr = void 0;
|
|
58245
58672
|
left_value.lower_bound_expr = void 0;
|
|
58246
58673
|
left_value.upper_bound_exprs = void 0;
|