@asmlift/core 0.8.0 → 0.9.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 +34 -30
- package/package.json +1 -1
- package/src/aggregate.ts +248 -0
- package/src/backend/c.ts +5 -2
- package/src/backend/cfamily.ts +236 -37
- package/src/backend/cpp.ts +114 -9
- package/src/backend/pascal.ts +39 -0
- package/src/cdecl/constant.ts +59 -0
- package/src/cdecl/lex.ts +209 -0
- package/src/cdecl/parse.ts +975 -0
- package/src/cdecl/spell.ts +69 -0
- package/src/codegen-flags.ts +38 -6
- package/src/compiler-diagnostics.ts +194 -0
- package/src/contracts.ts +615 -79
- package/src/declare.ts +75 -5
- package/src/frontend/asmdata.ts +19 -5
- package/src/frontend/call-plan.ts +686 -0
- package/src/frontend/device-pins.ts +277 -0
- package/src/frontend/errors.ts +5 -0
- package/src/frontend/flags-edge.ts +101 -0
- package/src/frontend/fpu.ts +167 -0
- package/src/frontend/frame-objects.ts +2131 -0
- package/src/frontend/high-half.ts +2 -1
- package/src/frontend/local-object.ts +490 -0
- package/src/frontend/mips.ts +363 -40
- package/src/frontend/opaque.ts +97 -18
- package/src/frontend/ppc.ts +520 -129
- package/src/frontend/reloc-symbol.ts +94 -13
- package/src/frontend/splat.ts +108 -5
- package/src/frontend/ssa.ts +669 -76
- package/src/frontend/stackargs.ts +120 -39
- package/src/frontend/thumb.ts +2764 -2018
- package/src/ir/alias.ts +29 -0
- package/src/ir/core.ts +101 -3
- package/src/ir/discipline.ts +248 -0
- package/src/ir/float-bits.ts +47 -0
- package/src/ir/opcodes.ts +84 -112
- package/src/ir/parse.ts +1 -0
- package/src/ir/types.ts +118 -2
- package/src/ir/verify.ts +150 -3
- package/src/l3/address.ts +5 -0
- package/src/l3/argcopy.ts +38 -6
- package/src/l3/ast.ts +186 -14
- package/src/l3/coalesce.ts +21 -13
- package/src/l3/dce.ts +32 -10
- package/src/l3/gates.ts +9 -3
- package/src/l3/hoist.ts +8 -7
- package/src/l3/initfirst.ts +27 -9
- package/src/l3/intcell.ts +97 -0
- package/src/l3/mentions.ts +10 -4
- package/src/l3/narrowdecl.ts +218 -0
- package/src/l3/nearbase.ts +10 -5
- package/src/l3/parkfirst.ts +8 -5
- package/src/l3/pollguard.ts +3 -2
- package/src/l3/ptrcell.ts +357 -0
- package/src/l3/regspell.ts +17 -3
- package/src/l3/reindex.ts +10 -7
- package/src/l3/symbol-refs.ts +104 -23
- package/src/l3/tailmerge.ts +6 -19
- package/src/l3/typing.ts +137 -25
- package/src/l3/unreduce.ts +4 -4
- package/src/l3/volstore.ts +96 -7
- package/src/mangle.ts +11 -1
- package/src/pattern/engine.ts +19 -16
- package/src/pipeline.ts +57 -12
- package/src/proto-context.ts +788 -0
- package/src/proto.ts +689 -50
- package/src/raise/arrays.ts +4 -3
- package/src/raise/declared-volatile.ts +131 -0
- package/src/raise/divpow2.ts +2 -2
- package/src/raise/errors.ts +17 -0
- package/src/raise/floathelpers.ts +243 -0
- package/src/raise/globalshape.ts +11 -5
- package/src/raise/gvn.ts +26 -5
- package/src/raise/narrow.ts +29 -2
- package/src/raise/narrowlocal.ts +11 -6
- package/src/raise/offsetnames.ts +510 -0
- package/src/raise/pairparams.ts +81 -0
- package/src/raise/paramwidth.ts +7 -0
- package/src/raise/pre-recovery.ts +78 -4
- package/src/raise/recover.ts +14 -10
- package/src/raise/retsink.ts +1 -1
- package/src/raise/shortcircuit.ts +41 -19
- package/src/raise/softdiv.ts +21 -23
- package/src/raise/struct-arrays.ts +5 -4
- package/src/raise/structs.ts +311 -98
- package/src/raise/truncload.ts +366 -0
- package/src/raise/widehelpers.ts +106 -0
- package/src/rank-declare.ts +30 -5
- package/src/rank-variations.ts +36 -8
- package/src/rank.ts +276 -80
- package/src/runtime-helpers.ts +287 -0
- package/src/stillborn.ts +289 -0
- package/src/structure/analysis.ts +1285 -448
- package/src/structure/hazards.ts +633 -58
- package/src/structure/local-statics.ts +381 -0
- package/src/structure/namecoalesce.ts +1 -1
- package/src/structure/redundant-test.ts +99 -0
- package/src/structure/structure.ts +1772 -395
- package/src/structure/switch-recover.ts +159 -78
- package/src/symbols.ts +38 -3
- package/src/target.ts +662 -59
- package/src/trace.ts +44 -24
- package/src/variation-definitions.ts +119 -1
- package/src/variation-tokens.ts +20 -6
package/src/rank.ts
CHANGED
|
@@ -24,12 +24,12 @@ import {
|
|
|
24
24
|
assertLocalsWritten,
|
|
25
25
|
assertNoOrphanedLocals,
|
|
26
26
|
assertPlacementSurvives,
|
|
27
|
+
assertPostIncrUnshared,
|
|
27
28
|
assertResolved,
|
|
28
29
|
} from './contracts';
|
|
29
30
|
import type { AsmData } from './frontend/asmdata';
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
import { Fn, defOpMap } from './ir/core';
|
|
31
|
+
import { hasSetupArgsNarrowing, narrowToSetupArgs, setupArgsDiscardsPassedResult } from './frontend/ssa';
|
|
32
|
+
import { Fn, Value, defOpMap } from './ir/core';
|
|
33
33
|
import { T } from './ir/types';
|
|
34
34
|
import { verify } from './ir/verify';
|
|
35
35
|
import { advancedBases } from './l3/advance';
|
|
@@ -42,6 +42,7 @@ import type { Gate } from './l3/gates';
|
|
|
42
42
|
import type { HoistPlacement } from './l3/hoist';
|
|
43
43
|
import { homeSplitTag, homeSplitWithholds, splitHomeBases } from './l3/homesplit';
|
|
44
44
|
import { inlinableConstBases, inlineConstBases } from './l3/inlinebase';
|
|
45
|
+
import { integerCells } from './l3/intcell';
|
|
45
46
|
import { mulFirstSums } from './l3/mulfirst';
|
|
46
47
|
import { nearBaseClusters } from './l3/nearbase';
|
|
47
48
|
import { spellOperandMembers } from './l3/offmember';
|
|
@@ -58,9 +59,10 @@ import { volatileValueLocals } from './l3/volatileval';
|
|
|
58
59
|
import { volatileDeviceStores } from './l3/volstore';
|
|
59
60
|
import { zeroSubNegates } from './l3/zerosub';
|
|
60
61
|
import { RewritePattern } from './pattern/engine';
|
|
61
|
-
import { applyIdiomPatterns, raiseRecovered, structureChecked } from './pipeline';
|
|
62
|
-
import { type Prototypes, prototypesFromSymbols } from './proto';
|
|
62
|
+
import { applyIdiomPatterns, liftStamped, raiseRecovered, structureChecked } from './pipeline';
|
|
63
|
+
import { type Prototypes, declaresVoidReturn, prototypesFromSymbols } from './proto';
|
|
63
64
|
import { inferGlobalArrays, orderLicensedGlobals, sameDerivedShape } from './raise/globalshape';
|
|
65
|
+
import { OFFSET_NAME_PASS } from './raise/offsetnames';
|
|
64
66
|
import { runPreRecovery } from './raise/pre-recovery';
|
|
65
67
|
import { recoverTypes } from './raise/recover';
|
|
66
68
|
import { sinkStoreTails } from './raise/tailsink';
|
|
@@ -86,6 +88,7 @@ import {
|
|
|
86
88
|
createdLocals,
|
|
87
89
|
sameBases,
|
|
88
90
|
} from './rank-variations';
|
|
91
|
+
import { type Stillborn, stillbornVerdict } from './stillborn';
|
|
89
92
|
import { hasDivergentSharedRet } from './structure/structure';
|
|
90
93
|
import {
|
|
91
94
|
type SymbolInfo,
|
|
@@ -96,7 +99,13 @@ import {
|
|
|
96
99
|
symbolsByName,
|
|
97
100
|
} from './symbols';
|
|
98
101
|
import { type TargetDescription, structureOptionsFor } from './target';
|
|
99
|
-
import {
|
|
102
|
+
import {
|
|
103
|
+
type SubjectVariationName,
|
|
104
|
+
type Variation,
|
|
105
|
+
type VariationName,
|
|
106
|
+
offeredOn,
|
|
107
|
+
withSubject,
|
|
108
|
+
} from './variation-tokens';
|
|
100
109
|
|
|
101
110
|
/** Pin every SCALAR entry param (index not in `ptrIdx`) to the candidate signedness, before
|
|
102
111
|
* recovery. Answers whether any param was PINNABLE — not whether its type moved: which of the
|
|
@@ -106,16 +115,33 @@ import { type SubjectVariationName, type Variation, offeredOn, withSubject } fro
|
|
|
106
115
|
* A param NARROWED by raise/paramwidth.ts is not pinnable: the extension it was narrowed at states
|
|
107
116
|
* the signedness as well as the width — agbcc's shift pair by its `asr`/`lsr`, PPC's `extsb`/`extsh`
|
|
108
117
|
* by the opcode — so there is no question for the signedness variation to put to the differ, and pinning would
|
|
109
|
-
* widen it back to 32 bits.
|
|
118
|
+
* widen it back to 32 bits.
|
|
119
|
+
*
|
|
120
|
+
* Nor is a param NOTHING READS. Its declared signedness reaches no instruction, so the two
|
|
121
|
+
* candidates would differ only in the signature's text and compile to the same object. The frontend
|
|
122
|
+
* mints such params on purpose: an argument register below the highest one read keeps its slot
|
|
123
|
+
* (`frontend/ssa.ts` mintArgSlotHoles). Counting one as pinnable would double the fan of every
|
|
124
|
+
* function whose only scalar params are unread. */
|
|
110
125
|
function pinScalarParams(fn: Fn, signed: boolean, ptrIdx: Set<number>): boolean {
|
|
126
|
+
const read = new Set<Value>();
|
|
127
|
+
for (const b of fn.blocks) {
|
|
128
|
+
for (const op of b.ops) {
|
|
129
|
+
op.operands.forEach((v) => read.add(v));
|
|
130
|
+
op.successors.forEach((s) => s.args.forEach((v) => read.add(v)));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
111
133
|
let pinnable = false;
|
|
112
134
|
fn.blocks[0].params.forEach((p, i) => {
|
|
113
|
-
if (ptrIdx.has(i)) {
|
|
135
|
+
if (ptrIdx.has(i) || !read.has(p)) {
|
|
114
136
|
return;
|
|
115
137
|
}
|
|
116
|
-
|
|
138
|
+
// AT THE PARAMETER'S OWN WIDTH. The pin answers a question the asm does not — whether the
|
|
139
|
+
// source declared this parameter signed — and a 64-bit parameter has that question too; what
|
|
140
|
+
// it does not have is a spare 32 bits to lose to a constant here. A width the machine already
|
|
141
|
+
// narrowed (a declared `u8`) is not a scalar this variation re-decides, so it is left alone.
|
|
142
|
+
if (p.type.kind === 'unknown' || (p.type.kind === 'int' && p.type.width >= 32)) {
|
|
117
143
|
pinnable = true;
|
|
118
|
-
p.type =
|
|
144
|
+
p.type = T.int(p.type.width, signed);
|
|
119
145
|
}
|
|
120
146
|
});
|
|
121
147
|
return pinnable;
|
|
@@ -220,15 +246,25 @@ export interface Candidate {
|
|
|
220
246
|
*
|
|
221
247
|
* The third admission ground, and the narrowest. A respell variation must preserve semantics by
|
|
222
248
|
* construction (the POLICY note at the respell site), because on a nonmatch row the best
|
|
223
|
-
* spelling is what the user is shown.
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
*
|
|
249
|
+
* spelling is what the user is shown. Two spellings cannot meet that bar from inside the pass,
|
|
250
|
+
* each for its own fact (`PROOF_OBLIGATIONS` states them):
|
|
251
|
+
*
|
|
252
|
+
* • `l3/unreduce.ts` moves a memory read into a loop whose stores are all device registers, and
|
|
253
|
+
* on this board a device store can make the DEVICE write ordinary memory (a DMA trigger),
|
|
254
|
+
* which no gate over the C can rule out;
|
|
255
|
+
* • `l3/intcell.ts` spells a global as the integer a project may have declared it, where a
|
|
256
|
+
* pointer declaration scales the offset it adds, and no declaration is in the asm.
|
|
257
|
+
*
|
|
258
|
+
* What settles both instead is the object: a candidate that assembles to the target's own bytes
|
|
259
|
+
* IS the program, whatever a gate could have proved. So the spelling is offered, scored, and then
|
|
260
|
+
* either wins on proof or is WITHHELD — never shown as a best-effort answer. Both ranking drivers
|
|
261
|
+
* ask `withheldReason`, so neither can publish what the other would not. */
|
|
231
262
|
matchOnly?: true;
|
|
263
|
+
/** This candidate's lift is `/setup-args` and drops an earlier callee's result that a call through
|
|
264
|
+
* a register is passed (frontend/ssa.ts `setupArgsDiscardsPassedResult`). It loses every score
|
|
265
|
+
* tie (compareScored): a tie refutes neither reading, and a shorter spelling must not win by
|
|
266
|
+
* dropping a value the callee may read. */
|
|
267
|
+
discardsPassedResult?: true;
|
|
232
268
|
}
|
|
233
269
|
/** A candidate paired with its score `S` (the injected scorer's result shape — must carry `.score`). */
|
|
234
270
|
export interface Scored<S> extends Candidate {
|
|
@@ -261,10 +297,20 @@ export interface WithheldCandidate {
|
|
|
261
297
|
why: string;
|
|
262
298
|
}
|
|
263
299
|
|
|
300
|
+
/** A candidate that was NEVER COMPILED: the fan was declared stillborn (stillborn.ts) before its
|
|
301
|
+
* turn came. Kept apart from `dropped`, which means "the scorer refused it" — nothing refused
|
|
302
|
+
* this one, and a count that folded the two would report compiles that never ran. */
|
|
303
|
+
export interface NotCompiledCandidate {
|
|
304
|
+
variations: readonly string[];
|
|
305
|
+
/** the text the stop bet the compiler would refuse: a stop is re-placed whenever it changes, and
|
|
306
|
+
* the benchmark keys its STOP report on it (`fanNotCompiledDigest`) */
|
|
307
|
+
source: string;
|
|
308
|
+
}
|
|
309
|
+
|
|
264
310
|
/** EVERY candidate refused — `rankBy` has no ranked result to return, so it throws this.
|
|
265
311
|
*
|
|
266
|
-
* The
|
|
267
|
-
* nothing scored,
|
|
312
|
+
* The lists ride on the error, and that is the point of having a class at all: on a row where
|
|
313
|
+
* nothing scored, the three of them ARE the whole fan, and a bare `Error` discards it. A caller that
|
|
268
314
|
* prints one candidate's failure (the `cause`) is showing the LAST spelling the scorer refused,
|
|
269
315
|
* which is neither the first nor a representative one — the benchmark's own noncompile rows have
|
|
270
316
|
* up to a thousand siblings behind that single line. The MESSAGE is load-bearing and must stay
|
|
@@ -273,11 +319,20 @@ export interface WithheldCandidate {
|
|
|
273
319
|
export class NoScorableCandidateError extends Error {
|
|
274
320
|
readonly dropped: DroppedCandidate[];
|
|
275
321
|
readonly withheld: WithheldCandidate[];
|
|
276
|
-
|
|
322
|
+
/** empty unless the fan was declared stillborn; `dropped` is then only what WAS compiled */
|
|
323
|
+
readonly notCompiled: NotCompiledCandidate[];
|
|
324
|
+
constructor(
|
|
325
|
+
message: string,
|
|
326
|
+
dropped: DroppedCandidate[],
|
|
327
|
+
withheld: WithheldCandidate[],
|
|
328
|
+
notCompiled: NotCompiledCandidate[],
|
|
329
|
+
options?: ErrorOptions,
|
|
330
|
+
) {
|
|
277
331
|
super(message, options);
|
|
278
332
|
this.name = 'NoScorableCandidateError';
|
|
279
333
|
this.dropped = dropped;
|
|
280
334
|
this.withheld = withheld;
|
|
335
|
+
this.notCompiled = notCompiled;
|
|
281
336
|
}
|
|
282
337
|
}
|
|
283
338
|
|
|
@@ -309,14 +364,23 @@ export interface RankedResult<S> {
|
|
|
309
364
|
* drivers over one enumeration (this module's sync `rankBy` and the webapp's async await-loop),
|
|
310
365
|
* and a filter written twice is how they come to publish different answers. Null ⇒ publish.
|
|
311
366
|
*
|
|
312
|
-
* `score === 0` is objdiff's byte-exact match (
|
|
367
|
+
* `score === 0` is objdiff's byte-exact match (@match-kit/scoring states the equivalence), which is
|
|
313
368
|
* why a bare `.score` suffices and the generic needs no `match` field. */
|
|
314
369
|
export function withheldReason<S extends { score: number }>(c: Candidate, score: S): string | null {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
370
|
+
if (c.matchOnly !== true || score.score === 0) {
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
const facts = c.variations.flatMap((v) => PROOF_OBLIGATIONS.get(v) ?? []);
|
|
374
|
+
const fact = facts.length > 0 ? facts.join(' and on ') : 'a fact no gate over the C can settle';
|
|
375
|
+
return `this spelling rests on ${fact}; only a byte-exact score proves it`;
|
|
318
376
|
}
|
|
319
377
|
|
|
378
|
+
/** The fact each proof-carrying variation (`needsProof`) rests on, as `withheldReason` publishes it. */
|
|
379
|
+
const PROOF_OBLIGATIONS: ReadonlyMap<string, string> = new Map<VariationName, string>([
|
|
380
|
+
['unreduce', 'a device-behaviour fact no gate over the C can settle'],
|
|
381
|
+
['int-cell', 'the project declaring the global an integer, which the assembly does not show'],
|
|
382
|
+
]);
|
|
383
|
+
|
|
320
384
|
/** What a respell variation hands `respell`: its tree, or — when the variation cannot establish the
|
|
321
385
|
* candidate's semantics from inside the pass — the tree paired with that fact. `undefined`/`null`
|
|
322
386
|
* is a decline. */
|
|
@@ -408,9 +472,8 @@ export function enumerateCandidates(
|
|
|
408
472
|
// Same merge as `decompile`: the project's DWARF signatures fill in what the caller did not
|
|
409
473
|
// state, so both the annotate pass and the ranked candidates reason about one prototype table.
|
|
410
474
|
const prototypes = prototypesFromSymbols(opts.symbols, opts.prototypes ?? {});
|
|
411
|
-
const frontend = frontendFor(target);
|
|
412
475
|
const baseOpts = {
|
|
413
|
-
...structureOptionsFor(target, prototypes[name]
|
|
476
|
+
...structureOptionsFor(target, declaresVoidReturn(prototypes[name]), prototypes),
|
|
414
477
|
// See the same line in pipeline.ts: a backend that cannot print switch fall-through must not
|
|
415
478
|
// be handed a tree carrying one, because its refusal costs the whole candidate (and, when
|
|
416
479
|
// every candidate carries it, the whole row).
|
|
@@ -541,8 +604,7 @@ export function enumerateCandidates(
|
|
|
541
604
|
// pointers/aggregates so they are excluded from the signedness variation (see NO_PIN_KINDS). One
|
|
542
605
|
// extra lift+recover, no compile. (The shared lift deliberately stops after recoverTypes — it only reads the param KINDS, so
|
|
543
606
|
// the totality contract / return-sinking of the full spine are not run on it.)
|
|
544
|
-
const sharedLift =
|
|
545
|
-
verify(sharedLift);
|
|
607
|
+
const sharedLift = liftStamped(name, asm, target, prototypes, opts.asmData, opts.symbols);
|
|
546
608
|
// The ARRAY SHAPES the input assembly evidences (raise/globalshape.ts), for the DECLARATION
|
|
547
609
|
// half. Read off the shared lift's LIFTED form — before the fold below and the tower rewrite it —
|
|
548
610
|
// because the base-materialization order the derivation's licence reads does not survive them.
|
|
@@ -585,8 +647,7 @@ export function enumerateCandidates(
|
|
|
585
647
|
if (symbols === opts.symbols) {
|
|
586
648
|
return sharedLiftTreeOwned;
|
|
587
649
|
}
|
|
588
|
-
const p =
|
|
589
|
-
verify(p);
|
|
650
|
+
const p = liftStamped(name, asm, target, prototypes, opts.asmData, symbols);
|
|
590
651
|
applyIdiomPatterns(p, target, opts.patterns);
|
|
591
652
|
let seen = false;
|
|
592
653
|
runPreRecovery(p, target, () => verify(p), prototypes[name], {
|
|
@@ -800,6 +861,14 @@ export function enumerateCandidates(
|
|
|
800
861
|
s.ptrElems &&
|
|
801
862
|
s.declRank &&
|
|
802
863
|
STRUCTURE_VARIATIONS.every((variation) => !s[variation.flag]);
|
|
864
|
+
/** Does a failure ABORT THE ROW, rather than drop the setting that threw? Only on the default
|
|
865
|
+
* candidate's own path: no variation named by the symbol-map setting or the lift setting —
|
|
866
|
+
* `settingVariations` is both, as the candidate spells them — and, where a structure setting is
|
|
867
|
+
* named, the default one. Anywhere else the failure is a dropped variation, reported through
|
|
868
|
+
* `onEnumerationError` under the same names. The lift, the raise and the structuring all ask
|
|
869
|
+
* this one predicate, so a setting that names a new variation is seen by all three. */
|
|
870
|
+
const abortsRow = (settingVariations: readonly Variation[], s?: StructureSetting): boolean =>
|
|
871
|
+
settingVariations.length === 0 && (s === undefined || isDefaultSetting(s));
|
|
803
872
|
|
|
804
873
|
const seen = new Map<string, Candidate>();
|
|
805
874
|
const seenTrees = new Set<string>();
|
|
@@ -840,7 +909,7 @@ export function enumerateCandidates(
|
|
|
840
909
|
// every OTHER name in the function, so supplying more information made the tool strictly
|
|
841
910
|
// worse. A union cannot — each name is declared by whichever half knows more about it.
|
|
842
911
|
// SCOPE: `declSymbols` is used ONLY here. It must never reach `opts.symbols`/`baseOpts.symbols`
|
|
843
|
-
// or `
|
|
912
|
+
// or `liftStamped` — feeding it to the lift would turn on pool promotion, interior
|
|
844
913
|
// attribution and the `/raw-globals` variation, which is a different (and source-moving) change.
|
|
845
914
|
// THREE halves now, in increasing authority: the name-only pool/reloc symbols, the array shapes
|
|
846
915
|
// the asm evidences for them (raise/globalshape.ts — an `extern u16 gTbl[];` where the bare
|
|
@@ -856,6 +925,10 @@ export function enumerateCandidates(
|
|
|
856
925
|
accessFacts,
|
|
857
926
|
mapSymbols,
|
|
858
927
|
targetNames: new Set(targetSymbols.keys()),
|
|
928
|
+
// The MERGED table, the same one the frontend was handed above, so the declaration a candidate
|
|
929
|
+
// carries and the lift that produced it cannot state different things about a callee.
|
|
930
|
+
prototypes,
|
|
931
|
+
target,
|
|
859
932
|
refuse,
|
|
860
933
|
});
|
|
861
934
|
// THE RESPELL SET, as a function whose PARAMETER LIST is the invariant the tree skip below
|
|
@@ -923,9 +996,11 @@ export function enumerateCandidates(
|
|
|
923
996
|
// numeric-address pointer local — the joint spelling is reachable from neither variation
|
|
924
997
|
// alone, each composition narrows /volatile to that variation's own locals (volatilePtrLocals'
|
|
925
998
|
// `only`), and each needed a row to demand it. The STACKED variations (STACKED_VARIATIONS) are
|
|
926
|
-
// derived onto EVERY source: statement order/shape
|
|
927
|
-
//
|
|
928
|
-
//
|
|
999
|
+
// derived onto EVERY source: statement order/shape, and the width a narrow local is declared
|
|
1000
|
+
// at, are each orthogonal to what any respell variation outside that table changes — the same
|
|
1001
|
+
// kind of independent dimension as signedness —
|
|
1002
|
+
// so they are crossed with every source rather than paired (two members of the table may
|
|
1003
|
+
// compete for one node, and its order decides; rank-variations.ts); a third blanket composition needs
|
|
929
1004
|
// the same argument, not just a row. And a specific PAIRING is admitted on one of two
|
|
930
1005
|
// grounds, never on "it might help". FIRST, a row demands the joint spelling AND that
|
|
931
1006
|
// spelling is reachable from neither variation alone: /livebase × /indexed, × /sinkinit,
|
|
@@ -991,14 +1066,16 @@ export function enumerateCandidates(
|
|
|
991
1066
|
assertResolved(alt);
|
|
992
1067
|
assertDerefsTyped(alt);
|
|
993
1068
|
assertLocalsWritten(alt);
|
|
1069
|
+
assertPostIncrUnshared(alt);
|
|
994
1070
|
assertNoOrphanedLocals(sfn, alt);
|
|
995
1071
|
sources.push({ variations, source: backend.emit(alt), ...refsOf(alt), ...volOf(alt), ...proof });
|
|
996
1072
|
// STACKED variations, derived onto EVERY source (the POLICY note above carries the
|
|
997
|
-
// admission argument). Each is a statement-order/shape
|
|
998
|
-
//
|
|
1073
|
+
// admission argument). Each is a statement-order/shape or declaration-width fact
|
|
1074
|
+
// orthogonal to the respell variations outside the table; subsets compose in the fixed
|
|
1075
|
+
// table order, which decides between two members that compete for one node. A stacked variation that
|
|
999
1076
|
// never fires declines and costs nothing.
|
|
1000
1077
|
if (!alreadyShaped) {
|
|
1001
|
-
// A shape
|
|
1078
|
+
// A shape may REORDER statements, and it is derived after a variation has placed its defs —
|
|
1002
1079
|
// so the placement is re-checked on the shaped tree (contracts.ts). Differential: judged
|
|
1003
1080
|
// only where the unshaped tree already satisfied the walk, so a variation whose placement
|
|
1004
1081
|
// it never described is not dropped on the strength of a model that does not apply.
|
|
@@ -1029,6 +1106,7 @@ export function enumerateCandidates(
|
|
|
1029
1106
|
assertResolved(shaped.out);
|
|
1030
1107
|
assertDerefsTyped(shaped.out);
|
|
1031
1108
|
assertLocalsWritten(shaped.out);
|
|
1109
|
+
assertPostIncrUnshared(shaped.out);
|
|
1032
1110
|
assertNoOrphanedLocals(alt, shaped.out);
|
|
1033
1111
|
assertPlacementSurvives(alt, shaped.out, minted);
|
|
1034
1112
|
sources.push({
|
|
@@ -1143,9 +1221,9 @@ export function enumerateCandidates(
|
|
|
1143
1221
|
// only ties `/vol-store`'s 27, and RT's 32 beats the admitted standalone `/unreduce`'s 35, so
|
|
1144
1222
|
// "worse than an admitted variation" would exclude neither.
|
|
1145
1223
|
//
|
|
1146
|
-
// WHAT THE STANDALONE LINES COST, since
|
|
1147
|
-
//
|
|
1148
|
-
//
|
|
1224
|
+
// WHAT THE STANDALONE LINES COST, since the two variations rarely win an artifact row ALONE —
|
|
1225
|
+
// `apps/benchmark/test/census.test.ts` names every `/unreduce` and `/ptr-field` winner outside
|
|
1226
|
+
// a `/vol-store` pairing, and fails on one it does not name. They are kept because a
|
|
1149
1227
|
// variation has to be able to LOSE on its own terms: the admission posture (compareScored orders
|
|
1150
1228
|
// by score) is what makes a wrong respelled source harmless, and it is only observable when the
|
|
1151
1229
|
// single-variation candidate is in the fan —
|
|
@@ -1666,6 +1744,17 @@ export function enumerateCandidates(
|
|
|
1666
1744
|
// Which placement the source used is not derivable from the asm, so both are emitted and the
|
|
1667
1745
|
// differ referees.
|
|
1668
1746
|
respell(['sinkinit'], () => sinkInitsToFirstUse(sfn));
|
|
1747
|
+
// `/int-cell` — every pointer cell spelled as the integer a project may have declared it,
|
|
1748
|
+
// `g = g + K` and no conversions (l3/intcell.ts). The default's `g = (void *)((u8 *)g + K)` is
|
|
1749
|
+
// right under a pointer declaration of g and rejected by CodeWarrior under an integer one, where
|
|
1750
|
+
// this is the source; which the project declared is not in the asm, so it is published only at
|
|
1751
|
+
// a byte-exact score.
|
|
1752
|
+
respell(['int-cell'], () =>
|
|
1753
|
+
integerCells(sfn, (n) => {
|
|
1754
|
+
const shape = mapSymbols?.get(n)?.shape;
|
|
1755
|
+
return shape !== undefined && shape !== 'scalar';
|
|
1756
|
+
}),
|
|
1757
|
+
);
|
|
1669
1758
|
// the register-copy variation (l3/regspell.ts): 0–3 results (base; tail assign-back reusing
|
|
1670
1759
|
// the dead value var; tail assign-back into a fresh var — the tail decision is allocator-
|
|
1671
1760
|
// ambiguous, so both are ranked).
|
|
@@ -1707,8 +1796,13 @@ export function enumerateCandidates(
|
|
|
1707
1796
|
{ variations: ['raw-globals'], symbols: undefined },
|
|
1708
1797
|
]
|
|
1709
1798
|
: [{ variations: [] }];
|
|
1799
|
+
// The map's POINTER declarations go to the raw setting all the same: a raw name is still a
|
|
1800
|
+
// pointer in the project's file, so arithmetic on its value scales unless spelled in bytes.
|
|
1801
|
+
const pointerGlobals = new Set(
|
|
1802
|
+
[...(baseOpts.symbols?.entries() ?? [])].filter(([, info]) => info.shape === 'pointer').map(([n]) => n),
|
|
1803
|
+
);
|
|
1710
1804
|
for (const [symbolIndex, symbolSetting] of symbolSettings.entries()) {
|
|
1711
|
-
const symbolSettingOpts = symbolSetting.symbols ? baseOpts : { ...baseOpts, symbols: undefined };
|
|
1805
|
+
const symbolSettingOpts = symbolSetting.symbols ? baseOpts : { ...baseOpts, symbols: undefined, pointerGlobals };
|
|
1712
1806
|
// `/no-bitfield` names a spelling the MAP makes available, so it has no inhabitant on the
|
|
1713
1807
|
// symbol-map setting that structures without one: structure() normalizes `spellBitfieldMembers`
|
|
1714
1808
|
// to false when `symbols` is absent, so both settings structure the identical tree whatever
|
|
@@ -1728,7 +1822,21 @@ export function enumerateCandidates(
|
|
|
1728
1822
|
const usableStructureSettings = symbolSetting.symbols
|
|
1729
1823
|
? structureSettings
|
|
1730
1824
|
: structureSettings.filter((s) => s.bitfields && s.ptrElems && (s.declRank || rawDerivesRank));
|
|
1731
|
-
|
|
1825
|
+
// THE `/raw-globals` SETTING IS A VARIATION, so a lift that declines without the map drops it
|
|
1826
|
+
// and keeps the map's candidates (`abortsRow`). The two lifts can disagree on whether a
|
|
1827
|
+
// function lifts at all: a name only the map places is, without it, a pointer the frame-object
|
|
1828
|
+
// audit cannot bound. This is the setting's first lift, and every later one is the same lift
|
|
1829
|
+
// again.
|
|
1830
|
+
let treeOwnedFold: boolean;
|
|
1831
|
+
try {
|
|
1832
|
+
treeOwnedFold = treeOwnedIn(symbolSetting.symbols);
|
|
1833
|
+
} catch (e) {
|
|
1834
|
+
if (abortsRow(symbolSetting.variations)) {
|
|
1835
|
+
throw e;
|
|
1836
|
+
}
|
|
1837
|
+
reportThrow(symbolSetting.variations, e);
|
|
1838
|
+
continue;
|
|
1839
|
+
}
|
|
1732
1840
|
// The signedness variation DECLINES where the pin has nothing to pin. `pinScalarParams` writes only
|
|
1733
1841
|
// over an entry param still `unknown`/`int` that is not one of the recovered pointers/
|
|
1734
1842
|
// aggregates `ptrIdx` excludes; where no param is left, the second pass re-lifts, re-raises and
|
|
@@ -1744,7 +1852,7 @@ export function enumerateCandidates(
|
|
|
1744
1852
|
if (cand.signed && !pinnable) {
|
|
1745
1853
|
break;
|
|
1746
1854
|
}
|
|
1747
|
-
const base =
|
|
1855
|
+
const base = liftStamped(name, asm, target, prototypes, opts.asmData, symbolSetting.symbols);
|
|
1748
1856
|
// `/setup-args` — pass a prototype-less callee only what the CALLING BLOCK set up; which of
|
|
1749
1857
|
// the two readings the source spelled is genuinely ambiguous, and frontend/ssa.ts
|
|
1750
1858
|
// narrowToSetupArgs carries the argument for why the differ is what settles it.
|
|
@@ -1815,6 +1923,7 @@ export function enumerateCandidates(
|
|
|
1815
1923
|
);
|
|
1816
1924
|
for (const liftSetting of liftSettings) {
|
|
1817
1925
|
let fn: Fn;
|
|
1926
|
+
let discardsPassedResult = false;
|
|
1818
1927
|
let inferredSymbols = new Map<string, SymbolInfo>();
|
|
1819
1928
|
let orderLicensed: ReadonlySet<string> = new Set<string>();
|
|
1820
1929
|
try {
|
|
@@ -1823,10 +1932,11 @@ export function enumerateCandidates(
|
|
|
1823
1932
|
fn =
|
|
1824
1933
|
liftSetting.variations.length === 0
|
|
1825
1934
|
? base
|
|
1826
|
-
:
|
|
1935
|
+
: liftStamped(name, asm, target, prototypes, opts.asmData, symbolSetting.symbols);
|
|
1827
1936
|
if (liftSetting.narrow && !narrowToSetupArgs(fn)) {
|
|
1828
1937
|
continue; // nothing to cut after all — the default lift's own candidates already cover it
|
|
1829
1938
|
}
|
|
1939
|
+
discardsPassedResult = liftSetting.narrow && setupArgsDiscardsPassedResult(fn);
|
|
1830
1940
|
verify(fn);
|
|
1831
1941
|
// This lift's OWN array-shape evidence, off its own lifted fn (a symbol map promotes
|
|
1832
1942
|
// numeric pool words to `gaddr`, so the `/raw-globals` setting genuinely answers differently).
|
|
@@ -1855,6 +1965,13 @@ export function enumerateCandidates(
|
|
|
1855
1965
|
// declaration. A map that names the symbol an array takes the access to a bare `var`
|
|
1856
1966
|
// base anyway, which carries no licence: the two never meet.
|
|
1857
1967
|
orderLicensed = orderLicensedGlobals(fn, target);
|
|
1968
|
+
// …and the NAMES the map holds for the addresses this lift built by arithmetic off a
|
|
1969
|
+
// named one (raise/offsetnames.ts). Per lift setting, off that setting's own map: the
|
|
1970
|
+
// `/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.
|
|
1972
|
+
if (symbolSetting.symbols) {
|
|
1973
|
+
OFFSET_NAME_PASS.run(fn, symbolSetting.symbols, target.compilerBehaviors);
|
|
1974
|
+
}
|
|
1858
1975
|
for (const [n, si] of [...inferredSymbols]) {
|
|
1859
1976
|
if (baseOpts.symbols?.has(n) === true || !sameDerivedShape(declSymbols.get(n), si)) {
|
|
1860
1977
|
inferredSymbols.delete(n);
|
|
@@ -1879,14 +1996,15 @@ export function enumerateCandidates(
|
|
|
1879
1996
|
);
|
|
1880
1997
|
} catch (e) {
|
|
1881
1998
|
// THE DEFAULT CARRIES NO VARIATION, by construction: every lift variation appends one, so
|
|
1882
|
-
//
|
|
1999
|
+
// an empty list is the only spelling of "no lift variation is on" that stays correct
|
|
1883
2000
|
// when a fourth is added — the same reason the structure half below reads its table
|
|
1884
2001
|
// instead of naming its flags.
|
|
1885
|
-
|
|
2002
|
+
const settingVariations = [...liftSetting.variations, ...symbolSetting.variations];
|
|
2003
|
+
if (abortsRow(settingVariations)) {
|
|
1886
2004
|
throw e; // the default lift keeps its behavior: a raising failure aborts the row
|
|
1887
2005
|
}
|
|
1888
2006
|
// A dropped variation, never an aborted enumeration — the same posture as `respell`.
|
|
1889
|
-
reportThrow(
|
|
2007
|
+
reportThrow(settingVariations, e);
|
|
1890
2008
|
continue;
|
|
1891
2009
|
}
|
|
1892
2010
|
// THE SHARED-TAIL VARIATIONS: the same raised fn, structured again with `followEarlyReturns`,
|
|
@@ -1894,7 +2012,8 @@ export function enumerateCandidates(
|
|
|
1894
2012
|
// - `/shared-ret` is the follow ALONE, on the fn as raised. Some divergent `if` shares a
|
|
1895
2013
|
// `ret` the compiler left in place (`synthetic:gcseinner`).
|
|
1896
2014
|
// - `/shared-tail` is the follow after `sinkStoreTails` has rewritten the fn in place, which
|
|
1897
|
-
// is safe because `structure()`
|
|
2015
|
+
// is safe because `structure()` writes nothing on `fn` but the `declared` stamps, which it
|
|
2016
|
+
// re-derives from its map on every call, so the earlier passes are done with it.
|
|
1898
2017
|
// It is enumerated only where the sink changed something AND some divergent `if` of the
|
|
1899
2018
|
// SUNK fn shares a `ret`. That is the sink's price gate: without it, a tail copied into
|
|
1900
2019
|
// arms no `if` shares adds a candidate spelling it in each — 204 synthetic candidates on 8
|
|
@@ -1942,7 +2061,7 @@ export function enumerateCandidates(
|
|
|
1942
2061
|
verify(fn);
|
|
1943
2062
|
}
|
|
1944
2063
|
} catch (e) {
|
|
1945
|
-
reportThrow([...liftSetting.variations, 'shared-tail'], e);
|
|
2064
|
+
reportThrow([...liftSetting.variations, 'shared-tail', ...symbolSetting.variations], e);
|
|
1946
2065
|
break;
|
|
1947
2066
|
}
|
|
1948
2067
|
// Unsunk, this fn is the `/shared-ret` pass's again.
|
|
@@ -1996,8 +2115,9 @@ export function enumerateCandidates(
|
|
|
1996
2115
|
dropped.add(key);
|
|
1997
2116
|
continue;
|
|
1998
2117
|
}
|
|
1999
|
-
// structure()
|
|
2000
|
-
// senses structure the same
|
|
2118
|
+
// structure() produces a fresh SFn and writes nothing on `fn` but the `declared` stamps,
|
|
2119
|
+
// which it re-derives from its map on every call, so both branch senses structure the same
|
|
2120
|
+
// recovered function without re-lifting.
|
|
2001
2121
|
let sfn: SFn;
|
|
2002
2122
|
try {
|
|
2003
2123
|
sfn = structureChecked(fn, {
|
|
@@ -2019,7 +2139,7 @@ export function enumerateCandidates(
|
|
|
2019
2139
|
...(alternative ? { followEarlyReturns: true } : {}),
|
|
2020
2140
|
});
|
|
2021
2141
|
} catch (e) {
|
|
2022
|
-
if (liftVariations.
|
|
2142
|
+
if (abortsRow([...liftVariations, ...symbolSetting.variations], s)) {
|
|
2023
2143
|
throw e; // the default lift's default setting keeps its behavior: a failure aborts the row
|
|
2024
2144
|
}
|
|
2025
2145
|
// Recorded for EVERY dropped setting: a candidate with more variations on looks its siblings
|
|
@@ -2027,7 +2147,7 @@ export function enumerateCandidates(
|
|
|
2027
2147
|
dropped.add(key);
|
|
2028
2148
|
// an anchored setting that fails structuring or its contracts is a dropped variation, never
|
|
2029
2149
|
// an aborted enumeration — same rule as respell below
|
|
2030
|
-
reportThrow([...liftVariations, ...s.variations], e);
|
|
2150
|
+
reportThrow([...liftVariations, ...s.variations, ...symbolSetting.variations], e);
|
|
2031
2151
|
continue;
|
|
2032
2152
|
}
|
|
2033
2153
|
// A TREE another structure setting already produced. `respellTree` reads the tree and this
|
|
@@ -2092,6 +2212,7 @@ export function enumerateCandidates(
|
|
|
2092
2212
|
assertResolved(made);
|
|
2093
2213
|
assertDerefsTyped(made);
|
|
2094
2214
|
assertLocalsWritten(made);
|
|
2215
|
+
assertPostIncrUnshared(made);
|
|
2095
2216
|
assertNoOrphanedLocals(sfn, made);
|
|
2096
2217
|
// A backend refusal on this REWRITTEN tree is not a refusal of the row's own
|
|
2097
2218
|
// tree, so it never becomes the row's stated cause: `TreeSources.emit` is dropped
|
|
@@ -2154,6 +2275,9 @@ export function enumerateCandidates(
|
|
|
2154
2275
|
if (sp.matchOnly === undefined) {
|
|
2155
2276
|
delete dup.matchOnly;
|
|
2156
2277
|
}
|
|
2278
|
+
if (!discardsPassedResult) {
|
|
2279
|
+
delete dup.discardsPassedResult;
|
|
2280
|
+
}
|
|
2157
2281
|
continue;
|
|
2158
2282
|
}
|
|
2159
2283
|
const variations: readonly Variation[] = [
|
|
@@ -2176,6 +2300,7 @@ export function enumerateCandidates(
|
|
|
2176
2300
|
...(sp.symbolRefs ? { symbolRefs: sp.symbolRefs } : {}),
|
|
2177
2301
|
...(sp.deviceVolatile ? { deviceVolatile: sp.deviceVolatile } : {}),
|
|
2178
2302
|
...(sp.matchOnly ? { matchOnly: sp.matchOnly } : {}),
|
|
2303
|
+
...(discardsPassedResult ? { discardsPassedResult: true as const } : {}),
|
|
2179
2304
|
};
|
|
2180
2305
|
seen.set(source, made);
|
|
2181
2306
|
out.push(made);
|
|
@@ -2200,43 +2325,86 @@ export function enumerateCandidates(
|
|
|
2200
2325
|
/** Score each candidate with the injected `scoreFn` and rank by score (lowest first). A candidate
|
|
2201
2326
|
* whose `scoreFn` throws — e.g. its C failed to compile — is SKIPPED so it cannot sink a sibling
|
|
2202
2327
|
* that compiles and matches; only if EVERY candidate fails is the failure surfaced. Synchronous:
|
|
2203
|
-
* the scorer must be sync (the cli/Node objdiff path).
|
|
2204
|
-
* its
|
|
2205
|
-
*
|
|
2328
|
+
* the scorer must be sync (the cli/Node objdiff path). An async driver scores on its own schedule
|
|
2329
|
+
* and then ranks through here against its memoized outcomes (the pooled CLI driver, the webapp).
|
|
2330
|
+
*
|
|
2331
|
+
* A STILLBORN fan (stillborn.ts) is not compiled to the end: `scoreFn` is called for the default
|
|
2332
|
+
* candidate and the probes, and the rest ride on the thrown error as `notCompiled`. A `scoreFn`
|
|
2333
|
+
* that wants the rule to read its refusals throws `CompilerRejection`; any other throw ranks the
|
|
2334
|
+
* whole fan. */
|
|
2206
2335
|
export function rankBy<S extends { score: number; rows?: number }>(
|
|
2207
2336
|
candidates: Candidate[],
|
|
2208
2337
|
symbol: string,
|
|
2209
2338
|
scoreFn: (source: string, symbol: string, candidate: Candidate) => S,
|
|
2210
2339
|
): RankedResult<S> {
|
|
2340
|
+
const outcomes = new Map<number, { score: S } | { thrown: unknown }>();
|
|
2341
|
+
const outcomeAt = (i: number): { score: S } | { thrown: unknown } => {
|
|
2342
|
+
let outcome = outcomes.get(i);
|
|
2343
|
+
if (outcome === undefined) {
|
|
2344
|
+
try {
|
|
2345
|
+
outcome = { score: scoreFn(candidates[i].source, symbol, candidates[i]) };
|
|
2346
|
+
} catch (e) {
|
|
2347
|
+
outcome = { thrown: e };
|
|
2348
|
+
}
|
|
2349
|
+
outcomes.set(i, outcome);
|
|
2350
|
+
}
|
|
2351
|
+
return outcome;
|
|
2352
|
+
};
|
|
2353
|
+
const stillborn = stillbornVerdict(candidates, (i) => {
|
|
2354
|
+
const outcome = outcomeAt(i);
|
|
2355
|
+
return 'score' in outcome ? 'compiled' : outcome;
|
|
2356
|
+
});
|
|
2357
|
+
if (stillborn !== null) {
|
|
2358
|
+
const thrownAt = (i: number): unknown => {
|
|
2359
|
+
const outcome = outcomeAt(i);
|
|
2360
|
+
if ('score' in outcome) {
|
|
2361
|
+
throw new Error(`internal: a stillborn fan holds a candidate that scored (${i})`);
|
|
2362
|
+
}
|
|
2363
|
+
return outcome.thrown;
|
|
2364
|
+
};
|
|
2365
|
+
throw new NoScorableCandidateError(
|
|
2366
|
+
`no scorable candidate for '${symbol}': ${stillbornNote(stillborn, candidates.length)}\n` +
|
|
2367
|
+
`The default candidate's compile: ${fullMessage(thrownAt(0))}`,
|
|
2368
|
+
stillborn.compiled.map((i) => ({ variations: candidates[i].variations, error: firstLine(thrownAt(i)) })),
|
|
2369
|
+
[],
|
|
2370
|
+
stillborn.notCompiled.map((i) => ({ variations: candidates[i].variations, source: candidates[i].source })),
|
|
2371
|
+
{ cause: thrownAt(0) },
|
|
2372
|
+
);
|
|
2373
|
+
}
|
|
2211
2374
|
const results: (Scored<S> & { order: number })[] = [];
|
|
2212
2375
|
const dropped: DroppedCandidate[] = []; // candidates that failed to build; only fatal if ALL do
|
|
2213
2376
|
const withheld: WithheldCandidate[] = []; // candidates that built but did not earn publication
|
|
2214
2377
|
let lastScoreErr: unknown = null;
|
|
2215
2378
|
candidates.forEach((c, order) => {
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2379
|
+
const outcome = outcomeAt(order);
|
|
2380
|
+
if ('thrown' in outcome) {
|
|
2381
|
+
lastScoreErr = outcome.thrown;
|
|
2382
|
+
dropped.push({ variations: c.variations, error: firstLine(outcome.thrown) });
|
|
2383
|
+
return;
|
|
2384
|
+
}
|
|
2385
|
+
const { score } = outcome;
|
|
2386
|
+
const why = withheldReason(c, score);
|
|
2387
|
+
if (why !== null) {
|
|
2388
|
+
withheld.push({
|
|
2389
|
+
variations: c.variations,
|
|
2390
|
+
score: score.score,
|
|
2391
|
+
...(score.rows === undefined ? {} : { rows: score.rows }),
|
|
2392
|
+
why,
|
|
2393
|
+
});
|
|
2394
|
+
return;
|
|
2232
2395
|
}
|
|
2396
|
+
results.push({ ...c, order, score });
|
|
2233
2397
|
});
|
|
2234
2398
|
if (results.length === 0) {
|
|
2235
2399
|
// Naming the withheld count matters here: "no scorable candidate" with a null cause reads as a
|
|
2236
2400
|
// scorer failure, and a list that was entirely proof-gated is a different thing entirely.
|
|
2401
|
+
// NOT `firstLine`: a compile failure puts the COMMAND on line one and the compiler's own
|
|
2402
|
+
// complaint after it, so summarising to one line published the thing the reader already knows
|
|
2403
|
+
// and dropped the only sentence that says what went wrong. The per-candidate `dropped` list
|
|
2404
|
+
// above stays one line each — it is a roster, not a diagnosis.
|
|
2237
2405
|
const why =
|
|
2238
|
-
lastScoreErr !== null ?
|
|
2239
|
-
throw new NoScorableCandidateError(`no scorable candidate for '${symbol}': ${why}`, dropped, withheld, {
|
|
2406
|
+
lastScoreErr !== null ? fullMessage(lastScoreErr) : `${withheld.length} candidate(s) withheld, none scored`;
|
|
2407
|
+
throw new NoScorableCandidateError(`no scorable candidate for '${symbol}': ${why}`, dropped, withheld, [], {
|
|
2240
2408
|
cause: lastScoreErr,
|
|
2241
2409
|
});
|
|
2242
2410
|
}
|
|
@@ -2244,15 +2412,36 @@ export function rankBy<S extends { score: number; rows?: number }>(
|
|
|
2244
2412
|
return { winner: results[0], candidates: results.map(({ order: _order, ...c }) => c), dropped, withheld };
|
|
2245
2413
|
}
|
|
2246
2414
|
|
|
2247
|
-
/**
|
|
2248
|
-
*
|
|
2249
|
-
*
|
|
2250
|
-
*
|
|
2415
|
+
/** The sentence a stillborn fan's error OPENS on: how much of the fan was never compiled, on what
|
|
2416
|
+
* evidence, and the errors each half's compiled candidates kept. The DEFAULT candidate's own
|
|
2417
|
+
* diagnostic — the spelling the rule is anchored on, rather than whichever was refused last —
|
|
2418
|
+
* follows it, so that a printer bounding a long diagnostic keeps the verdict. */
|
|
2419
|
+
function stillbornNote(stillborn: Stillborn, fan: number): string {
|
|
2420
|
+
const tally = new Map<string, number>();
|
|
2421
|
+
for (const m of stillborn.messages) {
|
|
2422
|
+
tally.set(m, (tally.get(m) ?? 0) + 1);
|
|
2423
|
+
}
|
|
2424
|
+
const shared = [...tally].map(([m, n]) => ` ${m}${n > 1 ? ` (x${n})` : ''}`).join('\n');
|
|
2425
|
+
return (
|
|
2426
|
+
`${stillborn.notCompiled.length} of ${fan} candidates were NOT COMPILED: in each signedness half, the default ` +
|
|
2427
|
+
`candidate and one probe per variation (${stillborn.compiled.length} compiled) were all rejected, every probe ` +
|
|
2428
|
+
`keeping one of its default's errors:\n${shared}`
|
|
2429
|
+
);
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
/** THE candidate ordering — score, then semantics, then preference, then readability, then enumeration
|
|
2433
|
+
* order. Exported because it is asked outside `rankBy` too — the benchmark's sweep driver and
|
|
2434
|
+
* fan runner order what they read back — and a second copy would let the same input produce two
|
|
2435
|
+
* different winners.
|
|
2251
2436
|
*
|
|
2252
2437
|
* SCORE dominates absolutely: the differ is the fitness function, and a tie means the variation
|
|
2253
2438
|
* that separates these two candidates did not change the bytes — so everything below only chooses what
|
|
2254
2439
|
* the READER sees, and can never cost a match.
|
|
2255
2440
|
*
|
|
2441
|
+
* A DISCARDED PASSED RESULT next (`Candidate.discardsPassedResult`): equal bytes refute neither
|
|
2442
|
+
* reading, so the one that drops a value the callee may read wins only where it scores strictly
|
|
2443
|
+
* better.
|
|
2444
|
+
*
|
|
2256
2445
|
* PREFERENCE next: a named symbol-map spelling beats its `/raw-globals` sibling at equal bytes.
|
|
2257
2446
|
*
|
|
2258
2447
|
* DEVICE VOLATILITY next: at equal bytes, the spelling that qualifies a DEVICE REGISTER
|
|
@@ -2270,7 +2459,9 @@ export function rankBy<S extends { score: number; rows?: number }>(
|
|
|
2270
2459
|
* with no score and no outcome moving. That is a judgement about the source rather than a
|
|
2271
2460
|
* measurement of it — the differ never refereed those six — and it is the same judgement this
|
|
2272
2461
|
* term was declared to make, taken on the same evidence. What it must never do is change WHICH
|
|
2273
|
-
* candidates exist; that stays an admission question, one variation at a time.
|
|
2462
|
+
* candidates exist; that stays an admission question, one variation at a time. The CAST COUNT
|
|
2463
|
+
* below does the same for `/narrow-decl`, which drops one cast per local it narrows and so wins
|
|
2464
|
+
* every tie against the candidate it is derived onto.
|
|
2274
2465
|
*
|
|
2275
2466
|
* CAST COUNT next, and only WITHIN a preference. A wrong signedness pin is what manufactures casts —
|
|
2276
2467
|
* the C backend has to cast a shift operand back to the signedness the machine op needs, so
|
|
@@ -2301,6 +2492,7 @@ export function compareScored<S extends { score: number }>(
|
|
|
2301
2492
|
): number {
|
|
2302
2493
|
return (
|
|
2303
2494
|
a.score.score - b.score.score ||
|
|
2495
|
+
(a.discardsPassedResult ? 1 : 0) - (b.discardsPassedResult ? 1 : 0) ||
|
|
2304
2496
|
a.preference - b.preference ||
|
|
2305
2497
|
(b.deviceVolatile ?? 0) - (a.deviceVolatile ?? 0) ||
|
|
2306
2498
|
castCount(a.source) - castCount(b.source) ||
|
|
@@ -2343,3 +2535,7 @@ function castCount(source: string): number {
|
|
|
2343
2535
|
function firstLine(e: unknown): string {
|
|
2344
2536
|
return e instanceof Error ? e.message.split('\n')[0] : String(e);
|
|
2345
2537
|
}
|
|
2538
|
+
|
|
2539
|
+
function fullMessage(e: unknown): string {
|
|
2540
|
+
return e instanceof Error ? e.message : String(e);
|
|
2541
|
+
}
|