@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
package/src/rank.ts
CHANGED
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
import { cBackend } from './backend/c';
|
|
22
22
|
import {
|
|
23
23
|
assertDerefsTyped,
|
|
24
|
+
assertFrameUnionsViewed,
|
|
25
|
+
assertLocalStorageStaysInteger,
|
|
24
26
|
assertLocalsWritten,
|
|
25
27
|
assertNoOrphanedLocals,
|
|
26
28
|
assertPlacementSurvives,
|
|
@@ -53,14 +55,16 @@ import { reindexWalks } from './l3/reindex';
|
|
|
53
55
|
import { hoistScopedBases } from './l3/scopebase';
|
|
54
56
|
import { sinkInitsToFirstUse } from './l3/sinkinit';
|
|
55
57
|
import type { SymbolRef } from './l3/symbol-refs';
|
|
58
|
+
import { signedOverUnsignedCompare } from './l3/typing';
|
|
56
59
|
import { type UnreduceResult, unreduceAccumulators } from './l3/unreduce';
|
|
57
60
|
import { deviceVolatileClaims, volatilePtrLocals, volatileSubsetCandidates } from './l3/volatileptr';
|
|
58
61
|
import { volatileValueLocals } from './l3/volatileval';
|
|
59
62
|
import { volatileDeviceStores } from './l3/volstore';
|
|
60
63
|
import { zeroSubNegates } from './l3/zerosub';
|
|
61
64
|
import { RewritePattern } from './pattern/engine';
|
|
62
|
-
import { applyIdiomPatterns, liftStamped, raiseRecovered, structureChecked } from './pipeline';
|
|
63
|
-
import { type Prototypes, declaresVoidReturn, prototypesFromSymbols } from './proto';
|
|
65
|
+
import { applyIdiomPatterns, liftStamped, raiseRecovered, stampBehaviorsFor, structureChecked } from './pipeline';
|
|
66
|
+
import { type FnProto, type Prototypes, declaresVoidReturn, prototypesFromSymbols } from './proto';
|
|
67
|
+
import { type ContextGlobals, splitOwnContext } from './proto-context';
|
|
64
68
|
import { inferGlobalArrays, orderLicensedGlobals, sameDerivedShape } from './raise/globalshape';
|
|
65
69
|
import { OFFSET_NAME_PASS } from './raise/offsetnames';
|
|
66
70
|
import { runPreRecovery } from './raise/pre-recovery';
|
|
@@ -70,6 +74,7 @@ import {
|
|
|
70
74
|
type RefusedDeclarationReason,
|
|
71
75
|
bareGlobalAccessFacts,
|
|
72
76
|
bareGlobalSymbols,
|
|
77
|
+
initAddressSymbols,
|
|
73
78
|
makeRefCollector,
|
|
74
79
|
} from './rank-declare';
|
|
75
80
|
import {
|
|
@@ -82,14 +87,16 @@ import {
|
|
|
82
87
|
SIGNEDNESS,
|
|
83
88
|
STACKED_SUBSETS,
|
|
84
89
|
STRUCTURE_VARIATIONS,
|
|
90
|
+
StackedMemberThrew,
|
|
85
91
|
type StructureVariation,
|
|
86
92
|
UNFOLDED_HOISTS,
|
|
87
93
|
applyStacked,
|
|
88
94
|
createdLocals,
|
|
95
|
+
firstFired,
|
|
89
96
|
sameBases,
|
|
90
97
|
} from './rank-variations';
|
|
91
98
|
import { type Stillborn, stillbornVerdict } from './stillborn';
|
|
92
|
-
import { hasDivergentSharedRet } from './structure/structure';
|
|
99
|
+
import { DefaultAcceptsMemo, DefaultAcceptsMemoMisuse, hasDivergentSharedRet } from './structure/structure';
|
|
93
100
|
import {
|
|
94
101
|
type SymbolInfo,
|
|
95
102
|
type SymbolMap,
|
|
@@ -159,6 +166,10 @@ export interface EnumerateOptions {
|
|
|
159
166
|
asmData?: AsmData;
|
|
160
167
|
/** address→symbol map (symbols.ts) — same contract as DecompileOptions.symbols */
|
|
161
168
|
symbols?: SymbolMap;
|
|
169
|
+
/** same contract as DecompileOptions.contextGlobals */
|
|
170
|
+
contextGlobals?: ContextGlobals;
|
|
171
|
+
/** same contract as DecompileOptions.ownContextProto */
|
|
172
|
+
ownContextProto?: FnProto;
|
|
162
173
|
/** Called when a respell variation THROWS or fails a boundary contract, so the failure is visible
|
|
163
174
|
* instead of the candidate silently not existing. Enumeration continues either way — the default
|
|
164
175
|
* candidate is unaffected — but a variation that never fires because it always throws is a defect,
|
|
@@ -255,16 +266,41 @@ export interface Candidate {
|
|
|
255
266
|
* • `l3/intcell.ts` spells a global as the integer a project may have declared it, where a
|
|
256
267
|
* pointer declaration scales the offset it adds, and no declaration is in the asm.
|
|
257
268
|
*
|
|
258
|
-
*
|
|
259
|
-
*
|
|
269
|
+
* A third obligation is judged on the finished tree rather than declared by a variation
|
|
270
|
+
* (`signedUnsignedCompare` below):
|
|
271
|
+
*
|
|
272
|
+
* • `/signed-cmp`, or a respell over its tree, spells an unsigned compare over signed-rendering
|
|
273
|
+
* operands, which compiles to a signed compare unless the operands are non-negative, and the
|
|
274
|
+
* compiler's proof of that is not in the asm.
|
|
275
|
+
*
|
|
276
|
+
* What settles all three instead is the object: a candidate that assembles to the target's own
|
|
277
|
+
* bytes IS the program, whatever a gate could have proved. So the spelling is offered, scored, and then
|
|
260
278
|
* either wins on proof or is WITHHELD — never shown as a best-effort answer. Both ranking drivers
|
|
261
279
|
* ask `withheldReason`, so neither can publish what the other would not. */
|
|
262
280
|
matchOnly?: true;
|
|
281
|
+
/** The tree this source was emitted from spells some unsigned compare so that C evaluates it
|
|
282
|
+
* signed (l3/typing.ts `signedOverUnsignedCompare`, judged on the finished tree), which makes it
|
|
283
|
+
* `matchOnly` and names the fact it owes. Set for whichever variation got it there: emission
|
|
284
|
+
* under `/signed-cmp`, or a respell that substituted into a compare emission had pinned. It is
|
|
285
|
+
* the only place this obligation is decided: a pass that rebuilds a compare spreads the node to
|
|
286
|
+
* keep its `unsignedCmp` evidence (l3/regspell.ts), a merge that keeps one of two equal nodes
|
|
287
|
+
* keeps the evidence of both (l3/ast.ts `unionEvidence`), or drops it only where its own gate proved
|
|
288
|
+
* the new spelling compares the same values (l3/initfirst.ts `swapSide`, l3/reindex.ts). */
|
|
289
|
+
signedUnsignedCompare?: true;
|
|
263
290
|
/** This candidate's lift is `/setup-args` and drops an earlier callee's result that a call through
|
|
264
291
|
* a register is passed (frontend/ssa.ts `setupArgsDiscardsPassedResult`). It loses every score
|
|
265
292
|
* tie (compareScored): a tie refutes neither reading, and a shorter spelling must not win by
|
|
266
293
|
* dropping a value the callee may read. */
|
|
267
294
|
discardsPassedResult?: true;
|
|
295
|
+
/** This candidate's lift is `/narrow-param` and declares a parameter narrow that no prototype
|
|
296
|
+
* declares (raise/pre-recovery.ts `onUndeclaredWidth`). It loses every score tie
|
|
297
|
+
* (compareScored): a tie refutes neither declaration, and the narrow one changes the bytes of
|
|
298
|
+
* every prototyped caller, which this function's score cannot see. */
|
|
299
|
+
guessesParamWidth?: true;
|
|
300
|
+
/** This candidate's lift is `/far-cast` and spells a far member store through a cast of its
|
|
301
|
+
* base. It loses every score tie (compareScored): a tie means the compiler built the member's
|
|
302
|
+
* object too, and the member is the spelling that names the field. */
|
|
303
|
+
farStoreCast?: true;
|
|
268
304
|
}
|
|
269
305
|
/** A candidate paired with its score `S` (the injected scorer's result shape — must carry `.score`). */
|
|
270
306
|
export interface Scored<S> extends Candidate {
|
|
@@ -356,7 +392,7 @@ export interface RankedResult<S> {
|
|
|
356
392
|
candidates: Scored<S>[]; // sorted best (lowest) first
|
|
357
393
|
/** candidates whose scoreFn threw — empty when every spelling built */
|
|
358
394
|
dropped: DroppedCandidate[];
|
|
359
|
-
/** candidates withheld for want of a byte-exact proof — empty unless a `matchOnly`
|
|
395
|
+
/** candidates withheld for want of a byte-exact proof — empty unless a `matchOnly` candidate scored a nonmatch */
|
|
360
396
|
withheld: WithheldCandidate[];
|
|
361
397
|
}
|
|
362
398
|
|
|
@@ -371,6 +407,9 @@ export function withheldReason<S extends { score: number }>(c: Candidate, score:
|
|
|
371
407
|
return null;
|
|
372
408
|
}
|
|
373
409
|
const facts = c.variations.flatMap((v) => PROOF_OBLIGATIONS.get(v) ?? []);
|
|
410
|
+
if (c.signedUnsignedCompare === true) {
|
|
411
|
+
facts.push(SIGNED_COMPARE_OBLIGATION);
|
|
412
|
+
}
|
|
374
413
|
const fact = facts.length > 0 ? facts.join(' and on ') : 'a fact no gate over the C can settle';
|
|
375
414
|
return `this spelling rests on ${fact}; only a byte-exact score proves it`;
|
|
376
415
|
}
|
|
@@ -381,6 +420,11 @@ const PROOF_OBLIGATIONS: ReadonlyMap<string, string> = new Map<VariationName, st
|
|
|
381
420
|
['int-cell', 'the project declaring the global an integer, which the assembly does not show'],
|
|
382
421
|
]);
|
|
383
422
|
|
|
423
|
+
/** The fact `Candidate.signedUnsignedCompare` rests on. Keyed by the flag, not by `/signed-cmp`:
|
|
424
|
+
* that name is on candidates that owe nothing (a compare over a `u32` operand) and missing from
|
|
425
|
+
* respells that do. */
|
|
426
|
+
const SIGNED_COMPARE_OBLIGATION = "the compiler having proved every unsigned compare's operands non-negative";
|
|
427
|
+
|
|
384
428
|
/** What a respell variation hands `respell`: its tree, or — when the variation cannot establish the
|
|
385
429
|
* candidate's semantics from inside the pass — the tree paired with that fact. `undefined`/`null`
|
|
386
430
|
* is a decline. */
|
|
@@ -444,6 +488,8 @@ interface TreeSource {
|
|
|
444
488
|
deviceVolatile?: number;
|
|
445
489
|
/** see `Candidate.matchOnly` — set by a variation that cannot establish its own semantics */
|
|
446
490
|
matchOnly?: true;
|
|
491
|
+
/** see `Candidate.signedUnsignedCompare` */
|
|
492
|
+
signedUnsignedCompare?: true;
|
|
447
493
|
}
|
|
448
494
|
|
|
449
495
|
/** Emit the DISTINCT type/branch-sense candidates for `name` — PURE, no scoring.
|
|
@@ -471,14 +517,17 @@ export function enumerateCandidates(
|
|
|
471
517
|
let lastEmitError: unknown = null;
|
|
472
518
|
// Same merge as `decompile`: the project's DWARF signatures fill in what the caller did not
|
|
473
519
|
// state, so both the annotate pass and the ranked candidates reason about one prototype table.
|
|
474
|
-
const
|
|
520
|
+
const ownContext = splitOwnContext(opts.prototypes, name, opts.ownContextProto);
|
|
521
|
+
const prototypes = prototypesFromSymbols(opts.symbols, ownContext.prototypes ?? {});
|
|
475
522
|
const baseOpts = {
|
|
476
523
|
...structureOptionsFor(target, declaresVoidReturn(prototypes[name]), prototypes),
|
|
477
524
|
// See the same line in pipeline.ts: a backend that cannot print switch fall-through must not
|
|
478
525
|
// be handed a tree carrying one, because its refusal costs the whole candidate (and, when
|
|
479
526
|
// every candidate carries it, the whole row).
|
|
480
527
|
spellSwitchFallthrough: backend.spellsSwitchFallthrough,
|
|
528
|
+
spellSwitchBreak: backend.spellsSwitchBreak,
|
|
481
529
|
...(opts.symbols ? { symbols: symbolsByName(opts.symbols) } : {}),
|
|
530
|
+
...(opts.contextGlobals ? { contextGlobals: opts.contextGlobals } : {}),
|
|
482
531
|
};
|
|
483
532
|
// Branch-sense is a differ-ranked VARIATION, the same class as param signedness: a divergent `if`
|
|
484
533
|
// can be spelled with either sense (`if (c) A else B` vs `if (!c) B else A`), and which one the
|
|
@@ -600,11 +649,23 @@ export function enumerateCandidates(
|
|
|
600
649
|
// it buys is that a lift-time change which splits them enumerates both settings rather than
|
|
601
650
|
// silently dropping one, the failure nothing reports.
|
|
602
651
|
let sharedLiftTreeOwned = false;
|
|
652
|
+
let sharedLiftExtendedReaders = false;
|
|
653
|
+
let sharedLiftFoldedStore = false;
|
|
603
654
|
// Shared lift: recover ONCE with no signedness pin, to learn which entry params are
|
|
604
655
|
// pointers/aggregates so they are excluded from the signedness variation (see NO_PIN_KINDS). One
|
|
605
656
|
// extra lift+recover, no compile. (The shared lift deliberately stops after recoverTypes — it only reads the param KINDS, so
|
|
606
657
|
// the totality contract / return-sinking of the full spine are not run on it.)
|
|
607
|
-
const sharedLift = liftStamped(
|
|
658
|
+
const sharedLift = liftStamped(
|
|
659
|
+
name,
|
|
660
|
+
asm,
|
|
661
|
+
target,
|
|
662
|
+
prototypes,
|
|
663
|
+
opts.asmData,
|
|
664
|
+
opts.symbols,
|
|
665
|
+
opts.contextGlobals,
|
|
666
|
+
undefined,
|
|
667
|
+
ownContext.ownContextProto,
|
|
668
|
+
);
|
|
608
669
|
// The ARRAY SHAPES the input assembly evidences (raise/globalshape.ts), for the DECLARATION
|
|
609
670
|
// half. Read off the shared lift's LIFTED form — before the fold below and the tower rewrite it —
|
|
610
671
|
// because the base-materialization order the derivation's licence reads does not survive them.
|
|
@@ -619,6 +680,16 @@ export function enumerateCandidates(
|
|
|
619
680
|
sharedLiftTreeOwned = true;
|
|
620
681
|
},
|
|
621
682
|
},
|
|
683
|
+
paramWidth: {
|
|
684
|
+
onExtendedReaders: () => {
|
|
685
|
+
sharedLiftExtendedReaders = true;
|
|
686
|
+
},
|
|
687
|
+
},
|
|
688
|
+
displaced: {
|
|
689
|
+
onFoldedStore: () => {
|
|
690
|
+
sharedLiftFoldedStore = true;
|
|
691
|
+
},
|
|
692
|
+
},
|
|
622
693
|
});
|
|
623
694
|
recoverTypes(sharedLift);
|
|
624
695
|
const ptrIdx = new Set<number>(
|
|
@@ -643,17 +714,47 @@ export function enumerateCandidates(
|
|
|
643
714
|
// shared lift's `onTreeOwned` hook has had its chance to fire. Called any earlier it would report a confident
|
|
644
715
|
// `false` for a function that owns a tree. As a `const` below that call, an early call is a TDZ
|
|
645
716
|
// ReferenceError instead — a wrong answer traded for a loud one.
|
|
646
|
-
|
|
717
|
+
//
|
|
718
|
+
// `/narrow-param`'s gate is read off the same lift the same way: the width pass reports a
|
|
719
|
+
// parameter the variation's lift would narrow (raise/pre-recovery.ts `ParamWidthOptions`). So is
|
|
720
|
+
// `/far-cast`'s: the displaced-access fold reports a store it moved into a struct.
|
|
721
|
+
const liftReportsIn = (
|
|
722
|
+
symbols: typeof opts.symbols,
|
|
723
|
+
): { treeOwned: boolean; extendedReaders: boolean; foldedStore: boolean } => {
|
|
647
724
|
if (symbols === opts.symbols) {
|
|
648
|
-
return
|
|
725
|
+
return {
|
|
726
|
+
treeOwned: sharedLiftTreeOwned,
|
|
727
|
+
extendedReaders: sharedLiftExtendedReaders,
|
|
728
|
+
foldedStore: sharedLiftFoldedStore,
|
|
729
|
+
};
|
|
649
730
|
}
|
|
650
|
-
const p = liftStamped(
|
|
731
|
+
const p = liftStamped(
|
|
732
|
+
name,
|
|
733
|
+
asm,
|
|
734
|
+
target,
|
|
735
|
+
prototypes,
|
|
736
|
+
opts.asmData,
|
|
737
|
+
symbols,
|
|
738
|
+
opts.contextGlobals,
|
|
739
|
+
undefined,
|
|
740
|
+
ownContext.ownContextProto,
|
|
741
|
+
);
|
|
651
742
|
applyIdiomPatterns(p, target, opts.patterns);
|
|
652
|
-
|
|
743
|
+
const seen = { treeOwned: false, extendedReaders: false, foldedStore: false };
|
|
653
744
|
runPreRecovery(p, target, () => verify(p), prototypes[name], {
|
|
654
745
|
shortCircuit: {
|
|
655
746
|
onTreeOwned: () => {
|
|
656
|
-
seen = true;
|
|
747
|
+
seen.treeOwned = true;
|
|
748
|
+
},
|
|
749
|
+
},
|
|
750
|
+
paramWidth: {
|
|
751
|
+
onExtendedReaders: () => {
|
|
752
|
+
seen.extendedReaders = true;
|
|
753
|
+
},
|
|
754
|
+
},
|
|
755
|
+
displaced: {
|
|
756
|
+
onFoldedStore: () => {
|
|
757
|
+
seen.foldedStore = true;
|
|
657
758
|
},
|
|
658
759
|
},
|
|
659
760
|
});
|
|
@@ -916,7 +1017,11 @@ export function enumerateCandidates(
|
|
|
916
1017
|
// spelling needs one, and the declaration a candidate spelling `gTbl[i]` cannot compile
|
|
917
1018
|
// without), and the project map, which knows more than either.
|
|
918
1019
|
const mapSymbols = baseOpts.symbols;
|
|
919
|
-
const targetSymbols = new Map<string, SymbolInfo>([
|
|
1020
|
+
const targetSymbols = new Map<string, SymbolInfo>([
|
|
1021
|
+
...initAddressSymbols(sharedLift),
|
|
1022
|
+
...bareGlobalSymbols(sharedLift),
|
|
1023
|
+
...sharedLiftShapes,
|
|
1024
|
+
]);
|
|
920
1025
|
const declSymbols = new Map<string, SymbolInfo>([...targetSymbols, ...(mapSymbols ?? [])]);
|
|
921
1026
|
// The four per-enumeration constants named at the seam rather than captured across 60 lines of
|
|
922
1027
|
// closure (rank-declare.ts states why they belong on one object).
|
|
@@ -930,6 +1035,7 @@ export function enumerateCandidates(
|
|
|
930
1035
|
prototypes,
|
|
931
1036
|
target,
|
|
932
1037
|
refuse,
|
|
1038
|
+
own: Object.hasOwn(prototypes, name) ? prototypes[name] : ownContext.ownContextProto,
|
|
933
1039
|
});
|
|
934
1040
|
// THE RESPELL SET, as a function whose PARAMETER LIST is the invariant the tree skip below
|
|
935
1041
|
// rests on: every source here is a pure function of the structured tree and this call's own
|
|
@@ -953,6 +1059,14 @@ export function enumerateCandidates(
|
|
|
953
1059
|
// of `/volatile` — a candidate that did not fail and is still in the fan. The order
|
|
954
1060
|
// is the candidates' own (`[pf.name, ...sp.variations]`), so a reported name and an enumerated
|
|
955
1061
|
// candidate's variations name the same candidate the same way.
|
|
1062
|
+
//
|
|
1063
|
+
// THE COMPARE-SIGNEDNESS PROOF, asked of every tree a source is emitted from rather than of the
|
|
1064
|
+
// passes that build them. Emission pins each unsigned compare it lowers (structure.ts), but a
|
|
1065
|
+
// respell that substitutes into a compare — `/unmerge` putting an arm's `s16` global where the
|
|
1066
|
+
// pinned `u32` local stood, against an `s32` parameter — re-renders it signed. So the finished
|
|
1067
|
+
// tree is judged here, and a signed rendering of an `icmp_u*` is `matchOnly` whoever produced it.
|
|
1068
|
+
const cmpProof = (tree: SFn): { matchOnly?: true; signedUnsignedCompare?: true } =>
|
|
1069
|
+
signedOverUnsignedCompare(tree) ? { matchOnly: true, signedUnsignedCompare: true } : {};
|
|
956
1070
|
const respellTree = (sfn: SFn, preRespellVariations: readonly Variation[] = []): TreeSources => {
|
|
957
1071
|
// The walk→index respell variation (l3/reindex.ts) is a THIRD variation on the same footing as
|
|
958
1072
|
// signedness and branch sense: whether the source spelled `*p; p++` or `arr[i]` is
|
|
@@ -970,7 +1084,7 @@ export function enumerateCandidates(
|
|
|
970
1084
|
// Pascal backend loud-declines). Refusing EVERY tree is still loud — the empty-enumeration
|
|
971
1085
|
// check at the end raises the last refusal.
|
|
972
1086
|
try {
|
|
973
|
-
sources.push({ variations: [], source: backend.emit(sfn), ...refsOf(sfn), ...volOf(sfn) });
|
|
1087
|
+
sources.push({ variations: [], source: backend.emit(sfn), ...refsOf(sfn), ...volOf(sfn), ...cmpProof(sfn) });
|
|
974
1088
|
} catch (e) {
|
|
975
1089
|
reportThrow(preRespellVariations, e);
|
|
976
1090
|
return { sources, emit: { error: e } };
|
|
@@ -1068,7 +1182,16 @@ export function enumerateCandidates(
|
|
|
1068
1182
|
assertLocalsWritten(alt);
|
|
1069
1183
|
assertPostIncrUnshared(alt);
|
|
1070
1184
|
assertNoOrphanedLocals(sfn, alt);
|
|
1071
|
-
|
|
1185
|
+
assertLocalStorageStaysInteger(sfn, alt);
|
|
1186
|
+
assertFrameUnionsViewed(alt);
|
|
1187
|
+
sources.push({
|
|
1188
|
+
variations,
|
|
1189
|
+
source: backend.emit(alt),
|
|
1190
|
+
...refsOf(alt),
|
|
1191
|
+
...volOf(alt),
|
|
1192
|
+
...proof,
|
|
1193
|
+
...cmpProof(alt),
|
|
1194
|
+
});
|
|
1072
1195
|
// STACKED variations, derived onto EVERY source (the POLICY note above carries the
|
|
1073
1196
|
// admission argument). Each is a statement-order/shape or declaration-width fact
|
|
1074
1197
|
// orthogonal to the respell variations outside the table; subsets compose in the fixed
|
|
@@ -1095,19 +1218,24 @@ export function enumerateCandidates(
|
|
|
1095
1218
|
// which moves only const or pure-read assigns and so cannot lift a read of a base local
|
|
1096
1219
|
// above its init.
|
|
1097
1220
|
const minted = createdLocals(sfn, alt);
|
|
1221
|
+
const firedSets = new Set<string>();
|
|
1098
1222
|
for (const subset of STACKED_SUBSETS) {
|
|
1099
1223
|
// ONE TRY PER SHAPE — a shape is its own candidate and fails as its own candidate.
|
|
1100
1224
|
// Sharing the respell variation's outer try would let a throw deriving one subset
|
|
1101
1225
|
// discard every later one, under that variation's name, which names no shape.
|
|
1102
|
-
|
|
1226
|
+
// A throw past applyStacked is reported under the fired set, the candidate's own name.
|
|
1227
|
+
let shapeVariations: readonly Variation[] = [];
|
|
1103
1228
|
try {
|
|
1104
1229
|
const shaped = applyStacked(subset, alt);
|
|
1105
|
-
if (shaped !== null) {
|
|
1230
|
+
if (shaped !== null && firstFired(firedSets, shaped.variations)) {
|
|
1231
|
+
shapeVariations = shaped.variations;
|
|
1106
1232
|
assertResolved(shaped.out);
|
|
1107
1233
|
assertDerefsTyped(shaped.out);
|
|
1108
1234
|
assertLocalsWritten(shaped.out);
|
|
1109
1235
|
assertPostIncrUnshared(shaped.out);
|
|
1110
1236
|
assertNoOrphanedLocals(alt, shaped.out);
|
|
1237
|
+
assertLocalStorageStaysInteger(alt, shaped.out);
|
|
1238
|
+
assertFrameUnionsViewed(shaped.out);
|
|
1111
1239
|
assertPlacementSurvives(alt, shaped.out, minted);
|
|
1112
1240
|
sources.push({
|
|
1113
1241
|
variations: [...variations, ...shaped.variations],
|
|
@@ -1116,10 +1244,12 @@ export function enumerateCandidates(
|
|
|
1116
1244
|
...volOf(shaped.out),
|
|
1117
1245
|
// a shape derived from a proof-gated spelling inherits the requirement
|
|
1118
1246
|
...proof,
|
|
1247
|
+
...cmpProof(shaped.out),
|
|
1119
1248
|
});
|
|
1120
1249
|
}
|
|
1121
1250
|
} catch (e) {
|
|
1122
|
-
|
|
1251
|
+
const step = e instanceof StackedMemberThrew ? e.variations : shapeVariations;
|
|
1252
|
+
reportThrow([...preRespellVariations, ...variations, ...step], e);
|
|
1123
1253
|
}
|
|
1124
1254
|
}
|
|
1125
1255
|
}
|
|
@@ -1135,19 +1265,21 @@ export function enumerateCandidates(
|
|
|
1135
1265
|
// `/argbase` — name a call's argument bases before the call (l3/argbase.ts). A variation on the
|
|
1136
1266
|
// same footing as the others: the default inline spelling stays in the list, so the differ
|
|
1137
1267
|
// referees and this can never cost a match.
|
|
1268
|
+
const firedSets = new Set<string>();
|
|
1138
1269
|
for (const subset of STACKED_SUBSETS) {
|
|
1139
1270
|
// the truthful variations need the pass to RUN first, so this bypasses respell's
|
|
1140
1271
|
// variations-then-thunk shape: same try posture, variations from the fired members
|
|
1141
1272
|
try {
|
|
1142
1273
|
const shaped = applyStacked(subset, sfn);
|
|
1143
|
-
if (shaped !== null) {
|
|
1274
|
+
if (shaped !== null && firstFired(firedSets, shaped.variations)) {
|
|
1144
1275
|
// the ONE call whose variations already name shapes — say so, rather than making `respell`
|
|
1145
1276
|
// read them back out of the variations it was handed
|
|
1146
1277
|
respell(shaped.variations, () => shaped.out, true);
|
|
1147
1278
|
}
|
|
1148
1279
|
} catch (e) {
|
|
1149
|
-
//
|
|
1150
|
-
|
|
1280
|
+
// `respell` reports its own throws, so only a member can throw here
|
|
1281
|
+
const step = e instanceof StackedMemberThrew ? e.variations : subset.map((x) => x.name);
|
|
1282
|
+
reportThrow([...preRespellVariations, ...step], e);
|
|
1151
1283
|
}
|
|
1152
1284
|
}
|
|
1153
1285
|
respell(['argbase'], () => materializeArgBases(sfn));
|
|
@@ -1828,8 +1960,10 @@ export function enumerateCandidates(
|
|
|
1828
1960
|
// audit cannot bound. This is the setting's first lift, and every later one is the same lift
|
|
1829
1961
|
// again.
|
|
1830
1962
|
let treeOwnedFold: boolean;
|
|
1963
|
+
let extendedReaders: boolean;
|
|
1964
|
+
let foldedStore: boolean;
|
|
1831
1965
|
try {
|
|
1832
|
-
treeOwnedFold =
|
|
1966
|
+
({ treeOwned: treeOwnedFold, extendedReaders, foldedStore } = liftReportsIn(symbolSetting.symbols));
|
|
1833
1967
|
} catch (e) {
|
|
1834
1968
|
if (abortsRow(symbolSetting.variations)) {
|
|
1835
1969
|
throw e;
|
|
@@ -1852,7 +1986,22 @@ export function enumerateCandidates(
|
|
|
1852
1986
|
if (cand.signed && !pinnable) {
|
|
1853
1987
|
break;
|
|
1854
1988
|
}
|
|
1855
|
-
|
|
1989
|
+
let tailSlot = false;
|
|
1990
|
+
const base = liftStamped(
|
|
1991
|
+
name,
|
|
1992
|
+
asm,
|
|
1993
|
+
target,
|
|
1994
|
+
prototypes,
|
|
1995
|
+
opts.asmData,
|
|
1996
|
+
symbolSetting.symbols,
|
|
1997
|
+
opts.contextGlobals,
|
|
1998
|
+
{
|
|
1999
|
+
onTailSlot: () => {
|
|
2000
|
+
tailSlot = true;
|
|
2001
|
+
},
|
|
2002
|
+
},
|
|
2003
|
+
ownContext.ownContextProto,
|
|
2004
|
+
);
|
|
1856
2005
|
// `/setup-args` — pass a prototype-less callee only what the CALLING BLOCK set up; which of
|
|
1857
2006
|
// the two readings the source spelled is genuinely ambiguous, and frontend/ssa.ts
|
|
1858
2007
|
// narrowToSetupArgs carries the argument for why the differ is what settles it.
|
|
@@ -1918,12 +2067,66 @@ export function enumerateCandidates(
|
|
|
1918
2067
|
{ variations: ['setup-args'], narrow: true },
|
|
1919
2068
|
]
|
|
1920
2069
|
: [{ variations: [], narrow: false }];
|
|
2070
|
+
// `/narrow-param` — declare a parameter every reader of which is an extension at the widest
|
|
2071
|
+
// reader's width (raise/paramwidth.ts, EVERY READER AN EXTENSION). A lift variation for the
|
|
2072
|
+
// reason `/connective` is: the raise mutates in place, and the parameter's width is what
|
|
2073
|
+
// recovery and every later stage read. Enumerated only where this symbol-map setting's lift
|
|
2074
|
+
// reports such a parameter; crossed with the other two, and the dedup collapses what agrees.
|
|
2075
|
+
const paramSettings: { variations: readonly Variation[]; extendedReaders: boolean }[] = extendedReaders
|
|
2076
|
+
? [
|
|
2077
|
+
{ variations: [], extendedReaders: false },
|
|
2078
|
+
{ variations: ['narrow-param'], extendedReaders: true },
|
|
2079
|
+
]
|
|
2080
|
+
: [{ variations: [], extendedReaders: false }];
|
|
2081
|
+
// `/kept-slot` — keep the delay slot of a MIPS `b`/`j` whose slot ends a common tail both arms
|
|
2082
|
+
// spell, where the default lift retargets it to the copy before its target and merges the
|
|
2083
|
+
// arms (frontend/mips.ts `normaliseBranchLikely`: the two are one object, and which the source
|
|
2084
|
+
// wrote is the differ's to say). A LIFT variation because the rewrite is the CFG every later
|
|
2085
|
+
// stage reads. Enumerated only where this setting's default lift reports such a slot; crossed
|
|
2086
|
+
// with the others, and the dedup collapses what agrees. An IDO reading: GCC 2.7.2 and KMC
|
|
2087
|
+
// cross-jump a tail both arms spell before they fill a slot, so they leave no such tail.
|
|
2088
|
+
const tailSettings: { variations: readonly Variation[]; keepTailSlots: boolean }[] = tailSlot
|
|
2089
|
+
? [
|
|
2090
|
+
{ variations: [], keepTailSlots: false },
|
|
2091
|
+
{ variations: ['kept-slot'], keepTailSlots: true },
|
|
2092
|
+
]
|
|
2093
|
+
: [{ variations: [], keepTailSlots: false }];
|
|
2094
|
+
// `/far-cast` — keep each store the displaced-access fold would move into a struct on the
|
|
2095
|
+
// address it was reached through (raise/structs.ts `foldDisplacedAccesses`). On agbcc the
|
|
2096
|
+
// member store and the cast store are two objects (target.ts `farMemberStoreOrder`), and a
|
|
2097
|
+
// hoisted pointer the source held builds the cast's. Elsewhere a folded store stops aliasing
|
|
2098
|
+
// the loads beside it and a held pointer is spelled as members, which KMC, GCC 2.7.2 and mwcc
|
|
2099
|
+
// build into other objects. So which one the source wrote is the differ's to say, on every
|
|
2100
|
+
// target. A LIFT variation because the fold decides which struct the stores join, which
|
|
2101
|
+
// recovery and every later stage read. Enumerated only where this setting's lift folded a
|
|
2102
|
+
// store.
|
|
2103
|
+
const castSettings: { variations: readonly Variation[]; keepStores: boolean }[] =
|
|
2104
|
+
foldedStore && offeredOn(target, ['far-cast'])
|
|
2105
|
+
? [
|
|
2106
|
+
{ variations: [], keepStores: false },
|
|
2107
|
+
{ variations: ['far-cast'], keepStores: true },
|
|
2108
|
+
]
|
|
2109
|
+
: [{ variations: [], keepStores: false }];
|
|
1921
2110
|
const liftSettings = narrowSettings.flatMap((l) =>
|
|
1922
|
-
connectiveSettings.
|
|
2111
|
+
connectiveSettings.flatMap((c) =>
|
|
2112
|
+
paramSettings.flatMap((w) =>
|
|
2113
|
+
tailSettings.flatMap((k) =>
|
|
2114
|
+
castSettings.map((f) => ({
|
|
2115
|
+
...l,
|
|
2116
|
+
...c,
|
|
2117
|
+
...w,
|
|
2118
|
+
...k,
|
|
2119
|
+
...f,
|
|
2120
|
+
variations: [...l.variations, ...c.variations, ...w.variations, ...k.variations, ...f.variations],
|
|
2121
|
+
})),
|
|
2122
|
+
),
|
|
2123
|
+
),
|
|
2124
|
+
),
|
|
1923
2125
|
);
|
|
1924
2126
|
for (const liftSetting of liftSettings) {
|
|
1925
2127
|
let fn: Fn;
|
|
1926
2128
|
let discardsPassedResult = false;
|
|
2129
|
+
let guessesParamWidth = false;
|
|
1927
2130
|
let inferredSymbols = new Map<string, SymbolInfo>();
|
|
1928
2131
|
let orderLicensed: ReadonlySet<string> = new Set<string>();
|
|
1929
2132
|
try {
|
|
@@ -1932,7 +2135,17 @@ export function enumerateCandidates(
|
|
|
1932
2135
|
fn =
|
|
1933
2136
|
liftSetting.variations.length === 0
|
|
1934
2137
|
? base
|
|
1935
|
-
: liftStamped(
|
|
2138
|
+
: liftStamped(
|
|
2139
|
+
name,
|
|
2140
|
+
asm,
|
|
2141
|
+
target,
|
|
2142
|
+
prototypes,
|
|
2143
|
+
opts.asmData,
|
|
2144
|
+
symbolSetting.symbols,
|
|
2145
|
+
opts.contextGlobals,
|
|
2146
|
+
{ keepTailSlots: liftSetting.keepTailSlots },
|
|
2147
|
+
ownContext.ownContextProto,
|
|
2148
|
+
);
|
|
1936
2149
|
if (liftSetting.narrow && !narrowToSetupArgs(fn)) {
|
|
1937
2150
|
continue; // nothing to cut after all — the default lift's own candidates already cover it
|
|
1938
2151
|
}
|
|
@@ -1970,7 +2183,7 @@ export function enumerateCandidates(
|
|
|
1970
2183
|
// `/raw-globals` sibling has no map and so no name to reach. AFTER the two readings, and
|
|
1971
2184
|
// the staleness that leaves them is priced in pipeline.ts's copy of this note.
|
|
1972
2185
|
if (symbolSetting.symbols) {
|
|
1973
|
-
OFFSET_NAME_PASS.run(fn, symbolSetting.symbols, target.
|
|
2186
|
+
OFFSET_NAME_PASS.run(fn, symbolSetting.symbols, stampBehaviorsFor(target, opts.contextGlobals));
|
|
1974
2187
|
}
|
|
1975
2188
|
for (const [n, si] of [...inferredSymbols]) {
|
|
1976
2189
|
if (baseOpts.symbols?.has(n) === true || !sameDerivedShape(declSymbols.get(n), si)) {
|
|
@@ -1992,7 +2205,16 @@ export function enumerateCandidates(
|
|
|
1992
2205
|
},
|
|
1993
2206
|
},
|
|
1994
2207
|
prototypes[name],
|
|
1995
|
-
{
|
|
2208
|
+
{
|
|
2209
|
+
shortCircuit: { foldTreeOwned: liftSetting.connective },
|
|
2210
|
+
paramWidth: {
|
|
2211
|
+
extendedReaders: liftSetting.extendedReaders,
|
|
2212
|
+
onUndeclaredWidth: () => {
|
|
2213
|
+
guessesParamWidth = true;
|
|
2214
|
+
},
|
|
2215
|
+
},
|
|
2216
|
+
displaced: { keepStores: liftSetting.keepStores },
|
|
2217
|
+
},
|
|
1996
2218
|
);
|
|
1997
2219
|
} catch (e) {
|
|
1998
2220
|
// THE DEFAULT CARRIES NO VARIATION, by construction: every lift variation appends one, so
|
|
@@ -2049,6 +2271,8 @@ export function enumerateCandidates(
|
|
|
2049
2271
|
// `droppedDefault` is the DEFAULT pass's drops. Each shared-tail pass reads it and keeps its
|
|
2050
2272
|
// own drops in a copy, so one pass's structuring failure never removes the other's candidate.
|
|
2051
2273
|
const droppedDefault = new Set<string>();
|
|
2274
|
+
// What `assertDefaultAccepts` learned about this fn, shared by every setting structured from it.
|
|
2275
|
+
let acceptsMemo = new DefaultAcceptsMemo(fn);
|
|
2052
2276
|
for (const pass of ['default', 'follow', 'sink'] as const) {
|
|
2053
2277
|
if (pass === 'follow' && !hasDivergentSharedRet(fn)) {
|
|
2054
2278
|
continue;
|
|
@@ -2057,6 +2281,8 @@ export function enumerateCandidates(
|
|
|
2057
2281
|
let sunk: boolean;
|
|
2058
2282
|
try {
|
|
2059
2283
|
sunk = sinkStoreTails(fn);
|
|
2284
|
+
// The sink rewrites fn in place, so nothing the memo learned before it holds after it.
|
|
2285
|
+
acceptsMemo = new DefaultAcceptsMemo(fn);
|
|
2060
2286
|
if (sunk) {
|
|
2061
2287
|
verify(fn);
|
|
2062
2288
|
}
|
|
@@ -2120,25 +2346,33 @@ export function enumerateCandidates(
|
|
|
2120
2346
|
// recovered function without re-lifting.
|
|
2121
2347
|
let sfn: SFn;
|
|
2122
2348
|
try {
|
|
2123
|
-
sfn = structureChecked(
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2349
|
+
sfn = structureChecked(
|
|
2350
|
+
fn,
|
|
2351
|
+
{
|
|
2352
|
+
...symbolSettingOpts,
|
|
2353
|
+
...(inferredSymbols.size ? { inferredSymbols } : {}),
|
|
2354
|
+
...(orderLicensed.size ? { orderLicensedGlobals: orderLicensed } : {}),
|
|
2355
|
+
preserveDivergentBranchSense: s.sense,
|
|
2356
|
+
negateJoinedBranchSense: s.join ? !defSense : defSense,
|
|
2357
|
+
...(s.flipSites ? { branchSenseFlipSites: s.flipSites } : {}),
|
|
2358
|
+
anchorConstCopies: s.anchor,
|
|
2359
|
+
anchorLoopEntryConsts: s.entry,
|
|
2360
|
+
spellBitfieldMembers: s.bitfields,
|
|
2361
|
+
spellPtrMemberElements: s.ptrElems,
|
|
2362
|
+
spellDeclaredSubscripts: s.declRank,
|
|
2363
|
+
...STRUCTURE_VARIATIONS.reduce(
|
|
2364
|
+
(acc, variation) => ({ ...acc, ...variation.options(s[variation.flag]) }),
|
|
2365
|
+
{},
|
|
2366
|
+
),
|
|
2367
|
+
...(alternative ? { followEarlyReturns: true } : {}),
|
|
2368
|
+
},
|
|
2369
|
+
undefined,
|
|
2370
|
+
acceptsMemo,
|
|
2371
|
+
);
|
|
2141
2372
|
} catch (e) {
|
|
2373
|
+
if (e instanceof DefaultAcceptsMemoMisuse) {
|
|
2374
|
+
throw e; // a memo wired to the wrong fn is a defect here, never a dropped variation
|
|
2375
|
+
}
|
|
2142
2376
|
if (abortsRow([...liftVariations, ...symbolSetting.variations], s)) {
|
|
2143
2377
|
throw e; // the default lift's default setting keeps its behavior: a failure aborts the row
|
|
2144
2378
|
}
|
|
@@ -2214,6 +2448,8 @@ export function enumerateCandidates(
|
|
|
2214
2448
|
assertLocalsWritten(made);
|
|
2215
2449
|
assertPostIncrUnshared(made);
|
|
2216
2450
|
assertNoOrphanedLocals(sfn, made);
|
|
2451
|
+
assertLocalStorageStaysInteger(sfn, made);
|
|
2452
|
+
assertFrameUnionsViewed(made);
|
|
2217
2453
|
// A backend refusal on this REWRITTEN tree is not a refusal of the row's own
|
|
2218
2454
|
// tree, so it never becomes the row's stated cause: `TreeSources.emit` is dropped
|
|
2219
2455
|
// here and only the call over the row's own tree above records one.
|
|
@@ -2274,10 +2510,17 @@ export function enumerateCandidates(
|
|
|
2274
2510
|
// whichever route the enumeration happened to walk first.
|
|
2275
2511
|
if (sp.matchOnly === undefined) {
|
|
2276
2512
|
delete dup.matchOnly;
|
|
2513
|
+
delete dup.signedUnsignedCompare;
|
|
2277
2514
|
}
|
|
2278
2515
|
if (!discardsPassedResult) {
|
|
2279
2516
|
delete dup.discardsPassedResult;
|
|
2280
2517
|
}
|
|
2518
|
+
if (!guessesParamWidth) {
|
|
2519
|
+
delete dup.guessesParamWidth;
|
|
2520
|
+
}
|
|
2521
|
+
if (!liftSetting.keepStores) {
|
|
2522
|
+
delete dup.farStoreCast;
|
|
2523
|
+
}
|
|
2281
2524
|
continue;
|
|
2282
2525
|
}
|
|
2283
2526
|
const variations: readonly Variation[] = [
|
|
@@ -2299,8 +2542,11 @@ export function enumerateCandidates(
|
|
|
2299
2542
|
preference: symbolIndex,
|
|
2300
2543
|
...(sp.symbolRefs ? { symbolRefs: sp.symbolRefs } : {}),
|
|
2301
2544
|
...(sp.deviceVolatile ? { deviceVolatile: sp.deviceVolatile } : {}),
|
|
2302
|
-
...(sp.matchOnly ? { matchOnly:
|
|
2545
|
+
...(sp.matchOnly ? { matchOnly: true as const } : {}),
|
|
2546
|
+
...(sp.signedUnsignedCompare ? { signedUnsignedCompare: true as const } : {}),
|
|
2303
2547
|
...(discardsPassedResult ? { discardsPassedResult: true as const } : {}),
|
|
2548
|
+
...(guessesParamWidth ? { guessesParamWidth: true as const } : {}),
|
|
2549
|
+
...(liftSetting.keepStores ? { farStoreCast: true as const } : {}),
|
|
2304
2550
|
};
|
|
2305
2551
|
seen.set(source, made);
|
|
2306
2552
|
out.push(made);
|
|
@@ -2440,7 +2686,8 @@ function stillbornNote(stillborn: Stillborn, fan: number): string {
|
|
|
2440
2686
|
*
|
|
2441
2687
|
* A DISCARDED PASSED RESULT next (`Candidate.discardsPassedResult`): equal bytes refute neither
|
|
2442
2688
|
* reading, so the one that drops a value the callee may read wins only where it scores strictly
|
|
2443
|
-
* better.
|
|
2689
|
+
* better. A GUESSED PARAMETER WIDTH (`Candidate.guessesParamWidth`) likewise: a narrow declaration
|
|
2690
|
+
* no prototype supplied changes its callers' bytes, which no score here reads.
|
|
2444
2691
|
*
|
|
2445
2692
|
* PREFERENCE next: a named symbol-map spelling beats its `/raw-globals` sibling at equal bytes.
|
|
2446
2693
|
*
|
|
@@ -2463,6 +2710,11 @@ function stillbornNote(stillborn: Stillborn, fan: number): string {
|
|
|
2463
2710
|
* below does the same for `/narrow-decl`, which drops one cast per local it narrows and so wins
|
|
2464
2711
|
* every tie against the candidate it is derived onto.
|
|
2465
2712
|
*
|
|
2713
|
+
* A FAR STORE SPELLED THROUGH A CAST (`Candidate.farStoreCast`) next: where `/far-cast` ties the
|
|
2714
|
+
* member spelling, the compiler built the member's object too, and the member names the field. Its
|
|
2715
|
+
* `(u8 *)` base is no scalar cast and its line count is no higher, so neither term below would
|
|
2716
|
+
* prefer the member on its own.
|
|
2717
|
+
*
|
|
2466
2718
|
* CAST COUNT next, and only WITHIN a preference. A wrong signedness pin is what manufactures casts —
|
|
2467
2719
|
* the C backend has to cast a shift operand back to the signedness the machine op needs, so
|
|
2468
2720
|
* pinning `u32` on a genuinely-signed parameter buys `s32 f(u32 a0) { return (s32)a0 >> a1; }`
|
|
@@ -2480,12 +2732,11 @@ function stillbornNote(stillborn: Stillborn, fan: number): string {
|
|
|
2480
2732
|
* result deterministic. Spelled explicitly rather than leaning on Array#sort's stability, which
|
|
2481
2733
|
* would make each preference an accident of two unrelated decisions.
|
|
2482
2734
|
*
|
|
2483
|
-
* WHAT NO TERM HERE WEIGHS: a comparison's rendered SIGNEDNESS.
|
|
2484
|
-
*
|
|
2485
|
-
*
|
|
2486
|
-
*
|
|
2487
|
-
*
|
|
2488
|
-
* comparator carries; `deviceVolatile` is the shape such a term would take. */
|
|
2735
|
+
* WHAT NO TERM HERE WEIGHS: a comparison's rendered SIGNEDNESS. It needs none: every tree a
|
|
2736
|
+
* source is emitted from is judged for an unsigned compare that renders signed
|
|
2737
|
+
* (`Candidate.signedUnsignedCompare`), whichever variation spelled it so, and such a source is
|
|
2738
|
+
* `matchOnly` — on a nonmatch it is withheld before this comparator sees it, whatever it scores,
|
|
2739
|
+
* and on a match both spellings are the program. */
|
|
2489
2740
|
export function compareScored<S extends { score: number }>(
|
|
2490
2741
|
a: Candidate & { score: S; order: number },
|
|
2491
2742
|
b: Candidate & { score: S; order: number },
|
|
@@ -2493,8 +2744,10 @@ export function compareScored<S extends { score: number }>(
|
|
|
2493
2744
|
return (
|
|
2494
2745
|
a.score.score - b.score.score ||
|
|
2495
2746
|
(a.discardsPassedResult ? 1 : 0) - (b.discardsPassedResult ? 1 : 0) ||
|
|
2747
|
+
(a.guessesParamWidth ? 1 : 0) - (b.guessesParamWidth ? 1 : 0) ||
|
|
2496
2748
|
a.preference - b.preference ||
|
|
2497
2749
|
(b.deviceVolatile ?? 0) - (a.deviceVolatile ?? 0) ||
|
|
2750
|
+
(a.farStoreCast ? 1 : 0) - (b.farStoreCast ? 1 : 0) ||
|
|
2498
2751
|
castCount(a.source) - castCount(b.source) ||
|
|
2499
2752
|
lineCount(a.source) - lineCount(b.source) ||
|
|
2500
2753
|
a.order - b.order
|