@asmlift/core 0.8.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -31
- package/package.json +1 -1
- package/src/aggregate.ts +397 -0
- package/src/backend/c.ts +6 -2
- package/src/backend/cfamily.ts +370 -56
- package/src/backend/cpp.ts +116 -10
- package/src/backend/pascal.ts +69 -11
- package/src/cdecl/constant.ts +59 -0
- package/src/cdecl/lex.ts +209 -0
- package/src/cdecl/parse.ts +975 -0
- package/src/cdecl/spell.ts +69 -0
- package/src/codegen-flags.ts +38 -6
- package/src/compiler-diagnostics.ts +194 -0
- package/src/contracts.ts +777 -84
- package/src/declare.ts +132 -13
- package/src/frontend/asmdata.ts +26 -6
- package/src/frontend/call-plan.ts +1590 -0
- package/src/frontend/dead-reads.ts +628 -0
- package/src/frontend/device-pins.ts +296 -0
- package/src/frontend/emit.ts +4 -3
- package/src/frontend/errors.ts +5 -0
- package/src/frontend/flags-edge.ts +101 -0
- package/src/frontend/fpu.ts +374 -0
- package/src/frontend/frame-objects.ts +3171 -0
- package/src/frontend/frontend.ts +64 -3
- package/src/frontend/high-half.ts +55 -5
- package/src/frontend/jump-table.ts +530 -0
- package/src/frontend/local-object.ts +616 -0
- package/src/frontend/mips.ts +2392 -208
- package/src/frontend/opaque.ts +97 -18
- package/src/frontend/own-struct-return.ts +500 -0
- package/src/frontend/ppc.ts +3137 -267
- package/src/frontend/register-struct-call.ts +484 -0
- package/src/frontend/reloc-symbol.ts +109 -13
- package/src/frontend/splat.ts +108 -5
- package/src/frontend/ssa.ts +2098 -89
- package/src/frontend/stackargs.ts +172 -42
- package/src/frontend/thumb.ts +4563 -2091
- package/src/ir/alias.ts +71 -0
- package/src/ir/bits.ts +681 -1
- package/src/ir/core.ts +199 -6
- package/src/ir/discipline.ts +313 -0
- package/src/ir/entry.ts +159 -0
- package/src/ir/float-bits.ts +47 -0
- package/src/{structure → ir}/loops.ts +6 -2
- package/src/ir/opcodes.ts +193 -121
- package/src/ir/parse.ts +1 -0
- package/src/ir/types.ts +202 -2
- package/src/ir/verify.ts +219 -3
- package/src/l3/address.ts +5 -0
- package/src/l3/argcopy.ts +38 -6
- package/src/l3/ast.ts +467 -23
- package/src/l3/basecse.ts +33 -5
- package/src/l3/coalesce.ts +21 -13
- package/src/l3/constinit.ts +382 -0
- package/src/l3/dce.ts +34 -12
- package/src/l3/gates.ts +9 -3
- package/src/l3/hoist.ts +8 -7
- package/src/l3/initfirst.ts +68 -19
- package/src/l3/intcell.ts +98 -0
- package/src/l3/mentions.ts +10 -4
- package/src/l3/narrowdecl.ts +259 -0
- package/src/l3/nearbase.ts +10 -5
- package/src/l3/parkfirst.ts +8 -5
- package/src/l3/pollguard.ts +11 -4
- package/src/l3/ptrcell.ts +354 -0
- package/src/l3/ptrfield.ts +123 -8
- package/src/l3/regspell.ts +26 -7
- package/src/l3/reindex.ts +293 -103
- package/src/l3/storage.ts +98 -1
- package/src/l3/symbol-refs.ts +182 -23
- package/src/l3/tailmerge.ts +9 -20
- package/src/l3/typing.ts +237 -32
- package/src/l3/unreduce.ts +10 -9
- package/src/l3/volatileptr.ts +17 -54
- package/src/l3/volstore.ts +96 -7
- package/src/mangle.ts +41 -1
- package/src/pattern/engine.ts +373 -82
- package/src/pipeline.ts +589 -27
- package/src/proto-context.ts +1648 -0
- package/src/proto.ts +1203 -49
- package/src/raise/aggmembers.ts +290 -0
- package/src/raise/arrays.ts +8 -3
- package/src/raise/declared-volatile.ts +307 -0
- package/src/raise/divpow2.ts +2 -2
- package/src/raise/errors.ts +17 -0
- package/src/raise/floathelpers.ts +464 -0
- package/src/raise/globalshape.ts +11 -5
- package/src/raise/gvn.ts +29 -8
- package/src/raise/latch.ts +162 -6
- package/src/raise/memberarrays.ts +10 -4
- package/src/raise/narrow.ts +29 -2
- package/src/raise/narrowlocal.ts +11 -6
- package/src/raise/offsetnames.ts +494 -0
- package/src/raise/pairparams.ts +114 -0
- package/src/raise/paramwidth.ts +203 -6
- package/src/raise/pre-recovery.ts +174 -17
- package/src/raise/pre-structure.ts +54 -0
- package/src/raise/recover.ts +26 -15
- package/src/raise/reroll.ts +694 -0
- package/src/raise/retsink.ts +1 -1
- package/src/raise/shortcircuit.ts +66 -30
- package/src/raise/softdiv.ts +21 -23
- package/src/raise/storesign.ts +153 -0
- package/src/raise/struct-arrays.ts +34 -14
- package/src/raise/structs.ts +674 -98
- package/src/raise/truncload.ts +367 -0
- package/src/raise/unthread.ts +400 -0
- package/src/raise/widehelpers.ts +106 -0
- package/src/rank-declare.ts +143 -12
- package/src/rank-variations.ts +117 -31
- package/src/rank.ts +563 -122
- package/src/runtime-helpers.ts +405 -0
- package/src/stillborn.ts +289 -0
- package/src/structure/analysis.ts +1449 -449
- package/src/structure/arith-ops.ts +23 -0
- package/src/structure/globalaccess.ts +111 -9
- package/src/structure/hazards.ts +641 -114
- package/src/structure/local-statics.ts +517 -0
- package/src/structure/namecoalesce.ts +2 -2
- package/src/structure/pointer-spelling.ts +1106 -0
- package/src/structure/redundant-test.ts +99 -0
- package/src/structure/structure.ts +3478 -975
- package/src/structure/switch-recover.ts +403 -178
- package/src/symbols.ts +44 -3
- package/src/target.ts +1387 -71
- package/src/trace.ts +66 -28
- package/src/variation-definitions.ts +243 -13
- package/src/variation-gates.ts +2 -0
- package/src/variation-tokens.ts +25 -7
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
// asmlift — A WHOLE-WORD READ OF A DECLARED STRUCT LOCAL, every use of which takes one member's
|
|
2
|
+
// bits, READS THOSE MEMBERS.
|
|
3
|
+
//
|
|
4
|
+
// agbcc has no sp-relative halfword or byte load, so `struct S4 { u16 a; u8 b; u8 pad; } s =
|
|
5
|
+
// mk(x); gOut = s.a;` reads the struct-return temp a word at a time and narrows in registers:
|
|
6
|
+
//
|
|
7
|
+
// mov r0, sp / bl mk / ldr r1, [sp] / ldr r0, =gOut / strh r1, [r0]
|
|
8
|
+
//
|
|
9
|
+
// The frame-object audit types the temp as the struct the call returns (frame-objects.ts, the
|
|
10
|
+
// `aggregate` stamp), so the word is bytes of a declared object, and each use of it names a byte
|
|
11
|
+
// range: a narrowing store or extension (the low end), or one of those applied to a right shift
|
|
12
|
+
// by whole bytes, or such a shift alone (the top end, whose zero fill is the member's own zero
|
|
13
|
+
// extension). Where every use names exactly one declared integer member, the word is those
|
|
14
|
+
// member reads — the same bytes, read at the same program point: each member load is placed where
|
|
15
|
+
// the word load was, and the word load is left to dce.
|
|
16
|
+
//
|
|
17
|
+
// REFUSALS, each leaving the word read whole (spelled through a cast, which is the same bytes):
|
|
18
|
+
// * the base is not a frame object the audit typed as a struct, or the read is qualified;
|
|
19
|
+
// * a use is anything but the shapes above (an add, a compare of the whole word, a phi
|
|
20
|
+
// argument, a call argument), or a shift whose uses are not all of one width;
|
|
21
|
+
// * a range names no declared member exactly (a straddle, padding, a bitfield, a nested
|
|
22
|
+
// aggregate) — the member must start at that byte and be an integer of that width.
|
|
23
|
+
import { type Block, type Fn, type Op, type Value, mkOp, mkValue } from '../ir/core';
|
|
24
|
+
import { qualified } from '../ir/discipline';
|
|
25
|
+
import { type IrType, T } from '../ir/types';
|
|
26
|
+
|
|
27
|
+
type StructType = Extract<IrType, { kind: 'struct' }>;
|
|
28
|
+
|
|
29
|
+
/** The struct a frame object's address points at, where the audit stamped one. */
|
|
30
|
+
function frameStructOf(defs: ReadonlyMap<Value, Op>, base: Value): StructType | undefined {
|
|
31
|
+
const d = defs.get(base);
|
|
32
|
+
const t = d?.results[0].type;
|
|
33
|
+
return d?.opcode === 'laddr' && d.attrs.aggregate === true && t?.kind === 'ptr' && t.to.kind === 'struct'
|
|
34
|
+
? t.to
|
|
35
|
+
: undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** The declared integer member at exactly `[byte, byte + width)`: its signedness, or undefined. */
|
|
39
|
+
function memberAt(st: StructType, byte: number, width: number): boolean | undefined {
|
|
40
|
+
const f = st.fields.find((m) => m.off === byte);
|
|
41
|
+
return f !== undefined && f.type.kind === 'int' && f.type.width === width * 8 ? f.type.signed : undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface Use {
|
|
45
|
+
readonly op: Op;
|
|
46
|
+
readonly idx: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** The bytes a use keeps of the value it reads, low end first: a narrowing store's or an
|
|
50
|
+
* extension's width; undefined for any other use. */
|
|
51
|
+
function narrowedTo(u: Use): number | undefined {
|
|
52
|
+
if (u.op.opcode === 'store' && u.idx === 1) {
|
|
53
|
+
return u.op.attrs.width as number;
|
|
54
|
+
}
|
|
55
|
+
return u.op.opcode === 'zext' || u.op.opcode === 'sext' ? (u.op.attrs.width as number) / 8 : undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** One member a word's uses take: its byte range in the word, and each op it is read for with the
|
|
59
|
+
* rewiring of that op onto the member's load `m` (returning an op to place after the load). */
|
|
60
|
+
interface MemberUse {
|
|
61
|
+
readonly byte: number;
|
|
62
|
+
readonly width: number;
|
|
63
|
+
readonly sites: readonly { readonly op: Op; readonly rewire: (m: Value, signed: boolean) => Op | undefined }[];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** WHERE A MEMBER IS READ. The word was read once, where the compiler CSE'd the member reads of
|
|
67
|
+
* the source into it; the source read each member where it is used. So a member read for an op
|
|
68
|
+
* in another block is placed just before that op — where no write to the object lies on any path
|
|
69
|
+
* from the word's load to it, which is the condition under which the two read the same bytes.
|
|
70
|
+
* A write is any op but a load that takes the object's address (its struct-return call). */
|
|
71
|
+
function readableAt(
|
|
72
|
+
load: Op,
|
|
73
|
+
site: Op,
|
|
74
|
+
{ blockOf, preds, writes }: { blockOf: Map<Op, Block>; preds: Map<Block, Block[]>; writes: (op: Op) => boolean },
|
|
75
|
+
): boolean {
|
|
76
|
+
const seen = new Set<Block>();
|
|
77
|
+
const work: [Block, number][] = [[blockOf.get(site)!, blockOf.get(site)!.ops.indexOf(site)]];
|
|
78
|
+
while (work.length > 0) {
|
|
79
|
+
const [b, end] = work.pop()!;
|
|
80
|
+
let reached = false;
|
|
81
|
+
for (let i = end - 1; i >= 0; i--) {
|
|
82
|
+
if (b.ops[i] === load) {
|
|
83
|
+
reached = true;
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
if (writes(b.ops[i])) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (reached) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const ps = preds.get(b) ?? [];
|
|
94
|
+
if (ps.length === 0) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
for (const p of ps) {
|
|
98
|
+
if (!seen.has(p)) {
|
|
99
|
+
seen.add(p);
|
|
100
|
+
work.push([p, p.ops.length]);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function readDeclaredMembers(fn: Fn, littleEndian: boolean): number {
|
|
108
|
+
const defs = new Map<Value, Op>();
|
|
109
|
+
const uses = new Map<Value, Use[]>();
|
|
110
|
+
const branchArgs = new Set<Value>();
|
|
111
|
+
const blockOf = new Map<Op, Block>();
|
|
112
|
+
const preds = new Map<Block, Block[]>();
|
|
113
|
+
for (const b of fn.blocks) {
|
|
114
|
+
for (const op of b.ops) {
|
|
115
|
+
blockOf.set(op, b);
|
|
116
|
+
op.results.forEach((r) => defs.set(r, op));
|
|
117
|
+
op.operands.forEach((v, idx) => (uses.get(v) ?? uses.set(v, []).get(v)!).push({ op, idx }));
|
|
118
|
+
op.successors.forEach((s) => {
|
|
119
|
+
s.args.forEach((a) => branchArgs.add(a));
|
|
120
|
+
(preds.get(s.block) ?? preds.set(s.block, []).get(s.block)!).push(b);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// every read of `from`, an edge's argument included
|
|
125
|
+
const replaceAll = (from: Value, to: Value): void => {
|
|
126
|
+
for (const { op, idx } of uses.get(from) ?? []) {
|
|
127
|
+
op.operands[idx] = to;
|
|
128
|
+
}
|
|
129
|
+
if (branchArgs.has(from)) {
|
|
130
|
+
for (const b of fn.blocks) {
|
|
131
|
+
for (const op of b.ops) {
|
|
132
|
+
op.successors.forEach((sc) => (sc.args = sc.args.map((a) => (a === from ? to : a))));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
// a narrowing use of `m`: a store takes the member's low bytes, an extension of the member's
|
|
138
|
+
// own kind is the member read, another extension extends the member
|
|
139
|
+
const narrowOnto = (u: Use, m: Value, signed: boolean): undefined => {
|
|
140
|
+
if (u.op.opcode === 'store') {
|
|
141
|
+
u.op.operands[1] = m;
|
|
142
|
+
} else if (signed === (u.op.opcode === 'sext')) {
|
|
143
|
+
replaceAll(u.op.results[0], m);
|
|
144
|
+
} else {
|
|
145
|
+
u.op.operands[0] = m;
|
|
146
|
+
}
|
|
147
|
+
return undefined;
|
|
148
|
+
};
|
|
149
|
+
const narrowSite = (u: Use) => ({ op: u.op, rewire: (m: Value, signed: boolean) => narrowOnto(u, m, signed) });
|
|
150
|
+
// a shift that leaves exactly a member's bits, extended its own way: the member, or the member
|
|
151
|
+
// extended that way where it is declared the other
|
|
152
|
+
const extractSite = (sh: Op, width: number) => ({
|
|
153
|
+
op: sh,
|
|
154
|
+
rewire: (m: Value, signed: boolean): Op | undefined => {
|
|
155
|
+
const want = sh.opcode === 'shr_s';
|
|
156
|
+
if (signed === want) {
|
|
157
|
+
replaceAll(sh.results[0], m);
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
const x = mkOp(want ? 'sext' : 'zext', {
|
|
161
|
+
operands: [m],
|
|
162
|
+
results: [mkValue(T.unk(32))],
|
|
163
|
+
attrs: { width: width * 8 },
|
|
164
|
+
});
|
|
165
|
+
replaceAll(sh.results[0], x.results[0]);
|
|
166
|
+
return x;
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
const isShr = (op: Op): boolean => (op.opcode === 'shr_u' || op.opcode === 'shr_s') && op.operands.length === 1;
|
|
170
|
+
const frameOff = (v: Value): number | undefined => {
|
|
171
|
+
const d = defs.get(v);
|
|
172
|
+
return d?.opcode === 'laddr' ? (d.attrs.off as number) : undefined;
|
|
173
|
+
};
|
|
174
|
+
let changed = 0;
|
|
175
|
+
for (const blk of fn.blocks) {
|
|
176
|
+
for (let i = 0; i < blk.ops.length; i++) {
|
|
177
|
+
const load = blk.ops[i];
|
|
178
|
+
if (load.opcode !== 'load' || qualified(load)) {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
const st = frameStructOf(defs, load.operands[0]);
|
|
182
|
+
const word = load.attrs.width as number;
|
|
183
|
+
const at0 = load.attrs.off as number;
|
|
184
|
+
const v = load.results[0];
|
|
185
|
+
if (st === undefined || branchArgs.has(v)) {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
// the byte a `width`-byte range `shift` bits up the word starts at
|
|
189
|
+
const byteOf = (shift: number, width: number): number => (littleEndian ? shift / 8 : word - shift / 8 - width);
|
|
190
|
+
const plan: MemberUse[] = [];
|
|
191
|
+
let ok = (uses.get(v) ?? []).length > 0;
|
|
192
|
+
for (const u of uses.get(v) ?? []) {
|
|
193
|
+
const width = narrowedTo(u);
|
|
194
|
+
if (width !== undefined && width < word) {
|
|
195
|
+
plan.push({ byte: byteOf(0, width), width, sites: [narrowSite(u)] });
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
// `(x << a) >> b`: bits [b - a, 32 - a) of the word, the middle members
|
|
199
|
+
if (u.op.opcode === 'shl' && u.op.operands.length === 1 && !branchArgs.has(u.op.results[0])) {
|
|
200
|
+
const a = u.op.attrs.imm as number;
|
|
201
|
+
const shrs = uses.get(u.op.results[0])?.map((x) => x.op) ?? [];
|
|
202
|
+
const b = shrs[0]?.attrs.imm as number | undefined;
|
|
203
|
+
const width = b === undefined ? 0 : (32 - b) / 8;
|
|
204
|
+
if (
|
|
205
|
+
b !== undefined &&
|
|
206
|
+
shrs.every((x) => isShr(x) && x.attrs.imm === b && !branchArgs.has(x.results[0])) &&
|
|
207
|
+
b >= a &&
|
|
208
|
+
(b - a) % 8 === 0 &&
|
|
209
|
+
(width === 1 || width === 2) &&
|
|
210
|
+
32 - a <= word * 8
|
|
211
|
+
) {
|
|
212
|
+
plan.push({ byte: byteOf(b - a, width), width, sites: shrs.map((x) => extractSite(x, width)) });
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
ok = false;
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
const shift = isShr(u.op) ? (u.op.attrs.imm as number) : 0;
|
|
219
|
+
if (shift <= 0 || shift % 8 !== 0 || shift >= word * 8 || branchArgs.has(u.op.results[0])) {
|
|
220
|
+
ok = false;
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
const top = word - shift / 8;
|
|
224
|
+
const shUses = uses.get(u.op.results[0]) ?? [];
|
|
225
|
+
const widths = new Set(shUses.map(narrowedTo));
|
|
226
|
+
const narrowed = [...widths][0];
|
|
227
|
+
if (
|
|
228
|
+
u.op.opcode === 'shr_u' &&
|
|
229
|
+
shUses.length > 0 &&
|
|
230
|
+
widths.size === 1 &&
|
|
231
|
+
narrowed !== undefined &&
|
|
232
|
+
narrowed < top
|
|
233
|
+
) {
|
|
234
|
+
plan.push({ byte: byteOf(shift, narrowed), width: narrowed, sites: shUses.map(narrowSite) });
|
|
235
|
+
} else if (word === 4 && (top === 1 || top === 2)) {
|
|
236
|
+
// the shift's fill is the top member's own extension — of a whole word, whose bits above the
|
|
237
|
+
// member are the shift's alone, not a narrower load's extension
|
|
238
|
+
plan.push({ byte: byteOf(shift, top), width: top, sites: [extractSite(u.op, top)] });
|
|
239
|
+
} else {
|
|
240
|
+
ok = false;
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
const signs = plan.map((p) => memberAt(st, at0 + p.byte, p.width));
|
|
245
|
+
if (!ok || signs.some((s) => s === undefined)) {
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
const obj = frameOff(load.operands[0]);
|
|
249
|
+
const writes = (op: Op): boolean => op.opcode !== 'load' && op.operands.some((o) => frameOff(o) === obj);
|
|
250
|
+
const objectIsBranchArg = [...branchArgs].some((a) => frameOff(a) === obj);
|
|
251
|
+
const mkLoad = (p: MemberUse, signed: boolean): Op =>
|
|
252
|
+
mkOp('load', {
|
|
253
|
+
operands: [load.operands[0]],
|
|
254
|
+
results: [mkValue(T.unk(32))],
|
|
255
|
+
attrs: { off: at0 + p.byte, width: p.width, signed },
|
|
256
|
+
});
|
|
257
|
+
// a member read for an op of the word's own block shares one load where the word was read
|
|
258
|
+
const shared = new Map<string, Op>();
|
|
259
|
+
const after: Op[] = [];
|
|
260
|
+
const sunk: [Op, Op[]][] = [];
|
|
261
|
+
plan.forEach((p, k) => {
|
|
262
|
+
const signed = signs[k]!;
|
|
263
|
+
for (const site of p.sites) {
|
|
264
|
+
const elsewhere = blockOf.get(site.op) !== blk;
|
|
265
|
+
if (elsewhere && !objectIsBranchArg && readableAt(load, site.op, { blockOf, preds, writes })) {
|
|
266
|
+
const m = mkLoad(p, signed);
|
|
267
|
+
const extra = site.rewire(m.results[0], signed);
|
|
268
|
+
sunk.push([site.op, extra === undefined ? [m] : [m, extra]]);
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
const key = `${p.byte}:${p.width}`;
|
|
272
|
+
const m = shared.get(key) ?? shared.set(key, mkLoad(p, signed)).get(key)!;
|
|
273
|
+
const extra = site.rewire(m.results[0], signed);
|
|
274
|
+
if (extra !== undefined) {
|
|
275
|
+
after.push(extra);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
for (const [site, ops] of sunk) {
|
|
280
|
+
const b = blockOf.get(site)!;
|
|
281
|
+
b.ops.splice(b.ops.indexOf(site), 0, ...ops);
|
|
282
|
+
ops.forEach((o) => blockOf.set(o, b));
|
|
283
|
+
}
|
|
284
|
+
blk.ops.splice(blk.ops.indexOf(load) + 1, 0, ...shared.values(), ...after);
|
|
285
|
+
i = blk.ops.indexOf(load);
|
|
286
|
+
changed++;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return changed;
|
|
290
|
+
}
|
package/src/raise/arrays.ts
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
// byte form (`add(base, index)`, elemSize 1) is deferred: without types, base and index are
|
|
21
21
|
// indistinguishable there.
|
|
22
22
|
import { Fn, Op, Value, defOpMap, mkOp, mkValue, replaceAllUsesWith } from '../ir/core';
|
|
23
|
+
import { carryDiscipline } from '../ir/discipline';
|
|
23
24
|
|
|
24
25
|
// If `addr` is `add(base, shl(index, k))` with `1 << k === width`, return {base, index}.
|
|
25
26
|
// The `add` is commutative, so the scaled side may be either operand.
|
|
@@ -62,11 +63,11 @@ export function recognizeArrays(fn: Fn): number {
|
|
|
62
63
|
operands: [m.base, m.index],
|
|
63
64
|
results: [res],
|
|
64
65
|
// listOrder rides along: an ldmia-expanded load keeps its stream-order caveat as an aload
|
|
65
|
-
attrs: {
|
|
66
|
+
attrs: carryDiscipline(op, {
|
|
66
67
|
elemSize: op.attrs.width as number,
|
|
67
68
|
signed: op.attrs.signed as boolean,
|
|
68
69
|
...(op.attrs.listOrder === true && { listOrder: true }),
|
|
69
|
-
},
|
|
70
|
+
}),
|
|
70
71
|
});
|
|
71
72
|
replaceAllUsesWith(fn, op.results[0], res);
|
|
72
73
|
count++;
|
|
@@ -77,7 +78,11 @@ export function recognizeArrays(fn: Fn): number {
|
|
|
77
78
|
}
|
|
78
79
|
b.ops[i] = mkOp('astore', {
|
|
79
80
|
operands: [m.base, m.index, op.operands[1]],
|
|
80
|
-
|
|
81
|
+
// the signedness raise/storesign.ts read off a stored constant, when it read one
|
|
82
|
+
attrs: carryDiscipline(op, {
|
|
83
|
+
elemSize: op.attrs.width as number,
|
|
84
|
+
...(op.attrs.signed !== undefined && { signed: op.attrs.signed }),
|
|
85
|
+
}),
|
|
81
86
|
});
|
|
82
87
|
count++;
|
|
83
88
|
}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
// asmlift — the decider for ir/discipline.ts's `declared` placement: a memory access of an object
|
|
2
|
+
// the project's symbol map, or its declaration context, declares volatile.
|
|
3
|
+
//
|
|
4
|
+
// The stamp is a function of the IR, the map the function is lifted and structured under, the
|
|
5
|
+
// declaration context's globals and the compiler, and nothing else: an access whose base reaches a
|
|
6
|
+
// global the map qualifies, at the byte the access names (`declaresVolatile`), or one the context
|
|
7
|
+
// declares with a volatile byte and the map does not lay out (`ContextGlobals.volatiles`, per
|
|
8
|
+
// object, not per byte), or one through the value of a pointer the context declares to point at a
|
|
9
|
+
// volatile (`ContextGlobals.pointeeVolatiles`) — and, for a read, one the compiler could have made
|
|
10
|
+
// of a `volatile` (target.ts `readCouldBeVolatile`, which the device pin asks too). It is put as the
|
|
11
|
+
// lift is made (pipeline.ts `liftStamped`), before the first pass that asks whether a read may be
|
|
12
|
+
// deleted, folded or moved, and re-derived by every pass that changes which object an access names
|
|
13
|
+
// and by structuring. Structuring the same function under another map — the `/raw-globals` setting
|
|
14
|
+
// has none — re-runs it, and an op the new map does not qualify loses the stamp, so a structuring
|
|
15
|
+
// never answers for a map it was not given. The context is not the map: every candidate compiles
|
|
16
|
+
// against it whichever names it spells, so it is read in both settings.
|
|
17
|
+
//
|
|
18
|
+
// The base is read as the structurer will read it: a join whose every edge passes one address is
|
|
19
|
+
// that address (`addressThroughJoins`), and one every edge of which reaches one object — two of its
|
|
20
|
+
// addresses, or a pointer a loop steps through it — is an access of that object, as one every edge
|
|
21
|
+
// of which moves a pointer global's value is an access of its pointee (`reachThroughJoins`, one
|
|
22
|
+
// reader for both). A join whose edges reach different globals (`c ? gP : &gV`) is an access of any
|
|
23
|
+
// of them, placed only where every one is declared. A base with an edge that reaches no name — a
|
|
24
|
+
// pointer parameter, a raw address — has no declaration in evidence, and the access stays
|
|
25
|
+
// unplaced unless the lift pinned it (`device`).
|
|
26
|
+
//
|
|
27
|
+
// A read structuring stamps that the earlier stamps left plain was plain to every raising pass, and a
|
|
28
|
+
// short-circuit fold may have lifted it out of the arm it ran in. structure.ts declines on such a
|
|
29
|
+
// read in a `&&`/`||`'s guarded operand rather than place it.
|
|
30
|
+
import { globalCellOf } from '../ir/alias';
|
|
31
|
+
import { type Fn, type Op, type Value, defOpMap } from '../ir/core';
|
|
32
|
+
import { DECLARED_VOLATILE } from '../ir/discipline';
|
|
33
|
+
import { MEM_BASE_OPS } from '../ir/opcodes';
|
|
34
|
+
import type { ContextGlobals } from '../proto-context';
|
|
35
|
+
import { type SymbolInfo, type SymbolMap, declaresVolatile, symbolsByName } from '../symbols';
|
|
36
|
+
import { type TargetDescription, readCouldBeVolatile } from '../target';
|
|
37
|
+
import { valueNumberOf } from './gvn';
|
|
38
|
+
|
|
39
|
+
/** The compiler fact the stamp reads, and the declaration context's globals when there is one.
|
|
40
|
+
* The context is a REQUIRED key, `undefined` when there is none: the stamp clears every stamp it
|
|
41
|
+
* does not re-derive, so a re-stamp handed the compiler behaviors alone would unplace every read
|
|
42
|
+
* the context made volatile. Build one with pipeline.ts `stampBehaviorsFor`. */
|
|
43
|
+
export type StampBehaviors = Pick<TargetDescription['compilerBehaviors'], 'volatileReadsExtendInRegister'> & {
|
|
44
|
+
readonly contextGlobals: ContextGlobals | undefined;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/** Does the map lay out this object's volatility itself? A layout answers per member, which is
|
|
48
|
+
* finer than the context's per-object fact; an entry with none (a name and a size off the symbol
|
|
49
|
+
* table) leaves the context's answer standing. */
|
|
50
|
+
const laysOut = (info: SymbolInfo | undefined): boolean => Array.isArray(info?.layout) && info.layout.length > 0;
|
|
51
|
+
|
|
52
|
+
/** May this memory access be stamped: a store, or a read the compiler could have made of a
|
|
53
|
+
* `volatile`? */
|
|
54
|
+
const stampable = (op: Op, behaviors: StampBehaviors): boolean =>
|
|
55
|
+
MEM_BASE_OPS.has(op.opcode) &&
|
|
56
|
+
(op.opcode === 'store' || op.opcode === 'astore' || readCouldBeVolatile(behaviors, op));
|
|
57
|
+
|
|
58
|
+
/** The address a value stands for once `addrnum` (raise/pre-recovery.ts) has run: a block parameter
|
|
59
|
+
* every in-edge of which, a back edge's self-reference aside, passes one value after numbering
|
|
60
|
+
* (raise/gvn.ts `valueNumberOf`) is that value, as `simplifyTrivialPhis` collapses it; any other
|
|
61
|
+
* value stands for itself. Each arm of an `if` loading `&gVolReg` into the register the join reads
|
|
62
|
+
* is such a parameter, and the stamp is put before that pass, so it reads the base the structurer
|
|
63
|
+
* will. */
|
|
64
|
+
function addressThroughJoins(fn: Fn): (v: Value) => Value {
|
|
65
|
+
const incoming = joinIncoming(fn);
|
|
66
|
+
const numberOf = valueNumberOf(fn);
|
|
67
|
+
const through = (v: Value, onWalk: Set<Value>): Value | null => {
|
|
68
|
+
const ins = incoming.get(v);
|
|
69
|
+
if (ins === undefined) {
|
|
70
|
+
return v;
|
|
71
|
+
}
|
|
72
|
+
if (onWalk.has(v)) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
onWalk.add(v);
|
|
76
|
+
const each = ins.filter((a) => a !== v).map((a) => through(a, onWalk));
|
|
77
|
+
onWalk.delete(v);
|
|
78
|
+
const first = each[0];
|
|
79
|
+
return first !== undefined && first !== null && each.every((a) => a !== null && numberOf(a) === numberOf(first))
|
|
80
|
+
? first
|
|
81
|
+
: null;
|
|
82
|
+
};
|
|
83
|
+
return (v) => through(v, new Set()) ?? v;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Every block parameter's incoming values, one per in-edge (the entry block's excepted). */
|
|
87
|
+
function joinIncoming(fn: Fn): Map<Value, Value[]> {
|
|
88
|
+
const incoming = new Map<Value, Value[]>();
|
|
89
|
+
for (const b of fn.blocks) {
|
|
90
|
+
for (const op of b.ops) {
|
|
91
|
+
for (const s of op.successors) {
|
|
92
|
+
if (s.block === fn.blocks[0]) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
s.block.params.forEach((param, i) => {
|
|
96
|
+
if (i < s.args.length) {
|
|
97
|
+
(incoming.get(param) ?? incoming.set(param, []).get(param)!).push(s.args[i]);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return incoming;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** What an address reaches: an access of a global OBJECT (`gW.b`, `gVA[i]`), or of what the pointer a
|
|
107
|
+
* global holds points at (`*gP`, `gQ->b`) — the POINTEE of that global. */
|
|
108
|
+
type Reach = { readonly kind: 'object' | 'pointee'; readonly sym: string };
|
|
109
|
+
|
|
110
|
+
/** The globals an address reaches, one per edge that reaches a different one, each as an object or
|
|
111
|
+
* as a pointer's pointee, read through address arithmetic and joins alike: a `gaddr` is its object;
|
|
112
|
+
* a word read from a global's own first byte is that global's value, so an address moved from it
|
|
113
|
+
* reaches the global's pointee (asked only of a global in `pointers`, the ones the context declares
|
|
114
|
+
* to point at a volatile: any other word read is an index, not a base) — and so is a word read from
|
|
115
|
+
* a join every edge of which is such a global's address; an `add` with one reaching operand and a
|
|
116
|
+
* `sub` from one reach what they do; and a block parameter reaches what each in-edge's value
|
|
117
|
+
* reaches, an edge that passes the parameter itself moved by an offset (a strength-reduced loop
|
|
118
|
+
* pointer `v0 = v0 + 2`) aside. So `c ? &gW.a : &gW.b` and a pointer seeded with `&gVA` and stepped
|
|
119
|
+
* through it are accesses of that one object, `c ? gP + 1 : gP + 2` and a pointer seeded with `gP`
|
|
120
|
+
* and stepped are accesses of gP's pointee, and `c ? gP : gQ` or `c ? gP : &gV` is an access of
|
|
121
|
+
* either, which the caller places only where every one is declared. ONE reader answers all of
|
|
122
|
+
* them, so a shape the object path reads the pointee path reads too. Null where an edge reaches
|
|
123
|
+
* none. `addressThroughJoins` — one value on every edge — is narrower. */
|
|
124
|
+
function reachThroughJoins(
|
|
125
|
+
fn: Fn,
|
|
126
|
+
defs: Map<Value, Op>,
|
|
127
|
+
pointers: ReadonlySet<string> | undefined,
|
|
128
|
+
): (v: Value) => readonly Reach[] | null {
|
|
129
|
+
const incoming = joinIncoming(fn);
|
|
130
|
+
const SELF = Symbol('self');
|
|
131
|
+
type Step = readonly Reach[] | typeof SELF | null;
|
|
132
|
+
const union = (steps: readonly (readonly Reach[])[]): Reach[] => {
|
|
133
|
+
const out: Reach[] = [];
|
|
134
|
+
for (const r of steps.flat()) {
|
|
135
|
+
if (!out.some((o) => o.kind === r.kind && o.sym === r.sym)) {
|
|
136
|
+
out.push(r);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return out;
|
|
140
|
+
};
|
|
141
|
+
/** The globals whose own address every edge of `v` passes, unmoved; null where one passes anything else. */
|
|
142
|
+
// A value's answer is the same from every walk that never cut a cycle below it, so those are
|
|
143
|
+
// remembered: without that, a chain of diamonds whose arms both step one pointer is walked once
|
|
144
|
+
// per path, 2^n times, on every stamp. An answer that cut a cycle depends on the walk and is not.
|
|
145
|
+
let cuts = 0;
|
|
146
|
+
const addressMemo = new Map<Value, string[] | null>();
|
|
147
|
+
const reachMemo = new Map<Value, Step>();
|
|
148
|
+
const addresses = (v: Value, onWalk: Set<Value>): string[] | null => {
|
|
149
|
+
const d = defs.get(v);
|
|
150
|
+
if (d?.opcode === 'gaddr') {
|
|
151
|
+
return [d.attrs.sym as string];
|
|
152
|
+
}
|
|
153
|
+
const ins = d === undefined ? incoming.get(v) : undefined;
|
|
154
|
+
if (ins === undefined) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
if (onWalk.has(v)) {
|
|
158
|
+
cuts++;
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
if (addressMemo.has(v)) {
|
|
162
|
+
return addressMemo.get(v)!;
|
|
163
|
+
}
|
|
164
|
+
const before = cuts;
|
|
165
|
+
onWalk.add(v);
|
|
166
|
+
const each = ins.filter((a) => a !== v).map((a) => addresses(a, onWalk));
|
|
167
|
+
onWalk.delete(v);
|
|
168
|
+
const out = each.length > 0 && each.every((a) => a !== null) ? [...new Set(each.flat())] : null;
|
|
169
|
+
if (cuts === before) {
|
|
170
|
+
addressMemo.set(v, out);
|
|
171
|
+
}
|
|
172
|
+
return out;
|
|
173
|
+
};
|
|
174
|
+
const reach = (v: Value, onWalk: Set<Value>): Step => {
|
|
175
|
+
if (reachMemo.has(v)) {
|
|
176
|
+
return reachMemo.get(v)!;
|
|
177
|
+
}
|
|
178
|
+
const before = cuts;
|
|
179
|
+
const out = reachOnce(v, onWalk);
|
|
180
|
+
if (cuts === before) {
|
|
181
|
+
reachMemo.set(v, out);
|
|
182
|
+
}
|
|
183
|
+
return out;
|
|
184
|
+
};
|
|
185
|
+
const reachOnce = (v: Value, onWalk: Set<Value>): Step => {
|
|
186
|
+
const d = defs.get(v);
|
|
187
|
+
if (d?.opcode === 'gaddr') {
|
|
188
|
+
return [{ kind: 'object', sym: d.attrs.sym as string }];
|
|
189
|
+
}
|
|
190
|
+
if (d?.opcode === 'load' && ((d.attrs.off as number | undefined) ?? 0) === 0) {
|
|
191
|
+
const syms = addresses(d.operands[0], new Set());
|
|
192
|
+
return syms !== null && syms.every((sym) => pointers?.has(sym) === true)
|
|
193
|
+
? syms.map((sym) => ({ kind: 'pointee', sym }))
|
|
194
|
+
: null;
|
|
195
|
+
}
|
|
196
|
+
if (d?.opcode === 'add' && d.operands.length === 2) {
|
|
197
|
+
const a = reach(d.operands[0], onWalk);
|
|
198
|
+
const b = reach(d.operands[1], onWalk);
|
|
199
|
+
if (a !== null && b !== null) {
|
|
200
|
+
// An object's address plus a pointer's value is an index into the object (a byte array
|
|
201
|
+
// subscripted by a global the context also declares a pointer, or by a join of two such
|
|
202
|
+
// values); two of a kind name none, and neither does a side that mixes the kinds.
|
|
203
|
+
if (a === SELF || b === SELF) {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
const all = (r: readonly Reach[], kind: Reach['kind']): boolean => r.every((x) => x.kind === kind);
|
|
207
|
+
if (all(a, 'object') && all(b, 'pointee')) {
|
|
208
|
+
return a;
|
|
209
|
+
}
|
|
210
|
+
return all(b, 'object') && all(a, 'pointee') ? b : null;
|
|
211
|
+
}
|
|
212
|
+
return a ?? b;
|
|
213
|
+
}
|
|
214
|
+
if (d?.opcode === 'sub' && d.operands.length === 2) {
|
|
215
|
+
return reach(d.operands[0], onWalk);
|
|
216
|
+
}
|
|
217
|
+
const ins = d === undefined ? incoming.get(v) : undefined;
|
|
218
|
+
if (ins === undefined) {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
if (onWalk.has(v)) {
|
|
222
|
+
cuts++;
|
|
223
|
+
return SELF;
|
|
224
|
+
}
|
|
225
|
+
onWalk.add(v);
|
|
226
|
+
const each = ins.map((a) => reach(a, onWalk)).filter((r) => r !== SELF);
|
|
227
|
+
onWalk.delete(v);
|
|
228
|
+
return each.length > 0 && each.every((r) => r !== null) ? union(each as (readonly Reach[])[]) : null;
|
|
229
|
+
};
|
|
230
|
+
return (v) => {
|
|
231
|
+
const r = reach(v, new Set());
|
|
232
|
+
return r === null || r === SELF ? null : r;
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** Stamp every memory access of `fn` to an object `symbols` or `behaviors.contextGlobals` declares
|
|
237
|
+
* volatile — a read only where a compiler behaving as `behaviors` says could have made it of one —
|
|
238
|
+
* and clear the stamp from every other op. Returns the ops it stamped that were not stamped before. */
|
|
239
|
+
export function stampDeclaredVolatile(
|
|
240
|
+
fn: Fn,
|
|
241
|
+
symbols: ReadonlyMap<string, SymbolInfo> | undefined,
|
|
242
|
+
behaviors: StampBehaviors,
|
|
243
|
+
): Op[] {
|
|
244
|
+
const added: Op[] = [];
|
|
245
|
+
const contextVolatiles = behaviors.contextGlobals?.volatiles;
|
|
246
|
+
const pointees = behaviors.contextGlobals?.pointeeVolatiles;
|
|
247
|
+
const reads =
|
|
248
|
+
symbols !== undefined ||
|
|
249
|
+
(contextVolatiles !== undefined && contextVolatiles.size > 0) ||
|
|
250
|
+
(pointees !== undefined && pointees.size > 0);
|
|
251
|
+
const defs = reads ? defOpMap(fn) : undefined;
|
|
252
|
+
const addressOf = reads ? addressThroughJoins(fn) : undefined;
|
|
253
|
+
const reachOf = reads ? reachThroughJoins(fn, defs!, pointees) : undefined;
|
|
254
|
+
for (const b of fn.blocks) {
|
|
255
|
+
for (const op of b.ops) {
|
|
256
|
+
const addr = defs && stampable(op, behaviors) ? addressOf!(op.operands[0]) : undefined;
|
|
257
|
+
const reached = addr !== undefined ? reachOf!(addr) : null;
|
|
258
|
+
// A subscript reaches the object while naming no cell, so only a `load`/`store` asks by byte,
|
|
259
|
+
// and an access that names no byte is placed only where every byte it could reach is
|
|
260
|
+
// qualified. An access that may reach any of several globals is placed only where each one
|
|
261
|
+
// declares it.
|
|
262
|
+
const cell =
|
|
263
|
+
reached?.length === 1 && reached[0].kind === 'object' && (op.opcode === 'load' || op.opcode === 'store')
|
|
264
|
+
? globalCellOf(defs!, addr!, op.attrs.off as number)
|
|
265
|
+
: null;
|
|
266
|
+
const declares = (r: Reach): boolean => {
|
|
267
|
+
if (r.kind === 'pointee') {
|
|
268
|
+
return pointees?.has(r.sym) === true;
|
|
269
|
+
}
|
|
270
|
+
const info = symbols?.get(r.sym);
|
|
271
|
+
return (
|
|
272
|
+
declaresVolatile(info, cell === null ? null : cell.byte) ||
|
|
273
|
+
(contextVolatiles?.has(r.sym) === true && !laysOut(info))
|
|
274
|
+
);
|
|
275
|
+
};
|
|
276
|
+
const declared = reached !== null && reached.every(declares);
|
|
277
|
+
// A fresh record either way: a rebuilder may hand two ops one `attrs` object.
|
|
278
|
+
if (declared && op.attrs[DECLARED_VOLATILE] !== true) {
|
|
279
|
+
op.attrs = { ...op.attrs, [DECLARED_VOLATILE]: true };
|
|
280
|
+
added.push(op);
|
|
281
|
+
} else if (!declared && DECLARED_VOLATILE in op.attrs) {
|
|
282
|
+
const { [DECLARED_VOLATILE]: _, ...rest } = op.attrs;
|
|
283
|
+
op.attrs = rest;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return added;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** {@link stampDeclaredVolatile} under an address-keyed project map, through the name-keyed view
|
|
291
|
+
* structuring is handed for the same map (symbols.ts `symbolsByName`), narrowed to the names the
|
|
292
|
+
* function's own `gaddr`s carry. */
|
|
293
|
+
export function stampDeclaredVolatileUnder(fn: Fn, map: SymbolMap | undefined, behaviors: StampBehaviors): void {
|
|
294
|
+
if (map === undefined) {
|
|
295
|
+
stampDeclaredVolatile(fn, undefined, behaviors);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
const names = new Set<string>();
|
|
299
|
+
for (const b of fn.blocks) {
|
|
300
|
+
for (const op of b.ops) {
|
|
301
|
+
if (op.opcode === 'gaddr') {
|
|
302
|
+
names.add(op.attrs.sym as string);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
stampDeclaredVolatile(fn, symbolsByName(map, names), behaviors);
|
|
307
|
+
}
|
package/src/raise/divpow2.ts
CHANGED
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
// the biased arm must be the negative one — the consistency check m2c's `49b5d87` also adds),
|
|
42
42
|
// because every one of those is a way for a superficially similar diamond to mean something else.
|
|
43
43
|
import { Block, Fn, Op, Value, defOpMap, mkOp, mkValue, predecessors, replaceAllUsesWith } from '../ir/core';
|
|
44
|
-
import {
|
|
44
|
+
import { effectful } from '../ir/discipline';
|
|
45
45
|
import { T } from '../ir/types';
|
|
46
46
|
|
|
47
47
|
/** `shr_s v {imm=k}` → k, else null. */
|
|
@@ -110,7 +110,7 @@ export function recognizeDivPow2(fn: Fn): boolean {
|
|
|
110
110
|
continue;
|
|
111
111
|
}
|
|
112
112
|
const bp = preds.get(bias) ?? [];
|
|
113
|
-
if (bp.length !== 1 || bias.ops.some(
|
|
113
|
+
if (bp.length !== 1 || bias.ops.some(effectful)) {
|
|
114
114
|
continue;
|
|
115
115
|
}
|
|
116
116
|
const h = bp[0];
|
package/src/raise/errors.ts
CHANGED
|
@@ -8,3 +8,20 @@ export class RaiseUnsupportedError extends Error {
|
|
|
8
8
|
this.name = 'RaiseUnsupportedError';
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
|
+
|
|
12
|
+
/** The one raise refusal a caller ACTS ON rather than only reporting: raise/structs.ts cannot
|
|
13
|
+
* reproduce a layout with a plain struct because two accesses' byte ranges COLLIDE. For a base
|
|
14
|
+
* whose address is declared outside the function that is not a reason to decline, since each
|
|
15
|
+
* access is spellable on its own; for any other base the overlap is declared as a union member
|
|
16
|
+
* where the union can be laid out. A CLASS rather than a substring test on the message, because
|
|
17
|
+
* the two facts a caller needs (which refusal this is, and what to tell the reader) would
|
|
18
|
+
* otherwise be the same string, and the guard would then fall open the day the message is
|
|
19
|
+
* reworded. Every other raise/structs.ts refusal (a packed layout, a union it cannot lay out)
|
|
20
|
+
* stays a plain RaiseUnsupportedError and declines: a subclass is minted for a caller that acts
|
|
21
|
+
* on it, not for every refusal that wants a name. */
|
|
22
|
+
export class StructOverlapError extends RaiseUnsupportedError {
|
|
23
|
+
constructor(message: string) {
|
|
24
|
+
super(message);
|
|
25
|
+
this.name = 'StructOverlapError';
|
|
26
|
+
}
|
|
27
|
+
}
|