@asmlift/core 0.8.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -31
- package/package.json +1 -1
- package/src/aggregate.ts +397 -0
- package/src/backend/c.ts +6 -2
- package/src/backend/cfamily.ts +370 -56
- package/src/backend/cpp.ts +116 -10
- package/src/backend/pascal.ts +69 -11
- package/src/cdecl/constant.ts +59 -0
- package/src/cdecl/lex.ts +209 -0
- package/src/cdecl/parse.ts +975 -0
- package/src/cdecl/spell.ts +69 -0
- package/src/codegen-flags.ts +38 -6
- package/src/compiler-diagnostics.ts +194 -0
- package/src/contracts.ts +777 -84
- package/src/declare.ts +132 -13
- package/src/frontend/asmdata.ts +26 -6
- package/src/frontend/call-plan.ts +1590 -0
- package/src/frontend/dead-reads.ts +628 -0
- package/src/frontend/device-pins.ts +296 -0
- package/src/frontend/emit.ts +4 -3
- package/src/frontend/errors.ts +5 -0
- package/src/frontend/flags-edge.ts +101 -0
- package/src/frontend/fpu.ts +374 -0
- package/src/frontend/frame-objects.ts +3171 -0
- package/src/frontend/frontend.ts +64 -3
- package/src/frontend/high-half.ts +55 -5
- package/src/frontend/jump-table.ts +530 -0
- package/src/frontend/local-object.ts +616 -0
- package/src/frontend/mips.ts +2392 -208
- package/src/frontend/opaque.ts +97 -18
- package/src/frontend/own-struct-return.ts +500 -0
- package/src/frontend/ppc.ts +3137 -267
- package/src/frontend/register-struct-call.ts +484 -0
- package/src/frontend/reloc-symbol.ts +109 -13
- package/src/frontend/splat.ts +108 -5
- package/src/frontend/ssa.ts +2098 -89
- package/src/frontend/stackargs.ts +172 -42
- package/src/frontend/thumb.ts +4563 -2091
- package/src/ir/alias.ts +71 -0
- package/src/ir/bits.ts +681 -1
- package/src/ir/core.ts +199 -6
- package/src/ir/discipline.ts +313 -0
- package/src/ir/entry.ts +159 -0
- package/src/ir/float-bits.ts +47 -0
- package/src/{structure → ir}/loops.ts +6 -2
- package/src/ir/opcodes.ts +193 -121
- package/src/ir/parse.ts +1 -0
- package/src/ir/types.ts +202 -2
- package/src/ir/verify.ts +219 -3
- package/src/l3/address.ts +5 -0
- package/src/l3/argcopy.ts +38 -6
- package/src/l3/ast.ts +467 -23
- package/src/l3/basecse.ts +33 -5
- package/src/l3/coalesce.ts +21 -13
- package/src/l3/constinit.ts +382 -0
- package/src/l3/dce.ts +34 -12
- package/src/l3/gates.ts +9 -3
- package/src/l3/hoist.ts +8 -7
- package/src/l3/initfirst.ts +68 -19
- package/src/l3/intcell.ts +98 -0
- package/src/l3/mentions.ts +10 -4
- package/src/l3/narrowdecl.ts +259 -0
- package/src/l3/nearbase.ts +10 -5
- package/src/l3/parkfirst.ts +8 -5
- package/src/l3/pollguard.ts +11 -4
- package/src/l3/ptrcell.ts +354 -0
- package/src/l3/ptrfield.ts +123 -8
- package/src/l3/regspell.ts +26 -7
- package/src/l3/reindex.ts +293 -103
- package/src/l3/storage.ts +98 -1
- package/src/l3/symbol-refs.ts +182 -23
- package/src/l3/tailmerge.ts +9 -20
- package/src/l3/typing.ts +237 -32
- package/src/l3/unreduce.ts +10 -9
- package/src/l3/volatileptr.ts +17 -54
- package/src/l3/volstore.ts +96 -7
- package/src/mangle.ts +41 -1
- package/src/pattern/engine.ts +373 -82
- package/src/pipeline.ts +589 -27
- package/src/proto-context.ts +1648 -0
- package/src/proto.ts +1203 -49
- package/src/raise/aggmembers.ts +290 -0
- package/src/raise/arrays.ts +8 -3
- package/src/raise/declared-volatile.ts +307 -0
- package/src/raise/divpow2.ts +2 -2
- package/src/raise/errors.ts +17 -0
- package/src/raise/floathelpers.ts +464 -0
- package/src/raise/globalshape.ts +11 -5
- package/src/raise/gvn.ts +29 -8
- package/src/raise/latch.ts +162 -6
- package/src/raise/memberarrays.ts +10 -4
- package/src/raise/narrow.ts +29 -2
- package/src/raise/narrowlocal.ts +11 -6
- package/src/raise/offsetnames.ts +494 -0
- package/src/raise/pairparams.ts +114 -0
- package/src/raise/paramwidth.ts +203 -6
- package/src/raise/pre-recovery.ts +174 -17
- package/src/raise/pre-structure.ts +54 -0
- package/src/raise/recover.ts +26 -15
- package/src/raise/reroll.ts +694 -0
- package/src/raise/retsink.ts +1 -1
- package/src/raise/shortcircuit.ts +66 -30
- package/src/raise/softdiv.ts +21 -23
- package/src/raise/storesign.ts +153 -0
- package/src/raise/struct-arrays.ts +34 -14
- package/src/raise/structs.ts +674 -98
- package/src/raise/truncload.ts +367 -0
- package/src/raise/unthread.ts +400 -0
- package/src/raise/widehelpers.ts +106 -0
- package/src/rank-declare.ts +143 -12
- package/src/rank-variations.ts +117 -31
- package/src/rank.ts +563 -122
- package/src/runtime-helpers.ts +405 -0
- package/src/stillborn.ts +289 -0
- package/src/structure/analysis.ts +1449 -449
- package/src/structure/arith-ops.ts +23 -0
- package/src/structure/globalaccess.ts +111 -9
- package/src/structure/hazards.ts +641 -114
- package/src/structure/local-statics.ts +517 -0
- package/src/structure/namecoalesce.ts +2 -2
- package/src/structure/pointer-spelling.ts +1106 -0
- package/src/structure/redundant-test.ts +99 -0
- package/src/structure/structure.ts +3478 -975
- package/src/structure/switch-recover.ts +403 -178
- package/src/symbols.ts +44 -3
- package/src/target.ts +1387 -71
- package/src/trace.ts +66 -28
- package/src/variation-definitions.ts +243 -13
- package/src/variation-gates.ts +2 -0
- package/src/variation-tokens.ts +25 -7
package/src/stillborn.ts
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
// asmlift — THE STILLBORN FAN: when to stop compiling a fan that cannot compile.
|
|
2
|
+
//
|
|
3
|
+
// A fan is a product of variations over ONE function body, and every variation only re-spells
|
|
4
|
+
// statements. When the body holds a statement the compiler refuses for a reason no variation
|
|
5
|
+
// reaches — a call with more arguments than the project's header declares, a struct-typed global
|
|
6
|
+
// read as a scalar — every candidate is refused for it, and the fan's size is the number of times
|
|
7
|
+
// the same sentence gets printed: 30,240 on `kleod:PauseMenuScreenHandler`.
|
|
8
|
+
//
|
|
9
|
+
// THE RULE. The DEFAULT candidate (first in enumeration order) is compiled first. Only if the
|
|
10
|
+
// compiler REJECTS it is anything else asked: a PROBE per variation name — the candidate with the
|
|
11
|
+
// fewest variations that carries it, per signedness half (HALF BY HALF) — is compiled, and the fan
|
|
12
|
+
// is stillborn only when the default holds an error that SURVIVES every probe: a message the probe
|
|
13
|
+
// was rejected with too, exactly as many times as the default was (compiler-diagnostics.ts
|
|
14
|
+
// `errorMessages`, positions normalised away). Then the remaining candidates are NOT COMPILED, and
|
|
15
|
+
// are reported as exactly that.
|
|
16
|
+
//
|
|
17
|
+
// WHAT IT BETS ON: that a statement no probe re-spells is re-spelled by no product of the probes'
|
|
18
|
+
// variations either, so its error is in every candidate. That is a bet, not a theorem — a product
|
|
19
|
+
// is not the sum of its variations (THE RESIDUALS, below) — and the rule never compiles a product
|
|
20
|
+
// to check it.
|
|
21
|
+
//
|
|
22
|
+
// One survivor is enough; the probes may reach the default's other errors. In
|
|
23
|
+
// `ac-decomp:aINS_destruct:mwcc_242_81`'s vendored-context attempt `/setup-args` cures the call to
|
|
24
|
+
// `mPlib_Get_item_net_catch_label`, both `illegal implicit conversion` errors stay, and the fan of
|
|
25
|
+
// 12 stops after 8 compiles.
|
|
26
|
+
//
|
|
27
|
+
// EXACTLY as many times: a probe that prints the survivor more often has re-spelled a statement
|
|
28
|
+
// that raises it (`/reread-globals` re-reading a global whose type is refused), and a probe that
|
|
29
|
+
// prints it fewer times has cured one. Either one reached it, so neither vouches that a product
|
|
30
|
+
// leaves it alone.
|
|
31
|
+
//
|
|
32
|
+
// PER ATTEMPT. A rejection may hold several compiles (compiler-diagnostics.ts `attemptsOf`: the
|
|
33
|
+
// benchmark's real tier tries every candidate in a ladder of contexts and two dialects), and a
|
|
34
|
+
// candidate compiles when ANY attempt does. So a survivor is asked of EACH attempt, and one attempt
|
|
35
|
+
// without a survivor keeps the fan alive: an attempt no candidate can compile in — a context that
|
|
36
|
+
// cannot open its own headers — prints the same sentences for every candidate, and those survive
|
|
37
|
+
// every probe while another attempt's product compiles. Every probe must have been tried in the
|
|
38
|
+
// same attempts, under the same labels, as the default.
|
|
39
|
+
//
|
|
40
|
+
// HALF BY HALF. Every fan is enumerated at BOTH signednesses (variation-tokens.ts, kind
|
|
41
|
+
// `signedness`), and a signedness re-types the whole body, so the survivor is worded once per half:
|
|
42
|
+
// aINS_destruct's `illegal implicit conversion` to `struct game_s *` names `unsigned long` in one
|
|
43
|
+
// half and `long` in the other, and asked over the whole fan nothing in its vendored-context
|
|
44
|
+
// attempt survives the `signed` probe. So each half is asked as a fan of its own — its first
|
|
45
|
+
// candidate is its default, its probes are the smallest carrier of each variation name INSIDE it —
|
|
46
|
+
// and the fan is stillborn only when every half is. A half is found by the registry's kind, never
|
|
47
|
+
// by a name, and a fan with no signedness in its names is one half. A stopped fan pays both halves'
|
|
48
|
+
// probes.
|
|
49
|
+
//
|
|
50
|
+
// KNOWN GAP: a variation that re-words the survivor in PART of the fan is not split on, so its
|
|
51
|
+
// probe leaves that attempt without a survivor and the fan is ranked whole —
|
|
52
|
+
// `kleod:WorldMapScreenUnlockNewWorld:agbcc`'s only vendored-context error, `incompatible types in
|
|
53
|
+
// assignment`, reads `invalid operands to binary &` under `/derived-home` and `/setup-args`.
|
|
54
|
+
//
|
|
55
|
+
// Anything else ranks the whole fan. A probe that compiles or is withheld: the fan is alive. An
|
|
56
|
+
// attempt with no survivor: a product of variations may cure what no single one does. An attempt
|
|
57
|
+
// whose errors are not read — none recognised, or the compiler stopped reporting — or an attempt
|
|
58
|
+
// list that differs from the default's: an unread sentence equals nothing. A throw that is not a
|
|
59
|
+
// `CompilerRejection`: a timeout or a killed compiler says nothing about the candidate.
|
|
60
|
+
//
|
|
61
|
+
// THE RESIDUALS, which this rule does NOT close: a product that cures the survivor while every
|
|
62
|
+
// probe keeps it. One survivor being enough, a stop may rest its whole bet on a single message in
|
|
63
|
+
// the one attempt a candidate could compile in. Two shapes are known.
|
|
64
|
+
//
|
|
65
|
+
// JOINT CURE: ONE error MESSAGE that needs TWO variations together. `a & b` is `invalid operands
|
|
66
|
+
// to binary &` while either operand is a struct; if one variation re-types `a` and another re-types
|
|
67
|
+
// `b`, each probe leaves the message where it was, and only their product compiles. The same shape
|
|
68
|
+
// arises without any re-typing wherever the compiler prints ONE message for a statement holding TWO
|
|
69
|
+
// defects: mwcc and IDO report `y = g(1, 2) + h(3, 4)` as a single message, and agbcc, kmc and IDO
|
|
70
|
+
// report an undeclared name once however many times it is used. Two DIFFERENT messages each cured
|
|
71
|
+
// by its own variation are not this case: each probe removes its message, and neither survives.
|
|
72
|
+
// Signedness re-types operands and can cure half a statement — against a declared
|
|
73
|
+
// `CARDGetSectorSize(long, unsigned long *)`, `signed` turns a call's `(unsigned long, long *)` into
|
|
74
|
+
// `(long, long *)`, one message either way — and HALF BY HALF is what closes it there: a half holds
|
|
75
|
+
// its signedness fixed. Any OTHER variation that re-types an operand or cures half a statement
|
|
76
|
+
// must revisit this rule.
|
|
77
|
+
//
|
|
78
|
+
// INTERACTION: a variation whose reach depends on another. A respell runs over whatever tree the
|
|
79
|
+
// structure variations built, so a structure variation × respell product re-spells statements
|
|
80
|
+
// neither of its probes touched: `/unmerge` copies a join's call into both arms only on the
|
|
81
|
+
// `/flip-join` tree, so their product can print a message more often than the default and either
|
|
82
|
+
// probe do. A product that moves a survivor UP keeps it an error; one that moved it to zero, leaving
|
|
83
|
+
// no other error, would be a candidate that compiles and that the stop never compiles.
|
|
84
|
+
//
|
|
85
|
+
// ONE COPY. Probe selection and the verdict are pure functions over indices, so the sync driver
|
|
86
|
+
// (rank.ts `rankBy`), the pooled CLI driver and the webapp's async loop all sequence their own
|
|
87
|
+
// compiles and ask the same question. Every driver finishes the probe phase before compiling
|
|
88
|
+
// anything else, which is what keeps the verdict independent of a scheduler.
|
|
89
|
+
import { CompilerRejection, attemptsOf, verdictMessages } from './compiler-diagnostics';
|
|
90
|
+
import { VARIATION_TOKENS } from './variation-tokens';
|
|
91
|
+
|
|
92
|
+
/** What compiling one candidate came to, as far as this rule reads it. `compiled` covers scored
|
|
93
|
+
* AND withheld: either way the compiler accepted the text. */
|
|
94
|
+
export type ProbeOutcome = 'compiled' | { thrown: unknown };
|
|
95
|
+
|
|
96
|
+
/** The signedness names, read off the registry by kind. */
|
|
97
|
+
const SIGNEDNESS = new Set<string>(VARIATION_TOKENS.filter((t) => t.variationKind === 'signedness').map((t) => t.name));
|
|
98
|
+
|
|
99
|
+
/** The fan's signedness HALVES: candidates grouped by the signedness their names carry, each in
|
|
100
|
+
* enumeration order, the halves in the order of their first candidate. Index 0 opens the first. */
|
|
101
|
+
function halvesOf(candidates: readonly { variations: readonly string[] }[]): number[][] {
|
|
102
|
+
const halves = new Map<string, number[]>();
|
|
103
|
+
candidates.forEach((c, i) => {
|
|
104
|
+
const key = c.variations.filter((v) => SIGNEDNESS.has(v)).join('/');
|
|
105
|
+
const half = halves.get(key);
|
|
106
|
+
if (half === undefined) {
|
|
107
|
+
halves.set(key, [i]);
|
|
108
|
+
} else {
|
|
109
|
+
half.push(i);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
return [...halves.values()];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** A half's probes: for every variation name in the half, the index of the candidate IN THE HALF
|
|
116
|
+
* with the FEWEST variations that carries it, enumeration order breaking a tie. Ascending, and
|
|
117
|
+
* without the half's own first candidate, which is its default.
|
|
118
|
+
*
|
|
119
|
+
* A NAME is the whole part as the candidate carries it, subject included: `argcopy-a0@1.0` and
|
|
120
|
+
* `argcopy-a0@2.0` are two probes, because each re-spells a different statement and the rule asks
|
|
121
|
+
* whether ANY re-spelling reaches the failing one. Collapsing them to the registered name would
|
|
122
|
+
* probe one region and answer for both. */
|
|
123
|
+
function probesOf(candidates: readonly { variations: readonly string[] }[], half: readonly number[]): number[] {
|
|
124
|
+
const smallest = new Map<string, number>();
|
|
125
|
+
for (const i of half) {
|
|
126
|
+
for (const v of candidates[i].variations) {
|
|
127
|
+
const held = smallest.get(v);
|
|
128
|
+
if (held === undefined || candidates[i].variations.length < candidates[held].variations.length) {
|
|
129
|
+
smallest.set(v, i);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return [...new Set(smallest.values())].filter((i) => i !== half[0]).sort((a, b) => a - b);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** The candidates to compile once the default was rejected: every half's default and probes
|
|
137
|
+
* (`halvesOf`, `probesOf`). Ascending, and without the default itself. */
|
|
138
|
+
export function probeIndices(candidates: readonly { variations: readonly string[] }[]): number[] {
|
|
139
|
+
const probes = new Set<number>();
|
|
140
|
+
for (const half of halvesOf(candidates)) {
|
|
141
|
+
for (const i of [half[0], ...probesOf(candidates, half)]) {
|
|
142
|
+
probes.add(i);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
probes.delete(0);
|
|
146
|
+
return [...probes].sort((a, b) => a - b);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** A fan declared stillborn. */
|
|
150
|
+
export interface Stillborn {
|
|
151
|
+
/** each half's default's errors that survived every probe of that half, as the compiler worded
|
|
152
|
+
* them in that half: a message named at the most times one attempt printed it */
|
|
153
|
+
messages: string[];
|
|
154
|
+
/** indices that WERE compiled — the default and the probes, ascending */
|
|
155
|
+
compiled: number[];
|
|
156
|
+
/** indices that were not, ascending: the rest of the fan */
|
|
157
|
+
notCompiled: number[];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** A rejection read attempt by attempt: each attempt's label and the multiset of its error
|
|
161
|
+
* messages. Null for anything the rule cannot read as a verdict — a compile, a throw that is no
|
|
162
|
+
* `CompilerRejection`, or any attempt whose errors are unread. */
|
|
163
|
+
interface Reading {
|
|
164
|
+
labels: string[];
|
|
165
|
+
errors: Map<string, number>[];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Whether a throw is the compiler REFUSING the text, read as the rule reads a probe: a
|
|
169
|
+
* `CompilerRejection` whose every attempt prints an error the rule reads. Any other throw — a
|
|
170
|
+
* killed compiler, a timeout — says nothing about the candidate, and neither does a rejection
|
|
171
|
+
* that prints no such error: a Docker CLI that cannot reach its daemon exits 1, as a refusing
|
|
172
|
+
* compiler does. Whatever checks the rule's bet by compiling a stopped fan's rest (the
|
|
173
|
+
* benchmark's `bench fan --whole`) counts a refusal through this test, never a second one. */
|
|
174
|
+
export const readableRefusal = (thrown: unknown): boolean => readingOf({ thrown }) !== null;
|
|
175
|
+
|
|
176
|
+
function readingOf(outcome: ProbeOutcome): Reading | null {
|
|
177
|
+
if (outcome === 'compiled' || !(outcome.thrown instanceof CompilerRejection)) {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
const attempts = attemptsOf(outcome.thrown.diagnostic);
|
|
181
|
+
const errors: Map<string, number>[] = [];
|
|
182
|
+
for (const attempt of attempts) {
|
|
183
|
+
const messages = verdictMessages(attempt.diagnostic);
|
|
184
|
+
if (messages === null) {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
const tally = new Map<string, number>();
|
|
188
|
+
for (const m of messages) {
|
|
189
|
+
tally.set(m, (tally.get(m) ?? 0) + 1);
|
|
190
|
+
}
|
|
191
|
+
errors.push(tally);
|
|
192
|
+
}
|
|
193
|
+
return { labels: attempts.map((a) => a.label), errors };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** The default's errors that survive `probes`, per attempt — null as soon as one attempt has none
|
|
197
|
+
* left, or a probe cannot be read against the default. Asks `outcomeOf` for the probes in order
|
|
198
|
+
* and for none past the first that ends it. */
|
|
199
|
+
function survivors(
|
|
200
|
+
anchor: number,
|
|
201
|
+
probes: readonly number[],
|
|
202
|
+
outcomeOf: (index: number) => ProbeOutcome,
|
|
203
|
+
): Map<string, number>[] | null {
|
|
204
|
+
const anchored = readingOf(outcomeOf(anchor));
|
|
205
|
+
if (anchored === null) {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
const left = anchored.errors.map((tally) => new Map(tally));
|
|
209
|
+
for (const i of probes) {
|
|
210
|
+
const probe = readingOf(outcomeOf(i));
|
|
211
|
+
if (
|
|
212
|
+
probe === null ||
|
|
213
|
+
probe.labels.length !== anchored.labels.length ||
|
|
214
|
+
probe.labels.some((label, k) => label !== anchored.labels[k])
|
|
215
|
+
) {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
for (const [k, tally] of left.entries()) {
|
|
219
|
+
for (const [message, times] of tally) {
|
|
220
|
+
if (probe.errors[k].get(message) !== times) {
|
|
221
|
+
tally.delete(message);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (tally.size === 0) {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return left;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** The verdict over a fan whose default and probes have all been compiled — null for "rank the
|
|
233
|
+
* whole fan", which is every case but one (the module header states the rule).
|
|
234
|
+
*
|
|
235
|
+
* `outcomeOf` is asked for index 0 and then, only while the answer can still be "stillborn", for
|
|
236
|
+
* the probes of its half in order, then for each further half's default and probes — so a
|
|
237
|
+
* synchronous driver may compile on demand inside it and pays for no probe past the first that
|
|
238
|
+
* ends it. It must answer whenever asked: a driver that skipped a
|
|
239
|
+
* probe has not run the rule, and "stillborn" over the probes it happened to compile would be a
|
|
240
|
+
* different, weaker rule. */
|
|
241
|
+
export function stillbornVerdict(
|
|
242
|
+
candidates: readonly { variations: readonly string[] }[],
|
|
243
|
+
outcomeOf: (index: number) => ProbeOutcome | undefined,
|
|
244
|
+
): Stillborn | null {
|
|
245
|
+
if (candidates.length === 0) {
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
const asked = (i: number): ProbeOutcome => {
|
|
249
|
+
const outcome = outcomeOf(i);
|
|
250
|
+
if (outcome === undefined) {
|
|
251
|
+
throw new Error(`internal: the stillborn verdict was asked before candidate ${i} was compiled`);
|
|
252
|
+
}
|
|
253
|
+
return outcome;
|
|
254
|
+
};
|
|
255
|
+
const perHalf: Map<string, number>[][] = [];
|
|
256
|
+
for (const half of halvesOf(candidates)) {
|
|
257
|
+
const left = survivors(half[0], probesOf(candidates, half), asked);
|
|
258
|
+
if (left === null) {
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
perHalf.push(left);
|
|
262
|
+
}
|
|
263
|
+
const compiled = [0, ...probeIndices(candidates)];
|
|
264
|
+
const tried = new Set(compiled);
|
|
265
|
+
return {
|
|
266
|
+
messages: namedOnce(perHalf.flat()),
|
|
267
|
+
compiled,
|
|
268
|
+
notCompiled: candidates.map((_, i) => i).filter((i) => !tried.has(i)),
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** Every half's and attempt's survivors as one list: a message in the order it first appears, as
|
|
273
|
+
* many times as the attempt that printed it most. */
|
|
274
|
+
function namedOnce(perAttempt: readonly Map<string, number>[]): string[] {
|
|
275
|
+
const most = new Map<string, number>();
|
|
276
|
+
for (const tally of perAttempt) {
|
|
277
|
+
for (const [message, times] of tally) {
|
|
278
|
+
most.set(message, Math.max(most.get(message) ?? 0, times));
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return [...most].flatMap(([message, times]) => Array.from({ length: times }, () => message));
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** Whether a driver that has compiled ONLY the default should now compile the probes — i.e.
|
|
285
|
+
* whether the default was rejected in a way the rule can read. A driver may skip this and compile
|
|
286
|
+
* the probes regardless; asking first is what keeps an ordinary fan's compile order untouched. */
|
|
287
|
+
export function defaultIsReadableRejection(outcome: ProbeOutcome): boolean {
|
|
288
|
+
return readingOf(outcome) !== null;
|
|
289
|
+
}
|