@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.
Files changed (130) hide show
  1. package/README.md +36 -31
  2. package/package.json +1 -1
  3. package/src/aggregate.ts +397 -0
  4. package/src/backend/c.ts +6 -2
  5. package/src/backend/cfamily.ts +370 -56
  6. package/src/backend/cpp.ts +116 -10
  7. package/src/backend/pascal.ts +69 -11
  8. package/src/cdecl/constant.ts +59 -0
  9. package/src/cdecl/lex.ts +209 -0
  10. package/src/cdecl/parse.ts +975 -0
  11. package/src/cdecl/spell.ts +69 -0
  12. package/src/codegen-flags.ts +38 -6
  13. package/src/compiler-diagnostics.ts +194 -0
  14. package/src/contracts.ts +777 -84
  15. package/src/declare.ts +132 -13
  16. package/src/frontend/asmdata.ts +26 -6
  17. package/src/frontend/call-plan.ts +1590 -0
  18. package/src/frontend/dead-reads.ts +628 -0
  19. package/src/frontend/device-pins.ts +296 -0
  20. package/src/frontend/emit.ts +4 -3
  21. package/src/frontend/errors.ts +5 -0
  22. package/src/frontend/flags-edge.ts +101 -0
  23. package/src/frontend/fpu.ts +374 -0
  24. package/src/frontend/frame-objects.ts +3171 -0
  25. package/src/frontend/frontend.ts +64 -3
  26. package/src/frontend/high-half.ts +55 -5
  27. package/src/frontend/jump-table.ts +530 -0
  28. package/src/frontend/local-object.ts +616 -0
  29. package/src/frontend/mips.ts +2392 -208
  30. package/src/frontend/opaque.ts +97 -18
  31. package/src/frontend/own-struct-return.ts +500 -0
  32. package/src/frontend/ppc.ts +3137 -267
  33. package/src/frontend/register-struct-call.ts +484 -0
  34. package/src/frontend/reloc-symbol.ts +109 -13
  35. package/src/frontend/splat.ts +108 -5
  36. package/src/frontend/ssa.ts +2098 -89
  37. package/src/frontend/stackargs.ts +172 -42
  38. package/src/frontend/thumb.ts +4563 -2091
  39. package/src/ir/alias.ts +71 -0
  40. package/src/ir/bits.ts +681 -1
  41. package/src/ir/core.ts +199 -6
  42. package/src/ir/discipline.ts +313 -0
  43. package/src/ir/entry.ts +159 -0
  44. package/src/ir/float-bits.ts +47 -0
  45. package/src/{structure → ir}/loops.ts +6 -2
  46. package/src/ir/opcodes.ts +193 -121
  47. package/src/ir/parse.ts +1 -0
  48. package/src/ir/types.ts +202 -2
  49. package/src/ir/verify.ts +219 -3
  50. package/src/l3/address.ts +5 -0
  51. package/src/l3/argcopy.ts +38 -6
  52. package/src/l3/ast.ts +467 -23
  53. package/src/l3/basecse.ts +33 -5
  54. package/src/l3/coalesce.ts +21 -13
  55. package/src/l3/constinit.ts +382 -0
  56. package/src/l3/dce.ts +34 -12
  57. package/src/l3/gates.ts +9 -3
  58. package/src/l3/hoist.ts +8 -7
  59. package/src/l3/initfirst.ts +68 -19
  60. package/src/l3/intcell.ts +98 -0
  61. package/src/l3/mentions.ts +10 -4
  62. package/src/l3/narrowdecl.ts +259 -0
  63. package/src/l3/nearbase.ts +10 -5
  64. package/src/l3/parkfirst.ts +8 -5
  65. package/src/l3/pollguard.ts +11 -4
  66. package/src/l3/ptrcell.ts +354 -0
  67. package/src/l3/ptrfield.ts +123 -8
  68. package/src/l3/regspell.ts +26 -7
  69. package/src/l3/reindex.ts +293 -103
  70. package/src/l3/storage.ts +98 -1
  71. package/src/l3/symbol-refs.ts +182 -23
  72. package/src/l3/tailmerge.ts +9 -20
  73. package/src/l3/typing.ts +237 -32
  74. package/src/l3/unreduce.ts +10 -9
  75. package/src/l3/volatileptr.ts +17 -54
  76. package/src/l3/volstore.ts +96 -7
  77. package/src/mangle.ts +41 -1
  78. package/src/pattern/engine.ts +373 -82
  79. package/src/pipeline.ts +589 -27
  80. package/src/proto-context.ts +1648 -0
  81. package/src/proto.ts +1203 -49
  82. package/src/raise/aggmembers.ts +290 -0
  83. package/src/raise/arrays.ts +8 -3
  84. package/src/raise/declared-volatile.ts +307 -0
  85. package/src/raise/divpow2.ts +2 -2
  86. package/src/raise/errors.ts +17 -0
  87. package/src/raise/floathelpers.ts +464 -0
  88. package/src/raise/globalshape.ts +11 -5
  89. package/src/raise/gvn.ts +29 -8
  90. package/src/raise/latch.ts +162 -6
  91. package/src/raise/memberarrays.ts +10 -4
  92. package/src/raise/narrow.ts +29 -2
  93. package/src/raise/narrowlocal.ts +11 -6
  94. package/src/raise/offsetnames.ts +494 -0
  95. package/src/raise/pairparams.ts +114 -0
  96. package/src/raise/paramwidth.ts +203 -6
  97. package/src/raise/pre-recovery.ts +174 -17
  98. package/src/raise/pre-structure.ts +54 -0
  99. package/src/raise/recover.ts +26 -15
  100. package/src/raise/reroll.ts +694 -0
  101. package/src/raise/retsink.ts +1 -1
  102. package/src/raise/shortcircuit.ts +66 -30
  103. package/src/raise/softdiv.ts +21 -23
  104. package/src/raise/storesign.ts +153 -0
  105. package/src/raise/struct-arrays.ts +34 -14
  106. package/src/raise/structs.ts +674 -98
  107. package/src/raise/truncload.ts +367 -0
  108. package/src/raise/unthread.ts +400 -0
  109. package/src/raise/widehelpers.ts +106 -0
  110. package/src/rank-declare.ts +143 -12
  111. package/src/rank-variations.ts +117 -31
  112. package/src/rank.ts +563 -122
  113. package/src/runtime-helpers.ts +405 -0
  114. package/src/stillborn.ts +289 -0
  115. package/src/structure/analysis.ts +1449 -449
  116. package/src/structure/arith-ops.ts +23 -0
  117. package/src/structure/globalaccess.ts +111 -9
  118. package/src/structure/hazards.ts +641 -114
  119. package/src/structure/local-statics.ts +517 -0
  120. package/src/structure/namecoalesce.ts +2 -2
  121. package/src/structure/pointer-spelling.ts +1106 -0
  122. package/src/structure/redundant-test.ts +99 -0
  123. package/src/structure/structure.ts +3478 -975
  124. package/src/structure/switch-recover.ts +403 -178
  125. package/src/symbols.ts +44 -3
  126. package/src/target.ts +1387 -71
  127. package/src/trace.ts +66 -28
  128. package/src/variation-definitions.ts +243 -13
  129. package/src/variation-gates.ts +2 -0
  130. package/src/variation-tokens.ts +25 -7
