@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.
- package/LICENSE +21 -0
- package/README.md +148 -0
- package/package.json +14 -0
- package/src/backend/c.ts +20 -0
- package/src/backend/cfamily.ts +352 -0
- package/src/backend/cpp.ts +145 -0
- package/src/backend/pascal.ts +279 -0
- package/src/contracts.ts +131 -0
- package/src/detect.ts +12 -0
- package/src/frontend/asmdata.ts +170 -0
- package/src/frontend/disasm.ts +102 -0
- package/src/frontend/emit.ts +57 -0
- package/src/frontend/errors.ts +14 -0
- package/src/frontend/format.ts +47 -0
- package/src/frontend/frontend.ts +22 -0
- package/src/frontend/mips.ts +875 -0
- package/src/frontend/opaque.ts +82 -0
- package/src/frontend/ppc.ts +990 -0
- package/src/frontend/registry.ts +34 -0
- package/src/frontend/ssa.ts +214 -0
- package/src/frontend/thumb.ts +1419 -0
- package/src/ir/core.ts +104 -0
- package/src/ir/opcodes.ts +143 -0
- package/src/ir/parse.ts +221 -0
- package/src/ir/print.ts +77 -0
- package/src/ir/types.ts +106 -0
- package/src/ir/verify.ts +221 -0
- package/src/l3/ast.ts +301 -0
- package/src/l3/basecse.ts +218 -0
- package/src/l3/dce.ts +256 -0
- package/src/l3/regspell.ts +331 -0
- package/src/l3/reindex.ts +447 -0
- package/src/l3/typing.ts +145 -0
- package/src/mangle.ts +135 -0
- package/src/pattern/engine.ts +392 -0
- package/src/pipeline.ts +272 -0
- package/src/proto.ts +42 -0
- package/src/raise/arrays.ts +84 -0
- package/src/raise/const.ts +52 -0
- package/src/raise/errors.ts +10 -0
- package/src/raise/magicdiv.ts +386 -0
- package/src/raise/pre-recovery.ts +71 -0
- package/src/raise/recover.ts +215 -0
- package/src/raise/retsink.ts +72 -0
- package/src/raise/shortcircuit.ts +207 -0
- package/src/raise/softdiv.ts +62 -0
- package/src/raise/struct-arrays.ts +257 -0
- package/src/raise/structs.ts +223 -0
- package/src/rank.ts +208 -0
- package/src/structure/analysis.ts +410 -0
- package/src/structure/hazards.ts +142 -0
- package/src/structure/loops.ts +169 -0
- package/src/structure/structure.ts +1726 -0
- package/src/structure/switch-recover.ts +410 -0
- package/src/target.ts +140 -0
- 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
|
+
}
|