@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.
Files changed (117) hide show
  1. package/README.md +25 -24
  2. package/package.json +1 -1
  3. package/src/aggregate.ts +149 -0
  4. package/src/backend/c.ts +2 -0
  5. package/src/backend/cfamily.ts +197 -61
  6. package/src/backend/cpp.ts +3 -1
  7. package/src/backend/lines.ts +10 -0
  8. package/src/backend/pascal.ts +47 -16
  9. package/src/cdecl/parse.ts +21 -0
  10. package/src/contracts.ts +246 -36
  11. package/src/declare.ts +69 -20
  12. package/src/frontend/arm-body.ts +155 -0
  13. package/src/frontend/asmdata.ts +9 -2
  14. package/src/frontend/call-plan.ts +1117 -61
  15. package/src/frontend/callee-body.ts +370 -0
  16. package/src/frontend/dead-reads.ts +628 -0
  17. package/src/frontend/device-pins.ts +33 -4
  18. package/src/frontend/emit.ts +4 -3
  19. package/src/frontend/fpu.ts +224 -17
  20. package/src/frontend/frame-objects.ts +1689 -206
  21. package/src/frontend/frontend.ts +84 -3
  22. package/src/frontend/high-half.ts +53 -4
  23. package/src/frontend/jump-table.ts +545 -0
  24. package/src/frontend/local-object.ts +158 -32
  25. package/src/frontend/mips.ts +2296 -217
  26. package/src/frontend/own-struct-return.ts +500 -0
  27. package/src/frontend/ppc.ts +2748 -245
  28. package/src/frontend/register-struct-call.ts +399 -0
  29. package/src/frontend/reloc-symbol.ts +22 -7
  30. package/src/frontend/ssa.ts +1721 -43
  31. package/src/frontend/stackargs.ts +199 -72
  32. package/src/frontend/thumb-entry.ts +340 -0
  33. package/src/frontend/thumb.ts +2400 -325
  34. package/src/ir/alias.ts +65 -2
  35. package/src/ir/bits.ts +1699 -4
  36. package/src/ir/core.ts +254 -9
  37. package/src/ir/discipline.ts +86 -21
  38. package/src/ir/entry.ts +157 -0
  39. package/src/{structure → ir}/loops.ts +6 -2
  40. package/src/ir/opcodes.ts +137 -9
  41. package/src/ir/parse.ts +1 -0
  42. package/src/ir/simplify.ts +61 -10
  43. package/src/ir/types.ts +86 -2
  44. package/src/ir/verify.ts +96 -11
  45. package/src/l3/argbase.ts +2 -0
  46. package/src/l3/ast.ts +582 -18
  47. package/src/l3/basecse.ts +68 -15
  48. package/src/l3/caseendgoto.ts +223 -0
  49. package/src/l3/coalesce.ts +42 -14
  50. package/src/l3/constinit.ts +410 -0
  51. package/src/l3/dce.ts +53 -9
  52. package/src/l3/initfirst.ts +42 -11
  53. package/src/l3/intcell.ts +2 -1
  54. package/src/l3/narrowdecl.ts +77 -36
  55. package/src/l3/pollguard.ts +8 -2
  56. package/src/l3/ptrcell.ts +33 -34
  57. package/src/l3/ptrfield.ts +123 -8
  58. package/src/l3/regspell.ts +11 -4
  59. package/src/l3/reindex.ts +295 -99
  60. package/src/l3/scopebase.ts +2 -0
  61. package/src/l3/storage.ts +98 -1
  62. package/src/l3/symbol-refs.ts +84 -6
  63. package/src/l3/tailmerge.ts +6 -2
  64. package/src/l3/typing.ts +108 -15
  65. package/src/l3/unreduce.ts +6 -5
  66. package/src/l3/volatileptr.ts +17 -54
  67. package/src/mangle.ts +30 -0
  68. package/src/pattern/engine.ts +361 -73
  69. package/src/pipeline.ts +1039 -38
  70. package/src/proto-context.ts +1039 -45
  71. package/src/proto.ts +555 -40
  72. package/src/raise/aggmembers.ts +290 -0
  73. package/src/raise/arrays.ts +5 -1
  74. package/src/raise/const.ts +25 -0
  75. package/src/raise/declared-volatile.ts +207 -31
  76. package/src/raise/divpow2.ts +192 -0
  77. package/src/raise/floathelpers.ts +254 -33
  78. package/src/raise/gvn.ts +3 -3
  79. package/src/raise/latch.ts +162 -6
  80. package/src/raise/memberarrays.ts +10 -4
  81. package/src/raise/offsetnames.ts +422 -36
  82. package/src/raise/pairparams.ts +33 -0
  83. package/src/raise/paramwidth.ts +196 -6
  84. package/src/raise/pre-recovery.ts +107 -21
  85. package/src/raise/pre-structure.ts +73 -0
  86. package/src/raise/recover.ts +12 -5
  87. package/src/raise/reroll.ts +674 -0
  88. package/src/raise/shortcircuit.ts +217 -34
  89. package/src/raise/softdiv.ts +1 -1
  90. package/src/raise/storesign.ts +153 -0
  91. package/src/raise/struct-arrays.ts +30 -11
  92. package/src/raise/structs.ts +396 -33
  93. package/src/raise/truncload.ts +4 -3
  94. package/src/raise/unpre.ts +594 -0
  95. package/src/raise/unthread.ts +400 -0
  96. package/src/rank-declare.ts +118 -12
  97. package/src/rank-variations.ts +186 -35
  98. package/src/rank.ts +536 -68
  99. package/src/runtime-helpers.ts +140 -22
  100. package/src/structure/analysis.ts +217 -8
  101. package/src/structure/arith-ops.ts +23 -0
  102. package/src/structure/cross-jumped-tail.ts +216 -0
  103. package/src/structure/globalaccess.ts +157 -10
  104. package/src/structure/hazards.ts +16 -64
  105. package/src/structure/local-statics.ts +145 -9
  106. package/src/structure/namecoalesce.ts +1 -1
  107. package/src/structure/pointer-spelling.ts +1106 -0
  108. package/src/structure/redundant-test.ts +1 -1
  109. package/src/structure/structure.ts +6192 -1213
  110. package/src/structure/switch-recover.ts +583 -120
  111. package/src/structure/unfolded-branch-constant.ts +90 -0
  112. package/src/symbols.ts +58 -15
  113. package/src/target.ts +1028 -54
  114. package/src/trace.ts +49 -17
  115. package/src/variation-definitions.ts +333 -20
  116. package/src/variation-gates.ts +4 -0
  117. package/src/variation-tokens.ts +68 -3
