@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.
- package/README.md +25 -24
- package/package.json +1 -1
- package/src/aggregate.ts +149 -0
- package/src/backend/c.ts +1 -0
- package/src/backend/cfamily.ts +165 -50
- package/src/backend/cpp.ts +2 -1
- package/src/backend/pascal.ts +31 -12
- package/src/contracts.ts +173 -16
- package/src/declare.ts +69 -20
- package/src/frontend/asmdata.ts +7 -1
- package/src/frontend/call-plan.ts +964 -60
- package/src/frontend/dead-reads.ts +628 -0
- package/src/frontend/device-pins.ts +23 -4
- package/src/frontend/emit.ts +4 -3
- package/src/frontend/fpu.ts +224 -17
- package/src/frontend/frame-objects.ts +1232 -192
- package/src/frontend/frontend.ts +64 -3
- package/src/frontend/high-half.ts +53 -4
- package/src/frontend/jump-table.ts +530 -0
- package/src/frontend/local-object.ts +158 -32
- package/src/frontend/mips.ts +2076 -215
- package/src/frontend/own-struct-return.ts +500 -0
- package/src/frontend/ppc.ts +2711 -232
- package/src/frontend/register-struct-call.ts +484 -0
- package/src/frontend/reloc-symbol.ts +22 -7
- package/src/frontend/ssa.ts +1460 -44
- package/src/frontend/stackargs.ts +106 -57
- package/src/frontend/thumb.ts +2044 -318
- package/src/ir/alias.ts +42 -0
- package/src/ir/bits.ts +681 -1
- package/src/ir/core.ts +100 -5
- package/src/ir/discipline.ts +86 -21
- package/src/ir/entry.ts +159 -0
- package/src/{structure → ir}/loops.ts +6 -2
- package/src/ir/opcodes.ts +109 -9
- package/src/ir/types.ts +86 -2
- package/src/ir/verify.ts +80 -11
- package/src/l3/ast.ts +290 -18
- package/src/l3/basecse.ts +33 -5
- package/src/l3/constinit.ts +382 -0
- package/src/l3/dce.ts +2 -2
- package/src/l3/initfirst.ts +42 -11
- package/src/l3/intcell.ts +2 -1
- package/src/l3/narrowdecl.ts +77 -36
- package/src/l3/pollguard.ts +8 -2
- package/src/l3/ptrcell.ts +31 -34
- package/src/l3/ptrfield.ts +123 -8
- package/src/l3/regspell.ts +9 -4
- package/src/l3/reindex.ts +283 -96
- package/src/l3/storage.ts +98 -1
- package/src/l3/symbol-refs.ts +84 -6
- package/src/l3/tailmerge.ts +4 -2
- package/src/l3/typing.ts +108 -15
- package/src/l3/unreduce.ts +6 -5
- package/src/l3/volatileptr.ts +17 -54
- package/src/mangle.ts +30 -0
- package/src/pattern/engine.ts +361 -73
- package/src/pipeline.ts +541 -24
- package/src/proto-context.ts +905 -45
- package/src/proto.ts +555 -40
- package/src/raise/aggmembers.ts +290 -0
- package/src/raise/arrays.ts +5 -1
- package/src/raise/declared-volatile.ts +207 -31
- package/src/raise/floathelpers.ts +254 -33
- package/src/raise/gvn.ts +3 -3
- package/src/raise/latch.ts +162 -6
- package/src/raise/memberarrays.ts +10 -4
- package/src/raise/offsetnames.ts +10 -26
- package/src/raise/pairparams.ts +33 -0
- package/src/raise/paramwidth.ts +196 -6
- package/src/raise/pre-recovery.ts +102 -19
- package/src/raise/pre-structure.ts +54 -0
- package/src/raise/recover.ts +12 -5
- package/src/raise/reroll.ts +694 -0
- package/src/raise/shortcircuit.ts +26 -12
- package/src/raise/softdiv.ts +1 -1
- package/src/raise/storesign.ts +153 -0
- package/src/raise/struct-arrays.ts +30 -11
- package/src/raise/structs.ts +396 -33
- package/src/raise/truncload.ts +4 -3
- package/src/raise/unthread.ts +400 -0
- package/src/rank-declare.ts +118 -12
- package/src/rank-variations.ts +89 -31
- package/src/rank.ts +307 -54
- package/src/runtime-helpers.ts +140 -22
- package/src/structure/analysis.ts +165 -2
- package/src/structure/arith-ops.ts +23 -0
- package/src/structure/globalaccess.ts +111 -9
- package/src/structure/hazards.ts +16 -64
- package/src/structure/local-statics.ts +145 -9
- package/src/structure/namecoalesce.ts +1 -1
- package/src/structure/pointer-spelling.ts +1106 -0
- package/src/structure/redundant-test.ts +1 -1
- package/src/structure/structure.ts +2180 -1054
- package/src/structure/switch-recover.ts +244 -100
- package/src/symbols.ts +6 -0
- package/src/target.ts +765 -52
- package/src/trace.ts +26 -8
- package/src/variation-definitions.ts +126 -14
- package/src/variation-gates.ts +2 -0
- 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
|
+
}
|