@asmlift/core 0.5.0 → 0.7.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 +22 -16
- package/package.json +1 -1
- package/src/backend/c.ts +1 -0
- package/src/backend/cfamily.ts +270 -171
- package/src/backend/cpp.ts +1 -0
- package/src/backend/pascal.ts +26 -12
- package/src/contracts.ts +243 -39
- package/src/declare.ts +41 -4
- package/src/frontend/mips.ts +11 -0
- package/src/frontend/ppc.ts +43 -7
- package/src/frontend/ssa.ts +404 -29
- package/src/frontend/thumb.ts +2176 -686
- package/src/ir/alias.ts +78 -0
- package/src/ir/bits.ts +75 -0
- package/src/ir/core.ts +345 -2
- package/src/ir/opcodes.ts +176 -21
- package/src/ir/parse.ts +19 -2
- package/src/ir/print.ts +27 -2
- package/src/ir/simplify.ts +190 -3
- package/src/ir/struct-names.ts +42 -0
- package/src/ir/verify.ts +43 -49
- package/src/l3/address.ts +62 -0
- package/src/l3/advance.ts +373 -0
- package/src/l3/argbase.ts +6 -5
- package/src/l3/ast.ts +510 -59
- package/src/l3/basecse.ts +686 -78
- package/src/l3/coalesce.ts +432 -46
- package/src/l3/dce.ts +31 -9
- package/src/l3/gates.ts +96 -1
- package/src/l3/hoist.ts +293 -14
- package/src/l3/homesplit.ts +285 -0
- package/src/l3/initfirst.ts +301 -0
- package/src/l3/inlinebase.ts +193 -0
- package/src/l3/mentions.ts +176 -0
- package/src/l3/mulfirst.ts +42 -0
- package/src/l3/nearbase.ts +152 -0
- package/src/l3/offmember.ts +371 -0
- package/src/l3/parkfirst.ts +96 -0
- package/src/l3/pollguard.ts +154 -0
- package/src/l3/ptrfield.ts +227 -0
- package/src/l3/regspell.ts +114 -89
- package/src/l3/reindex.ts +722 -80
- package/src/l3/scopebase.ts +649 -220
- package/src/l3/sinkinit.ts +40 -0
- package/src/l3/slotorder.ts +123 -0
- package/src/l3/storage.ts +48 -0
- package/src/l3/symbol-refs.ts +41 -8
- package/src/l3/tailmerge.ts +16 -1
- package/src/l3/typing.ts +198 -9
- package/src/l3/unmerge.ts +687 -0
- package/src/l3/unreduce.ts +971 -0
- package/src/l3/volatileptr.ts +207 -0
- package/src/l3/volatileval.ts +130 -0
- package/src/l3/volstore.ts +229 -0
- package/src/l3/zerosub.ts +62 -0
- package/src/pattern/engine.ts +239 -16
- package/src/pipeline.ts +173 -60
- package/src/proto.ts +112 -14
- package/src/raise/arrays.ts +6 -1
- package/src/raise/const.ts +203 -3
- package/src/raise/divpow2.ts +4 -4
- package/src/raise/extscale.ts +342 -0
- package/src/raise/globalshape.ts +1058 -0
- package/src/raise/gvn.ts +33 -18
- package/src/raise/latch.ts +126 -0
- package/src/raise/magicdiv.ts +2 -2
- package/src/raise/memberarrays.ts +594 -0
- package/src/raise/narrow.ts +124 -0
- package/src/raise/narrowlocal.ts +572 -0
- package/src/raise/paramwidth.ts +201 -0
- package/src/raise/pre-recovery.ts +169 -21
- package/src/raise/recover.ts +56 -23
- package/src/raise/retsink.ts +585 -19
- package/src/raise/shortcircuit.ts +1050 -89
- package/src/raise/struct-arrays.ts +19 -2
- package/src/raise/structs.ts +34 -4
- package/src/raise/tailsink.ts +126 -0
- package/src/rank-declare.ts +256 -0
- package/src/rank-variations.ts +760 -0
- package/src/rank.ts +2122 -326
- package/src/structure/analysis.ts +1398 -150
- package/src/structure/bitfields.ts +432 -0
- package/src/structure/globalaccess.ts +300 -0
- package/src/structure/hazards.ts +411 -20
- package/src/structure/loops.ts +2 -49
- package/src/structure/namecoalesce.ts +454 -0
- package/src/structure/structure.ts +3979 -612
- package/src/structure/switch-recover.ts +710 -145
- package/src/symbols.ts +188 -6
- package/src/target.ts +495 -32
- package/src/trace.ts +112 -33
- package/src/variation-definitions.ts +1540 -0
- package/src/variation-gates.ts +89 -0
- package/src/variation-tokens.ts +355 -0
package/src/symbols.ts
CHANGED
|
@@ -52,9 +52,30 @@ 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
|
+
/** ARRAY field only: the RANK — the per-dimension extents, outermost first (`u8 x[6][8]` →
|
|
56
|
+
* `[6, 8]`), `null` for an unbounded one. {@link SymbolInfo.dims}'s law, one indirection down,
|
|
57
|
+
* and for the same reason: `length` is the PRODUCT of the dimensions, so it cannot say how many
|
|
58
|
+
* subscripts reach an ELEMENT. `->x[i]` on a `[6][8]` member is a ROW, and against the
|
|
59
|
+
* project's own header that is a type error — or, where the row address flows into an integer
|
|
60
|
+
* context, silently the wrong address.
|
|
61
|
+
*
|
|
62
|
+
* ABSENCE READS DIFFERENTLY HERE THAN IT DOES FOR A GLOBAL, and the difference is the whole
|
|
63
|
+
* point of the field. {@link arrayInnerExtents} may read an absent `SymbolInfo.dims` as rank 1
|
|
64
|
+
* because the ELF provider's capability gate refuses a @gba-kit/debug-info that cannot report
|
|
65
|
+
* a global's rank. No such gate covers a MEMBER's rank — a member simply omits the key both
|
|
66
|
+
* when the DWARF carries no subranges and when the package is too old to look — so absence
|
|
67
|
+
* here means "the map could not say". A consumer that must type-check against a FOREIGN
|
|
68
|
+
* declaration (the indexed member spelling, structure.ts's `pointeeElement`) refuses on
|
|
69
|
+
* absence; a consumer synthesizing its OWN declaration may still flatten, because its access
|
|
70
|
+
* and its declaration then agree by construction. That is why {@link structFieldInnerExtents}
|
|
71
|
+
* answers `[]` for absence and the refusal lives at the access site instead. */
|
|
72
|
+
dims?: (number | null)[];
|
|
55
73
|
/** BITFIELD field only: the field's width in BITS. Its PRESENCE is what marks a field a
|
|
56
|
-
* bitfield — `size` above stays the byte
|
|
57
|
-
*
|
|
74
|
+
* bitfield — `size` above stays the byte SPAN its bits touch, which is why the exact
|
|
75
|
+
* (offset,size) scalar-field rules must exclude it. The span is NOT the access the compiler
|
|
76
|
+
* uses: measured with the pinned agbcc, `u32 x : 8` at bit 12 spans 2 bytes and is reached by
|
|
77
|
+
* a WORD, `u32 a : 20` spans 3, and `u16 a : 2` spans 1 in a 2-byte container. A consumer
|
|
78
|
+
* needing the access width computes it from `bitOffset`/`bitWidth` (structure/bitfields.ts). The
|
|
58
79
|
* provider only emits these for LITTLE-ENDIAN ELFs: both the extract equation the access
|
|
59
80
|
* recognizer solves and the `u32 name : n` layout model the synthesis verifies are LE-GCC
|
|
60
81
|
* semantics, so a big-endian map carries no bitfield members at all (today's behavior). */
|
|
@@ -90,7 +111,12 @@ export interface SymbolPointee {
|
|
|
90
111
|
|
|
91
112
|
/** One declared type in a signature — width, signedness, pointer-ness. Deliberately the same
|
|
92
113
|
* vocabulary a struct member uses, so a parameter and a field of the same C type describe
|
|
93
|
-
* identically. `size: null` = the DWARF did not size it.
|
|
114
|
+
* identically. `size: null` = the DWARF did not size it.
|
|
115
|
+
*
|
|
116
|
+
* NO POINTEE, and the absence is UPSTREAM's rather than a shape asmlift dropped:
|
|
117
|
+
* `@gba-kit/debug-info`'s `TypeFacts` — what a `FunctionSignature`'s params are made of — is
|
|
118
|
+
* exactly these three fields. {@link SymbolInfo.pointee} exists only for a symbol AT AN ADDRESS.
|
|
119
|
+
* Widening this is priced in docs/level-tower.md and pinned in test/param-pointee-variation.test.ts. */
|
|
94
120
|
export interface SymbolTypeFacts {
|
|
95
121
|
size: number | null;
|
|
96
122
|
signed: boolean | null;
|
|
@@ -192,6 +218,28 @@ export function arrayInnerExtents(info: SymbolInfo): number[] | null {
|
|
|
192
218
|
return inner.every((d) => typeof d === 'number' && d > 0) ? (inner as number[]) : null;
|
|
193
219
|
}
|
|
194
220
|
|
|
221
|
+
/** {@link arrayInnerExtents}, one indirection down: the INNER extents of an array MEMBER, shared
|
|
222
|
+
* by the access side (structure.ts's indexed member spelling) and the declaration side
|
|
223
|
+
* (symbolFieldType) so the two cannot disagree about a member's shape.
|
|
224
|
+
*
|
|
225
|
+
* `[]` is the rank-1 answer — one subscript, `u8 x[48];`, the spelling every map has had. It is
|
|
226
|
+
* ALSO what an absent `dims` answers, because the declaration synthesis that reads this renders
|
|
227
|
+
* the member itself and so cannot be wrong about it. A consumer type-checking against a header it
|
|
228
|
+
* did NOT write must test `f.dims === undefined` first and decline; see {@link
|
|
229
|
+
* SymbolStructField.dims} for why that asymmetry is real rather than a shortcut.
|
|
230
|
+
*
|
|
231
|
+
* Null means NO consistent pair is available (a stated rank with an unknown or non-positive inner
|
|
232
|
+
* extent, which neither a declaration nor a subscript can spell), and both sides honour it the
|
|
233
|
+
* same way: the declaration falls back to the flat member, the access to the cast form. */
|
|
234
|
+
export function structFieldInnerExtents(f: SymbolStructField): number[] | null {
|
|
235
|
+
const dims = isArrayField(f) ? f.dims : undefined;
|
|
236
|
+
if (dims === undefined || dims.length <= 1) {
|
|
237
|
+
return [];
|
|
238
|
+
}
|
|
239
|
+
const inner = dims.slice(1);
|
|
240
|
+
return inner.every((d) => typeof d === 'number' && d > 0) ? (inner as number[]) : null;
|
|
241
|
+
}
|
|
242
|
+
|
|
195
243
|
/** address → symbols at that address; `[0]` is the provider's canonical pick. */
|
|
196
244
|
export type SymbolMap = Map<number, SymbolInfo[]>;
|
|
197
245
|
|
|
@@ -202,6 +250,17 @@ export function isArrayField(f: SymbolStructField): boolean {
|
|
|
202
250
|
return f.elemSize !== undefined;
|
|
203
251
|
}
|
|
204
252
|
|
|
253
|
+
/** THE one test for "is this field a POINTER", and it is a test of TWO facts. The flag alone is
|
|
254
|
+
* not enough: {@link symbolFieldType} declares a pointer only at `size === 4`, so a `pointer`
|
|
255
|
+
* member of any other size declares as a scalar cell or a byte array — and a consumer trusting
|
|
256
|
+
* the flag alone would spell pointer arithmetic on a value the very declaration beside it calls a
|
|
257
|
+
* `u16`. The two answers must be the SAME answer, for the same reason declaredFields and the
|
|
258
|
+
* synthesis must: core reasoning about a member as something the declaration does not declare is
|
|
259
|
+
* non-compiling C. */
|
|
260
|
+
export function isPtrField(f: SymbolStructField): boolean {
|
|
261
|
+
return f.pointer === true && f.size === 4;
|
|
262
|
+
}
|
|
263
|
+
|
|
205
264
|
/** THE one test for "is this field a bitfield" — the PRESENCE of `bitWidth` (see the field doc).
|
|
206
265
|
* The exact (offset,size) scalar-field rules must exclude these: a 7-bit field whose bits span
|
|
207
266
|
* 2 bytes carries `size: 2` and would otherwise match a plain u16 read at its offset. */
|
|
@@ -209,6 +268,13 @@ export function isBitfieldField(f: SymbolStructField): boolean {
|
|
|
209
268
|
return f.bitWidth !== undefined;
|
|
210
269
|
}
|
|
211
270
|
|
|
271
|
+
/** Does the map declare a bitfield member anywhere, in a symbol's own layout or its pointee's? */
|
|
272
|
+
export function declaresBitfields(symbols: SymbolMap): boolean {
|
|
273
|
+
return [...symbols.values()].some((infos) =>
|
|
274
|
+
infos.some((i) => [...(i.layout ?? []), ...(i.pointee?.layout ?? [])].some(isBitfieldField)),
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
212
278
|
/** A layout member that {@link declaredFields} passed: sizable, and seated at an offset no
|
|
213
279
|
* earlier member already covers. */
|
|
214
280
|
export type DeclaredField = SymbolStructField & { size: number };
|
|
@@ -228,6 +294,31 @@ function wellFormedField(f: unknown): f is SymbolStructField {
|
|
|
228
294
|
) {
|
|
229
295
|
return false;
|
|
230
296
|
}
|
|
297
|
+
// `dims`, when stated, must be an array of positive extents or nulls whose product is the
|
|
298
|
+
// member's own `length` — three facts that contradict each other cannot all be trusted, exactly
|
|
299
|
+
// as `elemSize * length !== size` declines the layout below. A member with no `elemSize` is not
|
|
300
|
+
// an array and may not carry a rank at all.
|
|
301
|
+
if (m.dims !== undefined) {
|
|
302
|
+
if (
|
|
303
|
+
!Array.isArray(m.dims) ||
|
|
304
|
+
m.dims.length === 0 ||
|
|
305
|
+
m.elemSize === undefined ||
|
|
306
|
+
!m.dims.every((d) => d === null || (typeof d === 'number' && Number.isInteger(d) && d > 0))
|
|
307
|
+
) {
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
// The product test has to hold over the NUMERIC extents alone, not only when every extent is
|
|
311
|
+
// numeric. A partly-null rank still constrains `length`: `[null, 5]` says the member's
|
|
312
|
+
// elements come in rows of five, so a `length` of 48 contradicts it exactly as `[6, 9]` and 48
|
|
313
|
+
// do — and symbolFieldType, which recovers a missing outer extent as `length / prod(inner)`,
|
|
314
|
+
// would spell that pair `u8 grid[9.6][5];`. A non-integral quotient is not a spelling defect
|
|
315
|
+
// to patch downstream: it is the same three-facts-disagree malformation, declined here.
|
|
316
|
+
const numeric = (m.dims as (number | null)[]).filter((d): d is number => typeof d === 'number');
|
|
317
|
+
const stated = numeric.reduce((a, b) => a * b, 1);
|
|
318
|
+
if (m.length !== undefined && (numeric.length === m.dims.length ? stated !== m.length : m.length % stated !== 0)) {
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
231
322
|
// A bitfield's two facts must be present TOGETHER and internally consistent — a bitWidth with
|
|
232
323
|
// no bitOffset (or bits outside the byte span `size` claims) leaves the field unseatable, so
|
|
233
324
|
// the member is malformed and the layout declines whole like any other malformed member.
|
|
@@ -325,9 +416,29 @@ export function declaredFields(layout: SymbolStructField[] | undefined): Declare
|
|
|
325
416
|
export function symbolFieldType(f: DeclaredField): IrType {
|
|
326
417
|
if (isArrayField(f)) {
|
|
327
418
|
const scalarElem = f.elemSigned !== undefined && (f.elemSize === 1 || f.elemSize === 2 || f.elemSize === 4);
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
419
|
+
if (!(scalarElem && f.length !== undefined && f.elemSize! * f.length === f.size)) {
|
|
420
|
+
return T.array(T.u(8), f.size);
|
|
421
|
+
}
|
|
422
|
+
// The declared RANK, when the map states one: `u8 x[6][8]` rather than `u8 x[48]`. Same
|
|
423
|
+
// object and same size either way — but a foreign header declares the member ONE of those two
|
|
424
|
+
// ways, and an access spelled against the other does not type-check (see
|
|
425
|
+
// SymbolStructField.dims). An unspellable rank (null) keeps the flat member.
|
|
426
|
+
//
|
|
427
|
+
// The outer extent is RECOVERED as `length / prod(inner)` — the map may leave the outermost
|
|
428
|
+
// subrange null where it knows the total. wellFormedField already declines a `length` the
|
|
429
|
+
// inner extents do not divide, so the quotient is a whole number for any layout that reached
|
|
430
|
+
// here; it is re-checked rather than assumed because this function is exported and a
|
|
431
|
+
// fractional extent prints `u8 grid[9.6][5];`, which is not C. The flat member is the honest
|
|
432
|
+
// fallback, the same one an unspellable inner extent takes.
|
|
433
|
+
const inner = structFieldInnerExtents(f);
|
|
434
|
+
const elem = T.int(f.elemSize! * 8, f.elemSigned!);
|
|
435
|
+
if (inner === null || inner.length === 0) {
|
|
436
|
+
return T.array(elem, f.length);
|
|
437
|
+
}
|
|
438
|
+
const outer = f.length / inner.reduce((a, b) => a * b, 1);
|
|
439
|
+
return Number.isInteger(outer) && outer > 0
|
|
440
|
+
? [outer, ...inner].reverse().reduce<IrType>((t, n) => T.array(t, n), elem)
|
|
441
|
+
: T.array(elem, f.length);
|
|
331
442
|
}
|
|
332
443
|
if (f.pointer && f.size === 4) {
|
|
333
444
|
// The pointee width is byte-load-bearing: arithmetic on the loaded pointer scales by it, so
|
|
@@ -349,6 +460,32 @@ export function symbolFieldType(f: DeclaredField): IrType {
|
|
|
349
460
|
}
|
|
350
461
|
return T.array(T.u(8), f.size);
|
|
351
462
|
}
|
|
463
|
+
|
|
464
|
+
/** The array shape the DECLARATION actually spells for a member — the extents outermost first and
|
|
465
|
+
* the element type left at the bottom — read back out of {@link symbolFieldType} rather than
|
|
466
|
+
* re-derived from the map's own facts. It answers whatever that declaration is, which for a
|
|
467
|
+
* member with no spellable element type is the flat `u8 name[size]`: extents `[size]` over `u8`,
|
|
468
|
+
* not the empty shape.
|
|
469
|
+
*
|
|
470
|
+
* Exists because "how many subscripts does this member take, and of what" was being answered
|
|
471
|
+
* twice: once here (which needs `length` and a base-type `elemSigned`, and declares the flat byte
|
|
472
|
+
* array without either) and once straight off `dims` at the access site. `dims` and `length` are
|
|
473
|
+
* INDEPENDENT facts — a flexible array member states a stride and no bound (see
|
|
474
|
+
* SymbolStructField.length), and a member whose OUTERMOST subrange is unbounded (`u8 data[][8]`,
|
|
475
|
+
* legal C) reaches a map as `dims: [null, 8]` with no `length` at all. The declaration then spells
|
|
476
|
+
* `u8 grid[48];` while an access reading the rank off `dims` spells `gPtr->grid[0][a0];`, which is
|
|
477
|
+
* not C (`subscripted value is not an array, pointer, or vector`). An access side that asks THIS
|
|
478
|
+
* question cannot diverge from the declaration whatever gate symbolFieldType grows next. */
|
|
479
|
+
export function declaredArrayShape(f: DeclaredField): { extents: number[]; elem: IrType } {
|
|
480
|
+
let t = symbolFieldType(f);
|
|
481
|
+
const extents: number[] = [];
|
|
482
|
+
while (t.kind === 'array') {
|
|
483
|
+
extents.push(t.count);
|
|
484
|
+
t = t.elem;
|
|
485
|
+
}
|
|
486
|
+
return { extents, elem: t };
|
|
487
|
+
}
|
|
488
|
+
|
|
352
489
|
/** A 4-byte member/scalar with NO base-type signedness is the enum idiom — C89 says int. */
|
|
353
490
|
export const ENUM_IS_SIGNED = true;
|
|
354
491
|
|
|
@@ -539,3 +676,48 @@ export function symbolMapFromJson(obj: Record<string, SymbolInfo[]>): SymbolMap
|
|
|
539
676
|
}
|
|
540
677
|
return map;
|
|
541
678
|
}
|
|
679
|
+
|
|
680
|
+
const HEX_KEY = /^(0x)?[0-9a-fA-F]+$/;
|
|
681
|
+
|
|
682
|
+
/** STRUCTURAL VALIDATION of the vendored-map JSON, in core because BOTH readers of that format
|
|
683
|
+
* need it and a second hand-rolled copy is how they come to disagree about what a map is: the
|
|
684
|
+
* webapp's Symbols pane (which degrades — a bad map is reported and the run proceeds without
|
|
685
|
+
* one) and the cli's `tools.asmlift.symbols` (which is LOUD — an explicit config that failed to
|
|
686
|
+
* load must never read like a row that never had a map).
|
|
687
|
+
*
|
|
688
|
+
* `symbolMapFromJson` alone cannot serve either: it is a total function over `Object.entries`,
|
|
689
|
+
* so `[]` and `{}` and `{"nope": []}` all yield an EMPTY map and no error at all. An empty map
|
|
690
|
+
* is exactly what a map-less run holds, and the two produce different source — so the shapes
|
|
691
|
+
* that silently reduce to it are the ones this checks. Only the load-bearing minimum is
|
|
692
|
+
* validated (hex keys, non-empty arrays, string `name`, `kind` ∈ code|data); the declaration
|
|
693
|
+
* SHAPE fields are typed by this module and fail soft downstream.
|
|
694
|
+
*
|
|
695
|
+
* Emptiness is REPORTED, not judged: a caller that tolerates an empty map reads `map.size`
|
|
696
|
+
* itself. */
|
|
697
|
+
export function parseSymbolMapJson(obj: unknown): { map: SymbolMap } | { error: string } {
|
|
698
|
+
if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
|
|
699
|
+
return {
|
|
700
|
+
error: 'expected an object of hex addresses, e.g. {"0x03001234": [{"name": "gCounter", "kind": "data"}]}',
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
for (const [key, infos] of Object.entries(obj)) {
|
|
704
|
+
if (!HEX_KEY.test(key)) {
|
|
705
|
+
return { error: `"${key}" is not a hex address key (e.g. "0x03001234")` };
|
|
706
|
+
}
|
|
707
|
+
if (!Array.isArray(infos) || infos.length === 0) {
|
|
708
|
+
return { error: `"${key}" must map to a non-empty array of symbols` };
|
|
709
|
+
}
|
|
710
|
+
for (const info of infos as unknown[]) {
|
|
711
|
+
const si = info as Partial<SymbolInfo> | null;
|
|
712
|
+
if (
|
|
713
|
+
typeof si !== 'object' ||
|
|
714
|
+
si === null ||
|
|
715
|
+
typeof si.name !== 'string' ||
|
|
716
|
+
(si.kind !== 'code' && si.kind !== 'data')
|
|
717
|
+
) {
|
|
718
|
+
return { error: `"${key}": every symbol needs a string "name" and "kind": "code" | "data"` };
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
return { map: symbolMapFromJson(obj as Record<string, SymbolInfo[]>) };
|
|
723
|
+
}
|