@asmlift/core 0.1.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.
Files changed (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +148 -0
  3. package/package.json +14 -0
  4. package/src/backend/c.ts +20 -0
  5. package/src/backend/cfamily.ts +352 -0
  6. package/src/backend/cpp.ts +145 -0
  7. package/src/backend/pascal.ts +279 -0
  8. package/src/contracts.ts +131 -0
  9. package/src/detect.ts +12 -0
  10. package/src/frontend/asmdata.ts +170 -0
  11. package/src/frontend/disasm.ts +102 -0
  12. package/src/frontend/emit.ts +57 -0
  13. package/src/frontend/errors.ts +14 -0
  14. package/src/frontend/format.ts +47 -0
  15. package/src/frontend/frontend.ts +22 -0
  16. package/src/frontend/mips.ts +875 -0
  17. package/src/frontend/opaque.ts +82 -0
  18. package/src/frontend/ppc.ts +990 -0
  19. package/src/frontend/registry.ts +34 -0
  20. package/src/frontend/ssa.ts +214 -0
  21. package/src/frontend/thumb.ts +1419 -0
  22. package/src/ir/core.ts +104 -0
  23. package/src/ir/opcodes.ts +143 -0
  24. package/src/ir/parse.ts +221 -0
  25. package/src/ir/print.ts +77 -0
  26. package/src/ir/types.ts +106 -0
  27. package/src/ir/verify.ts +221 -0
  28. package/src/l3/ast.ts +301 -0
  29. package/src/l3/basecse.ts +218 -0
  30. package/src/l3/dce.ts +256 -0
  31. package/src/l3/regspell.ts +331 -0
  32. package/src/l3/reindex.ts +447 -0
  33. package/src/l3/typing.ts +145 -0
  34. package/src/mangle.ts +135 -0
  35. package/src/pattern/engine.ts +392 -0
  36. package/src/pipeline.ts +272 -0
  37. package/src/proto.ts +42 -0
  38. package/src/raise/arrays.ts +84 -0
  39. package/src/raise/const.ts +52 -0
  40. package/src/raise/errors.ts +10 -0
  41. package/src/raise/magicdiv.ts +386 -0
  42. package/src/raise/pre-recovery.ts +71 -0
  43. package/src/raise/recover.ts +215 -0
  44. package/src/raise/retsink.ts +72 -0
  45. package/src/raise/shortcircuit.ts +207 -0
  46. package/src/raise/softdiv.ts +62 -0
  47. package/src/raise/struct-arrays.ts +257 -0
  48. package/src/raise/structs.ts +223 -0
  49. package/src/rank.ts +208 -0
  50. package/src/structure/analysis.ts +410 -0
  51. package/src/structure/hazards.ts +142 -0
  52. package/src/structure/loops.ts +169 -0
  53. package/src/structure/structure.ts +1726 -0
  54. package/src/structure/switch-recover.ts +410 -0
  55. package/src/target.ts +140 -0
  56. package/src/trace.ts +233 -0
@@ -0,0 +1,170 @@
1
+ // asmlift — the AsmData side-table. MIPS/PPC frontends parse
2
+ // `objdump -d` (`.text` only), so a dense-switch's case→target jump table — which lives in a DATA
3
+ // section (`.rodata`/`.data`/`.sdata2`) as bytes + relocations — never reaches them. This module is
4
+ // an ISA-agnostic bag of `{ section bytes, relocations, symbols }` extracted from a companion
5
+ // `objdump -s`/`-r`/`-t` pass on the SAME object, threaded as an OPTIONAL `lift()` parameter.
6
+ // Absent ⇒ the frontends decline; only Regime-B recovery reads it. Reloc *interpretation* (MIPS
7
+ // gp-relative vs PPC named-symbol) stays per-frontend — this layer is pure parse + a fail-closed
8
+ // table reader.
9
+ //
10
+ // Empirically grounded against the three
11
+ // toolchains: IDO stores `.text` offsets in `.rodata` with `R_MIPS_GPREL32 .text` relocs; KMC gcc
12
+ // stores them in `.rodata` with `R_MIPS_32 .text`; mwcc stores ZERO bytes in `.data` and carries
13
+ // the whole map in `R_PPC_ADDR32 <fn>+<off>` relocs. All three reduce to: target .text offset =
14
+ // symbolBase(reloc.sym) + reloc.addend + inlineWord — the reader below.
15
+
16
+ /** One relocation record (from `objdump -r`). `sym` is the target symbol (a section symbol like
17
+ * `.text`/`.rodata`, an object symbol like `@15`, or a function symbol like `sw_jt`); `addend` is the
18
+ * RELA addend (0 for REL relocs, whose addend lives inline in the section bytes). */
19
+ export interface Reloc {
20
+ section: string;
21
+ offset: number;
22
+ type: string;
23
+ sym: string;
24
+ addend: number;
25
+ }
26
+
27
+ /** A parsed object's data sections + relocations + symbol offsets. Big-endian for MIPS-N64 / PPC (the
28
+ * only Regime-B consumers today); `bigEndian` records it so the word reader is not ISA-hardcoded. */
29
+ export interface AsmData {
30
+ sections: Map<string, Uint8Array>; // section name → raw bytes (file order)
31
+ relocs: Reloc[];
32
+ symbols: Map<string, { section: string; value: number }>; // symbol name → {section, offset-in-section}
33
+ bigEndian: boolean;
34
+ }
35
+
36
+ const readU32 = (b: Uint8Array, off: number, big: boolean): number =>
37
+ big
38
+ ? ((b[off] << 24) | (b[off + 1] << 16) | (b[off + 2] << 8) | b[off + 3]) >>> 0
39
+ : ((b[off + 3] << 24) | (b[off + 2] << 16) | (b[off + 1] << 8) | b[off]) >>> 0;
40
+
41
+ // Parse an `objdump -r` VALUE field: `sw_jt+0x00000020` / `.text` / `@15` → {sym, addend}.
42
+ function parseRelocValue(v: string): { sym: string; addend: number } {
43
+ const plus = v.indexOf('+');
44
+ if (plus < 0) {
45
+ return { sym: v, addend: 0 };
46
+ }
47
+ const a = v.slice(plus + 1).trim();
48
+ return { sym: v.slice(0, plus), addend: parseInt(a, /^0x/i.test(a) ? 16 : 10) >>> 0 };
49
+ }
50
+
51
+ /** Parse the three companion objdump dumps into an `AsmData`. Pure (no shell) so it is unit-testable
52
+ * against captured output; the callers (cli score.ts, benchmark cache.ts) supply the strings. */
53
+ export function parseAsmData(
54
+ sectionsDump: string,
55
+ relocsDump: string,
56
+ symbolsDump: string,
57
+ bigEndian: boolean,
58
+ ): AsmData {
59
+ // --- `objdump -s`: `Contents of section .rodata:` then ` 0000 00000034 0000003c … ascii` ---
60
+ const sections = new Map<string, Uint8Array>();
61
+ let curSec: string | null = null;
62
+ let bytes: number[] = [];
63
+ const flush = () => {
64
+ if (curSec) {
65
+ sections.set(curSec, Uint8Array.from(bytes));
66
+ }
67
+ curSec = null;
68
+ bytes = [];
69
+ };
70
+ for (const line of sectionsDump.split('\n')) {
71
+ const hdr = line.match(/^Contents of section (\S+):/);
72
+ if (hdr) {
73
+ flush();
74
+ curSec = hdr[1];
75
+ continue;
76
+ }
77
+ if (!curSec) {
78
+ continue;
79
+ }
80
+ // ` 0000 00000034 0000003c 00000044 0000004c ...4...<...D...L` — hex columns end at the 2-space
81
+ // gap before the ascii gutter; take everything between the offset and that gap.
82
+ const m = line.match(/^\s*[0-9a-f]+\s+([0-9a-f ]+?)\s{2,}/i);
83
+ if (!m) {
84
+ continue;
85
+ }
86
+ for (const grp of m[1].trim().split(/\s+/)) {
87
+ for (let i = 0; i + 1 < grp.length; i += 2) {
88
+ bytes.push(parseInt(grp.slice(i, i + 2), 16));
89
+ }
90
+ }
91
+ }
92
+ flush();
93
+
94
+ // --- `objdump -r`: `RELOCATION RECORDS FOR [.text]:` then `OFFSET TYPE VALUE` rows ---
95
+ const relocs: Reloc[] = [];
96
+ let relSec: string | null = null;
97
+ for (const line of relocsDump.split('\n')) {
98
+ const hdr = line.match(/^RELOCATION RECORDS FOR \[(\S+)\]:/);
99
+ if (hdr) {
100
+ relSec = hdr[1];
101
+ continue;
102
+ }
103
+ if (!relSec) {
104
+ continue;
105
+ }
106
+ const m = line.match(/^([0-9a-f]+)\s+(R_\S+)\s+(\S+)/i);
107
+ if (!m) {
108
+ continue;
109
+ }
110
+ const { sym, addend } = parseRelocValue(m[3]);
111
+ relocs.push({ section: relSec, offset: parseInt(m[1], 16), type: m[2], sym, addend });
112
+ }
113
+
114
+ // --- `objdump -t`: `VALUE FLAGS SECTION\tSIZE NAME` (section/size split by TAB) ---
115
+ const symbols = new Map<string, { section: string; value: number }>();
116
+ for (const line of symbolsDump.split('\n')) {
117
+ const tab = line.indexOf('\t');
118
+ if (tab < 0) {
119
+ continue;
120
+ }
121
+ const left = line.slice(0, tab),
122
+ right = line.slice(tab + 1);
123
+ const lm = left.match(/^([0-9a-f]+)\s+.{6,8}\s(\S+)\s*$/i); // value … flags(7) section
124
+ const rm = right.match(/^[0-9a-f]+\s+(.+?)\s*$/i); // size name
125
+ if (!lm || !rm) {
126
+ continue;
127
+ }
128
+ symbols.set(rm[1], { section: lm[2], value: parseInt(lm[1], 16) });
129
+ }
130
+
131
+ return { sections, relocs, symbols, bigEndian };
132
+ }
133
+
134
+ /** Read a dense jump table's N target `.text` byte-offsets, or `null` if ANYTHING doesn't resolve
135
+ * cleanly (fail-closed — a partial/ambiguous table declines, never a wrong switch).
136
+ *
137
+ * `tableSym`/`tableAddend` locate the table (from the dispatch's `.text` reloc: MIPS `GOT16`/`HI16`/
138
+ * `LO16 .rodata`; PPC `ADDR16_HA/LO @tbl`). Each entry resolves to a `.text` offset via
139
+ * `symbolBase(entryReloc.sym) + entryReloc.addend + inlineWord`, and MUST target `.text`. */
140
+ export function readJumpTable(ad: AsmData, tableSym: string, tableAddend: number, n: number): number[] | null {
141
+ if (n < 2) {
142
+ return null;
143
+ }
144
+ const tsym = ad.symbols.get(tableSym);
145
+ const section = tsym ? tsym.section : tableSym; // ".rodata" is its own section symbol
146
+ const baseOff = (tsym ? tsym.value : 0) + tableAddend;
147
+ const secBytes = ad.sections.get(section);
148
+ const out: number[] = [];
149
+ for (let i = 0; i < n; i++) {
150
+ const slot = baseOff + i * 4;
151
+ const r = ad.relocs.find((x) => x.section === section && x.offset === slot);
152
+ if (!r) {
153
+ return null;
154
+ } // every entry must be a relocated pointer
155
+ const rsym = ad.symbols.get(r.sym);
156
+ const rsec = rsym ? rsym.section : r.sym; // ".text" section symbol → ".text"
157
+ if (rsec !== '.text') {
158
+ return null;
159
+ } // only .text-directed entries are valid
160
+ const inline = secBytes && slot + 4 <= secBytes.length ? readU32(secBytes, slot, ad.bigEndian) : 0;
161
+ out.push(((rsym ? rsym.value : 0) + r.addend + inline) >>> 0);
162
+ }
163
+ return out;
164
+ }
165
+
166
+ /** The single `.text` relocation whose *instruction* address is `insnAddr` (a table-base HI/LO/GOT16
167
+ * or ADDR16 reloc), or `null`. Used by a frontend to find where its dispatch's table lives. */
168
+ export function textRelocAt(ad: AsmData, insnAddr: number): Reloc | null {
169
+ return ad.relocs.find((r) => r.section === '.text' && r.offset === insnAddr) ?? null;
170
+ }
@@ -0,0 +1,102 @@
1
+ // asmlift — shared objdump-text scaffolding for the MIPS and PPC frontends. The Thumb frontend
2
+ // parses GNU-as text, not objdump, so it does not route through here.
3
+ import { FrontendUnsupportedError } from './errors';
4
+
5
+ /** Slice a multi-symbol objdump listing down to ONE function's lines. objdump marks each
6
+ * function with an `ADDR <sym>:` header line; when headers are present the input is sliced to
7
+ * exactly the requested symbol — and an ABSENT symbol declines LOUD, because emitting some
8
+ * other function's body under the requested name is precisely the silent miscompile the
9
+ * cardinal rule forbids. Headerless input (a raw instruction fragment) passes through. */
10
+ export function sliceSymbol(disasm: string, symbol: string): string {
11
+ const lines = disasm.split('\n');
12
+ const headers: { line: number; sym: string }[] = [];
13
+ for (let i = 0; i < lines.length; i++) {
14
+ const m = lines[i].match(/^[0-9a-f]+\s+<([^>]+)>:\s*$/i);
15
+ if (m) {
16
+ headers.push({ line: i, sym: m[1] });
17
+ }
18
+ }
19
+ if (headers.length === 0) {
20
+ return disasm;
21
+ }
22
+ const at = headers.findIndex((h) => h.sym === symbol);
23
+ if (at === -1) {
24
+ throw new FrontendUnsupportedError(
25
+ `symbol '${symbol}' not found in the disassembly (symbols present: ${headers.map((h) => h.sym).join(', ')})`,
26
+ );
27
+ }
28
+ const end = at + 1 < headers.length ? headers[at + 1].line : lines.length;
29
+ return lines.slice(headers[at].line, end).join('\n');
30
+ }
31
+
32
+ /** One disassembled instruction. `target` is a decoded branch-target address (objdump prints the
33
+ * target as `10 <sym+0x10>` in the last operand); `sym` is a relocation-attached callee symbol
34
+ * (PPC `-r` output), absent otherwise. */
35
+ export interface DisasmInstr {
36
+ addr: number;
37
+ mnemonic: string;
38
+ ops: string[];
39
+ target?: number;
40
+ sym?: string;
41
+ }
42
+
43
+ export interface DisasmOptions {
44
+ /** Attach relocation lines (`ADDR: R_* <sym>[+addend]`) to the PRECEDING instruction — the
45
+ * callee symbol for a `bl` whose encoded offset is a 0 placeholder (PPC `-r` output). Tested
46
+ * BEFORE the instruction regex, which would otherwise mis-read `R_PPC_…` as a mnemonic. */
47
+ relocs?: boolean;
48
+ /** Strip branch-prediction hint suffixes glued onto the mnemonic (`blt-`, `bge+`, `bgelr-`).
49
+ * The suffix is a prediction hint, not a different instruction — without stripping, the
50
+ * mnemonic misses the cond tables and the branch is silently dropped. */
51
+ hintSuffixes?: boolean;
52
+ }
53
+
54
+ /** Parse objdump `-d --no-show-raw-insn` output into a flat instruction list with addresses. */
55
+ export function parseDisasm(disasm: string, opts: DisasmOptions = {}): DisasmInstr[] {
56
+ const out: DisasmInstr[] = [];
57
+ for (const raw of disasm.split('\n')) {
58
+ if (opts.relocs) {
59
+ const rel = raw.match(/^\s+[0-9a-f]+:\s+R_\w+\s+(\S+)/i);
60
+ if (rel) {
61
+ if (out.length) {
62
+ out[out.length - 1].sym = rel[1].split('+')[0];
63
+ }
64
+ continue;
65
+ }
66
+ }
67
+ const m = opts.hintSuffixes
68
+ ? raw.match(/^\s*([0-9a-f]+):\s+([a-z][a-z0-9._]*)([-+]?)\s*(.*?)\s*$/i)
69
+ : raw.match(/^\s*([0-9a-f]+):\s+([a-z][a-z0-9._]*)\s*(.*?)\s*$/i);
70
+ if (!m) {
71
+ continue;
72
+ }
73
+ const addr = parseInt(m[1], 16);
74
+ const mnemonic = m[2]; // hint suffix (group 3), when parsed, is dropped
75
+ const opsStr = opts.hintSuffixes ? m[4] : m[3];
76
+ const ops = opsStr
77
+ ? opsStr
78
+ .split(',')
79
+ .map((s) => s.trim())
80
+ .filter(Boolean)
81
+ : [];
82
+ let target: number | undefined;
83
+ const tm = ops.length ? ops[ops.length - 1].match(/^([0-9a-f]+)\s+</i) : null;
84
+ if (tm) {
85
+ target = parseInt(tm[1], 16);
86
+ }
87
+ out.push({ addr, mnemonic, ops, target });
88
+ }
89
+ return out;
90
+ }
91
+
92
+ /** An objdump immediate: decimal or hex (objdump prints hex as 0x…, negatives as -N). */
93
+ export const parseImm = (s: string): number => parseInt(s, /^-?0x/i.test(s) ? 16 : 10);
94
+
95
+ /** A memory operand `off(base)` (e.g. `8(a0)`, `-4(r1)`) → constant byte offset + base register.
96
+ * `baseRe` narrows what counts as a base (PPC: `r\d+` — a non-register base is an SDA/global
97
+ * placeholder the caller must decline). A non-matching operand falls back to offset 0 with the
98
+ * parens stripped. */
99
+ export function parseMem(operand: string, baseRe: RegExp = /\w+/): { off: number; base: string } {
100
+ const m = operand.match(new RegExp(`^(-?(?:0x)?[0-9a-f]+)\\((${baseRe.source})\\)$`, 'i'));
101
+ return m ? { off: parseImm(m[1]), base: m[2] } : { off: 0, base: operand.replace(/[()]/g, '') };
102
+ }
@@ -0,0 +1,57 @@
1
+ // asmlift — the shared per-block IR emitter kit for the MIPS and PPC decode loops. Bound per
2
+ // block (the `ops` array and `write` are block-scoped); the ISA-specific readers/guards
3
+ // (sp-as-data, frame transparency) stay in the frontends.
4
+ import { Op, Successor, Value, mkOp, mkValue } from '../ir/core';
5
+ import type { Opcode } from '../ir/opcodes';
6
+ import { T } from '../ir/types';
7
+
8
+ export interface EmitKit {
9
+ /** a fresh `const` value */
10
+ cnst: (n: number) => Value;
11
+ /** emit `opc` over operands into register `d`; returns the result value */
12
+ emit: (opc: Opcode, d: string, operands: Value[], attrs?: Record<string, number | boolean | string>) => Value;
13
+ bin: (opc: Opcode, d: string, x: Value, y: Value) => Value;
14
+ un: (opc: Opcode, d: string, x: Value) => Value;
15
+ shImm: (opc: Opcode, d: string, x: Value, imm: number) => Value;
16
+ /** emit `opc` into an unwritten temporary (a value feeding a following op, not a register dest) */
17
+ tmp: (opc: Opcode, operands: Value[], attrs?: Record<string, number | boolean | string>) => Value;
18
+ }
19
+
20
+ export function mkEmitKit(ops: Op[], write: (reg: string, v: Value) => void): EmitKit {
21
+ const tmp = (opc: Opcode, operands: Value[], attrs?: Record<string, number | boolean | string>): Value => {
22
+ const v = mkValue(T.unk(32));
23
+ ops.push(mkOp(opc, { operands, results: [v], ...(attrs ? { attrs } : {}) }));
24
+ return v;
25
+ };
26
+ const emit = (
27
+ opc: Opcode,
28
+ d: string,
29
+ operands: Value[],
30
+ attrs?: Record<string, number | boolean | string>,
31
+ ): Value => {
32
+ const res = tmp(opc, operands, attrs);
33
+ write(d, res);
34
+ return res;
35
+ };
36
+ return {
37
+ tmp,
38
+ emit,
39
+ cnst: (n) => tmp('const', [], { value: n }),
40
+ bin: (opc, d, x, y) => emit(opc, d, [x, y]),
41
+ un: (opc, d, x) => emit(opc, d, [x]),
42
+ shImm: (opc, d, x, imm) => emit(opc, d, [x], { imm }),
43
+ };
44
+ }
45
+
46
+ /** Emit a recovered jump-table dispatch: N case successors followed by the default (LAST), with
47
+ * the dense 0..N-1 `cases` list derived here — the single home of the case/successor
48
+ * alignment invariant. */
49
+ export function pushSwitchBr(ops: Op[], scrut: Value, successors: Successor[]): void {
50
+ ops.push(
51
+ mkOp('switch_br', {
52
+ operands: [scrut],
53
+ successors,
54
+ attrs: { cases: successors.slice(0, -1).map((_, k) => k) },
55
+ }),
56
+ );
57
+ }
@@ -0,0 +1,14 @@
1
+ // asmlift — the shared DESIGNED loud-failure signal for ISA frontends. A frontend throws this when
2
+ // it meets a construct it cannot faithfully model (an out-of-scope call, an indirect/tail transfer,
3
+ // a stack frame it can't abstract) — the "fail LOUD, never silently miscompile" contract for cases
4
+ // the `opaque`-destination path cannot reach: an instruction whose clobbered register is IMPLICIT
5
+ // (a call's return reg) or a control transfer with no data destination at all. Distinct from a
6
+ // runtime crash (TypeError/RangeError): this is a catchable, intentional "out of scope" boundary
7
+ // signal. `PpcUnsupportedError` subclasses it (annotate-mode classification and the loud-error
8
+ // tests use `instanceof` against this base).
9
+ export class FrontendUnsupportedError extends Error {
10
+ constructor(message: string) {
11
+ super(message);
12
+ this.name = 'FrontendUnsupportedError';
13
+ }
14
+ }
@@ -0,0 +1,47 @@
1
+ // asmlift — input-text format classification. Each target's ecosystem produces a different
2
+ // textual artifact (agbcc emits GNU-as `.s`; IDO/mwcc emit no asm text, so their input is
3
+ // `objdump -d` output), and each frontend reads exactly one of them. Feeding the wrong one
4
+ // would otherwise fail confusingly deep in decode (an objdump header parsed as an instruction,
5
+ // or a crash on an empty CFG) — this module makes the mismatch a boundary decline instead.
6
+ //
7
+ // Classification is CONSERVATIVE: it only names a format on a positive signal, and a frontend
8
+ // only declines on a positive MISMATCH. Text with no recognizable signals (a bare fragment of
9
+ // hand-written instructions) stays "unknown" and flows through to the frontend — the
10
+ // decode-level loud-fail nets still own that case.
11
+ import { FrontendUnsupportedError } from './errors';
12
+
13
+ export type AsmTextFormat = 'objdump' | 'gnu-as';
14
+
15
+ const FORMAT_LABEL: Record<AsmTextFormat, string> = {
16
+ objdump: 'objdump disassembly (`objdump -d --no-show-raw-insn` output)',
17
+ 'gnu-as': 'GNU-as assembly text (compiler-emitted `.s`)',
18
+ };
19
+
20
+ // objdump output: `ADDR <sym>:` section headers, address-prefixed instruction lines, or the
21
+ // `file format` banner. GNU-as text: assembler directives (`.text`, `.globl`, `.thumb_func`…).
22
+ const OBJDUMP_SIGNAL = /^[0-9a-f]{2,} <[^>]+>:|^\s+[0-9a-f]+:\t|file format /im;
23
+ const GNU_AS_SIGNAL =
24
+ /^\s*\.(text|code|align|globl|global|thumb_func|section|syntax|arch|cpu|set|ent|type|size|file)\b/im;
25
+
26
+ /** Classify assembly TEXT by positive signals; "unknown" when neither (or both) match. */
27
+ export function classifyAsmText(text: string): AsmTextFormat | 'unknown' {
28
+ const objdump = OBJDUMP_SIGNAL.test(text);
29
+ const gnuAs = GNU_AS_SIGNAL.test(text);
30
+ if (objdump === gnuAs) {
31
+ return 'unknown';
32
+ } // neither, or contradictory signals
33
+ return objdump ? 'objdump' : 'gnu-as';
34
+ }
35
+
36
+ /** Decline loudly when the input positively classifies as a format this frontend does not
37
+ * read. Called at the top of every frontend's lift. */
38
+ export function assertInputFormat(frontendId: string, expected: AsmTextFormat, asm: string): void {
39
+ const got = classifyAsmText(asm);
40
+ if (got === 'unknown' || got === expected) {
41
+ return;
42
+ }
43
+ throw new FrontendUnsupportedError(
44
+ `cannot lift: input looks like ${FORMAT_LABEL[got]}, but the '${frontendId}' frontend reads ` +
45
+ `${FORMAT_LABEL[expected]} — MIPS/PPC targets take objdump output; the ARM/agbcc target takes agbcc .s text`,
46
+ );
47
+ }
@@ -0,0 +1,22 @@
1
+ // asmlift — the ISA-frontend seam. A Frontend turns one function's assembly text into an L1
2
+ // Fn (block-argument SSA); everything downstream (recover, structure, backends) is ISA-
3
+ // neutral and consumes the Fn without knowing which frontend produced it. See registry.ts
4
+ // for target→frontend dispatch.
5
+ import type { Fn } from '../ir/core';
6
+ import type { Prototypes } from '../proto';
7
+ import type { TargetDescription } from '../target';
8
+ import type { AsmData } from './asmdata';
9
+ import type { AsmTextFormat } from './format';
10
+
11
+ export interface Frontend {
12
+ /** stable id, e.g. "thumb", "mips" — for diagnostics/reporting, not dispatch */
13
+ id: string;
14
+ /** the one input-text format this frontend reads; a positive mismatch declines at the
15
+ * lift boundary (format.ts) instead of failing confusingly deep in decode */
16
+ inputFormat: AsmTextFormat;
17
+ /** decode one function's assembly into an L1 Fn. `prototypes` supplies callee arities
18
+ * (and any other header facts the frontend needs); an empty map is valid. `asmData` is the
19
+ * OPTIONAL Regime-B side-table (data-section jump tables + relocations); absent ⇒ a
20
+ * dense-switch dispatch declines/loud-fails. */
21
+ lift(name: string, asm: string, target: TargetDescription, prototypes: Prototypes, asmData?: AsmData): Fn;
22
+ }