@asmlift/core 0.3.0 → 0.4.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 +5 -3
- package/package.json +1 -1
- package/src/backend/cfamily.ts +125 -2
- package/src/backend/cpp.ts +3 -1
- package/src/backend/pascal.ts +11 -0
- package/src/contracts.ts +15 -2
- package/src/declare.ts +35 -9
- package/src/frontend/mips.ts +24 -23
- package/src/frontend/opaque.ts +39 -2
- package/src/frontend/ssa.ts +32 -53
- package/src/frontend/thumb.ts +301 -26
- package/src/ir/opcodes.ts +44 -0
- package/src/ir/simplify.ts +72 -0
- package/src/l3/argbase.ts +216 -0
- package/src/l3/ast.ts +118 -4
- package/src/l3/basecse.ts +3 -40
- package/src/l3/coalesce.ts +146 -0
- package/src/l3/dce.ts +2 -23
- package/src/l3/hoist.ts +65 -0
- package/src/l3/reindex.ts +7 -0
- package/src/l3/scopebase.ts +436 -0
- package/src/l3/tailmerge.ts +120 -0
- package/src/macros.ts +222 -13
- package/src/pattern/engine.ts +99 -6
- package/src/pipeline.ts +5 -2
- package/src/raise/divpow2.ts +226 -0
- package/src/raise/gvn.ts +141 -0
- package/src/raise/pre-recovery.ts +37 -3
- package/src/raise/recover.ts +24 -7
- package/src/raise/retsink.ts +36 -7
- package/src/raise/shortcircuit.ts +264 -22
- package/src/raise/structs.ts +12 -2
- package/src/rank.ts +172 -20
- package/src/structure/analysis.ts +42 -1
- package/src/structure/structure.ts +399 -31
- package/src/structure/switch-recover.ts +21 -3
- package/src/symbols.ts +128 -13
- package/src/target.ts +4 -2
- package/src/trace.ts +9 -0
|
@@ -18,6 +18,12 @@ export interface SwitchRecoverDeps {
|
|
|
18
18
|
/** is this opcode an integer comparison? */
|
|
19
19
|
isCmpOpcode: (opcode: string) => boolean;
|
|
20
20
|
switchAllowsNeqCase: boolean;
|
|
21
|
+
/** does emitting this block's ops carry a statement beyond the ops themselves? A def-site
|
|
22
|
+
* ANCHORED merge copy (structure.ts anchorConstCopies) is attached to a const op and emitted
|
|
23
|
+
* with the block's side effects — a test block carrying one is not pure however pure its
|
|
24
|
+
* opcodes look, because collapsing it into a `switch` discards the write while the edge copy
|
|
25
|
+
* it replaced stays suppressed. */
|
|
26
|
+
emitsAnchoredWrite: (blk: Block) => boolean;
|
|
21
27
|
expr: (v: Value) => Expr;
|
|
22
28
|
structureRegion: (b: Block, stop: Block | null) => Stmt[];
|
|
23
29
|
}
|
|
@@ -29,7 +35,19 @@ export interface SwitchRecovery {
|
|
|
29
35
|
}
|
|
30
36
|
|
|
31
37
|
export function makeSwitchRecovery(deps: SwitchRecoverDeps): SwitchRecovery {
|
|
32
|
-
const {
|
|
38
|
+
const {
|
|
39
|
+
fn,
|
|
40
|
+
defs,
|
|
41
|
+
dom,
|
|
42
|
+
ipdom,
|
|
43
|
+
opBlock,
|
|
44
|
+
isNamed,
|
|
45
|
+
isCmpOpcode,
|
|
46
|
+
switchAllowsNeqCase,
|
|
47
|
+
emitsAnchoredWrite,
|
|
48
|
+
expr,
|
|
49
|
+
structureRegion,
|
|
50
|
+
} = deps;
|
|
33
51
|
|
|
34
52
|
// --- Regime A: comparison-tree switch recovery ----------------------------------------------------
|
|
35
53
|
// Every ambiguity declines. Four preconditions are enforced below, annotated PRE1..PRE4:
|
|
@@ -128,9 +146,9 @@ export function makeSwitchRecovery(deps: SwitchRecoverDeps): SwitchRecovery {
|
|
|
128
146
|
if (!cmp || !isCmpOpcode(cmp.opcode)) {
|
|
129
147
|
return null;
|
|
130
148
|
}
|
|
131
|
-
if (!isRoot && blk.ops.some((op) => SIDE_EFFECTFUL.has(op.opcode))) {
|
|
149
|
+
if (!isRoot && (blk.ops.some((op) => SIDE_EFFECTFUL.has(op.opcode)) || emitsAnchoredWrite(blk))) {
|
|
132
150
|
return null;
|
|
133
|
-
} // PRE4
|
|
151
|
+
} // PRE4 — anchored writes included: discarded with the block, while their edge copies stay suppressed
|
|
134
152
|
// Which operand is the scrutinee, which is the constant?
|
|
135
153
|
const [lo, ro] = cmp.operands;
|
|
136
154
|
const lc = evalConst(lo),
|
package/src/symbols.ts
CHANGED
|
@@ -52,6 +52,17 @@ export interface SymbolStructField {
|
|
|
52
52
|
/** ARRAY field only: the element count (absent for a flexible array member, which declares a
|
|
53
53
|
* stride but no bound) — types the synthesized `T name[n];` field decl */
|
|
54
54
|
length?: number;
|
|
55
|
+
/** BITFIELD field only: the field's width in BITS. Its PRESENCE is what marks a field a
|
|
56
|
+
* bitfield — `size` above stays the byte span its bits touch (the read width the compiler
|
|
57
|
+
* uses), which is why the exact (offset,size) scalar-field rules must exclude it. The
|
|
58
|
+
* provider only emits these for LITTLE-ENDIAN ELFs: both the extract equation the access
|
|
59
|
+
* recognizer solves and the `u32 name : n` layout model the synthesis verifies are LE-GCC
|
|
60
|
+
* semantics, so a big-endian map carries no bitfield members at all (today's behavior). */
|
|
61
|
+
bitWidth?: number;
|
|
62
|
+
/** BITFIELD field only: the bit position of the field's LOW bit within the byte at `offset`
|
|
63
|
+
* (LSB-first) — the field's absolute low bit is `offset*8 + bitOffset`. Required alongside
|
|
64
|
+
* `bitWidth`; a bitfield missing it is malformed and declines the whole layout. */
|
|
65
|
+
bitOffset?: number;
|
|
55
66
|
}
|
|
56
67
|
|
|
57
68
|
/** What a `shape:'pointer'` global POINTS AT, when the sidecar says its target is a struct/union.
|
|
@@ -118,6 +129,15 @@ export interface SymbolInfo {
|
|
|
118
129
|
elemSize?: number;
|
|
119
130
|
/** element signedness for `shape:'array'` (default unsigned) — types the env entry */
|
|
120
131
|
elemSigned?: boolean;
|
|
132
|
+
/** ARRAY RANK for `shape:'array'` — the per-dimension extents, outermost first (`u16
|
|
133
|
+
* g[4][0x400]` → `[4, 1024]`), `null` for an unbounded one. It is NOT `size`/`elemSize`
|
|
134
|
+
* restated: those size the object, this says how many subscripts reach an ELEMENT. `gSym[i]`
|
|
135
|
+
* on a rank-2 array is a ROW — against the project's own header that is a type error, or,
|
|
136
|
+
* where the row address flows into an integer context, silently the wrong address. So the
|
|
137
|
+
* bare spelling needs the leading subscripts (`gSym[0][i]`), and its ABSENCE is what forbids
|
|
138
|
+
* the bare spelling from being attempted at all (see the provider's dims capability gate:
|
|
139
|
+
* a package that cannot report rank must not be read as "rank 1"). */
|
|
140
|
+
dims?: (number | null)[];
|
|
121
141
|
/** the real struct tag for `shape:'struct'` — names the synthesized struct declaration
|
|
122
142
|
* (absent ⇒ synthesis mints a placeholder tag; the tag is codegen-arbitrary) */
|
|
123
143
|
structName?: string;
|
|
@@ -145,6 +165,33 @@ export interface SymbolInfo {
|
|
|
145
165
|
macroBody?: string;
|
|
146
166
|
}
|
|
147
167
|
|
|
168
|
+
/** THE one reading of {@link SymbolInfo.dims} for spelling C, shared by the access side
|
|
169
|
+
* (structure.ts's bare-name gate) and the declaration side (declare.ts) so the two cannot
|
|
170
|
+
* disagree about an array's shape.
|
|
171
|
+
*
|
|
172
|
+
* Returns the INNER extents — every dimension but the outermost. The outermost is excluded
|
|
173
|
+
* because C lets a declaration omit it, and the inner ones are exactly what scales a leading
|
|
174
|
+
* subscript. `[]` is the rank-1 answer: one subscript, `extern T gSym[];`, the spelling this has
|
|
175
|
+
* always had.
|
|
176
|
+
*
|
|
177
|
+
* An ABSENT `dims` also reads as rank 1, because that is what the author of such a map said: the
|
|
178
|
+
* ELF provider's capability gate refuses a @gba-kit/debug-info that cannot report rank, so
|
|
179
|
+
* absence here can only come from a hand-written map whose `shape:'array'` states a plain array.
|
|
180
|
+
* Absence never means "the package could not say" — that case fails loudly at load.
|
|
181
|
+
*
|
|
182
|
+
* Null means NO consistent pair is available (a stated rank with an unknown inner extent, which
|
|
183
|
+
* neither a declaration nor a subscript can spell). Both sides honour it the same way: the access
|
|
184
|
+
* falls back to `((T *)&gSym)[i]`, the declaration to the flat `extern T gSym[];` — valid
|
|
185
|
+
* together under whatever the project's own header says. */
|
|
186
|
+
export function arrayInnerExtents(info: SymbolInfo): number[] | null {
|
|
187
|
+
const dims = info.shape === 'array' ? info.dims : undefined;
|
|
188
|
+
if (dims === undefined || dims.length <= 1) {
|
|
189
|
+
return [];
|
|
190
|
+
}
|
|
191
|
+
const inner = dims.slice(1);
|
|
192
|
+
return inner.every((d) => typeof d === 'number' && d > 0) ? (inner as number[]) : null;
|
|
193
|
+
}
|
|
194
|
+
|
|
148
195
|
/** address → symbols at that address; `[0]` is the provider's canonical pick. */
|
|
149
196
|
export type SymbolMap = Map<number, SymbolInfo[]>;
|
|
150
197
|
|
|
@@ -155,6 +202,13 @@ export function isArrayField(f: SymbolStructField): boolean {
|
|
|
155
202
|
return f.elemSize !== undefined;
|
|
156
203
|
}
|
|
157
204
|
|
|
205
|
+
/** THE one test for "is this field a bitfield" — the PRESENCE of `bitWidth` (see the field doc).
|
|
206
|
+
* The exact (offset,size) scalar-field rules must exclude these: a 7-bit field whose bits span
|
|
207
|
+
* 2 bytes carries `size: 2` and would otherwise match a plain u16 read at its offset. */
|
|
208
|
+
export function isBitfieldField(f: SymbolStructField): boolean {
|
|
209
|
+
return f.bitWidth !== undefined;
|
|
210
|
+
}
|
|
211
|
+
|
|
158
212
|
/** A layout member that {@link declaredFields} passed: sizable, and seated at an offset no
|
|
159
213
|
* earlier member already covers. */
|
|
160
214
|
export type DeclaredField = SymbolStructField & { size: number };
|
|
@@ -166,12 +220,30 @@ function wellFormedField(f: unknown): f is SymbolStructField {
|
|
|
166
220
|
return false;
|
|
167
221
|
}
|
|
168
222
|
const m = f as Partial<SymbolStructField>;
|
|
169
|
-
|
|
170
|
-
typeof m.name
|
|
171
|
-
typeof m.offset
|
|
172
|
-
Number.isFinite(m.offset)
|
|
173
|
-
(m.size === null || (typeof m.size === 'number' && Number.isFinite(m.size) && m.size >= 0))
|
|
174
|
-
)
|
|
223
|
+
if (
|
|
224
|
+
typeof m.name !== 'string' ||
|
|
225
|
+
typeof m.offset !== 'number' ||
|
|
226
|
+
!Number.isFinite(m.offset) ||
|
|
227
|
+
!(m.size === null || (typeof m.size === 'number' && Number.isFinite(m.size) && m.size >= 0))
|
|
228
|
+
) {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
// A bitfield's two facts must be present TOGETHER and internally consistent — a bitWidth with
|
|
232
|
+
// no bitOffset (or bits outside the byte span `size` claims) leaves the field unseatable, so
|
|
233
|
+
// the member is malformed and the layout declines whole like any other malformed member.
|
|
234
|
+
if (m.bitWidth !== undefined) {
|
|
235
|
+
return (
|
|
236
|
+
typeof m.bitWidth === 'number' &&
|
|
237
|
+
Number.isInteger(m.bitWidth) &&
|
|
238
|
+
m.bitWidth > 0 &&
|
|
239
|
+
typeof m.bitOffset === 'number' &&
|
|
240
|
+
Number.isInteger(m.bitOffset) &&
|
|
241
|
+
m.bitOffset >= 0 &&
|
|
242
|
+
typeof m.size === 'number' &&
|
|
243
|
+
m.bitOffset + m.bitWidth <= m.size * 8
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
return true;
|
|
175
247
|
}
|
|
176
248
|
|
|
177
249
|
/**
|
|
@@ -202,15 +274,33 @@ export function declaredFields(layout: SymbolStructField[] | undefined): Declare
|
|
|
202
274
|
return null;
|
|
203
275
|
}
|
|
204
276
|
}
|
|
205
|
-
|
|
277
|
+
// The cursor is in BITS so co-located bitfields seat correctly: `u32 a:2; u32 b:3;` are two
|
|
278
|
+
// members at byte offset 0, not a union alias. For plain members the arithmetic is the old
|
|
279
|
+
// byte cursor times 8 — behavior-identical for every bitfield-free layout.
|
|
280
|
+
const lowBitOf = (m: DeclaredField): number => m.offset * 8 + (m.bitWidth !== undefined ? m.bitOffset! : 0);
|
|
281
|
+
const members = (layout as DeclaredField[])
|
|
282
|
+
.slice()
|
|
283
|
+
.sort((a, b) => lowBitOf(a) - lowBitOf(b) || (a.bitWidth ?? -1) - (b.bitWidth ?? -1));
|
|
206
284
|
const out: DeclaredField[] = [];
|
|
207
|
-
let
|
|
285
|
+
let bitCursor = 0;
|
|
208
286
|
for (const m of members) {
|
|
209
|
-
|
|
287
|
+
const lo = lowBitOf(m);
|
|
288
|
+
if (lo < bitCursor) {
|
|
210
289
|
continue; // an overlapping (union) member: the first view is declared, the alias is not
|
|
211
290
|
}
|
|
212
|
-
|
|
213
|
-
|
|
291
|
+
if (m.bitWidth !== undefined) {
|
|
292
|
+
// The synthesis lays bitfields as LE-GCC `u32 name : n`, whose allocation never straddles
|
|
293
|
+
// a 32-bit unit — a field that would cannot be reproduced, so it is not declared (its bits
|
|
294
|
+
// pad instead) and no access may name it. The cursor does NOT advance: the bits stay a hole.
|
|
295
|
+
if (Math.floor(lo / 32) !== Math.floor((lo + m.bitWidth - 1) / 32)) {
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
out.push(m);
|
|
299
|
+
bitCursor = lo + m.bitWidth;
|
|
300
|
+
} else {
|
|
301
|
+
out.push(m);
|
|
302
|
+
bitCursor = (m.offset + m.size) * 8;
|
|
303
|
+
}
|
|
214
304
|
}
|
|
215
305
|
return out;
|
|
216
306
|
}
|
|
@@ -247,14 +337,39 @@ export function symbolFieldType(f: DeclaredField): IrType {
|
|
|
247
337
|
const scalarPointee = f.pointeeSize === 1 || f.pointeeSize === 2 || f.pointeeSize === 4;
|
|
248
338
|
return T.ptr(scalarPointee ? T.int(f.pointeeSize! * 8, f.pointeeSigned ?? false) : T.void());
|
|
249
339
|
}
|
|
250
|
-
if (f
|
|
251
|
-
|
|
340
|
+
if (isBitfieldField(f)) {
|
|
341
|
+
// The BASE type of the synthesized `u32 name : n` — the `: n` itself is the declaration
|
|
342
|
+
// renderer's job (StructFieldDecl.bits). 32-bit base always: that is the LE-GCC unit model
|
|
343
|
+
// declaredFields verified the layout against. A signless bitfield declares unsigned — the
|
|
344
|
+
// extract recognizer refuses to NAME one anyway, so the choice only types padding.
|
|
345
|
+
return T.int(32, f.signed ?? false);
|
|
346
|
+
}
|
|
347
|
+
if (isScalarCellSize(f.size)) {
|
|
348
|
+
return scalarCellType(f.size, f.signed);
|
|
252
349
|
}
|
|
253
350
|
return T.array(T.u(8), f.size);
|
|
254
351
|
}
|
|
255
352
|
/** A 4-byte member/scalar with NO base-type signedness is the enum idiom — C89 says int. */
|
|
256
353
|
export const ENUM_IS_SIGNED = true;
|
|
257
354
|
|
|
355
|
+
/** Is this byte size one a base type can spell? */
|
|
356
|
+
export function isScalarCellSize(size: number | undefined): size is 1 | 2 | 4 {
|
|
357
|
+
return size === 1 || size === 2 || size === 4;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/** THE DECLARED type of a 1/2/4-byte scalar cell — what `extern T gSym;` synthesis writes, and
|
|
361
|
+
* therefore what `&gSym` actually points to.
|
|
362
|
+
*
|
|
363
|
+
* Extracted because this rule had drifted into a fourth copy. It is NOT `scalarTypeForAccess`,
|
|
364
|
+
* which answers a different question — the type an ACCESS of that width reads — and collapses
|
|
365
|
+
* every 4-byte access to `s32` whatever the signedness. Using that one to decide "does `&gSym`
|
|
366
|
+
* already have the destination's type" silently answered YES for a `u32` cell reaching an
|
|
367
|
+
* `s32 *`, and the incompatible-pointer assignment survived. Declaration side and access side are
|
|
368
|
+
* separate facts; this is the declaration one. */
|
|
369
|
+
export function scalarCellType(size: 1 | 2 | 4, signed: boolean | undefined): IrType {
|
|
370
|
+
return T.int(size * 8, signed ?? (size === 4 ? ENUM_IS_SIGNED : false));
|
|
371
|
+
}
|
|
372
|
+
|
|
258
373
|
/**
|
|
259
374
|
* THE gate on every spelling through a POINTER global's value: the members a `gPtr->member`
|
|
260
375
|
* spelling may name, or null when nothing may be named through this pointee at all. Null unless
|
package/src/target.ts
CHANGED
|
@@ -34,7 +34,7 @@ export interface TargetDescription {
|
|
|
34
34
|
returnReg: string;
|
|
35
35
|
// HARDWARE / ISA facts — independent of the compiler.
|
|
36
36
|
capabilities: {
|
|
37
|
-
endianness: 'little' | 'big'; //
|
|
37
|
+
endianness: 'little' | 'big'; // consumed by structureOptionsFor (bitfield extract recognition is LSB-first)
|
|
38
38
|
hwDivide: boolean; // consumed by patternApplies (idiom gating)
|
|
39
39
|
hwFloat: boolean; // consumed by patternApplies (idiom gating)
|
|
40
40
|
flags: boolean; // RESERVED — no pass reads it yet (PPC condition regs will)
|
|
@@ -132,7 +132,9 @@ export const PPC_MWCC: TargetDescription = {
|
|
|
132
132
|
* target's compiler behaviors flow into the target-agnostic structurer — a new behavior lever
|
|
133
133
|
* is a field in `compilerBehaviors`, consumed automatically. */
|
|
134
134
|
export function structureOptionsFor(t: TargetDescription, returnsVoid: boolean): StructureOptions {
|
|
135
|
-
|
|
135
|
+
// `littleEndian` is the one HARDWARE capability the structurer consumes (bitfield extract
|
|
136
|
+
// recognition is LSB-first); everything else is a compiler behavior.
|
|
137
|
+
return { returnsVoid, littleEndian: t.capabilities.endianness === 'little', ...t.compilerBehaviors };
|
|
136
138
|
}
|
|
137
139
|
|
|
138
140
|
export const C_TYPEDEFS =
|
package/src/trace.ts
CHANGED
|
@@ -72,12 +72,21 @@ export interface TraceOptions {
|
|
|
72
72
|
// (not in pre-recovery.ts) because these strings are a trace concern — the driver itself is
|
|
73
73
|
// trace-agnostic. `title` is a function so `arrays` can fold its scaled-access count in.
|
|
74
74
|
const PRE_RECOVERY_TRACE: Record<string, { stage: string; title: (result: number | boolean) => string }> = {
|
|
75
|
+
addrnum: {
|
|
76
|
+
stage: 'stage:addrnum',
|
|
77
|
+
title: (r) => `Address numbering (${r} duplicate address def(s) / trivial phi(s) collapsed)`,
|
|
78
|
+
},
|
|
75
79
|
const: { stage: 'stage:const', title: () => 'Const materialize (lui;ori → one 32-bit const)' },
|
|
76
80
|
magicdiv: { stage: 'stage:magicdiv', title: () => 'Magic-number division recovery (mulh/mulhu → sdiv/udiv)' },
|
|
77
81
|
softdiv: { stage: 'stage:softdiv', title: () => 'Soft-division lower (bl __divsi3 → division op)' },
|
|
78
82
|
arrays: { stage: 'stage:legalize', title: (r) => `Array legalize (${r} scaled access(es) → aload/astore)` },
|
|
79
83
|
structs: { stage: 'stage:structs', title: () => 'Struct-pointer recovery (access-pattern evidence)' },
|
|
80
84
|
shortcircuit: { stage: 'stage:shortcircuit', title: () => 'Short-circuit recovery (boolean && / ||)' },
|
|
85
|
+
'branch-shortcircuit': {
|
|
86
|
+
stage: 'stage:branch-shortcircuit',
|
|
87
|
+
title: () => 'Short-circuit recovery (control-flow && / ||)',
|
|
88
|
+
},
|
|
89
|
+
'struct-arrays': { stage: 'stage:struct-arrays', title: () => 'Struct-array recovery (element stride evidence)' },
|
|
81
90
|
};
|
|
82
91
|
|
|
83
92
|
/** Run the tower while recording a TraceReport. Strict mode throws on any gap (like decompile);
|