@@ -0,0 +1,155 @@
1
+ // asmlift — what a straight-line ARM (A32) body reads of its argument registers, and the Thumb
2
+ // interworking veneer that reaches one.
3
+ //
4
+ // A GBA callee written in ARM is reached from Thumb through a veneer (`bx pc; nop` then ARM `b sym`),
5
+ // and the Thumb frontend lifts no ARM, so the callee-body bound (frontend/callee-body.ts) has nothing
6
+ // to lift. Where the body is straight-line, though, what it reads needs no lift: one pass over its
7
+ // instruction words, in order, says which argument register it reads before writing it.
8
+ //
9
+ // THE SCAN ANSWERS ONLY WHERE EVERY PATH IS THE ONE IT READ. It reads instruction words from the
10
+ // entry to the first `bx lr`, and answers nothing (undefined) on:
11
+ // - any instruction whose condition is not AL: a conditional one makes the body two paths;
12
+ // - any branch, call or other write of pc but that final `bx lr` (`b`, `bl`, `bx` of another
13
+ // register, `mov pc, …`, `ldr pc, …`, `ldm`/`stm`): the body is no longer the words that follow;
14
+ // - any write of lr, which makes the final `bx lr` go somewhere else;
15
+ // - any use of sp: a stack argument read is a fifth word, and a frame is no straight-line scratch;
16
+ // - any instruction it does not model (coprocessor, swi, status register moves, swap, long
17
+ // multiply, the halfword forms ARMv4T leaves undefined), and running out of words before `bx lr`.
18
+ // What it answers is the number of argument words read: one past the highest of r0-r3 that some
19
+ // instruction reads before any instruction writes it, and the final `bx lr` reads r0 where nothing
20
+ // wrote it, since r0 is the return register and holds the caller's first argument there (`void
21
+ // *id(void *p) { return p; }` is a lone `bx lr`), unless the callee is declared to return nothing.
22
+ // That is an UPPER bound on what the body reads of its arguments, as the callee-body bound is.
23
+ // KNOWN GAP: a return in r0:r1 that nothing declares counts r0 only.
24
+
25
+ /** The registers the scan counts argument words in, in order: the A32 procedure call standard's. */
26
+ export const ARM_ARGUMENT_REGISTERS: readonly string[] = ['r0', 'r1', 'r2', 'r3'];
27
+
28
+ /** Whether the scan's count is a count of `target`'s argument words: where the target passes its
29
+ * arguments in the registers it counts, so the caller's argument word k is the register the scan
30
+ * reads as k. Read off the ABI's `argRegs`, the datum the callers of the bound index it by. */
31
+ export function scansArgumentsOf(target: { readonly argRegs: readonly string[] }): boolean {
32
+ return (
33
+ target.argRegs.length === ARM_ARGUMENT_REGISTERS.length &&
34
+ target.argRegs.every((r, k) => r === ARM_ARGUMENT_REGISTERS[k])
35
+ );
36
+ }
37
+
38
+ /** The argument words a straight-line ARM body reads at most, or undefined (see the header). `words`
39
+ * are its instruction words from the entry, in order; `returnsValue` is false only where the
40
+ * callee is declared to return nothing, so that its `bx lr` reads no r0. */
41
+ export function armArgumentWords(words: readonly number[], returnsValue = true): number | undefined {
42
+ const written = new Set<number>();
43
+ let reads = 0;
44
+ for (const w of words) {
45
+ if (w >>> 28 !== 0xe) {
46
+ return undefined;
47
+ }
48
+ if (w >>> 0 === 0xe12fff1e) {
49
+ return returnsValue && !written.has(0) ? Math.max(reads, 1) : reads; // bx lr, which returns r0
50
+ }
51
+ const effect = decode(w >>> 0);
52
+ if (effect === undefined) {
53
+ return undefined;
54
+ }
55
+ const regs = [...effect.reads, ...effect.writes];
56
+ if (regs.includes(SP) || effect.writes.includes(LR) || effect.writes.includes(PC)) {
57
+ return undefined;
58
+ }
59
+ for (const r of effect.reads) {
60
+ if (r < 4 && !written.has(r)) {
61
+ reads = Math.max(reads, r + 1);
62
+ }
63
+ }
64
+ effect.writes.forEach((r) => written.add(r));
65
+ }
66
+ return undefined;
67
+ }
68
+
69
+ const SP = 13;
70
+ const LR = 14;
71
+ const PC = 15;
72
+
73
+ interface Effect {
74
+ reads: number[];
75
+ writes: number[];
76
+ }
77
+
78
+ const reg = (w: number, at: number): number => (w >>> at) & 0xf;
79
+
80
+ /** The registers one unconditional ARMv4T instruction reads and writes, or undefined for one this
81
+ * scan does not model. */
82
+ function decode(w: number): Effect | undefined {
83
+ // MUL / MLA: 0000 00AS dddd nnnn ssss 1001 mmmm
84
+ if ((w & 0x0fc000f0) === 0x00000090) {
85
+ const accumulate = (w >>> 21) & 1;
86
+ const reads = [reg(w, 0), reg(w, 8), ...(accumulate ? [reg(w, 12)] : [])];
87
+ return { reads, writes: [reg(w, 16)] };
88
+ }
89
+ // LDRH / STRH / LDRSB / LDRSH: 000P UIWL nnnn dddd oooo 1SH1 mmmm, SH ≠ 00
90
+ if ((w & 0x0e000090) === 0x00000090) {
91
+ const sh = (w >>> 5) & 3;
92
+ const load = (w >>> 20) & 1;
93
+ if (sh === 0 || (!load && sh !== 1)) {
94
+ return undefined; // swap, or a store form ARMv4T leaves undefined
95
+ }
96
+ const immediate = (w >>> 22) & 1;
97
+ return transfer(w, load === 1, immediate === 1 ? [] : [reg(w, 0)]);
98
+ }
99
+ // data processing: 00I oooo S nnnn dddd operand2
100
+ if ((w & 0x0c000000) === 0) {
101
+ const op = (w >>> 21) & 0xf;
102
+ const setsFlags = (w >>> 20) & 1;
103
+ const compare = op >= 8 && op <= 11;
104
+ if (compare && !setsFlags) {
105
+ return undefined; // MRS, MSR, BX and the rest of that space
106
+ }
107
+ const reads: number[] = [];
108
+ if (op !== 13 && op !== 15) {
109
+ reads.push(reg(w, 16)); // every op but MOV and MVN reads Rn
110
+ }
111
+ if (((w >>> 25) & 1) === 0) {
112
+ reads.push(reg(w, 0));
113
+ if ((w >>> 4) & 1) {
114
+ reads.push(reg(w, 8)); // shift by a register
115
+ }
116
+ }
117
+ return { reads, writes: compare ? [] : [reg(w, 12)] };
118
+ }
119
+ // LDR / STR / LDRB / STRB: 01I PUBW L nnnn dddd offset
120
+ if ((w & 0x0c000000) === 0x04000000) {
121
+ const registerOffset = (w >>> 25) & 1;
122
+ if (registerOffset && (w >>> 4) & 1) {
123
+ return undefined; // the undefined-instruction space
124
+ }
125
+ return transfer(w, ((w >>> 20) & 1) === 1, registerOffset ? [reg(w, 0)] : []);
126
+ }
127
+ return undefined;
128
+ }
129
+
130
+ /** A single load or store: it reads its base and `offset`, a store its source too; a load writes
131
+ * its destination, and a post-indexed or write-back form its base. */
132
+ function transfer(w: number, load: boolean, offset: number[]): Effect | undefined {
133
+ const base = reg(w, 16);
134
+ const data = reg(w, 12);
135
+ const writeBack = ((w >>> 24) & 1) === 0 || ((w >>> 21) & 1) === 1;
136
+ if (data === PC || (writeBack && base === PC)) {
137
+ return undefined;
138
+ }
139
+ return {
140
+ reads: [base, ...offset, ...(load ? [] : [data])],
141
+ writes: [...(load ? [data] : []), ...(writeBack ? [base] : [])],
142
+ };
143
+ }
144
+
145
+ /** Where a Thumb interworking veneer at a word-aligned `entry` branches to: its two halfwords are
146
+ * `bx pc` (0x4778) and `nop` (`mov r8, r8`, 0x46c0), which enter ARM state at `entry + 4`, and the
147
+ * ARM word there is an unconditional `b`. Undefined for anything else: the veneer is then some
148
+ * other code, and nothing here reads it. */
149
+ export function thumbToArmVeneerTarget(entry: number, h0: number, h1: number, armWord: number): number | undefined {
150
+ if (entry % 4 !== 0 || h0 !== 0x4778 || h1 !== 0x46c0 || (armWord & 0xff000000) >>> 0 !== 0xea000000) {
151
+ return undefined;
152
+ }
153
+ const offset = ((armWord & 0x00ffffff) << 8) >> 6; // sign-extended, in bytes
154
+ return (entry + 4 + 8 + offset) >>> 0;
155
+ }
@@ -45,13 +45,20 @@ export interface AsmSymbol {
45
45
  index: number;
46
46
  }
