@asmlift/core 0.6.0 → 0.8.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 +48 -24
- package/package.json +1 -1
- package/src/backend/cfamily.ts +39 -11
- package/src/backend/pascal.ts +2 -2
- package/src/codegen-flags.ts +640 -0
- package/src/contracts.ts +60 -11
- package/src/frontend/disasm.ts +141 -11
- package/src/frontend/high-half.ts +149 -0
- package/src/frontend/mips.ts +458 -209
- package/src/frontend/ppc.ts +332 -67
- package/src/frontend/reloc-symbol.ts +109 -0
- package/src/frontend/splat.ts +56 -18
- package/src/frontend/ssa.ts +127 -30
- package/src/frontend/stackargs.ts +420 -0
- package/src/frontend/thumb.ts +209 -232
- package/src/ir/alias.ts +24 -0
- package/src/ir/core.ts +70 -3
- package/src/ir/opcodes.ts +52 -7
- package/src/ir/parse.ts +7 -1
- 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 +6 -6
- package/src/l3/argcopy.ts +269 -0
- package/src/l3/ast.ts +110 -22
- package/src/l3/basecse.ts +50 -30
- package/src/l3/coalesce.ts +118 -61
- 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 +32 -29
- 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/tailret.ts +70 -0
- package/src/l3/typing.ts +3 -3
- package/src/l3/unmerge.ts +483 -59
- package/src/l3/unreduce.ts +15 -14
- 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/mangle.ts +49 -0
- package/src/pattern/engine.ts +132 -17
- package/src/pipeline.ts +39 -16
- 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 +345 -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 +133 -3
- package/src/raise/pre-recovery.ts +100 -25
- package/src/raise/retsink.ts +389 -19
- package/src/raise/shortcircuit.ts +595 -34
- package/src/raise/structs.ts +4 -4
- package/src/raise/tailsink.ts +141 -0
- package/src/rank-declare.ts +21 -13
- package/src/{rank-axes.ts → rank-variations.ts} +319 -189
- package/src/rank.ts +1176 -805
- 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/retspell.ts +95 -0
- package/src/structure/structure.ts +1425 -201
- package/src/structure/switch-recover.ts +101 -8
- package/src/symbols.ts +127 -6
- package/src/target.ts +374 -44
- package/src/trace.ts +28 -19
- package/src/variation-definitions.ts +1590 -0
- package/src/variation-gates.ts +92 -0
- package/src/variation-tokens.ts +356 -0
package/README.md
CHANGED
|
@@ -15,7 +15,8 @@ The operative invariant everywhere: **loud decline > silent miscompile**. Where
|
|
|
15
15
|
cannot be byte-faithful it throws a typed error (strict mode) or emits an `ASMLIFT_ERROR`-marked
|
|
16
16
|
stub (`onGap: "annotate"`) — never plausible wrong code.
|
|
17
17
|
|
|
18
|
-
>
|
|
18
|
+
> Published as [`@asmlift/core`](https://www.npmjs.com/package/@asmlift/core); inside this repo it
|
|
19
|
+
> resolves through the pnpm workspace.
|
|
19
20
|
|
|
20
21
|
## Usage
|
|
21
22
|
|
|
@@ -31,7 +32,10 @@ console.log(source); // s32 my_func(s32 a0) { ... }
|
|
|
31
32
|
Input is **text**, following what each target's toolchain produces:
|
|
32
33
|
|
|
33
34
|
- The ARM target reads GBA `.s`, produced by agbcc and pret-style project splits.
|
|
34
|
-
- The MIPS
|
|
35
|
+
- The MIPS targets read `objdump -d --no-show-raw-insn` output and Splat-disassembled `.s`.
|
|
36
|
+
- The PPC target reads `powerpc-eabi-objdump -d -r -M gekko --no-show-raw-insn` output. `-r` carries
|
|
37
|
+
the relocation lines a `bl`'s callee name lives in, and `-M gekko` names CodeWarrior's machine:
|
|
38
|
+
the generic dialect decodes the GameCube's paired-single opcodes as POWER VSX.
|
|
35
39
|
|
|
36
40
|
### `decompile(name, asm, target, opts?)`
|
|
37
41
|
|
|
@@ -41,9 +45,20 @@ Input is **text**, following what each target's toolchain produces:
|
|
|
41
45
|
| `patterns` | Idiom rewrite patterns. Omitted = `DEFAULT_IDIOM_PATTERNS` (self-selects per target: most are compiler-gated, the boolean-negation folds are universal); `[]` = none |
|
|
42
46
|
| `prototypes` | Callee arities + void-ness, as a real project takes them from headers — drives call-argument recovery |
|
|
43
47
|
| `asmData` | Optional `objdump -s -r -t` side-table; required to recover MIPS/PPC jump-table switches |
|
|
48
|
+
| `symbols` | The project's address→symbol map: names, declaration shapes and signatures for the addresses the assembly only numbers. What each channel buys and how to build the ELF behind it: [`@asmlift/cli`](../cli/README.md#the-symbol-map-elf) |
|
|
44
49
|
| `onGap` | `"strict"` (default): throw on any gap. `"annotate"`: emit best-effort source with `ASMLIFT_ERROR` markers; every gap is also returned in the structured `diagnostics` array (empty ⇔ gap-free) |
|
|
45
50
|
|
|
46
|
-
|
|
51
|
+
`DecompileResult.assumedSymbols` carries the declarations the returned source is correct only
|
|
52
|
+
BESIDE — array shapes derived from the assembly rather than read from `symbols`. A caller that
|
|
53
|
+
publishes the source alone publishes a spelling whose meaning it has not stated, so render them
|
|
54
|
+
with it.
|
|
55
|
+
|
|
56
|
+
Targets: `ARMV4T_AGBCC`, `MIPS_IDO`, `MIPS_GCC`, `PPC_MWCC` (`@asmlift/core/target`). A toolchain
|
|
57
|
+
id plus the flags its build compiles the function with resolves to one through
|
|
58
|
+
`targetFor(toolchain, cflags)`, which returns that description alongside the `CodegenProfile` the
|
|
59
|
+
flags describe — optimisation level, debug info, and the words the flag table does not name.
|
|
60
|
+
`TOOLCHAIN_TARGETS` is the registry it reads, and `canonicalFlagsOf` gives a toolchain's default
|
|
61
|
+
set where it has one.
|
|
47
62
|
|
|
48
63
|
### Other entry points
|
|
49
64
|
|
|
@@ -58,8 +73,8 @@ Everything under `src/` is importable as `@asmlift/core/<path>` (e.g.
|
|
|
58
73
|
|
|
59
74
|
## Architecture
|
|
60
75
|
|
|
61
|
-
Three ISA frontends (ARMv4T/Thumb, MIPS, PowerPC),
|
|
62
|
-
|
|
76
|
+
Three ISA frontends (ARMv4T/Thumb, MIPS, PowerPC), four compiler families (`agbcc`, `ido`, `gcc`,
|
|
77
|
+
`mwcc` — `codegen-flags.ts`'s `FlagFamily`) over seven target keys, three language backends over one neutral AST — all scored across the package seam
|
|
63
78
|
by [`@asmlift/cli`](../cli/README.md) with the community `objdiff` engine (in-process, pinned
|
|
64
79
|
`objdiff-wasm`; asmlift never hand-rolls a diff).
|
|
65
80
|
|
|
@@ -79,20 +94,24 @@ injected via hooks, never copied. `verify()` runs after every IR-mutating pass;
|
|
|
79
94
|
|
|
80
95
|
### Modules
|
|
81
96
|
|
|
82
|
-
| Module
|
|
83
|
-
|
|
|
84
|
-
| `ir
|
|
85
|
-
| `frontend/{thumb,mips,ppc}.ts`
|
|
86
|
-
| `pattern/engine.ts`
|
|
87
|
-
| `raise/*.ts`
|
|
88
|
-
| `structure/*.ts`
|
|
89
|
-
| `l3/*.ts`
|
|
90
|
-
| `backend/{c,cpp,cfamily,pascal}.ts`
|
|
91
|
-
| `pipeline.ts`
|
|
92
|
-
| `trace.ts`
|
|
93
|
-
| `rank.ts`
|
|
94
|
-
| `
|
|
95
|
-
| `
|
|
97
|
+
| Module | What it is |
|
|
98
|
+
| ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
99
|
+
| `ir/*.ts` | MLIR-lite substrate (`types`, `core`, `opcodes`, `print`, `parse`, `verify`, plus `alias.ts`'s alias predicate, `simplify.ts`, `bits.ts` and `struct-names.ts`): CFG of blocks + typed **block-arguments**, the typed opcode registry (`Opcode`, the one `effects` table DCE and hoist guards derive from), printer + parser (round-trip for L1/scalar types — see the domain note in parse.ts), verifier (arity/attrs/terminators/SSA dominance, located errors); `core.ts` also owns the CFG facts every level reads — `successorsOf`, `predecessors`, `dominators` |
|
|
100
|
+
| `frontend/{thumb,mips,ppc}.ts` | ISA frontends: decode → CFG → L1 with **Braun-2013 block-arg SSA** (`ssa.ts`), incl. loops, calls (signature-driven arity), memory, jump tables. Shared scaffolding: `disasm.ts` (objdump parsing), `splat.ts` (Splat-dialect MIPS → objdump-shaped instrs), `format.ts` (input-format classification), `emit.ts` (per-block emitter kit + `switch_br`), `opaque.ts` (the unmodelled-op → loud-`opaque` contract), `errors.ts` (`FrontendUnsupportedError`; PPC's subclass), `registry.ts`, `asmdata.ts` (Regime-B jump-table side-table), `high-half.ts` (a relocated address's high half is a link-time placeholder, never a value), `reloc-symbol.ts` (the per-KIND policy deciding which linker names C can spell), `stackargs.ts` (the outgoing stack-argument fixpoint, over digested slot events rather than instructions) |
|
|
101
|
+
| `pattern/engine.ts` | Idiom layer: **rewrite patterns as data** + greedy driver + DCE; `patternApplies` gates on Target capabilities |
|
|
102
|
+
| `raise/*.ts` | The pre-recovery recognizers, in ONE ordered list — `PRE_RECOVERY_PASSES` in `pre-recovery.ts` is the source of truth for the order, and reads: address-numbering (`gvn.ts`) → const-materialize → magic-division (`magicdiv.ts`, Hacker's Delight inverse) → pow2-division (`divpow2.ts`) → soft-division → scaled-extension (`extscale.ts`) → array-legalize → struct-array → member-array (`memberarrays.ts`, a constant offset feeding a variable-index walk is a struct's ARRAY member) → struct-pointer → short-circuit → branch-short-circuit → narrow-reads (`narrow.ts`) → narrow-local (`narrowlocal.ts`, a carrier read only through its own extension IS that width) → parameter-width (`paramwidth.ts`, a parameter extended in the prologue is DECLARED at that width) → scaled-extension-restore; plus `recover.ts` (L1→L2 type recovery), `retsink.ts` + `tailsink.ts` (return- and tail-sinking), `latch.ts` (empty-latch folding), `globalshape.ts` (which derived shapes the source is only correct beside), `errors.ts` (`RaiseUnsupportedError`) |
|
|
103
|
+
| `structure/*.ts` | L2→L3: `loops.ts` (natural-loop discovery, over `ir/core.ts`'s dominators), `analysis.ts` (use registry, liveness, C4 materialization), `switch-recover.ts` (Regime-A comparison-tree recovery), `hazards.ts` (the checks a loop emitter runs before committing to a loop form), `namecoalesce.ts` (copy coalescing over the interference graph — the `/merge-names` variation), `structure.ts` (SSA-destruction coalescing with interference checks + emission: if/while/do-while/for/switch, break/early-return), `retspell.ts` (which `return;` the source wrote, read off the epilogue's in-edges), `bitfields.ts` + `globalaccess.ts` (the map-driven bitfield and global-access spellings, both refusal machines) |
|
|
104
|
+
| `l3/*.ts` | `ast.ts`: language-**neutral** structured AST, the one traversal vocabulary (`exprChildren` etc.), and the `LanguageBackend` seam. Post-structure passes `dce.ts` + `basecse.ts`, the shared hoist mechanism `hoist.ts`, the differ-ranked respell variations (`regspell.ts`, `reindex.ts`, `argbase.ts`, `scopebase.ts`, `argcopy.ts` — five of the 31 the registry holds; a module may mint more than one, as `scopebase.ts` does with `/scopebase` and `/regionbase`), the spelling pass `tailret.ts` (drop a void `return;` `structure/retspell.ts` marked unspelled), and `typing.ts` (the rendered-expression C type the backends and contracts share) |
|
|
105
|
+
| `backend/{c,cpp,cfamily,pascal}.ts` | Three backends: C and C++ (CodeWarrior mangling via `mangle.ts`) over the shared `cfamily.ts` substrate, and Pascal (`:=`, `div`, tail-position returns; unspellable constructs throw) |
|
|
106
|
+
| `pipeline.ts` | `decompile()` + the shared tower spine + annotate-mode stubs/diagnostics |
|
|
107
|
+
| `trace.ts` | `decompileTraced` — the traced tower (per-stage IR dumps + pattern before/after events), browser-pure; @asmlift/cli's `report.ts` enriches it with objdiff scores/candidates, the playground's Pipeline tab renders it directly |
|
|
108
|
+
| `rank.ts` | Pure candidate enumeration + `rankBy` (an injected score function ranks). @asmlift/cli's differ ranks through `rankBy`; the playground's wasm scorer consumes the same enumeration with its own async loop |
|
|
109
|
+
| `rank-variations.ts`, `rank-declare.ts` | The two halves `rank.ts` walks rather than hand-codes: the enumeration TABLES (declaration order is published behaviour — it breaks a score tie), and the declarations a candidate needs to compile outside the project's headers, including which names one must REFUSE to claim |
|
|
110
|
+
| `variation-{tokens,definitions,gates}.ts` | The variation REGISTRY as data: every variation a candidate's name can carry, what each one MEANS for a reader, and the admission tables a definition names. Keyed by the registry in both directions, so a variation with no definition and a definition naming no variation are each a type error |
|
|
111
|
+
| `codegen-flags.ts` | Compiler flags parsed into the codegen profile a function was compiled at — each option that can change what the compiler emits, as a named slot. One flag set reaches both the compile and the decompile |
|
|
112
|
+
| `symbols.ts`, `declare.ts`, `macros.ts` | The address→symbol map seam (core consumes the VALUE; the providers that read files live in `@asmlift/cli`), the declaration block a self-declaring candidate carries, and the address-cast macro spelling — a `#define` emits a NUMERIC pool word where an `extern` emits a relocated one, so the two are not interchangeable in the bytes |
|
|
113
|
+
| `target.ts` | `TargetDescription` (ABI + capabilities + compilerBehaviors as data — no `arch ==` in shared code); toolchain paths live in `@asmlift/toolchains` |
|
|
114
|
+
| `contracts.ts`, `proto.ts`, `mangle.ts` | Boundary contracts; prototype tables; the CodeWarrior mangler |
|
|
96
115
|
|
|
97
116
|
Scoring and ranking live across the package seam in [`@asmlift/cli`](../cli/README.md):
|
|
98
117
|
`score.ts` + `objdiff.ts` (toolchain compiles → in-process pinned `objdiff-wasm`, fail-closed)
|
|
@@ -103,12 +122,16 @@ and `rank.ts` (ranked type candidates re-ranked by the differ).
|
|
|
103
122
|
Recovered today: straight-line, if/else diamonds, natural loops (`while` / `do-while` / `for`,
|
|
104
123
|
properly nested, in-body `break`/early-`return`), comparison-tree and jump-table switches,
|
|
105
124
|
direct calls, constant-offset and variable-index memory (`*p`, `p[n]`, `a[i]`, struct fields),
|
|
106
|
-
magic-number and soft division, short-circuit booleans, width casts
|
|
125
|
+
magic-number and soft division, short-circuit booleans, width casts, PowerPC small-data access
|
|
126
|
+
through its `R_PPC_EMB_SDA21` relocation, a call's outgoing stack arguments on agbcc, where the
|
|
127
|
+
declaration and the code agree (`stagesOutgoingArgsInFrame`; MIPS still declines the same shape),
|
|
128
|
+
and a MIPS likely branch's nullified delay slot. Still DECLINED (loud, never
|
|
107
129
|
wrong code): **local stack frames** (address-taken locals / sp-as-data; MIPS models word `sp`
|
|
108
130
|
slots and PPC elides callee-saved save slots, so a spill/reload pair is modelled on those two —
|
|
109
131
|
anything the narrow models cannot honour declines),
|
|
110
132
|
**cross-block condition flags** on PPC (a `cmpw` whose branch lands in another block — the
|
|
111
|
-
capability gap behind the mwcc switch stubs), computed tail calls, PIC
|
|
133
|
+
capability gap behind the mwcc switch stubs), computed tail calls, PIC and `gp`-relative access outside PowerPC's
|
|
134
|
+
small-data relocation — MIPS refuses `R_MIPS_GOT16`/`R_MIPS_CALL16`/`R_MIPS_GPREL16` alike —
|
|
112
135
|
switch fall-through, multi-latch/irreducible loops, floats, and 64-bit memory ops. Prototypes
|
|
113
136
|
(callee arities, void-ness) come from a caller-supplied map, as a real project takes them from
|
|
114
137
|
headers.
|
|
@@ -130,9 +153,10 @@ Landmarks (not exhaustive — suites are named for what they pin):
|
|
|
130
153
|
that keeps every already-matching function matching. How to add a fixture — and when a
|
|
131
154
|
matching test is the right tool at all, vs a benchmark row — is under
|
|
132
155
|
[`@asmlift/cli` › Tests](../cli/CONTRIBUTION.md#tests).
|
|
133
|
-
- `contract-invariant
|
|
134
|
-
-
|
|
135
|
-
|
|
156
|
+
- `contract-invariant`, and `contracts` (in `../cli/test/matching/`) — the loud-fail contract,
|
|
157
|
+
mutation-proven.
|
|
158
|
+
- `structure-guard`, and `structure-soundness` / `audit-regression` (both in
|
|
159
|
+
`../cli/test/matching/`) — the adversarial-audit repro locks.
|
|
136
160
|
- `one-version` — a WORKSPACE-scope invariant, like `offline-list`: every dependency that two or
|
|
137
161
|
more workspace packages declare must be declared AND resolved at one version. It exists because
|
|
138
162
|
`packages/cli` scored with `objdiff-wasm` 3.7.3 while `apps/web` scored with 3.7.0, so the
|
package/package.json
CHANGED
package/src/backend/cfamily.ts
CHANGED
|
@@ -80,10 +80,18 @@ export function cType(t: IrType): string {
|
|
|
80
80
|
|
|
81
81
|
/** Declare a name of a given type, C declarator rules: an array puts its length AFTER the name
|
|
82
82
|
* (`u8 _pad[4]`), a pointer binds its `*` to the declarator (`void *p`), everything else is
|
|
83
|
-
* the prefix `cType name`.
|
|
83
|
+
* the prefix `cType name`. A NESTED array spells every extent after the name in declaration
|
|
84
|
+
* order (`u8 unk8[6][8]`) — one declarator, not an element type that is itself an array, which
|
|
85
|
+
* C has no syntax for and `cType` marks ill-formed as a prefix. */
|
|
84
86
|
function cDeclare(t: IrType, name: string): string {
|
|
85
87
|
if (t.kind === 'array') {
|
|
86
|
-
|
|
88
|
+
const extents: number[] = [];
|
|
89
|
+
let e: IrType = t;
|
|
90
|
+
while (e.kind === 'array') {
|
|
91
|
+
extents.push(e.count);
|
|
92
|
+
e = e.elem;
|
|
93
|
+
}
|
|
94
|
+
return `${cType(e)} ${name}${extents.map((n) => `[${n}]`).join('')}`;
|
|
87
95
|
}
|
|
88
96
|
if (t.kind === 'ptr') {
|
|
89
97
|
return `${cType(t.to)} *${name}`;
|
|
@@ -136,7 +144,19 @@ export type LeafHook = (e: Expr, rec: (e: Expr, p: number) => string) => string
|
|
|
136
144
|
* -fprologue-bugfix`), and referring to a volatile object through a non-volatile lvalue is
|
|
137
145
|
* undefined behaviour (C99 6.7.3p5). */
|
|
138
146
|
function legalizedIndexBase(ix: Extract<Expr, { k: 'index' }>, vt: PrintEnv): Expr {
|
|
139
|
-
|
|
147
|
+
// `baseElem` states the element type the base's own DECLARATION gives it, where the type walk
|
|
148
|
+
// cannot reconstruct one (a map-declared array MEMBER — see l3/ast.ts). It is a FALLBACK, not an
|
|
149
|
+
// override, and the order is the whole guard: the walk reads the tree in front of it, so
|
|
150
|
+
// wherever it answers at all it is the better answer and the stated one is not consulted.
|
|
151
|
+
// Inverted (`declared ?? walked`), a stale statement would stand — `derefStrideOk` cannot catch
|
|
152
|
+
// one, because it tests the STATED type against the access width and never against the base.
|
|
153
|
+
// No pass carries a statement onto a foreign base today (each refuses a `field` base by its own
|
|
154
|
+
// predicate: basecse `isHoistableBase`, scopebase/argbase `eligible`, nearbase's untouched
|
|
155
|
+
// `field` subtree, reindex's `base.k === 'var'`), but `mapExprChildren` spreads the field across
|
|
156
|
+
// an arbitrary base substitution, so that is five accidents rather than a check. This ordering
|
|
157
|
+
// is the check. Measured: zero test moves either way.
|
|
158
|
+
const declared = ix.baseElem !== undefined ? T.ptr(ix.baseElem) : undefined;
|
|
159
|
+
return derefStrideOk(exprCType(ix.base, vt.type) ?? declared, ix.width, ix.signed)
|
|
140
160
|
? ix.base
|
|
141
161
|
: {
|
|
142
162
|
k: 'cast',
|
|
@@ -226,11 +246,19 @@ function printExpr(e: Expr, parentPrec: number, vt: PrintEnv, leaf?: LeafHook):
|
|
|
226
246
|
// Leading constant subscripts (a multidimensional array global's bare spelling) keep the
|
|
227
247
|
// postfix form whatever `idx` is: `g[0][0]` is the element, `*g[0]` would be its ROW.
|
|
228
248
|
//
|
|
229
|
-
// `lead` implies the base already strides the access width
|
|
230
|
-
// matching element type for the
|
|
231
|
-
//
|
|
232
|
-
//
|
|
233
|
-
//
|
|
249
|
+
// `lead` implies the base already strides the access width, and its producers reach that by
|
|
250
|
+
// TWO different mechanisms. The two GLOBAL ones register a matching element type for the
|
|
251
|
+
// global (structure/globalaccess.ts `bareArrayLead` and `declaredSubscripts`, both through
|
|
252
|
+
// structure.ts's `noteGlobal`, so the type walk finds it); the MEMBER one
|
|
253
|
+
// (structure.ts `pointeeElement`, `gPtr->grid[0][i]`) has no global to register and states
|
|
254
|
+
// the element type on the node instead (`baseElem`). Nothing else enforced either, and the
|
|
255
|
+
// failure would be quiet-ish: legalization would wrap the base, spelling `((u16 *)g)[0][i]`,
|
|
256
|
+
// which subscripts a `u16` twice. Check it rather than assume.
|
|
257
|
+
//
|
|
258
|
+
// This throw covers rank >= 2 ONLY, because only a rank >= 2 access carries `lead`. A rank-1
|
|
259
|
+
// member (the large majority of the corpus's dims-carrying members) has no guard here: a
|
|
260
|
+
// missing or stale `baseElem` there degrades silently to the cast form the rule replaces,
|
|
261
|
+
// which is a lost spelling rather than a wrong address.
|
|
234
262
|
if (e.lead && e.lead.length > 0) {
|
|
235
263
|
if (base !== e.base) {
|
|
236
264
|
throw new Error(
|
|
@@ -396,8 +424,8 @@ function printSwitchStmt(s: Extract<Stmt, { k: 'switch' }>, indent: string, vt:
|
|
|
396
424
|
// arm first, loudly), so it is a contract on the next one.
|
|
397
425
|
//
|
|
398
426
|
// `continue;` is deliberately NOT refused: C binds it to the smallest enclosing ITERATION
|
|
399
|
-
// statement, which a `switch` is not, so it already means what L3 means. What a
|
|
400
|
-
//
|
|
427
|
+
// statement, which a `switch` is not, so it already means what L3 means. What a pass that re-spells
|
|
428
|
+
// a loop must preserve is exactly that — the three that can re-spell a loop into one whose
|
|
401
429
|
// `continue` would run a different increment (`recognizeForLoops` in structure.ts,
|
|
402
430
|
// `respellCountdown` in l3/reindex.ts, and l3/unreduce.ts) each scan switch arms for the node
|
|
403
431
|
// before firing, and a fourth must too.
|
|
@@ -539,7 +567,7 @@ function cFamilyBody(fn0: SFn, leaf?: LeafHook): string[] {
|
|
|
539
567
|
// differently: on a scalar the qualifier binds to the object (`volatile u16 sp0`), on a
|
|
540
568
|
// pointer declarator to the pointee — the INNERMOST one for a multi-level pointer
|
|
541
569
|
// (`volatile u16 ** p`). An object-volatile POINTER (`u16 *volatile p`) has no inhabitant —
|
|
542
|
-
// no
|
|
570
|
+
// no variation or recognizer produces one.
|
|
543
571
|
lines.push(` ${l.volatile || l.pointeeVolatile ? 'volatile ' : ''}${cType(l.type)} ${l.name};`);
|
|
544
572
|
}
|
|
545
573
|
for (const s of fn.body) {
|
package/src/backend/pascal.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// (`rshift` is arithmetic on a signed Integer → `sra`.) This is the concrete difference from
|
|
10
10
|
// Turbo/Delphi/FreePascal.
|
|
11
11
|
import { IrType, typeToString } from '../ir/types';
|
|
12
|
-
import { BinOp, Expr, LanguageBackend, SFn, Stmt } from '../l3/ast';
|
|
12
|
+
import { BinOp, Expr, LanguageBackend, SFn, Stmt, isLoop } from '../l3/ast';
|
|
13
13
|
import { orderSlotLocals } from '../l3/slotorder';
|
|
14
14
|
import { type VarTypes, declaredTypes, derefStrideOk, exprCType, writesNonPointerIntoPointer } from '../l3/typing';
|
|
15
15
|
|
|
@@ -240,7 +240,7 @@ function makePrinter(vt: VarTypes) {
|
|
|
240
240
|
(st) =>
|
|
241
241
|
st.k === 'return' ||
|
|
242
242
|
(st.k === 'if' && (hasReturn(st.then) || hasReturn(st.else))) ||
|
|
243
|
-
((st
|
|
243
|
+
(isLoop(st) && hasReturn(st.body)) ||
|
|
244
244
|
(st.k === 'switch' && (st.cases.some((c) => hasReturn(c.body)) || hasReturn(st.default ?? []))),
|
|
245
245
|
);
|
|
246
246
|
if (s.cases.some((c) => hasReturn(c.body)) || hasReturn(s.default ?? [])) {
|