@asmlift/core 0.9.0 → 0.11.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 +2 -0
- package/src/backend/cfamily.ts +197 -61
- package/src/backend/cpp.ts +3 -1
- package/src/backend/lines.ts +10 -0
- package/src/backend/pascal.ts +47 -16
- package/src/cdecl/parse.ts +21 -0
- package/src/contracts.ts +246 -36
- package/src/declare.ts +69 -20
- package/src/frontend/arm-body.ts +155 -0
- package/src/frontend/asmdata.ts +9 -2
- package/src/frontend/call-plan.ts +1117 -61
- package/src/frontend/callee-body.ts +370 -0
- package/src/frontend/dead-reads.ts +628 -0
- package/src/frontend/device-pins.ts +33 -4
- package/src/frontend/emit.ts +4 -3
- package/src/frontend/fpu.ts +224 -17
- package/src/frontend/frame-objects.ts +1689 -206
- package/src/frontend/frontend.ts +84 -3
- package/src/frontend/high-half.ts +53 -4
- package/src/frontend/jump-table.ts +545 -0
- package/src/frontend/local-object.ts +158 -32
- package/src/frontend/mips.ts +2296 -217
- package/src/frontend/own-struct-return.ts +500 -0
- package/src/frontend/ppc.ts +2748 -245
- package/src/frontend/register-struct-call.ts +399 -0
- package/src/frontend/reloc-symbol.ts +22 -7
- package/src/frontend/ssa.ts +1721 -43
- package/src/frontend/stackargs.ts +199 -72
- package/src/frontend/thumb-entry.ts +340 -0
- package/src/frontend/thumb.ts +2400 -325
- package/src/ir/alias.ts +65 -2
- package/src/ir/bits.ts +1699 -4
- package/src/ir/core.ts +254 -9
- package/src/ir/discipline.ts +86 -21
- package/src/ir/entry.ts +157 -0
- package/src/{structure → ir}/loops.ts +6 -2
- package/src/ir/opcodes.ts +137 -9
- package/src/ir/parse.ts +1 -0
- package/src/ir/simplify.ts +61 -10
- package/src/ir/types.ts +86 -2
- package/src/ir/verify.ts +96 -11
- package/src/l3/argbase.ts +2 -0
- package/src/l3/ast.ts +582 -18
- package/src/l3/basecse.ts +68 -15
- package/src/l3/caseendgoto.ts +223 -0
- package/src/l3/coalesce.ts +42 -14
- package/src/l3/constinit.ts +410 -0
- package/src/l3/dce.ts +53 -9
- 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 +33 -34
- package/src/l3/ptrfield.ts +123 -8
- package/src/l3/regspell.ts +11 -4
- package/src/l3/reindex.ts +295 -99
- package/src/l3/scopebase.ts +2 -0
- package/src/l3/storage.ts +98 -1
- package/src/l3/symbol-refs.ts +84 -6
- package/src/l3/tailmerge.ts +6 -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 +1039 -38
- package/src/proto-context.ts +1039 -45
- package/src/proto.ts +555 -40
- package/src/raise/aggmembers.ts +290 -0
- package/src/raise/arrays.ts +5 -1
- package/src/raise/const.ts +25 -0
- package/src/raise/declared-volatile.ts +207 -31
- package/src/raise/divpow2.ts +192 -0
- 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 +422 -36
- package/src/raise/pairparams.ts +33 -0
- package/src/raise/paramwidth.ts +196 -6
- package/src/raise/pre-recovery.ts +107 -21
- package/src/raise/pre-structure.ts +73 -0
- package/src/raise/recover.ts +12 -5
- package/src/raise/reroll.ts +674 -0
- package/src/raise/shortcircuit.ts +217 -34
- 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/unpre.ts +594 -0
- package/src/raise/unthread.ts +400 -0
- package/src/rank-declare.ts +118 -12
- package/src/rank-variations.ts +186 -35
- package/src/rank.ts +536 -68
- package/src/runtime-helpers.ts +140 -22
- package/src/structure/analysis.ts +217 -8
- package/src/structure/arith-ops.ts +23 -0
- package/src/structure/cross-jumped-tail.ts +216 -0
- package/src/structure/globalaccess.ts +157 -10
- 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 +6192 -1213
- package/src/structure/switch-recover.ts +583 -120
- package/src/structure/unfolded-branch-constant.ts +90 -0
- package/src/symbols.ts +58 -15
- package/src/target.ts +1028 -54
- package/src/trace.ts +49 -17
- package/src/variation-definitions.ts +333 -20
- package/src/variation-gates.ts +4 -0
- package/src/variation-tokens.ts +68 -3
package/src/rank.ts
CHANGED
|
@@ -20,7 +20,11 @@
|
|
|
20
20
|
// names, and which of those names a declaration must refuse to claim.
|
|
21
21
|
import { cBackend } from './backend/c';
|
|
22
22
|
import {
|
|
23
|
+
assertCaseGotosNarrow,
|
|
23
24
|
assertDerefsTyped,
|
|
25
|
+
assertEffectsPreserved,
|
|
26
|
+
assertFrameUnionsViewed,
|
|
27
|
+
assertLocalStorageStaysInteger,
|
|
24
28
|
assertLocalsWritten,
|
|
25
29
|
assertNoOrphanedLocals,
|
|
26
30
|
assertPlacementSurvives,
|
|
@@ -28,6 +32,7 @@ import {
|
|
|
28
32
|
assertResolved,
|
|
29
33
|
} from './contracts';
|
|
30
34
|
import type { AsmData } from './frontend/asmdata';
|
|
35
|
+
import type { CalleeBodies } from './frontend/callee-body';
|
|
31
36
|
import { hasSetupArgsNarrowing, narrowToSetupArgs, setupArgsDiscardsPassedResult } from './frontend/ssa';
|
|
32
37
|
import { Fn, Value, defOpMap } from './ir/core';
|
|
33
38
|
import { T } from './ir/types';
|
|
@@ -53,14 +58,24 @@ import { reindexWalks } from './l3/reindex';
|
|
|
53
58
|
import { hoistScopedBases } from './l3/scopebase';
|
|
54
59
|
import { sinkInitsToFirstUse } from './l3/sinkinit';
|
|
55
60
|
import type { SymbolRef } from './l3/symbol-refs';
|
|
61
|
+
import { signedOverUnsignedCompare } from './l3/typing';
|
|
56
62
|
import { type UnreduceResult, unreduceAccumulators } from './l3/unreduce';
|
|
57
63
|
import { deviceVolatileClaims, volatilePtrLocals, volatileSubsetCandidates } from './l3/volatileptr';
|
|
58
64
|
import { volatileValueLocals } from './l3/volatileval';
|
|
59
65
|
import { volatileDeviceStores } from './l3/volstore';
|
|
60
66
|
import { zeroSubNegates } from './l3/zerosub';
|
|
61
67
|
import { RewritePattern } from './pattern/engine';
|
|
62
|
-
import {
|
|
63
|
-
|
|
68
|
+
import {
|
|
69
|
+
applyIdiomPatterns,
|
|
70
|
+
liftStamped,
|
|
71
|
+
offsetNameBehaviorsFor,
|
|
72
|
+
raiseRecovered,
|
|
73
|
+
sourceLineCount,
|
|
74
|
+
structureChecked,
|
|
75
|
+
structureReturnTailReading,
|
|
76
|
+
} from './pipeline';
|
|
77
|
+
import { type FnProto, type Prototypes, declaresVoidReturn, prototypesFromSymbols } from './proto';
|
|
78
|
+
import { type ContextGlobals, splitOwnContext } from './proto-context';
|
|
64
79
|
import { inferGlobalArrays, orderLicensedGlobals, sameDerivedShape } from './raise/globalshape';
|
|
65
80
|
import { OFFSET_NAME_PASS } from './raise/offsetnames';
|
|
66
81
|
import { runPreRecovery } from './raise/pre-recovery';
|
|
@@ -70,6 +85,7 @@ import {
|
|
|
70
85
|
type RefusedDeclarationReason,
|
|
71
86
|
bareGlobalAccessFacts,
|
|
72
87
|
bareGlobalSymbols,
|
|
88
|
+
initAddressSymbols,
|
|
73
89
|
makeRefCollector,
|
|
74
90
|
} from './rank-declare';
|
|
75
91
|
import {
|
|
@@ -82,14 +98,22 @@ import {
|
|
|
82
98
|
SIGNEDNESS,
|
|
83
99
|
STACKED_SUBSETS,
|
|
84
100
|
STRUCTURE_VARIATIONS,
|
|
101
|
+
StackedMemberThrew,
|
|
85
102
|
type StructureVariation,
|
|
86
103
|
UNFOLDED_HOISTS,
|
|
87
104
|
applyStacked,
|
|
88
105
|
createdLocals,
|
|
106
|
+
firstFired,
|
|
89
107
|
sameBases,
|
|
90
108
|
} from './rank-variations';
|
|
91
109
|
import { type Stillborn, stillbornVerdict } from './stillborn';
|
|
92
|
-
import {
|
|
110
|
+
import {
|
|
111
|
+
DefaultAcceptsMemo,
|
|
112
|
+
DefaultAcceptsMemoMisuse,
|
|
113
|
+
FOR_BOUND_GATES,
|
|
114
|
+
StructureError,
|
|
115
|
+
hasDivergentSharedRet,
|
|
116
|
+
} from './structure/structure';
|
|
93
117
|
import {
|
|
94
118
|
type SymbolInfo,
|
|
95
119
|
type SymbolMap,
|
|
@@ -159,6 +183,12 @@ export interface EnumerateOptions {
|
|
|
159
183
|
asmData?: AsmData;
|
|
160
184
|
/** address→symbol map (symbols.ts) — same contract as DecompileOptions.symbols */
|
|
161
185
|
symbols?: SymbolMap;
|
|
186
|
+
/** same contract as DecompileOptions.contextGlobals */
|
|
187
|
+
contextGlobals?: ContextGlobals;
|
|
188
|
+
/** same contract as DecompileOptions.ownContextProto */
|
|
189
|
+
ownContextProto?: FnProto;
|
|
190
|
+
/** same contract as DecompileOptions.calleeBodies */
|
|
191
|
+
calleeBodies?: CalleeBodies;
|
|
162
192
|
/** Called when a respell variation THROWS or fails a boundary contract, so the failure is visible
|
|
163
193
|
* instead of the candidate silently not existing. Enumeration continues either way — the default
|
|
164
194
|
* candidate is unaffected — but a variation that never fires because it always throws is a defect,
|
|
@@ -255,16 +285,41 @@ export interface Candidate {
|
|
|
255
285
|
* • `l3/intcell.ts` spells a global as the integer a project may have declared it, where a
|
|
256
286
|
* pointer declaration scales the offset it adds, and no declaration is in the asm.
|
|
257
287
|
*
|
|
258
|
-
*
|
|
259
|
-
*
|
|
288
|
+
* A third obligation is judged on the finished tree rather than declared by a variation
|
|
289
|
+
* (`signedUnsignedCompare` below):
|
|
290
|
+
*
|
|
291
|
+
* • `/signed-cmp`, or a respell over its tree, spells an unsigned compare over signed-rendering
|
|
292
|
+
* operands, which compiles to a signed compare unless the operands are non-negative, and the
|
|
293
|
+
* compiler's proof of that is not in the asm.
|
|
294
|
+
*
|
|
295
|
+
* What settles all three instead is the object: a candidate that assembles to the target's own
|
|
296
|
+
* bytes IS the program, whatever a gate could have proved. So the spelling is offered, scored, and then
|
|
260
297
|
* either wins on proof or is WITHHELD — never shown as a best-effort answer. Both ranking drivers
|
|
261
298
|
* ask `withheldReason`, so neither can publish what the other would not. */
|
|
262
299
|
matchOnly?: true;
|
|
300
|
+
/** The tree this source was emitted from spells some unsigned compare so that C evaluates it
|
|
301
|
+
* signed (l3/typing.ts `signedOverUnsignedCompare`, judged on the finished tree), which makes it
|
|
302
|
+
* `matchOnly` and names the fact it owes. Set for whichever variation got it there: emission
|
|
303
|
+
* under `/signed-cmp`, or a respell that substituted into a compare emission had pinned. It is
|
|
304
|
+
* the only place this obligation is decided: a pass that rebuilds a compare spreads the node to
|
|
305
|
+
* keep its `unsignedCmp` evidence (l3/regspell.ts), a merge that keeps one of two equal nodes
|
|
306
|
+
* keeps the evidence of both (l3/ast.ts `unionEvidence`), or drops it only where its own gate proved
|
|
307
|
+
* the new spelling compares the same values (l3/initfirst.ts `swapSide`, l3/reindex.ts). */
|
|
308
|
+
signedUnsignedCompare?: true;
|
|
263
309
|
/** This candidate's lift is `/setup-args` and drops an earlier callee's result that a call through
|
|
264
310
|
* a register is passed (frontend/ssa.ts `setupArgsDiscardsPassedResult`). It loses every score
|
|
265
311
|
* tie (compareScored): a tie refutes neither reading, and a shorter spelling must not win by
|
|
266
312
|
* dropping a value the callee may read. */
|
|
267
313
|
discardsPassedResult?: true;
|
|
314
|
+
/** This candidate's lift is `/narrow-param` and declares a parameter narrow that no prototype
|
|
315
|
+
* declares (raise/pre-recovery.ts `onUndeclaredWidth`). It loses every score tie
|
|
316
|
+
* (compareScored): a tie refutes neither declaration, and the narrow one changes the bytes of
|
|
317
|
+
* every prototyped caller, which this function's score cannot see. */
|
|
318
|
+
guessesParamWidth?: true;
|
|
319
|
+
/** This candidate's lift is `/far-cast` and spells a far member store through a cast of its
|
|
320
|
+
* base. It loses every score tie (compareScored): a tie means the compiler built the member's
|
|
321
|
+
* object too, and the member is the spelling that names the field. */
|
|
322
|
+
farStoreCast?: true;
|
|
268
323
|
}
|
|
269
324
|
/** A candidate paired with its score `S` (the injected scorer's result shape — must carry `.score`). */
|
|
270
325
|
export interface Scored<S> extends Candidate {
|
|
@@ -356,7 +411,7 @@ export interface RankedResult<S> {
|
|
|
356
411
|
candidates: Scored<S>[]; // sorted best (lowest) first
|
|
357
412
|
/** candidates whose scoreFn threw — empty when every spelling built */
|
|
358
413
|
dropped: DroppedCandidate[];
|
|
359
|
-
/** candidates withheld for want of a byte-exact proof — empty unless a `matchOnly`
|
|
414
|
+
/** candidates withheld for want of a byte-exact proof — empty unless a `matchOnly` candidate scored a nonmatch */
|
|
360
415
|
withheld: WithheldCandidate[];
|
|
361
416
|
}
|
|
362
417
|
|
|
@@ -371,6 +426,9 @@ export function withheldReason<S extends { score: number }>(c: Candidate, score:
|
|
|
371
426
|
return null;
|
|
372
427
|
}
|
|
373
428
|
const facts = c.variations.flatMap((v) => PROOF_OBLIGATIONS.get(v) ?? []);
|
|
429
|
+
if (c.signedUnsignedCompare === true) {
|
|
430
|
+
facts.push(SIGNED_COMPARE_OBLIGATION);
|
|
431
|
+
}
|
|
374
432
|
const fact = facts.length > 0 ? facts.join(' and on ') : 'a fact no gate over the C can settle';
|
|
375
433
|
return `this spelling rests on ${fact}; only a byte-exact score proves it`;
|
|
376
434
|
}
|
|
@@ -381,6 +439,11 @@ const PROOF_OBLIGATIONS: ReadonlyMap<string, string> = new Map<VariationName, st
|
|
|
381
439
|
['int-cell', 'the project declaring the global an integer, which the assembly does not show'],
|
|
382
440
|
]);
|
|
383
441
|
|
|
442
|
+
/** The fact `Candidate.signedUnsignedCompare` rests on. Keyed by the flag, not by `/signed-cmp`:
|
|
443
|
+
* that name is on candidates that owe nothing (a compare over a `u32` operand) and missing from
|
|
444
|
+
* respells that do. */
|
|
445
|
+
const SIGNED_COMPARE_OBLIGATION = "the compiler having proved every unsigned compare's operands non-negative";
|
|
446
|
+
|
|
384
447
|
/** What a respell variation hands `respell`: its tree, or — when the variation cannot establish the
|
|
385
448
|
* candidate's semantics from inside the pass — the tree paired with that fact. `undefined`/`null`
|
|
386
449
|
* is a decline. */
|
|
@@ -444,6 +507,29 @@ interface TreeSource {
|
|
|
444
507
|
deviceVolatile?: number;
|
|
445
508
|
/** see `Candidate.matchOnly` — set by a variation that cannot establish its own semantics */
|
|
446
509
|
matchOnly?: true;
|
|
510
|
+
/** see `Candidate.signedUnsignedCompare` */
|
|
511
|
+
signedUnsignedCompare?: true;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/** Did `bound-plus-one-counter-wraps` CAUSE this structuring's decline? The rule fires from every
|
|
515
|
+
* attempt `forPlanAt` makes, committed or not, and on loops the thrown refusal never reaches
|
|
516
|
+
* (`synthetic:sw_gotocase_inner:ido7.1` fires it and declines on its switches' cases), so its firing
|
|
517
|
+
* alone says nothing about the decline. The question is asked the one way that answers it: the
|
|
518
|
+
* same structuring with that rule off. Where that one structures, the refusal was the rule's, and
|
|
519
|
+
* the other pin, whose counter the rule admits, can take the loop; where it still throws, the
|
|
520
|
+
* decline is the function's and the row aborts as any other default decline does. The tree the
|
|
521
|
+
* ablated run builds is discarded — it is the `for` the rule refuses as wrong under this pin.
|
|
522
|
+
* Not holding every pinnable decline instead: that would let the signed pin past any structure
|
|
523
|
+
* refusal that reads a declared type, not only this one, and re-lift every declined row. */
|
|
524
|
+
function declinesOnCounterSign(fn: Fn, opts: Parameters<typeof structureChecked>[1]): boolean {
|
|
525
|
+
try {
|
|
526
|
+
structureChecked(fn, opts, {
|
|
527
|
+
forBoundGates: FOR_BOUND_GATES.filter((g) => g.id !== 'bound-plus-one-counter-wraps'),
|
|
528
|
+
});
|
|
529
|
+
return true;
|
|
530
|
+
} catch {
|
|
531
|
+
return false;
|
|
532
|
+
}
|
|
447
533
|
}
|
|
448
534
|
|
|
449
535
|
/** Emit the DISTINCT type/branch-sense candidates for `name` — PURE, no scoring.
|
|
@@ -469,16 +555,31 @@ export function enumerateCandidates(
|
|
|
469
555
|
/** The last refusal from a backend asked to spell a tree — what the empty-enumeration check
|
|
470
556
|
* below reports, so "this backend can spell nothing here" names its reason. */
|
|
471
557
|
let lastEmitError: unknown = null;
|
|
558
|
+
/** THE UNSIGNED PIN'S DECLINE, HELD FOR THE SIGNED ONE. The default candidate's structuring aborts
|
|
559
|
+
* the row (`abortsRow`) on the premise that a structure refusal does not depend on the pin. One
|
|
560
|
+
* does: `forPlanAt`'s `bound-plus-one-counter-wraps` reads the counter's declared type, so IDO's
|
|
561
|
+
* counted loop (`synthetic:sw_gotocase2:ido7.1`) takes its `for` under the `s32` pin and not the
|
|
562
|
+
* `u32` one, and without the `for` the switch's loop exit cannot be spelled at all. The decline is
|
|
563
|
+
* held only where that rule CAUSED it (`declinesOnCounterSign`), and then the rest of the unsigned
|
|
564
|
+
* pin is skipped: its other settings would run behind a declined default, which `abortsRow` exists
|
|
565
|
+
* to forbid. The signed pin runs as it would have; a row that then produces no candidate declines
|
|
566
|
+
* with the held error. Nothing the signed pin builds is new: it is the same pass, under its own
|
|
567
|
+
* default setting and its own gates. */
|
|
568
|
+
let heldPinDecline: unknown = undefined;
|
|
472
569
|
// Same merge as `decompile`: the project's DWARF signatures fill in what the caller did not
|
|
473
570
|
// state, so both the annotate pass and the ranked candidates reason about one prototype table.
|
|
474
|
-
const
|
|
571
|
+
const ownContext = splitOwnContext(opts.prototypes, name, opts.ownContextProto);
|
|
572
|
+
const prototypes = prototypesFromSymbols(opts.symbols, ownContext.prototypes ?? {});
|
|
475
573
|
const baseOpts = {
|
|
476
574
|
...structureOptionsFor(target, declaresVoidReturn(prototypes[name]), prototypes),
|
|
477
575
|
// See the same line in pipeline.ts: a backend that cannot print switch fall-through must not
|
|
478
576
|
// be handed a tree carrying one, because its refusal costs the whole candidate (and, when
|
|
479
577
|
// every candidate carries it, the whole row).
|
|
480
578
|
spellSwitchFallthrough: backend.spellsSwitchFallthrough,
|
|
579
|
+
spellSwitchBreak: backend.spellsSwitchBreak,
|
|
580
|
+
spellEarlyReturns: backend.spellsEarlyReturn,
|
|
481
581
|
...(opts.symbols ? { symbols: symbolsByName(opts.symbols) } : {}),
|
|
582
|
+
...(opts.contextGlobals ? { contextGlobals: opts.contextGlobals } : {}),
|
|
482
583
|
};
|
|
483
584
|
// Branch-sense is a differ-ranked VARIATION, the same class as param signedness: a divergent `if`
|
|
484
585
|
// can be spelled with either sense (`if (c) A else B` vs `if (!c) B else A`), and which one the
|
|
@@ -600,11 +701,23 @@ export function enumerateCandidates(
|
|
|
600
701
|
// it buys is that a lift-time change which splits them enumerates both settings rather than
|
|
601
702
|
// silently dropping one, the failure nothing reports.
|
|
602
703
|
let sharedLiftTreeOwned = false;
|
|
704
|
+
let sharedLiftExtendedReaders = false;
|
|
705
|
+
let sharedLiftFoldedStore = false;
|
|
603
706
|
// Shared lift: recover ONCE with no signedness pin, to learn which entry params are
|
|
604
707
|
// pointers/aggregates so they are excluded from the signedness variation (see NO_PIN_KINDS). One
|
|
605
708
|
// extra lift+recover, no compile. (The shared lift deliberately stops after recoverTypes — it only reads the param KINDS, so
|
|
606
709
|
// the totality contract / return-sinking of the full spine are not run on it.)
|
|
607
|
-
const sharedLift = liftStamped(
|
|
710
|
+
const sharedLift = liftStamped(
|
|
711
|
+
name,
|
|
712
|
+
asm,
|
|
713
|
+
target,
|
|
714
|
+
prototypes,
|
|
715
|
+
opts.asmData,
|
|
716
|
+
opts.symbols,
|
|
717
|
+
opts.contextGlobals,
|
|
718
|
+
{ calleeBodies: opts.calleeBodies },
|
|
719
|
+
ownContext.ownContextProto,
|
|
720
|
+
);
|
|
608
721
|
// The ARRAY SHAPES the input assembly evidences (raise/globalshape.ts), for the DECLARATION
|
|
609
722
|
// half. Read off the shared lift's LIFTED form — before the fold below and the tower rewrite it —
|
|
610
723
|
// because the base-materialization order the derivation's licence reads does not survive them.
|
|
@@ -619,6 +732,16 @@ export function enumerateCandidates(
|
|
|
619
732
|
sharedLiftTreeOwned = true;
|
|
620
733
|
},
|
|
621
734
|
},
|
|
735
|
+
paramWidth: {
|
|
736
|
+
onExtendedReaders: () => {
|
|
737
|
+
sharedLiftExtendedReaders = true;
|
|
738
|
+
},
|
|
739
|
+
},
|
|
740
|
+
displaced: {
|
|
741
|
+
onFoldedStore: () => {
|
|
742
|
+
sharedLiftFoldedStore = true;
|
|
743
|
+
},
|
|
744
|
+
},
|
|
622
745
|
});
|
|
623
746
|
recoverTypes(sharedLift);
|
|
624
747
|
const ptrIdx = new Set<number>(
|
|
@@ -643,17 +766,47 @@ export function enumerateCandidates(
|
|
|
643
766
|
// shared lift's `onTreeOwned` hook has had its chance to fire. Called any earlier it would report a confident
|
|
644
767
|
// `false` for a function that owns a tree. As a `const` below that call, an early call is a TDZ
|
|
645
768
|
// ReferenceError instead — a wrong answer traded for a loud one.
|
|
646
|
-
|
|
769
|
+
//
|
|
770
|
+
// `/narrow-param`'s gate is read off the same lift the same way: the width pass reports a
|
|
771
|
+
// parameter the variation's lift would narrow (raise/pre-recovery.ts `ParamWidthOptions`). So is
|
|
772
|
+
// `/far-cast`'s: the displaced-access fold reports a store it moved into a struct.
|
|
773
|
+
const liftReportsIn = (
|
|
774
|
+
symbols: typeof opts.symbols,
|
|
775
|
+
): { treeOwned: boolean; extendedReaders: boolean; foldedStore: boolean } => {
|
|
647
776
|
if (symbols === opts.symbols) {
|
|
648
|
-
return
|
|
777
|
+
return {
|
|
778
|
+
treeOwned: sharedLiftTreeOwned,
|
|
779
|
+
extendedReaders: sharedLiftExtendedReaders,
|
|
780
|
+
foldedStore: sharedLiftFoldedStore,
|
|
781
|
+
};
|
|
649
782
|
}
|
|
650
|
-
const p = liftStamped(
|
|
783
|
+
const p = liftStamped(
|
|
784
|
+
name,
|
|
785
|
+
asm,
|
|
786
|
+
target,
|
|
787
|
+
prototypes,
|
|
788
|
+
opts.asmData,
|
|
789
|
+
symbols,
|
|
790
|
+
opts.contextGlobals,
|
|
791
|
+
{ calleeBodies: opts.calleeBodies },
|
|
792
|
+
ownContext.ownContextProto,
|
|
793
|
+
);
|
|
651
794
|
applyIdiomPatterns(p, target, opts.patterns);
|
|
652
|
-
|
|
795
|
+
const seen = { treeOwned: false, extendedReaders: false, foldedStore: false };
|
|
653
796
|
runPreRecovery(p, target, () => verify(p), prototypes[name], {
|
|
654
797
|
shortCircuit: {
|
|
655
798
|
onTreeOwned: () => {
|
|
656
|
-
seen = true;
|
|
799
|
+
seen.treeOwned = true;
|
|
800
|
+
},
|
|
801
|
+
},
|
|
802
|
+
paramWidth: {
|
|
803
|
+
onExtendedReaders: () => {
|
|
804
|
+
seen.extendedReaders = true;
|
|
805
|
+
},
|
|
806
|
+
},
|
|
807
|
+
displaced: {
|
|
808
|
+
onFoldedStore: () => {
|
|
809
|
+
seen.foldedStore = true;
|
|
657
810
|
},
|
|
658
811
|
},
|
|
659
812
|
});
|
|
@@ -916,7 +1069,11 @@ export function enumerateCandidates(
|
|
|
916
1069
|
// spelling needs one, and the declaration a candidate spelling `gTbl[i]` cannot compile
|
|
917
1070
|
// without), and the project map, which knows more than either.
|
|
918
1071
|
const mapSymbols = baseOpts.symbols;
|
|
919
|
-
const targetSymbols = new Map<string, SymbolInfo>([
|
|
1072
|
+
const targetSymbols = new Map<string, SymbolInfo>([
|
|
1073
|
+
...initAddressSymbols(sharedLift),
|
|
1074
|
+
...bareGlobalSymbols(sharedLift),
|
|
1075
|
+
...sharedLiftShapes,
|
|
1076
|
+
]);
|
|
920
1077
|
const declSymbols = new Map<string, SymbolInfo>([...targetSymbols, ...(mapSymbols ?? [])]);
|
|
921
1078
|
// The four per-enumeration constants named at the seam rather than captured across 60 lines of
|
|
922
1079
|
// closure (rank-declare.ts states why they belong on one object).
|
|
@@ -930,6 +1087,7 @@ export function enumerateCandidates(
|
|
|
930
1087
|
prototypes,
|
|
931
1088
|
target,
|
|
932
1089
|
refuse,
|
|
1090
|
+
own: Object.hasOwn(prototypes, name) ? prototypes[name] : ownContext.ownContextProto,
|
|
933
1091
|
});
|
|
934
1092
|
// THE RESPELL SET, as a function whose PARAMETER LIST is the invariant the tree skip below
|
|
935
1093
|
// rests on: every source here is a pure function of the structured tree and this call's own
|
|
@@ -953,6 +1111,14 @@ export function enumerateCandidates(
|
|
|
953
1111
|
// of `/volatile` — a candidate that did not fail and is still in the fan. The order
|
|
954
1112
|
// is the candidates' own (`[pf.name, ...sp.variations]`), so a reported name and an enumerated
|
|
955
1113
|
// candidate's variations name the same candidate the same way.
|
|
1114
|
+
//
|
|
1115
|
+
// THE COMPARE-SIGNEDNESS PROOF, asked of every tree a source is emitted from rather than of the
|
|
1116
|
+
// passes that build them. Emission pins each unsigned compare it lowers (structure.ts), but a
|
|
1117
|
+
// respell that substitutes into a compare — `/unmerge` putting an arm's `s16` global where the
|
|
1118
|
+
// pinned `u32` local stood, against an `s32` parameter — re-renders it signed. So the finished
|
|
1119
|
+
// tree is judged here, and a signed rendering of an `icmp_u*` is `matchOnly` whoever produced it.
|
|
1120
|
+
const cmpProof = (tree: SFn): { matchOnly?: true; signedUnsignedCompare?: true } =>
|
|
1121
|
+
signedOverUnsignedCompare(tree) ? { matchOnly: true, signedUnsignedCompare: true } : {};
|
|
956
1122
|
const respellTree = (sfn: SFn, preRespellVariations: readonly Variation[] = []): TreeSources => {
|
|
957
1123
|
// The walk→index respell variation (l3/reindex.ts) is a THIRD variation on the same footing as
|
|
958
1124
|
// signedness and branch sense: whether the source spelled `*p; p++` or `arr[i]` is
|
|
@@ -970,7 +1136,7 @@ export function enumerateCandidates(
|
|
|
970
1136
|
// Pascal backend loud-declines). Refusing EVERY tree is still loud — the empty-enumeration
|
|
971
1137
|
// check at the end raises the last refusal.
|
|
972
1138
|
try {
|
|
973
|
-
sources.push({ variations: [], source: backend.emit(sfn), ...refsOf(sfn), ...volOf(sfn) });
|
|
1139
|
+
sources.push({ variations: [], source: backend.emit(sfn), ...refsOf(sfn), ...volOf(sfn), ...cmpProof(sfn) });
|
|
974
1140
|
} catch (e) {
|
|
975
1141
|
reportThrow(preRespellVariations, e);
|
|
976
1142
|
return { sources, emit: { error: e } };
|
|
@@ -1050,6 +1216,7 @@ export function enumerateCandidates(
|
|
|
1050
1216
|
// `assertEffectsPreserved` is the fourth and is NOT here: it needs the L1 `fn`, and
|
|
1051
1217
|
// `respellTree`'s parameter list is the invariant the tree-dedup skip rests on (see its header).
|
|
1052
1218
|
// Widening it for a contract is a defensible change and an argued one — not a silent import.
|
|
1219
|
+
// The PRE-RESPELL loop below has `fn` in scope and no such invariant, so it runs it there.
|
|
1053
1220
|
// A respell variation returns its tree, or `{ sfn, needsProof }` when it cannot establish its own
|
|
1054
1221
|
// semantics from inside the pass (Candidate.matchOnly carries the argument).
|
|
1055
1222
|
const respell = (variations: readonly Variation[], make: () => RespellResult, alreadyShaped = false): void => {
|
|
@@ -1068,7 +1235,17 @@ export function enumerateCandidates(
|
|
|
1068
1235
|
assertLocalsWritten(alt);
|
|
1069
1236
|
assertPostIncrUnshared(alt);
|
|
1070
1237
|
assertNoOrphanedLocals(sfn, alt);
|
|
1071
|
-
|
|
1238
|
+
assertLocalStorageStaysInteger(sfn, alt);
|
|
1239
|
+
assertFrameUnionsViewed(alt);
|
|
1240
|
+
assertCaseGotosNarrow(alt);
|
|
1241
|
+
sources.push({
|
|
1242
|
+
variations,
|
|
1243
|
+
source: backend.emit(alt),
|
|
1244
|
+
...refsOf(alt),
|
|
1245
|
+
...volOf(alt),
|
|
1246
|
+
...proof,
|
|
1247
|
+
...cmpProof(alt),
|
|
1248
|
+
});
|
|
1072
1249
|
// STACKED variations, derived onto EVERY source (the POLICY note above carries the
|
|
1073
1250
|
// admission argument). Each is a statement-order/shape or declaration-width fact
|
|
1074
1251
|
// orthogonal to the respell variations outside the table; subsets compose in the fixed
|
|
@@ -1095,19 +1272,25 @@ export function enumerateCandidates(
|
|
|
1095
1272
|
// which moves only const or pure-read assigns and so cannot lift a read of a base local
|
|
1096
1273
|
// above its init.
|
|
1097
1274
|
const minted = createdLocals(sfn, alt);
|
|
1275
|
+
const firedSets = new Set<string>();
|
|
1098
1276
|
for (const subset of STACKED_SUBSETS) {
|
|
1099
1277
|
// ONE TRY PER SHAPE — a shape is its own candidate and fails as its own candidate.
|
|
1100
1278
|
// Sharing the respell variation's outer try would let a throw deriving one subset
|
|
1101
1279
|
// discard every later one, under that variation's name, which names no shape.
|
|
1102
|
-
|
|
1280
|
+
// A throw past applyStacked is reported under the fired set, the candidate's own name.
|
|
1281
|
+
let shapeVariations: readonly Variation[] = [];
|
|
1103
1282
|
try {
|
|
1104
1283
|
const shaped = applyStacked(subset, alt);
|
|
1105
|
-
if (shaped !== null) {
|
|
1284
|
+
if (shaped !== null && firstFired(firedSets, shaped.variations)) {
|
|
1285
|
+
shapeVariations = shaped.variations;
|
|
1106
1286
|
assertResolved(shaped.out);
|
|
1107
1287
|
assertDerefsTyped(shaped.out);
|
|
1108
1288
|
assertLocalsWritten(shaped.out);
|
|
1109
1289
|
assertPostIncrUnshared(shaped.out);
|
|
1110
1290
|
assertNoOrphanedLocals(alt, shaped.out);
|
|
1291
|
+
assertLocalStorageStaysInteger(alt, shaped.out);
|
|
1292
|
+
assertFrameUnionsViewed(shaped.out);
|
|
1293
|
+
assertCaseGotosNarrow(shaped.out);
|
|
1111
1294
|
assertPlacementSurvives(alt, shaped.out, minted);
|
|
1112
1295
|
sources.push({
|
|
1113
1296
|
variations: [...variations, ...shaped.variations],
|
|
@@ -1116,10 +1299,12 @@ export function enumerateCandidates(
|
|
|
1116
1299
|
...volOf(shaped.out),
|
|
1117
1300
|
// a shape derived from a proof-gated spelling inherits the requirement
|
|
1118
1301
|
...proof,
|
|
1302
|
+
...cmpProof(shaped.out),
|
|
1119
1303
|
});
|
|
1120
1304
|
}
|
|
1121
1305
|
} catch (e) {
|
|
1122
|
-
|
|
1306
|
+
const step = e instanceof StackedMemberThrew ? e.variations : shapeVariations;
|
|
1307
|
+
reportThrow([...preRespellVariations, ...variations, ...step], e);
|
|
1123
1308
|
}
|
|
1124
1309
|
}
|
|
1125
1310
|
}
|
|
@@ -1135,19 +1320,21 @@ export function enumerateCandidates(
|
|
|
1135
1320
|
// `/argbase` — name a call's argument bases before the call (l3/argbase.ts). A variation on the
|
|
1136
1321
|
// same footing as the others: the default inline spelling stays in the list, so the differ
|
|
1137
1322
|
// referees and this can never cost a match.
|
|
1323
|
+
const firedSets = new Set<string>();
|
|
1138
1324
|
for (const subset of STACKED_SUBSETS) {
|
|
1139
1325
|
// the truthful variations need the pass to RUN first, so this bypasses respell's
|
|
1140
1326
|
// variations-then-thunk shape: same try posture, variations from the fired members
|
|
1141
1327
|
try {
|
|
1142
1328
|
const shaped = applyStacked(subset, sfn);
|
|
1143
|
-
if (shaped !== null) {
|
|
1329
|
+
if (shaped !== null && firstFired(firedSets, shaped.variations)) {
|
|
1144
1330
|
// the ONE call whose variations already name shapes — say so, rather than making `respell`
|
|
1145
1331
|
// read them back out of the variations it was handed
|
|
1146
1332
|
respell(shaped.variations, () => shaped.out, true);
|
|
1147
1333
|
}
|
|
1148
1334
|
} catch (e) {
|
|
1149
|
-
//
|
|
1150
|
-
|
|
1335
|
+
// `respell` reports its own throws, so only a member can throw here
|
|
1336
|
+
const step = e instanceof StackedMemberThrew ? e.variations : subset.map((x) => x.name);
|
|
1337
|
+
reportThrow([...preRespellVariations, ...step], e);
|
|
1151
1338
|
}
|
|
1152
1339
|
}
|
|
1153
1340
|
respell(['argbase'], () => materializeArgBases(sfn));
|
|
@@ -1828,8 +2015,10 @@ export function enumerateCandidates(
|
|
|
1828
2015
|
// audit cannot bound. This is the setting's first lift, and every later one is the same lift
|
|
1829
2016
|
// again.
|
|
1830
2017
|
let treeOwnedFold: boolean;
|
|
2018
|
+
let extendedReaders: boolean;
|
|
2019
|
+
let foldedStore: boolean;
|
|
1831
2020
|
try {
|
|
1832
|
-
treeOwnedFold =
|
|
2021
|
+
({ treeOwned: treeOwnedFold, extendedReaders, foldedStore } = liftReportsIn(symbolSetting.symbols));
|
|
1833
2022
|
} catch (e) {
|
|
1834
2023
|
if (abortsRow(symbolSetting.variations)) {
|
|
1835
2024
|
throw e;
|
|
@@ -1848,11 +2037,27 @@ export function enumerateCandidates(
|
|
|
1848
2037
|
// Read off the pin's OWN call, per symbol-map setting — the `/raw-globals` setting lifts without
|
|
1849
2038
|
// the map and answers for itself, so no lift is governed by a fact measured on a different one.
|
|
1850
2039
|
let pinnable = false;
|
|
1851
|
-
for (const cand of SIGNEDNESS) {
|
|
2040
|
+
pins: for (const cand of SIGNEDNESS) {
|
|
1852
2041
|
if (cand.signed && !pinnable) {
|
|
1853
2042
|
break;
|
|
1854
2043
|
}
|
|
1855
|
-
|
|
2044
|
+
let tailSlot = false;
|
|
2045
|
+
const base = liftStamped(
|
|
2046
|
+
name,
|
|
2047
|
+
asm,
|
|
2048
|
+
target,
|
|
2049
|
+
prototypes,
|
|
2050
|
+
opts.asmData,
|
|
2051
|
+
symbolSetting.symbols,
|
|
2052
|
+
opts.contextGlobals,
|
|
2053
|
+
{
|
|
2054
|
+
onTailSlot: () => {
|
|
2055
|
+
tailSlot = true;
|
|
2056
|
+
},
|
|
2057
|
+
calleeBodies: opts.calleeBodies,
|
|
2058
|
+
},
|
|
2059
|
+
ownContext.ownContextProto,
|
|
2060
|
+
);
|
|
1856
2061
|
// `/setup-args` — pass a prototype-less callee only what the CALLING BLOCK set up; which of
|
|
1857
2062
|
// the two readings the source spelled is genuinely ambiguous, and frontend/ssa.ts
|
|
1858
2063
|
// narrowToSetupArgs carries the argument for why the differ is what settles it.
|
|
@@ -1918,12 +2123,66 @@ export function enumerateCandidates(
|
|
|
1918
2123
|
{ variations: ['setup-args'], narrow: true },
|
|
1919
2124
|
]
|
|
1920
2125
|
: [{ variations: [], narrow: false }];
|
|
2126
|
+
// `/narrow-param` — declare a parameter every reader of which is an extension at the widest
|
|
2127
|
+
// reader's width (raise/paramwidth.ts, EVERY READER AN EXTENSION). A lift variation for the
|
|
2128
|
+
// reason `/connective` is: the raise mutates in place, and the parameter's width is what
|
|
2129
|
+
// recovery and every later stage read. Enumerated only where this symbol-map setting's lift
|
|
2130
|
+
// reports such a parameter; crossed with the other two, and the dedup collapses what agrees.
|
|
2131
|
+
const paramSettings: { variations: readonly Variation[]; extendedReaders: boolean }[] = extendedReaders
|
|
2132
|
+
? [
|
|
2133
|
+
{ variations: [], extendedReaders: false },
|
|
2134
|
+
{ variations: ['narrow-param'], extendedReaders: true },
|
|
2135
|
+
]
|
|
2136
|
+
: [{ variations: [], extendedReaders: false }];
|
|
2137
|
+
// `/kept-slot` — keep the delay slot of a MIPS `b`/`j` whose slot ends a common tail both arms
|
|
2138
|
+
// spell, where the default lift retargets it to the copy before its target and merges the
|
|
2139
|
+
// arms (frontend/mips.ts `normaliseBranchLikely`: the two are one object, and which the source
|
|
2140
|
+
// wrote is the differ's to say). A LIFT variation because the rewrite is the CFG every later
|
|
2141
|
+
// stage reads. Enumerated only where this setting's default lift reports such a slot; crossed
|
|
2142
|
+
// with the others, and the dedup collapses what agrees. An IDO reading: GCC 2.7.2 and KMC
|
|
2143
|
+
// cross-jump a tail both arms spell before they fill a slot, so they leave no such tail.
|
|
2144
|
+
const tailSettings: { variations: readonly Variation[]; keepTailSlots: boolean }[] = tailSlot
|
|
2145
|
+
? [
|
|
2146
|
+
{ variations: [], keepTailSlots: false },
|
|
2147
|
+
{ variations: ['kept-slot'], keepTailSlots: true },
|
|
2148
|
+
]
|
|
2149
|
+
: [{ variations: [], keepTailSlots: false }];
|
|
2150
|
+
// `/far-cast` — keep each store the displaced-access fold would move into a struct on the
|
|
2151
|
+
// address it was reached through (raise/structs.ts `foldDisplacedAccesses`). On agbcc the
|
|
2152
|
+
// member store and the cast store are two objects (target.ts `farMemberStoreOrder`), and a
|
|
2153
|
+
// hoisted pointer the source held builds the cast's. Elsewhere a folded store stops aliasing
|
|
2154
|
+
// the loads beside it and a held pointer is spelled as members, which KMC, GCC 2.7.2 and mwcc
|
|
2155
|
+
// build into other objects. So which one the source wrote is the differ's to say, on every
|
|
2156
|
+
// target. A LIFT variation because the fold decides which struct the stores join, which
|
|
2157
|
+
// recovery and every later stage read. Enumerated only where this setting's lift folded a
|
|
2158
|
+
// store.
|
|
2159
|
+
const castSettings: { variations: readonly Variation[]; keepStores: boolean }[] =
|
|
2160
|
+
foldedStore && offeredOn(target, ['far-cast'])
|
|
2161
|
+
? [
|
|
2162
|
+
{ variations: [], keepStores: false },
|
|
2163
|
+
{ variations: ['far-cast'], keepStores: true },
|
|
2164
|
+
]
|
|
2165
|
+
: [{ variations: [], keepStores: false }];
|
|
1921
2166
|
const liftSettings = narrowSettings.flatMap((l) =>
|
|
1922
|
-
connectiveSettings.
|
|
2167
|
+
connectiveSettings.flatMap((c) =>
|
|
2168
|
+
paramSettings.flatMap((w) =>
|
|
2169
|
+
tailSettings.flatMap((k) =>
|
|
2170
|
+
castSettings.map((f) => ({
|
|
2171
|
+
...l,
|
|
2172
|
+
...c,
|
|
2173
|
+
...w,
|
|
2174
|
+
...k,
|
|
2175
|
+
...f,
|
|
2176
|
+
variations: [...l.variations, ...c.variations, ...w.variations, ...k.variations, ...f.variations],
|
|
2177
|
+
})),
|
|
2178
|
+
),
|
|
2179
|
+
),
|
|
2180
|
+
),
|
|
1923
2181
|
);
|
|
1924
2182
|
for (const liftSetting of liftSettings) {
|
|
1925
2183
|
let fn: Fn;
|
|
1926
2184
|
let discardsPassedResult = false;
|
|
2185
|
+
let guessesParamWidth = false;
|
|
1927
2186
|
let inferredSymbols = new Map<string, SymbolInfo>();
|
|
1928
2187
|
let orderLicensed: ReadonlySet<string> = new Set<string>();
|
|
1929
2188
|
try {
|
|
@@ -1932,7 +2191,17 @@ export function enumerateCandidates(
|
|
|
1932
2191
|
fn =
|
|
1933
2192
|
liftSetting.variations.length === 0
|
|
1934
2193
|
? base
|
|
1935
|
-
: liftStamped(
|
|
2194
|
+
: liftStamped(
|
|
2195
|
+
name,
|
|
2196
|
+
asm,
|
|
2197
|
+
target,
|
|
2198
|
+
prototypes,
|
|
2199
|
+
opts.asmData,
|
|
2200
|
+
symbolSetting.symbols,
|
|
2201
|
+
opts.contextGlobals,
|
|
2202
|
+
{ keepTailSlots: liftSetting.keepTailSlots, calleeBodies: opts.calleeBodies },
|
|
2203
|
+
ownContext.ownContextProto,
|
|
2204
|
+
);
|
|
1936
2205
|
if (liftSetting.narrow && !narrowToSetupArgs(fn)) {
|
|
1937
2206
|
continue; // nothing to cut after all — the default lift's own candidates already cover it
|
|
1938
2207
|
}
|
|
@@ -1968,9 +2237,10 @@ export function enumerateCandidates(
|
|
|
1968
2237
|
// …and the NAMES the map holds for the addresses this lift built by arithmetic off a
|
|
1969
2238
|
// named one (raise/offsetnames.ts). Per lift setting, off that setting's own map: the
|
|
1970
2239
|
// `/raw-globals` sibling has no map and so no name to reach. AFTER the two readings, and
|
|
1971
|
-
// the staleness that leaves them is priced in pipeline.ts's copy of this note
|
|
2240
|
+
// the staleness that leaves them is priced in pipeline.ts's copy of this note, which also
|
|
2241
|
+
// states the one-spelling-per-cell decline the pass throws from here.
|
|
1972
2242
|
if (symbolSetting.symbols) {
|
|
1973
|
-
OFFSET_NAME_PASS.run(fn, symbolSetting.symbols, target.
|
|
2243
|
+
OFFSET_NAME_PASS.run(fn, symbolSetting.symbols, offsetNameBehaviorsFor(target, opts.contextGlobals));
|
|
1974
2244
|
}
|
|
1975
2245
|
for (const [n, si] of [...inferredSymbols]) {
|
|
1976
2246
|
if (baseOpts.symbols?.has(n) === true || !sameDerivedShape(declSymbols.get(n), si)) {
|
|
@@ -1992,7 +2262,16 @@ export function enumerateCandidates(
|
|
|
1992
2262
|
},
|
|
1993
2263
|
},
|
|
1994
2264
|
prototypes[name],
|
|
1995
|
-
{
|
|
2265
|
+
{
|
|
2266
|
+
shortCircuit: { foldTreeOwned: liftSetting.connective },
|
|
2267
|
+
paramWidth: {
|
|
2268
|
+
extendedReaders: liftSetting.extendedReaders,
|
|
2269
|
+
onUndeclaredWidth: () => {
|
|
2270
|
+
guessesParamWidth = true;
|
|
2271
|
+
},
|
|
2272
|
+
},
|
|
2273
|
+
displaced: { keepStores: liftSetting.keepStores },
|
|
2274
|
+
},
|
|
1996
2275
|
);
|
|
1997
2276
|
} catch (e) {
|
|
1998
2277
|
// THE DEFAULT CARRIES NO VARIATION, by construction: every lift variation appends one, so
|
|
@@ -2007,6 +2286,33 @@ export function enumerateCandidates(
|
|
|
2007
2286
|
reportThrow(settingVariations, e);
|
|
2008
2287
|
continue;
|
|
2009
2288
|
}
|
|
2289
|
+
// THE OTHER RETURN-TAIL READING, `/ret-copy` or `/ret-join`, in its own `reading` pass
|
|
2290
|
+
// between the follow and the sink. The default pass structures each setting both ways where
|
|
2291
|
+
// the join past early returns took a join somewhere (`onJoinPastReturns`), and publishes one
|
|
2292
|
+
// (pipeline.ts `structureReturnTailReading`). On a target that declares the join its default
|
|
2293
|
+
// (`joinPastReturnsByDefault`: mwcc 2.4.2 and 2.3.3) it structures the join first
|
|
2294
|
+
// and publishes the copies (`copyReturnTails`) only where they spell the function in fewer
|
|
2295
|
+
// lines, the layout shows a cross-jumped return tail, or an early return keeps a
|
|
2296
|
+
// branch-proven constant unfolded. On every other target it structures the copies first and publishes them, and the
|
|
2297
|
+
// join only where the copies refuse. This pass
|
|
2298
|
+
// enumerates the tree it did not publish, so neither reading is structured twice:
|
|
2299
|
+
// - `/ret-copy`, the copies, where the join was published. A chain of `if`s each holding
|
|
2300
|
+
// an early `return;` has no CFG join (`synthetic:retchain`), and where the early returns
|
|
2301
|
+
// cross-jump into one return tail the follow lands on that tail
|
|
2302
|
+
// (`synthetic:retchain_xj`); without the join both copy the code below every such `if`
|
|
2303
|
+
// into its arms, and a switch with a `case` that returns early likewise. The copies are
|
|
2304
|
+
// kept as a candidate because some rows' bytes are them (`synthetic:sw_fall:ido7.1`);
|
|
2305
|
+
// - `/ret-join`, the join, where the copies were published: the join copies a return tail
|
|
2306
|
+
// into each early return in its turn, and where that is the longer spelling it is kept
|
|
2307
|
+
// as a candidate beside the copies.
|
|
2308
|
+
// A setting whose structuring never took the join has one reading, and pays no candidate.
|
|
2309
|
+
// With both in the fan, the bound moves a ranked score nowhere: `compareScored` orders a
|
|
2310
|
+
// score tie by goto, cast and line counts before enumeration order.
|
|
2311
|
+
//
|
|
2312
|
+
// The shared-tail passes below structure with the copies too: their follow and sink were
|
|
2313
|
+
// ranked against the copies, their winners hold those trees, and crossing them with the join
|
|
2314
|
+
// is a reading nothing has measured.
|
|
2315
|
+
//
|
|
2010
2316
|
// THE SHARED-TAIL VARIATIONS: the same raised fn, structured again with `followEarlyReturns`,
|
|
2011
2317
|
// in two passes after the default one.
|
|
2012
2318
|
// - `/shared-ret` is the follow ALONE, on the fn as raised. Some divergent `if` shares a
|
|
@@ -2049,14 +2355,30 @@ export function enumerateCandidates(
|
|
|
2049
2355
|
// `droppedDefault` is the DEFAULT pass's drops. Each shared-tail pass reads it and keeps its
|
|
2050
2356
|
// own drops in a copy, so one pass's structuring failure never removes the other's candidate.
|
|
2051
2357
|
const droppedDefault = new Set<string>();
|
|
2052
|
-
|
|
2358
|
+
// Per DEFAULT-pass setting whose structuring was a choice between the join past early
|
|
2359
|
+
// returns and the return-tail copies (pipeline.ts `structureReturnTailReading`), the reading
|
|
2360
|
+
// it did not publish: the only trees the `reading` pass enumerates, as `/ret-copy` where the
|
|
2361
|
+
// join was published and `/ret-join` where the copies were.
|
|
2362
|
+
const unpublishedDefault = new Map<string, { readonly reading: 'copies' | 'join'; readonly sfn: SFn }>();
|
|
2363
|
+
// DEFAULT-pass settings whose copies were dropped (they declined or failed a boundary
|
|
2364
|
+
// contract) or barred: the strip closure over them, as `dropped` is over whole settings, so
|
|
2365
|
+
// `X/merge-names` never publishes or offers the copies where `X`'s copies failed.
|
|
2366
|
+
const copiesDropped = new Set<string>();
|
|
2367
|
+
// What `assertDefaultAccepts` learned about this fn, shared by every setting structured from it.
|
|
2368
|
+
let acceptsMemo = new DefaultAcceptsMemo(fn);
|
|
2369
|
+
for (const pass of ['default', 'follow', 'reading', 'sink'] as const) {
|
|
2053
2370
|
if (pass === 'follow' && !hasDivergentSharedRet(fn)) {
|
|
2054
2371
|
continue;
|
|
2055
2372
|
}
|
|
2373
|
+
if (pass === 'reading' && unpublishedDefault.size === 0) {
|
|
2374
|
+
continue;
|
|
2375
|
+
}
|
|
2056
2376
|
if (pass === 'sink') {
|
|
2057
2377
|
let sunk: boolean;
|
|
2058
2378
|
try {
|
|
2059
2379
|
sunk = sinkStoreTails(fn);
|
|
2380
|
+
// The sink rewrites fn in place, so nothing the memo learned before it holds after it.
|
|
2381
|
+
acceptsMemo = new DefaultAcceptsMemo(fn);
|
|
2060
2382
|
if (sunk) {
|
|
2061
2383
|
verify(fn);
|
|
2062
2384
|
}
|
|
@@ -2084,6 +2406,8 @@ export function enumerateCandidates(
|
|
|
2084
2406
|
const settingsForLift = usableStructureSettings.filter((s) =>
|
|
2085
2407
|
offForThisLift.every((variation) => !s[variation.flag]),
|
|
2086
2408
|
);
|
|
2409
|
+
// Per setting key structured in THIS pass, the `offWitness` hooks its structuring fired.
|
|
2410
|
+
const witnessed = new Map<string, Set<NonNullable<StructureVariation['offWitness']>>>();
|
|
2087
2411
|
// `/merge-names` combinations whose un-merged sibling was DROPPED. `structure()` already
|
|
2088
2412
|
// refuses to let the variation unlock a function the default declines, but it can only see its own
|
|
2089
2413
|
// refusals — a boundary contract fails out here, in `structureChecked`. Without this a
|
|
@@ -2100,14 +2424,50 @@ export function enumerateCandidates(
|
|
|
2100
2424
|
if (alternative && droppedDefault.has(key)) {
|
|
2101
2425
|
continue;
|
|
2102
2426
|
}
|
|
2103
|
-
|
|
2427
|
+
// THE OTHER RETURN-TAIL READING: the tree the default pass structured and did not publish,
|
|
2428
|
+
// enumerated as it is. It is the same setting's, so the witness and the strip closure
|
|
2429
|
+
// below already answered for it there.
|
|
2430
|
+
const otherReading = pass === 'reading' ? unpublishedDefault.get(key) : undefined;
|
|
2431
|
+
if (pass === 'reading' && otherReading === undefined) {
|
|
2432
|
+
continue;
|
|
2433
|
+
}
|
|
2434
|
+
const settingLiftVariations: readonly Variation[] =
|
|
2435
|
+
otherReading === undefined
|
|
2436
|
+
? liftVariations
|
|
2437
|
+
: [...liftSetting.variations, otherReading.reading === 'copies' ? 'ret-copy' : 'ret-join'];
|
|
2438
|
+
// THE OFF-SIBLING WITNESS (`StructureVariation.offWitness`): a setting whose variation's
|
|
2439
|
+
// OFF sibling structured without firing the witness would rebuild that sibling's tree, so
|
|
2440
|
+
// it is skipped and reported as the tree dedup it is. A sibling not structured in this
|
|
2441
|
+
// pass (dropped, or skipped above) witnessed nothing, and the setting is structured.
|
|
2442
|
+
//
|
|
2443
|
+
// A setting skipped HERE builds its sibling's structuring, so it fires the hooks the
|
|
2444
|
+
// sibling fired, and it is recorded with the sibling's set. Without that the gate does
|
|
2445
|
+
// not compose: a setting carrying two witnessed variations (`tail-copies/for-continue`)
|
|
2446
|
+
// finds each sibling unrecorded and is structured however inert both are. A setting
|
|
2447
|
+
// skipped here in the default pass has no reading of its own in the `reading` pass:
|
|
2448
|
+
// the sibling's fired set is the union of both its readings' (`structureReturnTailReading`),
|
|
2449
|
+
// so both of this setting's readings are the sibling's trees, which the sibling enumerates.
|
|
2450
|
+
const sameAs = STRUCTURE_VARIATIONS.flatMap((variation) => {
|
|
2451
|
+
if (otherReading !== undefined || variation.offWitness === undefined || !s[variation.flag]) {
|
|
2452
|
+
return [];
|
|
2453
|
+
}
|
|
2454
|
+
const sibling = s.variations.filter((v) => v !== variation.name).join('/');
|
|
2455
|
+
const seen = witnessed.get(sibling);
|
|
2456
|
+
return seen !== undefined && !seen.has(variation.offWitness) ? [seen] : [];
|
|
2457
|
+
});
|
|
2458
|
+
if (sameAs.length > 0) {
|
|
2459
|
+
witnessed.set(key, sameAs[0]);
|
|
2460
|
+
opts.onTreeDeduped?.();
|
|
2461
|
+
continue;
|
|
2462
|
+
}
|
|
2463
|
+
const stripped = (set: ReadonlySet<string>): boolean =>
|
|
2104
2464
|
STRUCTURE_VARIATIONS.some(
|
|
2105
2465
|
(variation) =>
|
|
2106
2466
|
variation.strip &&
|
|
2107
2467
|
s[variation.flag] &&
|
|
2108
|
-
|
|
2109
|
-
)
|
|
2110
|
-
) {
|
|
2468
|
+
set.has(s.variations.filter((v) => v !== variation.name).join('/')),
|
|
2469
|
+
);
|
|
2470
|
+
if (otherReading === undefined && stripped(dropped)) {
|
|
2111
2471
|
// A SKIPPED setting is recorded exactly like a dropped one, or the closure would not be
|
|
2112
2472
|
// transitive: with plain X dropped and X/inplace skipped-but-unrecorded,
|
|
2113
2473
|
// X/inplace/merge-names would find neither stripped key and run — shipping a
|
|
@@ -2119,28 +2479,97 @@ export function enumerateCandidates(
|
|
|
2119
2479
|
// which it re-derives from its map on every call, so both branch senses structure the same
|
|
2120
2480
|
// recovered function without re-lifting.
|
|
2121
2481
|
let sfn: SFn;
|
|
2482
|
+
const fired = new Set<NonNullable<StructureVariation['offWitness']>>();
|
|
2483
|
+
const witnessHooks = Object.fromEntries(
|
|
2484
|
+
STRUCTURE_VARIATIONS.flatMap((variation) =>
|
|
2485
|
+
variation.offWitness !== undefined && !s[variation.flag]
|
|
2486
|
+
? [[variation.offWitness, () => fired.add(variation.offWitness!)]]
|
|
2487
|
+
: [],
|
|
2488
|
+
),
|
|
2489
|
+
);
|
|
2490
|
+
let counterSignRefused = false;
|
|
2491
|
+
const structOpts = {
|
|
2492
|
+
...symbolSettingOpts,
|
|
2493
|
+
...(inferredSymbols.size ? { inferredSymbols } : {}),
|
|
2494
|
+
...(orderLicensed.size ? { orderLicensedGlobals: orderLicensed } : {}),
|
|
2495
|
+
preserveDivergentBranchSense: s.sense,
|
|
2496
|
+
negateJoinedBranchSense: s.join ? !defSense : defSense,
|
|
2497
|
+
...(s.flipSites ? { branchSenseFlipSites: s.flipSites } : {}),
|
|
2498
|
+
anchorConstCopies: s.anchor,
|
|
2499
|
+
anchorLoopEntryConsts: s.entry,
|
|
2500
|
+
spellBitfieldMembers: s.bitfields,
|
|
2501
|
+
spellPtrMemberElements: s.ptrElems,
|
|
2502
|
+
spellDeclaredSubscripts: s.declRank,
|
|
2503
|
+
...STRUCTURE_VARIATIONS.reduce(
|
|
2504
|
+
(acc, variation) => ({ ...acc, ...variation.options(s[variation.flag]) }),
|
|
2505
|
+
{},
|
|
2506
|
+
),
|
|
2507
|
+
...(alternative ? { followEarlyReturns: true, copyReturnTails: true } : {}),
|
|
2508
|
+
};
|
|
2509
|
+
const hooks = {
|
|
2510
|
+
...witnessHooks,
|
|
2511
|
+
onCounterSignRefused: () => {
|
|
2512
|
+
counterSignRefused = true;
|
|
2513
|
+
},
|
|
2514
|
+
};
|
|
2122
2515
|
try {
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2516
|
+
if (otherReading !== undefined) {
|
|
2517
|
+
sfn = otherReading.sfn;
|
|
2518
|
+
} else if (pass === 'default') {
|
|
2519
|
+
// THE RETURN-TAIL READING, bounded by size and read off the layout (pipeline.ts
|
|
2520
|
+
// `structureReturnTailReading`): on a target that declares the join past early
|
|
2521
|
+
// returns its default, the join, or the copies where they spell this setting's
|
|
2522
|
+
// function shorter or the asm layout or an unfolded return marks them; on every other
|
|
2523
|
+
// target the copies, structured first, and the join only where they refuse, with
|
|
2524
|
+
// their refusal reported under `ret-copy` (the reading the fan then cannot hold). A
|
|
2525
|
+
// setting fails only where both refuse, and that failure is handled below as any
|
|
2526
|
+
// structuring failure. The losing tree is kept for the `reading` pass, so neither
|
|
2527
|
+
// reading is structured twice.
|
|
2528
|
+
const copiesBarred = stripped(copiesDropped);
|
|
2529
|
+
const read = structureReturnTailReading(
|
|
2530
|
+
fn,
|
|
2531
|
+
{ ...structOpts, joinPastReturns: copiesBarred },
|
|
2532
|
+
(t) => backend.emit(t),
|
|
2533
|
+
hooks,
|
|
2534
|
+
acceptsMemo,
|
|
2535
|
+
);
|
|
2536
|
+
sfn = read.sfn;
|
|
2537
|
+
if (read.unpublished === undefined) {
|
|
2538
|
+
if (copiesBarred) {
|
|
2539
|
+
copiesDropped.add(key);
|
|
2540
|
+
}
|
|
2541
|
+
} else if ('error' in read.unpublished) {
|
|
2542
|
+
copiesDropped.add(key);
|
|
2543
|
+
reportThrow(
|
|
2544
|
+
[...liftVariations, 'ret-copy', ...s.variations, ...symbolSetting.variations],
|
|
2545
|
+
read.unpublished.error,
|
|
2546
|
+
);
|
|
2547
|
+
} else {
|
|
2548
|
+
unpublishedDefault.set(key, read.unpublished);
|
|
2549
|
+
}
|
|
2550
|
+
witnessed.set(key, fired);
|
|
2551
|
+
} else {
|
|
2552
|
+
sfn = structureChecked(fn, structOpts, hooks, acceptsMemo);
|
|
2553
|
+
witnessed.set(key, fired);
|
|
2554
|
+
}
|
|
2141
2555
|
} catch (e) {
|
|
2556
|
+
if (e instanceof DefaultAcceptsMemoMisuse) {
|
|
2557
|
+
throw e; // a memo wired to the wrong fn is a defect here, never a dropped variation
|
|
2558
|
+
}
|
|
2142
2559
|
if (abortsRow([...liftVariations, ...symbolSetting.variations], s)) {
|
|
2143
|
-
|
|
2560
|
+
if (!(
|
|
2561
|
+
counterSignRefused &&
|
|
2562
|
+
!cand.signed &&
|
|
2563
|
+
pinnable &&
|
|
2564
|
+
e instanceof StructureError &&
|
|
2565
|
+
declinesOnCounterSign(fn, structOpts)
|
|
2566
|
+
)) {
|
|
2567
|
+
throw e; // the default lift's default setting keeps its behavior: a failure aborts the row
|
|
2568
|
+
}
|
|
2569
|
+
// THE UNSIGNED PIN'S DECLINE, HELD FOR THE SIGNED ONE — and the rest of the unsigned
|
|
2570
|
+
// pin is not built: every other setting of it would run behind a declined default.
|
|
2571
|
+
heldPinDecline ??= e;
|
|
2572
|
+
continue pins;
|
|
2144
2573
|
}
|
|
2145
2574
|
// Recorded for EVERY dropped setting: a candidate with more variations on looks its siblings
|
|
2146
2575
|
// up by stripping one variation at a time, and the stripped key can itself carry the other.
|
|
@@ -2191,7 +2620,13 @@ export function enumerateCandidates(
|
|
|
2191
2620
|
// The PRE-RESPELL variations (PRE_RESPELL_VARIATIONS, the last composition the POLICY note
|
|
2192
2621
|
// names): rewrite the TREE, then run the whole respell set over the result, so every
|
|
2193
2622
|
// respell variation derives from the rewrite instead of composing onto it. The gate is the pass's
|
|
2194
|
-
// own decline; the contracts are `respell`'s
|
|
2623
|
+
// own decline; the contracts are `respell`'s, for `respell`'s reasons, plus the effect count
|
|
2624
|
+
// `respell` cannot run (its note above): a pre-respell rewrite moves control, not just
|
|
2625
|
+
// spelling — `/unmerge` copies a join into its arms, `/case-end-goto` jumps over the code
|
|
2626
|
+
// between a loop and its arm's end — and the per-path count is what refuses a rewrite that
|
|
2627
|
+
// runs a call twice on one path or drops one. It is also the only production reader of
|
|
2628
|
+
// that count's case-end-goto paths (contracts.ts `PathCounts.end`): structuring never
|
|
2629
|
+
// writes the goto, so without it those paths are read by unit tests alone.
|
|
2195
2630
|
for (const pf of PRE_RESPELL_VARIATIONS) {
|
|
2196
2631
|
try {
|
|
2197
2632
|
const made = pf.apply(sfn);
|
|
@@ -2214,6 +2649,9 @@ export function enumerateCandidates(
|
|
|
2214
2649
|
assertLocalsWritten(made);
|
|
2215
2650
|
assertPostIncrUnshared(made);
|
|
2216
2651
|
assertNoOrphanedLocals(sfn, made);
|
|
2652
|
+
assertLocalStorageStaysInteger(sfn, made);
|
|
2653
|
+
assertFrameUnionsViewed(made);
|
|
2654
|
+
assertEffectsPreserved(fn, made);
|
|
2217
2655
|
// A backend refusal on this REWRITTEN tree is not a refusal of the row's own
|
|
2218
2656
|
// tree, so it never becomes the row's stated cause: `TreeSources.emit` is dropped
|
|
2219
2657
|
// here and only the call over the row's own tree above records one.
|
|
@@ -2274,15 +2712,22 @@ export function enumerateCandidates(
|
|
|
2274
2712
|
// whichever route the enumeration happened to walk first.
|
|
2275
2713
|
if (sp.matchOnly === undefined) {
|
|
2276
2714
|
delete dup.matchOnly;
|
|
2715
|
+
delete dup.signedUnsignedCompare;
|
|
2277
2716
|
}
|
|
2278
2717
|
if (!discardsPassedResult) {
|
|
2279
2718
|
delete dup.discardsPassedResult;
|
|
2280
2719
|
}
|
|
2720
|
+
if (!guessesParamWidth) {
|
|
2721
|
+
delete dup.guessesParamWidth;
|
|
2722
|
+
}
|
|
2723
|
+
if (!liftSetting.keepStores) {
|
|
2724
|
+
delete dup.farStoreCast;
|
|
2725
|
+
}
|
|
2281
2726
|
continue;
|
|
2282
2727
|
}
|
|
2283
2728
|
const variations: readonly Variation[] = [
|
|
2284
2729
|
cand.variation,
|
|
2285
|
-
...
|
|
2730
|
+
...settingLiftVariations,
|
|
2286
2731
|
...s.variations,
|
|
2287
2732
|
...sp.variations,
|
|
2288
2733
|
...symbolSetting.variations,
|
|
@@ -2299,8 +2744,11 @@ export function enumerateCandidates(
|
|
|
2299
2744
|
preference: symbolIndex,
|
|
2300
2745
|
...(sp.symbolRefs ? { symbolRefs: sp.symbolRefs } : {}),
|
|
2301
2746
|
...(sp.deviceVolatile ? { deviceVolatile: sp.deviceVolatile } : {}),
|
|
2302
|
-
...(sp.matchOnly ? { matchOnly:
|
|
2747
|
+
...(sp.matchOnly ? { matchOnly: true as const } : {}),
|
|
2748
|
+
...(sp.signedUnsignedCompare ? { signedUnsignedCompare: true as const } : {}),
|
|
2303
2749
|
...(discardsPassedResult ? { discardsPassedResult: true as const } : {}),
|
|
2750
|
+
...(guessesParamWidth ? { guessesParamWidth: true as const } : {}),
|
|
2751
|
+
...(liftSetting.keepStores ? { farStoreCast: true as const } : {}),
|
|
2304
2752
|
};
|
|
2305
2753
|
seen.set(source, made);
|
|
2306
2754
|
out.push(made);
|
|
@@ -2313,6 +2761,9 @@ export function enumerateCandidates(
|
|
|
2313
2761
|
// Every tree the fan produced was refused by the backend. Each refusal on its own is a dropped
|
|
2314
2762
|
// candidate; all of them together is the row, and it stays LOUD — the alternative is a caller
|
|
2315
2763
|
// ranking an empty list and reporting no match for a function nothing ever tried to spell.
|
|
2764
|
+
if (out.length === 0 && heldPinDecline !== undefined) {
|
|
2765
|
+
throw heldPinDecline;
|
|
2766
|
+
}
|
|
2316
2767
|
if (out.length === 0) {
|
|
2317
2768
|
throw new NoSpellableCandidateError(
|
|
2318
2769
|
`no spellable candidate for '${name}': ${firstLine(lastEmitError ?? 'no candidate produced')}`,
|
|
@@ -2440,7 +2891,8 @@ function stillbornNote(stillborn: Stillborn, fan: number): string {
|
|
|
2440
2891
|
*
|
|
2441
2892
|
* A DISCARDED PASSED RESULT next (`Candidate.discardsPassedResult`): equal bytes refute neither
|
|
2442
2893
|
* reading, so the one that drops a value the callee may read wins only where it scores strictly
|
|
2443
|
-
* better.
|
|
2894
|
+
* better. A GUESSED PARAMETER WIDTH (`Candidate.guessesParamWidth`) likewise: a narrow declaration
|
|
2895
|
+
* no prototype supplied changes its callers' bytes, which no score here reads.
|
|
2444
2896
|
*
|
|
2445
2897
|
* PREFERENCE next: a named symbol-map spelling beats its `/raw-globals` sibling at equal bytes.
|
|
2446
2898
|
*
|
|
@@ -2463,6 +2915,11 @@ function stillbornNote(stillborn: Stillborn, fan: number): string {
|
|
|
2463
2915
|
* below does the same for `/narrow-decl`, which drops one cast per local it narrows and so wins
|
|
2464
2916
|
* every tie against the candidate it is derived onto.
|
|
2465
2917
|
*
|
|
2918
|
+
* A FAR STORE SPELLED THROUGH A CAST (`Candidate.farStoreCast`) next: where `/far-cast` ties the
|
|
2919
|
+
* member spelling, the compiler built the member's object too, and the member names the field. Its
|
|
2920
|
+
* `(u8 *)` base is no scalar cast and its line count is no higher, so neither term below would
|
|
2921
|
+
* prefer the member on its own.
|
|
2922
|
+
*
|
|
2466
2923
|
* CAST COUNT next, and only WITHIN a preference. A wrong signedness pin is what manufactures casts —
|
|
2467
2924
|
* the C backend has to cast a shift operand back to the signedness the machine op needs, so
|
|
2468
2925
|
* pinning `u32` on a genuinely-signed parameter buys `s32 f(u32 a0) { return (s32)a0 >> a1; }`
|
|
@@ -2480,12 +2937,11 @@ function stillbornNote(stillborn: Stillborn, fan: number): string {
|
|
|
2480
2937
|
* result deterministic. Spelled explicitly rather than leaning on Array#sort's stability, which
|
|
2481
2938
|
* would make each preference an accident of two unrelated decisions.
|
|
2482
2939
|
*
|
|
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. */
|
|
2940
|
+
* WHAT NO TERM HERE WEIGHS: a comparison's rendered SIGNEDNESS. It needs none: every tree a
|
|
2941
|
+
* source is emitted from is judged for an unsigned compare that renders signed
|
|
2942
|
+
* (`Candidate.signedUnsignedCompare`), whichever variation spelled it so, and such a source is
|
|
2943
|
+
* `matchOnly` — on a nonmatch it is withheld before this comparator sees it, whatever it scores,
|
|
2944
|
+
* and on a match both spellings are the program. */
|
|
2489
2945
|
export function compareScored<S extends { score: number }>(
|
|
2490
2946
|
a: Candidate & { score: S; order: number },
|
|
2491
2947
|
b: Candidate & { score: S; order: number },
|
|
@@ -2493,20 +2949,32 @@ export function compareScored<S extends { score: number }>(
|
|
|
2493
2949
|
return (
|
|
2494
2950
|
a.score.score - b.score.score ||
|
|
2495
2951
|
(a.discardsPassedResult ? 1 : 0) - (b.discardsPassedResult ? 1 : 0) ||
|
|
2952
|
+
(a.guessesParamWidth ? 1 : 0) - (b.guessesParamWidth ? 1 : 0) ||
|
|
2496
2953
|
a.preference - b.preference ||
|
|
2497
2954
|
(b.deviceVolatile ?? 0) - (a.deviceVolatile ?? 0) ||
|
|
2955
|
+
(a.farStoreCast ? 1 : 0) - (b.farStoreCast ? 1 : 0) ||
|
|
2956
|
+
gotoCount(a.source) - gotoCount(b.source) ||
|
|
2498
2957
|
castCount(a.source) - castCount(b.source) ||
|
|
2499
2958
|
lineCount(a.source) - lineCount(b.source) ||
|
|
2500
2959
|
a.order - b.order
|
|
2501
2960
|
);
|
|
2502
2961
|
}
|
|
2503
2962
|
|
|
2504
|
-
/**
|
|
2505
|
-
*
|
|
2506
|
-
|
|
2507
|
-
|
|
2963
|
+
/** `goto` statements in a candidate's rendered source — the readability tie-break above, asked
|
|
2964
|
+
* before the casts. Each goto asmlift writes (l3/ast.ts: a jump to a case label of the switch it
|
|
2965
|
+
* sits in, or to the `break;` that ends its arm) is a ranked spelling against a goto-free one —
|
|
2966
|
+
* copies (`/case-goto`) or a flag (`/case-end-goto`) — and where the two score alike the goto-free
|
|
2967
|
+
* one is published: a goto is the narrow exception the differ has to earn, never a tie's winner.
|
|
2968
|
+
* Deterministic, and total on any string. */
|
|
2969
|
+
export function gotoCount(source: string): number {
|
|
2970
|
+
return source.match(/\bgoto [A-Za-z_]\w*;/g)?.length ?? 0;
|
|
2508
2971
|
}
|
|
2509
2972
|
|
|
2973
|
+
/** Non-blank lines in a candidate's rendered source — the compactness tie-break above, counted
|
|
2974
|
+
* exactly as `quality.ts` counts `lines`, and the one count the default's return-tail reading is
|
|
2975
|
+
* bounded by (pipeline.ts `sourceLineCount`). Deterministic, and total on any string. */
|
|
2976
|
+
const lineCount = sourceLineCount;
|
|
2977
|
+
|
|
2510
2978
|
/** Scalar casts in a candidate's rendered source — the readability tie-break above.
|
|
2511
2979
|
*
|
|
2512
2980
|
* A WITHIN-PREFERENCE tie-break over two spellings of ONE function, and deliberately NARROWER than
|