package/src/declare.ts CHANGED
@@ -19,8 +19,10 @@
19
19
  // ldrb+lsl+asr where u8 is ldrb alone, so a guessed signedness is a wrong-bytes decl;
20
20
  // • `volatile` is load-bearing (a non-volatile MMIO decl lets the compiler fold/reorder
21
21
  // accesses), `const` is the ROM-table spelling;
22
- // • code symbols get `void Name(void);` ONLY when value-referenced — call targets are never
23
- // in `symbolRefs` (core excludes them: prototyping a called symbol is C89 poison);
22
+ // • code symbols get `void Name(void);` when value-referenced, and a CALL TARGET gets its own
23
+ // prototype re-spelled from the project's `FnProto` — only where that states both a typed
24
+ // parameter list and a `returns`, because `void Name(void);` over a call that passes
25
+ // arguments is C89 poison (core decides it: `spellableProto`);
24
26
  // • nothing guesses, with TWO documented exceptions: a SHAPED symbol without the facts to
25
27
  // declare faithfully is SKIPPED — the candidate then fails to compile LOUDLY and is
26
28
  // dropped by rankBy. Exception one is the 4-byte signless-non-pointer cell (see
@@ -51,6 +53,7 @@
51
53
  import { type StructFieldDecl, renderStructDecl } from './backend/cfamily';
