@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
@@ -3,7 +3,8 @@
3
3
  // neutral and consumes the Fn without knowing which frontend produced it. See registry.ts
4
4
  // for target→frontend dispatch.
5
5
  import type { Fn } from '../ir/core';
6
- import type { Prototypes } from '../proto';
6
+ import type { FnProto, Prototypes } from '../proto';
7
+ import type { ContextGlobals } from '../proto-context';
7
8
  import type { SymbolMap } from '../symbols';
8
9
  import type { TargetDescription } from '../target';
9
10
  import type { AsmData } from './asmdata';
@@ -19,8 +20,10 @@ export interface Frontend {
19
20
  * (and any other header facts the frontend needs); an empty map is valid. `asmData` is the
20
21
  * OPTIONAL Regime-B side-table (data-section jump tables + relocations); absent ⇒ a
21
22
  * dense-switch dispatch declines/loud-fails. `symbols` is the OPTIONAL address→symbol map
22
- * (symbols.ts); today only the Thumb frontend consumes it (numeric-pool promotion) — the
23
- * MIPS/PPC objdump dialect already carries symbol names in the asm text. */
23
+ * (symbols.ts): the Thumb frontend promotes a numeric pool word through it — the
24
+ * MIPS/PPC objdump dialect already carries symbol names in the asm text — and every frontend
25
+ * hands it, with the OPTIONAL declaration `context`, to the dead-read pin, which reads a global's
26
+ * declaration there (frontend/dead-reads.ts `declared-plain`). */
24
27
  lift(
25
28
  name: string,
26
29
  asm: string,
@@ -28,5 +31,63 @@ export interface Frontend {
28
31
  prototypes: Prototypes,
29
32
  asmData?: AsmData,
30
33
  symbols?: SymbolMap,
34
+ context?: ContextGlobals,
35
+ options?: LiftOptions,
31
36
  ): Fn;
32
37
  }
38
+
39
+ /** THE FUNCTION'S OWN DECLARATION, as a call lowering reads it (frontend/call-plan.ts
40
+ * `callDeclarations`): a stated entry, which `proves`, or the declaration context's
41
+ * (`LiftOptions.contextOwnProto`). */
42
+ export interface OwnDeclaration {
43
+ readonly proto: FnProto;
44
+ /** whether it may rule a reading OUT (a hole, a shadow, a float) as well as refuse one */
45
+ readonly proves: boolean;
46
+ }
47
+
48
+ /** The own declaration of `name`: the stated entry in `prototypes`, else the context's. */
49
+ export function ownDeclarationOf(
50
+ name: string,
51
+ prototypes: Prototypes,
52
+ context: OwnDeclaration | undefined,
53
+ ): OwnDeclaration | undefined {
54
+ return Object.hasOwn(prototypes, name) ? { proto: prototypes[name], proves: true } : context;
55
+ }
56
+
57
+ /** A question the asm leaves open that a frontend answers one way by default and, asked, the other
58
+ * way: the LIFT variations of rank.ts that live in the frontend. A frontend with no such question
59
+ * ignores the whole bag. Two inputs ride beside them that no variation sets, both from the
60
+ * declaration context's own entry: its bound (`contextOwnWords`) and the entry itself
61
+ * (`contextOwnProto`). */
62
+ export interface LiftOptions {
63
+ /** The argument words the declaration context's own entry for the function fills, where no
64
+ * stated entry does (pipeline.ts `liftWithinContextBound`, which raises it past a register the
65
+ * lift reads, and lifts without it where it refuses), so a frontend given it reads it in place
66
+ * of the stated entry's words (frontend/ssa.ts `registerLiveIns`). Every frontend reads it; PowerPC,
67
+ * as with a stated entry, only for a body a compiler wrote. */
68
+ contextOwnWords?: number;
69
+ /** The declaration context's own entry itself, where no stated entry binds the body. Read where
70
+ * the TYPES of the own parameters decide something the word count cannot (frontend/call-plan.ts
71
+ * `decideGap`). `proves` says whether it may serve as positive proof as well. It holds only where
72
+ * the lift is bounded by exactly the entry's words, or the entry fills every argument register,
73
+ * and never once the bound was raised past a register the body reads or the lift fell back to
74
+ * no bound, which says the entry is wrong. Without `proves`, it only refuses. */
75
+ contextOwnProto?: OwnDeclaration;
76
+ /** The size in bytes of a struct the function's own declaration returns through a hidden pointer
77
+ * (pipeline.ts `liftStamped`), and where to hand a refusal that declaration may answer: one about
78
+ * the RETURN's width that such a struct explains, which the frontend then defers instead of
79
+ * throwing, as the function that throws it. The pipeline calls that after all unless the lift
80
+ * proves the pointer is that struct's storage (frontend/own-struct-return.ts), handing it why the
81
+ * proof failed, which the refusal then says. Thumb only: agbcc's interworking epilogue sizes an
82
+ * 8-byte struct returned through memory as it sizes a `long long` (`eightByteReturnScratch`). */
83
+ ownMemoryReturn?: { bytes: number; defer(refuse: (unproved: string) => never): void };
84
+ /** MIPS: keep the slot of a `b`/`j` whose slot is the last word of a common tail its two arms
85
+ * both spell (`sll; b T; sra` against `sll; sra; T:`), rather than retarget it to the copy at
86
+ * T-4 and merge the arms. The default retargets (frontend/mips.ts `normaliseBranchLikely`). An
87
+ * IDO question: GCC 2.7.2 and KMC cross-jump such a tail into one copy before filling a slot, so
88
+ * on their output the slot is never a tail written twice. */
89
+ keepTailSlots?: boolean;
90
+ /** Called when the lift met such a slot, whichever way it answered: what rank.ts asks before
91
+ * enumerating `/kept-slot` at all. */
92
+ onTailSlot?: () => void;
93
+ }
@@ -6,7 +6,9 @@
6
6
  // lives entirely in the two relocation records — so the high half is NOT A VALUE but a placeholder
7
7
  // that means nothing until its matching low half completes it. Letting one reach the IR as an
8
8
  // ordinary number hands the pipeline a plausible address that compiles and is simply wrong, so the
9
- // invariant is absolute: a placeholder is either consumed by its low half or refuses.
9
+ // invariant is absolute: a placeholder is either consumed by its low half or refuses. The one other
10
+ // consumer is `index`, and it does not break the rule: it consumes the half only to define a NEW
11
+ // placeholder, the half plus a runtime index, which is held to the same rule in turn.
10
12
  //
11
13
  // KEYED BY VALUE, NOT BY REGISTER, and the difference is the whole point. A register-keyed map
12
14
  // answers "was a high half put in rX?", which is not the question; the question is "does the half
@@ -23,6 +25,7 @@
23
25
  // Either way `addend` is "the part of the address this producer contributed", and the consumer adds
24
26
  // its own.
25
27
  import type { Block, Value } from '../ir/core';
28
+ import type { FrontendRefusal } from './errors';
26
29
 
27
30
  /** A pending high half: what it names, what it contributed, and the instruction that produced it,
28
31
  * so a refusal can point a reader at it. Whether it has been consumed is deliberately NOT here: it
@@ -34,6 +37,11 @@ export interface HighHalfInfo {
34
37
  addend: number;
35
38
  addr: number;
36
39
  mnemonic: string;
40
+ /** A RUNTIME BYTE INDEX added between the halves (`lui rH,%hi(S); addu rH,rH,idx; lw x,%lo(S)(rH)`,
41
+ * which is how ido, KMC and GCC 2.7.2 address `S[i]`), set only on a half `index` derived. The
42
+ * index sits outside both relocated immediates, so the `%lo` carry is the same with and without
43
+ * it and the completed address is exactly `S + addend + lo + index`. */
44
+ index?: Value;
37
45
  }
