@asmlift/core 0.9.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/README.md +25 -24
- package/package.json +1 -1
- package/src/aggregate.ts +149 -0
- package/src/backend/c.ts +2 -0
- package/src/backend/cfamily.ts +197 -61
- package/src/backend/cpp.ts +3 -1
- package/src/backend/lines.ts +10 -0
- package/src/backend/pascal.ts +47 -16
- package/src/cdecl/parse.ts +21 -0
- package/src/contracts.ts +246 -36
- package/src/declare.ts +69 -20
- package/src/frontend/arm-body.ts +155 -0
- package/src/frontend/asmdata.ts +9 -2
- package/src/frontend/call-plan.ts +1117 -61
- package/src/frontend/callee-body.ts +370 -0
- package/src/frontend/dead-reads.ts +628 -0
- package/src/frontend/device-pins.ts +33 -4
- package/src/frontend/emit.ts +4 -3
- package/src/frontend/fpu.ts +224 -17
- package/src/frontend/frame-objects.ts +1689 -206
- package/src/frontend/frontend.ts +84 -3
- package/src/frontend/high-half.ts +53 -4
- package/src/frontend/jump-table.ts +545 -0
- package/src/frontend/local-object.ts +158 -32
- package/src/frontend/mips.ts +2296 -217
- package/src/frontend/own-struct-return.ts +500 -0
- package/src/frontend/ppc.ts +2748 -245
- package/src/frontend/register-struct-call.ts +399 -0
- package/src/frontend/reloc-symbol.ts +22 -7
- package/src/frontend/ssa.ts +1721 -43
- package/src/frontend/stackargs.ts +199 -72
- package/src/frontend/thumb-entry.ts +340 -0
- package/src/frontend/thumb.ts +2400 -325
- package/src/ir/alias.ts +65 -2
- package/src/ir/bits.ts +1699 -4
- package/src/ir/core.ts +254 -9
- package/src/ir/discipline.ts +86 -21
- package/src/ir/entry.ts +157 -0
- package/src/{structure → ir}/loops.ts +6 -2
- package/src/ir/opcodes.ts +137 -9
- package/src/ir/parse.ts +1 -0
- package/src/ir/simplify.ts +61 -10
- package/src/ir/types.ts +86 -2
- package/src/ir/verify.ts +96 -11
- package/src/l3/argbase.ts +2 -0
- package/src/l3/ast.ts +582 -18
- package/src/l3/basecse.ts +68 -15
- package/src/l3/caseendgoto.ts +223 -0
- package/src/l3/coalesce.ts +42 -14
- package/src/l3/constinit.ts +410 -0
- package/src/l3/dce.ts +53 -9
- package/src/l3/initfirst.ts +42 -11
- package/src/l3/intcell.ts +2 -1
- package/src/l3/narrowdecl.ts +77 -36
- package/src/l3/pollguard.ts +8 -2
- package/src/l3/ptrcell.ts +33 -34
- package/src/l3/ptrfield.ts +123 -8
- package/src/l3/regspell.ts +11 -4
- package/src/l3/reindex.ts +295 -99
- package/src/l3/scopebase.ts +2 -0
- package/src/l3/storage.ts +98 -1
- package/src/l3/symbol-refs.ts +84 -6
- package/src/l3/tailmerge.ts +6 -2
- package/src/l3/typing.ts +108 -15
- package/src/l3/unreduce.ts +6 -5
- package/src/l3/volatileptr.ts +17 -54
- package/src/mangle.ts +30 -0
- package/src/pattern/engine.ts +361 -73
- package/src/pipeline.ts +1039 -38
- package/src/proto-context.ts +1039 -45
- package/src/proto.ts +555 -40
- package/src/raise/aggmembers.ts +290 -0
- package/src/raise/arrays.ts +5 -1
- package/src/raise/const.ts +25 -0
- package/src/raise/declared-volatile.ts +207 -31
- package/src/raise/divpow2.ts +192 -0
- package/src/raise/floathelpers.ts +254 -33
- package/src/raise/gvn.ts +3 -3
- package/src/raise/latch.ts +162 -6
- package/src/raise/memberarrays.ts +10 -4
- package/src/raise/offsetnames.ts +422 -36
- package/src/raise/pairparams.ts +33 -0
- package/src/raise/paramwidth.ts +196 -6
- package/src/raise/pre-recovery.ts +107 -21
- package/src/raise/pre-structure.ts +73 -0
- package/src/raise/recover.ts +12 -5
- package/src/raise/reroll.ts +674 -0
- package/src/raise/shortcircuit.ts +217 -34
- package/src/raise/softdiv.ts +1 -1
- package/src/raise/storesign.ts +153 -0
- package/src/raise/struct-arrays.ts +30 -11
- package/src/raise/structs.ts +396 -33
- package/src/raise/truncload.ts +4 -3
- package/src/raise/unpre.ts +594 -0
- package/src/raise/unthread.ts +400 -0
- package/src/rank-declare.ts +118 -12
- package/src/rank-variations.ts +186 -35
- package/src/rank.ts +536 -68
- package/src/runtime-helpers.ts +140 -22
- package/src/structure/analysis.ts +217 -8
- package/src/structure/arith-ops.ts +23 -0
- package/src/structure/cross-jumped-tail.ts +216 -0
- package/src/structure/globalaccess.ts +157 -10
- package/src/structure/hazards.ts +16 -64
- package/src/structure/local-statics.ts +145 -9
- package/src/structure/namecoalesce.ts +1 -1
- package/src/structure/pointer-spelling.ts +1106 -0
- package/src/structure/redundant-test.ts +1 -1
- package/src/structure/structure.ts +6192 -1213
- package/src/structure/switch-recover.ts +583 -120
- package/src/structure/unfolded-branch-constant.ts +90 -0
- package/src/symbols.ts +58 -15
- package/src/target.ts +1028 -54
- package/src/trace.ts +49 -17
- package/src/variation-definitions.ts +333 -20
- package/src/variation-gates.ts +4 -0
- package/src/variation-tokens.ts +68 -3
package/README.md
CHANGED
|
@@ -94,25 +94,25 @@ injected via hooks, never copied. `verify()` runs after every IR-mutating pass;
|
|
|
94
94
|
|
|
95
95
|
### Modules
|
|
96
96
|
|
|
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` and its `effects`/`reads`/`traps` flags), `discipline.ts` (the access discipline: an op's placement — `call`, `helper`, `device`, `declared` — and the questions DCE, hoist and placement guards ask of an op instead of an opcode set), 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), `frame-objects.ts` (the ISA-neutral audit of every address-taken frame object, over the frame partition a frontend measured), `device-pins.ts` (the device pin that audit answers: which device-register accesses must run where the machine ran them, per `DEVICE_PIN_POLICIES` row)
|
|
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), `declared-volatile.ts` (the `declared` stamp on
|
|
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`, `narrowdecl.ts`, six of the modules behind the registry in `variation-tokens.ts`; a module may mint more than one, as `scopebase.ts` does with `/scopebase` and `/regionbase` and `narrowdecl.ts` with `/narrow-decl` and `/narrow-read`), 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). Every driver ranks through `rankBy`: the sync driver (@asmlift/cli's `decompileRanked`, which the benchmark and `bench fan` run) scores inside it, and the two async drivers (the CLI's pooled ranker, the playground's wasm scorer) compile on their own schedule, memoize the outcomes and hand `rankBy` the memo
|
|
109
|
-
| `stillborn.ts`, `compiler-diagnostics.ts` | The stillborn-fan rule, one copy for every driver: when the default candidate is rejected, each signedness half's default and one probe per variation name in that half are compiled, and only a fan whose every half's default keeps an error through every probe of its half, in every attempt, is left uncompiled — the rest reported as NOT COMPILED, never as dropped. `compiler-diagnostics.ts` is how a rejection is read: `CompilerRejection` (a compile that ran to completion and said no, as distinct from a transient), the error lines of each compiler's output with positions normalised away, the attempts of a rejection that holds several compiles (`joinAttempts`/`attemptsOf`), and `errorsFirst` for whatever bounds a diagnostic downstream
|
|
110
|
-
| `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
|
|
111
|
-
| `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
|
|
112
|
-
| `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
|
|
113
|
-
| `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
|
|
114
|
-
| `target.ts` | `TargetDescription` (ABI + capabilities + compilerBehaviors as data — no `arch ==` in shared code); toolchain paths live in `@asmlift/toolchains`
|
|
115
|
-
| `contracts.ts`, `proto.ts`, `mangle.ts` | Boundary contracts; prototype tables; the CodeWarrior mangler
|
|
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` and its `effects`/`reads`/`traps` flags), `discipline.ts` (the access discipline: an op's placement — `call`, `helper`, `device`, `declared` — and the questions DCE, hoist and placement guards ask of an op instead of an opcode set), 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), `frame-objects.ts` (the ISA-neutral audit of every address-taken frame object, over the frame partition a frontend measured), `device-pins.ts` (the device pin that audit answers: which device-register accesses must run where the machine ran them, per `DEVICE_PIN_POLICIES` row) |
|
|
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), `declared-volatile.ts` (the `declared` stamp on an access of an object the symbol map or the declaration context declares volatile, or through a pointer the context declares to point at a volatile), `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`, `narrowdecl.ts`, six of the modules behind the registry in `variation-tokens.ts`; a module may mint more than one, as `scopebase.ts` does with `/scopebase` and `/regionbase` and `narrowdecl.ts` with `/narrow-decl` and `/narrow-read`), 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). Every driver ranks through `rankBy`: the sync driver (@asmlift/cli's `decompileRanked`, which the benchmark and `bench fan` run) scores inside it, and the two async drivers (the CLI's pooled ranker, the playground's wasm scorer) compile on their own schedule, memoize the outcomes and hand `rankBy` the memo |
|
|
109
|
+
| `stillborn.ts`, `compiler-diagnostics.ts` | The stillborn-fan rule, one copy for every driver: when the default candidate is rejected, each signedness half's default and one probe per variation name in that half are compiled, and only a fan whose every half's default keeps an error through every probe of its half, in every attempt, is left uncompiled — the rest reported as NOT COMPILED, never as dropped. `compiler-diagnostics.ts` is how a rejection is read: `CompilerRejection` (a compile that ran to completion and said no, as distinct from a transient), the error lines of each compiler's output with positions normalised away, the attempts of a rejection that holds several compiles (`joinAttempts`/`attemptsOf`), and `errorsFirst` for whatever bounds a diagnostic downstream |
|
|
110
|
+
| `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 |
|
|
111
|
+
| `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 |
|
|
112
|
+
| `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 |
|
|
113
|
+
| `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 |
|
|
114
|
+
| `target.ts` | `TargetDescription` (ABI + capabilities + compilerBehaviors as data — no `arch ==` in shared code); toolchain paths live in `@asmlift/toolchains` |
|
|
115
|
+
| `contracts.ts`, `proto.ts`, `mangle.ts` | Boundary contracts; prototype tables; the CodeWarrior mangler |
|
|
116
116
|
|
|
117
117
|
Scoring and ranking live across the package seam in [`@asmlift/cli`](../cli/README.md):
|
|
118
118
|
`score.ts` (toolchain compiles → `@match-kit/scoring`, the in-process pinned objdiff engine, fail-closed)
|
|
@@ -125,14 +125,15 @@ properly nested, in-body `break`/early-`return`), comparison-tree and jump-table
|
|
|
125
125
|
direct calls, constant-offset and variable-index memory (`*p`, `p[n]`, `a[i]`, struct fields),
|
|
126
126
|
magic-number and soft division, short-circuit booleans, width casts, PowerPC small-data access
|
|
127
127
|
through its `R_PPC_EMB_SDA21` relocation, a call's outgoing stack arguments on agbcc, where the
|
|
128
|
-
declaration and the code agree (`stagesOutgoingArgsInFrame`; MIPS
|
|
128
|
+
declaration and the code agree (`stagesOutgoingArgsInFrame`; MIPS lifts a declared callee's too),
|
|
129
129
|
a compare read by a branch in a later block that only its own block enters (Thumb and
|
|
130
130
|
PowerPC, `frontend/flags-edge.ts`), and a MIPS likely branch's nullified delay slot. Still DECLINED (loud,
|
|
131
|
-
never wrong code): **local stack frames** beyond what the models prove (Thumb
|
|
131
|
+
never wrong code): **local stack frames** beyond what the models prove (Thumb, MIPS and PPC lift an
|
|
132
132
|
address-taken local the frame-object audit can bound — a scalar, or an untyped buffer that fills
|
|
133
|
-
the frame — and
|
|
134
|
-
|
|
135
|
-
|
|
133
|
+
the frame — and decline the rest, MIPS inside the O32 local area and PPC inside mwcc's local area
|
|
134
|
+
that its compiler's measured layout draws; MIPS and PPC also read an incoming stack word as a
|
|
135
|
+
parameter, MIPS models word `sp` slots and PPC elides callee-saved save slots, so a spill/reload
|
|
136
|
+
pair is modelled on those two — anything the narrow models cannot honour declines),
|
|
136
137
|
**condition flags at a join** (a compare two or more edges bring to one branch, or one the caller
|
|
137
138
|
set), computed tail calls, PIC and `gp`-relative access outside PowerPC's
|
|
138
139
|
small-data relocation — MIPS refuses `R_MIPS_GOT16`/`R_MIPS_CALL16`/`R_MIPS_GPREL16` alike —
|
package/package.json
CHANGED
package/src/aggregate.ts
CHANGED
|
@@ -9,9 +9,14 @@ import {
|
|
|
9
9
|
type FnProto,
|
|
10
10
|
type Prototypes,
|
|
11
11
|
STANDARD_SIGNATURES,
|
|
12
|
+
declaredCallArgs,
|
|
12
13
|
declaredWidth,
|
|
13
14
|
declaresAggregateReturn,
|
|
14
15
|
declaresVoidReturn,
|
|
16
|
+
ownArgumentWords,
|
|
17
|
+
ownLeadingWordParams,
|
|
18
|
+
paramClassOf,
|
|
19
|
+
wordsOf,
|
|
15
20
|
} from './proto';
|
|
16
21
|
import type { TargetDescription } from './target';
|
|
17
22
|
|
|
@@ -151,6 +156,28 @@ export function returnedAggregate(p: FnProto): AggregateLayout {
|
|
|
151
156
|
return p.returnLayout ?? { kind: /\bunion\b/.test(p.returns ?? '') ? 'union' : 'struct' };
|
|
152
157
|
}
|
|
153
158
|
|
|
159
|
+
/** THE ARGUMENT WORDS A FUNCTION'S OWN DECLARATION FILLS (`proto.ts` `ownArgumentWords`), with the
|
|
160
|
+
* hidden pointer a struct return may put ahead of them decided by this target's rule: a struct
|
|
161
|
+
* `returnsInMemory` answers `false` for comes back in registers and takes no argument word, as a
|
|
162
|
+
* call to it does (frontend/call-plan.ts `returnedStruct`). One the rule cannot place still counts
|
|
163
|
+
* the word, since the bound must not fall short. */
|
|
164
|
+
export function ownArgumentWordsOn(
|
|
165
|
+
name: string,
|
|
166
|
+
p: FnProto | undefined,
|
|
167
|
+
target: TargetDescription,
|
|
168
|
+
): number | undefined {
|
|
169
|
+
const registerReturn = declaresAggregateReturn(p) && returnsInMemory(returnedAggregate(p!), target) === false;
|
|
170
|
+
return ownArgumentWords(name, p, target, registerReturn);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** The first argument registers that surely hold one of the function's own word parameters
|
|
174
|
+
* (`proto.ts` `ownLeadingWordParams`), with the hidden pointer decided by this target's rule as
|
|
175
|
+
* {@link ownArgumentWordsOn} decides it. */
|
|
176
|
+
export function ownLeadingWordParamsOn(name: string, p: FnProto | undefined, target: TargetDescription): number {
|
|
177
|
+
const registerReturn = declaresAggregateReturn(p) && returnsInMemory(returnedAggregate(p!), target) === false;
|
|
178
|
+
return ownLeadingWordParams(name, p, target, registerReturn);
|
|
179
|
+
}
|
|
180
|
+
|
|
154
181
|
/** Whether a function declared to return `layout` by value hands it back through memory — so its
|
|
155
182
|
* caller passes the address as argument 0 — or `undefined` where the target states no rule or the
|
|
156
183
|
* rule needs a size the layout does not give. */
|
|
@@ -159,6 +186,9 @@ export function returnsInMemory(layout: AggregateLayout, target: TargetDescripti
|
|
|
159
186
|
if (rule === undefined) {
|
|
160
187
|
return undefined;
|
|
161
188
|
}
|
|
189
|
+
if (rule === 'memory') {
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
162
192
|
if (rule === 'svr4') {
|
|
163
193
|
const size = aggregateSize(layout, target)?.size;
|
|
164
194
|
return size === undefined ? undefined : size > 8;
|
|
@@ -246,3 +276,122 @@ export function returnsWithoutHiddenPointer(
|
|
|
246
276
|
}
|
|
247
277
|
return Object.hasOwn(STANDARD_SIGNATURES, callee) && travelsInRegisters(STANDARD_SIGNATURES[callee].returns);
|
|
248
278
|
}
|
|
279
|
+
|
|
280
|
+
/** One struct a call passes by value in argument words (`byValueCallArgs`). */
|
|
281
|
+
export interface ByValueArg {
|
|
282
|
+
/** the parameter's index in the declared list */
|
|
283
|
+
readonly param: number;
|
|
284
|
+
/** its first argument word, among the call's widths */
|
|
285
|
+
readonly word: number;
|
|
286
|
+
/** how many argument words it fills */
|
|
287
|
+
readonly words: number;
|
|
288
|
+
/** the struct as the IR types it (`aggregateType`) */
|
|
289
|
+
readonly type: IrType;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/** Where a declaration's by-value structs travel (`byValueCallArgs`), or why that is not known. */
|
|
293
|
+
type ByValueLayout =
|
|
294
|
+
| { widths: readonly number[]; doubles: ReadonlySet<number>; byValue: readonly ByValueArg[] }
|
|
295
|
+
| { refused: string; param: number };
|
|
296
|
+
|
|
297
|
+
/** THE ARGUMENT WORDS OF A DECLARATION WITH A STRUCT PASSED BY VALUE, where the target passes one
|
|
298
|
+
* in consecutive argument words (`compilerBehaviors.byValueAggregateArgWords`) and the declaration
|
|
299
|
+
* lays it out (`FnProto.byValueLayouts`): each such parameter is its size in words, rounded up, of
|
|
300
|
+
* 32 bits each, and every other parameter is what `declaredCallArgs` makes of it. Undefined where
|
|
301
|
+
* any parameter is unsized that way — a union, a layout this target does not size, a spelling that
|
|
302
|
+
* names no tag — or placed where no rule here follows it (`byValueRefusal` names which), and where
|
|
303
|
+
* the declaration passes no struct by value at all, which `declaredCallArgs` answers alone. */
|
|
304
|
+
export function byValueCallArgs(
|
|
305
|
+
p: FnProto | undefined,
|
|
306
|
+
target: TargetDescription,
|
|
307
|
+
hidden = 0,
|
|
308
|
+
): { widths: readonly number[]; doubles: ReadonlySet<number>; byValue: readonly ByValueArg[] } | undefined {
|
|
309
|
+
const laid = layOutByValue(p, target, hidden);
|
|
310
|
+
return laid === undefined || 'refused' in laid ? undefined : laid;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** Why `byValueCallArgs` places none of a declaration that does pass a struct or union by value:
|
|
314
|
+
* the parameter it stops at (an index into `params`) and the reason. Undefined where it places
|
|
315
|
+
* them, or where the declaration passes none. */
|
|
316
|
+
export function byValueRefusal(
|
|
317
|
+
p: FnProto | undefined,
|
|
318
|
+
target: TargetDescription,
|
|
319
|
+
hidden = 0,
|
|
320
|
+
): { refused: string; param: number } | undefined {
|
|
321
|
+
const laid = layOutByValue(p, target, hidden);
|
|
322
|
+
return laid !== undefined && 'refused' in laid ? laid : undefined;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/** `hidden` is the argument words ahead of the declared list — 1 for a call handed a hidden
|
|
326
|
+
* struct-return pointer in argument 0 — which the even-word rule counts and `widths` leaves out. */
|
|
327
|
+
function layOutByValue(p: FnProto | undefined, target: TargetDescription, hidden: number): ByValueLayout | undefined {
|
|
328
|
+
if (!Array.isArray(p?.params)) {
|
|
329
|
+
return undefined;
|
|
330
|
+
}
|
|
331
|
+
const params = p.params;
|
|
332
|
+
const first = params.findIndex((_, i) => paramClassOf(p, params, i) === 'by-value');
|
|
333
|
+
if (first < 0) {
|
|
334
|
+
return undefined;
|
|
335
|
+
}
|
|
336
|
+
if (!target.compilerBehaviors.byValueAggregateArgWords) {
|
|
337
|
+
return { refused: 'this target states no rule for which argument words carry one', param: first };
|
|
338
|
+
}
|
|
339
|
+
const widths: number[] = [];
|
|
340
|
+
const doubles = new Set<number>();
|
|
341
|
+
const byValue: ByValueArg[] = [];
|
|
342
|
+
for (const [i, t] of params.entries()) {
|
|
343
|
+
if (paramClassOf(p, params, i) === 'by-value') {
|
|
344
|
+
const layout =
|
|
345
|
+
p.byValueLayouts !== undefined && Object.hasOwn(p.byValueLayouts, i) ? p.byValueLayouts[i] : undefined;
|
|
346
|
+
if (layout === undefined) {
|
|
347
|
+
return { refused: 'the declaration does not lay it out (a context does, by defining the struct)', param: i };
|
|
348
|
+
}
|
|
349
|
+
const spelling = t
|
|
350
|
+
.replace(/\b(?:const|volatile)\b/g, ' ')
|
|
351
|
+
.trim()
|
|
352
|
+
.replace(/\s+/g, ' ');
|
|
353
|
+
const tag = /^(?:struct\s+)?([A-Za-z_]\w*)$/.exec(spelling)?.[1];
|
|
354
|
+
const type = tag === undefined ? undefined : aggregateType(tag, spelling, layout, target);
|
|
355
|
+
if (type?.kind !== 'struct' || type.size === undefined) {
|
|
356
|
+
return { refused: 'it is a union, or has a member this target does not size', param: i };
|
|
357
|
+
}
|
|
358
|
+
// A STRUCT OF NO BYTES (GNU C's `struct E { };`) occupies no argument word, so it would be an
|
|
359
|
+
// argument with no operand to name it, and the fold that names the struct has none to read.
|
|
360
|
+
if (type.size === 0) {
|
|
361
|
+
return { refused: 'it has no members, so no argument word carries it', param: i };
|
|
362
|
+
}
|
|
363
|
+
// An 8-aligned struct starts at an even argument word where the target aligns one
|
|
364
|
+
// (`evenWordAlignedArgs`). Placed at an even word that changes nothing; placed at an odd
|
|
365
|
+
// one, the skipped word would be an argument operand nothing passes, so that one refuses.
|
|
366
|
+
// The word is counted from argument 0, a hidden struct-return pointer included: O32's
|
|
367
|
+
// `struct S8 f(s32 k, struct L l)` passes `l` in a2:a3, and `struct S8 f(struct L l)` skips a1.
|
|
368
|
+
if (
|
|
369
|
+
target.evenWordAlignedArgs === true &&
|
|
370
|
+
(aggregateSize(layout, target)?.align ?? 0) >= 8 &&
|
|
371
|
+
(hidden + wordsOf(widths, target)) % 2 === 1
|
|
372
|
+
) {
|
|
373
|
+
return {
|
|
374
|
+
refused:
|
|
375
|
+
'it is aligned to 8 and would start at an odd argument word, which this target skips — an argument word left empty is not modelled',
|
|
376
|
+
param: i,
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
const words = Math.ceil(type.size / 4);
|
|
380
|
+
byValue.push({ param: i, word: widths.length, words, type });
|
|
381
|
+
widths.push(...Array.from({ length: words }, () => 32));
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
const one = declaredCallArgs({ params: [t], wordParams: p.wordParams?.includes(i) ? [0] : [] }, target);
|
|
385
|
+
if (one === undefined) {
|
|
386
|
+
return {
|
|
387
|
+
refused: `its parameter ${i + 1} '${t}' is not sized, so where the struct starts is not known`,
|
|
388
|
+
param: first,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
if (one.doubles.size > 0) {
|
|
392
|
+
doubles.add(widths.length);
|
|
393
|
+
}
|
|
394
|
+
widths.push(...one.widths);
|
|
395
|
+
}
|
|
396
|
+
return { widths, doubles, byValue };
|
|
397
|
+
}
|
package/src/backend/c.ts
CHANGED
|
@@ -13,6 +13,8 @@ export { cComment }; // re-export: the shared spelling lives in cfamily.ts
|
|
|
13
13
|
export const cBackend: LanguageBackend = {
|
|
14
14
|
id: 'c',
|
|
15
15
|
spellsSwitchFallthrough: true,
|
|
16
|
+
spellsSwitchBreak: true,
|
|
17
|
+
spellsEarlyReturn: true,
|
|
16
18
|
emit(fn: SFn): string {
|
|
17
19
|
// The SAME declarator placement the local list and the struct-field printer use, so one
|
|
18
20
|
// function cannot spell the `*` on the type in its signature and on the declarator in its
|