@asmlift/core 0.8.1 → 0.9.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 +34 -30
- package/package.json +1 -1
- package/src/aggregate.ts +248 -0
- package/src/backend/c.ts +5 -2
- package/src/backend/cfamily.ts +236 -37
- package/src/backend/cpp.ts +114 -9
- package/src/backend/pascal.ts +39 -0
- 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 +615 -79
- package/src/declare.ts +75 -5
- package/src/frontend/asmdata.ts +19 -5
- package/src/frontend/call-plan.ts +686 -0
- package/src/frontend/device-pins.ts +277 -0
- package/src/frontend/errors.ts +5 -0
- package/src/frontend/flags-edge.ts +101 -0
- package/src/frontend/fpu.ts +167 -0
- package/src/frontend/frame-objects.ts +2131 -0
- package/src/frontend/high-half.ts +2 -1
- package/src/frontend/local-object.ts +490 -0
- package/src/frontend/mips.ts +363 -40
- package/src/frontend/opaque.ts +97 -18
- package/src/frontend/ppc.ts +520 -129
- package/src/frontend/reloc-symbol.ts +94 -13
- package/src/frontend/splat.ts +108 -5
- package/src/frontend/ssa.ts +669 -76
- package/src/frontend/stackargs.ts +120 -39
- package/src/frontend/thumb.ts +2764 -2018
- package/src/ir/alias.ts +29 -0
- package/src/ir/core.ts +101 -3
- package/src/ir/discipline.ts +248 -0
- package/src/ir/float-bits.ts +47 -0
- package/src/ir/opcodes.ts +84 -112
- package/src/ir/parse.ts +1 -0
- package/src/ir/types.ts +118 -2
- package/src/ir/verify.ts +150 -3
- package/src/l3/address.ts +5 -0
- package/src/l3/argcopy.ts +38 -6
- package/src/l3/ast.ts +186 -14
- package/src/l3/coalesce.ts +21 -13
- package/src/l3/dce.ts +32 -10
- package/src/l3/gates.ts +9 -3
- package/src/l3/hoist.ts +8 -7
- package/src/l3/initfirst.ts +27 -9
- package/src/l3/intcell.ts +97 -0
- package/src/l3/mentions.ts +10 -4
- package/src/l3/narrowdecl.ts +218 -0
- package/src/l3/nearbase.ts +10 -5
- package/src/l3/parkfirst.ts +8 -5
- package/src/l3/pollguard.ts +3 -2
- package/src/l3/ptrcell.ts +357 -0
- package/src/l3/regspell.ts +17 -3
- package/src/l3/reindex.ts +10 -7
- package/src/l3/symbol-refs.ts +104 -23
- package/src/l3/tailmerge.ts +6 -19
- package/src/l3/typing.ts +137 -25
- package/src/l3/unreduce.ts +4 -4
- package/src/l3/volstore.ts +96 -7
- package/src/mangle.ts +11 -1
- package/src/pattern/engine.ts +19 -16
- package/src/pipeline.ts +57 -12
- package/src/proto-context.ts +788 -0
- package/src/proto.ts +689 -50
- package/src/raise/arrays.ts +4 -3
- package/src/raise/declared-volatile.ts +131 -0
- package/src/raise/divpow2.ts +2 -2
- package/src/raise/errors.ts +17 -0
- package/src/raise/floathelpers.ts +243 -0
- package/src/raise/globalshape.ts +11 -5
- package/src/raise/gvn.ts +26 -5
- package/src/raise/narrow.ts +29 -2
- package/src/raise/narrowlocal.ts +11 -6
- package/src/raise/offsetnames.ts +510 -0
- package/src/raise/pairparams.ts +81 -0
- package/src/raise/paramwidth.ts +7 -0
- package/src/raise/pre-recovery.ts +78 -4
- package/src/raise/recover.ts +14 -10
- package/src/raise/retsink.ts +1 -1
- package/src/raise/shortcircuit.ts +41 -19
- package/src/raise/softdiv.ts +21 -23
- package/src/raise/struct-arrays.ts +5 -4
- package/src/raise/structs.ts +311 -98
- package/src/raise/truncload.ts +366 -0
- package/src/raise/widehelpers.ts +106 -0
- package/src/rank-declare.ts +30 -5
- package/src/rank-variations.ts +36 -8
- package/src/rank.ts +267 -79
- package/src/runtime-helpers.ts +287 -0
- package/src/stillborn.ts +289 -0
- package/src/structure/analysis.ts +1285 -448
- package/src/structure/hazards.ts +633 -58
- package/src/structure/local-statics.ts +381 -0
- package/src/structure/namecoalesce.ts +1 -1
- package/src/structure/redundant-test.ts +99 -0
- package/src/structure/structure.ts +1772 -395
- package/src/structure/switch-recover.ts +159 -78
- package/src/symbols.ts +38 -3
- package/src/target.ts +662 -59
- package/src/trace.ts +44 -24
- package/src/variation-definitions.ts +119 -1
- package/src/variation-tokens.ts +20 -6
|
@@ -5,9 +5,12 @@
|
|
|
5
5
|
// late-bound callbacks into the emission phase, so case bodies reuse the ordinary structuring
|
|
6
6
|
// machinery (loops/ifs inside cases, the onStack guard).
|
|
7
7
|
import { Block, Fn, Op, Value, forwardingTarget, isBodyless, successorsOf } from '../ir/core';
|
|
8
|
-
import {
|
|
8
|
+
import { orderSensitive } from '../ir/discipline';
|
|
9
9
|
import { Expr, Stmt, SwitchCase } from '../l3/ast';
|
|
10
10
|
|
|
11
|
+
/** Which side of a relational test a compiler's dispatch lands a pinned case body on. */
|
|
12
|
+
export type SwitchBoundCase = 'taken' | 'either';
|
|
13
|
+
|
|
11
14
|
export interface SwitchRecoverDeps {
|
|
12
15
|
fn: Fn;
|
|
13
16
|
defs: Map<Value, Op>;
|
|
@@ -19,8 +22,9 @@ export interface SwitchRecoverDeps {
|
|
|
19
22
|
/** is this opcode an integer comparison? */
|
|
20
23
|
isCmpOpcode: (opcode: string) => boolean;
|
|
21
24
|
switchAllowsNeqCase: boolean;
|
|
22
|
-
/** read a relational test
|
|
23
|
-
|
|
25
|
+
/** read a relational test as a case where the values that can reach it leave exactly one on
|
|
26
|
+
* this side of it — its branch, or either side — or never (null) */
|
|
27
|
+
switchBoundCase: SwitchBoundCase | null;
|
|
24
28
|
/** emit the case arms in the ASSEMBLY's block-layout order rather than by ascending case value */
|
|
25
29
|
switchArmsFollowLayout: boolean;
|
|
26
30
|
/** DECLINE a recovered tree whose own layout INTERLEAVES a test with a case body. A source
|
|
@@ -128,66 +132,126 @@ export interface TestInfo {
|
|
|
128
132
|
xOnLeft: boolean;
|
|
129
133
|
}
|
|
130
134
|
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
135
|
+
// WHICH SCRUTINEE VALUES CAN REACH A TEST, AND WHICH ONE A SIDE OF IT PINS. A comparison dispatch
|
|
136
|
+
// pins its last value with a RELATIONAL test once the tests above it have narrowed the scrutinee to
|
|
137
|
+
// one value on that side: mwcc's `switch (x) { case 0: … case 1: … default: … }` is `cmpwi r0,1;
|
|
138
|
+
// beq- case1; bge- default; cmpwi r0,0; bge- case0; b default`, where `x >= 0` is `case 0` only
|
|
139
|
+
// because `x != 1` and `x < 1` came first. agbcc's `emit_case_nodes` does the same on the branch it
|
|
140
|
+
// takes once `node_is_bounded` holds, and its `x <u 1` for `case 0` of an unsigned switch is the
|
|
141
|
+
// instance that needs nothing from the tests above: one value at an end of the 32-bit domain. Read
|
|
142
|
+
// as navigation instead, the pinned arm's body becomes a second default candidate and the whole
|
|
143
|
+
// tree declines. Which SIDE of a test may pin a case is the compiler's (`switchBoundCase`,
|
|
144
|
+
// target.ts).
|
|
145
|
+
//
|
|
146
|
+
// EXACT, not a bound. A range is the set of 32-bit register values, as disjoint signed intervals in
|
|
147
|
+
// ascending order, and every test the walk narrows it by — `==`, `!=`, and a relational test under
|
|
148
|
+
// either signedness — is an exact set over that register. An unsigned interval straddling 2^31 is
|
|
149
|
+
// two signed ones (`fromUnsigned`). So a side that comes out a singleton names the only value that
|
|
150
|
+
// CAN take it, and a side that comes out empty is one no value takes. Whether the value DOES reach
|
|
151
|
+
// the body is still PRE3's to say, simulating the original tree case by case.
|
|
152
|
+
//
|
|
153
|
+
// THE DOMAIN IS THE REGISTER's, not the scrutinee's recovered type, so a narrower type has nearer
|
|
154
|
+
// ends this misses. That costs a case and never invents one.
|
|
155
|
+
type Ranges = readonly (readonly [number, number])[];
|
|
156
|
+
|
|
157
|
+
const INT_MIN = -0x80000000;
|
|
158
|
+
const INT_MAX = 0x7fffffff;
|
|
159
|
+
const ALL: Ranges = [[INT_MIN, INT_MAX]];
|
|
160
|
+
|
|
161
|
+
/** The same opcode with its operands swapped: `k < x` is `x > k`. */
|
|
162
|
+
const MIRRORED: Record<string, string> = {
|
|
163
|
+
icmp_slt: 'icmp_sgt',
|
|
164
|
+
icmp_sgt: 'icmp_slt',
|
|
165
|
+
icmp_sle: 'icmp_sge',
|
|
166
|
+
icmp_sge: 'icmp_sle',
|
|
167
|
+
icmp_ult: 'icmp_ugt',
|
|
168
|
+
icmp_ugt: 'icmp_ult',
|
|
169
|
+
icmp_ule: 'icmp_uge',
|
|
170
|
+
icmp_uge: 'icmp_ule',
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
/** `[lo, hi]` of the UNSIGNED order as signed intervals: one, or two where it straddles 2^31. */
|
|
174
|
+
function fromUnsigned(lo: number, hi: number): Ranges {
|
|
175
|
+
if (lo > hi) {
|
|
176
|
+
return [];
|
|
157
177
|
}
|
|
178
|
+
return hi <= INT_MAX || lo > INT_MAX
|
|
179
|
+
? [[lo | 0, hi | 0]]
|
|
180
|
+
: [
|
|
181
|
+
[INT_MIN, hi | 0],
|
|
182
|
+
[lo, INT_MAX],
|
|
183
|
+
];
|
|
158
184
|
}
|
|
159
185
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
[max, max - 1],
|
|
185
|
-
]) {
|
|
186
|
-
if (evalCmp(ti.opcode, ti.xOnLeft, v, ti.k) && !evalCmp(ti.opcode, ti.xOnLeft, next, ti.k)) {
|
|
187
|
-
return v;
|
|
186
|
+
function complement(r: Ranges): Ranges {
|
|
187
|
+
const out: [number, number][] = [];
|
|
188
|
+
let next = INT_MIN;
|
|
189
|
+
for (const [lo, hi] of r) {
|
|
190
|
+
if (lo > next) {
|
|
191
|
+
out.push([next, lo - 1]);
|
|
192
|
+
}
|
|
193
|
+
next = hi + 1;
|
|
194
|
+
}
|
|
195
|
+
if (next <= INT_MAX) {
|
|
196
|
+
out.push([next, INT_MAX]);
|
|
197
|
+
}
|
|
198
|
+
return out;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function intersect(a: Ranges, b: Ranges): Ranges {
|
|
202
|
+
const out: [number, number][] = [];
|
|
203
|
+
for (const [alo, ahi] of a) {
|
|
204
|
+
for (const [blo, bhi] of b) {
|
|
205
|
+
const lo = Math.max(alo, blo);
|
|
206
|
+
const hi = Math.min(ahi, bhi);
|
|
207
|
+
if (lo <= hi) {
|
|
208
|
+
out.push([lo, hi]);
|
|
209
|
+
}
|
|
188
210
|
}
|
|
189
211
|
}
|
|
190
|
-
return
|
|
212
|
+
return out.sort((x, y) => x[0] - y[0]);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** The register values that take the test's BRANCH. */
|
|
216
|
+
function takenBy(ti: TestInfo): Ranges {
|
|
217
|
+
const op = ti.xOnLeft ? ti.opcode : (MIRRORED[ti.opcode] ?? ti.opcode);
|
|
218
|
+
if (op === 'icmp_eq' || op === 'icmp_ne') {
|
|
219
|
+
const k = ti.k | 0;
|
|
220
|
+
return op === 'icmp_eq' ? [[k, k]] : complement([[k, k]]);
|
|
221
|
+
}
|
|
222
|
+
const uns = op.startsWith('icmp_u');
|
|
223
|
+
const [min, max, k] = uns ? [0, 0xffffffff, ti.k >>> 0] : [INT_MIN, INT_MAX, ti.k | 0];
|
|
224
|
+
const rel = op.slice('icmp_s'.length);
|
|
225
|
+
const [lo, hi] =
|
|
226
|
+
rel === 'lt'
|
|
227
|
+
? [min, k - 1]
|
|
228
|
+
: rel === 'le'
|
|
229
|
+
? [min, k]
|
|
230
|
+
: rel === 'gt'
|
|
231
|
+
? [k + 1, max]
|
|
232
|
+
: rel === 'ge'
|
|
233
|
+
? [k, max]
|
|
234
|
+
: [min, max];
|
|
235
|
+
return uns ? fromUnsigned(lo, hi) : lo > hi ? [] : [[lo, hi]];
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** What of `path` follows the test's `taken` edge, or its fall-through. */
|
|
239
|
+
function narrow(path: Ranges, ti: TestInfo, taken: boolean): Ranges {
|
|
240
|
+
const t = takenBy(ti);
|
|
241
|
+
return intersect(path, taken ? t : complement(t));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** Whether register value `v` is in `r`. PRE3's concrete simulation reads each test through this
|
|
245
|
+
* and {@link takenBy}, so the tree it walks and the ranges a bound case is read off are one
|
|
246
|
+
* definition of the icmp family, not two. */
|
|
247
|
+
function contains(r: Ranges, v: number): boolean {
|
|
248
|
+
const x = v | 0;
|
|
249
|
+
return r.some(([lo, hi]) => lo <= x && x <= hi);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** The one value in `r`, if it holds exactly one. */
|
|
253
|
+
function singleton(r: Ranges): number | null {
|
|
254
|
+
return r.length === 1 && r[0][0] === r[0][1] ? r[0][0] : null;
|
|
191
255
|
}
|
|
192
256
|
|
|
193
257
|
/** Re-thread `order` so every FALLING arm sits directly above the arm it falls into. Each
|
|
@@ -251,7 +315,7 @@ export function makeSwitchRecovery(deps: SwitchRecoverDeps): SwitchRecovery {
|
|
|
251
315
|
isNamed,
|
|
252
316
|
isCmpOpcode,
|
|
253
317
|
switchAllowsNeqCase,
|
|
254
|
-
|
|
318
|
+
switchBoundCase,
|
|
255
319
|
switchArmsFollowLayout,
|
|
256
320
|
switchRequiresFrontLoadedTests,
|
|
257
321
|
spellSwitchFallthrough,
|
|
@@ -265,9 +329,10 @@ export function makeSwitchRecovery(deps: SwitchRecoverDeps): SwitchRecovery {
|
|
|
265
329
|
// A block's index in `fn.blocks` as its position in the ASSEMBLY — the warrant for every reading
|
|
266
330
|
// of layout below, and true PER FRONTEND rather than of the IR:
|
|
267
331
|
// - thumb.ts and mips.ts build the list by scanning the instruction stream in address order;
|
|
268
|
-
// - ppc.ts does
|
|
269
|
-
// for every conditional-return branch, wherever in the stream that branch sits
|
|
270
|
-
//
|
|
332
|
+
// - ppc.ts does too, and then APPENDS a synthetic return block (`synthReturn`) at the end of
|
|
333
|
+
// the list for every conditional-return branch, wherever in the stream that branch sits. So
|
|
334
|
+
// its real blocks keep address order among themselves, and each synthetic one — a bare
|
|
335
|
+
// return, never a test — sorts after all of them;
|
|
271
336
|
// - raising only ever REMOVES blocks from the list (raise/{divpow2,latch,retsink,shortcircuit}
|
|
272
337
|
// .ts all `filter`), never inserts or reorders, so the frontend's order is what survives.
|
|
273
338
|
// TWO READERS, both target-gated, and they need DIFFERENT strengths of the compiler half:
|
|
@@ -278,9 +343,14 @@ export function makeSwitchRecovery(deps: SwitchRecoverDeps): SwitchRecovery {
|
|
|
278
343
|
// `MIPS_GCC` declares `switchRequiresFrontLoadedTests` and NOT `switchArmsFollowLayout` for
|
|
279
344
|
// exactly that reason — it has a scheduler and fills delay slots (target.ts).
|
|
280
345
|
// Both are therefore claims about a target's FRONTEND as much as about its compiler, and a target
|
|
281
|
-
// opts in on both halves
|
|
282
|
-
//
|
|
283
|
-
//
|
|
346
|
+
// opts in on both halves. PPC_MWCC's frontend holds the weaker half only: a synthetic return body
|
|
347
|
+
// sorting last can make PRE5 keep a `switch` it should decline, never decline one it should keep,
|
|
348
|
+
// so mwcc declares `switchRequiresFrontLoadedTests` and could not declare the placing reading.
|
|
349
|
+
// That is the frontend's limit and not the compiler's: mwcc lays its case bodies in source order
|
|
350
|
+
// (the compiled pairs at target.ts `switchArmsFollowLayout`), so a synthetic block placed at its
|
|
351
|
+
// branch's address would give PPC both halves.
|
|
352
|
+
// Anything added below that reads `layoutIndex` inherits the frontend half and owes a statement
|
|
353
|
+
// of which strength of the compiler half it needs.
|
|
284
354
|
const blockIndex = new Map(fn.blocks.map((blk, i) => [blk, i] as const));
|
|
285
355
|
const layoutIndex = (blk: Block): number => blockIndex.get(blk) ?? -1;
|
|
286
356
|
|
|
@@ -432,7 +502,7 @@ export function makeSwitchRecovery(deps: SwitchRecoverDeps): SwitchRecovery {
|
|
|
432
502
|
//
|
|
433
503
|
// PRE4 (purity) is the second. A collapsed test block's ops re-render at whichever use inlines
|
|
434
504
|
// them, at a point the switch decides — so the question is motion, not deletion, and
|
|
435
|
-
// `
|
|
505
|
+
// `orderSensitive` is the question that asks it. NOT the trapping divides: a use is dominated by
|
|
436
506
|
// its def, so the re-rendered op runs on a subset of the paths it already ran on — nothing is
|
|
437
507
|
// speculated. `emitsOwnStatement` covers what motion cannot save: a statement belonging to the
|
|
438
508
|
// block rather than to a use. The root is exempt from all of it — its ops are already emitted as
|
|
@@ -441,8 +511,7 @@ export function makeSwitchRecovery(deps: SwitchRecoverDeps): SwitchRecovery {
|
|
|
441
511
|
// A block that tests the scrutinee and is NOT collapsible is still dispatch, so the walk must
|
|
442
512
|
// read it as dispatch and decline, never re-read it as a case body — that would spell an arm
|
|
443
513
|
// whose guard the dispatch has already decided.
|
|
444
|
-
const collapsible = (blk: Block): boolean =>
|
|
445
|
-
!blk.ops.some((op) => ORDER_SENSITIVE_OPS.has(op.opcode)) && !emitsOwnStatement(blk);
|
|
514
|
+
const collapsible = (blk: Block): boolean => !blk.ops.some(orderSensitive) && !emitsOwnStatement(blk);
|
|
446
515
|
const testInfo = (blk: Block): TestInfo | null => {
|
|
447
516
|
const term = blk.ops[blk.ops.length - 1];
|
|
448
517
|
if (term.opcode !== 'cond_br') {
|
|
@@ -588,12 +657,15 @@ export function makeSwitchRecovery(deps: SwitchRecoverDeps): SwitchRecovery {
|
|
|
588
657
|
}
|
|
589
658
|
guard.add(cur);
|
|
590
659
|
const term = cur.ops[cur.ops.length - 1];
|
|
591
|
-
const taken =
|
|
660
|
+
const taken = contains(takenBy(ti), xv);
|
|
592
661
|
cur = forwardingTarget(term.successors[taken ? 0 : 1].block);
|
|
593
662
|
}
|
|
594
663
|
};
|
|
595
664
|
const seen = new Set<Block>();
|
|
596
665
|
const work: Block[] = [b];
|
|
666
|
+
// The values that can reach each test block, written as the walk pushes it. Only a bound case
|
|
667
|
+
// reads it; the tree is a tree (a second visit declines below), so each block has one entry.
|
|
668
|
+
const pathOf = new Map<Block, Ranges>([[b, ALL]]);
|
|
597
669
|
while (work.length) {
|
|
598
670
|
const blk = work.pop()!;
|
|
599
671
|
if (seen.has(blk)) {
|
|
@@ -628,10 +700,13 @@ export function makeSwitchRecovery(deps: SwitchRecoverDeps): SwitchRecovery {
|
|
|
628
700
|
cases.set(k, child);
|
|
629
701
|
return true;
|
|
630
702
|
};
|
|
703
|
+
const path = pathOf.get(blk)!;
|
|
631
704
|
/** Read `child` as a NAVIGATION edge: more dispatch to walk, or a non-test leaf, which is a
|
|
632
|
-
* default candidate. Never declines — the leaf's own reading is settled below.
|
|
633
|
-
|
|
705
|
+
* default candidate. Never declines — the leaf's own reading is settled below. `onTaken`
|
|
706
|
+
* says which of `blk`'s edges it is, which decides what the range narrows to. */
|
|
707
|
+
const asNav = (child: Block, onTaken: boolean): boolean => {
|
|
634
708
|
if (isTestOn(child)) {
|
|
709
|
+
pathOf.set(child, narrow(path, ti, onTaken));
|
|
635
710
|
work.push(child);
|
|
636
711
|
return true;
|
|
637
712
|
}
|
|
@@ -642,7 +717,7 @@ export function makeSwitchRecovery(deps: SwitchRecoverDeps): SwitchRecovery {
|
|
|
642
717
|
if (!asCase(taken, ti.k)) {
|
|
643
718
|
return null;
|
|
644
719
|
} // x==k → taken is case k
|
|
645
|
-
if (!asNav(fall)) {
|
|
720
|
+
if (!asNav(fall, false)) {
|
|
646
721
|
return null;
|
|
647
722
|
}
|
|
648
723
|
} else if (ti.cls === 'ne') {
|
|
@@ -652,12 +727,12 @@ export function makeSwitchRecovery(deps: SwitchRecoverDeps): SwitchRecovery {
|
|
|
652
727
|
if (!asCase(fall, ti.k)) {
|
|
653
728
|
return null;
|
|
654
729
|
} // x!=k → the EQUAL side (fall) is case k
|
|
655
|
-
if (!asNav(taken)) {
|
|
730
|
+
if (!asNav(taken, true)) {
|
|
656
731
|
return null;
|
|
657
732
|
}
|
|
658
733
|
} else {
|
|
659
|
-
// relational → navigation, except where
|
|
660
|
-
//
|
|
734
|
+
// relational → navigation, except where a side the compiler declared (`switchBoundCase`)
|
|
735
|
+
// admits a single value and lands on a BODY. Two more refusals:
|
|
661
736
|
// - a bound test at the ROOT. `emit_case_nodes` emits a single-valued node's own
|
|
662
737
|
// `do_jump_if_equal` before either descent test, so a bound test always sits under
|
|
663
738
|
// another test of the same tree; one that OPENS the region did not come from this
|
|
@@ -665,11 +740,13 @@ export function makeSwitchRecovery(deps: SwitchRecoverDeps): SwitchRecovery {
|
|
|
665
740
|
// - a singleton branch onto another TEST of the scrutinee, which is the search
|
|
666
741
|
// descending to pin the value. It is dispatch, so the walk reads it as dispatch —
|
|
667
742
|
// recovering it, or declining at PRE4 if it is not collapsible.
|
|
668
|
-
const
|
|
669
|
-
|
|
743
|
+
const sides = blk !== b ? switchBoundCase : null;
|
|
744
|
+
const kTaken = sides && !isTestOn(taken) ? singleton(narrow(path, ti, true)) : null;
|
|
745
|
+
const kFall = sides === 'either' && !isTestOn(fall) ? singleton(narrow(path, ti, false)) : null;
|
|
746
|
+
if (!(kTaken === null ? asNav(taken, true) : asCase(taken, kTaken))) {
|
|
670
747
|
return null;
|
|
671
748
|
}
|
|
672
|
-
if (!asNav(fall)) {
|
|
749
|
+
if (!(kFall === null ? asNav(fall, false) : asCase(fall, kFall))) {
|
|
673
750
|
return null;
|
|
674
751
|
}
|
|
675
752
|
}
|
|
@@ -1060,3 +1137,7 @@ export function makeSwitchRecovery(deps: SwitchRecoverDeps): SwitchRecovery {
|
|
|
1060
1137
|
};
|
|
1061
1138
|
return { recognizeSwitch, analyzeArmExit, layoutIndex, defaultLayoutPos, chainArms };
|
|
1062
1139
|
}
|
|
1140
|
+
|
|
1141
|
+
/** Internal surface for this module's own tests, and for nothing else: PRE3 reads each test
|
|
1142
|
+
* through {@link takenBy}, so the ranges need a check that does not share their definition. */
|
|
1143
|
+
export const __testing = { takenBy, narrow, contains, ALL };
|
package/src/symbols.ts
CHANGED
|
@@ -268,6 +268,27 @@ export function isBitfieldField(f: SymbolStructField): boolean {
|
|
|
268
268
|
return f.bitWidth !== undefined;
|
|
269
269
|
}
|
|
270
270
|
|
|
271
|
+
/** Does this declaration make an access at `byte` of the object volatile? The object's own
|
|
272
|
+
* qualifier answers for every byte. A qualified MEMBER answers only for the bytes it spans — the
|
|
273
|
+
* `vu16 field;` idiom puts one in a struct whose other members are ordinary cells — and one of
|
|
274
|
+
* unknown extent spans whatever follows it. `null` is an access whose offset is not pinned, which
|
|
275
|
+
* may reach any member, so only a layout whose every member is qualified answers it. A member this
|
|
276
|
+
* module cannot read (`SymbolMap` is caller-supplied) qualifies nothing. */
|
|
277
|
+
export function declaresVolatile(info: SymbolInfo | undefined, byte: number | null): boolean {
|
|
278
|
+
if (info?.volatile === true) {
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
const layout: unknown = info?.layout;
|
|
282
|
+
if (!Array.isArray(layout) || layout.length === 0) {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
const qualified = (f: unknown): f is SymbolStructField => wellFormedField(f) && f.volatile === true;
|
|
286
|
+
if (byte === null) {
|
|
287
|
+
return layout.every(qualified);
|
|
288
|
+
}
|
|
289
|
+
return layout.some((f) => qualified(f) && byte >= f.offset && (f.size === null || byte < f.offset + f.size));
|
|
290
|
+
}
|
|
291
|
+
|
|
271
292
|
/** Does the map declare a bitfield member anywhere, in a symbol's own layout or its pointee's? */
|
|
272
293
|
export function declaresBitfields(symbols: SymbolMap): boolean {
|
|
273
294
|
return [...symbols.values()].some((infos) =>
|
|
@@ -494,6 +515,16 @@ export function isScalarCellSize(size: number | undefined): size is 1 | 2 | 4 {
|
|
|
494
515
|
return size === 1 || size === 2 || size === 4;
|
|
495
516
|
}
|
|
496
517
|
|
|
518
|
+
/** THE SIGNEDNESS AN ACCESS OF THIS WIDTH STATES. A sub-word load says in the instruction whether
|
|
519
|
+
* it sign- or zero-fills; a whole-word one performs no extension at all, so whatever `signed`
|
|
520
|
+
* its attrs carry says nothing about the cell. Every comparison between an access and a
|
|
521
|
+
* declaration normalizes both sides through here — `rank-declare.ts`'s `bareGlobalAccessFacts`
|
|
522
|
+
* for a bare name over a whole function, `raise/offsetnames.ts` for the accesses made through one
|
|
523
|
+
* address — or a 4-byte load's incidental attr decides a declaration. */
|
|
524
|
+
export function accessSignedness(width: number, signed: boolean | undefined): boolean {
|
|
525
|
+
return signed === true && width < 4;
|
|
526
|
+
}
|
|
527
|
+
|
|
497
528
|
/** THE DECLARED type of a 1/2/4-byte scalar cell — what `extern T gSym;` synthesis writes, and
|
|
498
529
|
* therefore what `&gSym` actually points to.
|
|
499
530
|
*
|
|
@@ -625,8 +656,9 @@ function factsOf(info: SymbolInfo): string {
|
|
|
625
656
|
);
|
|
626
657
|
}
|
|
627
658
|
|
|
628
|
-
/** NAME-keyed view over every symbol in the map
|
|
629
|
-
* symbol names, not addresses). Aliases at one address each appear under
|
|
659
|
+
/** NAME-keyed view over every symbol in the map, or over the names in `only` — what the structurer
|
|
660
|
+
* consumes (it sees gaddr symbol names, not addresses). Aliases at one address each appear under
|
|
661
|
+
* their own name.
|
|
630
662
|
*
|
|
631
663
|
* One name can sit at SEVERAL addresses in a real project (file-static `sMenu` in two
|
|
632
664
|
* translation units, a `.symtab` full of same-named locals). Where those entries agree on their
|
|
@@ -639,11 +671,14 @@ function factsOf(info: SymbolInfo): string {
|
|
|
639
671
|
* spelling question into a compile failure); only the shape facts, which are what could be wrong,
|
|
640
672
|
* are withheld — the honest cast spellings take over. `kind` is kept: it never disagrees in the
|
|
641
673
|
* vendored maps, and it is settled address-side by `lookupSymbol` before a name is ever used. */
|
|
642
|
-
export function symbolsByName(map: SymbolMap): Map<string, SymbolInfo> {
|
|
674
|
+
export function symbolsByName(map: SymbolMap, only?: ReadonlySet<string>): Map<string, SymbolInfo> {
|
|
643
675
|
const byName = new Map<string, SymbolInfo>();
|
|
644
676
|
const conflicted = new Set<string>();
|
|
645
677
|
for (const infos of map.values()) {
|
|
646
678
|
for (const info of infos) {
|
|
679
|
+
if (only !== undefined && !only.has(info.name)) {
|
|
680
|
+
continue;
|
|
681
|
+
}
|
|
647
682
|
const prev = byName.get(info.name);
|
|
648
683
|
if (prev === undefined) {
|
|
649
684
|
byName.set(info.name, info);
|