@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
package/src/rank-declare.ts
CHANGED
|
@@ -9,10 +9,14 @@
|
|
|
9
9
|
// asks each emitted tree for its references. A SIBLING MODULE, never a `rank/` directory — see the
|
|
10
10
|
// same note on rank-variations.ts.
|
|
11
11
|
import { type Fn, type Value, defOpMap } from './ir/core';
|
|
12
|
+
import { qualified } from './ir/discipline';
|
|
13
|
+
import { MEM_BASE_OPS } from './ir/opcodes';
|
|
12
14
|
import type { SFn } from './l3/ast';
|
|
13
|
-
import { type SymbolRef, collectSymbolRefs } from './l3/symbol-refs';
|
|
14
|
-
import type {
|
|
15
|
-
import {
|
|
15
|
+
import { type SymbolRef, collectSymbolRefs, ownReturnDefinition } from './l3/symbol-refs';
|
|
16
|
+
import type { FnProto, Prototypes } from './proto';
|
|
17
|
+
import { storeSigned, storedConstantSign } from './raise/storesign';
|
|
18
|
+
import { type SymbolInfo, accessSignedness } from './symbols';
|
|
19
|
+
import { C_TYPEDEFS, type TargetDescription, globalRegisterSymbol } from './target';
|
|
16
20
|
|
|
17
21
|
/** Bare-global ACCESS FACTS for name-only map symbols — the width/signedness authority the
|
|
18
22
|
* declaration synthesis (declare.ts) uses when the map has no shape. The map knows only the
|
|
@@ -22,15 +26,35 @@ import { C_TYPEDEFS } from './target';
|
|
|
22
26
|
* Mirrors structure()'s scalar-global rule: a fact is recorded only for a symbol accessed
|
|
23
27
|
* EXCLUSIVELY at offset 0 with ONE width and ONE load signedness — anything else (interior
|
|
24
28
|
* offsets, address arithmetic, width or sign conflicts) records nothing, because those
|
|
25
|
-
* spellings go through `&gSym` casts where every object decl is address-identical.
|
|
29
|
+
* spellings go through `&gSym` casts where every object decl is address-identical.
|
|
30
|
+
*
|
|
31
|
+
* A symbol never LOADED takes its sign from its stamped stores (raise/storesign.ts); an unstamped
|
|
32
|
+
* store says nothing, so a cell only ever stored unstamped keeps the unsigned default. A load
|
|
33
|
+
* still wins, as in raise/structs.ts. Stores whose stamps disagree keep the width with the
|
|
34
|
+
* unsigned default: dropping the fact would declare `extern u32` under a bare `gSym = v` that
|
|
35
|
+
* then compiles to `sw` where the target has `sh`. The bare spelling has no cast to carry the
|
|
36
|
+
* stamp, so this declaration is the only place the stored constant's view can be written.
|
|
37
|
+
*
|
|
38
|
+
* An UNSTAMPED store of a constant zero-extended from its narrow sign bit vetoes a signed
|
|
39
|
+
* declaration. storesign.ts leaves such a store unstamped when a wider use shares the constant,
|
|
40
|
+
* and the structurer spells it as the literal (`gS = 65535; gW = 65535;`). Under `extern s16 gS`,
|
|
41
|
+
* that literal converts to -1, and the shared register splits in two. The veto only ever chooses
|
|
42
|
+
* the unsigned default, which is what the declaration was before stamps existed. */
|
|
26
43
|
export function bareGlobalAccessFacts(fn: Fn): Map<string, { width: number; signed: boolean }> {
|
|
27
44
|
const defs = defOpMap(fn);
|
|
28
45
|
const symOf = (v: Value): string | null => {
|
|
29
46
|
const d = defs.get(v);
|
|
30
47
|
return d?.opcode === 'gaddr' && d.attrs.code !== true ? (d.attrs.sym as string) : null;
|
|
31
48
|
};
|
|
32
|
-
const acc = new Map<
|
|
33
|
-
|
|
49
|
+
const acc = new Map<
|
|
50
|
+
string,
|
|
51
|
+
{ widths: Set<number>; signs: Set<boolean>; stored: Set<boolean>; unsignedLiteral: boolean; interior: boolean }
|
|
52
|
+
>();
|
|
53
|
+
const get = (s: string) =>
|
|
54
|
+
acc.get(s) ??
|
|
55
|
+
acc
|
|
56
|
+
.set(s, { widths: new Set(), signs: new Set(), stored: new Set(), unsignedLiteral: false, interior: false })
|
|
57
|
+
.get(s)!;
|
|
34
58
|
for (const b of fn.blocks) {
|
|
35
59
|
for (const op of b.ops) {
|
|
36
60
|
if (op.opcode === 'load' || op.opcode === 'store') {
|
|
@@ -42,7 +66,17 @@ export function bareGlobalAccessFacts(fn: Fn): Map<string, { width: number; sign
|
|
|
42
66
|
} else {
|
|
43
67
|
a.widths.add(op.attrs.width as number);
|
|
44
68
|
if (op.opcode === 'load') {
|
|
45
|
-
a.signs.add((
|
|
69
|
+
a.signs.add(accessSignedness(op.attrs.width as number, op.attrs.signed as boolean | undefined));
|
|
70
|
+
} else if (op.attrs.signed !== undefined) {
|
|
71
|
+
a.stored.add(storeSigned(op, op.attrs.width as number));
|
|
72
|
+
} else {
|
|
73
|
+
const d = defs.get(op.operands[1]);
|
|
74
|
+
if (
|
|
75
|
+
d?.opcode === 'const' &&
|
|
76
|
+
storedConstantSign(d.attrs.value as number, op.attrs.width as number) === false
|
|
77
|
+
) {
|
|
78
|
+
a.unsignedLiteral = true;
|
|
79
|
+
}
|
|
46
80
|
}
|
|
47
81
|
}
|
|
48
82
|
}
|
|
@@ -64,9 +98,15 @@ export function bareGlobalAccessFacts(fn: Fn): Map<string, { width: number; sign
|
|
|
64
98
|
}
|
|
65
99
|
const out = new Map<string, { width: number; signed: boolean }>();
|
|
66
100
|
for (const [s, a] of acc) {
|
|
67
|
-
if (
|
|
68
|
-
|
|
101
|
+
if (a.interior || a.widths.size !== 1 || a.signs.size > 1) {
|
|
102
|
+
continue;
|
|
69
103
|
}
|
|
104
|
+
// Loads decide when there are any. Stores whose stamps disagree pick no view, nor does a signed
|
|
105
|
+
// stamp beside an unstamped zero-extended literal: the declaration keeps the width and takes
|
|
106
|
+
// the unsigned default it had before stamps existed.
|
|
107
|
+
const signed =
|
|
108
|
+
a.signs.size === 1 ? a.signs.has(true) : a.stored.size === 1 && a.stored.has(true) && !a.unsignedLiteral;
|
|
109
|
+
out.set(s, { width: [...a.widths][0], signed });
|
|
70
110
|
}
|
|
71
111
|
return out;
|
|
72
112
|
}
|
|
@@ -172,16 +212,73 @@ export type RefusedDeclarationReason =
|
|
|
172
212
|
* module note. */
|
|
173
213
|
export function bareGlobalSymbols(fn: Fn): Map<string, SymbolInfo> {
|
|
174
214
|
const out = new Map<string, SymbolInfo>();
|
|
215
|
+
const defs = defOpMap(fn);
|
|
216
|
+
// A name the lift accessed QUALIFIED at its own address (ir/discipline.ts `qualified`, whichever
|
|
217
|
+
// placement put it there) is declared `volatile`: the structurer spells that access by the bare
|
|
218
|
+
// name, whose declaration owns its qualifiers (l3/volstore.ts `qualifiedMemoryAccess`), so a plain
|
|
219
|
+
// declaration would drop the qualifier the access carries — `gR;` under `extern u32 gR;` compiles
|
|
220
|
+
// to no read at all. A `declared` placement says the project's own header qualifies the name, and
|
|
221
|
+
// the declaration synthesized beside it says so too.
|
|
222
|
+
const qualifiedNames = new Set<string>();
|
|
223
|
+
for (const b of fn.blocks) {
|
|
224
|
+
for (const op of b.ops) {
|
|
225
|
+
const d = MEM_BASE_OPS.has(op.opcode) ? defs.get(op.operands[0]) : undefined;
|
|
226
|
+
if (d?.opcode === 'gaddr' && op.attrs.off === 0 && qualified(op)) {
|
|
227
|
+
qualifiedNames.add(d.attrs.sym as string);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
175
231
|
for (const b of fn.blocks) {
|
|
176
232
|
for (const op of b.ops) {
|
|
177
233
|
if (op.opcode === 'gaddr' && typeof op.attrs.sym === 'string') {
|
|
178
|
-
|
|
234
|
+
const sym = op.attrs.sym;
|
|
235
|
+
out.set(sym, {
|
|
236
|
+
name: sym,
|
|
237
|
+
kind: 'data',
|
|
238
|
+
...(qualifiedNames.has(sym) ? { volatile: true } : {}),
|
|
239
|
+
...(typeof op.attrs.globalRegister === 'string' ? { globalRegister: op.attrs.globalRegister } : {}),
|
|
240
|
+
});
|
|
179
241
|
}
|
|
180
242
|
}
|
|
181
243
|
}
|
|
182
244
|
return out;
|
|
183
245
|
}
|
|
184
246
|
|
|
247
|
+
/** The globals a function-scope static's initializer holds the address of (ir/core.ts
|
|
248
|
+
* `LocalObject.relocs`), as name-only symbols for the same synthesis: the candidate spells each in
|
|
249
|
+
* the static's definition (`(u32)&Name`), and only the static's relocations name it. Kept apart
|
|
250
|
+
* from {@link bareGlobalSymbols}, whose names the variation gates read as the globals the BODY
|
|
251
|
+
* addresses: an address in a table no access ever subscripts says nothing of how one is spelled. */
|
|
252
|
+
export function initAddressSymbols(fn: Fn): Map<string, SymbolInfo> {
|
|
253
|
+
const out = new Map<string, SymbolInfo>();
|
|
254
|
+
for (const obj of fn.localObjects?.values() ?? []) {
|
|
255
|
+
for (const r of obj.relocs ?? []) {
|
|
256
|
+
out.set(r.symbol, { name: r.symbol, kind: 'data' });
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return out;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** The global register variables a compiler that declares them only as a contiguous run
|
|
263
|
+
* (`globalRegisterVariables.contiguous`, mwcc 2.4: "gaps between assigned global register variables
|
|
264
|
+
* are not permitted") needs declared below the highest one `refs` names, and they do not. Each is a
|
|
265
|
+
* word nothing reads, declared only so the one the body uses is legal. */
|
|
266
|
+
function globalRegisterFillers(
|
|
267
|
+
refs: readonly SymbolRef[],
|
|
268
|
+
target: Pick<TargetDescription, 'globalRegisterVariables'>,
|
|
269
|
+
): SymbolRef[] {
|
|
270
|
+
const run = target.globalRegisterVariables?.contiguous ?? [];
|
|
271
|
+
const named = new Set(refs.flatMap((r) => (r.info.globalRegister === undefined ? [] : [r.info.globalRegister])));
|
|
272
|
+
const top = Math.max(-1, ...[...named].map((r) => run.indexOf(r)));
|
|
273
|
+
return run
|
|
274
|
+
.slice(0, top + 1)
|
|
275
|
+
.filter((r) => !named.has(r))
|
|
276
|
+
.map((r) => {
|
|
277
|
+
const name = globalRegisterSymbol(r);
|
|
278
|
+
return { name, info: { name, kind: 'data', globalRegister: r }, synthesized: true };
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
185
282
|
/** The reference collector one enumeration uses, over ONE dictionary. A factory rather than a
|
|
186
283
|
* free function because all four of its inputs are per-enumeration constants that must not vary
|
|
187
284
|
* per tree — naming them here is what keeps a caller from passing a different dictionary to two
|
|
@@ -202,15 +299,39 @@ export function makeRefCollector(ctx: {
|
|
|
202
299
|
/** the globals the TARGET names — the pool/reloc names and the shapes the asm evidences, before
|
|
203
300
|
* the map is unioned in */
|
|
204
301
|
targetNames: ReadonlySet<string>;
|
|
302
|
+
/** the project's prototype table — what lets a CALL TARGET be declared rather than refused */
|
|
303
|
+
prototypes: Prototypes;
|
|
304
|
+
/** the target the lift ran for, whose ABI says which declared types a printed prototype carries */
|
|
305
|
+
target: Pick<TargetDescription, 'doubleArgWords' | 'globalRegisterVariables'>;
|
|
205
306
|
/** reports a refusal at most once per (name, reason); the caller owns the dedup */
|
|
206
307
|
refuse: (name: string, reason: RefusedDeclarationReason) => void;
|
|
308
|
+
/** the function's own declaration — stated, or the context's own entry — whose struct return a
|
|
309
|
+
* tree that returns it needs defined (`ownReturnDefinition`) */
|
|
310
|
+
own?: FnProto;
|
|
207
311
|
}): (tree: SFn) => { symbolRefs?: SymbolRef[] } {
|
|
208
|
-
const { declSymbols, accessFacts, mapSymbols, targetNames, refuse } = ctx;
|
|
312
|
+
const { declSymbols, accessFacts, mapSymbols, targetNames, prototypes, target, refuse, own } = ctx;
|
|
209
313
|
return (tree: SFn): { symbolRefs?: SymbolRef[] } => {
|
|
210
314
|
// The names THIS tree binds. Computed per tree because the emitter mints local names per
|
|
211
315
|
// spelling — but the test below is NOT `bound` alone, and the difference is a wrong answer.
|
|
212
316
|
const bound = new Set<string>([...tree.params.map((p) => p.name), ...tree.locals.map((l) => l.name)]);
|
|
213
|
-
|
|
317
|
+
// A function-scope static is declared in the body beside the locals, so it shares their
|
|
318
|
+
// namespace: one the emitter's own names can reach dies with the spelling, as below.
|
|
319
|
+
const statics = new Set((tree.statics ?? []).map((st) => st.name));
|
|
320
|
+
// …and the compiler's anonymous images, which reach the C only as a local's initializer
|
|
321
|
+
const images = new Set((tree.images ?? []).map((im) => im.name));
|
|
322
|
+
for (const st of statics) {
|
|
323
|
+
if (bound.has(st) || EMITTER_NAME.test(st)) {
|
|
324
|
+
refuse(st, 'emitter-name');
|
|
325
|
+
throw new Error(
|
|
326
|
+
`cannot spell '${tree.name}': the target names a function-scope static '${st}', which is a ` +
|
|
327
|
+
`name the emitted C uses for its own locals and parameters — no declaration can bind it`,
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
const refs = collectSymbolRefs(tree, declSymbols, prototypes, target, refuse).flatMap((r) => {
|
|
332
|
+
if (statics.has(r.name) || images.has(r.name)) {
|
|
333
|
+
return []; // defined in the body — the map's global of the same name is another object
|
|
334
|
+
}
|
|
214
335
|
// THE ONE REFUSAL THAT IS NOT A REFUSAL — a name the emitted C uses for its OWN storage
|
|
215
336
|
// kills the SPELLING, because no declaration makes that candidate right and no declaration
|
|
216
337
|
// makes it fail either. Two shapes, and the second is why the test is the emitter's whole
|
|
@@ -257,8 +378,18 @@ export function makeRefCollector(ctx: {
|
|
|
257
378
|
// A ref no MAP accounts for is a hypothesis read out of the target asm, and it is marked
|
|
258
379
|
// as one all the way to the consumer (SymbolRef.synthesized).
|
|
259
380
|
const synthesized = mapSymbols?.has(r.name) ? {} : { synthesized: true as const };
|
|
381
|
+
// A global the asm reads narrower than a word holds no pointer, whatever it meets bare.
|
|
382
|
+
if (r.holdsPointer && access !== undefined && access.width !== 4) {
|
|
383
|
+
const { holdsPointer: _, ...integer } = r;
|
|
384
|
+
return [{ ...integer, access, ...synthesized }];
|
|
385
|
+
}
|
|
260
386
|
return [{ ...r, ...(access ? { access } : {}), ...synthesized }];
|
|
261
387
|
});
|
|
388
|
+
refs.push(...globalRegisterFillers(refs, target));
|
|
389
|
+
const returned = ownReturnDefinition(tree, own);
|
|
390
|
+
if (returned !== undefined) {
|
|
391
|
+
refs.push(returned);
|
|
392
|
+
}
|
|
262
393
|
return refs.length ? { symbolRefs: refs } : {};
|
|
263
394
|
};
|
|
264
395
|
}
|
package/src/rank-variations.ts
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
import type { Gate } from './l3/gates';
|
|
28
28
|
import type { HoistPlacement } from './l3/hoist';
|
|
29
29
|
import { initFirstGuards } from './l3/initfirst';
|
|
30
|
+
import { narrowDeclarations, narrowLoadDeclarations, narrowReadDeclarations } from './l3/narrowdecl';
|
|
30
31
|
import { pollGuards, pollReads } from './l3/pollguard';
|
|
31
32
|
import { unmergeJoins } from './l3/unmerge';
|
|
32
33
|
// TYPE-ONLY, and deliberately: the structure-variation table types its `options` as
|
|
@@ -35,6 +36,7 @@ import { unmergeJoins } from './l3/unmerge';
|
|
|
35
36
|
import type { structureChecked } from './pipeline';
|
|
36
37
|
import {
|
|
37
38
|
hasDerivedReadHome,
|
|
39
|
+
hasEscapingExtension,
|
|
38
40
|
hasHomeableSharedAddress,
|
|
39
41
|
hasLoopSharedPureValue,
|
|
40
42
|
hasMergeFeedHome,
|
|
@@ -53,11 +55,12 @@ import type { VariationName } from './variation-tokens';
|
|
|
53
55
|
* that setting's own lifted fn (a map-lifted shared lift spells const bases as gaddr, which would
|
|
54
56
|
* blind the /raw-globals siblings — the /addr-home lesson). `strip` opts the variation into the
|
|
55
57
|
* dropped-sibling closure: a candidate that applies it is skipped when its default sibling (the
|
|
56
|
-
* same setting without it) failed the boundary contracts.
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
58
|
+
* same setting without it) failed the boundary contracts. Which of these entries `structure()`'s
|
|
59
|
+
* `assertDefaultAccepts` guard does NOT reset — the ones that cannot unlock a function the default
|
|
60
|
+
* declines, and so carry a written argument in place of the guard — is stated ONCE, beside that
|
|
61
|
+
* guard in structure.ts, and derived from the guard's own reset list in
|
|
62
|
+
* test/variation-offers.test.ts — never restated here, where a second copy drifts from the guard
|
|
63
|
+
* without anything going red. */
|
|
61
64
|
export interface StructureVariation {
|
|
62
65
|
flag:
|
|
63
66
|
| 'reread'
|
|
@@ -67,7 +70,8 @@ export interface StructureVariation {
|
|
|
67
70
|
| 'exprHome'
|
|
68
71
|
| 'derivedHome'
|
|
69
72
|
| 'mergeHome'
|
|
70
|
-
| '
|
|
73
|
+
| 'escapeHome'
|
|
74
|
+
| 'signedCmp'
|
|
71
75
|
| 'freshMerge'
|
|
72
76
|
| 'copyDefPos'
|
|
73
77
|
| 'siteSense';
|
|
@@ -203,17 +207,40 @@ export const STRUCTURE_VARIATIONS: readonly StructureVariation[] = [
|
|
|
203
207
|
perLiftGate: hasMergeFeedHome,
|
|
204
208
|
strip: true,
|
|
205
209
|
},
|
|
206
|
-
// `/
|
|
207
|
-
//
|
|
208
|
-
//
|
|
209
|
-
//
|
|
210
|
-
//
|
|
211
|
-
//
|
|
212
|
-
//
|
|
210
|
+
// `/escape-home` — the escaping-extension-home variation (structure/analysis.ts AnalyzeOptions
|
|
211
|
+
// homeEscapingExtensions): a `zext`/`sext` with 2+ consumers, none of them in its own block,
|
|
212
|
+
// materializes into a local — the register the asm narrowed into once and every later block read
|
|
213
|
+
// — where the default re-evaluates the truncation at each consumer (`(u16)v` per use). The
|
|
214
|
+
// MIRROR of `/expr-home`, which wants a def outside a loop and a consumer inside; neither scope
|
|
215
|
+
// reaches the other's shape. Gated per symbol-map setting like its four siblings.
|
|
216
|
+
//
|
|
217
|
+
// A VARIATION, not a default, and `pokeemerald:AcroBikeHandleInputTurning:agbcc` is the row that
|
|
218
|
+
// decides it: it is a MATCH inside the scope, so forced on the spelling would be REPLACED across
|
|
219
|
+
// the fan rather than added to it. As a variation `compareScored` rides the un-homed sibling
|
|
220
|
+
// beside it and the match cannot be lost — the same argument `/merge-home` records above.
|
|
213
221
|
{
|
|
214
|
-
flag: '
|
|
215
|
-
name: '
|
|
216
|
-
options: (on) => ({
|
|
222
|
+
flag: 'escapeHome',
|
|
223
|
+
name: 'escape-home',
|
|
224
|
+
options: (on) => ({ homeEscapingExtensions: on }),
|
|
225
|
+
perLiftGate: hasEscapingExtension,
|
|
226
|
+
strip: true,
|
|
227
|
+
},
|
|
228
|
+
// `/signed-cmp` — spell an unsigned compare SIGNED (structure.ts unsignedCompareSpelling OFF): no
|
|
229
|
+
// `(u32)` cast at an icmp_u* and no u32 reconciliation of a mixed-claimant declaration. The
|
|
230
|
+
// default is the one spelling value-faithful without a proof; this one is a real source where the
|
|
231
|
+
// compiler PROVED both sides non-negative (only then does it emit the unsigned branch from a
|
|
232
|
+
// signed compare), which emission cannot always repeat. Where the proof does not hold it is a
|
|
233
|
+
// different program, so rank.ts judges every emitted tree (`signedOverUnsignedCompare`) and makes
|
|
234
|
+
// a signed rendering `Candidate.matchOnly`, whichever setting or respell spelled it. A tie or a
|
|
235
|
+
// better nonmatch score is no evidence for it (`synthetic:selloop:gcc2.7.2kmc`).
|
|
236
|
+
// KNOWN GAP, in the safe direction: the judgment reads the tree's declarations, and globals are
|
|
237
|
+
// not among them, so a compare that renders unsigned only through a `void *` global the backend
|
|
238
|
+
// declares (ptrcell.test.ts KR1, `g >= gLimit`) is still withheld on a nonmatch.
|
|
239
|
+
// Gated on the function having an unsigned compare at all.
|
|
240
|
+
{
|
|
241
|
+
flag: 'signedCmp',
|
|
242
|
+
name: 'signed-cmp',
|
|
243
|
+
options: (on) => ({ unsignedCompareSpelling: !on }),
|
|
217
244
|
sharedGate: (sharedLift) => sharedLift.blocks.some((b) => b.ops.some((op) => op.opcode.startsWith('icmp_u'))),
|
|
218
245
|
strip: true,
|
|
219
246
|
},
|
|
@@ -351,18 +378,31 @@ export const STRUCTURE_VARIATIONS: readonly StructureVariation[] = [
|
|
|
351
378
|
},
|
|
352
379
|
];
|
|
353
380
|
|
|
354
|
-
/** The
|
|
355
|
-
|
|
356
|
-
* onto every source. Each fires alone, plus
|
|
357
|
-
* all of them together in table order — not the full subset lattice; the pairs question is
|
|
358
|
-
* settled by applyStacked' skip-on-decline below, and a row demanding a true EXCLUSION pair —
|
|
359
|
-
* all three fire, the match needs exactly two — is what would earn the lattice. */
|
|
360
|
-
export const STACKED_VARIATIONS: { name: VariationName; apply: (sfn: SFn) => SFn | null }[] = [
|
|
381
|
+
/** The statement-order/shape members. */
|
|
382
|
+
const STACKED_SHAPES: { name: VariationName; apply: (sfn: SFn) => SFn | null }[] = [
|
|
361
383
|
{ name: 'initfirst', apply: initFirstGuards },
|
|
362
384
|
{ name: 'pollguard', apply: pollGuards },
|
|
363
385
|
{ name: 'pollread', apply: pollReads },
|
|
364
386
|
];
|
|
365
387
|
|
|
388
|
+
/** The declaration-width members: one dimension, the width each kind of local is declared at
|
|
389
|
+
* (l3/narrowdecl.ts). A function holding a cast-written local,
|
|
390
|
+
* load-written locals and call-result locals may need any combination of the three kinds narrow
|
|
391
|
+
* while all three fire (the matching suite compiles all eight), so every subset is offered. */
|
|
392
|
+
const STACKED_WIDTHS: { name: VariationName; apply: (sfn: SFn) => SFn | null }[] = [
|
|
393
|
+
{ name: 'narrow-decl', apply: narrowDeclarations },
|
|
394
|
+
{ name: 'narrow-load', apply: narrowLoadDeclarations },
|
|
395
|
+
{ name: 'narrow-read', apply: narrowReadDeclarations },
|
|
396
|
+
];
|
|
397
|
+
|
|
398
|
+
/** The STACKED variations (sanctioned in the POLICY note at rank.ts's respell site): each entry is
|
|
399
|
+
* a statement-order/shape or declaration-width respell variation orthogonal to every respell
|
|
400
|
+
* variation outside this table, derived onto every source. Two members need not commute
|
|
401
|
+
* (`/narrow-decl` and `/narrow-read` compete for one cast, `/narrow-load` and `/narrow-read` for
|
|
402
|
+
* one local), and table order then decides which one takes it in a candidate holding both. Which
|
|
403
|
+
* of them a candidate applies is STACKED_SUBSETS. */
|
|
404
|
+
export const STACKED_VARIATIONS = [...STACKED_SHAPES, ...STACKED_WIDTHS];
|
|
405
|
+
|
|
366
406
|
/** The PRE-RESPELL variations (sanctioned in the POLICY note at rank.ts's respell site): a tree
|
|
367
407
|
* rewrite applied BEFORE the respell set, so the whole set derives from its output instead of
|
|
368
408
|
* composing onto it. Same record type as STACKED_VARIATIONS above, and deliberately so — the only difference is
|
|
@@ -430,17 +470,47 @@ export const STACKED_VARIATIONS: { name: VariationName; apply: (sfn: SFn) => SFn
|
|
|
430
470
|
* method are in `apps/benchmark/dataset/synthetic.ts`'s `/unmerge` block. */
|
|
431
471
|
export const PRE_RESPELL_VARIATIONS: typeof STACKED_VARIATIONS = [{ name: 'unmerge', apply: unmergeJoins }];
|
|
432
472
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
473
|
+
/** Every subset of `xs`, the empty one first, each in `xs`' order. */
|
|
474
|
+
const subsets = <X>(xs: readonly X[]): X[][] =>
|
|
475
|
+
xs.reduce<X[][]>((acc, x) => [...acc, ...acc.map((s) => [...s, x])], [[]]);
|
|
476
|
+
|
|
477
|
+
/** The stacked members a candidate applies: the table's two dimensions crossed, a shape choice
|
|
478
|
+
* (none, each shape alone, or every shape) by a width choice (any subset of the widths, none
|
|
479
|
+
* included), the shapes first. A product, because a function may need a shape AND a strict subset
|
|
480
|
+
* of the widths that fire: `initfirst` beside a narrow cast-written local and wide load-written
|
|
481
|
+
* ones (the matching suite compiles that pair both ways). The shapes are not a lattice of their own:
|
|
482
|
+
* applyStacked's skip-on-decline makes "every shape" each pair of them wherever the third declines,
|
|
483
|
+
* and a row demanding a strict subset of the shapes while all fire is what would earn one. Fewest
|
|
484
|
+
* members first, so where everything else ties the smaller set is enumerated first and wins. */
|
|
485
|
+
export const STACKED_SUBSETS: (typeof STACKED_VARIATIONS)[number][][] = subsets(STACKED_WIDTHS)
|
|
486
|
+
.flatMap((widths) =>
|
|
487
|
+
[[], ...STACKED_SHAPES.map((x) => [x]), ...(STACKED_SHAPES.length > 1 ? [STACKED_SHAPES] : [])].map((shapes) => [
|
|
488
|
+
...shapes,
|
|
489
|
+
...widths,
|
|
490
|
+
]),
|
|
491
|
+
)
|
|
492
|
+
.filter((subset) => subset.length > 0)
|
|
493
|
+
.sort((a, b) => a.length - b.length);
|
|
494
|
+
|
|
495
|
+
/** A stacked member that threw, named by the members that fired before it and then itself: the
|
|
496
|
+
* step that threw, which every subset reaching it shares. Reported under that name rather than
|
|
497
|
+
* the whole subset's, so a consumer deduping on the step prints one broken member once and not
|
|
498
|
+
* once per subset holding it. */
|
|
499
|
+
export class StackedMemberThrew extends Error {
|
|
500
|
+
constructor(
|
|
501
|
+
readonly variations: readonly VariationName[],
|
|
502
|
+
cause: unknown,
|
|
503
|
+
) {
|
|
504
|
+
super(cause instanceof Error ? cause.message : String(cause), { cause });
|
|
505
|
+
}
|
|
506
|
+
}
|
|
437
507
|
|
|
438
508
|
/** The subset applied in table order, SKIP-ON-DECLINE: a member that declines contributes
|
|
439
509
|
* nothing rather than killing the combination — the all-shapes candidate is "everything that
|
|
440
510
|
* fires", so a pair is reachable whenever the third declines. The variations are the members that
|
|
441
|
-
* actually FIRED, so they never name a variation that declined; a fired
|
|
442
|
-
* duplicates a smaller subset
|
|
443
|
-
* nothing fired. */
|
|
511
|
+
* actually FIRED, so they never name a variation that declined; a fired set that
|
|
512
|
+
* duplicates a smaller subset's derives the same tree, which the caller emits once
|
|
513
|
+
* (`firstFired`). Null when nothing fired. A member that throws throws StackedMemberThrew. */
|
|
444
514
|
export const applyStacked = (
|
|
445
515
|
subset: readonly (typeof STACKED_VARIATIONS)[number][],
|
|
446
516
|
from: SFn,
|
|
@@ -448,7 +518,12 @@ export const applyStacked = (
|
|
|
448
518
|
let cur = from;
|
|
449
519
|
const fired: VariationName[] = [];
|
|
450
520
|
for (const sp of subset) {
|
|
451
|
-
|
|
521
|
+
let r: SFn | null;
|
|
522
|
+
try {
|
|
523
|
+
r = sp.apply(cur);
|
|
524
|
+
} catch (e) {
|
|
525
|
+
throw new StackedMemberThrew([...fired, sp.name], e);
|
|
526
|
+
}
|
|
452
527
|
if (r) {
|
|
453
528
|
cur = r;
|
|
454
529
|
fired.push(sp.name);
|
|
@@ -457,6 +532,17 @@ export const applyStacked = (
|
|
|
457
532
|
return fired.length > 0 ? { out: cur, variations: fired } : null;
|
|
458
533
|
};
|
|
459
534
|
|
|
535
|
+
/** Whether `fired` is a fired set not yet in `seen`, recording it. Two subsets that fire the same
|
|
536
|
+
* members derive the same tree, so a caller emits only the first. */
|
|
537
|
+
export const firstFired = (seen: Set<string>, fired: readonly VariationName[]): boolean => {
|
|
538
|
+
const key = fired.join('/');
|
|
539
|
+
if (seen.has(key)) {
|
|
540
|
+
return false;
|
|
541
|
+
}
|
|
542
|
+
seen.add(key);
|
|
543
|
+
return true;
|
|
544
|
+
};
|
|
545
|
+
|
|
460
546
|
/** The locals a variation added — a NAME diff rather than a positional slice, so a pass that ever
|
|
461
547
|
* reorders locals cannot silently empty the set. It is what scopes `/volatile` to the pointers
|
|
462
548
|
* the variation itself created (volatilePtrLocals' `only`), leaving the tree's own locals alone. */
|