@asmlift/core 0.9.0 → 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 (101) 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 +1 -0
  5. package/src/backend/cfamily.ts +165 -50
  6. package/src/backend/cpp.ts +2 -1
  7. package/src/backend/pascal.ts +31 -12
  8. package/src/contracts.ts +173 -16
  9. package/src/declare.ts +69 -20
  10. package/src/frontend/asmdata.ts +7 -1
  11. package/src/frontend/call-plan.ts +964 -60
  12. package/src/frontend/dead-reads.ts +628 -0
  13. package/src/frontend/device-pins.ts +23 -4
  14. package/src/frontend/emit.ts +4 -3
  15. package/src/frontend/fpu.ts +224 -17
  16. package/src/frontend/frame-objects.ts +1232 -192
  17. package/src/frontend/frontend.ts +64 -3
  18. package/src/frontend/high-half.ts +53 -4
  19. package/src/frontend/jump-table.ts +530 -0
  20. package/src/frontend/local-object.ts +158 -32
  21. package/src/frontend/mips.ts +2076 -215
  22. package/src/frontend/own-struct-return.ts +500 -0
  23. package/src/frontend/ppc.ts +2711 -232
  24. package/src/frontend/register-struct-call.ts +484 -0
  25. package/src/frontend/reloc-symbol.ts +22 -7
  26. package/src/frontend/ssa.ts +1460 -44
  27. package/src/frontend/stackargs.ts +106 -57
  28. package/src/frontend/thumb.ts +2044 -318
  29. package/src/ir/alias.ts +42 -0
  30. package/src/ir/bits.ts +681 -1
  31. package/src/ir/core.ts +100 -5
  32. package/src/ir/discipline.ts +86 -21
  33. package/src/ir/entry.ts +159 -0
  34. package/src/{structure → ir}/loops.ts +6 -2
  35. package/src/ir/opcodes.ts +109 -9
  36. package/src/ir/types.ts +86 -2
  37. package/src/ir/verify.ts +80 -11
  38. package/src/l3/ast.ts +290 -18
  39. package/src/l3/basecse.ts +33 -5
  40. package/src/l3/constinit.ts +382 -0
  41. package/src/l3/dce.ts +2 -2
  42. package/src/l3/initfirst.ts +42 -11
  43. package/src/l3/intcell.ts +2 -1
  44. package/src/l3/narrowdecl.ts +77 -36
  45. package/src/l3/pollguard.ts +8 -2
  46. package/src/l3/ptrcell.ts +31 -34
  47. package/src/l3/ptrfield.ts +123 -8
  48. package/src/l3/regspell.ts +9 -4
  49. package/src/l3/reindex.ts +283 -96
  50. package/src/l3/storage.ts +98 -1
  51. package/src/l3/symbol-refs.ts +84 -6
  52. package/src/l3/tailmerge.ts +4 -2
  53. package/src/l3/typing.ts +108 -15
  54. package/src/l3/unreduce.ts +6 -5
  55. package/src/l3/volatileptr.ts +17 -54
  56. package/src/mangle.ts +30 -0
  57. package/src/pattern/engine.ts +361 -73
  58. package/src/pipeline.ts +541 -24
  59. package/src/proto-context.ts +905 -45
  60. package/src/proto.ts +555 -40
  61. package/src/raise/aggmembers.ts +290 -0
  62. package/src/raise/arrays.ts +5 -1
  63. package/src/raise/declared-volatile.ts +207 -31
  64. package/src/raise/floathelpers.ts +254 -33
  65. package/src/raise/gvn.ts +3 -3
  66. package/src/raise/latch.ts +162 -6
  67. package/src/raise/memberarrays.ts +10 -4
  68. package/src/raise/offsetnames.ts +10 -26
  69. package/src/raise/pairparams.ts +33 -0
  70. package/src/raise/paramwidth.ts +196 -6
  71. package/src/raise/pre-recovery.ts +102 -19
  72. package/src/raise/pre-structure.ts +54 -0
  73. package/src/raise/recover.ts +12 -5
  74. package/src/raise/reroll.ts +694 -0
  75. package/src/raise/shortcircuit.ts +26 -12
  76. package/src/raise/softdiv.ts +1 -1
  77. package/src/raise/storesign.ts +153 -0
  78. package/src/raise/struct-arrays.ts +30 -11
  79. package/src/raise/structs.ts +396 -33
  80. package/src/raise/truncload.ts +4 -3
  81. package/src/raise/unthread.ts +400 -0
  82. package/src/rank-declare.ts +118 -12
  83. package/src/rank-variations.ts +89 -31
  84. package/src/rank.ts +307 -54
  85. package/src/runtime-helpers.ts +140 -22
  86. package/src/structure/analysis.ts +165 -2
  87. package/src/structure/arith-ops.ts +23 -0
  88. package/src/structure/globalaccess.ts +111 -9
  89. package/src/structure/hazards.ts +16 -64
  90. package/src/structure/local-statics.ts +145 -9
  91. package/src/structure/namecoalesce.ts +1 -1
  92. package/src/structure/pointer-spelling.ts +1106 -0
  93. package/src/structure/redundant-test.ts +1 -1
  94. package/src/structure/structure.ts +2180 -1054
  95. package/src/structure/switch-recover.ts +244 -100
  96. package/src/symbols.ts +6 -0
  97. package/src/target.ts +765 -52
  98. package/src/trace.ts +26 -8
  99. package/src/variation-definitions.ts +126 -14
  100. package/src/variation-gates.ts +2 -0
  101. package/src/variation-tokens.ts +5 -1