52
54
  import { T } from './ir/types';
53
55
  import type { SymbolRef } from './l3/symbol-refs';
56
+ import { type AggregateLayout, type AggregateMember, declaredWidth, spellableType } from './proto';
54
57
  import {
55
58
  ENUM_IS_SIGNED,
56
59
  type SymbolInfo,
@@ -134,6 +137,43 @@ function structDecl(tag: string, layout: SymbolStructField[] | undefined, size:
134
137
  return renderStructDecl(tag, fields);
135
138
  }
136
139
 
140
+ /** A struct the project's headers define, transcribed from their declaration so the compiler lays
141
+ * it out as it lays out theirs: each member in order with its extents and bit width, a nested
142
+ * struct or union inline, and every type spelled so that it needs nothing else declared — a
143
+ * pointer as `void *`, an enum as an inline enum of its own, a scalar the prelude does not name
144
+ * as the prelude's type of its width and signedness. Its members are what size it, not what is
145
+ * read of it: no emitted expression names one (aggregate.ts `aggregateType`). */
146
+ function declaredStructDecl(tag: string, layout: AggregateLayout): string {
147
+ let enums = 0;
148
+ const scalar = (spelling: string): string => {
149
+ const s = spelling
150
+ .replace(/\b(?:const|volatile)\b/g, ' ')
151
+ .trim()
152
+ .replace(/\s+/g, ' ');
153
+ if (s.endsWith('*')) {
154
+ return 'void *';
155
+ }
156
+ if (/^enum\b/.test(s)) {
157
+ return `enum { asmlift_${tag}_enum${enums++} }`;
158
+ }
159
+ if (s === 'float' || s === 'double' || spellableType(s)) {
160
+ return s;
161
+ }
162
+ const bits = declaredWidth(s);
163
+ return `${/^u\d|\bunsigned\b/.test(s) ? 'u' : 's'}${bits}`;
164
+ };
165
+ const body = (members: readonly AggregateMember[]): string =>
166
+ members
167
+ .map((m) => {
168
+ const type = typeof m.type === 'string' ? scalar(m.type) : `${m.type.kind} { ${body(m.type.members!)} }`;
169
+ const declarator = `${m.name}${(m.dims ?? []).map((n) => `[${n}]`).join('')}`;
170
+ const bits = m.bits !== undefined ? ` : ${m.bits}` : '';
171
+ return `${type}${type.endsWith('*') || declarator === '' ? '' : ' '}${declarator}${bits};`;
172
+ })
173
+ .join(' ');
174
+ return `struct ${tag} { ${body(layout.members!)} };`;
175
+ }
176
+
137
177
  /**
138
178
  * Render the declaration block for a candidate's recorded symbol references. Deterministic
139
179
  * (refs arrive name-sorted from core; struct decls dedupe by tag). The block is prepended by
@@ -144,7 +184,37 @@ function structDecl(tag: string, layout: SymbolStructField[] | undefined, size:
144
184
  export function renderDeclarations(refs: SymbolRef[]): string {
145
185
  const lines: string[] = [];
146
186
  const declaredTags = new Set<string>();
147
- for (const { name, info, access } of refs) {
187
+ const declaredTypedefs = new Set<string>();
188
+ const defineStruct = (s: { readonly name: string; readonly declared: string; readonly layout: AggregateLayout }) => {
189
+ if (!declaredTags.has(s.name)) {
190
+ declaredTags.add(s.name);
191
+ lines.push(declaredStructDecl(s.name, s.layout));
192
+ }
193
+ if (!/^(?:struct|union)\s/.test(s.declared) && !declaredTypedefs.has(s.declared)) {
194
+ declaredTypedefs.add(s.declared);
195
+ lines.push(`typedef struct ${s.name} ${s.declared};`);
196
+ }
197
+ };
198
+ for (const {
199
+ name,
200
+ info,
201
+ access,
202
+ proto,
203
+ returned,
204
+ passed,
205
+ passedAs,
206
+ holdsPointer,
207
+ implicitCall,
208
+ definesReturn,
209
+ } of refs) {
210
+ // THE FUNCTION'S OWN STRUCT RETURN, defined as a callee's is; the function itself is declared
211
+ // by its definition
212
+ if (definesReturn) {
213
+ if (returned !== undefined) {
214
+ defineStruct(returned);
215
+ }
216
+ continue;
217
+ }
148
218
  // An address-cast macro declares itself: the header's own body, verbatim. It must NOT become
149
219
  // an `extern` — that is the whole point of the fact (an extern emits a relocated pool word
150
220
  // where the macro emits the numeric one the target shows).
@@ -152,10 +222,42 @@ export function renderDeclarations(refs: SymbolRef[]): string {
152
222
  lines.push(`#define ${name} ${info.macroBody}`);
153
223
  continue;
154
224
  }
225
+ // A GLOBAL REGISTER VARIABLE declares itself in its register (SymbolInfo.globalRegister), typed
226
+ // as a name-only global is: a pointer where the body stores or meets one, else the word the
227
+ // body accesses.
228
+ if (info.globalRegister !== undefined) {
229
+ const t = holdsPointer ? 'void *' : `${intType(access?.width ?? 4, access?.signed ?? true) ?? 's32'} `;
230
+ lines.push(`register ${t}${name} __asm__("${info.globalRegister}");`);
231
+ continue;
232
+ }
233
+ // A CALL TARGET the project declared completely — its own prototype, re-spelled from the
234
+ // parameter and return type texts the project wrote. This is the one declaration here that
235
+ // is load-bearing for the CALL and not only for the name: an implicit declaration is `int`,
236
+ // which narrows a 64-bit return and adds the sign-extension the target does not have. A
237
+ // parameterless list is spelled `(void)`, never `()`, because `()` declares nothing about
238
+ // the arguments and gcc-2.9 then promotes them.
239
+ if (proto !== undefined) {
240
+ // A STRUCT IT RETURNS THROUGH MEMORY is the headers' type, spelled as they spell it, so it is
241
+ // defined here with the rest of what they own: by its tag, once, whoever else declares that
242
+ // tag — and a typedef name is bound to it on top.
243
+ // …and so is a struct it is passed by value, the same way.
244
+ for (const s of [...(returned?.declared !== undefined ? [returned] : []), ...(passed ?? [])]) {
245
+ defineStruct(s);
246
+ }
247
+ lines.push(`${proto.returns} ${name}(${proto.params.length > 0 ? proto.params.join(', ') : 'void'});`);
248
+ continue;
249
+ }
250
+ // A CALL TARGET no prototype spells, named ahead of its first call by an initializer — C's
251
+ // implicit declaration, `int F();`, written where the initializer can see it. No parameter
252
+ // list, so the call binds to it exactly as it bound to the implicit one (see collectSymbolRefs).
253
+ if (implicitCall) {
254
+ lines.push(`int ${name}();`);
255
+ continue;
256
+ }
155
257
  if (info.kind === 'code') {
156
258
  // value-referenced code symbol ((u32)Func): any prototype makes the name visible, and
157
- // the address is arity-independent. Call targets never reach this module (core excludes
158
- // them from symbolRefs — see collectSymbolRefs).
259
+ // the address is arity-independent. A call target reaches here only with a complete
260
+ // prototype or as an initializer's implicit call, which the branches above printed.
159
261
  lines.push(`void ${name}(void);`);
160
262
  continue;
161
263
  }
@@ -247,6 +349,18 @@ export function renderDeclarations(refs: SymbolRef[]): string {
247
349
  // argument applies. For a `synthesized` one it does not — the width came from the
248
350
  // target's own asm, so this line is a hypothesis fitted to the bytes; see the module
249
351
  // note's "WHERE THE ONLY-LOSES-SCORE ARGUMENT STOPS".
352
+ // A global that holds a pointer (ref.holdsPointer) is `void *`, the type every object
353
+ // pointer converts to without a cast; the qualifiers bind to the cell.
354
+ if (holdsPointer) {
355
+ lines.push(`extern void *${info.volatile ? 'volatile ' : ''}${info.const ? 'const ' : ''}${name};`);
356
+ break;
357
+ }
358
+ // A global passed bare as a struct by value is that struct, which its callee's prototype
359
+ // defines (`passed`); a declaration of an incomplete tag is completed by that definition.
360
+ if (passedAs !== undefined) {
361
+ lines.push(`extern ${quals(info)}${passedAs} ${name};`);
362
+ break;
363
+ }
250
364
  const t = access ? intType(access.width, access.signed) : null;
251
365
  lines.push(`extern ${quals(info)}${t ?? 'u32'} ${name};`);
252
366
  break;
@@ -256,18 +370,23 @@ export function renderDeclarations(refs: SymbolRef[]): string {
256
370
  return lines.length ? lines.join('\n') + '\n' : '';
257
371
  }
258
372
 
259
- /** The object-like `#define`s out of a rendered declaration block.
373
+ /** THE LINES OF A RENDERED DECLARATION BLOCK THAT THE HEADERS WORLD KEEPS: its object-like
374
+ * `#define`s and its global register variables. Everything else there is owned by the injected
375
+ * headers (a duplicate typedef or struct definition is a C89 hard error), and these two are not:
376
+ *
377
+ * - an address-cast macro, because a duplicate `#define` with an identical body is legal — and a
378
+ * PREPROCESSED project context has no macros left at all, so dropping these turns a macro-named
379
+ * candidate into an `undeclared identifier` rather than a spelling choice;
380
+ * - a global register variable (`register T greg_r8 __asm__("r8");`, SymbolInfo.globalRegister),
381
+ * because no header declares the name the lift made up for it.
260
382
  *
261
- * Address-cast macro defines are the one part of a synthesized block that must survive into the
262
- * HEADERS world too. Everything else there is owned by the injected headers (a duplicate typedef
263
- * or struct definition is a C89 hard error), but a duplicate `#define` with an identical body is
264
- * legal — and a PREPROCESSED project context has no macros left at all, so dropping these turns a
265
- * macro-named candidate into an `undeclared identifier` rather than a spelling choice. */
266
- export function macroDefinesOf(declarations: string | undefined): string {
383
+ * Chosen by each line's leading keyword, which `renderDeclarations` prints for these two kinds
384
+ * only: a new kind of line it prints with either keyword rides into the headers world too. */
385
+ export function declarationsKeptWithHeaders(declarations: string | undefined): string {
267
386
  if (!declarations) {
268
387
  return '';
269
388
  }
270
- const lines = declarations.split('\n').filter((l) => l.startsWith('#define '));
389
+ const lines = declarations.split('\n').filter((l) => l.startsWith('#define ') || l.startsWith('register '));
271
390
  return lines.length ? lines.join('\n') + '\n' : '';
272
391
  }
273
392
 
@@ -29,17 +29,35 @@ export interface Reloc {
29
29
  export interface AsmData {
30
30
  sections: Map<string, Uint8Array>; // section name → raw bytes (file order)
31
31
  relocs: Reloc[];
32
- symbols: Map<string, { section: string; value: number }>; // symbol name → {section, offset-in-section}
32
+ symbols: Map<string, AsmSymbol>;
33
+ /** how many symbols the `objdump -t` listing printed — the symbol table's entries past its null one */
34
+ symbolCount: number;
33
35
  bigEndian: boolean;
34
36
  }
35
37
 
38
+ /** One `objdump -t` symbol: its section, offset in it and size, and `index`, its position in the
39
+ * listing counting from 1 — its symbol-table index, which is what a per-symbol side table in the
40
+ * object (mwcc's `.comment`, frontend/local-object.ts) is indexed by. */
41
+ export interface AsmSymbol {
42
+ section: string;
43
+ value: number;
44
+ size: number;
45
+ index: number;
46
+ }
47
+
36
48
  const readU32 = (b: Uint8Array, off: number, big: boolean): number =>
37
49
  big
38
50
  ? ((b[off] << 24) | (b[off + 1] << 16) | (b[off + 2] << 8) | b[off + 3]) >>> 0
39
51
  : ((b[off + 3] << 24) | (b[off + 2] << 16) | (b[off + 1] << 8) | b[off]) >>> 0;
40
52
 
41
- // Parse an `objdump -r` VALUE field: `sw_jt+0x00000020` / `.text` / `@15` → {sym, addend}.
53
+ // Parse an `objdump -r` VALUE field: `sw_jt+0x00000020` / `.text` / `@15` → {sym, addend}. A
54
+ // negative RELA addend prints as `garr-0x00000004` (mwcc's `&garr[-1]`, compiled), and is kept as
55
+ // the same 32-bit word a positive one is.
42
56
  function parseRelocValue(v: string): { sym: string; addend: number } {
57
+ const minus = v.match(/^(.+)-(0x[0-9a-f]+)$/i);
58
+ if (minus) {
59
+ return { sym: minus[1], addend: -parseInt(minus[2], 16) >>> 0 };
60
+ }
43
61
  const plus = v.indexOf('+');
44
62
  if (plus < 0) {
45
63
  return { sym: v, addend: 0 };
@@ -112,7 +130,8 @@ export function parseAsmData(
112
130
  }
113
131
 
114
132
  // --- `objdump -t`: `VALUE FLAGS SECTION\tSIZE NAME` (section/size split by TAB) ---
115
- const symbols = new Map<string, { section: string; value: number }>();
133
+ const symbols = new Map<string, AsmSymbol>();
134
+ let symbolCount = 0;
116
135
  for (const line of symbolsDump.split('\n')) {
117
136
  const tab = line.indexOf('\t');
118
137
  if (tab < 0) {
@@ -121,14 +140,15 @@ export function parseAsmData(
121
140
  const left = line.slice(0, tab),
122
141
  right = line.slice(tab + 1);
123
142
  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
143
+ const rm = right.match(/^([0-9a-f]+)\s+(.+?)\s*$/i); // size name
125
144
  if (!lm || !rm) {
126
145
  continue;
127
146
  }
128
- symbols.set(rm[1], { section: lm[2], value: parseInt(lm[1], 16) });
147
+ symbolCount++;
148
+ symbols.set(rm[2], { section: lm[2], value: parseInt(lm[1], 16), size: parseInt(rm[1], 16), index: symbolCount });
129
149
  }
130
150
 
131
- return { sections, relocs, symbols, bigEndian };
151
+ return { sections, relocs, symbols, symbolCount, bigEndian };
132
152
  }
133
153
 
134
154
  /** Read a dense jump table's N target `.text` byte-offsets, or `null` if ANYTHING doesn't resolve