@asmlift/core 0.6.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 +2 -2
- package/package.json +1 -1
- package/src/backend/cfamily.ts +39 -11
- package/src/contracts.ts +60 -11
- package/src/frontend/ssa.ts +1 -1
- package/src/frontend/thumb.ts +2 -2
- package/src/ir/alias.ts +24 -0
- package/src/ir/core.ts +8 -0
- package/src/ir/opcodes.ts +43 -7
- package/src/ir/simplify.ts +1 -1
- package/src/l3/address.ts +2 -2
- package/src/l3/advance.ts +373 -0
- package/src/l3/argbase.ts +4 -4
- package/src/l3/ast.ts +65 -21
- package/src/l3/basecse.ts +48 -28
- package/src/l3/coalesce.ts +9 -9
- package/src/l3/gates.ts +75 -1
- package/src/l3/hoist.ts +1 -1
- package/src/l3/homesplit.ts +13 -13
- package/src/l3/initfirst.ts +3 -3
- package/src/l3/inlinebase.ts +16 -16
- package/src/l3/mentions.ts +68 -5
- package/src/l3/mulfirst.ts +3 -3
- package/src/l3/nearbase.ts +4 -4
- package/src/l3/offmember.ts +5 -5
- package/src/l3/parkfirst.ts +6 -6
- package/src/l3/pollguard.ts +3 -3
- package/src/l3/ptrfield.ts +4 -4
- package/src/l3/regspell.ts +8 -8
- package/src/l3/reindex.ts +22 -17
- package/src/l3/scopebase.ts +28 -25
- package/src/l3/sinkinit.ts +7 -7
- package/src/l3/slotorder.ts +3 -3
- package/src/l3/storage.ts +1 -1
- package/src/l3/tailmerge.ts +2 -2
- package/src/l3/typing.ts +3 -3
- package/src/l3/unmerge.ts +483 -59
- package/src/l3/unreduce.ts +13 -13
- package/src/l3/volatileptr.ts +11 -11
- package/src/l3/volatileval.ts +11 -11
- package/src/l3/volstore.ts +16 -16
- package/src/l3/zerosub.ts +6 -6
- package/src/pattern/engine.ts +4 -4
- package/src/pipeline.ts +17 -5
- package/src/proto.ts +2 -2
- package/src/raise/const.ts +203 -3
- package/src/raise/divpow2.ts +2 -2
- package/src/raise/extscale.ts +342 -0
- package/src/raise/globalshape.ts +32 -12
- package/src/raise/gvn.ts +2 -2
- package/src/raise/magicdiv.ts +2 -2
- package/src/raise/memberarrays.ts +4 -4
- package/src/raise/narrowlocal.ts +18 -2
- package/src/raise/paramwidth.ts +24 -2
- package/src/raise/pre-recovery.ts +90 -25
- package/src/raise/retsink.ts +381 -15
- package/src/raise/shortcircuit.ts +595 -34
- package/src/raise/structs.ts +4 -4
- package/src/raise/tailsink.ts +126 -0
- package/src/rank-declare.ts +4 -4
- package/src/{rank-axes.ts → rank-variations.ts} +319 -189
- package/src/rank.ts +1148 -803
- package/src/structure/analysis.ts +87 -90
- package/src/structure/bitfields.ts +130 -30
- package/src/structure/globalaccess.ts +30 -4
- package/src/structure/namecoalesce.ts +32 -13
- package/src/structure/structure.ts +1415 -200
- package/src/structure/switch-recover.ts +100 -7
- package/src/symbols.ts +127 -6
- package/src/target.ts +155 -35
- package/src/trace.ts +1 -1
- package/src/variation-definitions.ts +1540 -0
- package/src/variation-gates.ts +89 -0
- package/src/variation-tokens.ts +355 -0
|
@@ -23,6 +23,12 @@ export interface SwitchRecoverDeps {
|
|
|
23
23
|
switchAllowsBoundCase: boolean;
|
|
24
24
|
/** emit the case arms in the ASSEMBLY's block-layout order rather than by ascending case value */
|
|
25
25
|
switchArmsFollowLayout: boolean;
|
|
26
|
+
/** DECLINE a recovered tree whose own layout INTERLEAVES a test with a case body. A source
|
|
27
|
+
* `switch` front-loads its whole dispatch ahead of every arm body; an if/else-if LADDER emits
|
|
28
|
+
* each test directly above its own body. See StructureOptions for the compiled evidence, for
|
|
29
|
+
* the frontend premise this shares with `switchArmsFollowLayout`, and for what the decline
|
|
30
|
+
* costs when it is wrong. */
|
|
31
|
+
switchRequiresFrontLoadedTests: boolean;
|
|
26
32
|
/** may the emitted SOURCE say "this arm runs on into the next one"? False for a language whose
|
|
27
33
|
* `case` cannot fall through (Pascal), and then Regime A declines a falling arm to if-recovery
|
|
28
34
|
* — the behaviourally identical recovery that backend CAN print. See StructureOptions. */
|
|
@@ -247,6 +253,7 @@ export function makeSwitchRecovery(deps: SwitchRecoverDeps): SwitchRecovery {
|
|
|
247
253
|
switchAllowsNeqCase,
|
|
248
254
|
switchAllowsBoundCase,
|
|
249
255
|
switchArmsFollowLayout,
|
|
256
|
+
switchRequiresFrontLoadedTests,
|
|
250
257
|
spellSwitchFallthrough,
|
|
251
258
|
emitsOwnStatement,
|
|
252
259
|
blockOf,
|
|
@@ -255,17 +262,25 @@ export function makeSwitchRecovery(deps: SwitchRecoverDeps): SwitchRecovery {
|
|
|
255
262
|
structureRegion,
|
|
256
263
|
} = deps;
|
|
257
264
|
|
|
258
|
-
// A block's index in `fn.blocks` as its position in the ASSEMBLY — the
|
|
259
|
-
//
|
|
265
|
+
// A block's index in `fn.blocks` as its position in the ASSEMBLY — the warrant for every reading
|
|
266
|
+
// of layout below, and true PER FRONTEND rather than of the IR:
|
|
260
267
|
// - thumb.ts and mips.ts build the list by scanning the instruction stream in address order;
|
|
261
268
|
// - ppc.ts does not. It APPENDS a synthetic return block (`synthReturn`) at the end of the list
|
|
262
269
|
// for every conditional-return branch, wherever in the stream that branch sits, so its list
|
|
263
270
|
// is not address order at all;
|
|
264
271
|
// - raising only ever REMOVES blocks from the list (raise/{divpow2,latch,retsink,shortcircuit}
|
|
265
272
|
// .ts all `filter`), never inserts or reorders, so the frontend's order is what survives.
|
|
266
|
-
//
|
|
267
|
-
//
|
|
268
|
-
//
|
|
273
|
+
// TWO READERS, both target-gated, and they need DIFFERENT strengths of the compiler half:
|
|
274
|
+
// - `switchArmsFollowLayout` (arm ORDER, the sort below) needs the full claim — no block moved
|
|
275
|
+
// at all — because it PLACES the arms from the layout;
|
|
276
|
+
// - `switchRequiresFrontLoadedTests` (PRE5, whether to recover at all) needs only that no case
|
|
277
|
+
// BODY was moved above a dispatch test, which a compiler with a scheduler can still satisfy;
|
|
278
|
+
// `MIPS_GCC` declares `switchRequiresFrontLoadedTests` and NOT `switchArmsFollowLayout` for
|
|
279
|
+
// exactly that reason — it has a scheduler and fills delay slots (target.ts).
|
|
280
|
+
// Both are therefore claims about a target's FRONTEND as much as about its compiler, and a target
|
|
281
|
+
// opts in on both halves — which is why PPC_MWCC, whose frontend fails the frontend half outright,
|
|
282
|
+
// declares neither. Anything added below that reads `layoutIndex` inherits the frontend half and
|
|
283
|
+
// owes a statement of which strength of the compiler half it needs.
|
|
269
284
|
const blockIndex = new Map(fn.blocks.map((blk, i) => [blk, i] as const));
|
|
270
285
|
const layoutIndex = (blk: Block): number => blockIndex.get(blk) ?? -1;
|
|
271
286
|
|
|
@@ -336,9 +351,10 @@ export function makeSwitchRecovery(deps: SwitchRecoverDeps): SwitchRecovery {
|
|
|
336
351
|
};
|
|
337
352
|
|
|
338
353
|
// --- Regime A: comparison-tree switch recovery ----------------------------------------------------
|
|
339
|
-
// Every ambiguity declines.
|
|
354
|
+
// Every ambiguity declines. Five preconditions are enforced below, annotated PRE1..PRE5:
|
|
340
355
|
// scrutinee identity/dominance, ARM EXITS (per site: `break`, `fallthrough` — which `chainArms`
|
|
341
|
-
// then places — or a decline), concrete interval consistency, test purity
|
|
356
|
+
// then places — or a decline), concrete interval consistency, test purity, and — where the target
|
|
357
|
+
// has declared that its layout answers it — which SPELLING the source wrote.
|
|
342
358
|
|
|
343
359
|
// Fold a value that is a compile-time constant (a `const`, or a synthesized immediate like agbcc's
|
|
344
360
|
// `250 << 2` for a large sparse case) to a number — else null.
|
|
@@ -772,6 +788,83 @@ export function makeSwitchRecovery(deps: SwitchRecoverDeps): SwitchRecovery {
|
|
|
772
788
|
}
|
|
773
789
|
}
|
|
774
790
|
|
|
791
|
+
// PRE5 (WHICH SPELLING THE SOURCE WROTE), per SITE, and only where the caller has declared
|
|
792
|
+
// that the layout answers it. Everything above establishes that this tree CAN be spelled as a
|
|
793
|
+
// `switch`; this asks whether it WAS. The two spellings are different objects, not one object
|
|
794
|
+
// two ways: a source `switch` front-loads its whole dispatch ahead of every arm body (agbcc's
|
|
795
|
+
// `expand_end_case` closes with a `reorder_insns` that moves the dispatch in front of the
|
|
796
|
+
// bodies expanded before it — the same sources `switchArmsFollowLayout` is read off), while an
|
|
797
|
+
// if/else-if ladder emits each test directly above the body it guards. So a test block laid
|
|
798
|
+
// out AFTER a case body cannot have come from a source `switch`, and recovering one here spells
|
|
799
|
+
// a ladder as a `switch` with no dual anywhere in the fan for a differ to prefer.
|
|
800
|
+
//
|
|
801
|
+
// READ PER SITE, off THIS recovery's own blocks, never off the function. A function holding a
|
|
802
|
+
// ladder and a real `switch` must keep both readings, and a function-wide OR would collapse
|
|
803
|
+
// them into one boolean and be wrong on one site by construction.
|
|
804
|
+
//
|
|
805
|
+
// WHAT IT REFUSES TO DO. The reading runs BACKWARDS, from emission to spelling, so it inherits
|
|
806
|
+
// `layoutIndex`'s premise (above) at the weaker of the two strengths stated there. A frontend
|
|
807
|
+
// or compiler that breaks it can hand this a front-loaded layout for a ladder, and then the
|
|
808
|
+
// gate simply does not fire. The converse error, a real `switch` whose layout interleaves,
|
|
809
|
+
// costs only the `switch` SPELLING: the recovery declines to if-recovery, behaviourally
|
|
810
|
+
// identical (the file header) and scored by the differ. This gate can lose a match; it can
|
|
811
|
+
// never produce a wrong answer.
|
|
812
|
+
//
|
|
813
|
+
// WHAT THE DECLINE PRODUCES, AND IT IS NOT ALWAYS A CLEAN LADDER. Recovery is RECURSIVE:
|
|
814
|
+
// declining a tree here hands the tree back to if-recovery, which then runs recovery AGAIN on
|
|
815
|
+
// each sub-tree it structures. On a flat tree that is a ladder over every case value. On a
|
|
816
|
+
// NESTED tree it is not: `corpus/agbcc-swnested.s` (a source `switch` nested inside an
|
|
817
|
+
// if/else-if ladder) declines HERE on the outer tree — correctly, the ladder arm's body really
|
|
818
|
+
// does sit between the tests — and the sub-tree then re-recovers, so the emitted C is an `if`
|
|
819
|
+
// nest holding a `switch` over a STRICT SUBSET of the source's case labels. Still behaviourally
|
|
820
|
+
// identical, and no worse than the alternative (with the reading withdrawn that same function
|
|
821
|
+
// comes back as one `switch` merging two arms the source wrote apart), but it is a THIRD
|
|
822
|
+
// spelling that neither reading would give, and nothing in the output says a dispatch was
|
|
823
|
+
// half-declined: "the decline is a clean ladder" holds at the OUTERMOST tree only. Pinned by
|
|
824
|
+
// the test "PRE5 declines a tree RECURSIVELY" on the committed fixture.
|
|
825
|
+
//
|
|
826
|
+
// WHAT THE POSITIONS ARE READ THROUGH, which is the premise that lives inside asmlift rather
|
|
827
|
+
// than in the compiler. `caseBlocks` holds `forwardingTarget` results (`asCase` is called on the
|
|
828
|
+
// resolved block), so an arm whose body is a bare jump contributes ITS TARGET's position, not
|
|
829
|
+
// its own. A forwarding target laid out ABOVE the dispatch would then read as a body above a
|
|
830
|
+
// test and decline a real `switch`.
|
|
831
|
+
//
|
|
832
|
+
// WHY THAT HAS NO INHABITANT IS MEASURED, NOT ARGUED. Two arguments that look like proofs are
|
|
833
|
+
// not: "the block is reached by a back edge" needs the block to DOMINATE the dispatch, which an
|
|
834
|
+
// `if` arm ahead of the `switch` does not, and a cross-jumped arm tail reaches it on an
|
|
835
|
+
// ordinary forward edge; "this flag's targets reorder nothing" is false — `MIPS_GCC` declares
|
|
836
|
+
// it WITH a scheduler, on the half-premise stated at `layoutIndex` above. Measured: across 28
|
|
837
|
+
// compiled probes on agbcc, gcc2.7.2 -O1 and gcc2.7.2kmc -O2 — arms cross-jumped onto a shared
|
|
838
|
+
// tail ahead of the dispatch, `goto`s out of an arm to a label above it, shared early returns,
|
|
839
|
+
// `continue`/`break` out of a switch inside a loop — no toolchain placed an arm's forwarding
|
|
840
|
+
// target above its own dispatch: every firing came back with zero bodies above the first test.
|
|
841
|
+
// Safe because no compiler in the corpus emits that shape, not because none can; whoever makes
|
|
842
|
+
// one — a new frontend, a relaxed loop recovery, a scheduler that hoists a merged tail —
|
|
843
|
+
// inherits this reader.
|
|
844
|
+
//
|
|
845
|
+
// `bodyPos` covers the CASE bodies only. `defaultBlk` is left out deliberately: it can be the
|
|
846
|
+
// dispatch's own fall-out block rather than an arm the source wrote (the W2/W4 withholdings
|
|
847
|
+
// above), so its position is one the source never chose. Both omissions can only make the gate
|
|
848
|
+
// UNDER-fire, i.e. keep a `switch`, which is the safe direction.
|
|
849
|
+
//
|
|
850
|
+
// AND THE READING REFUSES ITSELF where it has nothing to read. `layoutIndex` answers `-1` for a
|
|
851
|
+
// block absent from `fn.blocks`, which is not a position — taken as one it sorts below every
|
|
852
|
+
// real body and declines the tree holding it. That state has no inhabitant and cannot get one
|
|
853
|
+
// without a bug upstream: `ir/verify.ts` rejects a successor that is not a block of the fn and
|
|
854
|
+
// the tower runs it after the lift and after every raising pass, and `predecessorBlocks` throws
|
|
855
|
+
// by name on the same state near the top of `structure()`. So this is belt-and-braces over a
|
|
856
|
+
// VERIFIER BUG, not over an expected shape — kept because standing down costs nothing. Its test
|
|
857
|
+
// pins the UNREACHABILITY (both throws fire first); the `placed` branch itself stays
|
|
858
|
+
// unexecuted, and cannot be covered without breaking one of those two invariants.
|
|
859
|
+
if (switchRequiresFrontLoadedTests) {
|
|
860
|
+
const testPos = [...seen].map(layoutIndex);
|
|
861
|
+
const bodyPos = [...caseBlocks].map(layoutIndex);
|
|
862
|
+
const placed = [...testPos, ...bodyPos].every((i) => i >= 0);
|
|
863
|
+
if (placed && Math.max(...testPos) > Math.min(...bodyPos)) {
|
|
864
|
+
return null;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
|
|
775
868
|
// ARM ORDER. The case values are disjoint (PRE3), so where no arm falls through the order
|
|
776
869
|
// carries no meaning and is pure matching evidence: ascending case VALUE is the neutral
|
|
777
870
|
// spelling, and where a compiler has declared `switchArmsFollowLayout` the layout of the bodies
|
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). */
|
|
@@ -95,7 +116,7 @@ export interface SymbolPointee {
|
|
|
95
116
|
* NO POINTEE, and the absence is UPSTREAM's rather than a shape asmlift dropped:
|
|
96
117
|
* `@gba-kit/debug-info`'s `TypeFacts` — what a `FunctionSignature`'s params are made of — is
|
|
97
118
|
* exactly these three fields. {@link SymbolInfo.pointee} exists only for a symbol AT AN ADDRESS.
|
|
98
|
-
* Widening this is priced in docs/level-tower.md and pinned in test/param-pointee-
|
|
119
|
+
* Widening this is priced in docs/level-tower.md and pinned in test/param-pointee-variation.test.ts. */
|
|
99
120
|
export interface SymbolTypeFacts {
|
|
100
121
|
size: number | null;
|
|
101
122
|
signed: boolean | null;
|
|
@@ -197,6 +218,28 @@ export function arrayInnerExtents(info: SymbolInfo): number[] | null {
|
|
|
197
218
|
return inner.every((d) => typeof d === 'number' && d > 0) ? (inner as number[]) : null;
|
|
198
219
|
}
|
|
199
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
|
+
|
|
200
243
|
/** address → symbols at that address; `[0]` is the provider's canonical pick. */
|
|
201
244
|
export type SymbolMap = Map<number, SymbolInfo[]>;
|
|
202
245
|
|
|
@@ -225,6 +268,13 @@ export function isBitfieldField(f: SymbolStructField): boolean {
|
|
|
225
268
|
return f.bitWidth !== undefined;
|
|
226
269
|
}
|
|
227
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
|
+
|
|
228
278
|
/** A layout member that {@link declaredFields} passed: sizable, and seated at an offset no
|
|
229
279
|
* earlier member already covers. */
|
|
230
280
|
export type DeclaredField = SymbolStructField & { size: number };
|
|
@@ -244,6 +294,31 @@ function wellFormedField(f: unknown): f is SymbolStructField {
|
|
|
244
294
|
) {
|
|
245
295
|
return false;
|
|
246
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
|
+
}
|
|
247
322
|
// A bitfield's two facts must be present TOGETHER and internally consistent — a bitWidth with
|
|
248
323
|
// no bitOffset (or bits outside the byte span `size` claims) leaves the field unseatable, so
|
|
249
324
|
// the member is malformed and the layout declines whole like any other malformed member.
|
|
@@ -341,9 +416,29 @@ export function declaredFields(layout: SymbolStructField[] | undefined): Declare
|
|
|
341
416
|
export function symbolFieldType(f: DeclaredField): IrType {
|
|
342
417
|
if (isArrayField(f)) {
|
|
343
418
|
const scalarElem = f.elemSigned !== undefined && (f.elemSize === 1 || f.elemSize === 2 || f.elemSize === 4);
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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);
|
|
347
442
|
}
|
|
348
443
|
if (f.pointer && f.size === 4) {
|
|
349
444
|
// The pointee width is byte-load-bearing: arithmetic on the loaded pointer scales by it, so
|
|
@@ -365,6 +460,32 @@ export function symbolFieldType(f: DeclaredField): IrType {
|
|
|
365
460
|
}
|
|
366
461
|
return T.array(T.u(8), f.size);
|
|
367
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
|
+
|
|
368
489
|
/** A 4-byte member/scalar with NO base-type signedness is the enum idiom — C89 says int. */
|
|
369
490
|
export const ENUM_IS_SIGNED = true;
|
|
370
491
|
|