@asmlift/core 0.10.0 → 0.11.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/package.json +1 -1
- package/src/backend/c.ts +1 -0
- package/src/backend/cfamily.ts +33 -12
- package/src/backend/cpp.ts +1 -0
- package/src/backend/lines.ts +10 -0
- package/src/backend/pascal.ts +17 -5
- package/src/cdecl/parse.ts +21 -0
- package/src/contracts.ts +73 -20
- package/src/frontend/arm-body.ts +155 -0
- package/src/frontend/asmdata.ts +2 -1
- package/src/frontend/call-plan.ts +197 -45
- package/src/frontend/callee-body.ts +370 -0
- package/src/frontend/device-pins.ts +18 -8
- package/src/frontend/frame-objects.ts +504 -61
- package/src/frontend/frontend.ts +20 -0
- package/src/frontend/jump-table.ts +35 -20
- package/src/frontend/mips.ts +226 -8
- package/src/frontend/ppc.ts +26 -2
- package/src/frontend/register-struct-call.ts +1 -86
- package/src/frontend/ssa.ts +263 -1
- package/src/frontend/stackargs.ts +95 -17
- package/src/frontend/thumb-entry.ts +340 -0
- package/src/frontend/thumb.ts +410 -61
- package/src/ir/alias.ts +29 -8
- package/src/ir/bits.ts +1081 -66
- package/src/ir/core.ts +154 -4
- package/src/ir/entry.ts +3 -5
- package/src/ir/opcodes.ts +28 -0
- package/src/ir/parse.ts +1 -0
- package/src/ir/simplify.ts +61 -10
- package/src/ir/verify.ts +16 -0
- package/src/l3/argbase.ts +2 -0
- package/src/l3/ast.ts +294 -2
- package/src/l3/basecse.ts +35 -10
- package/src/l3/caseendgoto.ts +223 -0
- package/src/l3/coalesce.ts +42 -14
- package/src/l3/constinit.ts +34 -6
- package/src/l3/dce.ts +52 -8
- package/src/l3/ptrcell.ts +2 -0
- package/src/l3/regspell.ts +2 -0
- package/src/l3/reindex.ts +13 -4
- package/src/l3/scopebase.ts +2 -0
- package/src/l3/tailmerge.ts +2 -0
- package/src/pipeline.ts +507 -23
- package/src/proto-context.ts +135 -1
- package/src/raise/const.ts +25 -0
- package/src/raise/declared-volatile.ts +1 -1
- package/src/raise/divpow2.ts +192 -0
- package/src/raise/offsetnames.ts +412 -10
- package/src/raise/pre-recovery.ts +8 -5
- package/src/raise/pre-structure.ts +24 -5
- package/src/raise/reroll.ts +4 -24
- package/src/raise/shortcircuit.ts +192 -23
- package/src/raise/unpre.ts +594 -0
- package/src/rank-variations.ts +97 -4
- package/src/rank.ts +258 -43
- package/src/structure/analysis.ts +56 -10
- package/src/structure/cross-jumped-tail.ts +216 -0
- package/src/structure/globalaccess.ts +46 -1
- package/src/structure/structure.ts +4221 -368
- package/src/structure/switch-recover.ts +345 -26
- package/src/structure/unfolded-branch-constant.ts +90 -0
- package/src/symbols.ts +52 -15
- package/src/target.ts +269 -8
- package/src/trace.ts +28 -14
- package/src/variation-definitions.ts +207 -6
- package/src/variation-gates.ts +2 -0
- package/src/variation-tokens.ts +63 -2
package/src/symbols.ts
CHANGED
|
@@ -560,19 +560,48 @@ export function pointeeFields(pointee: SymbolPointee | undefined): DeclaredField
|
|
|
560
560
|
return declaredFields(pointee.layout);
|
|
561
561
|
}
|
|
562
562
|
|
|
563
|
-
/**
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
563
|
+
/** A name C can spell. Splat's `func_X.NON_MATCHING` labels are sized data aliases that are not. */
|
|
564
|
+
const C_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
565
|
+
|
|
566
|
+
/** THE one object an address names, decided once per address so every lookup that lands there
|
|
567
|
+
* spells the same name. Two names for one object inside one function are not a style problem:
|
|
568
|
+
* the compiler treats distinct symbols as non-aliasing and forwards a store made through one past
|
|
569
|
+
* a store made through the other.
|
|
570
|
+
*
|
|
571
|
+
* Usually the canonical `[0]`. The exception is an unsized data label ranked first over the sized
|
|
572
|
+
* object that starts at the same address (sa3's `gUnknown_08358528` over the 540-byte
|
|
573
|
+
* `Map_zone_1_act_hub_fg0`, pokeemerald's `gMultiBootProgram_BerryGlitchFix_End` over
|
|
574
|
+
* `gMultiBootProgram_PokemonColosseum_Start`): the object is the sized one, because only it has
|
|
575
|
+
* an extent, so both an exact hit and an interior hit name it. Never where any alias is code: a
|
|
576
|
+
* sized data alias there is a label over the function's bytes (MIPS `func_X.NON_MATCHING`, which
|
|
577
|
+
* splat maps rank behind an unsized `gcc2_compiled.` marker), not an object. */
|
|
578
|
+
function objectAt(infos: readonly SymbolInfo[]): SymbolInfo {
|
|
579
|
+
const head = infos[0];
|
|
580
|
+
if (head.kind !== 'data' || head.size !== undefined || infos.some((i) => i.kind === 'code')) {
|
|
581
|
+
return head;
|
|
582
|
+
}
|
|
583
|
+
return infos.find((i) => i.kind === 'data' && i.size !== undefined && C_IDENTIFIER.test(i.name)) ?? head;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/** Kind-aware two-probe lookup for a pool-loaded 32-bit value. Exact match first (any kind), which
|
|
587
|
+
* names {@link objectAt}'s object; on miss, `value & ~1` — accepted ONLY when the hit is code,
|
|
588
|
+
* because ELF function addresses are stored with the Thumb bit cleared while a Thumb code pointer
|
|
589
|
+
* in a pool is odd. An exact odd-DATA hit therefore wins over a masked code hit (odd data
|
|
590
|
+
* addresses are real).
|
|
591
|
+
*
|
|
592
|
+
* The masked probe asks for the CODE alias at that address, not for the canonical `[0]`: a
|
|
593
|
+
* table-end or segment marker can share its address with the function that follows it, and the
|
|
594
|
+
* provider may rank the marker first (pokeemerald's `gMysteryEventScriptCmdTableEnd` over
|
|
595
|
+
* `GameCubeMultiBoot_Hash`). An odd pool word there is still the function's Thumb pointer. */
|
|
567
596
|
export function lookupSymbol(map: SymbolMap, value: number): SymbolInfo | null {
|
|
568
|
-
const exact = map.get(value)
|
|
569
|
-
if (exact) {
|
|
570
|
-
return exact;
|
|
597
|
+
const exact = map.get(value);
|
|
598
|
+
if (exact !== undefined && exact.length > 0) {
|
|
599
|
+
return objectAt(exact);
|
|
571
600
|
}
|
|
572
601
|
if ((value & 1) === 1) {
|
|
573
|
-
const
|
|
574
|
-
if (
|
|
575
|
-
return
|
|
602
|
+
const code = map.get(value & ~1)?.find((info) => info.kind === 'code');
|
|
603
|
+
if (code) {
|
|
604
|
+
return code;
|
|
576
605
|
}
|
|
577
606
|
}
|
|
578
607
|
return null;
|
|
@@ -580,10 +609,11 @@ export function lookupSymbol(map: SymbolMap, value: number): SymbolInfo | null {
|
|
|
580
609
|
|
|
581
610
|
/** Interior attribution: the data symbol whose `[address, address+size)` range contains
|
|
582
611
|
* `value` strictly inside (offset > 0 — exact bases go through `lookupSymbol`). Only
|
|
583
|
-
* complete-typed globals carry a size, so unsized arrays never attribute.
|
|
612
|
+
* complete-typed globals carry a size, so unsized arrays never attribute. The symbol at each
|
|
613
|
+
* base is {@link objectAt}'s, the same one an exact hit on that base names. */
|
|
584
614
|
export function lookupInterior(map: SymbolMap, value: number): { info: SymbolInfo; offset: number } | null {
|
|
585
615
|
for (const [addr, infos] of map) {
|
|
586
|
-
const info = infos
|
|
616
|
+
const info = objectAt(infos);
|
|
587
617
|
if (info.kind !== 'data' || info.size === undefined) {
|
|
588
618
|
continue;
|
|
589
619
|
}
|
|
@@ -596,9 +626,16 @@ export function lookupInterior(map: SymbolMap, value: number): { info: SymbolInf
|
|
|
596
626
|
|
|
597
627
|
/** The `SymbolInfo` keys a project's DWARF can only carry because the symbol's DEFINITION was
|
|
598
628
|
* compiled from C. Everything else in the map survives a function that is still `INCLUDE_ASM`:
|
|
599
|
-
* its `.symtab` entry exists (the asm defines the label), and
|
|
600
|
-
*
|
|
601
|
-
*
|
|
629
|
+
* its `.symtab` entry exists (the asm defines the label), and most globals it reads are typed by
|
|
630
|
+
* other code that references them. Listed here, once, so {@link asIfUndecompiled} and any later
|
|
631
|
+
* definition-derived fact (a signature, a local's type, a register location) stay in sync.
|
|
632
|
+
*
|
|
633
|
+
* KNOWN GAP: "other code" is not guaranteed. agbcc writes a DWARF entry for an extern global only
|
|
634
|
+
* in a compilation unit that references it, so a global defined in asm (a NOTYPE symbol) that only
|
|
635
|
+
* the row's own body references takes its type from the row's decompiled C. Nothing here strips
|
|
636
|
+
* that type, because the map does not record which compilation unit typed a symbol. kleod has 10
|
|
637
|
+
* such globals across 6 rows, among them PauseMenuScreenHandler's `gUnk_081166F8[4][4]`. The
|
|
638
|
+
* CLI's repro reads the same ELF and sees the same types. */
|
|
602
639
|
const DEFINITION_DERIVED_KEYS = ['declared', 'signature'] as const satisfies readonly (keyof SymbolInfo)[];
|
|
603
640
|
|
|
604
641
|
/** The map a user actually has while decompiling `fn` — i.e. with `fn` still an `INCLUDE_ASM`
|
package/src/target.ts
CHANGED
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
// What each datum drives:
|
|
6
6
|
// • id → frontend dispatch (registry.ts)
|
|
7
7
|
// • compiler → idiom gating (patternApplies) + the report
|
|
8
|
-
// • argRegs / returnReg → entry-param ordering and return-value read in the frontends
|
|
8
|
+
// • argRegs / returnReg → entry-param ordering and return-value read in the frontends; argRegs
|
|
9
|
+
// also says whether an ARM body's argument scan counts this target's words
|
|
10
|
+
// (frontend/arm-body.ts `scansArgumentsOf`, read by frontend/callee-body.ts)
|
|
9
11
|
// • capabilities.hwDivide → gates the MIPS hardware-divide decode (mips.ts), the soft-division
|
|
10
12
|
// pre-recovery pass, and idiom gating; a `div` on a target declaring no divider degrades to
|
|
11
13
|
// a loud opaque (exercised by packages/cli/test/matching/divmul.test.ts). `hwFloat` → idiom
|
|
@@ -45,7 +47,7 @@
|
|
|
45
47
|
// `aggregateReturn`, `largestAlignment`, `enumBytes`, `bitfieldPacking` and
|
|
46
48
|
// `byValueAggregateArgWords` (aggregate.ts, for frontend/call-plan.ts — which the Thumb, MIPS
|
|
47
49
|
// and PowerPC lowerings share — frontend/thumb.ts, frontend/mips.ts, frontend/ppc.ts and
|
|
48
|
-
// frontend/frame-objects.ts),
|
|
50
|
+
// frontend/frame-objects.ts; `enumBytes` is the structurer's too, through StructureOptions),
|
|
49
51
|
// `hoistsSingleSetArm` (raise/narrowlocal.ts and raise/retsink.ts), `arrayShapeFromStride`
|
|
50
52
|
// (raise/globalshape.ts, run on the LIFTED fn), `eightByteReturnScratch`
|
|
51
53
|
// (frontend/thumb.ts, which reads the epilogue), `callThunks` (frontend/thumb.ts, which
|
|
@@ -701,7 +703,7 @@ export interface TargetDescription {
|
|
|
701
703
|
oneWordFrameIsTheCapturedObject?: boolean;
|
|
702
704
|
// A frame address this function hands out — in an argument register at a call, or stored to
|
|
703
705
|
// memory — is the address of a LOCAL, so its receiver may read the frame from there up and no
|
|
704
|
-
// word from there up is a call's outgoing argument. Its
|
|
706
|
+
// word from there up is a call's outgoing argument. Its first reader is the outgoing-argument
|
|
705
707
|
// analysis (`frontend/stackargs.ts`, the `escape` event): a store never reloaded HERE is read
|
|
706
708
|
// THERE. Absent ⇒ a handed-out frame address explains nothing.
|
|
707
709
|
//
|
|
@@ -719,8 +721,30 @@ export interface TargetDescription {
|
|
|
719
721
|
// flow-insensitively, it would set aside `qh5`'s fifth `h5` word and lift `h5(1, 2, 3, 4)`. The
|
|
720
722
|
// cost is false alarms that decline loud (`if (x) gt(&s); else h(y);`); where the analysis also
|
|
721
723
|
// reads the event whole-function, it only refuses or excuses a word no call takes
|
|
722
|
-
// (stackargs.ts). `oneWordFrameIsTheCapturedObject` is the flow-insensitive licence
|
|
723
|
-
// where no block copy fits.
|
|
724
|
+
// (stackargs.ts). `oneWordFrameIsTheCapturedObject` is the flow-insensitive licence for any
|
|
725
|
+
// receiver, sound only where no block copy fits.
|
|
726
|
+
//
|
|
727
|
+
// Its SECOND reader is the Thumb frame model's [sp,#0] capture (`frontend/thumb.ts`, beside
|
|
728
|
+
// `noCallTakesAStackWord`), which reads the universal whole-function at offset 0: where the
|
|
729
|
+
// frame base is handed as the hidden pointer of a callee DECLARED to return a struct through
|
|
730
|
+
// memory, it is a local's address, so no word of the frame is any call's outgoing argument and
|
|
731
|
+
// the area is empty — whatever the other calls are (sret2knr: `u8 rnd();` beside two temps).
|
|
732
|
+
// This premise is read only where some call may take a stack word: where none does there is
|
|
733
|
+
// no outgoing area to confuse, and an escaping frame base is a local's address on any target,
|
|
734
|
+
// whoever receives it (a DMA source published to the device's registers, `dmafill_wh`).
|
|
735
|
+
// The residue cannot reach it: a block copy's base goes to `memcpy`, no struct-returning
|
|
736
|
+
// callee. That reading is agbcc's layout, and is measured beyond `f0`: thumb.h:573
|
|
737
|
+
// STARTING_FRAME_OFFSET 0 and :620-621 add `current_function_outgoing_args_size` to every
|
|
738
|
+
// elimination to sp (:628 ACCUMULATE_OUTGOING_ARGS); function.c:2629 puts STACK_DYNAMIC_OFFSET
|
|
739
|
+
// past the same size; calls.c:1598-1609 precomputes into a temp every argument containing a
|
|
740
|
+
// call whenever the call has stack arguments, so a struct result is never built straight into
|
|
741
|
+
// an outgoing word. Compiled at the rows' flags: `mk(..)` by value as the 5th argument, as the
|
|
742
|
+
// 1st followed by four ints, `mk(..).a` as the 5th, a 20-byte struct by value, and a 5-argument
|
|
743
|
+
// K&R `rnd(1, 2, 3, 4, 5)` beside a temp each put the temp at sp+4 or higher, with [sp,#0] the
|
|
744
|
+
// outgoing word. A frame whose sp moves between two accesses is refused before this is read
|
|
745
|
+
// (thumb.ts `frameDepthBlocker`). Hand-written asm that builds a struct return at [sp,#0] and
|
|
746
|
+
// also stores a stack argument there declines; one that hands that word, unstored, to an
|
|
747
|
+
// undeclared callee as its fifth argument loses it from the callee's guessed arity.
|
|
724
748
|
escapedFrameAddressNamesALocal?: boolean;
|
|
725
749
|
// WHERE AN O32 FUNCTION KEEPS ITS LOCALS in the one frame `addiu sp,sp,-N` reserves — the area
|
|
726
750
|
// the MIPS frontend hands the frame-object audit as `ownedLocals`/`declaredLocals`. Below it
|
|
@@ -1099,6 +1123,16 @@ export interface TargetDescription {
|
|
|
1099
1123
|
// (proto-context.ts). ABSENT ⇒ unmeasured,
|
|
1100
1124
|
// and an aggregate with an enum member is not sized.
|
|
1101
1125
|
enumBytes?: number;
|
|
1126
|
+
// Whether a plain `char` is signed, read where a context declares a global `char` and the lift
|
|
1127
|
+
// asks whether a narrow read of it agrees with that declaration (structure.ts
|
|
1128
|
+
// `storageDisagrees`). The compiler's default, with no flag; `targetFor` applies the flags that
|
|
1129
|
+
// change it (`-funsigned-char`/`-fsigned-char`, IDO's `-signed`, mwcc's `-char`). MEASURED,
|
|
1130
|
+
// `s32 g(void){ return gC; }` over `extern char gC;` at each toolchain's canonical flags: agbcc
|
|
1131
|
+
// `ldrb` and ido7.1 `lbu` (unsigned), gcc2.7.2 `lb` (signed; gcc2.7.2kmc's canonical
|
|
1132
|
+
// `-funsigned-char` makes it `lbu`), mwcc_242_81 `lbz` + `extsb` (signed). ABSENT ⇒ unmeasured,
|
|
1133
|
+
// and a narrow read of a declared plain `char` agrees with neither sign, so it is spelled
|
|
1134
|
+
// through its address.
|
|
1135
|
+
plainCharSigned?: boolean;
|
|
1102
1136
|
// How a struct's bitfields are placed (`aggregate.ts`). 'contiguous' — each at the bit after the
|
|
1103
1137
|
// one before, straddling a byte or a word of its declared type, with the next member that is not
|
|
1104
1138
|
// a bitfield at the first byte past it that its alignment allows. agbcc: thumb.h defines no
|
|
@@ -1149,6 +1183,23 @@ export interface TargetDescription {
|
|
|
1149
1183
|
// ABSENT ⇒ false: a sign-extending load says nothing about the qualifier, and a device read is
|
|
1150
1184
|
// pinned, and a read of a declared object placed, whatever its extension.
|
|
1151
1185
|
volatileReadsExtendInRegister?: boolean;
|
|
1186
|
+
// Does this compiler RELOAD a cell stored through one spelling and read back through another —
|
|
1187
|
+
// two names, or a name and a numeric address, for the same byte? MEASURED at -O2, each probe
|
|
1188
|
+
// storing 5 to a word, 7 to one of its bytes, and reading the word back: IDO 7.1 reloads
|
|
1189
|
+
// (`D_80100000 = 5; ((u8 *)0x80100000)[1] = 7; return D_80100000;` is `sb; jr ra; lw v0`);
|
|
1190
|
+
// agbcc (`sRandCount = 5; ((u8 *)&sUnknown)[4] = 7; return sRandCount;` is `strb; mov r0,
|
|
1191
|
+
// #5`) and gcc 2.7.2 (`sb; jr ra; li v0, 5`) forward the stored value. IDO also reloads across
|
|
1192
|
+
// two externs, two statics, an array element beside a scalar, and a word-wide literal beside
|
|
1193
|
+
// a name. It reloads only a read that FOLLOWS the store: a read placed before a byte store
|
|
1194
|
+
// through another name is sunk past it (`v0 = D_80100004; ((u8 *)&D_80100000)[5] = 7;
|
|
1195
|
+
// return v0;` is `sb; jr ra; lw v0`). So where it holds, a pair of spellings in one block
|
|
1196
|
+
// outside any loop, the store first, is faithful lifted as-is, whether the asm carries both or
|
|
1197
|
+
// the map chose one; any other order, and every pair where it does not hold (an asm that went
|
|
1198
|
+
// back to memory proves the source did not spell the byte two ways), raise/offsetnames.ts
|
|
1199
|
+
// `cellSpelledTwice` declines.
|
|
1200
|
+
//
|
|
1201
|
+
// ABSENT ⇒ false: the compiler forwards (or is unmeasured, which declines the same pairs).
|
|
1202
|
+
reloadsAcrossSpellings?: boolean;
|
|
1152
1203
|
// Does this compiler, AT THESE FLAGS, keep a plain read whose value nothing uses? A FLAG fact,
|
|
1153
1204
|
// never written in a description: `targetFor` sets it from the optimisation level
|
|
1154
1205
|
// (`keepsUnusedReadsAt`), so a target at other flags has it absent. Where it holds, a read
|
|
@@ -1200,6 +1251,120 @@ export interface TargetDescription {
|
|
|
1200
1251
|
// load decides only where this is `false`. ABSENT ⇒ unmeasured, and read as `true`, which only
|
|
1201
1252
|
// refuses more.
|
|
1202
1253
|
discardedReadMayFillArgRegister?: boolean;
|
|
1254
|
+
// DOES THE COMPILER CROSS-JUMP RETURN TAILS? Where two arms end in the same code before the
|
|
1255
|
+
// same return, does it keep ONE copy and jump the other arm into it? Where it does, the source
|
|
1256
|
+
// may have written the tail into every arm (`h(10, 0); return;` in each) and the object still
|
|
1257
|
+
// holds it once. So a join in the CFG is no evidence the source wrote one. What tells the two
|
|
1258
|
+
// spellings apart is where the surviving copy sits, and which arms became one block. Read by
|
|
1259
|
+
// the return-tail chooser (pipeline.ts `structureReturnTailReading`): it publishes the copies
|
|
1260
|
+
// where the target's layout shows that merge (structure/cross-jumped-tail.ts), and never reads
|
|
1261
|
+
// the layout where this is absent.
|
|
1262
|
+
//
|
|
1263
|
+
// gcc 2.x's jump2 pass does it: jump.c, in the `JUMP_CROSS_JUMP` run of `jump_optimize` that
|
|
1264
|
+
// toplev.c makes at `optimize > 0`.
|
|
1265
|
+
// - An unconditional jump is first cross-jumped into the code before its own target label
|
|
1266
|
+
// (`find_cross_jump (insn, JUMP_LABEL (insn), 1)`).
|
|
1267
|
+
// - Only then is it tried against the other jumps to that label, and that search runs only
|
|
1268
|
+
// `if (INSN_UID (JUMP_LABEL (insn)) < max_uid)`.
|
|
1269
|
+
// - `do_cross_jump` keeps the LATER copy and jumps to a label before it. `get_label_before`
|
|
1270
|
+
// creates that label, so a jump already redirected is never searched for siblings again.
|
|
1271
|
+
// So the copies spelling leaves three shapes in the object (structure/cross-jumped-tail.ts):
|
|
1272
|
+
// - an arm that does not use the tail stays laid out after the surviving copy, and that copy
|
|
1273
|
+
// ends in a jump over it;
|
|
1274
|
+
// - identical arm bodies stay separate blocks, where the join's one original label would have
|
|
1275
|
+
// merged them;
|
|
1276
|
+
// - `find_cross_jump` compares backward and stops at the first pair that differs, so a call
|
|
1277
|
+
// the tail opens with can be cut from the argument setup each arm keeps.
|
|
1278
|
+
// MEASURED on two probe pairs, `corpus/probe-rettail-{pa,pb}-{copies,join}.c`: a three-arm `if`
|
|
1279
|
+
// and a seven-case `switch`. Each is compiled at each toolchain's canonical flags by
|
|
1280
|
+
// `scripts/regen-rettail-probes.ts`, and cross-jumped-tail.test.ts reads the objects:
|
|
1281
|
+
// - agbcc: the `pa` copies keep the tail mid-construct, ending in `b`, with the else arm laid
|
|
1282
|
+
// out after it. The `pb` copies hold the tail's store ONCE, in six case bodies; the join
|
|
1283
|
+
// has three bodies.
|
|
1284
|
+
// - gcc2.7.2 (-O1) and gcc2.7.2kmc (-O2): `pb` is the same, one store, six bodies against
|
|
1285
|
+
// three. The `pa` copies and join compile byte-identical, because the fall-through arm's
|
|
1286
|
+
// call merged too.
|
|
1287
|
+
// - ido7.1 and mwcc_242_81: NO. The `pb` copies keep all seven stores, each before its own
|
|
1288
|
+
// return, and the `pa` copies keep three calls.
|
|
1289
|
+
// So `true` on ARMV4T_AGBCC and on MIPS_GCC. Each toolchain MIPS_GCC serves owes its own pair
|
|
1290
|
+
// (the KEYED BY DESCRIPTION note at `spillSlotOrder`), and both are committed. ABSENT means no
|
|
1291
|
+
// such merge: the chooser's size bound alone decides. mwcc_233_163n and mwcc_247_107 are
|
|
1292
|
+
// unprobed, and PPC_MWCC claims nothing for them. Read only where the join is the default
|
|
1293
|
+
// (`joinPastReturnsByDefault`), which both descriptions declaring this field leave out, so the
|
|
1294
|
+
// marks decide no shipped default; the probe pairs still pin each one.
|
|
1295
|
+
crossJumpsReturnTails?: boolean;
|
|
1296
|
+
// DOES THE COMPILER FOLD THE CONSTANT A BRANCH PROVED INTO A RETURN ON THE BRANCH'S TAKEN EDGE?
|
|
1297
|
+
// After `if (x == 0)`, a `return x` the jump reaches is `return 0` where it does. gcc 2.x's cse
|
|
1298
|
+
// records the branch's equivalence on the NOT-taken edge of every jump (`record_jump_equiv
|
|
1299
|
+
// (insn, 0)`, cse.c:7597), and on the taken edge only along a path it follows (`record_jump_equiv
|
|
1300
|
+
// (insn, 1)`, cse.c:8545). It follows one only under -fcse-follow-jumps, which toplev.c:3606-3607
|
|
1301
|
+
// sets at `optimize >= 2` (-fcse-skip-blocks skips a block without recording the edge:
|
|
1302
|
+
// `cseFollowsJumpsUnder`). (Line numbers are agbcc's gcc 2.9 tree; for
|
|
1303
|
+
// 2.7.2 the probe below is the evidence.) So where this is `false`, a return of the tested value
|
|
1304
|
+
// left UNFOLDED in the object was the jump-target arm of its branch in the source: an `else`.
|
|
1305
|
+
// The join past early returns spells it `if (x == 0) return x;`, the then arm, which folds.
|
|
1306
|
+
// Read by the return-tail chooser (pipeline.ts `structureReturnTailReading`), which publishes the
|
|
1307
|
+
// copies at such a return (structure/unfolded-branch-constant.ts), and only where the join is
|
|
1308
|
+
// the default (`joinPastReturnsByDefault`), which MIPS_GCC, the one description declaring this
|
|
1309
|
+
// field, leaves out: so it decides no shipped default, and the probes below still pin it.
|
|
1310
|
+
// `false` only by the flags (`targetFor`), never declared.
|
|
1311
|
+
//
|
|
1312
|
+
// MEASURED on the probe pair `corpus/probe-rettail-pg-{copies,join}.c`, the `return gB` after a
|
|
1313
|
+
// `gB` tested against 0, compiled by scripts/regen-rettail-probes.ts. The `beqz`'s delay slot:
|
|
1314
|
+
// - gcc2.7.2 at -O1 (canonical): copies `nop`, join `move v0,zero`. With the arms of the
|
|
1315
|
+
// copies flipped (the return the then arm), and with `-O1 -fcse-follow-jumps`, `move v0,zero`.
|
|
1316
|
+
// - gcc2.7.2 at -O2, and gcc2.7.2kmc at -O2 (canonical): `move v0,zero` under every spelling.
|
|
1317
|
+
// So `true` on MIPS_GCC, and `false` from the flags where cse follows no jump (level 1 without
|
|
1318
|
+
// -fcse-follow-jumps, or -fno-cse-follow-jumps: `cseFollowsJumpsUnder`). Absent elsewhere: agbcc
|
|
1319
|
+
// at -O2 re-loads `gB` for the copies' `else` and does not fold it, which no value of this field
|
|
1320
|
+
// says; ido7.1 and mwcc are unprobed.
|
|
1321
|
+
foldsBranchConstantOnTakenEdge?: boolean;
|
|
1322
|
+
// IS THE JOIN PAST EARLY RETURNS THE DEFAULT READING HERE? Not a compiler behaviour but a MEASURED
|
|
1323
|
+
// DEFAULT, kept beside the behaviours because it is per compiler and read by the chooser they
|
|
1324
|
+
// feed (pipeline.ts `structureReturnTailReading`), which the size bound and the marks above
|
|
1325
|
+
// steer only where this is `true`. ABSENT ⇒ the return-tail copies are published, as before the
|
|
1326
|
+
// join was a default, and the join is still structured and kept in the ranked fan as
|
|
1327
|
+
// `/ret-join`. A compiler the held-out measurement has not reached publishes the copies first,
|
|
1328
|
+
// and the join only where the copies refuse, so it lifts every function it lifted before and
|
|
1329
|
+
// may lift one the old default declined (pipeline.ts `copiesByDefault`).
|
|
1330
|
+
// docs/level-tower.md (A MEASURED DEFAULT IS THE ONE EXCEPTION) says why a field whose readings
|
|
1331
|
+
// split site by site may still be declared here, and what re-measures it.
|
|
1332
|
+
//
|
|
1333
|
+
// MEASURED over held-out real functions, every function of a project outside the 620 the marks
|
|
1334
|
+
// were chosen on, both readings really compiled against the project's own objects (2026-11-06),
|
|
1335
|
+
// and over 160 generated functions written with the copies (2026-11-09). `true` only where the
|
|
1336
|
+
// size bound with every mark loses no match the copies have on every measured set:
|
|
1337
|
+
// - mwcc_233_163n (Pikmin at its own flags, 2733 functions lifted, 66 re-spelled): 0 lost,
|
|
1338
|
+
// 3 gained (two `actByEvent` methods 4/22 -> 0/20, `stickSlot__6PelletFi` 3/28 -> 0/26);
|
|
1339
|
+
// the generated functions: every source identical to the copies';
|
|
1340
|
+
// - mwcc_242_81 (ac-decomp and Mario Party 4, every unit compiled at this build although 9 of
|
|
1341
|
+
// Mario Party 4's rows are mwcc_247_107; 141 re-spelled): 0 lost, 0 gained; the generated
|
|
1342
|
+
// functions: every source identical to the copies'.
|
|
1343
|
+
// So `true` on PPC_MWCC (mwcc_242_81) and PPC_MWCC_233; PPC_MWCC_247 (mwcc_247_107) leaves it
|
|
1344
|
+
// out, no held-out unit measured at that build. Left out where it loses, the copies' default
|
|
1345
|
+
// given back with the join's gains:
|
|
1346
|
+
// - gcc2.7.2kmc: held out (Snowboard Kids 2, 78 re-spelled) 0 lost, 2 gained
|
|
1347
|
+
// (`func_8003513C_35D3C`, `func_80045564_46164`); generated, 3 lost (the copies match, the
|
|
1348
|
+
// join is 2 to 8 lines off) and 5 gained. Given up: those 2 held out and the 5 generated.
|
|
1349
|
+
// - gcc2.7.2: held out (Mario Party 3, all 10019 functions, 150 re-spelled) 0 lost once the
|
|
1350
|
+
// unfolded return is read (`foldsBranchConstantOnTakenEdge`), none gained; generated, 1 lost
|
|
1351
|
+
// (4 lines off) and 2 gained. No mark read from the layout or the unfolded return separates
|
|
1352
|
+
// the 4 generated losses on the two lanes. Given up: the 2 generated, and the held-out and
|
|
1353
|
+
// 620 functions the join brought closer.
|
|
1354
|
+
// - agbcc (pokeemerald, 15062 functions, 963 re-spelled): 9 lost, 18 gained. None of the 9 is
|
|
1355
|
+
// marked: switch and comparison-tree sites, a call cross-jumped across arms that pass it
|
|
1356
|
+
// different arguments, and a third source spelling the copies happen to share. Given up:
|
|
1357
|
+
// 18 held out and 2 of the 620.
|
|
1358
|
+
// - ido7.1 (Animal Forest, 11363 functions, 252 re-spelled): 4 lost, one shape, 18 gained. ido
|
|
1359
|
+
// never cross-jumps, so no cross-jump mark applies. Counting copies of the arm's store does
|
|
1360
|
+
// not separate the readings either: compiled, the join of `func_80A42DB4_jp` ALSO keeps two
|
|
1361
|
+
// `sw zero,0(a3)`, because ido fills a `beql`'s likely slot with a copy of its target's first
|
|
1362
|
+
// word. What differs is the branch the slot copy rides: the copies' arm schedules its own
|
|
1363
|
+
// store into the slot of a plain `b`, the join's copy sits in a `beql`'s. That is the
|
|
1364
|
+
// candidate signal, unmeasured: frontend/mips.ts `normaliseBranchLikely` rewrites both to one
|
|
1365
|
+
// shape (its `likely` and `freed` sets still tell them apart) before the layout is recorded,
|
|
1366
|
+
// so no layout entry carries it. Given up: 18 held out and 7 of the 620.
|
|
1367
|
+
joinPastReturnsByDefault?: boolean;
|
|
1203
1368
|
};
|
|
1204
1369
|
}
|
|
1205
1370
|
|
|
@@ -1335,6 +1500,10 @@ export const ARMV4T_AGBCC: TargetDescription = {
|
|
|
1335
1500
|
switchBoundCase: 'taken',
|
|
1336
1501
|
switchArmsFollowLayout: true,
|
|
1337
1502
|
switchRequiresFrontLoadedTests: true,
|
|
1503
|
+
// MEASURED on the probe pairs at the field (gcc 2.x jump.c's cross-jump). Read only where the join
|
|
1504
|
+
// is the default (`joinPastReturnsByDefault`), which agbcc leaves out: the join loses 9 of
|
|
1505
|
+
// pokeemerald's matches no mark separates.
|
|
1506
|
+
crossJumpsReturnTails: true,
|
|
1338
1507
|
hoistsSingleSetArm: true,
|
|
1339
1508
|
eightByteReturnScratch: 'r2',
|
|
1340
1509
|
arrayShapeFromStride: true,
|
|
@@ -1355,6 +1524,7 @@ export const ARMV4T_AGBCC: TargetDescription = {
|
|
|
1355
1524
|
discardedReadMayFillArgRegister: true,
|
|
1356
1525
|
byValueAggregateArgWords: true,
|
|
1357
1526
|
enumBytes: 4,
|
|
1527
|
+
plainCharSigned: false,
|
|
1358
1528
|
bitfieldPacking: 'contiguous',
|
|
1359
1529
|
roundTripsDoubleLiterals: true,
|
|
1360
1530
|
// agbcc 2.9 (gcc/varasm.c `assemble_variable`, gcc/thumb.h): an array takes its element's
|
|
@@ -1512,6 +1682,7 @@ export const MIPS_IDO: TargetDescription = {
|
|
|
1512
1682
|
// `switchAllowsNeqCase: false` — IDO's switch dispatch uses `==`/`<`, never `!=` cases;
|
|
1513
1683
|
// leaving it permissive mis-recognises `!=`-rooted if-else chains as switches.
|
|
1514
1684
|
compilerBehaviors: {
|
|
1685
|
+
plainCharSigned: false,
|
|
1515
1686
|
contractsFloatProducts: false,
|
|
1516
1687
|
coalesceLoopInit: true,
|
|
1517
1688
|
preserveDivergentBranchSense: true,
|
|
@@ -1519,6 +1690,8 @@ export const MIPS_IDO: TargetDescription = {
|
|
|
1519
1690
|
switchAllowsNeqCase: false,
|
|
1520
1691
|
// MEASURED — the pair at the field compiles to one load of `p[1]` for every local spelling.
|
|
1521
1692
|
reloadsLocalReread: false,
|
|
1693
|
+
// MEASURED: the probe at the field (`lw v0` after the byte store).
|
|
1694
|
+
reloadsAcrossSpellings: true,
|
|
1522
1695
|
// MEASURED: the probe at the field.
|
|
1523
1696
|
narrowStoreConstantTakesLvalueType: true,
|
|
1524
1697
|
aggregateBoundary: 1,
|
|
@@ -1643,6 +1816,7 @@ export const MIPS_GCC: TargetDescription = {
|
|
|
1643
1816
|
fpu: O32_FPU,
|
|
1644
1817
|
capabilities: { endianness: 'big', hwDivide: true, hwFloat: true, flags: false },
|
|
1645
1818
|
compilerBehaviors: {
|
|
1819
|
+
plainCharSigned: true,
|
|
1646
1820
|
contractsFloatProducts: false,
|
|
1647
1821
|
coalesceLoopInit: true,
|
|
1648
1822
|
preserveDivergentBranchSense: true,
|
|
@@ -1682,6 +1856,15 @@ export const MIPS_GCC: TargetDescription = {
|
|
|
1682
1856
|
// sub-trees a decline leaves, so a NESTED dispatch comes back as an `if` nest around a `switch`
|
|
1683
1857
|
// over some of its arms.
|
|
1684
1858
|
switchRequiresFrontLoadedTests: true,
|
|
1859
|
+
// MEASURED on BOTH toolchains this description serves, the probe pairs at the field.
|
|
1860
|
+
crossJumpsReturnTails: true,
|
|
1861
|
+
// MEASURED at -O2 on both toolchains this description serves, the probe pair at the field;
|
|
1862
|
+
// gcc2.7.2's canonical -O1 clears it (`targetFor`).
|
|
1863
|
+
foldsBranchConstantOnTakenEdge: true,
|
|
1864
|
+
// `joinPastReturnsByDefault` LEFT OUT, so the copies are published and the join is ranked as
|
|
1865
|
+
// `/ret-join`. On held-out real functions it lost none of the copies' matches on either toolchain
|
|
1866
|
+
// this description serves, but on 160 generated functions written with the copies it lost 3 on
|
|
1867
|
+
// gcc2.7.2kmc and 1 on gcc2.7.2, which no mark separates (the field's note).
|
|
1685
1868
|
// MEASURED on BOTH toolchains this description serves (the note above): one load of `p[1]` for
|
|
1686
1869
|
// every local spelling of the pair at the field, gcc2.7.2kmc at -O2 and gcc2.7.2 at -O1 alike.
|
|
1687
1870
|
reloadsLocalReread: false,
|
|
@@ -1778,6 +1961,7 @@ export const PPC_MWCC: TargetDescription = {
|
|
|
1778
1961
|
// defaults; coalesceLoopInit false until a CW loop fixture says otherwise — the second of the
|
|
1779
1962
|
// two compiler-wide guesses standing in for the per-function observation named at MIPS_GCC.
|
|
1780
1963
|
compilerBehaviors: {
|
|
1964
|
+
plainCharSigned: true,
|
|
1781
1965
|
contractsFloatProducts: true,
|
|
1782
1966
|
// MEASURED on all three builds in C: an integer, a pointer and a `u16 *` passed to an `s32 *`,
|
|
1783
1967
|
// `u32` and `s32 *` parameter each fail to compile, and each compiles once cast (see the field).
|
|
@@ -1849,12 +2033,31 @@ export const PPC_MWCC: TargetDescription = {
|
|
|
1849
2033
|
// MEASURED on all three builds: the probes at the fields.
|
|
1850
2034
|
forwardsStructReturnDestination: false,
|
|
1851
2035
|
discardedReadMayFillArgRegister: true,
|
|
2036
|
+
// MEASURED over held-out real functions at mwcc_242_81 and mwcc_233_163n, at the field;
|
|
2037
|
+
// PPC_MWCC_247 leaves it out.
|
|
2038
|
+
joinPastReturnsByDefault: true,
|
|
1852
2039
|
},
|
|
1853
2040
|
};
|
|
1854
2041
|
|
|
2042
|
+
/** A description's behaviours less `joinPastReturnsByDefault`, for a build that shares the
|
|
2043
|
+
* description but was not measured for that default. */
|
|
2044
|
+
function withoutJoinDefault(d: TargetDescription): TargetDescription['compilerBehaviors'] {
|
|
2045
|
+
const { joinPastReturnsByDefault: _measuredElsewhere, ...behaviors } = d.compilerBehaviors;
|
|
2046
|
+
return behaviors;
|
|
2047
|
+
}
|
|
2048
|
+
|
|
1855
2049
|
/** CodeWarrior 2.3.3b163n (Pikmin): `PPC_MWCC` but for the one declaration a probe refuted there —
|
|
1856
|
-
* it rejects a global register variable ("illegal storage class"), so it has no spelling for one.
|
|
1857
|
-
|
|
2050
|
+
* it rejects a global register variable ("illegal storage class"), so it has no spelling for one.
|
|
2051
|
+
* The return-tail default is PPC_MWCC's, measured at this build over Pikmin
|
|
2052
|
+
* (`joinPastReturnsByDefault`). */
|
|
2053
|
+
export const PPC_MWCC_233: TargetDescription = {
|
|
2054
|
+
...PPC_MWCC,
|
|
2055
|
+
globalRegisterVariables: undefined,
|
|
2056
|
+
};
|
|
2057
|
+
|
|
2058
|
+
/** CodeWarrior 2.4.7b107 (Mario Party 4's other build): `PPC_MWCC` less the return-tail default no
|
|
2059
|
+
* held-out function measured at this build (`joinPastReturnsByDefault`). */
|
|
2060
|
+
export const PPC_MWCC_247: TargetDescription = { ...PPC_MWCC, compilerBehaviors: withoutJoinDefault(PPC_MWCC) };
|
|
1858
2061
|
|
|
1859
2062
|
/** A toolchain: one compiler binary, named as decomp.me names it. Several toolchains may share a
|
|
1860
2063
|
* description (`MIPS_GCC` serves two), and each keeps its own evidence. */
|
|
@@ -1946,7 +2149,7 @@ export const TOOLCHAIN_TARGETS = {
|
|
|
1946
2149
|
},
|
|
1947
2150
|
mwcc_247_107: {
|
|
1948
2151
|
family: 'mwcc',
|
|
1949
|
-
description:
|
|
2152
|
+
description: PPC_MWCC_247,
|
|
1950
2153
|
},
|
|
1951
2154
|
} as const satisfies Readonly<Record<string, ToolchainTarget>>;
|
|
1952
2155
|
|
|
@@ -2038,9 +2241,67 @@ export function targetFor(toolchain: ToolchainId, cflags: readonly string[]): Re
|
|
|
2038
2241
|
const { enumBytes: _flagged, ...behaviors } = target.compilerBehaviors;
|
|
2039
2242
|
target = { ...target, compilerBehaviors: behaviors };
|
|
2040
2243
|
}
|
|
2244
|
+
if (target.compilerBehaviors.foldsBranchConstantOnTakenEdge === true && !cseFollowsJumpsUnder(profile)) {
|
|
2245
|
+
target = { ...target, compilerBehaviors: { ...target.compilerBehaviors, foldsBranchConstantOnTakenEdge: false } };
|
|
2246
|
+
}
|
|
2247
|
+
const charSigned = plainCharSignedUnder(t.family, profile.slots);
|
|
2248
|
+
if (charSigned !== 'default' && charSigned !== target.compilerBehaviors.plainCharSigned) {
|
|
2249
|
+
const { plainCharSigned: _default, ...behaviors } = target.compilerBehaviors;
|
|
2250
|
+
target = {
|
|
2251
|
+
...target,
|
|
2252
|
+
compilerBehaviors: charSigned === undefined ? behaviors : { ...behaviors, plainCharSigned: charSigned },
|
|
2253
|
+
};
|
|
2254
|
+
}
|
|
2041
2255
|
return { toolchain, cflags, target, profile };
|
|
2042
2256
|
}
|
|
2043
2257
|
|
|
2258
|
+
/** Does gcc's cse follow jumps at these flags (`compilerBehaviors.foldsBranchConstantOnTakenEdge`)?
|
|
2259
|
+
* Only a jump cse FOLLOWS gets its taken edge's equivalence: `cse_end_of_basic_block` marks a path
|
|
2260
|
+
* branch TAKEN only under `follow_jumps` (cse.c:8192-8211), and the branch around a block that
|
|
2261
|
+
* `skip_blocks` adds is AROUND, whose block is invalidated, not recorded (cse.c:8223, :8545-8548).
|
|
2262
|
+
* toplev.c:3606-3607 sets `flag_cse_follow_jumps` at `optimize >= 2`, and the switch sets or clears
|
|
2263
|
+
* it by name either way, the level's value standing where it is not given. -O0 is answered `true`:
|
|
2264
|
+
* cse does not run there, which is a different compiler again, and a level no table reads keeps
|
|
2265
|
+
* the declaration. (cse.c line numbers are agbcc's gcc 2.9 tree.)
|
|
2266
|
+
*
|
|
2267
|
+
* MEASURED on `corpus/probe-rettail-pg` at gcc2.7.2, the copies' `beqz` delay slot: `nop` (unfolded)
|
|
2268
|
+
* at -O1, at `-O1 -fcse-skip-blocks`, at `-O2 -fno-cse-follow-jumps` and at `-O2
|
|
2269
|
+
* -fno-cse-follow-jumps -fno-cse-skip-blocks`; `move v0,zero` at -O2, at `-O2 -fno-cse-skip-blocks`
|
|
2270
|
+
* and at `-O1 -fcse-follow-jumps`. The join is `move v0,zero` under all seven. */
|
|
2271
|
+
function cseFollowsJumpsUnder(profile: CodegenProfile): boolean {
|
|
2272
|
+
if (profile.family !== 'gcc' && profile.family !== 'agbcc') {
|
|
2273
|
+
return true;
|
|
2274
|
+
}
|
|
2275
|
+
if (profile.slots.O === '0') {
|
|
2276
|
+
return true;
|
|
2277
|
+
}
|
|
2278
|
+
const flag = profile.slots['-fcse-follow-jumps'];
|
|
2279
|
+
return flag === 'on' ? true : flag === 'off' ? false : profile.slots.O !== '1';
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
/** What the flags say a plain `char` is (`compilerBehaviors.plainCharSigned`): `default` where they
|
|
2283
|
+
* leave it to the compiler. gcc keeps one flag, `flag_signed_char`, that `-fsigned-char` and
|
|
2284
|
+
* `-fno-unsigned-char` set and `-funsigned-char` and `-fno-signed-char` clear, the last one given
|
|
2285
|
+
* winning; the profile keeps the two switches apart, so where both are given and they disagree the
|
|
2286
|
+
* order is lost and the answer is undefined. IDO's `-signed` makes it signed, and mwcc's `-char`
|
|
2287
|
+
* takes `signed` or `unsigned`. */
|
|
2288
|
+
function plainCharSignedUnder(
|
|
2289
|
+
family: FlagFamily,
|
|
2290
|
+
slots: Readonly<Record<string, string>>,
|
|
2291
|
+
): boolean | undefined | 'default' {
|
|
2292
|
+
if (family === 'ido') {
|
|
2293
|
+
return slots.char === 'signed' ? true : 'default';
|
|
2294
|
+
}
|
|
2295
|
+
if (family === 'mwcc') {
|
|
2296
|
+
return slots.char === 'signed' ? true : slots.char === 'unsigned' ? false : 'default';
|
|
2297
|
+
}
|
|
2298
|
+
const said = [
|
|
2299
|
+
slots['-fsigned-char'] === undefined ? undefined : slots['-fsigned-char'] === 'on',
|
|
2300
|
+
slots['-funsigned-char'] === undefined ? undefined : slots['-funsigned-char'] === 'off',
|
|
2301
|
+
].filter((x) => x !== undefined);
|
|
2302
|
+
return said.length === 0 ? 'default' : said.every((x) => x === said[0]) ? said[0] : undefined;
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2044
2305
|
/** Build the structurer's options for a target: the function's own `returnsVoid` plus every
|
|
2045
2306
|
* `compilerBehaviors` field. The ONE place a target's compiler behaviors flow into the
|
|
2046
2307
|
* target-agnostic structurer — a new compiler behavior is a field in `compilerBehaviors`, consumed
|
package/src/trace.ts
CHANGED
|
@@ -7,12 +7,20 @@
|
|
|
7
7
|
import { cBackend } from './backend/c';
|
|
8
8
|
import type { CodegenProfile } from './codegen-flags';
|
|
9
9
|
import type { AsmData } from './frontend/asmdata';
|
|
10
|
+
import type { CalleeBodies } from './frontend/callee-body';
|
|
10
11
|
import type { Fn } from './ir/core';
|
|
11
12
|
import { print } from './ir/print';
|
|
12
13
|
import { verify } from './ir/verify';
|
|
13
14
|
import type { LanguageBackend } from './l3/ast';
|
|
14
15
|
import { DEFAULT_IDIOM_PATTERNS, RewritePattern, applyPattern, dce, patternApplies } from './pattern/engine';
|
|
15
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
type OnGap,
|
|
18
|
+
liftStamped,
|
|
19
|
+
offsetNameBehaviorsFor,
|
|
20
|
+
raiseRecovered,
|
|
21
|
+
structureReturnTailReading,
|
|
22
|
+
stubResult,
|
|
23
|
+
} from './pipeline';
|
|
16
24
|
import { type FnProto, type Prototypes, declaresVoidReturn, prototypesFromSymbols } from './proto';
|
|
17
25
|
import { type ContextGlobals, splitOwnContext } from './proto-context';
|
|
18
26
|
import { assumedShapes, inferGlobalArrays, orderLicensedGlobals } from './raise/globalshape';
|
|
@@ -95,6 +103,7 @@ export interface TraceOptions {
|
|
|
95
103
|
symbols?: SymbolMap; // address→symbol map (symbols.ts), as in decompile(); absent ⇒ inert
|
|
96
104
|
contextGlobals?: ContextGlobals; // the declaration context's globals, as in decompile()
|
|
97
105
|
ownContextProto?: FnProto; // the context's own entry, as in decompile()
|
|
106
|
+
calleeBodies?: CalleeBodies; // bodies of callees outside the input, as in decompile()
|
|
98
107
|
onGap?: OnGap; // "strict" (default) | "annotate", as in decompile()
|
|
99
108
|
/** Source probe at pattern boundaries: the source asmlift would emit there, which the cli report
|
|
100
109
|
* scores. One call per boundary: pattern N's after-source is pattern N+1's before-source. Absent ⇒
|
|
@@ -207,7 +216,7 @@ function traceTower(
|
|
|
207
216
|
opts.asmData,
|
|
208
217
|
opts.symbols,
|
|
209
218
|
opts.contextGlobals,
|
|
210
|
-
|
|
219
|
+
{ calleeBodies: opts.calleeBodies },
|
|
211
220
|
ownContext.ownContextProto,
|
|
212
221
|
);
|
|
213
222
|
trace.push({ id: 'stage:lift', title: 'Lift (ISA frontend → typed-SSA IR)', irDump: irDump(fn), verified: true });
|
|
@@ -234,9 +243,9 @@ function traceTower(
|
|
|
234
243
|
|
|
235
244
|
// The names the map holds for addresses this machine code built by arithmetic off a named one
|
|
236
245
|
// (raise/offsetnames.ts), off the same lifted fn and for the same reason as pipeline.ts's
|
|
237
|
-
// runTower.
|
|
246
|
+
// runTower — and its one-spelling-per-cell decline, which throws from here (pipeline.ts's 1.6).
|
|
238
247
|
const named = opts.symbols
|
|
239
|
-
? OFFSET_NAME_PASS.run(fn, opts.symbols,
|
|
248
|
+
? OFFSET_NAME_PASS.run(fn, opts.symbols, offsetNameBehaviorsFor(target, opts.contextGlobals))
|
|
240
249
|
: [];
|
|
241
250
|
trace.push({
|
|
242
251
|
id: 'stage:offsetnames',
|
|
@@ -332,16 +341,21 @@ function traceTower(
|
|
|
332
341
|
// (4) structure → neutral AST; boundary contract: no unresolved value leaked (strict) or
|
|
333
342
|
// spelled as a loud ASMLIFT_ERROR marker (annotate) — same onGap option as decompile()
|
|
334
343
|
const mapSymbols = opts.symbols ? symbolsByName(opts.symbols) : undefined;
|
|
335
|
-
const sfn =
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
344
|
+
const { sfn } = structureReturnTailReading(
|
|
345
|
+
fn,
|
|
346
|
+
{
|
|
347
|
+
...structureOptionsFor(target, returnsVoid, prototypes),
|
|
348
|
+
spellSwitchFallthrough: (opts.backend ?? cBackend).spellsSwitchFallthrough,
|
|
349
|
+
spellSwitchBreak: (opts.backend ?? cBackend).spellsSwitchBreak,
|
|
350
|
+
spellEarlyReturns: (opts.backend ?? cBackend).spellsEarlyReturn,
|
|
351
|
+
onGap: opts.onGap ?? 'strict',
|
|
352
|
+
...(mapSymbols ? { symbols: mapSymbols } : {}),
|
|
353
|
+
...(opts.contextGlobals ? { contextGlobals: opts.contextGlobals } : {}),
|
|
354
|
+
...(inferredSymbols.size ? { inferredSymbols } : {}),
|
|
355
|
+
...(orderLicensed.size ? { orderLicensedGlobals: orderLicensed } : {}),
|
|
356
|
+
},
|
|
357
|
+
(t) => backend.emit(t),
|
|
358
|
+
);
|
|
345
359
|
trace.push({
|
|
346
360
|
id: 'stage:structure',
|
|
347
361
|
title: 'Structuring (IR → neutral AST)',
|