47
47
 
48
- const readU32 = (b: Uint8Array, off: number, big: boolean): number =>
48
+ /** The word at byte `off` of `b`, in the object's byte order. */
49
+ export const readU32 = (b: Uint8Array, off: number, big: boolean): number =>
49
50
  big
50
51
  ? ((b[off] << 24) | (b[off + 1] << 16) | (b[off + 2] << 8) | b[off + 3]) >>> 0
51
52
  : ((b[off + 3] << 24) | (b[off + 2] << 16) | (b[off + 1] << 8) | b[off]) >>> 0;
52
53
 
53
- // Parse an `objdump -r` VALUE field: `sw_jt+0x00000020` / `.text` / `@15` → {sym, addend}.
54
+ // Parse an `objdump -r` VALUE field: `sw_jt+0x00000020` / `.text` / `@15` → {sym, addend}. A
55
+ // negative RELA addend prints as `garr-0x00000004` (mwcc's `&garr[-1]`, compiled), and is kept as
56
+ // the same 32-bit word a positive one is.
54
57
  function parseRelocValue(v: string): { sym: string; addend: number } {
58
+ const minus = v.match(/^(.+)-(0x[0-9a-f]+)$/i);
59
+ if (minus) {
60
+ return { sym: minus[1], addend: -parseInt(minus[2], 16) >>> 0 };
61
+ }
55
62
  const plus = v.indexOf('+');
56
63
  if (plus < 0) {
57
64
  return { sym: v, addend: 0 };