@asmlift/core 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -24
- package/package.json +1 -1
- package/src/aggregate.ts +149 -0
- package/src/backend/c.ts +2 -0
- package/src/backend/cfamily.ts +197 -61
- package/src/backend/cpp.ts +3 -1
- package/src/backend/lines.ts +10 -0
- package/src/backend/pascal.ts +47 -16
- package/src/cdecl/parse.ts +21 -0
- package/src/contracts.ts +246 -36
- package/src/declare.ts +69 -20
- package/src/frontend/arm-body.ts +155 -0
- package/src/frontend/asmdata.ts +9 -2
- package/src/frontend/call-plan.ts +1117 -61
- package/src/frontend/callee-body.ts +370 -0
- package/src/frontend/dead-reads.ts +628 -0
- package/src/frontend/device-pins.ts +33 -4
- package/src/frontend/emit.ts +4 -3
- package/src/frontend/fpu.ts +224 -17
- package/src/frontend/frame-objects.ts +1689 -206
- package/src/frontend/frontend.ts +84 -3
- package/src/frontend/high-half.ts +53 -4
- package/src/frontend/jump-table.ts +545 -0
- package/src/frontend/local-object.ts +158 -32
- package/src/frontend/mips.ts +2296 -217
- package/src/frontend/own-struct-return.ts +500 -0
- package/src/frontend/ppc.ts +2748 -245
- package/src/frontend/register-struct-call.ts +399 -0
- package/src/frontend/reloc-symbol.ts +22 -7
- package/src/frontend/ssa.ts +1721 -43
- package/src/frontend/stackargs.ts +199 -72
- package/src/frontend/thumb-entry.ts +340 -0
- package/src/frontend/thumb.ts +2400 -325
- package/src/ir/alias.ts +65 -2
- package/src/ir/bits.ts +1699 -4
- package/src/ir/core.ts +254 -9
- package/src/ir/discipline.ts +86 -21
- package/src/ir/entry.ts +157 -0
- package/src/{structure → ir}/loops.ts +6 -2
- package/src/ir/opcodes.ts +137 -9
- package/src/ir/parse.ts +1 -0
- package/src/ir/simplify.ts +61 -10
- package/src/ir/types.ts +86 -2
- package/src/ir/verify.ts +96 -11
- package/src/l3/argbase.ts +2 -0
- package/src/l3/ast.ts +582 -18
- package/src/l3/basecse.ts +68 -15
- package/src/l3/caseendgoto.ts +223 -0
- package/src/l3/coalesce.ts +42 -14
- package/src/l3/constinit.ts +410 -0
- package/src/l3/dce.ts +53 -9
- package/src/l3/initfirst.ts +42 -11
- package/src/l3/intcell.ts +2 -1
- package/src/l3/narrowdecl.ts +77 -36
- package/src/l3/pollguard.ts +8 -2
- package/src/l3/ptrcell.ts +33 -34
- package/src/l3/ptrfield.ts +123 -8
- package/src/l3/regspell.ts +11 -4
- package/src/l3/reindex.ts +295 -99
- package/src/l3/scopebase.ts +2 -0
- package/src/l3/storage.ts +98 -1
- package/src/l3/symbol-refs.ts +84 -6
- package/src/l3/tailmerge.ts +6 -2
- package/src/l3/typing.ts +108 -15
- package/src/l3/unreduce.ts +6 -5
- package/src/l3/volatileptr.ts +17 -54
- package/src/mangle.ts +30 -0
- package/src/pattern/engine.ts +361 -73
- package/src/pipeline.ts +1039 -38
- package/src/proto-context.ts +1039 -45
- package/src/proto.ts +555 -40
- package/src/raise/aggmembers.ts +290 -0
- package/src/raise/arrays.ts +5 -1
- package/src/raise/const.ts +25 -0
- package/src/raise/declared-volatile.ts +207 -31
- package/src/raise/divpow2.ts +192 -0
- package/src/raise/floathelpers.ts +254 -33
- package/src/raise/gvn.ts +3 -3
- package/src/raise/latch.ts +162 -6
- package/src/raise/memberarrays.ts +10 -4
- package/src/raise/offsetnames.ts +422 -36
- package/src/raise/pairparams.ts +33 -0
- package/src/raise/paramwidth.ts +196 -6
- package/src/raise/pre-recovery.ts +107 -21
- package/src/raise/pre-structure.ts +73 -0
- package/src/raise/recover.ts +12 -5
- package/src/raise/reroll.ts +674 -0
- package/src/raise/shortcircuit.ts +217 -34
- package/src/raise/softdiv.ts +1 -1
- package/src/raise/storesign.ts +153 -0
- package/src/raise/struct-arrays.ts +30 -11
- package/src/raise/structs.ts +396 -33
- package/src/raise/truncload.ts +4 -3
- package/src/raise/unpre.ts +594 -0
- package/src/raise/unthread.ts +400 -0
- package/src/rank-declare.ts +118 -12
- package/src/rank-variations.ts +186 -35
- package/src/rank.ts +536 -68
- package/src/runtime-helpers.ts +140 -22
- package/src/structure/analysis.ts +217 -8
- package/src/structure/arith-ops.ts +23 -0
- package/src/structure/cross-jumped-tail.ts +216 -0
- package/src/structure/globalaccess.ts +157 -10
- package/src/structure/hazards.ts +16 -64
- package/src/structure/local-statics.ts +145 -9
- package/src/structure/namecoalesce.ts +1 -1
- package/src/structure/pointer-spelling.ts +1106 -0
- package/src/structure/redundant-test.ts +1 -1
- package/src/structure/structure.ts +6192 -1213
- package/src/structure/switch-recover.ts +583 -120
- package/src/structure/unfolded-branch-constant.ts +90 -0
- package/src/symbols.ts +58 -15
- package/src/target.ts +1028 -54
- package/src/trace.ts +49 -17
- package/src/variation-definitions.ts +333 -20
- package/src/variation-gates.ts +4 -0
- package/src/variation-tokens.ts +68 -3
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
// asmlift — how many words of its incoming stack area a Thumb callee's straight-line entry loads.
|
|
2
|
+
//
|
|
3
|
+
// A call nothing declares that stages words at [sp,#0] upward is refused, because the code alone
|
|
4
|
+
// cannot say where its outgoing stack block ends (frontend/stackargs.ts). The callee's body can say
|
|
5
|
+
// where it at least reaches: a compiled callee loads incoming stack word k only as its argument
|
|
6
|
+
// |argRegs|+k, so a body that loads word n-1 takes at least n stack words. That LOWER bound is
|
|
7
|
+
// `StackArgsCall.bodyReads`, and the caller's staged words close it from above.
|
|
8
|
+
//
|
|
9
|
+
// A callee that lifts answers by its lift, which reads every path (frontend/callee-body.ts). This
|
|
10
|
+
// pass is for one that does not (a `bl` to a label of its own, a computed jump), and for a Thumb
|
|
11
|
+
// body handed in as halfwords. It reads the ENTRY only: the sp delta through `push`, `sub sp, #k`
|
|
12
|
+
// and `add sp, #-k`, and a word load `ldr rD, [sp, #k]` with k at or past the delta, of a word
|
|
13
|
+
// nothing earlier in the pass stored, is incoming word (k - delta) / 4. agbcc loads a stack
|
|
14
|
+
// argument used on every path there, but one used on a single branch inside that branch (`br5` in
|
|
15
|
+
// corpus/agbcc-entry-stack.s loads [sp,#4] past a `bne`), so the pass may answer fewer words than
|
|
16
|
+
// the body loads, or none: a smaller lower bound, which the caller's staged words then contradict.
|
|
17
|
+
//
|
|
18
|
+
// THE PASS STOPS, keeping what it read, at the first instruction after which "the next instruction
|
|
19
|
+
// runs with this sp, on every call" or "this frame word holds the caller's word" may no longer hold:
|
|
20
|
+
// - a branch, a call, a `bx`, a `pop`, a write of pc, a directive, or the end of the text;
|
|
21
|
+
// - a label anything outside the debug sections names, which control may reach from elsewhere. A
|
|
22
|
+
// label only debug information names (agbcc -g's `.LFB14:` and `.LM82:`, which `.4byte` lines
|
|
23
|
+
// under `.section .debug*` point at) is followed past;
|
|
24
|
+
// - a frame address copied or added into a register (`mov rD, sp`, `add rD, sp, #k`, `add rD,
|
|
25
|
+
// sp`): the words above it may then be written through that register;
|
|
26
|
+
// - (halfwords) any encoding the decoder below does not model.
|
|
27
|
+
// A store through any other base is followed past: a C function addresses its incoming stack words
|
|
28
|
+
// only through its own frame address, which stops the pass first, so no pointer it was handed or
|
|
29
|
+
// loaded reaches them (`nine` stores to a global between its loads of words 2 and 3).
|
|
30
|
+
// Every load before the stop ran on every call, with the delta tracked to it. It gives NO answer at a
|
|
31
|
+
// write of sp it does not model (`mov sp, rS`, `add sp, rS`), or a delta that would go negative.
|
|
32
|
+
//
|
|
33
|
+
// Two front doors read the same pass: GNU-as text holding the callee's label (the input, or a
|
|
34
|
+
// listing handed in) and the callee's first halfwords out of a linked image (cli elf-bodies.ts
|
|
35
|
+
// `{kind: 'thumb'}`), through a decoder of the prologue subset of the Thumb formats.
|
|
36
|
+
|
|
37
|
+
/** One instruction as the pass reads it. */
|
|
38
|
+
type Step =
|
|
39
|
+
| { readonly kind: 'push'; readonly words: number }
|
|
40
|
+
| { readonly kind: 'sp-add'; readonly bytes: number }
|
|
41
|
+
| { readonly kind: 'load-sp'; readonly off: number }
|
|
42
|
+
| { readonly kind: 'store-sp'; readonly off: number }
|
|
43
|
+
| { readonly kind: 'other' }
|
|
44
|
+
| { readonly kind: 'stop' }
|
|
45
|
+
| { readonly kind: 'refuse' };
|
|
46
|
+
|
|
47
|
+
/** The most incoming words a scan answers: past it the entry is no call a compiler wrote (the Thumb
|
|
48
|
+
* frontend's own arity ceiling, 16 words, less the four argument registers). */
|
|
49
|
+
const MAX_INCOMING_WORDS = 12;
|
|
50
|
+
|
|
51
|
+
/** One past the highest incoming stack word `steps` load, or undefined where they load none or the
|
|
52
|
+
* pass refuses (see the header). */
|
|
53
|
+
function incomingWords(steps: Iterable<Step>): number | undefined {
|
|
54
|
+
let delta = 0;
|
|
55
|
+
const stored = new Set<number>();
|
|
56
|
+
let reads = 0;
|
|
57
|
+
for (const s of steps) {
|
|
58
|
+
if (s.kind === 'stop') {
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
switch (s.kind) {
|
|
62
|
+
case 'refuse':
|
|
63
|
+
return undefined;
|
|
64
|
+
case 'push':
|
|
65
|
+
delta += 4 * s.words;
|
|
66
|
+
break;
|
|
67
|
+
case 'sp-add':
|
|
68
|
+
delta -= s.bytes;
|
|
69
|
+
if (delta < 0 || delta % 4 !== 0) {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
break;
|
|
73
|
+
case 'store-sp':
|
|
74
|
+
if (s.off >= delta) {
|
|
75
|
+
stored.add((s.off - delta) / 4);
|
|
76
|
+
}
|
|
77
|
+
break;
|
|
78
|
+
case 'load-sp': {
|
|
79
|
+
const word = (s.off - delta) / 4;
|
|
80
|
+
if (s.off >= delta && !stored.has(word)) {
|
|
81
|
+
reads = Math.max(reads, word + 1);
|
|
82
|
+
}
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
default:
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return reads === 0 || reads > MAX_INCOMING_WORDS ? undefined : reads;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const SP = /^(?:sp|r13)$/i;
|
|
93
|
+
const PC = /^(?:pc|r15)$/i;
|
|
94
|
+
|
|
95
|
+
/** The register count of a `{…}` list (`{r4-r7, lr}`), or undefined for one it cannot read. */
|
|
96
|
+
function listWords(op: string): number | undefined {
|
|
97
|
+
const m = /^\{(.*)\}$/.exec(op.trim());
|
|
98
|
+
if (m === null) {
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
let n = 0;
|
|
102
|
+
for (const part of m[1].split(',').map((p) => p.trim())) {
|
|
103
|
+
const range = /^r(\d+)\s*-\s*r(\d+)$/i.exec(part);
|
|
104
|
+
if (range !== null) {
|
|
105
|
+
n += Number(range[2]) - Number(range[1]) + 1;
|
|
106
|
+
} else if (/^(?:r\d+|lr|sp|pc|ip|fp|sl|sb)$/i.test(part)) {
|
|
107
|
+
n += 1;
|
|
108
|
+
} else {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return n;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const imm = (op: string | undefined): number | undefined => {
|
|
116
|
+
const m = op === undefined ? null : /^#\s*(-?)(0x[0-9a-f]+|\d+)$/i.exec(op.trim());
|
|
117
|
+
return m === null ? undefined : (m[1] === '-' ? -1 : 1) * Number(m[2]);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/** `[sp]` / `[sp, #k]` → k; undefined for any other address. */
|
|
121
|
+
function spAddress(op: string): number | undefined {
|
|
122
|
+
const m = /^\[\s*(\w+)\s*(?:,\s*(#[^\]]+))?\]$/.exec(op.trim());
|
|
123
|
+
if (m === null || !SP.test(m[1])) {
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
return m[2] === undefined ? 0 : imm(m[2]);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Split an operand list at its top-level commas. */
|
|
130
|
+
function operands(s: string): string[] {
|
|
131
|
+
const out: string[] = [];
|
|
132
|
+
let depth = 0;
|
|
133
|
+
let cur = '';
|
|
134
|
+
for (const ch of s) {
|
|
135
|
+
if (ch === '[' || ch === '{') {
|
|
136
|
+
depth++;
|
|
137
|
+
} else if (ch === ']' || ch === '}') {
|
|
138
|
+
depth--;
|
|
139
|
+
}
|
|
140
|
+
if (ch === ',' && depth === 0) {
|
|
141
|
+
out.push(cur.trim());
|
|
142
|
+
cur = '';
|
|
143
|
+
} else {
|
|
144
|
+
cur += ch;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (cur.trim() !== '') {
|
|
148
|
+
out.push(cur.trim());
|
|
149
|
+
}
|
|
150
|
+
return out;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** One GNU-as Thumb line, read as a step. */
|
|
154
|
+
function textStep(line: string): Step {
|
|
155
|
+
const m = /^([a-z.]+)\s*(.*)$/i.exec(line);
|
|
156
|
+
if (m === null) {
|
|
157
|
+
return { kind: 'stop' };
|
|
158
|
+
}
|
|
159
|
+
const mn = m[1].toLowerCase();
|
|
160
|
+
const ops = operands(m[2]);
|
|
161
|
+
if (mn.startsWith('.')) {
|
|
162
|
+
return { kind: 'stop' };
|
|
163
|
+
}
|
|
164
|
+
if (mn === 'push') {
|
|
165
|
+
const n = ops.length === 1 ? listWords(ops[0]) : undefined;
|
|
166
|
+
return n === undefined ? { kind: 'stop' } : { kind: 'push', words: n };
|
|
167
|
+
}
|
|
168
|
+
const dest = ops[0] ?? '';
|
|
169
|
+
// `add sp, #k` / `add sp, sp, #k` / `sub …`: the only writes of sp the pass follows
|
|
170
|
+
if ((mn === 'add' || mn === 'sub') && SP.test(dest) && (ops.length === 2 || (ops.length === 3 && SP.test(ops[1])))) {
|
|
171
|
+
const k = imm(ops[ops.length - 1]);
|
|
172
|
+
if (k === undefined) {
|
|
173
|
+
return { kind: 'refuse' };
|
|
174
|
+
}
|
|
175
|
+
return { kind: 'sp-add', bytes: mn === 'add' ? k : -k };
|
|
176
|
+
}
|
|
177
|
+
if (
|
|
178
|
+
/^(?:b|bl|blx|bx|pop|swi|svc|bkpt)$/.test(mn) ||
|
|
179
|
+
/^b(?:eq|ne|cs|hs|cc|lo|mi|pl|vs|vc|hi|ls|ge|lt|gt|le|al)$/.test(mn)
|
|
180
|
+
) {
|
|
181
|
+
return { kind: 'stop' };
|
|
182
|
+
}
|
|
183
|
+
if (PC.test(dest)) {
|
|
184
|
+
return { kind: 'stop' };
|
|
185
|
+
}
|
|
186
|
+
if (mn === 'ldr' && ops.length === 2) {
|
|
187
|
+
const off = spAddress(ops[1]);
|
|
188
|
+
if (off !== undefined) {
|
|
189
|
+
return { kind: 'load-sp', off };
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (mn === 'str' && ops.length === 2) {
|
|
193
|
+
const off = spAddress(ops[1]);
|
|
194
|
+
if (off !== undefined) {
|
|
195
|
+
return { kind: 'store-sp', off };
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (SP.test(dest) && !/^(?:cmp|cmn|tst)$/.test(mn)) {
|
|
199
|
+
return { kind: 'refuse' };
|
|
200
|
+
}
|
|
201
|
+
// a frame address copied out (`mov rD, sp`, `add rD, sp, #k`), sp read as data any other way, or
|
|
202
|
+
// a frame access this does not read as a word
|
|
203
|
+
if (ops.slice(1).some((o) => SP.test(o) || /^\[\s*(?:sp|r13)\b/i.test(o))) {
|
|
204
|
+
return { kind: 'stop' };
|
|
205
|
+
}
|
|
206
|
+
return REGISTER_ONLY.test(mn) ? { kind: 'other' } : { kind: 'stop' };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** The mnemonics, in both their UAL and pre-UAL spellings, that touch no frame word the pass
|
|
210
|
+
* tracks once a sp base has been ruled out: the data-processing forms, the loads and the stores
|
|
211
|
+
* (see the header). Any other one stops it. */
|
|
212
|
+
const REGISTER_ONLY =
|
|
213
|
+
/^(?:(?:mov|mvn|add|adc|sub|sbc|neg|rsb|mul|and|orr|eor|bic|lsl|lsr|asr|ror)s?|cmp|cmn|tst|nop|ldr|ldrh|ldrb|ldrsh|ldrsb|ldsh|ldsb|str|strh|strb|ldm|ldmia|stm|stmia)$/;
|
|
214
|
+
|
|
215
|
+
const escaped = (s: string): string => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
216
|
+
|
|
217
|
+
/** The incoming stack words the entry of `callee` loads, read out of GNU-as `text` holding its
|
|
218
|
+
* `callee:` label: the lines after it, to the first stop (see the header). Undefined where the
|
|
219
|
+
* text holds no such label or the entry loads none. */
|
|
220
|
+
export function thumbEntryWordsFromText(callee: string, text: string): number | undefined {
|
|
221
|
+
const lines = text.split('\n');
|
|
222
|
+
const head = new RegExp(`^\\s*${escaped(callee)}\\s*:(.*)$`);
|
|
223
|
+
const at = lines.findIndex((l) => head.test(l.replace(/@.*$/, '')));
|
|
224
|
+
if (at < 0) {
|
|
225
|
+
return undefined;
|
|
226
|
+
}
|
|
227
|
+
const rest = head.exec(lines[at].replace(/@.*$/, ''))![1].trim();
|
|
228
|
+
const named = labelsNamed(lines);
|
|
229
|
+
function* steps(): Generator<Step> {
|
|
230
|
+
for (const raw of [rest, ...lines.slice(at + 1)]) {
|
|
231
|
+
let line = raw.replace(/@.*$/, '').trim();
|
|
232
|
+
// labels only the debug sections name are no join; the line after one is read as any other
|
|
233
|
+
for (let m = LABEL.exec(line); m !== null; m = LABEL.exec(line)) {
|
|
234
|
+
if (named.has(m[1])) {
|
|
235
|
+
yield { kind: 'stop' };
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
line = line.slice(m[0].length).trim();
|
|
239
|
+
}
|
|
240
|
+
if (line === '') {
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
yield textStep(line);
|
|
244
|
+
}
|
|
245
|
+
yield { kind: 'stop' };
|
|
246
|
+
}
|
|
247
|
+
return incomingWords(steps());
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** A label at the start of a line: its name, then the rest of the line. */
|
|
251
|
+
const LABEL = /^([\w.$]+)\s*:/;
|
|
252
|
+
|
|
253
|
+
/** Every name an instruction or a directive outside a debug section (`.debug*`, `.stab*`, `.line`)
|
|
254
|
+
* spells, which a label of that name may be reached through. */
|
|
255
|
+
function labelsNamed(lines: readonly string[]): Set<string> {
|
|
256
|
+
const out = new Set<string>();
|
|
257
|
+
let debug = false;
|
|
258
|
+
for (const raw of lines) {
|
|
259
|
+
const line = raw.replace(/@.*$/, '').replace(LABEL, '').trim();
|
|
260
|
+
const section = /^\.(?:section\s+([\w.$]+)|(text|data|bss)\b)/.exec(line);
|
|
261
|
+
if (section !== null) {
|
|
262
|
+
debug = /^\.(?:debug|stab|line)/.test(section[1] ?? '');
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
if (debug || /^\.stab/.test(line)) {
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
for (const m of line.matchAll(/[\w.$]+/g)) {
|
|
269
|
+
out.add(m[0]);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return out;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/** One Thumb halfword of the prologue subset, read as a step: every format but the branches, the
|
|
276
|
+
* calls, `pop` and the software interrupt, which stop the pass, as do `bl`'s two halves and any
|
|
277
|
+
* encoding this does not name. */
|
|
278
|
+
function halfwordStep(h: number): Step {
|
|
279
|
+
// format 14: push {rlist[, lr]} / pop
|
|
280
|
+
if ((h & 0xfe00) === 0xb400) {
|
|
281
|
+
let n = (h >> 8) & 1;
|
|
282
|
+
for (let r = 0; r < 8; r++) {
|
|
283
|
+
n += (h >> r) & 1;
|
|
284
|
+
}
|
|
285
|
+
return { kind: 'push', words: n };
|
|
286
|
+
}
|
|
287
|
+
// format 13: add sp, #±imm7*4
|
|
288
|
+
if ((h & 0xff00) === 0xb000) {
|
|
289
|
+
const bytes = (h & 0x7f) * 4;
|
|
290
|
+
return { kind: 'sp-add', bytes: (h & 0x80) !== 0 ? -bytes : bytes };
|
|
291
|
+
}
|
|
292
|
+
// format 11: ldr / str rD, [sp, #imm8*4]
|
|
293
|
+
if ((h & 0xf000) === 0x9000) {
|
|
294
|
+
const off = (h & 0xff) * 4;
|
|
295
|
+
return (h & 0x0800) !== 0 ? { kind: 'load-sp', off } : { kind: 'store-sp', off };
|
|
296
|
+
}
|
|
297
|
+
// format 12: add rD, sp, #imm (a frame address copied out) / add rD, pc, #imm
|
|
298
|
+
if ((h & 0xf000) === 0xa000) {
|
|
299
|
+
return (h & 0x0800) !== 0 ? { kind: 'stop' } : { kind: 'other' };
|
|
300
|
+
}
|
|
301
|
+
// formats 1-4: shifts, add/sub, the 8-bit immediates, the ALU: low registers only
|
|
302
|
+
if (h < 0x4400) {
|
|
303
|
+
return { kind: 'other' };
|
|
304
|
+
}
|
|
305
|
+
// format 5: the high-register add/cmp/mov, and bx
|
|
306
|
+
if ((h & 0xfc00) === 0x4400) {
|
|
307
|
+
const op = (h >> 8) & 3;
|
|
308
|
+
const rs = ((h >> 3) & 7) + ((h >> 6) & 1) * 8;
|
|
309
|
+
const rd = (h & 7) + ((h >> 7) & 1) * 8;
|
|
310
|
+
if (op === 3 || ((op === 0 || op === 2) && rd === 15)) {
|
|
311
|
+
return { kind: 'stop' };
|
|
312
|
+
}
|
|
313
|
+
if ((op === 0 || op === 2) && rd === 13) {
|
|
314
|
+
return { kind: 'refuse' };
|
|
315
|
+
}
|
|
316
|
+
return rs === 13 ? { kind: 'stop' } : { kind: 'other' };
|
|
317
|
+
}
|
|
318
|
+
// format 6: ldr rD, [pc, #imm]
|
|
319
|
+
if ((h & 0xf800) === 0x4800) {
|
|
320
|
+
return { kind: 'other' };
|
|
321
|
+
}
|
|
322
|
+
// formats 7-10: transfers through a low register, which holds no frame address (see the header);
|
|
323
|
+
// format 15: ldmia / stmia through one
|
|
324
|
+
if ((h & 0xf000) === 0x5000 || (h & 0xe000) === 0x6000 || (h & 0xf000) === 0x8000 || (h & 0xf000) === 0xc000) {
|
|
325
|
+
return { kind: 'other' };
|
|
326
|
+
}
|
|
327
|
+
return { kind: 'stop' };
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/** The incoming stack words the entry whose halfwords, from the entry on, are `halfwords` loads
|
|
331
|
+
* (see the header). Undefined where it loads none. */
|
|
332
|
+
export function thumbEntryWordsFromHalfwords(halfwords: readonly number[]): number | undefined {
|
|
333
|
+
function* steps(): Generator<Step> {
|
|
334
|
+
for (const h of halfwords) {
|
|
335
|
+
yield halfwordStep(h & 0xffff);
|
|
336
|
+
}
|
|
337
|
+
yield { kind: 'stop' };
|
|
338
|
+
}
|
|
339
|
+
return incomingWords(steps());
|
|
340
|
+
}
|