@asmlift/core 0.8.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -31
- package/package.json +1 -1
- package/src/aggregate.ts +397 -0
- package/src/backend/c.ts +6 -2
- package/src/backend/cfamily.ts +370 -56
- package/src/backend/cpp.ts +116 -10
- package/src/backend/pascal.ts +69 -11
- package/src/cdecl/constant.ts +59 -0
- package/src/cdecl/lex.ts +209 -0
- package/src/cdecl/parse.ts +975 -0
- package/src/cdecl/spell.ts +69 -0
- package/src/codegen-flags.ts +38 -6
- package/src/compiler-diagnostics.ts +194 -0
- package/src/contracts.ts +777 -84
- package/src/declare.ts +132 -13
- package/src/frontend/asmdata.ts +26 -6
- package/src/frontend/call-plan.ts +1590 -0
- package/src/frontend/dead-reads.ts +628 -0
- package/src/frontend/device-pins.ts +296 -0
- package/src/frontend/emit.ts +4 -3
- package/src/frontend/errors.ts +5 -0
- package/src/frontend/flags-edge.ts +101 -0
- package/src/frontend/fpu.ts +374 -0
- package/src/frontend/frame-objects.ts +3171 -0
- package/src/frontend/frontend.ts +64 -3
- package/src/frontend/high-half.ts +55 -5
- package/src/frontend/jump-table.ts +530 -0
- package/src/frontend/local-object.ts +616 -0
- package/src/frontend/mips.ts +2392 -208
- package/src/frontend/opaque.ts +97 -18
- package/src/frontend/own-struct-return.ts +500 -0
- package/src/frontend/ppc.ts +3137 -267
- package/src/frontend/register-struct-call.ts +484 -0
- package/src/frontend/reloc-symbol.ts +109 -13
- package/src/frontend/splat.ts +108 -5
- package/src/frontend/ssa.ts +2098 -89
- package/src/frontend/stackargs.ts +172 -42
- package/src/frontend/thumb.ts +4563 -2091
- package/src/ir/alias.ts +71 -0
- package/src/ir/bits.ts +681 -1
- package/src/ir/core.ts +199 -6
- package/src/ir/discipline.ts +313 -0
- package/src/ir/entry.ts +159 -0
- package/src/ir/float-bits.ts +47 -0
- package/src/{structure → ir}/loops.ts +6 -2
- package/src/ir/opcodes.ts +193 -121
- package/src/ir/parse.ts +1 -0
- package/src/ir/types.ts +202 -2
- package/src/ir/verify.ts +219 -3
- package/src/l3/address.ts +5 -0
- package/src/l3/argcopy.ts +38 -6
- package/src/l3/ast.ts +467 -23
- package/src/l3/basecse.ts +33 -5
- package/src/l3/coalesce.ts +21 -13
- package/src/l3/constinit.ts +382 -0
- package/src/l3/dce.ts +34 -12
- package/src/l3/gates.ts +9 -3
- package/src/l3/hoist.ts +8 -7
- package/src/l3/initfirst.ts +68 -19
- package/src/l3/intcell.ts +98 -0
- package/src/l3/mentions.ts +10 -4
- package/src/l3/narrowdecl.ts +259 -0
- package/src/l3/nearbase.ts +10 -5
- package/src/l3/parkfirst.ts +8 -5
- package/src/l3/pollguard.ts +11 -4
- package/src/l3/ptrcell.ts +354 -0
- package/src/l3/ptrfield.ts +123 -8
- package/src/l3/regspell.ts +26 -7
- package/src/l3/reindex.ts +293 -103
- package/src/l3/storage.ts +98 -1
- package/src/l3/symbol-refs.ts +182 -23
- package/src/l3/tailmerge.ts +9 -20
- package/src/l3/typing.ts +237 -32
- package/src/l3/unreduce.ts +10 -9
- package/src/l3/volatileptr.ts +17 -54
- package/src/l3/volstore.ts +96 -7
- package/src/mangle.ts +41 -1
- package/src/pattern/engine.ts +373 -82
- package/src/pipeline.ts +589 -27
- package/src/proto-context.ts +1648 -0
- package/src/proto.ts +1203 -49
- package/src/raise/aggmembers.ts +290 -0
- package/src/raise/arrays.ts +8 -3
- package/src/raise/declared-volatile.ts +307 -0
- package/src/raise/divpow2.ts +2 -2
- package/src/raise/errors.ts +17 -0
- package/src/raise/floathelpers.ts +464 -0
- package/src/raise/globalshape.ts +11 -5
- package/src/raise/gvn.ts +29 -8
- package/src/raise/latch.ts +162 -6
- package/src/raise/memberarrays.ts +10 -4
- package/src/raise/narrow.ts +29 -2
- package/src/raise/narrowlocal.ts +11 -6
- package/src/raise/offsetnames.ts +494 -0
- package/src/raise/pairparams.ts +114 -0
- package/src/raise/paramwidth.ts +203 -6
- package/src/raise/pre-recovery.ts +174 -17
- package/src/raise/pre-structure.ts +54 -0
- package/src/raise/recover.ts +26 -15
- package/src/raise/reroll.ts +694 -0
- package/src/raise/retsink.ts +1 -1
- package/src/raise/shortcircuit.ts +66 -30
- package/src/raise/softdiv.ts +21 -23
- package/src/raise/storesign.ts +153 -0
- package/src/raise/struct-arrays.ts +34 -14
- package/src/raise/structs.ts +674 -98
- package/src/raise/truncload.ts +367 -0
- package/src/raise/unthread.ts +400 -0
- package/src/raise/widehelpers.ts +106 -0
- package/src/rank-declare.ts +143 -12
- package/src/rank-variations.ts +117 -31
- package/src/rank.ts +563 -122
- package/src/runtime-helpers.ts +405 -0
- package/src/stillborn.ts +289 -0
- package/src/structure/analysis.ts +1449 -449
- package/src/structure/arith-ops.ts +23 -0
- package/src/structure/globalaccess.ts +111 -9
- package/src/structure/hazards.ts +641 -114
- package/src/structure/local-statics.ts +517 -0
- package/src/structure/namecoalesce.ts +2 -2
- package/src/structure/pointer-spelling.ts +1106 -0
- package/src/structure/redundant-test.ts +99 -0
- package/src/structure/structure.ts +3478 -975
- package/src/structure/switch-recover.ts +403 -178
- package/src/symbols.ts +44 -3
- package/src/target.ts +1387 -71
- package/src/trace.ts +66 -28
- package/src/variation-definitions.ts +243 -13
- package/src/variation-gates.ts +2 -0
- package/src/variation-tokens.ts +25 -7
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
// asmlift — the C DEFINITION of each function-scope static a function names (ir/core.ts
|
|
2
|
+
// `LocalObjects`), typed from the target's own definition and the accesses the function makes.
|
|
3
|
+
//
|
|
4
|
+
// The frontend hands over what the target shows: section, size and bytes, and where the target is
|
|
5
|
+
// an assembler listing (agbcc's `.s`) the definition's directives too. Those say the element: the
|
|
6
|
+
// compiler writes each scalar of the initializer with the directive of its width, so a table of
|
|
7
|
+
// `.short`s is one of 16-bit elements whatever the function reads out of it, and a narrow negative
|
|
8
|
+
// value is only ever written for a signed type. An object file (mwcc) shows no directives, and
|
|
9
|
+
// there the element is the one width every access agrees on. The loads of the element's width then
|
|
10
|
+
// settle its signedness — they are what the function computes, and a byte holds the same bits
|
|
11
|
+
// declared either way, so they outrank a negative the source wrote through another type (a struct's
|
|
12
|
+
// signed field, a cast) — and the definition settles it where no load does. An object nothing says
|
|
13
|
+
// anything about is bytes.
|
|
14
|
+
//
|
|
15
|
+
// The target also shows the ALIGNMENT — the listing's `.align`, mwcc's `.comment` record — which
|
|
16
|
+
// decides where the object lands after the statics declared before it, and whether a DMA or GX
|
|
17
|
+
// buffer is where the hardware needs it. The definition this pass picks gets its compiler's own
|
|
18
|
+
// alignment, which the target declares ({@link StaticLayout}): a scalar to its width, an array to
|
|
19
|
+
// its element or the compiler's aggregate floor, a string literal to the compiler's string
|
|
20
|
+
// alignment. A target aligned wider — a struct, an `s64`, an `ALIGNED(32)` — keeps that alignment
|
|
21
|
+
// in an attribute, because a table one element off is misread by the machine and scored as a
|
|
22
|
+
// MATCH. A byte array the listing wrote as strings and placed at the string alignment is spelled
|
|
23
|
+
// with a string literal, which is what gives it that alignment.
|
|
24
|
+
//
|
|
25
|
+
// The definition is a SymbolInfo as well as a declaration, because the structurer spells an access
|
|
26
|
+
// through a symbol's declared shape (`tide[i]` for an array, the bare `q` for a scalar): the shape
|
|
27
|
+
// it spells against and the object the backend defines are one reading of the same facts. Both are
|
|
28
|
+
// keyed by the LINKER name the IR carries (`tide.3`), and the finished tree is renamed to the
|
|
29
|
+
// source's (`tide`) in one step, `nameLocalStatics`, which is where a clash between that name and
|
|
30
|
+
// anything else the function names can first be seen whole.
|
|
31
|
+
//
|
|
32
|
+
// An element the target RELOCATES holds an address — a table of function pointers, of data
|
|
33
|
+
// pointers, of `&arr[k]` — and is spelled as the address constant it is, `(u32)&sym` or
|
|
34
|
+
// `(u32)((u8 *)&sym + k)`, in the integer element the rest of this typing picks: such an element
|
|
35
|
+
// compiles to the same word and the same relocation as the pointer-typed source (agbcc and mwcc,
|
|
36
|
+
// compiled), so the definition needs no pointer type to put the bytes where the target has them.
|
|
37
|
+
//
|
|
38
|
+
// REFUSES, naming the static, when the target's compiler declares no layout rules, when nothing
|
|
39
|
+
// in the definition settles the width and the accesses disagree on it, when the width is no
|
|
40
|
+
// integer type's or does not divide the size, when the loads disagree on the signedness, when the
|
|
41
|
+
// definition is aligned narrower than its declaration here would be, when a bss static sits where
|
|
42
|
+
// only a scalar initialized to zero is sure to land and it is no scalar (or the input does not
|
|
43
|
+
// show where it sits), when an element holding an address — a static's or the compiler's
|
|
44
|
+
// anonymous image's — is no pointer's width;
|
|
45
|
+
// and, at the rename, when two statics share a source name (one block cannot declare both) or
|
|
46
|
+
// when the function names anything else by it — a global, a callee, a parameter, a local, itself —
|
|
47
|
+
// which the block-scope static would hide — and when a global an initializer holds the address of
|
|
48
|
+
// is hidden there by a static, parameter or local of its name.
|
|
49
|
+
import {
|
|
50
|
+
type Fn,
|
|
51
|
+
LOCAL_OBJECT_ADDRESS_BYTES,
|
|
52
|
+
type LocalObject,
|
|
53
|
+
type LocalObjects,
|
|
54
|
+
type Op,
|
|
55
|
+
type Value,
|
|
56
|
+
defOpMap,
|
|
57
|
+
} from '../ir/core';
|
|
58
|
+
import { type IrType, T } from '../ir/types';
|
|
59
|
+
import {
|
|
60
|
+
type Expr,
|
|
61
|
+
type InitAddress,
|
|
62
|
+
type SFn,
|
|
63
|
+
type SImage,
|
|
64
|
+
type SStatic,
|
|
65
|
+
type Stmt,
|
|
66
|
+
mapExprChildren,
|
|
67
|
+
mapStmtExprs,
|
|
68
|
+
mapStmtLists,
|
|
69
|
+
walkExprs,
|
|
70
|
+
} from '../l3/ast';
|
|
71
|
+
import { takenNames } from '../l3/hoist';
|
|
72
|
+
import { type SymbolInfo, accessSignedness } from '../symbols';
|
|
73
|
+
|
|
74
|
+
/** How a compiler lays out a function-scope static — facts about the COMPILER, declared per target
|
|
75
|
+
* (target.ts `compilerBehaviors.staticLayout`), from which the definition that puts a static where
|
|
76
|
+
* the target has it follows. */
|
|
77
|
+
export interface StaticLayout {
|
|
78
|
+
/** the least alignment it gives an array or a struct, whatever the element */
|
|
79
|
+
aggregateAlign: number;
|
|
80
|
+
/** the alignment it gives a declaration initialized by a string literal */
|
|
81
|
+
stringAlign: number;
|
|
82
|
+
/** where a scalar initialized to zero goes: kept in `.data`, or moved to bss — where its
|
|
83
|
+
* initializer is gone, and one element of zero in `.data` must then have been an aggregate */
|
|
84
|
+
zeroScalar: 'data' | 'bss';
|
|
85
|
+
/** The bss sections of such a compiler, each laying out the scalars it moved there first, in
|
|
86
|
+
* declaration order, and then the statics with no initializer: in reverse declaration order
|
|
87
|
+
* (mwcc's `.sbss`: `a = 0; b; c = 0;` at +0, +8, +4), or in the order its code first uses them
|
|
88
|
+
* (mwcc's `.bss`: `a; b = 0;` used a first at +4, +0). Either way the offsets say which statics
|
|
89
|
+
* sit where only one with the `= 0` is sure to land. */
|
|
90
|
+
uninitOrder?: Readonly<Record<string, 'reversed' | 'first-use'>>;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** The bss statics of `objs` to write `= 0` ({@link StaticLayout.uninitOrder}), or
|
|
94
|
+
* the symbol of one whose placement the input does not show. In a section of either order a
|
|
95
|
+
* function's zero-initialized statics come first, scalars with their counters rising.
|
|
96
|
+
*
|
|
97
|
+
* Where the others follow reversed, a static with a later-declared one at a higher offset had the
|
|
98
|
+
* initializer. The last of the rising run cannot be told from the first of the falling one, and
|
|
99
|
+
* both declarations put it in the same place: it is left without.
|
|
100
|
+
*
|
|
101
|
+
* Where the others follow by first use, the leading run of `scalarShaped` statics with rising
|
|
102
|
+
* counters had it, save that a run that is the whole section leaves its last without, which lands
|
|
103
|
+
* after the rest either way. Any of that run may have had none and been used first, and the `= 0`
|
|
104
|
+
* puts it where it was, which the candidate's code would decide otherwise. */
|
|
105
|
+
function zeroInitialized(
|
|
106
|
+
objs: readonly LocalObject[],
|
|
107
|
+
uninitOrder: NonNullable<StaticLayout['uninitOrder']>,
|
|
108
|
+
scalarShaped: (o: LocalObject) => boolean,
|
|
109
|
+
): Set<string> | { symbol: string } {
|
|
110
|
+
const out = new Set<string>();
|
|
111
|
+
const bss = objs.filter((o) => o.section === 'bss');
|
|
112
|
+
for (const o of bss) {
|
|
113
|
+
if (o.placement === undefined) {
|
|
114
|
+
return { symbol: o.symbol };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
for (const [section, order] of Object.entries(uninitOrder)) {
|
|
118
|
+
const run = bss
|
|
119
|
+
.filter((o) => o.placement!.section === section)
|
|
120
|
+
.sort((a, b) => a.placement!.offset - b.placement!.offset);
|
|
121
|
+
if (order === 'reversed') {
|
|
122
|
+
for (const o of run) {
|
|
123
|
+
if (run.some((p) => p.placement!.offset > o.placement!.offset && p.order > o.order)) {
|
|
124
|
+
out.add(o.symbol);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
let lead = 0;
|
|
130
|
+
while (lead < run.length && scalarShaped(run[lead]) && (lead === 0 || run[lead].order > run[lead - 1].order)) {
|
|
131
|
+
lead++;
|
|
132
|
+
}
|
|
133
|
+
for (const o of run.slice(0, lead === run.length ? lead - 1 : lead)) {
|
|
134
|
+
out.add(o.symbol);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return out;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Each static's shape for the structurer, and its definition for the backend. */
|
|
141
|
+
export interface LocalStaticShapes {
|
|
142
|
+
infos: Map<string, SymbolInfo>;
|
|
143
|
+
statics: SStatic[];
|
|
144
|
+
/** the compiler's anonymous constant images, which no declaration spells (l3/constinit.ts) */
|
|
145
|
+
images: SImage[];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** One access through a static's address: the width it reads or writes, and a load's extension. */
|
|
149
|
+
interface Access {
|
|
150
|
+
width: number;
|
|
151
|
+
signed?: boolean;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** The width an `aload`/`astore` reads or writes. Its `elemSize` is the element's, which for an
|
|
155
|
+
* element of a struct array (raise/struct-arrays.ts, `fieldOff`) is the STRIDE: the access itself
|
|
156
|
+
* is the field's, whose type the base's struct carries. Null for a field that type does not hold. */
|
|
157
|
+
function indexedWidth(op: Op): number | null {
|
|
158
|
+
const fieldOff = op.attrs.fieldOff as number | undefined;
|
|
159
|
+
if (fieldOff === undefined) {
|
|
160
|
+
return op.attrs.elemSize as number;
|
|
161
|
+
}
|
|
162
|
+
const bt = op.operands[0].type;
|
|
163
|
+
const field = bt.kind === 'ptr' && bt.to.kind === 'struct' ? bt.to.fields.find((f) => f.off === fieldOff) : undefined;
|
|
164
|
+
return field !== undefined && field.type.kind === 'int' ? field.type.width / 8 : null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Every access through each static's address. */
|
|
168
|
+
function accessesOf(fn: Fn, names: ReadonlySet<string>): Map<string, Access[]> {
|
|
169
|
+
const defs = defOpMap(fn);
|
|
170
|
+
/** the static an address is computed from — its `gaddr`, through adds of anything else */
|
|
171
|
+
const baseOf = (v: Value, seen = new Set<Value>()): string | null => {
|
|
172
|
+
const d = defs.get(v);
|
|
173
|
+
if (d === undefined || seen.has(v)) {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
seen.add(v);
|
|
177
|
+
if (d.opcode === 'gaddr') {
|
|
178
|
+
return names.has(d.attrs.sym as string) ? (d.attrs.sym as string) : null;
|
|
179
|
+
}
|
|
180
|
+
if (d.opcode === 'add' || d.opcode === 'sub') {
|
|
181
|
+
const found = d.operands.slice(0, d.opcode === 'sub' ? 1 : undefined).map((o) => baseOf(o, seen));
|
|
182
|
+
const hits = found.filter((f): f is string => f !== null);
|
|
183
|
+
return hits.length === 1 ? hits[0] : null;
|
|
184
|
+
}
|
|
185
|
+
return null;
|
|
186
|
+
};
|
|
187
|
+
const out = new Map<string, Access[]>();
|
|
188
|
+
for (const b of fn.blocks) {
|
|
189
|
+
for (const op of b.ops) {
|
|
190
|
+
const plain = op.opcode === 'load' || op.opcode === 'store';
|
|
191
|
+
const indexed = op.opcode === 'aload' || op.opcode === 'astore';
|
|
192
|
+
if (!plain && !indexed) {
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
const sym = baseOf(op.operands[0]);
|
|
196
|
+
if (sym === null) {
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
const width = plain ? (op.attrs.width as number) : indexedWidth(op);
|
|
200
|
+
if (width === null) {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
const isLoad = op.opcode === 'load' || op.opcode === 'aload';
|
|
204
|
+
const list = out.get(sym) ?? out.set(sym, []).get(sym)!;
|
|
205
|
+
list.push({
|
|
206
|
+
width,
|
|
207
|
+
...(isLoad ? { signed: accessSignedness(width, op.attrs.signed as boolean | undefined) } : {}),
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return out;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** The elements of `obj`'s initial bytes read `width` bytes at a time in its byte order. */
|
|
215
|
+
function elements(obj: LocalObject, width: number, signed: boolean): number[] {
|
|
216
|
+
const b = obj.bytes!;
|
|
217
|
+
const out: number[] = [];
|
|
218
|
+
for (let at = 0; at < obj.size; at += width) {
|
|
219
|
+
let v = 0;
|
|
220
|
+
for (let i = 0; i < width; i++) {
|
|
221
|
+
v = v * 256 + b[obj.bigEndian ? at + i : at + width - 1 - i];
|
|
222
|
+
}
|
|
223
|
+
out.push(signed && v >= 2 ** (8 * width - 1) ? v - 2 ** (8 * width) : v);
|
|
224
|
+
}
|
|
225
|
+
return out;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** The width of `obj`'s element: the one its definition's directives write, else the one every
|
|
229
|
+
* access agrees on; with no access either, a pointer's where the target relocates a word of it (an
|
|
230
|
+
* address table only handed to a callee, on a compiler that writes no directives: mwcc), else the
|
|
231
|
+
* whole object where it is aligned below the compiler's aggregate floor, as only a scalar is, and
|
|
232
|
+
* a byte where it is not. A relocation the pointer width does not seat is still refused, by the
|
|
233
|
+
* caller's check of every relocation's offset. */
|
|
234
|
+
function elementWidth(
|
|
235
|
+
obj: LocalObject,
|
|
236
|
+
accesses: readonly Access[],
|
|
237
|
+
layout: StaticLayout,
|
|
238
|
+
): number | { refused: string } {
|
|
239
|
+
if (obj.directives?.unit !== undefined) {
|
|
240
|
+
return obj.directives.unit;
|
|
241
|
+
}
|
|
242
|
+
const widths = [...new Set(accesses.map((x) => x.width))];
|
|
243
|
+
if (widths.length > 1) {
|
|
244
|
+
return { refused: `whose accesses disagree on its element width (${widths.sort().join(' and ')} bytes)` };
|
|
245
|
+
}
|
|
246
|
+
if (widths.length === 1) {
|
|
247
|
+
return widths[0];
|
|
248
|
+
}
|
|
249
|
+
if ((obj.relocs?.length ?? 0) > 0 && obj.size % LOCAL_OBJECT_ADDRESS_BYTES === 0) {
|
|
250
|
+
return LOCAL_OBJECT_ADDRESS_BYTES;
|
|
251
|
+
}
|
|
252
|
+
const shown = obj.directives?.align ?? obj.placement?.align;
|
|
253
|
+
const scalarOnly = shown !== undefined && shown < layout.aggregateAlign;
|
|
254
|
+
return scalarOnly && [1, 2, 4].includes(obj.size) ? obj.size : 1;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** The shape and definition of every static `fn` defines, or the refusal: the static's linker name
|
|
258
|
+
* and the tail of the sentence. */
|
|
259
|
+
export function localStaticShapes(
|
|
260
|
+
fn: Fn,
|
|
261
|
+
layout: StaticLayout | undefined,
|
|
262
|
+
): LocalStaticShapes | { symbol: string; refused: string } {
|
|
263
|
+
const objs = fn.localObjects;
|
|
264
|
+
const infos = new Map<string, SymbolInfo>();
|
|
265
|
+
const statics: SStatic[] = [];
|
|
266
|
+
const images: SImage[] = [];
|
|
267
|
+
if (objs === undefined) {
|
|
268
|
+
return { infos, statics, images };
|
|
269
|
+
}
|
|
270
|
+
if (layout === undefined) {
|
|
271
|
+
const [first] = objs.keys();
|
|
272
|
+
return { symbol: first, refused: "whose layout rules this target's compiler does not declare" };
|
|
273
|
+
}
|
|
274
|
+
const access = accessesOf(fn, new Set(objs.keys()));
|
|
275
|
+
const widthOf = (o: LocalObject) => elementWidth(o, access.get(o.symbol) ?? [], layout);
|
|
276
|
+
/** Declared here as a scalar, or sized and aligned as one — the compiler aligns a scalar to at
|
|
277
|
+
* least its width. One declared as an array where only a zero scalar sits is refused below: an
|
|
278
|
+
* `s32 = 0` read a byte at a time, or only through its address, lands there as well as four
|
|
279
|
+
* bytes used first. */
|
|
280
|
+
const scalarShaped = (o: LocalObject): boolean =>
|
|
281
|
+
widthOf(o) === o.size || ([1, 2, 4, 8].includes(o.size) && (o.placement?.align ?? 0) >= o.size);
|
|
282
|
+
const zeroed =
|
|
283
|
+
layout.uninitOrder !== undefined
|
|
284
|
+
? zeroInitialized([...objs.values()], layout.uninitOrder, scalarShaped)
|
|
285
|
+
: new Set<string>();
|
|
286
|
+
if (!(zeroed instanceof Set)) {
|
|
287
|
+
return { symbol: zeroed.symbol, refused: 'in bss at an offset this input does not show' };
|
|
288
|
+
}
|
|
289
|
+
// Declared in the target's declaration order, which the compiler lays the objects out by (mwcc
|
|
290
|
+
// reverses it in `.sbss` for statics with no initializer), so that the candidate's compiler
|
|
291
|
+
// places each where the target has it — except mwcc's `.bss` statics with no initializer, which
|
|
292
|
+
// it places in the order it first uses them: the candidate's code decides that, no declaration.
|
|
293
|
+
for (const obj of [...objs.values()].sort((a, b) => a.order - b.order)) {
|
|
294
|
+
const say = (why: string) => ({ symbol: obj.symbol, refused: why });
|
|
295
|
+
if (obj.anonymous) {
|
|
296
|
+
// The compiler's own image of a local aggregate's initializer: typed by its directives alone,
|
|
297
|
+
// since nothing but the copy reads it, and never declared where it sits — l3/constinit.ts
|
|
298
|
+
// folds it into the local the copy fills, which is the declaration that placed it.
|
|
299
|
+
const unit = obj.directives?.unit;
|
|
300
|
+
if (unit === undefined || obj.bytes === undefined) {
|
|
301
|
+
return say('whose image writes no one element width — a struct, or padding alone');
|
|
302
|
+
}
|
|
303
|
+
if (![1, 2, 4].includes(unit) || obj.size % unit !== 0) {
|
|
304
|
+
return say(`whose ${unit}-byte elements are no integer array of its ${obj.size} bytes`);
|
|
305
|
+
}
|
|
306
|
+
const signed = obj.directives!.negative;
|
|
307
|
+
const count = obj.size / unit;
|
|
308
|
+
infos.set(obj.symbol, {
|
|
309
|
+
name: obj.symbol,
|
|
310
|
+
kind: 'data',
|
|
311
|
+
size: obj.size,
|
|
312
|
+
shape: 'array',
|
|
313
|
+
elemSize: unit,
|
|
314
|
+
elemSigned: signed,
|
|
315
|
+
dims: [count],
|
|
316
|
+
const: true,
|
|
317
|
+
});
|
|
318
|
+
// Only the elements the source spelled: an element it left out is a hole, and the holes after
|
|
319
|
+
// the last value are dropped. Spelled out as zeros, the initializer is a different one to the
|
|
320
|
+
// compiler, which clears and stores where most of one is zero rather than copy an image
|
|
321
|
+
// (expr.c `mostly_zeros_p`) — so the printer spells the element after a hole by designator,
|
|
322
|
+
// where the compiler parses one (`SFn.designators`).
|
|
323
|
+
const init: (number | InitAddress | null)[] = elements(obj, unit, signed);
|
|
324
|
+
for (const [from, n] of obj.directives!.gaps ?? []) {
|
|
325
|
+
if (from % unit !== 0 || n % unit !== 0) {
|
|
326
|
+
return say(`whose padding at byte ${from} does not fall on its ${unit}-byte elements`);
|
|
327
|
+
}
|
|
328
|
+
init.fill(null, from / unit, (from + n) / unit);
|
|
329
|
+
}
|
|
330
|
+
// An element the image relocates holds an address, as a static's does — an array of pointers
|
|
331
|
+
// (agbcc copies one with memcpy from 16 words, compiled).
|
|
332
|
+
for (const r of obj.relocs ?? []) {
|
|
333
|
+
if (unit !== LOCAL_OBJECT_ADDRESS_BYTES || r.offset % unit !== 0) {
|
|
334
|
+
return say(
|
|
335
|
+
`whose image holds the address '${r.symbol}', which its ${unit}-byte elements cannot — ` +
|
|
336
|
+
`an address is ${LOCAL_OBJECT_ADDRESS_BYTES}`,
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
init[r.offset / unit] = { symbol: r.symbol, addend: r.addend };
|
|
340
|
+
}
|
|
341
|
+
// A string literal's image is placed at the compiler's string alignment, which a brace list of
|
|
342
|
+
// strings is not (agbcc: `.align 2, 0` ahead of a STRING_CST's label, none ahead of
|
|
343
|
+
// `{"abcde"}`'s) — the same bytes, but spelled as a literal the image moves to a stricter
|
|
344
|
+
// alignment and shifts the unit's `.rodata`. Nor has it a padding run: one shorter than its
|
|
345
|
+
// array is copied at the literal's length and the tail cleared (`char a[16] = "abc…hijklm"` is
|
|
346
|
+
// memcpy 15 + memset 1), so a `.space` after the `.ascii` is a brace list's image copied whole.
|
|
347
|
+
// So the test is asked before the trailing holes are dropped, not after.
|
|
348
|
+
const string =
|
|
349
|
+
obj.directives!.string === true &&
|
|
350
|
+
unit === 1 &&
|
|
351
|
+
obj.directives!.align >= layout.stringAlign &&
|
|
352
|
+
!init.includes(null);
|
|
353
|
+
while (init.length > 0 && init[init.length - 1] === null) {
|
|
354
|
+
init.pop();
|
|
355
|
+
}
|
|
356
|
+
images.push({
|
|
357
|
+
name: obj.symbol,
|
|
358
|
+
type: T.array(T.int(unit * 8, signed), count),
|
|
359
|
+
init,
|
|
360
|
+
...(string ? { string: true as const } : {}),
|
|
361
|
+
});
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
const accesses = access.get(obj.symbol) ?? [];
|
|
365
|
+
const dir = obj.directives;
|
|
366
|
+
// the alignment the target shows
|
|
367
|
+
const shown = dir?.align ?? obj.placement?.align;
|
|
368
|
+
const width = widthOf(obj);
|
|
369
|
+
if (typeof width !== 'number') {
|
|
370
|
+
return say(width.refused);
|
|
371
|
+
}
|
|
372
|
+
if (![1, 2, 4].includes(width)) {
|
|
373
|
+
return say(`whose ${width}-byte elements are no integer type`);
|
|
374
|
+
}
|
|
375
|
+
if (obj.size % width !== 0) {
|
|
376
|
+
return say(`whose ${width}-byte elements do not divide its ${obj.size} bytes`);
|
|
377
|
+
}
|
|
378
|
+
// an access of another width reads through a cast, and says nothing about these elements
|
|
379
|
+
const signs = new Set(accesses.filter((x) => x.width === width && x.signed !== undefined).map((x) => x.signed));
|
|
380
|
+
if (signs.size > 1) {
|
|
381
|
+
return say('whose loads disagree on whether its elements are signed');
|
|
382
|
+
}
|
|
383
|
+
const signed = signs.size > 0 ? signs.has(true) : dir?.negative === true;
|
|
384
|
+
const count = obj.size / width;
|
|
385
|
+
const elem = T.int(width * 8, signed);
|
|
386
|
+
const init: (number | InitAddress)[] | undefined = zeroed.has(obj.symbol)
|
|
387
|
+
? [0]
|
|
388
|
+
: obj.bytes === undefined
|
|
389
|
+
? undefined
|
|
390
|
+
: elements(obj, width, signed);
|
|
391
|
+
// An element the target relocates holds an address, which only an element of a pointer's width
|
|
392
|
+
// can: the relocation fills exactly that many bytes.
|
|
393
|
+
for (const r of obj.relocs ?? []) {
|
|
394
|
+
if (width !== LOCAL_OBJECT_ADDRESS_BYTES || r.offset % width !== 0) {
|
|
395
|
+
return say(
|
|
396
|
+
`whose initializer holds the address '${r.symbol}', which its ${width}-byte elements cannot — ` +
|
|
397
|
+
`an address is ${LOCAL_OBJECT_ADDRESS_BYTES}`,
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
init![r.offset / width] = { symbol: r.symbol, addend: r.addend };
|
|
401
|
+
}
|
|
402
|
+
if (zeroed.has(obj.symbol) && count !== 1) {
|
|
403
|
+
return say(`laid out where only a scalar initialized to zero is, but of ${count} elements`);
|
|
404
|
+
}
|
|
405
|
+
// One element is a scalar — unless it is initialized data holding zero on a compiler that
|
|
406
|
+
// moves a zero scalar to bss, where it can only have been an aggregate, which the compiler
|
|
407
|
+
// keeps in .data.
|
|
408
|
+
const scalar =
|
|
409
|
+
count === 1 && !(layout.zeroScalar === 'bss' && obj.section === 'data' && init!.every((v) => v === 0));
|
|
410
|
+
const type: IrType = scalar ? elem : T.array(elem, count);
|
|
411
|
+
const string = dir?.string === true && width === 1 && !scalar && shown !== undefined && shown >= layout.stringAlign;
|
|
412
|
+
// The alignment the target shows against the one this definition gets from its compiler.
|
|
413
|
+
const own = scalar ? width : Math.max(width, layout.aggregateAlign, string ? layout.stringAlign : 1);
|
|
414
|
+
if (shown !== undefined && shown < own) {
|
|
415
|
+
return say(`whose definition is aligned to ${shown} bytes, less than the ${own} its declaration here would get`);
|
|
416
|
+
}
|
|
417
|
+
const align = shown !== undefined && shown > own ? shown : undefined;
|
|
418
|
+
const isConst = obj.section === 'rodata';
|
|
419
|
+
infos.set(obj.symbol, {
|
|
420
|
+
name: obj.symbol,
|
|
421
|
+
kind: 'data',
|
|
422
|
+
size: obj.size,
|
|
423
|
+
...(scalar
|
|
424
|
+
? { shape: 'scalar' as const, signed }
|
|
425
|
+
: { shape: 'array' as const, elemSize: width, elemSigned: signed, dims: [count] }),
|
|
426
|
+
...(isConst ? { const: true } : {}),
|
|
427
|
+
});
|
|
428
|
+
statics.push({
|
|
429
|
+
name: obj.name,
|
|
430
|
+
type,
|
|
431
|
+
...(isConst ? { const: true as const } : {}),
|
|
432
|
+
...(align !== undefined ? { align } : {}),
|
|
433
|
+
...(init ? { init } : {}),
|
|
434
|
+
...(string ? { string: true as const } : {}),
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
return { infos, statics, images };
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/** `sfn` with each static's linker name replaced by its source name — every reference, assignment
|
|
441
|
+
* targets and the typed-global list included — or the refusal. The tree is final here, so what the
|
|
442
|
+
* source name would collide with is all in it: every name its body mentions, callees included,
|
|
443
|
+
* plus its parameters, locals and own name. */
|
|
444
|
+
export function nameLocalStatics(sfn: SFn, objs: LocalObjects): SFn | { symbol: string; refused: string } {
|
|
445
|
+
const taken = takenNames(sfn);
|
|
446
|
+
for (const g of sfn.globals ?? []) {
|
|
447
|
+
taken.add(g.name);
|
|
448
|
+
}
|
|
449
|
+
// The globals an initializer holds the address of are named here as much as the body's: a static
|
|
450
|
+
// of the same name is in scope from its own declarator on, its initializer included.
|
|
451
|
+
const addressed = new Map<string, string>();
|
|
452
|
+
for (const obj of objs.values()) {
|
|
453
|
+
for (const r of obj.relocs ?? []) {
|
|
454
|
+
taken.add(r.symbol);
|
|
455
|
+
addressed.set(r.symbol, obj.symbol);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
const bound = new Set([...sfn.params, ...sfn.locals].map((x) => x.name));
|
|
459
|
+
// …and the function's own parameters hide one too: a static's initializer is read in their scope.
|
|
460
|
+
// A local is declared after every static and hides none, but its name is the emitter's to move.
|
|
461
|
+
for (const [name, holder] of addressed) {
|
|
462
|
+
if (bound.has(name)) {
|
|
463
|
+
return {
|
|
464
|
+
symbol: holder,
|
|
465
|
+
refused: `whose initializer holds the address of '${name}', which a parameter or local here also names — it would hide that global`,
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
const callees = new Set<string>([sfn.name]);
|
|
470
|
+
for (const e of walkExprs(sfn.body)) {
|
|
471
|
+
if (e.k === 'call' && typeof e.fn === 'string') {
|
|
472
|
+
callees.add(e.fn);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
const rename = new Map<string, string>();
|
|
476
|
+
const bySource = new Map<string, string>();
|
|
477
|
+
for (const obj of objs.values()) {
|
|
478
|
+
if (obj.anonymous) {
|
|
479
|
+
continue; // no source name: folded into a local's initializer at emission (l3/constinit.ts)
|
|
480
|
+
}
|
|
481
|
+
const say = (why: string) => ({ symbol: obj.symbol, refused: `whose source name '${obj.name}' ${why}` });
|
|
482
|
+
const other = bySource.get(obj.name);
|
|
483
|
+
if (other !== undefined) {
|
|
484
|
+
return say(`another static here ('${other}') also has — one block cannot declare both`);
|
|
485
|
+
}
|
|
486
|
+
if (callees.has(obj.name) || taken.has(obj.name)) {
|
|
487
|
+
const what = callees.has(obj.name) ? 'a function' : bound.has(obj.name) ? 'a parameter or local' : 'a global';
|
|
488
|
+
return say(`is also ${what} this one names — the static would hide it`);
|
|
489
|
+
}
|
|
490
|
+
bySource.set(obj.name, obj.symbol);
|
|
491
|
+
rename.set(obj.symbol, obj.name);
|
|
492
|
+
}
|
|
493
|
+
const expr = (e: Expr): Expr => {
|
|
494
|
+
const r = mapExprChildren(e, expr);
|
|
495
|
+
return (r.k === 'var' || r.k === 'addr' || r.k === 'postincr') && rename.has(r.name)
|
|
496
|
+
? { ...r, name: rename.get(r.name)! }
|
|
497
|
+
: r;
|
|
498
|
+
};
|
|
499
|
+
const assigns = (s: Stmt): Stmt => {
|
|
500
|
+
const r = mapStmtLists(s, (list) => list.map(assigns));
|
|
501
|
+
if (r.k === 'for') {
|
|
502
|
+
return { ...r, init: assigns(r.init), inc: assigns(r.inc) };
|
|
503
|
+
}
|
|
504
|
+
return r.k === 'assign' && rename.has(r.name) ? { ...r, name: rename.get(r.name)! } : r;
|
|
505
|
+
};
|
|
506
|
+
return {
|
|
507
|
+
...sfn,
|
|
508
|
+
body: sfn.body.map((s) => assigns(mapStmtExprs(s, expr))),
|
|
509
|
+
...(sfn.globals
|
|
510
|
+
? {
|
|
511
|
+
globals: sfn.globals
|
|
512
|
+
.map((g) => ({ ...g, name: rename.get(g.name) ?? g.name }))
|
|
513
|
+
.sort((a, b) => a.name.localeCompare(b.name)),
|
|
514
|
+
}
|
|
515
|
+
: {}),
|
|
516
|
+
};
|
|
517
|
+
}
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
// • `sitesOf` widens where a loop emitter may MOVE a copy, but the read side does not follow. For
|
|
87
87
|
// a block parameter `clobbers` falls to `liveIn` of the widened site, and a parameter's SSA def
|
|
88
88
|
// point is its own block's entry, not the position the emitter actually writes it at — so a
|
|
89
|
-
// header definition is not seen to clobber a value the sink copies
|
|
89
|
+
// header definition is not seen to clobber a value the sink copies into the body. The
|
|
90
90
|
// slot that is actually sunk is unreachable (its exit arg is a header parameter, which
|
|
91
91
|
// `loop-escape` rejects); reaching it needs a second, non-loop predecessor of the exit block
|
|
92
92
|
// passing the header's definition into the same slot, and no input has been built that does.
|
|
@@ -133,7 +133,7 @@ export interface NameCoalesceDeps {
|
|
|
133
133
|
varName: ReadonlyMap<Value, string>;
|
|
134
134
|
/** name → the type it is declared with */
|
|
135
135
|
varType: ReadonlyMap<string, IrType>;
|
|
136
|
-
/** every natural loop, as its header and the blocks its body contains (
|
|
136
|
+
/** every natural loop, as its header and the blocks its body contains (ir/loops.ts) */
|
|
137
137
|
loops: readonly { header: Block; body: ReadonlySet<Block> }[];
|
|
138
138
|
}
|
|
139
139
|
|