38
46
 
39
47
  /** How one ISA spells the two halves in a refusal, and how it fails loud. */
@@ -45,7 +53,7 @@ export interface HighHalfDialect {
45
53
  /** The low-half marker: `@l` (PowerPC), `%lo` (MIPS). */
46
54
  lo: string;
47
55
  /** The frontend's designed loud-failure signal (`PpcUnsupportedError`, `FrontendUnsupportedError`). */
48
- fail(message: string): never;
56
+ fail: FrontendRefusal;
49
57
  }
50
58
 
51
59
  export interface HighHalves {
@@ -56,8 +64,28 @@ export interface HighHalves {
56
64
  * placeholder a producer defined, and the two halves must name the same symbol (`alsoMatches`
57
65
  * adds the per-ISA rest of the match — PowerPC's RELA addend). Anything else refuses, naming
58
66
  * `reg` and what it really holds. The half is CONSUMED on the way out, which is why this is a
59
- * call and not a public flag: the mark and the proof cannot come apart. */
60
- pair(site: string, reg: string, v: Value, loSym: string, alsoMatches?: (hi: HighHalfInfo) => boolean): HighHalfInfo;
67
+ * call and not a public flag: the mark and the proof cannot come apart.
68
+ *
69
+ * An INDEXED half (`index`) pairs only for a caller that passes `takesIndex`, and the index and
70
+ * the proof cannot come apart either: a consumer that completes the address as `gaddr(sym) +
71
+ * addend` without reading `index` would drop the index and still compile. So a caller that has
72
+ * not said it adds `index` back gets a refusal in place of that address. */
73
+ pair(
74
+ site: string,
75
+ reg: string,
76
+ v: Value,
77
+ loSym: string,
78
+ alsoMatches?: (hi: HighHalfInfo) => boolean,
79
+ takesIndex?: boolean,
80
+ ): HighHalfInfo;
81
+ /** AN INDEXED HIGH HALF: `v` is the half `base` plus the runtime byte index `idx`, made by the
82
+ * instruction at `at`. It is a placeholder like the half it came from — read as a value, left
83
+ * uncompleted or carried into a merge it refuses through the same guards, because it lives in the
84
+ * same value-keyed map — and only its `%lo` completes it, `pair` handing the index back. Deriving
85
+ * CONSUMES `base`: the derivation is the use the base half was waiting for. Indexing a half that
86
+ * is already indexed refuses — no compiler measured chains two (lui;addu;addu); a frontend that
87
+ * meets one has hand-written or unmodelled code, not `S[i][j]`, which every compiler sums first. */
88
+ index(v: Value, base: Value, idx: Value, at: { addr: number; mnemonic: string }): void;
61
89
  /** True when `v` is a placeholder — for a caller that must EXCLUDE one (a call's argument count
62
90
  * must not treat a half parked in an argument register as an argument). */
63
91
  has(v: Value): boolean;
@@ -81,8 +109,30 @@ export function makeHighHalves(d: HighHalfDialect): HighHalves {
81
109
  return {
82
110
  record: (v, info) => void halves.set(v, { info, consumed: false }),
83
111
  has: (v) => halves.has(v),
84
- pair(site, reg, v, loSym, alsoMatches) {
112
+ index(v, base, idx, at) {
113
+ const entry = halves.get(base);
114
+ if (!entry) {
115
+ d.fail(`the '${at.mnemonic}' at 0x${at.addr.toString(16)} indexes a value that holds no high half`);
116
+ }
117
+ if (entry!.info.index !== undefined) {
118
+ d.fail(
119
+ `the '${at.mnemonic}' at 0x${at.addr.toString(16)} adds a second index to the high half of ` +
120
+ `'${entry!.info.sym}' (the '${entry!.info.mnemonic}' at 0x${entry!.info.addr.toString(16)}) — ` +
121
+ `a chain of indexes between the halves is not modelled`,
122
+ );
123
+ }
124
+ entry!.consumed = true;
125
+ halves.set(v, { info: { ...entry!.info, addr: at.addr, mnemonic: at.mnemonic, index: idx }, consumed: false });
126
+ },
127
+ pair(site, reg, v, loSym, alsoMatches, takesIndex = false) {
85
128
  const entry = halves.get(v);
129
+ if (entry?.info.index !== undefined && !takesIndex) {
130
+ d.fail(
131
+ `${site} carries the '${d.lo}' half of '${loSym}' but ${reg} holds the high half of ` +
132
+ `'${entry.info.sym}' plus a runtime index (the '${entry.info.mnemonic}' at ` +
133
+ `0x${entry.info.addr.toString(16)}), and this consumer completes no index`,
134
+ );
135
+ }
86
136
  const hi = entry && entry.info.sym === loSym && (alsoMatches?.(entry.info) ?? true) ? entry.info : undefined;
87
137
  if (!hi) {
88
138
  d.fail(