@@ -0,0 +1,1106 @@
1
+ // asmlift structurer — POINTER/INTEGER SPELLING: whether a value the asm did arithmetic on, or
2
+ // assigned into a temp or a word cell, is written as a pointer or as an integer, and through
3
+ // which cast. A global's C type is the project header's, which this pass cannot read, so each
4
+ // rule spells the asm's bytes under every declaration that header may carry; its doc names the
5
+ // declarations it covers and its known gaps.
6
+ //
7
+ // The arithmetic is decided in three steps. Each operand gets a ROLE (`Role`) before the op is
8
+ // printed, read from the operand as lowered: a constant, a global's address, a bare name with what
9
+ // the declarations and the IR say of it, a member, or a value this rule already spelled, which
10
+ // carries what it is made of. One table (`ARITH_ROWS`) maps the operator, the two roles and the IR
11
+ // result type to a plan: which cast each side takes, and the pointer type the sum goes back to.
12
+ // The plan is printed once. The operand order of a commutative load pair is decided before the
13
+ // table, by its own predicate (`pointerSide`), whose doc says which sides it keeps and where it
14
+ // reads other facts than the table.
15
+ //
16
+ // The factory takes its dependencies EXPLICITLY (`PointerSpellingDeps`), the switch-recover
17
+ // pattern. `varType` is captured as a LIVE reference: the naming pipeline is still declaring
18
+ // temps when the factory is created, and every rule types an expression over the declarations
19
+ // that exist at call time.
20
+ //
21
+ // Whether a temp the IR types an integer is declared a byte pointer (`declaresBytePointer`) is
22
+ // decided over the IR, which the factory's dependencies do not carry, so it is a free function.
23
+ // What it asks of a global it asks of the factory (`pointerGlobal`, `castsIntoGlobal`).
24
+ import { Op, type Value } from '../ir/core';
25
+ import { MEM_BASE_OPS } from '../ir/opcodes';
26
+ import { type IrType, T, typeEquals } from '../ir/types';
27
+ import { type BinOp, Expr } from '../l3/ast';
28
+ import { pointerCellValue } from '../l3/ptrcell';
29
+ import { exprCType, ptrElemBytes } from '../l3/typing';
30
+ import { type SymbolInfo, isBitfieldField, isScalarCellSize, scalarCellType, symbolFieldType } from '../symbols';
31
+ import type { TargetDescription } from '../target';
32
+ import { ARITH_TO_BIN } from './arith-ops';
33
+ import { type MemberLookup, declaredMemberOf, ptrMemberDecl, structMemberAt } from './globalaccess';
34
+
35
+ /** The slice of structure.ts's symbol-map rendering context these rules read. */
36
+ export interface PointerSpellingSymCtx extends MemberLookup {
37
+ info(name: string): SymbolInfo | undefined;
38
+ }
39
+
40
+ export interface PointerSpellingDeps {
41
+ /** absent ⇒ no map. */
42
+ sym: PointerSpellingSymCtx | undefined;
43
+ /** the globals the map declares pointers, for a structuring that does not spell from the map
44
+ * (the `pointerGlobals` structure option). */
45
+ pointerGlobals: ReadonlySet<string> | undefined;
46
+ /** the scalar globals the IR loads as a pointer at least once (structure.ts). */
47
+ pointerLoadedGlobals: ReadonlySet<string>;
48
+ /** the scalar globals the IR loads as a word (structure.ts). */
49
+ wordLoadedGlobals: ReadonlySet<string>;
50
+ /** the globals structure.ts spells bare, `gSym`: each accessed only at offset 0, at one width. */
51
+ scalarGlobals: ReadonlySet<string>;
52
+ /** each declared variable's type — LIVE, read at call time. */
53
+ varType: ReadonlyMap<string, IrType>;
54
+ /** the compiler behaviors a row is guarded on (`ArithRow.when`); absent ⇒ none holds. */
55
+ compiler?: ArithCompilerFacts;
56
+ /** the compiler's C refuses C's implicit integer-to-pointer conversion (the compiler behavior
57
+ * `strictPointerConversions`, mwcc), so an integer stored into a pointer cell is cast
58
+ * (`intoPtrCell`); absent ⇒ C's implicit conversion. */
59
+ strictPointerConversions?: boolean;
60
+ /** the emitted C is compiled as C++ (`SFn.dialect`), which converts an integer into a typed
61
+ * pointer cell only by a cast to that cell's own type (`intoPtrCell`); absent ⇒ C. */
62
+ dialect?: 'c++';
63
+ /** declines the function with `why` (structure.ts throws its `StructureError`); absent ⇒ throws a
64
+ * plain Error. */
65
+ refuse?: (why: string) => never;
66
+ /** each value's readers (analysis.ts `useSitesOf`); absent ⇒ none. */
67
+ useSitesOf?: ReadonlyMap<Value, readonly { op: Op }[]>;
68
+ /** whether a name holds the value — LIVE, read at call time; absent ⇒ none does. */
69
+ isNamed?: (v: Value) => boolean;
70
+ }
71
+
72
+ /** The compiler behaviors (target.ts `compilerBehaviors`) the table's rows may be guarded on. A row
73
+ * guarded on another one adds its name here; `StructureOptions` extends this type, so the behavior
74
+ * reaches the table from the target with no further edit. */
75
+ export type ArithCompilerFacts = Pick<TargetDescription['compilerBehaviors'], 'keepsPointerSumAddend'>;
76
+
77
+ /** Which globals a fact holds of: some global, and whether one of them is a global no map
78
+ * declaration types. */
79
+ interface GlobalFact {
80
+ readonly any: boolean;
81
+ readonly undeclared: boolean;
82
+ }
83
+ const NOT: GlobalFact = { any: false, undeclared: false };
84
+ const either = (...fs: GlobalFact[]): GlobalFact => ({
85
+ any: fs.some((f) => f.any),
86
+ undeclared: fs.some((f) => f.undeclared),
87
+ });
88
+
89
+ /** What the declarations and the IR say of a bare name. A name that is no global (a temp, a
90
+ * parameter) is no pointer value, loads nothing and is declared by nothing this pass reads. */
91
+ interface NameFacts {
92
+ /** a POINTER VALUE: see `pointerValue` in the factory */
93
+ readonly pointer: boolean;
94
+ /** no declaration this pass can read types it (the map's, or a `pointerGlobals` one) */
95
+ readonly undeclared: boolean;
96
+ /** the map has no declaration of it */
97
+ readonly mapUndeclared: boolean;
98
+ readonly pointerLoaded: boolean;
99
+ readonly wordLoaded: boolean;
100
+ }
101
+
102
+ /** What a value this rule spelled is made of, recorded when it is built so no later op has to
103
+ * read it back out of the printed C. */
104
+ interface Made {
105
+ /** every leaf is a constant */
106
+ readonly constant: boolean;
107
+ /** a global's address is in it (`(u32)&g`, a sum of one) */
108
+ readonly address: boolean;
109
+ /** `(u8 *)g`: a global's value as a byte pointer */
110
+ readonly globalBytes: GlobalFact;
111
+ /** `(u32)g`, `(u32)(u8 *)g`, or a byte sum of a global as its word */
112
+ readonly globalWord: GlobalFact;
113
+ /** a sum one of whose terms is a global's byte pointer (or a restored integer sum) */
114
+ readonly byteSum: GlobalFact;
115
+ /** a sum one of whose terms is a global's word */
116
+ readonly intSum: GlobalFact;
117
+ /** an integer sum of a global's word, cast back to the byte pointer it stands for */
118
+ readonly restored: GlobalFact;
119
+ }
120
+ const NOTHING_MADE: Made = {
121
+ constant: false,
122
+ address: false,
123
+ globalBytes: NOT,
124
+ globalWord: NOT,
125
+ byteSum: NOT,
126
+ intSum: NOT,
127
+ restored: NOT,
128
+ };
129
+
130
+ /** What an arithmetic operand IS, decided before the op is printed. It is read from the operand
131
+ * AS LOWERED, by its expression kind: a `const` (an IR constant, or the immediate of a
132
+ * one-operand op), an `addr` (a `gaddr`), a `var` (a scalar global's bare name, a temp, a
133
+ * parameter), a `field` (a member), and anything else, a `value` typed by the C it renders. A
134
+ * `cast` or a `sum` is one this rule spelled, found by the identity of the expression `arith`
135
+ * returned: a copy of it reads as a `value`, whose facts are read back out of its C
136
+ * (`constantExpr`, `carriesAddress`). So does a `(u8 *)g` another rule printed: it carries no
137
+ * global, and an op over it keeps the pointer sum where the table spells the integer sum in the
138
+ * asm's operand order. Each operand structure.ts hands `arith` is lowered from its IR value, and
139
+ * no lowering prints a global as a byte pointer; one that does goes through `arith`. */
140
+ export type Role =
141
+ | { readonly k: 'literal'; readonly e: Extract<Expr, { k: 'const' }> }
142
+ | { readonly k: 'address'; readonly e: Expr }
143
+ | { readonly k: 'name'; readonly e: Expr; readonly facts: NameFacts }
144
+ | { readonly k: 'member'; readonly e: Expr; readonly pointer: boolean }
145
+ | { readonly k: 'value'; readonly e: Expr; readonly made: Made }
146
+ | { readonly k: 'cast'; readonly e: Expr; readonly of: Role; readonly made: Made }
147
+ | {
148
+ readonly k: 'sum';
149
+ readonly e: Expr;
150
+ readonly op: BinOp;
151
+ readonly l: Role;
152
+ readonly r: Role;
153
+ readonly made: Made;
154
+ };
155
+
156
+ /** An expression every leaf of which is a constant (an unfolded `1 << 2`, a narrowed constant). */
157
+ const constantExpr = (x: Expr): boolean =>
158
+ x.k === 'const' || (x.k === 'cast' && constantExpr(x.e)) || (x.k === 'bin' && constantExpr(x.l) && constantExpr(x.r));
159
+ /** An expression whose value carries a global's address: `&g`, a cast of one, a sum with one. */
160
+ const carriesAddress = (x: Expr): boolean =>
161
+ x.k === 'addr' ||
162
+ (x.k === 'cast' && carriesAddress(x.e)) ||
163
+ (x.k === 'bin' && (x.op === '+' || x.op === '-') && (carriesAddress(x.l) || carriesAddress(x.r)));
164
+
165
+ const made = (x: Role): Made =>
166
+ x.k === 'cast' || x.k === 'sum' || x.k === 'value'
167
+ ? x.made
168
+ : x.k === 'literal'
169
+ ? { ...NOTHING_MADE, constant: true }
170
+ : x.k === 'address'
171
+ ? { ...NOTHING_MADE, address: true }
172
+ : NOTHING_MADE;
173
+
174
+ /** The facts of one side of an arithmetic op the table reads. */
175
+ export type SideFact =
176
+ /** an IR constant */
177
+ | 'literal'
178
+ /** a literal that is a whole number of the other side's pointee (wider than a byte) */
179
+ | 'exact'
180
+ /** renders a pointer */
181
+ | 'pointer'
182
+ /** renders a pointer whose pointee is not one byte */
183
+ | 'wide'
184
+ /** a pointer global's value or a pointer member (`pointerValue`) */
185
+ | 'pointerValue'
186
+ /** a bare name no map declaration types */
187
+ | 'undeclared'
188
+ /** a global's word no declaration types (`untypedWord`) */
189
+ | 'untyped'
190
+ | 'constant'
191
+ /** an additive expression with a constant term (`x + K`, `x - K`, `K + x`), read off the
192
+ * printed expression; `declaresBytePointer`'s `hasAddend` asks it of an IR value, through merges */
193
+ | 'addend'
194
+ /** a global's address is in it */
195
+ | 'address'
196
+ /** renders an integer, or is a bare name nothing types that is no pointer value, and holds no
197
+ * address */
198
+ | 'integer'
199
+ /** an integer sum of a global this rule cast back to the byte pointer */
200
+ | 'restored'
201
+ /** a sum this rule made with a global's byte pointer in it */
202
+ | 'byteSum'
203
+ /** a `byteSum` with the byte pointer of a global no map declaration types in it */
204
+ | 'undeclaredByteSum'
205
+ /** an additive sum this rule made, uncast */
206
+ | 'sum';
207
+
208
+ /** What each side of the op becomes, in order, each step applied to the last one's result:
209
+ * - `bytes`: `(u8 *)x`;
210
+ * - `elements`: the literal in elements of the other side's pointee;
211
+ * - `word`: a global's value as its word (`globalWord`);
212
+ * - `words`: each untyped global word in it as its word (`untypedWords`);
213
+ * - `integer`: the integer the value is under an operator C takes no pointer of (`integer`);
214
+ * - `partner`: the integer an integer sum's partner is (`partner`);
215
+ * - `asInt`: a byte sum as the integer it also is (`byteSumAsInt`). */
216
+ export type SideSpell = 'bytes' | 'elements' | 'word' | 'words' | 'integer' | 'partner' | 'asInt';
217
+
218
+ /** The pointer type the sum goes back to: the byte pointer, or the type the left or the right
219
+ * side renders. */
220
+ export type Restore = 'bytes' | 'left' | 'right';
221
+
222
+ export interface ArithRow {
223
+ /** the operator: `±` is both additive ones, `logical` is `&&`/`||`, `bitwise` is the rest */
224
+ readonly op: '+' | '-' | '±' | 'logical' | 'bitwise';
225
+ /** the row holds only where the compiler has this behavior */
226
+ readonly when?: keyof ArithCompilerFacts;
227
+ /** the row holds only where the sum is a value: held in a name, or read other than as the
228
+ * address of a load or store (`inlinedAccessBase`) */
229
+ readonly value?: true;
230
+ /** the IR types the op's result a pointer */
231
+ readonly resultPointer?: true;
232
+ readonly l?: Readonly<Partial<Record<SideFact, boolean>>>;
233
+ readonly r?: Readonly<Partial<Record<SideFact, boolean>>>;
234
+ /** what each side becomes */
235
+ readonly lSpell?: readonly SideSpell[];
236
+ readonly rSpell?: readonly SideSpell[];
237
+ readonly restore?: Restore;
238
+ }
239
+
240
+ /** THE ARITHMETIC TABLE, first match wins. An address operand is its word under every operator
241
+ * before the table is read (`(u32)&g`): its C type comes from the PROJECT's declaration, which
242
+ * makes `&gSym + K` byte-inexact (C scales K by sizeof(gSym)) and `&gSym & K` ill-formed; the
243
+ * deref folds look through that cast, so every access that can spell a named element or field
244
+ * still does.
245
+ *
246
+ * POINTER STRIDE. C pointer arithmetic is ELEMENT-scaled, but the asm added BYTES: a constant that
247
+ * is a whole number of the pointee's elements is divided by its size; any other offset on a
248
+ * pointer whose pointee is not one byte walks a byte pointer, `(u8 *)p + 62`, the same address in
249
+ * every world. Keyed on the RENDERED C type, never the IR value's recovered one: C scales by the
250
+ * type of the expression it sees, and a value recovered `s32*` can render as an int-typed tree,
251
+ * where dividing would bake in a wrong address. A RUNTIME offset has no element spelling at all,
252
+ * and the sum then goes back to the pointer type it started as, so the walk changes the
253
+ * arithmetic and nothing else: a bare `u8 *` sum would be a different C type from the slot it
254
+ * lands in (`v4 = (u8 *)a0 + (v1 << 2)` into an `s32 *` — an mwcc error). An inexact constant's
255
+ * sum is not cast back: a deref supplies its own cast, and intoDeclaredTemp's casts a temp's.
256
+ *
257
+ * TWO POINTERS. `ptr - ptr` is C's ELEMENT difference, where the asm subtracted bytes: agbcc
258
+ * compiles `(q - p) + m` on an `int *` to `sub; asr #2; add`. Both sides go byte pointers, the
259
+ * byte count in every world, unless both already are. `ptr + ptr` is not C; `ptr + (s32)ptr` is,
260
+ * so the left side is the base and walks as above.
261
+ *
262
+ * A POINTER VALUE (`gPtr`, `gSym.pBuf`, see `pointerValue`). C scales `gPtr + K` by
263
+ * sizeof(*gPtr) — 1 under the map's synthesized `void *`, but whatever the PROJECT's header
264
+ * declares in the world a user recompiles in. So the stride is made explicit, CAST-THEN-ADD,
265
+ * `(u8 *)gPtr + K`; add-then-cast (`(u8 *)(gPtr + K)`) is byte-correct in one world only. A
266
+ * MEMBER has no world where the raw spelling is right: the map declares its pointee width. Not
267
+ * foldable into the deref index either: `((u8 *)gPtr)[K + off]` re-scales K by the access width.
268
+ * `ptr + ptr` and `int - ptr` are not C, so the second pointer value goes its word.
269
+ *
270
+ * A global's value no declaration types, added to an integer, is a pointer value too when the IR
271
+ * types the SUM a pointer: `(u8 *)g + x` is the asm's address under every declaration of `g` once
272
+ * `x` renders an integer. An integer side that holds an address (`(u32)&gArr + gIdx`) says the
273
+ * address is the base and the global its index. Its partner is added as an integer, which it is
274
+ * under an integer declaration and a pointer one alike.
275
+ *
276
+ * THE INTEGER SUM. `x + (u8 *)g` is pointer arithmetic, and gcc makes the pointer the first
277
+ * operand of the add, which swaps the asm's. So under an integer left side, the value of a global
278
+ * no map declaration types, or a byte sum of one, is added as an integer in the asm's order, and
279
+ * the sum goes back to the byte pointer it would have been. The same global LEFT of a runtime
280
+ * offset is added as an integer too: CodeWarrior at -O4 puts the index first in every pointer
281
+ * sum, where an integer sum keeps the source's order, so `(u8 *)g + x` is the asm's order on
282
+ * agbcc, KMC gcc and IDO only. The partner goes integer with it, or a pointer partner would scale
283
+ * the sum. Except where the compiler keeps a pointer sum's constant addend and moves an integer
284
+ * sum's (`keepsPointerSumAddend`) and the offset has a constant term: the gcc family folds
285
+ * `(u32)g + (x + K)` to `(g + K) + x` and keeps the pointer sum's `g + (x + K)`, so there the
286
+ * offset keeps the pointer sum. Only where the sum is a VALUE, a call argument or a temp: spelled
287
+ * inside the load or store it is the address of, agbcc moves the constant out of both sums into
288
+ * the access, the pointer sum's as `(x + g) + K` and the integer sum's as `(g + x) + K`, so neither
289
+ * is the asm's `g + (x + K)` and the integer sum keeps its base-first order. A constant offset
290
+ * folds into the access and keeps `(u8 *)g + K`. A declared pointer keeps `x + (u8 *)p`, the
291
+ * operand the element and field spellings read.
292
+ *
293
+ * A byte sum less an integer sum of globals no declaration types: bare, `gB2 - gB3` is an element
294
+ * count under a wider pointer declaration of them, where the asm subtracted bytes.
295
+ *
296
+ * KNOWN GAP: a global's word no declaration types is left bare wherever no row casts it: beside an
297
+ * integer under an op the IR types an integer (`gW + x`, `gW + 4`), beside a global's address
298
+ * (`gW + (u32)&gArr`), left of a pointer it subtracts under an integer result or from an address
299
+ * (`gW - (s32)p`), and right of a byte pointer or a byte difference (`pb + gW`,
300
+ * `(u8 *)gPtr - (u8 *)gQ - gW`). That is the asm's integer under an integer declaration, the candidate's
301
+ * own world; under a wider pointer declaration C scales the other side or rejects the two
302
+ * pointers, and under a float one it is float math.
303
+ *
304
+ * C rejects a pointer operand under the non-additive operators (& | ^ << >> * / %), so there the
305
+ * asm's 32-bit integer math on the address is what is spelled. `&&`/`||` take a pointer operand
306
+ * legally — a truth test, no arithmetic. A pointer value under a COMPARISON needs nothing here: an
307
+ * icmp_s* pins every operand that does not provably render signed (structure.ts pinSigned). */
308
+ export const ARITH_ROWS: readonly ArithRow[] = [
309
+ { op: 'logical' },
310
+ { op: 'bitwise', lSpell: ['integer'], rSpell: ['integer'] },
311
+ // a literal right operand: the stride
312
+ { op: '±', r: { exact: true }, rSpell: ['elements'] },
313
+ { op: '±', l: { wide: true }, r: { literal: true }, lSpell: ['bytes'] },
314
+ { op: '±', l: { pointerValue: true }, r: { literal: true }, lSpell: ['bytes'] },
315
+ { op: '±', resultPointer: true, l: { untyped: true }, r: { literal: true }, lSpell: ['bytes'] },
316
+ { op: '±', r: { literal: true } },
317
+ // a literal left of `+`
318
+ { op: '+', l: { exact: true }, lSpell: ['elements'] },
319
+ { op: '+', l: { literal: true }, r: { wide: true }, rSpell: ['bytes'] },
320
+ { op: '+', l: { literal: true }, r: { pointer: true, undeclaredByteSum: true }, rSpell: ['asInt'], restore: 'bytes' },
321
+ { op: '+', l: { literal: true }, r: { pointerValue: true, undeclared: true }, rSpell: ['word'], restore: 'bytes' },
322
+ { op: '+', l: { literal: true }, r: { pointerValue: true }, rSpell: ['bytes'] },
323
+ { op: '+', resultPointer: true, l: { literal: true }, r: { untyped: true }, rSpell: ['word'], restore: 'bytes' },
324
+ { op: '+', l: { literal: true } },
325
+ // a rendered pointer left of `+`: the right side as an integer, and a wider pointer walked
326
+ { op: '+', l: { wide: true }, lSpell: ['bytes'], rSpell: ['integer'], restore: 'left' },
327
+ { op: '+', l: { pointer: true }, rSpell: ['integer'] },
328
+ // a rendered pointer left of `-`
329
+ { op: '-', l: { pointer: true }, r: { wide: true }, lSpell: ['bytes'], rSpell: ['bytes'] },
330
+ { op: '-', l: { wide: true }, r: { pointer: true }, lSpell: ['bytes'], rSpell: ['bytes'] },
331
+ { op: '-', l: { pointer: true }, r: { pointer: true } },
332
+ { op: '-', l: { wide: true }, r: { pointerValue: true }, lSpell: ['bytes'], rSpell: ['bytes'] },
333
+ { op: '-', l: { pointer: true }, r: { pointerValue: true }, rSpell: ['bytes'] },
334
+ { op: '-', l: { wide: true }, lSpell: ['bytes'], restore: 'left' },
335
+ { op: '-', l: { pointer: true, byteSum: true }, r: { sum: true }, rSpell: ['words'] },
336
+ { op: '-', l: { pointer: true } },
337
+ // `int - ptr`: the pointer goes its integer. A restored byte sum is that integer sum, which
338
+ // leaves a global the left side takes bare a pointer under a pointer declaration, which the
339
+ // integer would scale; a word, it is the asm's under any integer or pointer one.
340
+ { op: '-', l: { pointerValue: true, untyped: false }, r: { restored: true }, lSpell: ['bytes'], rSpell: ['integer'] },
341
+ { op: '-', r: { restored: true }, lSpell: ['words'], rSpell: ['integer'] },
342
+ { op: '-', l: { pointerValue: true }, r: { pointer: true }, lSpell: ['bytes'], rSpell: ['integer'] },
343
+ {
344
+ op: '-',
345
+ resultPointer: true,
346
+ l: { untyped: true },
347
+ r: { pointer: true, address: false },
348
+ lSpell: ['bytes'],
349
+ rSpell: ['integer'],
350
+ },
351
+ { op: '-', r: { pointer: true }, rSpell: ['integer'] },
352
+ // a pointer value left of `+`: one no map declares goes its word into the integer sum
353
+ {
354
+ op: '+',
355
+ l: { pointerValue: true, undeclared: true },
356
+ r: { wide: true, constant: false },
357
+ lSpell: ['word'],
358
+ rSpell: ['bytes', 'partner'],
359
+ restore: 'right',
360
+ },
361
+ {
362
+ op: '+',
363
+ when: 'keepsPointerSumAddend',
364
+ value: true,
365
+ l: { pointerValue: true, undeclared: true },
366
+ r: { integer: true, addend: true, constant: false },
367
+ lSpell: ['bytes'],
368
+ rSpell: ['words'],
369
+ },
370
+ {
371
+ op: '+',
372
+ l: { pointerValue: true, undeclared: true },
373
+ r: { constant: false },
374
+ lSpell: ['word'],
375
+ rSpell: ['partner'],
376
+ restore: 'bytes',
377
+ },
378
+ { op: '+', l: { pointerValue: true }, r: { wide: true }, lSpell: ['word'], rSpell: ['bytes'], restore: 'right' },
379
+ { op: '+', l: { pointerValue: true }, r: { pointer: true }, lSpell: ['word'] },
380
+ // a rendered pointer right of `+`
381
+ { op: '+', r: { wide: true }, rSpell: ['bytes'], restore: 'right' },
382
+ { op: '+', r: { pointer: true, undeclaredByteSum: true }, lSpell: ['words'], rSpell: ['asInt'], restore: 'bytes' },
383
+ { op: '+', r: { pointer: true } },
384
+ // no rendered pointer: `+`
385
+ { op: '+', l: { pointerValue: true }, r: { pointerValue: true }, lSpell: ['bytes'], rSpell: ['word'] },
386
+ { op: '+', l: { pointerValue: true }, lSpell: ['bytes'] },
387
+ { op: '+', r: { pointerValue: true, undeclared: true }, lSpell: ['words'], rSpell: ['word'], restore: 'bytes' },
388
+ { op: '+', r: { pointerValue: true }, rSpell: ['bytes'] },
389
+ {
390
+ op: '+',
391
+ resultPointer: true,
392
+ l: { untyped: true },
393
+ r: { integer: true, constant: false },
394
+ lSpell: ['word'],
395
+ rSpell: ['words'],
396
+ restore: 'bytes',
397
+ },
398
+ { op: '+', resultPointer: true, l: { untyped: true }, r: { integer: true }, lSpell: ['bytes'] },
399
+ {
400
+ op: '+',
401
+ resultPointer: true,
402
+ l: { integer: true },
403
+ r: { untyped: true },
404
+ lSpell: ['words'],
405
+ rSpell: ['word'],
406
+ restore: 'bytes',
407
+ },
408
+ { op: '+' },
409
+ // no rendered pointer: `-`
410
+ { op: '-', l: { pointerValue: true }, r: { pointerValue: true }, lSpell: ['bytes'], rSpell: ['bytes'] },
411
+ { op: '-', l: { pointerValue: true }, lSpell: ['bytes'] },
412
+ { op: '-', r: { pointerValue: true }, rSpell: ['word'] },
413
+ { op: '-', resultPointer: true, l: { untyped: true }, r: { integer: true }, lSpell: ['bytes'], rSpell: ['words'] },
414
+ { op: '-', l: { byteSum: true }, r: { sum: true }, rSpell: ['words'] },
415
+ { op: '-' },
416
+ ];
417
+
418
+ const ROW_TESTS = ARITH_ROWS.map((row) => ({
419
+ row,
420
+ l: Object.entries(row.l ?? {}) as [SideFact, boolean][],
421
+ r: Object.entries(row.r ?? {}) as [SideFact, boolean][],
422
+ }));
423
+
424
+ export interface PointerSpelling {
425
+ /** Whether a global's value is a POINTER VALUE, as every rule here reads it (`nameFacts`). */
426
+ pointerGlobal(name: string): boolean;
427
+ /** Whether a word stored at byte `byte` of global `name` lands in a cell `intoIntCell` casts a
428
+ * pointer into, as structure.ts spells the store: the bare global, or the member at that byte. */
429
+ castsIntoGlobal(name: string, byte: number): boolean;
430
+ needsIntSpelling(x: Expr): boolean;
431
+ intoDeclaredTemp(name: string, value: Expr): Expr;
432
+ intoPtrCell(lval: Expr, value: Expr): Expr;
433
+ /** The integer arithmetic op `d` (ARITH_TO_BIN) over its operands as lowered, spelled for the
434
+ * address the asm computed. `loadPairReversed`: `d` is commutative and its operands are a load
435
+ * pair the compiler evaluated right first (structure.ts), which re-spells them in that order
436
+ * unless `pointerSide` keeps them. */
437
+ arith(d: Op, l: Expr, r: Expr, loadPairReversed: boolean): Expr;
438
+ /** What an operand is, by the same reading `arith` makes. */
439
+ roleOf(x: Expr): Role;
440
+ }
441
+
442
+ const BYTE_PTR = T.ptr(T.u(8));
443
+
444
+ export function makePointerSpelling(deps: PointerSpellingDeps): PointerSpelling {
445
+ const {
446
+ sym,
447
+ pointerGlobals,
448
+ pointerLoadedGlobals,
449
+ wordLoadedGlobals,
450
+ scalarGlobals,
451
+ varType,
452
+ compiler = {},
453
+ strictPointerConversions = false,
454
+ dialect,
455
+ refuse = (why: string): never => {
456
+ throw new Error(why);
457
+ },
458
+ useSitesOf,
459
+ isNamed = () => false,
460
+ } = deps;
461
+ const ctype = (e0: Expr): IrType | undefined => exprCType(e0, (n) => varType.get(n));
462
+
463
+ /** The declared shape of a global as the pointer-value rules read it: the map's, or a pointer
464
+ * the map declares to a structuring that does not spell from it (`pointerGlobals`). The
465
+ * operand-order rules read `mapUndeclared` instead: a declared pointer keeps `x + (u8 *)p` for
466
+ * the element and field spellings to read, and a `pointerGlobals` name has no pointee for them,
467
+ * so its sum is spelled in the asm's order like any other undeclared global's. */
468
+ const declaredShape = (name: string): SymbolInfo['shape'] =>
469
+ sym?.info(name)?.shape ?? (pointerGlobals?.has(name) ? 'pointer' : undefined);
470
+
471
+ /** A bare name's facts. A POINTER VALUE is one whose type the project's header owns: a bare
472
+ * `gSym` naming a pointer global (the VALUE of a pointer cell). Load, store and compare of such
473
+ * a 4-byte cell are identical for any object-pointer type, so the declared pointee never matters
474
+ * to THEM; arithmetic on the loaded value is the opposite case, where the pointee's size scales
475
+ * what is added. `ctype` cannot see any of this: it types only params/locals.
476
+ *
477
+ * A bare global is one when its declared shape is `'pointer'`, or when no declaration this pass
478
+ * can read says anything (no map, a symtab-only name, entries that disagree and were dropped to
479
+ * the bare name, a name the map lacks) and the IR loads it as a pointer at least once. A map
480
+ * `shape:'scalar'` is a declaration and is excluded; so is a global the IR never loads as a
481
+ * pointer (`gCount + 1`, or the `u8` index in `gIdx + gItems`), where casting would make a
482
+ * pointer of an integer the source added as one. */
483
+ const nameFacts = (name: string): NameFacts => {
484
+ const shape = declaredShape(name);
485
+ const pointerLoaded = pointerLoadedGlobals.has(name);
486
+ return {
487
+ pointer: shape === 'pointer' || (shape === undefined && pointerLoaded),
488
+ undeclared: shape === undefined,
489
+ mapUndeclared: sym?.info(name)?.shape === undefined,
490
+ pointerLoaded,
491
+ wordLoaded: wordLoadedGlobals.has(name),
492
+ };
493
+ };
494
+
495
+ /** The values this rule spelled, by the expression it returned, so an op over one reads what it
496
+ * is made of. */
497
+ const spelled = new WeakMap<Expr, Role>();
498
+ const roleOf = (x: Expr): Role => {
499
+ const built = spelled.get(x);
500
+ if (built !== undefined) {
501
+ return built;
502
+ }
503
+ switch (x.k) {
504
+ case 'const':
505
+ return { k: 'literal', e: x };
506
+ case 'addr':
507
+ return { k: 'address', e: x };
508
+ case 'var':
509
+ return { k: 'name', e: x, facts: nameFacts(x.name) };
510
+ case 'field':
511
+ // a member the map declares a pointer (`gSym.pBuf`, `gPtr->pBuf`)
512
+ return { k: 'member', e: x, pointer: ptrMemberDecl(x, sym) !== null };
513
+ default:
514
+ return { k: 'value', e: x, made: { ...NOTHING_MADE, constant: constantExpr(x), address: carriesAddress(x) } };
515
+ }
516
+ };
517
+
518
+ /** A pointer value whose type the project's header owns (`nameFacts`), or a named member whose
519
+ * declaration is a pointer. */
520
+ const pointerValue = (x: Role): boolean => (x.k === 'name' && x.facts.pointer) || (x.k === 'member' && x.pointer);
521
+ /** A global's word value that no declaration this pass can read types, whatever the IR loaded
522
+ * it as. */
523
+ const untypedWord = (x: Role): boolean => x.k === 'name' && x.facts.wordLoaded && x.facts.undeclared;
524
+
525
+ const castTo = (to: IrType, of: Role): Role => {
526
+ const m = made(of);
527
+ const globalOf = (x: Role): GlobalFact =>
528
+ x.k === 'name' && (pointerValue(x) || untypedWord(x)) ? { any: true, undeclared: x.facts.mapUndeclared } : NOT;
529
+ const additive = of.k === 'sum' && (of.op === '+' || of.op === '-');
530
+ const bytes = typeEquals(to, BYTE_PTR);
531
+ const restored = bytes && additive ? m.intSum : NOT;
532
+ return {
533
+ k: 'cast',
534
+ e: { k: 'cast', to, e: of.e },
535
+ of,
536
+ made: {
537
+ constant: m.constant,
538
+ address: m.address,
539
+ globalBytes: bytes ? globalOf(of) : NOT,
540
+ globalWord: typeEquals(to, T.u(32))
541
+ ? either(m.globalBytes, of.k === 'sum' ? m.byteSum : NOT, globalOf(of))
542
+ : NOT,
543
+ byteSum: restored,
544
+ intSum: NOT,
545
+ restored,
546
+ },
547
+ };
548
+ };
549
+ const sumOf = (op: BinOp, l: Role, r: Role): Role => {
550
+ const [ml, mr] = [made(l), made(r)];
551
+ const additive = op === '+' || op === '-';
552
+ return {
553
+ k: 'sum',
554
+ e: { k: 'bin', op, l: l.e, r: r.e },
555
+ op,
556
+ l,
557
+ r,
558
+ made: {
559
+ constant: ml.constant && mr.constant,
560
+ address: additive && (ml.address || mr.address),
561
+ globalBytes: NOT,
562
+ globalWord: NOT,
563
+ byteSum: additive ? either(ml.globalBytes, mr.globalBytes, ml.byteSum, mr.byteSum) : NOT,
564
+ intSum: additive ? either(ml.globalWord, mr.globalWord, ml.intSum, mr.intSum) : NOT,
565
+ restored: NOT,
566
+ },
567
+ };
568
+ };
569
+
570
+ /** A pointer value or a global's word as the integer the asm added. A global no declaration
571
+ * types and the IR never loads as a pointer may be declared a float, whose value `(u32)g`
572
+ * converts, so it goes through `(u8 *)` first: `(u32)(u8 *)g` is `(u32)g`'s bytes under every
573
+ * integer and pointer declaration, and no C under a float one. KNOWN GAP: under an array or a
574
+ * function declaration `g` decays to its address, which this spells with no diagnostic; only a
575
+ * pun reads the word there, and intoDeclaredTemp's KNOWN GAP says why none is spelled. */
576
+ const globalWord = (x: Role): Role =>
577
+ castTo(T.u(32), x.k === 'name' && x.facts.undeclared && !x.facts.pointerLoaded ? castTo(BYTE_PTR, x) : x);
578
+ /** An integer operand with every untyped global word it adds or subtracts made a word as well:
579
+ * bare, `a0 + g` is pointer arithmetic under a pointer declaration of g. Other operators reject
580
+ * a pointer operand, and a load or a cast types its own value. */
581
+ const untypedWords = (x: Role): Role =>
582
+ untypedWord(x)
583
+ ? globalWord(x)
584
+ : x.k === 'sum' && (x.op === '+' || x.op === '-')
585
+ ? sumOf(x.op, untypedWords(x.l), untypedWords(x.r))
586
+ : x;
587
+ /** The integer a byte sum is. Every global it adds bare goes a word with it. A lone global it
588
+ * subtracts stays the pointer difference, `(u32)((u8 *)g + x - gB)`: the asm's integer under an
589
+ * integer or a byte-pointer declaration of `gB`, and no C under a wider pointer or array, a
590
+ * function or a float one, where the word `(u32)(u8 *)gB` would subtract an address. Under a byte
591
+ * array it subtracts the array's address: globalWord's KNOWN GAP. A difference has one operand
592
+ * order, so the asm's needs no integer sum.
593
+ *
594
+ * KNOWN GAP: an integer READER of a byte sum converts a pointer to an integer. The backend casts
595
+ * it where the reader's type is known (an assignment, a store through a typed slot, a return:
596
+ * cfamily `legalizePointerWrites`), and a compare against an integer compares it as a `u32`. A
597
+ * call argument and a global no declaration types keep the pointer, which agbcc and KMC gcc warn
598
+ * about and CodeWarrior rejects; only a pointer cell's sum passed to a parameter the prototype
599
+ * declares an integer is converted (l3/ptrcell.ts). The integer sum is no fix: gcc orders a
600
+ * pointer sum's operands and an integer sum's differently, so the bytes differ. */
601
+ const byteSumAsInt = (x: Role): Role => {
602
+ if (x.k === 'cast' && x.made.restored.any) {
603
+ return x.of;
604
+ }
605
+ if (x.k === 'cast' && x.made.globalBytes.any) {
606
+ return globalWord(x.of);
607
+ }
608
+ if (x.k === 'sum' && x.made.byteSum.any) {
609
+ return x.op === '-' && untypedWord(x.r) ? castTo(T.u(32), x) : sumOf(x.op, byteSumAsInt(x.l), byteSumAsInt(x.r));
610
+ }
611
+ return untypedWords(x);
612
+ };
613
+
614
+ /** A side's spelling step (`SideSpell`); `other` is the op's other side. */
615
+ const spell = (s: SideSpell, x: Role, other: Role): Role => {
616
+ switch (s) {
617
+ case 'bytes':
618
+ return castTo(BYTE_PTR, x);
619
+ case 'elements': {
620
+ const t = ctype(other.e) as Extract<IrType, { kind: 'ptr' }>;
621
+ return {
622
+ k: 'literal',
623
+ e: { k: 'const', value: (x.e as Extract<Expr, { k: 'const' }>).value / ptrElemBytes(t.to) },
624
+ };
625
+ }
626
+ case 'word':
627
+ return globalWord(x);
628
+ case 'words':
629
+ return untypedWords(x);
630
+ case 'integer':
631
+ return x.k === 'cast' && x.made.restored.any
632
+ ? x.of
633
+ : ctype(x.e)?.kind === 'ptr'
634
+ ? castTo(T.s(32), x)
635
+ : pointerValue(x)
636
+ ? globalWord(x)
637
+ : x;
638
+ case 'partner':
639
+ return pointerValue(x)
640
+ ? globalWord(x)
641
+ : ctype(x.e)?.kind === 'ptr'
642
+ ? made(x).byteSum.any
643
+ ? byteSumAsInt(x)
644
+ : castTo(T.u(32), x)
645
+ : untypedWords(x);
646
+ case 'asInt':
647
+ return byteSumAsInt(x);
648
+ }
649
+ };
650
+
651
+ const sideFacts = (x: Role, other: Role): Record<SideFact, boolean> => {
652
+ const t = ctype(x.e);
653
+ const ot = ctype(other.e);
654
+ const m = made(x);
655
+ const pv = pointerValue(x);
656
+ const es = ot?.kind === 'ptr' ? ptrElemBytes(ot.to) : 0;
657
+ return {
658
+ literal: x.k === 'literal',
659
+ exact: x.k === 'literal' && es > 1 && x.e.value % es === 0,
660
+ pointer: t?.kind === 'ptr',
661
+ wide: t?.kind === 'ptr' && ptrElemBytes(t.to) !== 1,
662
+ pointerValue: pv,
663
+ undeclared: x.k === 'name' && x.facts.mapUndeclared,
664
+ untyped: untypedWord(x),
665
+ constant: m.constant,
666
+ addend: x.e.k === 'bin' && (x.e.op === '+' || x.e.op === '-') && (constantExpr(x.e.l) || constantExpr(x.e.r)),
667
+ address: m.address,
668
+ integer: !m.address && (t?.kind === 'int' || (x.k === 'name' && t === undefined && !pv)),
669
+ restored: m.restored.any,
670
+ byteSum: m.byteSum.any,
671
+ undeclaredByteSum: m.byteSum.undeclared,
672
+ sum: x.k === 'sum' && (x.op === '+' || x.op === '-'),
673
+ };
674
+ };
675
+
676
+ /** A side the evaluation-order re-spelling may not move: a rendered pointer, or a global's
677
+ * value no map declaration types (a `pointerGlobals` name's too) that the IR loads as a pointer,
678
+ * or as a word into an op the IR types a pointer. Beside an integer the table spells such a
679
+ * global as its word in an integer sum, `(u32)gPtr + x` or `(u32)(u8 *)gIdx + (u32)(u8 *)gPtr`,
680
+ * and the IR's operand order is then the asm's where the IR evaluated the right side first: IDO
681
+ * 7.1 compiles `usep(gJ + gP)` after `use(*gP)`, and `use(*(gIdx + gPtr))`, from the integer sum
682
+ * in that order, where the evaluation order changes the code.
683
+ *
684
+ * It reads other facts than the table's rows. A pointer global the map declares is moved,
685
+ * though beside an integer the table spells it as a pointer, `x + (u8 *)gP`: a pointer sum,
686
+ * whose operand order is the compiler's own (THE INTEGER SUM in the table's doc). */
687
+ const pointerSide = (x: Role, resultPointer: boolean): boolean =>
688
+ ctype(x.e)?.kind === 'ptr' ||
689
+ (x.k === 'name' && x.facts.mapUndeclared && (x.facts.pointerLoaded || (resultPointer && x.facts.wordLoaded)));
690
+
691
+ const arith = (d: Op, l0: Expr, r0: Expr, loadPairReversed: boolean): Expr => {
692
+ const op = ARITH_TO_BIN[d.opcode];
693
+ const resultPointer = d.results[0]?.type.kind === 'ptr';
694
+ let l = roleOf(l0);
695
+ let r = roleOf(r0);
696
+ if (loadPairReversed && !pointerSide(l, resultPointer) && !pointerSide(r, resultPointer)) {
697
+ [l, r] = [r, l];
698
+ }
699
+ const addressWord = (x: Role): Role => (x.k === 'address' ? castTo(T.u(32), x) : x);
700
+ l = addressWord(l);
701
+ r = addressWord(r);
702
+ const opClass = op === '+' || op === '-' ? op : op === '&&' || op === '||' ? 'logical' : 'bitwise';
703
+ const lf = sideFacts(l, r);
704
+ const rf = sideFacts(r, l);
705
+ const result = d.results[0];
706
+ const readers = result === undefined ? [] : (useSitesOf?.get(result) ?? []).map((u) => u.op);
707
+ const accessAddress = result !== undefined && inlinedAccessBase(result, readers, isNamed);
708
+ const { row } = ROW_TESTS.find(
709
+ (w) =>
710
+ (w.row.op === opClass || (w.row.op === '±' && (opClass === '+' || opClass === '-'))) &&
711
+ (w.row.when === undefined || compiler[w.row.when] === true) &&
712
+ (w.row.value === undefined || !accessAddress) &&
713
+ (w.row.resultPointer === undefined || resultPointer) &&
714
+ w.l.every(([f, v]) => lf[f] === v) &&
715
+ w.r.every(([f, v]) => rf[f] === v),
716
+ )!;
717
+ const ls = (row.lSpell ?? []).reduce((x, s) => spell(s, x, r), l);
718
+ const rs = (row.rSpell ?? []).reduce((x, s) => spell(s, x, l), r);
719
+ const sum = sumOf(op, ls, rs);
720
+ const restoreTo =
721
+ row.restore === 'bytes'
722
+ ? BYTE_PTR
723
+ : row.restore === 'left'
724
+ ? ctype(l.e)
725
+ : row.restore === 'right'
726
+ ? ctype(r.e)
727
+ : undefined;
728
+ const out = restoreTo ? castTo(restoreTo, sum) : sum;
729
+ spelled.set(out.e, out);
730
+ return out.e;
731
+ };
732
+
733
+ /** Operands `-`/`~` cannot take as spelled: a rendered pointer, a bare `&gSym`, a pointer
734
+ * global's value. All three are ill-formed C under a unary arithmetic operator — the asm did
735
+ * 32-bit integer math on the address, so that is what gets spelled. */
736
+ const needsIntSpelling = (x: Expr): boolean => {
737
+ const role = roleOf(x);
738
+ return ctype(x)?.kind === 'ptr' || role.k === 'address' || pointerValue(role);
739
+ };
740
+
741
+ /** A value assigned into a TEMP THIS PASS DECLARES, spelled so the assignment is legal against
742
+ * that declaration. Two inhabitants, one argument: the value's type comes from somewhere this
743
+ * pass does not control, and the temp's comes from here.
744
+ *
745
+ * `&gSym` assigned to a `T *` local: the address of an AGGREGATE is not a pointer to its
746
+ * element. `&gArr` is `T (*)[n]`, `&gStruct` is `struct S *`, and neither is assignable to
747
+ * `T *` — yet the IR's `gaddr` value legitimately has type `T *`, because that is what the asm
748
+ * loaded. The bare spelling therefore states a type the project's own header contradicts.
749
+ *
750
+ * It survived because agbcc only WARNS ("assignment from incompatible pointer type") and
751
+ * computes the right address anyway. That leniency is not something to rely on: the Klonoa
752
+ * project's own build template treats these as fatal, so the row's emitted C does not build
753
+ * where its author would put it. The cast is the always-valid spelling — the same fallback
754
+ * `bareArrayLead` documents for the indexed form — and it is byte-identical, so no benchmark row
755
+ * moves either way and the rule that decides it is pinned in test/deref-typing.test.ts instead.
756
+ *
757
+ * The test is whether `&gSym`'s rendered type PROVABLY equals the destination's, not whether the
758
+ * symbol looks like an aggregate. A SHAPE ENUMERATION MISSES THREE WAYS, each real:
759
+ * `shape:'pointer'` declares a pointer cell (`void *gSym`, or `struct Tag *gSym` when the pointee
760
+ * has a declarable layout), so `&gSym` is a pointer-to-pointer either way; a `shape:'scalar'`
761
+ * whose width differs from the destination's pointee gives `s32 *` for a `u16 *` slot; and a
762
+ * NAME-ONLY symbol is synthesized as `extern u32 gSym;` (declare.ts), which is `u32 *`. So the
763
+ * default is to CAST, and the cast is omitted only
764
+ * where the declared cell type is known and matches exactly. Byte-identical either way, so the
765
+ * cost of casting one time too many is a redundant `(T *)`, never a wrong address. */
766
+ const intoDeclaredTemp = (name: string, value: Expr): Expr => {
767
+ const t = varType.get(name);
768
+ if (t === undefined) {
769
+ return value;
770
+ }
771
+ // ── pointer values whose type this side of the program does not own ────────────────────────
772
+ // A `gaddr` at least states a type the IR knows. `gSym.pBuf` and a pointer global's own value
773
+ // (pointerValue) state one only the project's header knows, and `ctype` — which types params
774
+ // and locals — reads them as `undefined`, so the test above cannot see them at all. Assigning
775
+ // one bare declares that the temp's type and the project's declaration of that pointer are the
776
+ // same type, which nothing here established: the project's header says
777
+ // `struct Unk_03005284 *` where the recovered temp says `struct Struct0 *`, and `-Werror` makes
778
+ // the mismatch fatal in the tree the source is pasted into. The destination's type is the one
779
+ // this pass DID choose, so unlike the map-declared cells below it can be named exactly rather
780
+ // than defused through `void *`. That holds for an INTEGER temp too, and there the diagnostic
781
+ // is the mirror one, `assignment makes integer from pointer without a cast` — same site, same
782
+ // argument, same `(T)` answer.
783
+ //
784
+ // A value that RENDERS a pointer of another type is the same assignment with its type in plain
785
+ // sight: the arithmetic table spells `(u8 *)gPtr + 3544` and `(u8 *)a0 + 2` for the address
786
+ // alone, and a `u16 *` or `s32 *` temp takes neither without the cast.
787
+ //
788
+ // KNOWN GAP: a global no declaration types, read at offset 0 only, may be declared an ARRAY
789
+ // (`u16 *gArr[4]`, the asm reading `gArr[0]`). Its value spelling is then the array's address,
790
+ // and this cast compiles that wrong value with no diagnostic, where the bare value keeps
791
+ // agbcc's `incompatible pointer type`, fatal under -Werror. The cast is load-bearing all the
792
+ // same: a substitution variation (`/unmerge`) carries the temp's value into the arithmetic it
793
+ // feeds, and bare, that arithmetic scales by the declared pointee. A pun, `*(T *)&gArr`, is no
794
+ // fix: where the declaration is not exactly T it loses gcc's fold of the read (agbcc
795
+ // c-typeck.c), it drops a `volatile` declaration's qualifier, and agbcc's strict aliasing at
796
+ // -O2 moves it past a store through the declared type.
797
+ const vt = ctype(value);
798
+ if (pointerValue(roleOf(value)) || (vt?.kind === 'ptr' && !typeEquals(vt, t))) {
799
+ return { k: 'cast', to: t, e: value };
800
+ }
801
+ if (t.kind !== 'ptr' || value.k !== 'addr') {
802
+ return value;
803
+ }
804
+ // The only provably-redundant case: a NON-VOLATILE scalar cell whose DECLARED type is the
805
+ // destination's pointee, where `&gSym` already denotes exactly `T *`.
806
+ //
807
+ // `scalarCellType` and not `scalarTypeForAccess`: the latter answers what an ACCESS of that
808
+ // width reads and collapses every 4-byte access to `s32`, so it called a `u32` cell equal to an
809
+ // `s32 *` destination and let the incompatible assignment through. And a `volatile` cell makes
810
+ // `&gSym` a `volatile T *`, so omitting the cast would DISCARD the qualifier — the same class of
811
+ // fatal-under-a-strict-build defect this rule exists to remove.
812
+ const si = sym?.info(value.name);
813
+ if (si?.shape === 'scalar' && !si.volatile && isScalarCellSize(si.size)) {
814
+ if (typeEquals(scalarCellType(si.size, si.signed), t.to)) {
815
+ return value;
816
+ }
817
+ }
818
+ return { k: 'cast', to: t, e: value };
819
+ };
820
+
821
+ /** The integer type the map declares a word cell: a scalar global, or a member a map
822
+ * declaration names (`gT.b`, `gPtr->b`) that is no pointer, bitfield or array. */
823
+ const intCellType = (lval: Expr): IrType | undefined => {
824
+ if (lval.k === 'var') {
825
+ const si = sym?.info(lval.name);
826
+ return si?.shape === 'scalar' && si.size === 4 ? scalarCellType(4, si.signed) : undefined;
827
+ }
828
+ const f = declaredMemberOf(lval, sym);
829
+ const t = f === null || isBitfieldField(f) ? undefined : symbolFieldType(f);
830
+ return t?.kind === 'int' && t.width === 32 ? t : undefined;
831
+ };
832
+
833
+ /** The mirror of `intoPtrCell`: a value that is no integer as spelled (`needsIntSpelling`)
834
+ * assigned into a word cell the map declares an integer (`intCellType`) takes that cell's type.
835
+ * Without it agbcc and KMC gcc warn `assignment makes integer from pointer without a cast` —
836
+ * fatal under the project's `-Werror` — and CodeWarrior rejects the write. A conversion of a
837
+ * word is no instruction, so the bytes are the uncast write's. A global no declaration types
838
+ * keeps the value as spelled: its candidate world may declare it either way. */
839
+ const intoIntCell = (lval: Expr, value: Expr): Expr => {
840
+ const t = intCellType(lval);
841
+ return t !== undefined && needsIntSpelling(value) ? { k: 'cast', to: t, e: value } : value;
842
+ };
843
+
844
+ /** A pointer VALUE assigned into a pointer CELL (pointerValue), spelled so the assignment is
845
+ * legal against ANY pointer declaration of that cell. The arithmetic table renders such a
846
+ * right-hand side `(u8 *)gS.pBuf + K` — the right ADDRESS in every world, which is the whole
847
+ * point of it, and a `u8 *` where the declaration says `u16 *`. READING one is fine (C converts
848
+ * an object pointer freely under a deref, a call argument or a compare); ASSIGNING one is
849
+ * `warning: assignment from incompatible pointer type`, and this project's own `-Werror`
850
+ * compiler template makes that FATAL — so a source that scores clean here fails to build in the
851
+ * tree a user pastes it into, which no score gate can observe.
852
+ *
853
+ * `void *`, NOT the map's declared pointee: it is the one target assignment-compatible with any
854
+ * object-pointer declaration, the same "same answer in every world" property the byte
855
+ * arithmetic exists for. Trusting the map's pointee would put the spelling back in one world. It
856
+ * costs nothing: agbcc compiles `p = (void *)((u8 *)p + 4)` and the warning-carrying
857
+ * `p = (u8 *)p + 4` to BYTE-IDENTICAL objects (`-mthumb-interwork -Wimplicit -O2 -fhex-asm
858
+ * -fprologue-bugfix`), so the fix is invisible to the differ and visible to the compiler.
859
+ *
860
+ * A project may still declare a cell no map types an INTEGER (`u32 gSym;`), where `(void *)`
861
+ * makes an integer from a pointer: agbcc warns, CodeWarrior rejects it. The candidate's own world
862
+ * declares the cell `void *` (l3/symbol-refs.ts), and `/int-cell` (l3/intcell.ts) spells the
863
+ * integer declaration's source, published only at a byte-exact score. */
864
+ const intoPtrCell = (lval: Expr, value: Expr): Expr => {
865
+ if (!pointerValue(roleOf(lval))) {
866
+ return intoIntCell(lval, value);
867
+ }
868
+ const vt = ctype(value);
869
+ // BOTH ways a pointer value reaches here. `ctype` types params and locals, so it sees the
870
+ // table's own `(u8 *)…` and nothing else: a bare `gSym.pBuf` or a pointer global's value —
871
+ // the population this rule exists for — reads `undefined` there. An already-`void *` value is
872
+ // assignable as it stands.
873
+ const isPtr = pointerValue(roleOf(value)) || (vt?.kind === 'ptr' && vt.to.kind !== 'void');
874
+ if (isPtr) {
875
+ return pointerCellValue(value);
876
+ }
877
+ // AN INTEGER stored into the cell — an address table's `u32` element (`gp = t[i]`), a numeric
878
+ // address — is a conversion C makes implicitly and mwcc's C refuses ("illegal implicit
879
+ // conversion" of a `const unsigned int` to an `int *`, compiled). The same `(void *)` spells it,
880
+ // which every object-pointer declaration of the cell takes, and a word's conversion is no
881
+ // instruction. The literal `0` is the null pointer and converts everywhere; anything
882
+ // `needsIntSpelling` reads as a pointer already is one.
883
+ //
884
+ // Only where a declaration SAYS the cell is a pointer: the map's `shape: 'pointer'`, or a
885
+ // member it declares one. A cell read as a pointer only because the body dereferences it may be
886
+ // the project's `u32 gSym;`, which mwcc refuses a `void *` (compiled) and takes the bare value.
887
+ // Nor a bare name no declaration this pass reads types (`gp = gq`, `gq` a shape-less global):
888
+ // its header may declare it a pointer, and C++ refuses `void *` into a typed pointer. A global
889
+ // the map declares a scalar IS typed — an integer, cast like `t[i]` (`gp = (void *)gw`, compiled).
890
+ //
891
+ // C++ takes neither spelling into the header's `int *gp` ("illegal implicit conversion from
892
+ // 'void *' to 'int *'", compiled): only a cast to the cell's own type converts, and the map
893
+ // names no pointee type to cast to. So C++ declines here rather than publish either.
894
+ const lv = roleOf(lval);
895
+ const declaredCell = (lv.k === 'name' && !lv.facts.undeclared) || lv.k === 'member';
896
+ const untypedName = value.k === 'var' && ctype(value) === undefined && declaredShape(value.name) !== 'scalar';
897
+ const integer = !(value.k === 'const' && value.value === 0) && !needsIntSpelling(value) && !untypedName;
898
+ if (dialect === 'c++' && declaredCell && integer) {
899
+ refuse(
900
+ 'stores an integer into a declared pointer cell, which C++ converts only by a cast to the cell’s own type, and no declaration this pass reads names it',
901
+ );
902
+ }
903
+ return strictPointerConversions && declaredCell && integer ? pointerCellValue(value) : value;
904
+ };
905
+
906
+ const pointerGlobal = (name: string): boolean => nameFacts(name).pointer;
907
+
908
+ // A volatile global is none: structure.ts may spell its store through a qualified pointer
909
+ // (`pinnedAccess`), a cell `intCellType` does not read.
910
+ const castsIntoGlobal = (name: string, byte: number): boolean => {
911
+ const si = sym?.info(name);
912
+ if (si === undefined || si.volatile) {
913
+ return false;
914
+ }
915
+ const member = structMemberAt(si, byte, 4, true);
916
+ const lval: Expr | undefined =
917
+ si.shape === 'scalar'
918
+ ? byte === 0 && scalarGlobals.has(name)
919
+ ? { k: 'var', name }
920
+ : undefined
921
+ : member && { k: 'field', base: { k: 'var', name }, name: member.name, dot: true };
922
+ return lval !== undefined && intCellType(lval) !== undefined;
923
+ };
924
+
925
+ return { pointerGlobal, castsIntoGlobal, needsIntSpelling, intoDeclaredTemp, intoPtrCell, arith, roleOf };
926
+ }
927
+
928
+ /** The IR around a value that `holdsPointerWord` and `declaresBytePointer` read. */
929
+ export interface PointerWordIr {
930
+ defOf(v: Value): Op | undefined;
931
+ /** a block parameter's in-edge values; undefined for any other value */
932
+ inArgs(v: Value): readonly Value[] | undefined;
933
+ /** the ops that read `v`, a terminator passing it on an edge included */
934
+ usesOf(v: Value): readonly Op[];
935
+ /** whether a name holds `v` */
936
+ isNamed(v: Value): boolean;
937
+ }
938
+
939
+ /** Whether `v` is spelled inside the loads and stores it is the address of and nowhere else, so
940
+ * is no value: no name holds it, and it has readers, each a load or store, an element access's
941
+ * included, reading it as its base (operand 0) and as nothing else. agbcc moves such a sum's
942
+ * constant addend into the access under the pointer and the integer sum alike (ARITH_ROWS, THE
943
+ * INTEGER SUM). The table's value row and `declaresBytePointer` read it. */
944
+ export function inlinedAccessBase(v: Value, readers: readonly Op[], isNamed: (v: Value) => boolean): boolean {
945
+ return (
946
+ !isNamed(v) &&
947
+ readers.length > 0 &&
948
+ readers.every((op) => MEM_BASE_OPS.has(op.opcode) && op.operands.lastIndexOf(v) === 0)
949
+ );
950
+ }
951
+
952
+ /** Whether `v` only ever holds a pointer global's value: a whole-word load at offset 0 of a global
953
+ * `pointerGlobal` says is a pointer value (`PointerSpelling.pointerGlobal`), or a block parameter
954
+ * every in-edge value of which is one.
955
+ *
956
+ * A temp holding such a value may be declared `u8 *` where the IR types it an integer
957
+ * (`declaresBytePointer` says when), on a compiler whose integer sum loses the source's
958
+ * association and whose pointer sum keeps it (`compilerBehaviors.keepsPointerSumAddend`,
959
+ * structure.ts). Byte arithmetic is the asm's address under every pointer declaration of the
960
+ * global; a declared pointee would scale it, and `void *` arithmetic is a GNU extension.
961
+ * `intoDeclaredTemp` casts the value into the temp, `(u8 *)g`, which is the same word under an
962
+ * integer declaration of a global no declaration types.
963
+ *
964
+ * A word the IR never loads as a pointer, of a global no declaration types (`untypedWord`), is
965
+ * not one: the table spells it an integer beside an integer, and the temp keeps the integer with
966
+ * it. Cast into a `u8 *` temp, it would be the array's address with no diagnostic under an array
967
+ * declaration (`intoDeclaredTemp`'s KNOWN GAP), where the integer temp's assignment warns. */
968
+ export function holdsPointerWord(
969
+ v: Value,
970
+ ir: Pick<PointerWordIr, 'defOf' | 'inArgs'>,
971
+ pointerGlobal: (name: string) => boolean,
972
+ seen: Set<Value> = new Set(),
973
+ ): boolean {
974
+ if (seen.has(v)) {
975
+ return true;
976
+ }
977
+ seen.add(v);
978
+ const d = ir.defOf(v);
979
+ if (d !== undefined) {
980
+ const g = d.opcode === 'load' && d.attrs.off === 0 && d.attrs.width === 4 ? ir.defOf(d.operands[0]) : undefined;
981
+ return g?.opcode === 'gaddr' && pointerGlobal(g.attrs.sym as string);
982
+ }
983
+ const ins = ir.inArgs(v);
984
+ return ins !== undefined && ins.length > 0 && ins.every((a) => holdsPointerWord(a, ir, pointerGlobal, seen));
985
+ }
986
+
987
+ /** Whether a temp whose values are `values` is declared `u8 *` where the IR types it an integer.
988
+ *
989
+ * Every value holds a pointer global's word (`holdsPointerWord`), and one of them is the base of a
990
+ * sum with a constant addend, `t + (x + K)`, where `x + K` may be merged across arms and `x` is
991
+ * not itself a constant, and the sum is a value (not an `inlinedAccessBase`). There the integer
992
+ * temp's sum loses the source's association and the byte pointer's keeps it. Without a variable
993
+ * addend the two compile alike, so the temp keeps the integer the IR gives it. A sum spelled only
994
+ * inside the accesses it addresses is the source's under neither: there agbcc compiles the byte
995
+ * pointer's index first, `(x + t) + K`, and which of the two is closer depends on the index, so
996
+ * it licenses nothing.
997
+ *
998
+ * A use the byte pointer would print wrong refuses it:
999
+ * - a sum, of the temp or of a sum it is the base of, whose other operand comes first: `x + t` is
1000
+ * a pointer sum the gcc family orders pointer first, the swap of the asm's `x + t` (ARITH_ROWS,
1001
+ * THE INTEGER SUM);
1002
+ * - such a sum with a global's address or a pointer: `t + gArr` prints the subscript `gArr[t]`,
1003
+ * which C rejects for a pointer index, and two pointers do not add;
1004
+ * - the temp, or such a sum, as a switch selector or an array index, which C rejects for a
1005
+ * pointer;
1006
+ * - the temp itself as a call argument, or stored to a global anywhere but a word cell the map
1007
+ * declares an integer (`castsIntoGlobal`), where the byte pointer buys no association and would
1008
+ * warn: the backend casts a pointer into a declared integer local or slot (cfamily
1009
+ * `legalizePointerWrites`) and that cell (`intoIntCell`), and not into a callee's parameter or a
1010
+ * global no declaration types. A SUM keeps the byte pointer there, its association being the
1011
+ * point, and a call argument keeps the pointer (`byteSumAsInt`'s KNOWN GAP). */
1012
+ export function declaresBytePointer(
1013
+ values: readonly Value[],
1014
+ ir: PointerWordIr,
1015
+ spelling: Pick<PointerSpelling, 'pointerGlobal' | 'castsIntoGlobal'>,
1016
+ ): boolean {
1017
+ const isGlobal = (x: Value): boolean => ir.defOf(x)?.opcode === 'gaddr';
1018
+ const intoGlobal = (x: Value, u: Op): boolean =>
1019
+ u.opcode === 'store' &&
1020
+ u.operands[1] === x &&
1021
+ isGlobal(u.operands[0]) &&
1022
+ !(
1023
+ u.attrs.width === 4 &&
1024
+ spelling.castsIntoGlobal(ir.defOf(u.operands[0])!.attrs.sym as string, u.attrs.off as number)
1025
+ );
1026
+ const integerOnly = (x: Value, u: Op): boolean =>
1027
+ u.opcode === 'switch_br' || ((u.opcode === 'aload' || u.opcode === 'astore') && u.operands[1] === x);
1028
+ const baseOf = (t: Value, u: Op): boolean => {
1029
+ const [l, r] = u.operands;
1030
+ return l === t && r !== t && !isGlobal(r) && r.type.kind !== 'ptr';
1031
+ };
1032
+ // a sum stays a pointer sum through every sum that reads it, each with it as the base
1033
+ const okSum = (s: Value): boolean =>
1034
+ ir.usesOf(s).every((w) => !integerOnly(s, w) && (w.opcode !== 'add' || (baseOf(s, w) && okSum(w.results[0]))));
1035
+ const okUse = (t: Value, u: Op): boolean =>
1036
+ u.opcode === 'add'
1037
+ ? baseOf(t, u) && okSum(u.results[0])
1038
+ : u.opcode !== 'call' && !integerOnly(t, u) && !intoGlobal(t, u);
1039
+ const constants = new Map<Value, boolean>();
1040
+ // `x + K` with `x` not itself constant, or a merge every in-edge of which passes one: the
1041
+ // arithmetic table's `addend` side fact, asked of an IR value rather than a printed expression
1042
+ const hasAddend = (x: Value, path: Set<Value> = new Set()): boolean => {
1043
+ const d = ir.defOf(x);
1044
+ if (d !== undefined) {
1045
+ const constant = d.operands.map((o) => isConstant(o, ir, constants));
1046
+ return (d.opcode === 'add' || d.opcode === 'sub') && constant.includes(true) && constant.includes(false);
1047
+ }
1048
+ const ins = ir.inArgs(x);
1049
+ if (path.has(x) || ins === undefined || ins.length === 0) {
1050
+ return false;
1051
+ }
1052
+ path.add(x);
1053
+ return ins.every((a) => hasAddend(a, path));
1054
+ };
1055
+ return (
1056
+ values.every((t) => holdsPointerWord(t, ir, spelling.pointerGlobal) && ir.usesOf(t).every((u) => okUse(t, u))) &&
1057
+ values.some((t) =>
1058
+ ir
1059
+ .usesOf(t)
1060
+ .some(
1061
+ (u) =>
1062
+ u.opcode === 'add' &&
1063
+ hasAddend(u.operands[1]) &&
1064
+ !inlinedAccessBase(u.results[0], ir.usesOf(u.results[0]), ir.isNamed),
1065
+ ),
1066
+ )
1067
+ );
1068
+ }
1069
+
1070
+ /** Ops whose value is a constant when every operand's is. */
1071
+ const CONSTANT_FOLDING = new Set(['shl', 'add', 'sub', 'mul', 'or', 'and', 'xor']);
1072
+
1073
+ /** Whether `v` is a constant: a `const`, a fold of constants, or a block parameter every in-edge
1074
+ * of which passes one. A loop back to a parameter is not, as a counter is not.
1075
+ *
1076
+ * `known` holds the answers already given. A value reached by two paths is answered once, or a
1077
+ * chain of N merges each of whose arms reads the last is walked 2^N times. An answer does not
1078
+ * depend on the path it was reached by: a value from which a cycle is reachable is no constant
1079
+ * from any path. */
1080
+ function isConstant(
1081
+ v: Value,
1082
+ ir: Pick<PointerWordIr, 'defOf' | 'inArgs'>,
1083
+ known: Map<Value, boolean>,
1084
+ path: Set<Value> = new Set(),
1085
+ ): boolean {
1086
+ const answer = known.get(v);
1087
+ if (answer !== undefined) {
1088
+ return answer;
1089
+ }
1090
+ if (path.has(v)) {
1091
+ return false;
1092
+ }
1093
+ const d = ir.defOf(v);
1094
+ if (d?.opcode === 'const') {
1095
+ return true;
1096
+ }
1097
+ const from = d === undefined ? ir.inArgs(v) : CONSTANT_FOLDING.has(d.opcode) ? d.operands : undefined;
1098
+ if (from === undefined || from.length === 0) {
1099
+ return false;
1100
+ }
1101
+ path.add(v);
1102
+ const all = from.every((x) => isConstant(x, ir, known, path));
1103
+ path.delete(v);
1104
+ known.set(v, all);
1105
+ return all;
1106
+ }