@asmlift/core 0.8.1 → 0.10.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 +36 -31
- package/package.json +1 -1
- package/src/aggregate.ts +397 -0
- package/src/backend/c.ts +6 -2
- package/src/backend/cfamily.ts +370 -56
- package/src/backend/cpp.ts +116 -10
- package/src/backend/pascal.ts +69 -11
- package/src/cdecl/constant.ts +59 -0
- package/src/cdecl/lex.ts +209 -0
- package/src/cdecl/parse.ts +975 -0
- package/src/cdecl/spell.ts +69 -0
- package/src/codegen-flags.ts +38 -6
- package/src/compiler-diagnostics.ts +194 -0
- package/src/contracts.ts +777 -84
- package/src/declare.ts +132 -13
- package/src/frontend/asmdata.ts +26 -6
- package/src/frontend/call-plan.ts +1590 -0
- package/src/frontend/dead-reads.ts +628 -0
- package/src/frontend/device-pins.ts +296 -0
- package/src/frontend/emit.ts +4 -3
- package/src/frontend/errors.ts +5 -0
- package/src/frontend/flags-edge.ts +101 -0
- package/src/frontend/fpu.ts +374 -0
- package/src/frontend/frame-objects.ts +3171 -0
- package/src/frontend/frontend.ts +64 -3
- package/src/frontend/high-half.ts +55 -5
- package/src/frontend/jump-table.ts +530 -0
- package/src/frontend/local-object.ts +616 -0
- package/src/frontend/mips.ts +2392 -208
- package/src/frontend/opaque.ts +97 -18
- package/src/frontend/own-struct-return.ts +500 -0
- package/src/frontend/ppc.ts +3137 -267
- package/src/frontend/register-struct-call.ts +484 -0
- package/src/frontend/reloc-symbol.ts +109 -13
- package/src/frontend/splat.ts +108 -5
- package/src/frontend/ssa.ts +2098 -89
- package/src/frontend/stackargs.ts +172 -42
- package/src/frontend/thumb.ts +4563 -2091
- package/src/ir/alias.ts +71 -0
- package/src/ir/bits.ts +681 -1
- package/src/ir/core.ts +199 -6
- package/src/ir/discipline.ts +313 -0
- package/src/ir/entry.ts +159 -0
- package/src/ir/float-bits.ts +47 -0
- package/src/{structure → ir}/loops.ts +6 -2
- package/src/ir/opcodes.ts +193 -121
- package/src/ir/parse.ts +1 -0
- package/src/ir/types.ts +202 -2
- package/src/ir/verify.ts +219 -3
- package/src/l3/address.ts +5 -0
- package/src/l3/argcopy.ts +38 -6
- package/src/l3/ast.ts +467 -23
- package/src/l3/basecse.ts +33 -5
- package/src/l3/coalesce.ts +21 -13
- package/src/l3/constinit.ts +382 -0
- package/src/l3/dce.ts +34 -12
- package/src/l3/gates.ts +9 -3
- package/src/l3/hoist.ts +8 -7
- package/src/l3/initfirst.ts +68 -19
- package/src/l3/intcell.ts +98 -0
- package/src/l3/mentions.ts +10 -4
- package/src/l3/narrowdecl.ts +259 -0
- package/src/l3/nearbase.ts +10 -5
- package/src/l3/parkfirst.ts +8 -5
- package/src/l3/pollguard.ts +11 -4
- package/src/l3/ptrcell.ts +354 -0
- package/src/l3/ptrfield.ts +123 -8
- package/src/l3/regspell.ts +26 -7
- package/src/l3/reindex.ts +293 -103
- package/src/l3/storage.ts +98 -1
- package/src/l3/symbol-refs.ts +182 -23
- package/src/l3/tailmerge.ts +9 -20
- package/src/l3/typing.ts +237 -32
- package/src/l3/unreduce.ts +10 -9
- package/src/l3/volatileptr.ts +17 -54
- package/src/l3/volstore.ts +96 -7
- package/src/mangle.ts +41 -1
- package/src/pattern/engine.ts +373 -82
- package/src/pipeline.ts +589 -27
- package/src/proto-context.ts +1648 -0
- package/src/proto.ts +1203 -49
- package/src/raise/aggmembers.ts +290 -0
- package/src/raise/arrays.ts +8 -3
- package/src/raise/declared-volatile.ts +307 -0
- package/src/raise/divpow2.ts +2 -2
- package/src/raise/errors.ts +17 -0
- package/src/raise/floathelpers.ts +464 -0
- package/src/raise/globalshape.ts +11 -5
- package/src/raise/gvn.ts +29 -8
- package/src/raise/latch.ts +162 -6
- package/src/raise/memberarrays.ts +10 -4
- package/src/raise/narrow.ts +29 -2
- package/src/raise/narrowlocal.ts +11 -6
- package/src/raise/offsetnames.ts +494 -0
- package/src/raise/pairparams.ts +114 -0
- package/src/raise/paramwidth.ts +203 -6
- package/src/raise/pre-recovery.ts +174 -17
- package/src/raise/pre-structure.ts +54 -0
- package/src/raise/recover.ts +26 -15
- package/src/raise/reroll.ts +694 -0
- package/src/raise/retsink.ts +1 -1
- package/src/raise/shortcircuit.ts +66 -30
- package/src/raise/softdiv.ts +21 -23
- package/src/raise/storesign.ts +153 -0
- package/src/raise/struct-arrays.ts +34 -14
- package/src/raise/structs.ts +674 -98
- package/src/raise/truncload.ts +367 -0
- package/src/raise/unthread.ts +400 -0
- package/src/raise/widehelpers.ts +106 -0
- package/src/rank-declare.ts +143 -12
- package/src/rank-variations.ts +117 -31
- package/src/rank.ts +563 -122
- package/src/runtime-helpers.ts +405 -0
- package/src/stillborn.ts +289 -0
- package/src/structure/analysis.ts +1449 -449
- package/src/structure/arith-ops.ts +23 -0
- package/src/structure/globalaccess.ts +111 -9
- package/src/structure/hazards.ts +641 -114
- package/src/structure/local-statics.ts +517 -0
- package/src/structure/namecoalesce.ts +2 -2
- package/src/structure/pointer-spelling.ts +1106 -0
- package/src/structure/redundant-test.ts +99 -0
- package/src/structure/structure.ts +3478 -975
- package/src/structure/switch-recover.ts +403 -178
- package/src/symbols.ts +44 -3
- package/src/target.ts +1387 -71
- package/src/trace.ts +66 -28
- package/src/variation-definitions.ts +243 -13
- package/src/variation-gates.ts +2 -0
- package/src/variation-tokens.ts +25 -7
package/README.md
CHANGED
|
@@ -75,8 +75,8 @@ Everything under `src/` is importable as `@asmlift/core/<path>` (e.g.
|
|
|
75
75
|
|
|
76
76
|
Three ISA frontends (ARMv4T/Thumb, MIPS, PowerPC), four compiler families (`agbcc`, `ido`, `gcc`,
|
|
77
77
|
`mwcc` — `codegen-flags.ts`'s `FlagFamily`) over seven target keys, three language backends over one neutral AST — all scored across the package seam
|
|
78
|
-
by [`@asmlift/cli`](../cli/README.md) with the community `objdiff` engine (in-process
|
|
79
|
-
`objdiff-wasm`; asmlift never hand-rolls a diff).
|
|
78
|
+
by [`@asmlift/cli`](../cli/README.md) with the community `objdiff` engine (in-process through
|
|
79
|
+
`@match-kit/scoring`, pinned `objdiff-wasm`; asmlift never hand-rolls a diff).
|
|
80
80
|
|
|
81
81
|
### The pipeline (`decompile()` in `pipeline.ts`)
|
|
82
82
|
|
|
@@ -94,27 +94,28 @@ 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
|
|
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`
|
|
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
|
|
109
|
-
| `
|
|
110
|
-
| `
|
|
111
|
-
| `
|
|
112
|
-
| `
|
|
113
|
-
| `
|
|
114
|
-
| `
|
|
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 |
|
|
115
116
|
|
|
116
117
|
Scoring and ranking live across the package seam in [`@asmlift/cli`](../cli/README.md):
|
|
117
|
-
`score.ts`
|
|
118
|
+
`score.ts` (toolchain compiles → `@match-kit/scoring`, the in-process pinned objdiff engine, fail-closed)
|
|
118
119
|
and `rank.ts` (ranked type candidates re-ranked by the differ).
|
|
119
120
|
|
|
120
121
|
### Honest coverage gaps
|
|
@@ -124,17 +125,21 @@ properly nested, in-body `break`/early-`return`), comparison-tree and jump-table
|
|
|
124
125
|
direct calls, constant-offset and variable-index memory (`*p`, `p[n]`, `a[i]`, struct fields),
|
|
125
126
|
magic-number and soft division, short-circuit booleans, width casts, PowerPC small-data access
|
|
126
127
|
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
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
128
|
+
declaration and the code agree (`stagesOutgoingArgsInFrame`; MIPS lifts a declared callee's too),
|
|
129
|
+
a compare read by a branch in a later block that only its own block enters (Thumb and
|
|
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, MIPS and PPC lift an
|
|
132
|
+
address-taken local the frame-object audit can bound — a scalar, or an untyped buffer that fills
|
|
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),
|
|
137
|
+
**condition flags at a join** (a compare two or more edges bring to one branch, or one the caller
|
|
138
|
+
set), computed tail calls, PIC and `gp`-relative access outside PowerPC's
|
|
134
139
|
small-data relocation — MIPS refuses `R_MIPS_GOT16`/`R_MIPS_CALL16`/`R_MIPS_GPREL16` alike —
|
|
135
|
-
switch fall-through,
|
|
136
|
-
(callee arities, void-ness) come from a caller-supplied map, as a
|
|
137
|
-
headers.
|
|
140
|
+
switch fall-through, irreducible loops and loops that exit to more than one live merge, floats,
|
|
141
|
+
and 64-bit memory ops. Prototypes (callee arities, void-ness) come from a caller-supplied map, as a
|
|
142
|
+
real project takes them from headers.
|
|
138
143
|
|
|
139
144
|
## Tests
|
|
140
145
|
|
package/package.json
CHANGED
package/src/aggregate.ts
ADDED
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
// asmlift — a declared struct or union on a target: its size, and whether a function returning it
|
|
2
|
+
// by value hands it back through a hidden pointer. The members come from a declaration
|
|
3
|
+
// (`proto.ts` AggregateLayout); the layout rules and the return rule are the target's
|
|
4
|
+
// (`compilerBehaviors.aggregateBoundary`, `largestAlignment`, `aggregateReturn`).
|
|
5
|
+
import { type IrType, type StructField, T } from './ir/types';
|
|
6
|
+
import {
|
|
7
|
+
type AggregateLayout,
|
|
8
|
+
type AggregateMember,
|
|
9
|
+
type FnProto,
|
|
10
|
+
type Prototypes,
|
|
11
|
+
STANDARD_SIGNATURES,
|
|
12
|
+
declaredCallArgs,
|
|
13
|
+
declaredWidth,
|
|
14
|
+
declaresAggregateReturn,
|
|
15
|
+
declaresVoidReturn,
|
|
16
|
+
ownArgumentWords,
|
|
17
|
+
ownLeadingWordParams,
|
|
18
|
+
paramClassOf,
|
|
19
|
+
wordsOf,
|
|
20
|
+
} from './proto';
|
|
21
|
+
import type { TargetDescription } from './target';
|
|
22
|
+
|
|
23
|
+
/** The size and alignment of a laid-out aggregate in bytes, or undefined where the declaration or
|
|
24
|
+
* the target leaves either open: a member not laid out, a member whose size or placement the
|
|
25
|
+
* target does not state (an enum, a bitfield), or a target that states no largest alignment. */
|
|
26
|
+
export function aggregateSize(
|
|
27
|
+
layout: AggregateLayout,
|
|
28
|
+
target: TargetDescription,
|
|
29
|
+
): { size: number; align: number } | undefined {
|
|
30
|
+
const placed = place(layout, target);
|
|
31
|
+
return placed && { size: placed.size, align: placed.align };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Each member's offset — a bitfield's is the byte its first bit is in — with the aggregate's size
|
|
35
|
+
* and alignment: `aggregateSize`'s walk. The cursor is in bits, since a bitfield can end
|
|
36
|
+
* mid-byte. */
|
|
37
|
+
function place(
|
|
38
|
+
layout: AggregateLayout,
|
|
39
|
+
target: TargetDescription,
|
|
40
|
+
): { size: number; align: number; offsets: number[] } | undefined {
|
|
41
|
+
const { aggregateBoundary, largestAlignment, bitfieldPacking } = target.compilerBehaviors;
|
|
42
|
+
if (layout.members === undefined || aggregateBoundary === undefined || largestAlignment === undefined) {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
let bits = 0;
|
|
46
|
+
let align = aggregateBoundary;
|
|
47
|
+
const offsets: number[] = [];
|
|
48
|
+
for (const m of layout.members) {
|
|
49
|
+
if (m.bits !== undefined) {
|
|
50
|
+
if (bitfieldPacking !== 'contiguous' || m.bits === 0 || typeof m.type !== 'string') {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
if (scalarBytes(m.type, target) === undefined) {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
offsets.push(layout.kind === 'struct' ? Math.floor(bits / 8) : 0);
|
|
57
|
+
bits = layout.kind === 'struct' ? bits + m.bits : Math.max(bits, m.bits);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const one = memberSize(m, target);
|
|
61
|
+
if (one === undefined) {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
align = Math.max(align, one.align);
|
|
65
|
+
if (layout.kind === 'struct') {
|
|
66
|
+
offsets.push(roundUp(Math.ceil(bits / 8), one.align));
|
|
67
|
+
bits = (offsets[offsets.length - 1] + one.size) * 8;
|
|
68
|
+
} else {
|
|
69
|
+
offsets.push(0);
|
|
70
|
+
bits = Math.max(bits, one.size * 8);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return { size: roundUp(Math.ceil(bits / 8), align), align, offsets };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** A declared struct as the IR types it: `name` at the size this target lays it out at, spelled as
|
|
77
|
+
* the headers spell it (`spelling`, which is what marks it theirs — ir/types.ts), with a field at
|
|
78
|
+
* its offset for each member the IR can type: a scalar, a pointer, or an array of either. A member
|
|
79
|
+
* it cannot — a nested struct or union, a bitfield, an enum, a plain `char`, whose signedness is
|
|
80
|
+
* the compiler's and stated nowhere — has no field, and so no read of it is typed. The struct is
|
|
81
|
+
* DEFINED from the declaration (declare.ts), not from these fields. Undefined for a union, whose IR
|
|
82
|
+
* type carries no name to declare it by, and where this target does not size it. */
|
|
83
|
+
export function aggregateType(
|
|
84
|
+
name: string,
|
|
85
|
+
spelling: string,
|
|
86
|
+
layout: AggregateLayout,
|
|
87
|
+
target: TargetDescription,
|
|
88
|
+
): IrType | undefined {
|
|
89
|
+
const placed = layout.kind === 'struct' ? place(layout, target) : undefined;
|
|
90
|
+
if (placed === undefined) {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
const fields: StructField[] = [];
|
|
94
|
+
for (const [i, m] of layout.members!.entries()) {
|
|
95
|
+
const scalar = m.bits === undefined && typeof m.type === 'string' ? scalarType(m.type) : undefined;
|
|
96
|
+
if (scalar !== undefined) {
|
|
97
|
+
const type = (m.dims ?? []).reduceRight<IrType>((elem, n) => T.array(elem, n), scalar);
|
|
98
|
+
fields.push({ off: placed.offsets[i], type, name: m.name });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return { kind: 'struct', name, fields, size: placed.size, declared: spelling };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** The IR type of a scalar or pointer member spelling: a pointer to a scalar keeps its pointee, any
|
|
105
|
+
* other pointer is `void *`, which lays out the same. */
|
|
106
|
+
function scalarType(spelling: string): IrType | undefined {
|
|
107
|
+
const s = spelling
|
|
108
|
+
.replace(/\b(?:const|volatile)\b/g, ' ')
|
|
109
|
+
.trim()
|
|
110
|
+
.replace(/\s+/g, ' ');
|
|
111
|
+
if (s.endsWith('*')) {
|
|
112
|
+
return T.ptr(scalarType(s.slice(0, -1)) ?? T.void());
|
|
113
|
+
}
|
|
114
|
+
if (s === 'float' || s === 'double') {
|
|
115
|
+
return s === 'float' ? T.f32() : T.f64();
|
|
116
|
+
}
|
|
117
|
+
const bits = declaredWidth(s);
|
|
118
|
+
return bits === undefined || s === 'char' ? undefined : T.int(bits, !/^u\d|\bunsigned\b/.test(s));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function memberSize(m: AggregateMember, target: TargetDescription): { size: number; align: number } | undefined {
|
|
122
|
+
const count = (m.dims ?? []).reduce((n, d) => n * d, 1);
|
|
123
|
+
if (typeof m.type !== 'string') {
|
|
124
|
+
const inner = aggregateSize(m.type, target);
|
|
125
|
+
return inner === undefined ? undefined : { size: inner.size * count, align: inner.align };
|
|
126
|
+
}
|
|
127
|
+
const bytes = scalarBytes(m.type, target);
|
|
128
|
+
if (bytes === undefined) {
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
return { size: bytes * count, align: Math.min(bytes, target.compilerBehaviors.largestAlignment!) };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** The bytes a scalar or pointer member spelled `spelling` takes in storage. Not `declaredWidth`'s
|
|
135
|
+
* question, which is how many argument registers a parameter takes, and which leaves the floating
|
|
136
|
+
* types out because on an FPU target they take none: in memory a `float` is 4 bytes and a `double`
|
|
137
|
+
* 8 on every target here (IEEE single and double). An enum (`enum E`) is the target's `enumBytes`,
|
|
138
|
+
* and unsized where it states none. */
|
|
139
|
+
function scalarBytes(spelling: string, target: TargetDescription): number | undefined {
|
|
140
|
+
const s = spelling
|
|
141
|
+
.replace(/\b(?:const|volatile)\b/g, ' ')
|
|
142
|
+
.trim()
|
|
143
|
+
.replace(/\s+/g, ' ');
|
|
144
|
+
if (/^enum [A-Za-z_]\w*$/.test(s)) {
|
|
145
|
+
return target.compilerBehaviors.enumBytes;
|
|
146
|
+
}
|
|
147
|
+
const bits = s === 'float' ? 32 : s === 'double' ? 64 : declaredWidth(s);
|
|
148
|
+
return bits === undefined ? undefined : bits / 8;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const roundUp = (n: number, to: number): number => Math.ceil(n / to) * to;
|
|
152
|
+
|
|
153
|
+
/** The aggregate a declaration says `p` returns by value (`declaresAggregateReturn`): its stated
|
|
154
|
+
* layout, or the bare kind its `returns` spells. */
|
|
155
|
+
export function returnedAggregate(p: FnProto): AggregateLayout {
|
|
156
|
+
return p.returnLayout ?? { kind: /\bunion\b/.test(p.returns ?? '') ? 'union' : 'struct' };
|
|
157
|
+
}
|
|
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
|
+
|
|
181
|
+
/** Whether a function declared to return `layout` by value hands it back through memory — so its
|
|
182
|
+
* caller passes the address as argument 0 — or `undefined` where the target states no rule or the
|
|
183
|
+
* rule needs a size the layout does not give. */
|
|
184
|
+
export function returnsInMemory(layout: AggregateLayout, target: TargetDescription): boolean | undefined {
|
|
185
|
+
const rule = target.compilerBehaviors.aggregateReturn;
|
|
186
|
+
if (rule === undefined) {
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
if (rule === 'memory') {
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
if (rule === 'svr4') {
|
|
193
|
+
const size = aggregateSize(layout, target)?.size;
|
|
194
|
+
return size === undefined ? undefined : size > 8;
|
|
195
|
+
}
|
|
196
|
+
// agbcc thumb.c:1423-1493: a struct's second member that is not a bitfield puts it in memory
|
|
197
|
+
// whatever its size; otherwise anything over a word does
|
|
198
|
+
const members = layout.members;
|
|
199
|
+
if (layout.kind === 'struct' && members?.slice(1).some((m) => m.bits === undefined)) {
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
const size = aggregateSize(layout, target)?.size;
|
|
203
|
+
if (size === undefined) {
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
206
|
+
if (size > 4 || layout.kind === 'struct') {
|
|
207
|
+
return size > 4;
|
|
208
|
+
}
|
|
209
|
+
// a union: in memory as soon as one member would be on its own — an array member is an aggregate
|
|
210
|
+
// that is neither a struct nor a union, which thumb.c puts in memory (line 1491)
|
|
211
|
+
let answer: boolean | undefined = false;
|
|
212
|
+
for (const m of members!) {
|
|
213
|
+
const own = m.dims !== undefined ? true : typeof m.type === 'string' ? false : returnsInMemory(m.type, target);
|
|
214
|
+
if (own === true) {
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
if (own === undefined) {
|
|
218
|
+
answer = undefined;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return answer;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Whether a call to `callee` is KNOWN not to be handed a hidden struct-return pointer in
|
|
225
|
+
* argument 0. A callee that returns nothing has no such pointer to be given; neither has one
|
|
226
|
+
* whose return travels in a register. Every other answer — including silence — is `false`,
|
|
227
|
+
* because this is a fact a caller must be TOLD: the two frames are the same instructions in the
|
|
228
|
+
* same order, so there is nothing in the assembly to read it off.
|
|
229
|
+
*
|
|
230
|
+
* A RETURN WIDER THAN A REGISTER STILL TRAVELS IN REGISTERS — it travels in a PAIR, which is
|
|
231
|
+
* still not a hidden pointer the caller supplied — so `declaredWidth` answering 64 is the right
|
|
232
|
+
* answer here rather than a width that slipped through a test meant for words. Nothing reaches
|
|
233
|
+
* it: `STANDARD_SIGNATURES` has one entry and it returns `void *`.
|
|
234
|
+
*
|
|
235
|
+
* TWO SOURCES AND NEITHER RANKS ABOVE THE OTHER, because on this one question they cannot
|
|
236
|
+
* disagree: the project's own headers — `returnsVoid`, or a struct or union `target` hands back
|
|
237
|
+
* in a register — and the `returns` of a signature the C standard fixes, which is as known as its
|
|
238
|
+
* parameters. That a project may re-declare a standard function differently is real and is why
|
|
239
|
+
* `declaredCall` ranks the two for ARITY — but a re-declaration that changed `memcpy` into a
|
|
240
|
+
* struct-returning function would not be `memcpy`.
|
|
241
|
+
*
|
|
242
|
+
* `Object.hasOwn`, not `in`: `prototypes` is caller-supplied JSON and the table is an object
|
|
243
|
+
* literal, so `in` would answer for `toString` and every other name on `Object.prototype`. The
|
|
244
|
+
* ENTRY is read through `?.` for the other half of the same fact: `decompile` is a published
|
|
245
|
+
* entry point that runs no `validatePrototypes`, so a `null` entry out of parsed JSON reaches
|
|
246
|
+
* here, and a raw TypeError would leave through neither the decline channel nor anything a
|
|
247
|
+
* caller can act on. Every other reader of this table — `declaredCallArgs`, and `declaredCall`
|
|
248
|
+
* through it — answers "nothing is declared" for such an entry, and so does this. */
|
|
249
|
+
export function returnsWithoutHiddenPointer(
|
|
250
|
+
callee: string,
|
|
251
|
+
prototypes: Prototypes,
|
|
252
|
+
target: TargetDescription,
|
|
253
|
+
): boolean {
|
|
254
|
+
// Nothing at all, a value in registers, or a spelling nobody here can size — the last of which is
|
|
255
|
+
// the only one that leaves the hidden pointer open. A pair is `declaredWidth` 64 and still
|
|
256
|
+
// travels in registers, so a width wider than a word is an answer here and not an overflow.
|
|
257
|
+
const travelsInRegisters = (spelling: string): boolean => {
|
|
258
|
+
const t = spelling.trim();
|
|
259
|
+
return t === 'void' || declaredWidth(t) !== undefined;
|
|
260
|
+
};
|
|
261
|
+
const own = Object.hasOwn(prototypes, callee) ? prototypes[callee] : undefined;
|
|
262
|
+
if (declaresVoidReturn(own)) {
|
|
263
|
+
return true;
|
|
264
|
+
}
|
|
265
|
+
// A STRUCT OR UNION IS THE TARGET'S TO PLACE — agbcc hands a one-word struct back in r0, mwcc up
|
|
266
|
+
// to eight bytes in r3/r3:r4 — so this asks the rule the call lowering reads, and the two cannot
|
|
267
|
+
// disagree. A rule or a size nothing states leaves the pointer open.
|
|
268
|
+
if (own && declaresAggregateReturn(own)) {
|
|
269
|
+
return returnsInMemory(returnedAggregate(own), target) === false;
|
|
270
|
+
}
|
|
271
|
+
// A PROJECT'S OWN `returns` ANSWERS THIS THROUGH THE SAME READING A STANDARD SIGNATURE'S DOES,
|
|
272
|
+
// and it ranks above the table for the same reason `declaredCall` ranks a re-declaration above
|
|
273
|
+
// one: a project that spells the return has told you about the function it is building.
|
|
274
|
+
if (own?.returns !== undefined) {
|
|
275
|
+
return travelsInRegisters(own.returns);
|
|
276
|
+
}
|
|
277
|
+
return Object.hasOwn(STANDARD_SIGNATURES, callee) && travelsInRegisters(STANDARD_SIGNATURES[callee].returns);
|
|
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
|
@@ -6,15 +6,19 @@
|
|
|
6
6
|
// this backend owns only the C SIGNATURE line. The Pascal backend implements the same
|
|
7
7
|
// LanguageBackend interface over the same L3 with its OWN spelling.
|
|
8
8
|
import { LanguageBackend, SFn } from '../l3/ast';
|
|
9
|
-
import { cComment, cType, emitCFamily } from './cfamily';
|
|
9
|
+
import { cComment, cDeclare, cType, emitCFamily } from './cfamily';
|
|
10
10
|
|
|
11
11
|
export { cComment }; // re-export: the shared spelling lives in cfamily.ts
|
|
12
12
|
|
|
13
13
|
export const cBackend: LanguageBackend = {
|
|
14
14
|
id: 'c',
|
|
15
15
|
spellsSwitchFallthrough: true,
|
|
16
|
+
spellsSwitchBreak: true,
|
|
16
17
|
emit(fn: SFn): string {
|
|
17
|
-
|
|
18
|
+
// The SAME declarator placement the local list and the struct-field printer use, so one
|
|
19
|
+
// function cannot spell the `*` on the type in its signature and on the declarator in its
|
|
20
|
+
// body.
|
|
21
|
+
const params = fn.params.map((p) => cDeclare(p.type, p.name)).join(', ') || 'void';
|
|
18
22
|
return emitCFamily(`${cType(fn.retType)} ${fn.name}(${params})`, fn);
|
|
19
23
|
},
|
|
20
24
|
comment: cComment,
|