@asmlift/core 0.6.0 → 0.7.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 +2 -2
- package/package.json +1 -1
- package/src/backend/cfamily.ts +39 -11
- package/src/contracts.ts +60 -11
- package/src/frontend/ssa.ts +1 -1
- package/src/frontend/thumb.ts +2 -2
- package/src/ir/alias.ts +24 -0
- package/src/ir/core.ts +8 -0
- package/src/ir/opcodes.ts +43 -7
- package/src/ir/simplify.ts +1 -1
- package/src/l3/address.ts +2 -2
- package/src/l3/advance.ts +373 -0
- package/src/l3/argbase.ts +4 -4
- package/src/l3/ast.ts +65 -21
- package/src/l3/basecse.ts +48 -28
- package/src/l3/coalesce.ts +9 -9
- package/src/l3/gates.ts +75 -1
- package/src/l3/hoist.ts +1 -1
- package/src/l3/homesplit.ts +13 -13
- package/src/l3/initfirst.ts +3 -3
- package/src/l3/inlinebase.ts +16 -16
- package/src/l3/mentions.ts +68 -5
- package/src/l3/mulfirst.ts +3 -3
- package/src/l3/nearbase.ts +4 -4
- package/src/l3/offmember.ts +5 -5
- package/src/l3/parkfirst.ts +6 -6
- package/src/l3/pollguard.ts +3 -3
- package/src/l3/ptrfield.ts +4 -4
- package/src/l3/regspell.ts +8 -8
- package/src/l3/reindex.ts +22 -17
- package/src/l3/scopebase.ts +28 -25
- package/src/l3/sinkinit.ts +7 -7
- package/src/l3/slotorder.ts +3 -3
- package/src/l3/storage.ts +1 -1
- package/src/l3/tailmerge.ts +2 -2
- package/src/l3/typing.ts +3 -3
- package/src/l3/unmerge.ts +483 -59
- package/src/l3/unreduce.ts +13 -13
- package/src/l3/volatileptr.ts +11 -11
- package/src/l3/volatileval.ts +11 -11
- package/src/l3/volstore.ts +16 -16
- package/src/l3/zerosub.ts +6 -6
- package/src/pattern/engine.ts +4 -4
- package/src/pipeline.ts +17 -5
- package/src/proto.ts +2 -2
- package/src/raise/const.ts +203 -3
- package/src/raise/divpow2.ts +2 -2
- package/src/raise/extscale.ts +342 -0
- package/src/raise/globalshape.ts +32 -12
- package/src/raise/gvn.ts +2 -2
- package/src/raise/magicdiv.ts +2 -2
- package/src/raise/memberarrays.ts +4 -4
- package/src/raise/narrowlocal.ts +18 -2
- package/src/raise/paramwidth.ts +24 -2
- package/src/raise/pre-recovery.ts +90 -25
- package/src/raise/retsink.ts +381 -15
- package/src/raise/shortcircuit.ts +595 -34
- package/src/raise/structs.ts +4 -4
- package/src/raise/tailsink.ts +126 -0
- package/src/rank-declare.ts +4 -4
- package/src/{rank-axes.ts → rank-variations.ts} +319 -189
- package/src/rank.ts +1148 -803
- package/src/structure/analysis.ts +87 -90
- package/src/structure/bitfields.ts +130 -30
- package/src/structure/globalaccess.ts +30 -4
- package/src/structure/namecoalesce.ts +32 -13
- package/src/structure/structure.ts +1415 -200
- package/src/structure/switch-recover.ts +100 -7
- package/src/symbols.ts +127 -6
- package/src/target.ts +155 -35
- package/src/trace.ts +1 -1
- package/src/variation-definitions.ts +1540 -0
- package/src/variation-gates.ts +89 -0
- package/src/variation-tokens.ts +355 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// asmlift — the candidate-enumeration TABLES, split out of rank.ts so the enumeration driver and
|
|
2
|
-
// the data it walks are two files. Every table here is DATA the driver derives from: adding
|
|
3
|
-
//
|
|
2
|
+
// the data it walks are two files. Every table here is DATA the driver derives from: adding a
|
|
3
|
+
// structure variation, a stacked variation or a base-CSE hoist is one entry in one of these lists, and the
|
|
4
4
|
// driver reads it without a second hand-edited site.
|
|
5
5
|
//
|
|
6
6
|
// DECLARATION ORDER IS PUBLISHED BEHAVIOUR. `compareScored` breaks a score tie by enumeration
|
|
7
|
-
// order, so the order of `
|
|
8
|
-
// decides which of two byte-identical
|
|
7
|
+
// order, so the order of `STRUCTURE_VARIATIONS`, `STACKED_VARIATIONS` and every `*_HOISTS` roster
|
|
8
|
+
// decides which of two byte-identical candidates wins and gets its variations into
|
|
9
9
|
// `results.json`. Reordering one of these arrays is a behaviour change wearing a cleanup's
|
|
10
10
|
// clothes — never do it as tidying.
|
|
11
11
|
//
|
|
@@ -29,7 +29,7 @@ import type { HoistPlacement } from './l3/hoist';
|
|
|
29
29
|
import { initFirstGuards } from './l3/initfirst';
|
|
30
30
|
import { pollGuards, pollReads } from './l3/pollguard';
|
|
31
31
|
import { unmergeJoins } from './l3/unmerge';
|
|
32
|
-
// TYPE-ONLY, and deliberately: the
|
|
32
|
+
// TYPE-ONLY, and deliberately: the structure-variation table types its `options` as
|
|
33
33
|
// `Parameters<typeof structureChecked>[1]`, which needs the binding in a `typeof` position and
|
|
34
34
|
// nothing at runtime. A value import here would make this module depend on the whole pipeline.
|
|
35
35
|
import type { structureChecked } from './pipeline';
|
|
@@ -40,24 +40,25 @@ import {
|
|
|
40
40
|
hasMergeFeedHome,
|
|
41
41
|
} from './structure/analysis';
|
|
42
42
|
import { edgeCopyOrdersDiffer, hasParamRootedMerge } from './structure/structure';
|
|
43
|
+
import type { VariationName } from './variation-tokens';
|
|
43
44
|
|
|
44
|
-
/** The
|
|
45
|
+
/** The STRUCTURE VARIATIONS — the boolean candidate dimensions crossed into every enumeration
|
|
45
46
|
* (after signedness/branch-sense/defsite/bitfields, which have their own shapes). One entry per
|
|
46
|
-
*
|
|
47
|
-
* StructureOptions, and the
|
|
48
|
-
* one entry — not four hand-edited sites that can drift.
|
|
47
|
+
* variation; chain construction, the dropped-sibling strip closure, the per-candidate
|
|
48
|
+
* StructureOptions, and the default-setting abort guard all derive from this table, so a new
|
|
49
|
+
* structure variation is one entry — not four hand-edited sites that can drift.
|
|
49
50
|
*
|
|
50
|
-
* `
|
|
51
|
-
* change must exist at all); `
|
|
52
|
-
* own lifted fn (a map-lifted
|
|
53
|
-
* /raw-globals siblings — the /addr-home lesson). `strip` opts the
|
|
54
|
-
* dropped-sibling closure:
|
|
55
|
-
* boundary contracts. Two
|
|
51
|
+
* `sharedGate` gates the alternative's ENUMERATION on the shared lift (the only thing the
|
|
52
|
+
* variation can change must exist at all); `perLiftGate` re-evaluates per symbol-map setting on
|
|
53
|
+
* that setting's own lifted fn (a map-lifted shared lift spells const bases as gaddr, which would
|
|
54
|
+
* blind the /raw-globals siblings — the /addr-home lesson). `strip` opts the variation into the
|
|
55
|
+
* dropped-sibling closure: a candidate that applies it is skipped when its default sibling (the
|
|
56
|
+
* same setting without it) failed the boundary contracts. Two variations are EXEMPT from structure()'s assertDefaultAccepts invariant:
|
|
56
57
|
* `/reread-globals` only relaxes inlining barriers and `/uns-cmp` only changes spelling and
|
|
57
58
|
* declarations — neither adds materialization or merging, so neither can unlock a function the
|
|
58
|
-
*
|
|
59
|
+
* default declines (reread also skips the strip closure). Both exemptions are stated here
|
|
59
60
|
* rather than left implicit in a missing `||` arm or trigger term. */
|
|
60
|
-
export interface
|
|
61
|
+
export interface StructureVariation {
|
|
61
62
|
flag:
|
|
62
63
|
| 'reread'
|
|
63
64
|
| 'inplace'
|
|
@@ -68,15 +69,16 @@ export interface StructuringAxis {
|
|
|
68
69
|
| 'mergeHome'
|
|
69
70
|
| 'unsCmp'
|
|
70
71
|
| 'freshMerge'
|
|
71
|
-
| 'copyDefPos'
|
|
72
|
-
|
|
72
|
+
| 'copyDefPos'
|
|
73
|
+
| 'siteSense';
|
|
74
|
+
name: VariationName;
|
|
73
75
|
options: (on: boolean) => Parameters<typeof structureChecked>[1];
|
|
74
|
-
|
|
75
|
-
|
|
76
|
+
sharedGate?: (sharedLift: Fn, defs: Map<Value, Op>) => boolean;
|
|
77
|
+
perLiftGate?: (fn: Fn) => boolean;
|
|
76
78
|
strip: boolean;
|
|
77
79
|
}
|
|
78
|
-
export const
|
|
79
|
-
// `/reread-globals` — the VALUE-HOME
|
|
80
|
+
export const STRUCTURE_VARIATIONS: readonly StructureVariation[] = [
|
|
81
|
+
// `/reread-globals` — the VALUE-HOME variation (structure/analysis.ts AnalyzeOptions). Whether the
|
|
80
82
|
// source read a global once into a variable or re-read it at each use is not derivable from
|
|
81
83
|
// asm: the compiler CSEs the second spelling back into one load, and the round-5 dogfood
|
|
82
84
|
// watched agbcc land on both sides inside a single function (its highest-cost defect, 25 of
|
|
@@ -84,10 +86,10 @@ export const STRUCTURING_AXES: readonly StructuringAxis[] = [
|
|
|
84
86
|
// having a load that resolves to a named global at all.
|
|
85
87
|
{
|
|
86
88
|
flag: 'reread',
|
|
87
|
-
|
|
89
|
+
name: 'reread-globals',
|
|
88
90
|
options: (on) => ({ rereadGlobals: on }),
|
|
89
|
-
|
|
90
|
-
|
|
91
|
+
sharedGate: (sharedLift, defs) =>
|
|
92
|
+
sharedLift.blocks.some((b) =>
|
|
91
93
|
b.ops.some((op) => op.opcode === 'load' && globalCellOf(defs, op.operands[0], op.attrs.off as number) !== null),
|
|
92
94
|
),
|
|
93
95
|
strip: false,
|
|
@@ -99,10 +101,10 @@ export const STRUCTURING_AXES: readonly StructuringAxis[] = [
|
|
|
99
101
|
// push — and the emptied arm flips the branch sense). Gated on a load-fed cond_br arg.
|
|
100
102
|
{
|
|
101
103
|
flag: 'inplace',
|
|
102
|
-
|
|
104
|
+
name: 'inplace',
|
|
103
105
|
options: (on) => ({ materializeJoinFeeds: on }),
|
|
104
|
-
|
|
105
|
-
|
|
106
|
+
sharedGate: (sharedLift, defs) =>
|
|
107
|
+
sharedLift.blocks.some((b) =>
|
|
106
108
|
b.ops.some(
|
|
107
109
|
(op) =>
|
|
108
110
|
op.opcode === 'cond_br' && op.successors.some((sx) => sx.args.some((a) => defs.get(a)?.opcode === 'load')),
|
|
@@ -116,78 +118,89 @@ export const STRUCTURING_AXES: readonly StructuringAxis[] = [
|
|
|
116
118
|
// itself, so which side scores better is per-function. Gated on a merge fed by 2+ edges.
|
|
117
119
|
{
|
|
118
120
|
flag: 'mergeNames',
|
|
119
|
-
|
|
121
|
+
name: 'merge-names',
|
|
120
122
|
options: (on) => ({ coalesceMergeNames: on }),
|
|
121
|
-
|
|
122
|
-
|
|
123
|
+
sharedGate: (sharedLift) =>
|
|
124
|
+
sharedLift.blocks
|
|
123
125
|
.slice(1)
|
|
124
126
|
.some(
|
|
125
|
-
(b) =>
|
|
127
|
+
(b) =>
|
|
128
|
+
b.params.length > 0 && new Set(sharedLift.blocks.filter((pr) => successorsOf(pr).includes(b))).size > 1,
|
|
126
129
|
),
|
|
127
130
|
strip: true,
|
|
128
131
|
},
|
|
129
|
-
// `/addr-home` — the address-home
|
|
132
|
+
// `/addr-home` — the address-home variation (structure/analysis.ts AnalyzeOptions
|
|
130
133
|
// homeSharedAddresses): a pure computed address dereferenced at 2+ sites, and the multi-render
|
|
131
134
|
// loads through it, materialize into locals — the source's pointer-local + scalar-temp
|
|
132
135
|
// spelling, where the default re-derives per use (a pool literal per folded offset). Gated PER
|
|
133
|
-
// SYMBOL
|
|
136
|
+
// SYMBOL-MAP SETTING (see the table doc) on that setting's own lifted fn having a homeable base.
|
|
134
137
|
{
|
|
135
138
|
flag: 'addrHome',
|
|
136
|
-
|
|
139
|
+
name: 'addr-home',
|
|
137
140
|
options: (on) => ({ homeSharedAddresses: on }),
|
|
138
|
-
|
|
141
|
+
perLiftGate: hasHomeableSharedAddress,
|
|
139
142
|
strip: true,
|
|
140
143
|
},
|
|
141
|
-
// `/expr-home` — the loop-expression-home
|
|
144
|
+
// `/expr-home` — the loop-expression-home variation (structure/analysis.ts AnalyzeOptions
|
|
142
145
|
// homeLoopExprs): a pure value defined outside a loop with 2+ distinct consumers, at least one
|
|
143
146
|
// of them inside it, materializes into a local carrying the value's recovered type — the register
|
|
144
147
|
// the compiler holds across the iterations (`u32 size = 16 << t;` driving a loop bound, a product
|
|
145
|
-
// and a shift), where the default re-derives per use. Gated per symbol
|
|
146
|
-
// (the cone refusal reads the
|
|
148
|
+
// and a shift), where the default re-derives per use. Gated per symbol-map setting like `/addr-home`
|
|
149
|
+
// (the cone refusal reads the setting's own lift).
|
|
147
150
|
{
|
|
148
151
|
flag: 'exprHome',
|
|
149
|
-
|
|
152
|
+
name: 'expr-home',
|
|
150
153
|
options: (on) => ({ homeLoopExprs: on }),
|
|
151
|
-
|
|
154
|
+
perLiftGate: hasLoopSharedPureValue,
|
|
152
155
|
strip: true,
|
|
153
156
|
},
|
|
154
|
-
// `/derived-home` — the derived-read-home
|
|
157
|
+
// `/derived-home` — the derived-read-home variation (structure/analysis.ts AnalyzeOptions
|
|
155
158
|
// homeDerivedReads): a pure value with 2+ consumers standing on a memory read materializes, and
|
|
156
159
|
// the read then renders once inside it — the register the asm carried the DERIVED value in
|
|
157
160
|
// (`eor r1,r1,r0` keeps `0x3FF ^ REG_KEYINPUT`), where the default homes the read and re-derives
|
|
158
161
|
// the computation at every use. Both spellings compile (agbcc CSEs the re-derivation back), so
|
|
159
|
-
// the differ referees. Gated per symbol
|
|
162
|
+
// the differ referees. Gated per symbol-map setting like its `/addr-home` and `/expr-home` siblings,
|
|
160
163
|
// and for the same reason the /addr-home lesson names: the scope refuses a cone holding a
|
|
161
164
|
// standalone address, and a pool constant the map lifts to a `gaddr` is a bare `const` in the
|
|
162
|
-
// `/raw-globals` sibling — so the two
|
|
165
|
+
// `/raw-globals` sibling — so the two settings genuinely answer differently.
|
|
163
166
|
{
|
|
164
167
|
flag: 'derivedHome',
|
|
165
|
-
|
|
168
|
+
name: 'derived-home',
|
|
166
169
|
options: (on) => ({ homeDerivedReads: on }),
|
|
167
|
-
|
|
170
|
+
perLiftGate: hasDerivedReadHome,
|
|
168
171
|
strip: true,
|
|
169
172
|
},
|
|
170
|
-
// `/merge-home` — the merge-feed-home
|
|
173
|
+
// `/merge-home` — the merge-feed-home variation (structure/analysis.ts AnalyzeOptions
|
|
171
174
|
// homeMergeFeeds): a pure value one join's incoming edges render into the SAME parameter slot
|
|
172
175
|
// from 2+ places materializes in the block that dominates them — the value the source computed
|
|
173
176
|
// once above the branch (`s32 m = (b & 1) ? 0x400 : 0;`), where the default has no name to
|
|
174
|
-
// reference on an edge and re-derives the whole expression per arm. Gated per symbol
|
|
177
|
+
// reference on an edge and re-derives the whole expression per arm. Gated per symbol-map setting on
|
|
175
178
|
// the scope itself rather than on an approximation of it.
|
|
176
179
|
//
|
|
177
|
-
//
|
|
178
|
-
// added to it, which costs `kleod:
|
|
179
|
-
// `
|
|
180
|
+
// A VARIATION, not a default: forced on, the spelling is REPLACED across the fan rather than
|
|
181
|
+
// added to it, which costs `kleod:MultiplyQ8` (measured on kl-eod-decomp's source, before 2026-09-13) and `pokeemerald:MathUtil_Mul16` their matches
|
|
182
|
+
// (and cost `MultiplyQ4`, its byte-for-byte twin, until the 2026-09 kleod source swap retired that
|
|
183
|
+
// row as a duplicate). As a variation that is unreachable — `compareScored`
|
|
180
184
|
// orders by score and the un-homed sibling rides beside it.
|
|
181
185
|
//
|
|
182
|
-
// Its fan is essentially one row's: over the
|
|
186
|
+
// Its fan is essentially one row's: over the corpus rows the gate admits, 2790 → 5841
|
|
183
187
|
// candidates map-less and 2538 → 5363 with a map, of which `kleod:UpdateCameraScroll` (outcome
|
|
184
188
|
// `noncompile`, so they buy nothing) is +2944 and +2752, three rows add none at all where
|
|
185
189
|
// `/defsite` already spells the same tree, and the rest pay 107 and 73 between them.
|
|
190
|
+
//
|
|
191
|
+
// THAT CENSUS IS A STATEMENT ABOUT `hasMergeFeedHome`'s ADMITTED SET, and the admitted set is not
|
|
192
|
+
// a property of this file: it moves whenever an upstream pass changes whether a merge feed still
|
|
193
|
+
// EXISTS in the IR. It now reads **20** of the 804 rows that lift (mine, both tiers), three more
|
|
194
|
+
// than the same census over the tree before this branch: `synthetic:sinkacc:agbcc`,
|
|
195
|
+
// `kleod:CountCollectedGems:agbcc` and `kleod:CheckWorldCompletion:agbcc`, which `raise/const.ts`
|
|
196
|
+
// gained by REFUSING to fold an accumulator's `add(const 0, const 1)` and so keeping the feed
|
|
197
|
+
// alive for this gate to see. Re-measure the counts above before tuning off them — the number
|
|
198
|
+
// recorded here has gone stale once already.
|
|
186
199
|
{
|
|
187
200
|
flag: 'mergeHome',
|
|
188
|
-
|
|
201
|
+
name: 'merge-home',
|
|
189
202
|
options: (on) => ({ homeMergeFeeds: on }),
|
|
190
|
-
|
|
203
|
+
perLiftGate: hasMergeFeedHome,
|
|
191
204
|
strip: true,
|
|
192
205
|
},
|
|
193
206
|
// `/uns-cmp` — spell unsigned compares unsigned (structure.ts unsignedCompareSpelling): an
|
|
@@ -199,17 +212,17 @@ export const STRUCTURING_AXES: readonly StructuringAxis[] = [
|
|
|
199
212
|
// compiler's. Gated on the function having an unsigned compare at all.
|
|
200
213
|
{
|
|
201
214
|
flag: 'unsCmp',
|
|
202
|
-
|
|
215
|
+
name: 'uns-cmp',
|
|
203
216
|
options: (on) => ({ unsignedCompareSpelling: on }),
|
|
204
|
-
|
|
217
|
+
sharedGate: (sharedLift) => sharedLift.blocks.some((b) => b.ops.some((op) => op.opcode.startsWith('icmp_u'))),
|
|
205
218
|
strip: true,
|
|
206
219
|
},
|
|
207
|
-
// `/fresh-merge` — the parameter-merge-home
|
|
220
|
+
// `/fresh-merge` — the parameter-merge-home variation (structure.ts `freshParamMerge`, whose
|
|
208
221
|
// `FRESH_MERGE_GATES` carry the argument): a merge whose carrier is a parameter takes its own
|
|
209
222
|
// local (`if (a1 < a0) { v0 = a0; } else { v0 = a1; }`) where the default assigns back into the
|
|
210
223
|
// parameter (`if (a1 < a0) a1 = a0;`). Both are ordinary C over the same values, so
|
|
211
224
|
// the differ decides. At TWO arguments they compile to the SAME bytes on agbcc and on mwcc
|
|
212
|
-
// (measured, both directions), which is why `maxi`/`mini` hold under the
|
|
225
|
+
// (measured, both directions), which is why `maxi`/`mini` hold under the variation.
|
|
213
226
|
//
|
|
214
227
|
// IT ALSO UNLOCKS `/defsite`. `anchorConstCopies` refuses a merge whose name claims another SSA
|
|
215
228
|
// value, so a merge that adopted its parameter is never anchored, while a minted home is sole by
|
|
@@ -223,15 +236,15 @@ export const STRUCTURING_AXES: readonly StructuringAxis[] = [
|
|
|
223
236
|
// it would read as verified. Re-run the sweep before budgeting against the totals.
|
|
224
237
|
//
|
|
225
238
|
// Gated on `hasParamRootedMerge`, which lives beside the rule it over-approximates. Structural,
|
|
226
|
-
// so it cannot answer differently per symbol
|
|
239
|
+
// so it cannot answer differently per symbol-map setting.
|
|
227
240
|
{
|
|
228
241
|
flag: 'freshMerge',
|
|
229
|
-
|
|
242
|
+
name: 'fresh-merge',
|
|
230
243
|
options: (on) => ({ freshParamMerge: on }),
|
|
231
|
-
|
|
244
|
+
sharedGate: (sharedLift) => hasParamRootedMerge(sharedLift),
|
|
232
245
|
strip: true,
|
|
233
246
|
},
|
|
234
|
-
// `/copy-defpos` — the EDGE-COPY ORDER
|
|
247
|
+
// `/copy-defpos` — the EDGE-COPY ORDER variation (structure.ts `preferDefPosCopyOrder`). The frontend
|
|
235
248
|
// measures the order each predecessor wrote its successors' keys (ir/core.ts `WriteOrder`) and
|
|
236
249
|
// the default lays the edge's copies out in it. That the compiler laid its copies out in the
|
|
237
250
|
// order it wrote them is only licensed for a CYCLIC copy set, where the spill has to be the
|
|
@@ -239,129 +252,244 @@ export const STRUCTURING_AXES: readonly StructuringAxis[] = [
|
|
|
239
252
|
// benchmark answers it BOTH WAYS INSIDE ONE COMPILER: `synthetic:gcd:mwcc_242_81` matches only
|
|
240
253
|
// with the record, while `memcpy1:mwcc_242_81` (19 → 23) and `memset1:mwcc_242_81` (19 → 21)
|
|
241
254
|
// score worse with it, as does `armfall:agbcc` (8 → 11). A per-compiler boolean cannot decide a
|
|
242
|
-
// question with rows on both sides of it inside one compiler, and a
|
|
255
|
+
// question with rows on both sides of it inside one compiler, and a variation that emits one tree
|
|
243
256
|
// referees nothing — so the def-position spelling is enumerated beside the record's and the
|
|
244
257
|
// differ picks, exactly as `/fresh-merge` above does for the merge home.
|
|
245
258
|
//
|
|
246
|
-
// THE
|
|
247
|
-
// neither
|
|
259
|
+
// THE VARIATION SPANS THE UNLICENSED HALF ONLY: its alternative keeps the record on cyclic sets,
|
|
260
|
+
// so neither spelling puts a cycle against the instruction that names the compiler's temp. Over the
|
|
248
261
|
// whole benchmark corpus that scoping moves nothing — `memcpy1`, `memset1` and `armfall` keep their
|
|
249
262
|
// `/copy-defpos` winners, so the acyclic half is what they needed — and it makes one spelling
|
|
250
|
-
// reachable that neither whole-function
|
|
263
|
+
// reachable that neither whole-function spelling has: the record on a cycle and the proxy on an
|
|
251
264
|
// acyclic edge of the same function, which is what `synthetic:gcd:agbcc`'s two edges want.
|
|
252
265
|
//
|
|
253
266
|
// Gated on the two orders actually differing somewhere in this function, so on a row where the
|
|
254
|
-
// record changes nothing the pair is one tree and the fan does not grow. PER SYMBOL
|
|
255
|
-
// that
|
|
256
|
-
// is withholding provably inert: same fn, same comparators, so a false answer means the
|
|
257
|
-
// would structure the tree the
|
|
267
|
+
// record changes nothing the pair is one tree and the fan does not grow. PER SYMBOL-MAP SETTING,
|
|
268
|
+
// on that setting's own fully-raised fn, because that is `structure()`'s own input and only there
|
|
269
|
+
// is withholding provably inert: same fn, same comparators, so a false answer means the
|
|
270
|
+
// alternative would structure the tree the default already produced. Asked any earlier the claim does not
|
|
258
271
|
// follow, and both ways it fails are real —
|
|
259
|
-
// - THE SYMBOL
|
|
272
|
+
// - THE SYMBOL-MAP SETTING. klonoa's `UpdateHUDCollectibleCount` answers false with the kleod map
|
|
260
273
|
// and true on the `/raw-globals` sibling's own lift (fixture: test/corpus/agbcc-hudcount.s).
|
|
261
|
-
// - THE STAGE. A
|
|
274
|
+
// - THE STAGE. A shared lift stopping after `recoverTypes` is asked before `foldEmptyLatches`
|
|
262
275
|
// (raise/latch.ts) repoints edges and rewrites the record the gate reads: klonoa's
|
|
263
276
|
// `EntityGravityAndFloorCheck` answers false there and true after that fold.
|
|
264
277
|
// Neither costs candidates today — over 192 klonoa functions enumerated with the map the fan is
|
|
265
278
|
// 27,847 either way, 1,970 of them `/copy-defpos`. `strip` like its neighbours: a reordering
|
|
266
|
-
// cannot rescue a spelling whose
|
|
279
|
+
// cannot rescue a spelling whose default sibling failed the boundary contracts.
|
|
267
280
|
{
|
|
268
281
|
flag: 'copyDefPos',
|
|
269
|
-
|
|
282
|
+
name: 'copy-defpos',
|
|
270
283
|
options: (on) => ({ preferDefPosCopyOrder: on }),
|
|
271
|
-
|
|
284
|
+
perLiftGate: edgeCopyOrdersDiffer,
|
|
272
285
|
strip: true,
|
|
273
286
|
},
|
|
287
|
+
// `/site-sense` — spell a folded short-circuit `if` from the FOLD'S own orientation evidence
|
|
288
|
+
// rather than from the per-function branch-sense boolean (structure.ts senseFromFoldEvidence,
|
|
289
|
+
// raise/shortcircuit.ts `scSharedOnFall` + `scSharedIsTaken` + `scEdgeRelayed`). The two sense
|
|
290
|
+
// booleans are per FUNCTION, so a function whose `if`s were written in opposite senses reaches
|
|
291
|
+
// neither spelling: over `synthetic:mixsense`'s four divergent ladder sites the whole 2^4
|
|
292
|
+
// per-site enumeration scores 10 at the source's own mixed configuration against 20 and 27 for
|
|
293
|
+
// the two the booleans reach,
|
|
294
|
+
// and `synthetic:joinsense` MATCHES at a mix the booleans cannot spell.
|
|
295
|
+
//
|
|
296
|
+
// WHY IT IS A VARIATION AND NOT THE DEFAULT. The reading rests on gcc laying a condition's arms out
|
|
297
|
+
// in SOURCE ORDER, so that a shared block the last test FELL INTO is the source's `then`. That is
|
|
298
|
+
// a claim about one compiler's layout, and the differ is what referees it per row — the long
|
|
299
|
+
// branch is the layout that BREAKS it, gcc inverting the last test and laying the `else` arm
|
|
300
|
+
// first (`synthetic:ifand_far` and `synthetic:ifor_far`, whose scores cannot referee anything:
|
|
301
|
+
// both MATCH on `/flip-join` either way, so their spellings are pinned by
|
|
302
|
+
// test/site-sense.test.ts instead).
|
|
303
|
+
//
|
|
304
|
+
// The premise is only ever consulted where it decides something. The fold also stamps the
|
|
305
|
+
// successor SLOT the shared arm landed in, and a shared arm in the FALL slot leaves the source's
|
|
306
|
+
// `then` in the taken slot whichever way the last test went — so at a CHAINED fold, whose outer
|
|
307
|
+
// `^g` is the head's taken edge, the site is positive and does not ask the premise at all
|
|
308
|
+
// (`synthetic:chainsense`, 4/44 when only the source arm is read, MATCH once the slot is). The THIRD stamp
|
|
309
|
+
// is the long-branch trampoline, the layout where the premise is known to be false: it is the one
|
|
310
|
+
// cell that is GUARDED rather than consulted, and without it the long `||` presents the same two
|
|
311
|
+
// booleans as the short `&&` and is spelled its own dual (`synthetic:ifor_far`, the row added for
|
|
312
|
+
// exactly that cell).
|
|
313
|
+
//
|
|
314
|
+
// THE MAPPING IS STILL NOT A FUNCTION, which is the standing reason this is a variation rather
|
|
315
|
+
// than a default: at `(scSharedOnFall=true, scSharedIsTaken=false)` the corpus holds one function with
|
|
316
|
+
// two sites of OPPOSITE source sense (`kleod:CheckWorldCompletion:agbcc`, 45/191 and unmoved
|
|
317
|
+
// because `/site-sense` is not its winner), so no constant is right there. Read the table in
|
|
318
|
+
// structure.ts as the best per-site default, never as a decision procedure — the enumeration that
|
|
319
|
+
// does not have to pick is `rank.ts`'s per-site `/sense-N` measurement.
|
|
320
|
+
//
|
|
321
|
+
// Gated on this lift's own fully-raised fn carrying a stamped branch at all, for
|
|
322
|
+
// `/copy-defpos`'s reason one entry up: the `/connective` lift variation and the symbol-map
|
|
323
|
+
// settings each change which sites fold, and a gate asked on a different lift would govern a fan
|
|
324
|
+
// it did not measure. A function with no fold structures the identical tree either way and the
|
|
325
|
+
// tree dedup collapses the pair before any compile.
|
|
326
|
+
//
|
|
327
|
+
// NOT in the strip closure, like the two per-function sense variations it refines — which are not in
|
|
328
|
+
// this table at all. `negateCond` is total (l3/ast.ts: a relational opcode swaps, a connective
|
|
329
|
+
// distributes, anything else takes a `!`), so re-spelling a sense can neither throw nor let a
|
|
330
|
+
// candidate structure a function the default declines; there is no failing default sibling for
|
|
331
|
+
// an alternative to ride past.
|
|
332
|
+
{
|
|
333
|
+
flag: 'siteSense',
|
|
334
|
+
name: 'site-sense',
|
|
335
|
+
options: (on) => ({ senseFromFoldEvidence: on }),
|
|
336
|
+
// ALL THREE stamps, which is the predicate the consumer admits a site on (structure.ts's
|
|
337
|
+
// `senseFromFoldEvidence` site default): one contract, not two spellings of it in two files.
|
|
338
|
+
// The fold writes the three in one object literal, so this is the same set of functions today —
|
|
339
|
+
// measured, 0 partially-stamped sites over the 1037 committed rows at both `/connective`
|
|
340
|
+
// settings.
|
|
341
|
+
perLiftGate: (fn) =>
|
|
342
|
+
fn.blocks.some((b) =>
|
|
343
|
+
b.ops.some(
|
|
344
|
+
(op) =>
|
|
345
|
+
typeof op.attrs.scSharedOnFall === 'boolean' &&
|
|
346
|
+
typeof op.attrs.scSharedIsTaken === 'boolean' &&
|
|
347
|
+
typeof op.attrs.scEdgeRelayed === 'boolean',
|
|
348
|
+
),
|
|
349
|
+
),
|
|
350
|
+
strip: false,
|
|
351
|
+
},
|
|
274
352
|
];
|
|
275
353
|
|
|
276
|
-
/** The
|
|
277
|
-
* statement-order/shape
|
|
278
|
-
*
|
|
354
|
+
/** The STACKED variations (sanctioned in the POLICY note at rank.ts's respell site): each entry is
|
|
355
|
+
* a statement-order/shape respell variation orthogonal to every other respell variation, derived
|
|
356
|
+
* onto every source. Each fires alone, plus
|
|
279
357
|
* all of them together in table order — not the full subset lattice; the pairs question is
|
|
280
|
-
* settled by
|
|
358
|
+
* settled by applyStacked' skip-on-decline below, and a row demanding a true EXCLUSION pair —
|
|
281
359
|
* all three fire, the match needs exactly two — is what would earn the lattice. */
|
|
282
|
-
export const
|
|
283
|
-
{
|
|
284
|
-
{
|
|
285
|
-
{
|
|
360
|
+
export const STACKED_VARIATIONS: { name: VariationName; apply: (sfn: SFn) => SFn | null }[] = [
|
|
361
|
+
{ name: 'initfirst', apply: initFirstGuards },
|
|
362
|
+
{ name: 'pollguard', apply: pollGuards },
|
|
363
|
+
{ name: 'pollread', apply: pollReads },
|
|
286
364
|
];
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
*
|
|
365
|
+
|
|
366
|
+
/** The PRE-RESPELL variations (sanctioned in the POLICY note at rank.ts's respell site): a tree
|
|
367
|
+
* rewrite applied BEFORE the respell set, so the whole set derives from its output instead of
|
|
368
|
+
* composing onto it. Same record type as STACKED_VARIATIONS above, and deliberately so — the only difference is
|
|
290
369
|
* WHERE it is applied, and that is the whole admission bar.
|
|
291
370
|
*
|
|
292
|
-
* ADMITTED on one ground: the spelling a row demands needs a downstream
|
|
371
|
+
* ADMITTED on one ground: the spelling a row demands needs a downstream respell variation to run on this
|
|
293
372
|
* rewrite's OUTPUT, and the measured pair shows neither order alone reaches it. For `/unmerge`
|
|
294
373
|
* (l3/unmerge.ts, the dual of the unconditional `tailmerge`) that measurement is
|
|
295
374
|
* `synthetic:dmascope`: the un-merged store has to land inside the arm's own region base
|
|
296
|
-
* (`p0[2] = …`), which only a base
|
|
375
|
+
* (`p0[2] = …`), which only a base-hoisting variation running AFTER the un-merge can spell — hand-compiled,
|
|
297
376
|
* that source is byte-exact where the merged spelling the structurer produces is 9, and applying
|
|
298
|
-
* the un-merge to the WINNER's tree instead measures 14. Every other
|
|
299
|
-
* tree,
|
|
377
|
+
* the un-merge to the WINNER's tree instead measures 14. Every other respell variation derives
|
|
378
|
+
* from the structured tree it is handed, before any other respell variation, so the order can
|
|
379
|
+
* only be had this way.
|
|
380
|
+
*
|
|
381
|
+
* A pre-respell variation only ADDS candidates, so it cannot cost a match; its price is a second
|
|
382
|
+
* respell set on every tree where the rewrite fires, which is why the table is not a place to put
|
|
383
|
+
* a variation that would compose perfectly well as a `respell`.
|
|
384
|
+
*
|
|
385
|
+
* NOT A SECOND ADMISSION GROUND — the bar above is still WHERE the rewrite is applied, and sign
|
|
386
|
+
* base-dependence discriminates nothing (every variation in this file has a base-dependent price).
|
|
387
|
+
* What follows is why the variation is ENUMERATED at all. `/unmerge`'S SIGN IS A PROPERTY OF THE
|
|
388
|
+
* BASE, NOT OF THE VARIATION. One variation, one row (`kleod:CountCollectedGems:agbcc`), with the
|
|
389
|
+
* variation untouched throughout — two MEASUREMENTS and three GRAFTS, kept apart because a graft is
|
|
390
|
+
* a lower bound with an unreliable sign AND magnitude, which this very variation then demonstrated:
|
|
391
|
+
*
|
|
392
|
+
* MEASURED +44 against the #172-era winner (three routes: the fan's own candidate, a graft,
|
|
393
|
+
* a recompile)
|
|
394
|
+
* MEASURED +24 at #169's base
|
|
395
|
+
* GRAFT +2 at a flat ladder
|
|
396
|
+
* GRAFT −3 at flat + no-copies — #172 published this as the prediction for THIS round;
|
|
397
|
+
* the build delivered −17, an under-read of 5.7×
|
|
398
|
+
* GRAFT +4 at flat + no-copies + connective — the rung a connective round makes real, and
|
|
399
|
+
* the ONLY one where the sign goes back POSITIVE at a base strictly CLOSER to
|
|
400
|
+
* the reference. Quote it with the rest: it says the "−17, among the winner's
|
|
401
|
+
* variations" state below may not survive the next rung.
|
|
402
|
+
* MEASURED −17/352 once #184 and #185 made the flat+no-copies base real
|
|
403
|
+
*
|
|
404
|
+
* Six numbers, two signs. Nothing about the tree changed the variation; what changed was which tree the
|
|
405
|
+
* fan derives from, and the fan is itself choosing that tree. So the mapping from a tree to "is the
|
|
406
|
+
* merged tail cheaper" is not a function of anything a gate here could read, and no predicate this
|
|
407
|
+
* pass could evaluate settles it. That is what enumeration is for. The corollary a future author
|
|
408
|
+
* needs more than the rule: MEASURING A VARIATION AT +44 ON TODAY'S WINNER DOES NOT REFUTE IT.
|
|
409
|
+
* `/unmerge` was the most expensive variation on that row for three rounds and is among its
|
|
410
|
+
* winner's variations now, with no change to the variation at all.
|
|
411
|
+
*
|
|
412
|
+
* WHAT ENUMERATION BUYS AND WHAT IT DOES NOT. It buys the whole-function delta above being honest
|
|
413
|
+
* — the differ picks, so the number cannot be WRONG. It buys no REACH: `unmergeJoins` rewrites
|
|
414
|
+
* every site clearing its gates and returns one tree, so the fan carries TWO trees, all-merged
|
|
415
|
+
* and all-un-merged, never 2^k, and a function wanting site A un-merged and site B merged has no
|
|
416
|
+
* candidate. Measured, not assumed — `unmergeAt`'s SUCCESSFUL returns counted per call under an
|
|
417
|
+
* instrumented `unmergeJoins` (`bench gates --pass unmerge` tallies each rule's REFUSALS and never
|
|
418
|
+
* a success), over the synthetic agbcc tier: k = 1 on 23 of the 25 firing rows and on
|
|
419
|
+
* `kleod:CountCollectedGems:agbcc` itself, k = 2 on `synthetic:joinsame` and
|
|
420
|
+
* `synthetic:joinsense` — both MATCH, so the refusal costs nothing today. State it as PR #120
|
|
421
|
+
* states its own: a price, never an immunity.
|
|
300
422
|
*
|
|
301
|
-
*
|
|
302
|
-
*
|
|
303
|
-
*
|
|
304
|
-
|
|
423
|
+
* WHAT BREAKS IF THIS TABLE IS EMPTIED, so an author editing here is not measuring it again:
|
|
424
|
+
* twelve MATCH rows fall, `sa3:numToASCII:agbcc` (REAL-TIER) among them, plus
|
|
425
|
+
* `synthetic:dmascope`, `synthetic:dmascope2`, `synthetic:joinsame`, `synthetic:joinsense`,
|
|
426
|
+
* `synthetic:armcb`, `synthetic:armcb2`, `synthetic:ladder4`, `synthetic:ladder5`,
|
|
427
|
+
* `synthetic:ladidx1`, `synthetic:ladidx2`, `synthetic:revlad5s`. `bench regression` is an
|
|
428
|
+
* OUTCOME gate over all tiers, so every one of them turns it red — but `benchmark.yml` is
|
|
429
|
+
* `workflow_dispatch`, manual, with no cron, so nothing runs that gate on a PR. The scores and the
|
|
430
|
+
* method are in `apps/benchmark/dataset/synthetic.ts`'s `/unmerge` block. */
|
|
431
|
+
export const PRE_RESPELL_VARIATIONS: typeof STACKED_VARIATIONS = [{ name: 'unmerge', apply: unmergeJoins }];
|
|
305
432
|
|
|
306
|
-
export const
|
|
307
|
-
...
|
|
308
|
-
...(
|
|
433
|
+
export const STACKED_SUBSETS: (typeof STACKED_VARIATIONS)[number][][] = [
|
|
434
|
+
...STACKED_VARIATIONS.map((x) => [x]),
|
|
435
|
+
...(STACKED_VARIATIONS.length > 1 ? [STACKED_VARIATIONS] : []),
|
|
309
436
|
];
|
|
310
437
|
|
|
311
438
|
/** The subset applied in table order, SKIP-ON-DECLINE: a member that declines contributes
|
|
312
439
|
* nothing rather than killing the combination — the all-shapes candidate is "everything that
|
|
313
|
-
* fires", so a pair is reachable whenever the third declines. The
|
|
314
|
-
*
|
|
440
|
+
* fires", so a pair is reachable whenever the third declines. The variations are the members that
|
|
441
|
+
* actually FIRED, so they never name a variation that declined; a fired-set that
|
|
315
442
|
* duplicates a smaller subset emits identical source and the dedup collapses it. Null when
|
|
316
443
|
* nothing fired. */
|
|
317
|
-
export const
|
|
318
|
-
subset: readonly (typeof
|
|
444
|
+
export const applyStacked = (
|
|
445
|
+
subset: readonly (typeof STACKED_VARIATIONS)[number][],
|
|
319
446
|
from: SFn,
|
|
320
|
-
): { out: SFn;
|
|
447
|
+
): { out: SFn; variations: VariationName[] } | null => {
|
|
321
448
|
let cur = from;
|
|
322
|
-
const fired:
|
|
449
|
+
const fired: VariationName[] = [];
|
|
323
450
|
for (const sp of subset) {
|
|
324
451
|
const r = sp.apply(cur);
|
|
325
452
|
if (r) {
|
|
326
453
|
cur = r;
|
|
327
|
-
fired.push(sp.
|
|
454
|
+
fired.push(sp.name);
|
|
328
455
|
}
|
|
329
456
|
}
|
|
330
|
-
return fired.length > 0 ? { out: cur,
|
|
457
|
+
return fired.length > 0 ? { out: cur, variations: fired } : null;
|
|
331
458
|
};
|
|
332
459
|
|
|
333
|
-
/** The locals a
|
|
460
|
+
/** The locals a variation added — a NAME diff rather than a positional slice, so a pass that ever
|
|
334
461
|
* reorders locals cannot silently empty the set. It is what scopes `/volatile` to the pointers
|
|
335
|
-
* the
|
|
462
|
+
* the variation itself created (volatilePtrLocals' `only`), leaving the tree's own locals alone. */
|
|
336
463
|
export const createdLocals = (from: SFn, to: SFn): Set<string> => {
|
|
337
464
|
const before = new Set(from.locals.map((l) => l.name));
|
|
338
465
|
return new Set(to.locals.filter((l) => !before.has(l.name)).map((l) => l.name));
|
|
339
466
|
};
|
|
340
467
|
|
|
341
|
-
/** The base-CSE
|
|
468
|
+
/** The base-CSE HOISTS `/livebase` offers the differ, widest first. WHICH of several numeric
|
|
342
469
|
* bases the source named is per-base knowledge the asm does not carry — a DMA register file wants
|
|
343
470
|
* one register held across the whole body while the IWRAM halfword beside it re-materializes — so
|
|
344
|
-
* each
|
|
345
|
-
*
|
|
346
|
-
* roster — not nine hand-edited sites that can drift; whether it also
|
|
347
|
-
*
|
|
471
|
+
* each hoist rides as its own candidate and the differ referees between them. A new
|
|
472
|
+
* hoist is one entry here, one gate table, and that table's line in the gate-contract
|
|
473
|
+
* roster — not nine hand-edited sites that can drift; whether it also joins the `/livebase`
|
|
474
|
+
* PAIRINGS is the entry's own `pairings`. A MIRROR hoist (bind the scalar cells, leave
|
|
348
475
|
* the register file inline) is that, with the complementary predicate; it is never another entry
|
|
349
476
|
* in LIVEBASE_BLOCK_GATES, which can only reject more.
|
|
350
477
|
*
|
|
351
|
-
* WHAT BOUNDS IT. A
|
|
352
|
-
* bound, and each
|
|
353
|
-
* inhabitant exists — over the corpus the second
|
|
354
|
-
* pairing 3, and its `/indexed
|
|
478
|
+
* WHAT BOUNDS IT. A hoist declines unless it binds a non-empty set of bases no earlier hoist
|
|
479
|
+
* already bound, and each composition declines wherever its own variation does, so the list widens
|
|
480
|
+
* only where an inhabitant exists — over the corpus the second hoist reaches 8 rows, its
|
|
481
|
+
* `/nearbase` pairing 3, and its `/indexed` and `/coalesce` pairings and its volatile subsets none
|
|
482
|
+
* at all. A function
|
|
355
483
|
* inhabiting them all pays far more, and the fan is not always a win there: the mixpoll dataset
|
|
356
484
|
* entry prices one where the `/coalesce` pairing costs the most candidates of any and scores two
|
|
357
|
-
* points worse than going unpaired. THAT row fans anyway because on the `/livebase`
|
|
358
|
-
* pairing belongs to the
|
|
359
|
-
* decision, not a property of the roster: three of the five
|
|
360
|
-
* is the field, and its own doc says how a
|
|
485
|
+
* points worse than going unpaired. THAT row fans anyway because on the `/livebase` hoists a
|
|
486
|
+
* pairing belongs to the VARIATION rather than to one of its hoists — but it is a per-hoist
|
|
487
|
+
* decision, not a property of the roster: three of the five hoists below are unpaired. `pairings`
|
|
488
|
+
* is the field, and its own doc says how a hoist earns a `true`.
|
|
361
489
|
*
|
|
362
|
-
* `/basefold` is the third and fourth
|
|
363
|
-
* conditional set —
|
|
364
|
-
* `compilerBehaviors.foldsConstAddrOffset`. They need no second "did the
|
|
490
|
+
* `/basefold` is the third and fourth hoist and `/unfolded` the fifth; those three are the
|
|
491
|
+
* conditional set — their registry entries' target gate offers them only where the target declares
|
|
492
|
+
* `compilerBehaviors.foldsConstAddrOffset`. They need no second "did the default already carry
|
|
365
493
|
* this" test: `structureChecked` runs the DEFAULT hoist to its fixpoint before any tree reaches
|
|
366
494
|
* here, so a key still admissible is by construction one `BASECSE_GATES` rejected, and binding
|
|
367
495
|
* nothing is the whole of the decline.
|
|
@@ -387,19 +515,19 @@ export const createdLocals = (from: SFn, to: SFn): Set<string> => {
|
|
|
387
515
|
* evidence on an instruction that carries the addend by construction, where there is none.
|
|
388
516
|
* On klonoa's `LoadBGTilemapData` — a checkout function rather than a row, so re-run it with the
|
|
389
517
|
* ranked command in docs/ranked-repro.md — the `/basefold` admission declines on every
|
|
390
|
-
* structuring, leaving that fan the size it was, with ZERO `basefold
|
|
391
|
-
* control
|
|
518
|
+
* structuring, leaving that fan the size it was, with ZERO candidates carrying `basefold` in the
|
|
519
|
+
* control run. NO FAN TOTAL IS QUOTED HERE ON PURPOSE: that function's fan was 112896 at this
|
|
392
520
|
* commit and two five-figure numbers ago at others, and a DELTA outlives the total it was
|
|
393
521
|
* measured beside — which is what makes a stale paragraph read as verified. Re-run the total
|
|
394
522
|
* before budgeting against it. All floors, though: the ranked path structures each function many ways
|
|
395
523
|
* where this census builds one tree per observation.
|
|
396
524
|
*
|
|
397
525
|
* WHAT THE PAIR COSTS, through the HARNESS's own enumeration and re-runnable from the recipe in
|
|
398
|
-
* the
|
|
526
|
+
* the BASEFOLD_HOISTS note below: enumerate every agbcc row with the pair on and off,
|
|
399
527
|
* `ASMLIFT_CANDCACHE=0`, candidates only. The pair adds 3921
|
|
400
528
|
* distinct candidate sources over 14 observations — 3911 over 12 real rows and 10 over 2
|
|
401
529
|
* synthetic ones (`foldsink` 4 → 12, `basecell` 2 → 4) — and every per-row delta equals that
|
|
402
|
-
* row's count of `basefold
|
|
530
|
+
* row's count of candidates carrying `basefold` exactly, which is both what says the ablation
|
|
403
531
|
* reached and what says these are sources nothing earlier in the roster emits.
|
|
404
532
|
* It is CONCENTRATED, not spread: in the map configuration the harness uses on real rows,
|
|
405
533
|
* `kleod:ProcessInputAndUpdateEntities` takes +2880 (14976 → 17856),
|
|
@@ -416,22 +544,23 @@ export const createdLocals = (from: SFn, to: SFn): Set<string> => {
|
|
|
416
544
|
* `kleod:ProcessInputAndUpdateEntities` 211 either way and `kleod:CountCollectedGems` 290 either
|
|
417
545
|
* way. A SCORE QUOTED HERE IS THE ARTIFACT'S: it moves whenever anything at all moves the row,
|
|
418
546
|
* a basefold change or not, so re-read it off the artifact rather than off this line. Read the
|
|
419
|
-
* ablation in the note on
|
|
547
|
+
* ablation in the note on BASEFOLD_HOISTS, which carries the fan counts that prove it
|
|
420
548
|
* reached. */
|
|
421
|
-
export interface
|
|
422
|
-
|
|
549
|
+
export interface BaseHoist {
|
|
550
|
+
/** the variations this hoist's candidates carry, in name order */
|
|
551
|
+
variations: readonly VariationName[];
|
|
423
552
|
gates: readonly Gate<BaseKey>[];
|
|
424
|
-
/** WHERE the locals this
|
|
425
|
-
* two questions and this roster answers both, so a
|
|
426
|
-
* position without a second gate table — and a
|
|
553
|
+
/** WHERE the locals this hoist binds are initialized (l3/hoist.ts). Eligibility and placement are
|
|
554
|
+
* two questions and this roster answers both, so a hoist can offer the same bases in the other
|
|
555
|
+
* position without a second gate table — and a variation that wants both offers both, as the
|
|
427
556
|
* `/basefold` pair below does. */
|
|
428
557
|
placement: HoistPlacement;
|
|
429
|
-
/** Whether the
|
|
430
|
-
* a row that demanded the joint spelling (see POLICY), and every demanding row so far is
|
|
431
|
-
* `/livebase`
|
|
558
|
+
/** Whether the hoist joins the `/livebase ×` PAIRINGS in rank.ts. Each of those pairings was added
|
|
559
|
+
* for a row that demanded the joint spelling (see POLICY), and every demanding row so far is served
|
|
560
|
+
* by a `/livebase` hoist — so a new hoist joins them when a row demands it, not by roster
|
|
432
561
|
* membership.
|
|
433
562
|
*
|
|
434
|
-
* ONE BOOLEAN PER
|
|
563
|
+
* ONE BOOLEAN PER HOIST, ANSWERING A QUESTION THAT IS REALLY PER FUNCTION, so a `false`
|
|
435
564
|
* here is a corpus claim and has to be measured like one — on the whole corpus, not on the
|
|
436
565
|
* synthetic row that earned the entry. The measurement is candidates-only and cheap: enumerate
|
|
437
566
|
* every agbcc row twice from `row.scripts.asmlift`'s heredocs and compare the distinct-source
|
|
@@ -442,12 +571,12 @@ export interface BaseAdmission {
|
|
|
442
571
|
pairings: boolean;
|
|
443
572
|
}
|
|
444
573
|
|
|
445
|
-
export const
|
|
446
|
-
{
|
|
447
|
-
{
|
|
574
|
+
export const LIVEBASE_HOISTS: readonly BaseHoist[] = [
|
|
575
|
+
{ variations: ['livebase'], gates: LIVEBASE_GATES, placement: 'head', pairings: true },
|
|
576
|
+
{ variations: ['livebase-block'], gates: LIVEBASE_BLOCK_GATES, placement: 'head', pairings: true },
|
|
448
577
|
];
|
|
449
578
|
|
|
450
|
-
/** Narrower than either `/livebase`
|
|
579
|
+
/** Narrower than either `/livebase` hoist, so both go last: they keep both placement heuristics and
|
|
451
580
|
* exempt only `single-use`, and only for a base whose offset survived the compiler's fold.
|
|
452
581
|
*
|
|
453
582
|
* They are ONE eligibility rule at the two placements, because for a base reached ONCE the
|
|
@@ -456,27 +585,28 @@ export const LIVEBASE_ADMISSIONS: readonly BaseAdmission[] = [
|
|
|
456
585
|
* where a single access loaded it. Which one the source wrote is per-function knowledge the asm
|
|
457
586
|
* does not carry, so both ride and the differ referees.
|
|
458
587
|
*
|
|
459
|
-
* WHAT EACH
|
|
460
|
-
* because a
|
|
461
|
-
* BRACKETED AND THE SUNK ONE IS NOT. `synthetic:foldhead` is MATCH at 0 under
|
|
588
|
+
* WHAT EACH HOIST IS WORTH, ablated through the harness rather than read off the winners'
|
|
589
|
+
* variations, because a winner a hoist names can be a TIE another hoist also reaches. THE HEAD
|
|
590
|
+
* HOIST IS BRACKETED AND THE SUNK ONE IS NOT. `synthetic:foldhead` is MATCH at 0 under
|
|
462
591
|
* `unsigned/basefold` and becomes NONMATCH 11 under `unsigned` the moment the HEAD entry is
|
|
463
592
|
* removed — and removing BOTH entries gives the same 11, so the sunk entry is what nothing here
|
|
464
593
|
* brackets. `synthetic:foldsink` and `synthetic:basecell` are unbracketed for a reason worth
|
|
465
594
|
* keeping: they are MATCH at 0 in every configuration because `/offmember` ALSO reaches 0 on
|
|
466
595
|
* them and wins `compareScored`'s line-count tie-break. A TIE IS NOT A SUBSUMPTION — that is
|
|
467
|
-
* why a census over
|
|
596
|
+
* why a census over winners' variations reads zero here, and reading that zero as "loses" would
|
|
468
597
|
* delete a pair that no other spelling reaches.
|
|
469
598
|
* (Their fans still move: `foldsink` 12 → 8 → 8 → 4 over control/sunk/head/both, `basecell`
|
|
470
599
|
* 4 → 4 → 4 → 2, the four-number sequence saying that on `basecell` the two entries emit the
|
|
471
600
|
* SAME two sources and `seen` collapses them, so only removing both takes the fan down.)
|
|
472
601
|
* `sa3:sub_803213C` MATCH, and — with the pair removed — `kleod:ProcessInputAndUpdateEntities`
|
|
473
602
|
* 211, `kleod:CountCollectedGems` 290 and `kleod:RollRandomLevelVariant` 18, each of them the
|
|
474
|
-
* number the artifact
|
|
603
|
+
* number the artifact carried then (all three retired 2026-09-13), and each of them ENTAILED
|
|
604
|
+
* rather than separately scored:
|
|
475
605
|
* the ablated candidate set is a strict SUBSET of the control one on every row here (enumerated
|
|
476
|
-
* both ways, 0 sources ADDED and 0
|
|
477
|
-
* with 10368 carrying
|
|
478
|
-
* 29 → 11 with 18, `sub_803213C` 36 → 20 with 16), and no winner
|
|
479
|
-
*
|
|
606
|
+
* both ways, 0 sources ADDED and 0 RENAMED — `ProcessInputAndUpdateEntities` 58752 → 48384
|
|
607
|
+
* with 10368 carrying `basefold`, `CountCollectedGems` 576 → 384 with 192, `RollRandomLevelVariant`
|
|
608
|
+
* 29 → 11 with 18, `sub_803213C` 36 → 20 with 16), and no winner carries `basefold`, so the
|
|
609
|
+
* minimum cannot move. A BRACKET IS A CLAIM ABOUT THE WHOLE TREE, so it expires
|
|
480
610
|
* whenever anything else learns to reach the same spelling more cheaply: re-run one before
|
|
481
611
|
* re-quoting it, including the number that survived the last re-run.
|
|
482
612
|
* The SUNK entry is kept ONLY because it is a real spelling: 3921 distinct candidate sources over
|
|
@@ -487,60 +617,60 @@ export const LIVEBASE_ADMISSIONS: readonly BaseAdmission[] = [
|
|
|
487
617
|
* entry is NOT in that category: deleting it costs `synthetic:foldhead` its match, which
|
|
488
618
|
* `bench regression` fails on.
|
|
489
619
|
* HOW THE ABLATION IS DONE, since there is no shipped knob: filter this roster at its one use
|
|
490
|
-
* site (the `
|
|
620
|
+
* site (the `hoists` const in `enumerateCandidates`) behind a temporary env read, run the
|
|
491
621
|
* rows with `ASMLIFT_CANDCACHE=0`, and revert. Prove the filter REACHED before believing a null
|
|
492
622
|
* result — `synthetic:livepark` MATCH → diff:3 with `/livebase` AND `/unfolded` both removed is
|
|
493
623
|
* the positive control, and a fan count per configuration is the second. Removing `/livebase`
|
|
494
624
|
* alone leaves that row MATCH today, which is a control silently going vacuous rather than a
|
|
495
|
-
*
|
|
496
|
-
*
|
|
625
|
+
* variation going dead: `/unfolded` binds the same base there. Any positive control naming ONE
|
|
626
|
+
* hoist expires the next time a hoist is added — re-run it, and if it no longer moves, widen the
|
|
497
627
|
* ablation until it does before concluding anything from a null. */
|
|
498
|
-
export const
|
|
499
|
-
{
|
|
500
|
-
{
|
|
628
|
+
export const BASEFOLD_HOISTS: readonly BaseHoist[] = [
|
|
629
|
+
{ variations: ['basefold'], gates: BASEFOLD_GATES, placement: 'head', pairings: false },
|
|
630
|
+
{ variations: ['basefold', 'sinkinit'], gates: BASEFOLD_GATES, placement: 'first-use', pairings: false },
|
|
501
631
|
];
|
|
502
632
|
|
|
503
|
-
/** The fifth
|
|
633
|
+
/** The fifth hoist: its table requires the fold evidence (l3/basecse.ts, UNFOLDED_GATES), so
|
|
504
634
|
* it binds the reused bases an operand offset says a pointer local strode and leaves the ones the
|
|
505
635
|
* pool already carried folded. `/livebase` and `/livebase-block` are a chain — all the reused
|
|
506
636
|
* bases, or those minus the scalar cells — and a source that parked one numeric base and spelled
|
|
507
|
-
* another inline is at neither end of it. This
|
|
637
|
+
* another inline is at neither end of it. This hoist is not a third link in that chain but beside
|
|
508
638
|
* it: `singleCell` and `unfoldedOffset` are independent fields, so each of the two tables binds
|
|
509
639
|
* keys the other refuses (censused, with its scope, in UNFOLDED_GATES' own note). Read the roster
|
|
510
640
|
* as hand-picked subsets, never as a narrowness ranking.
|
|
511
641
|
*
|
|
512
642
|
* LAST on the roster, so `seen` and `sameBases` between them keep it from restating an earlier
|
|
513
|
-
*
|
|
514
|
-
* EARLIER
|
|
643
|
+
* HOIST — but only `seen` does any work here. `sameBases` declines a hoist that binds what an
|
|
644
|
+
* EARLIER hoist binds AT THE SAME PLACEMENT, and the only earlier `first-use` hoist is
|
|
515
645
|
* `/basefold/sinkinit`, whose table keeps the two gates this one ablates; instrumented over every
|
|
516
646
|
* agbcc row it fires on 0 of 5541 roster observations for this entry (33 distinct functions),
|
|
517
647
|
* against 3939 for `/livebase-block`. So the shadow is available and vacuous, and what keeps this
|
|
518
|
-
*
|
|
519
|
-
* loop
|
|
520
|
-
* emit later is claimed
|
|
648
|
+
* hoist from restating anything is `seen` — WHICH MAKES IT A RENAMER, and it renames: the roster
|
|
649
|
+
* loop runs before the `/livebase ×` pairing loops, so a source one of those pairings would
|
|
650
|
+
* emit later is claimed under this hoist's variations instead. `synthetic:foldpark` is that case measured —
|
|
521
651
|
* fan 34 with this entry and 34 without, the same source winning at 0 under
|
|
522
652
|
* `signed/unfolded/volatile` here and `signed/livebase-block/volatile/sinkinit` there.
|
|
523
653
|
* Corpus-wide (map-less, candidates only, over the artifact's agbcc rows as they stood) 21 of the 333 rows
|
|
524
|
-
* whose distinct-source set is byte-identical either way carry `/unfolded
|
|
654
|
+
* whose distinct-source set is byte-identical either way carry candidates with `/unfolded`:
|
|
525
655
|
* 21 pure renames against 7 rows that really gain sources, and 0 that lose one. What that costs
|
|
526
|
-
* any census taken over
|
|
656
|
+
* any census taken over candidates' variations is at the `seen` dedup site below.
|
|
527
657
|
*
|
|
528
658
|
* ONE placement, unlike the `/basefold` pair, and by measurement rather than by symmetry. All
|
|
529
659
|
* four configurations scored on `synthetic:unfoldpark`, cache off — the fan, then that fan's best
|
|
530
660
|
* score:
|
|
531
661
|
* first-use, unpaired 44 0 MATCH — shipped
|
|
532
|
-
* first-use, paired 44 0 no
|
|
662
|
+
* first-use, paired 44 0 no paired candidate emits a source the unpaired row does not
|
|
533
663
|
* head, unpaired 44 9 the score the row already had without any of this
|
|
534
|
-
* head, paired 48 0 reached only through the `/sinkinit`
|
|
664
|
+
* head, paired 48 0 reached only through the `/sinkinit` pairing
|
|
535
665
|
* The head is where `/livebase` already offers a spelling for every base this table can bind —
|
|
536
|
-
* these are bases reached 2+ times — so what the
|
|
666
|
+
* these are bases reached 2+ times — so what the hoist adds is the SUNK init, which is where a
|
|
537
667
|
* source that declares its base pointer beside the loop it feeds puts the pool load. On both
|
|
538
668
|
* neighbouring rows the head placement is shadowed outright (`/unfolded` binds set-for-set what
|
|
539
669
|
* `/livebase` binds on `synthetic:livepark` and what `/livebase-block` binds on
|
|
540
|
-
* `synthetic:foldpark`). A second
|
|
670
|
+
* `synthetic:foldpark`). A second hoist at the head is one line and no new table; add it when a row
|
|
541
671
|
* demands it, which none does today.
|
|
542
672
|
*
|
|
543
|
-
* `pairings: false` for the reason the field's own doc gives — a
|
|
673
|
+
* `pairings: false` for the reason the field's own doc gives — a pairing is added for a row that
|
|
544
674
|
* demands the joint spelling, and the row that earned this entry does not: paired and unpaired
|
|
545
675
|
* are the same 44 candidates above. ONE 15-LINE FUNCTION CANNOT SETTLE A CORPUS QUESTION, so the
|
|
546
676
|
* same knob was censused over every agbcc row the artifact carries, candidates only: `true` adds
|
|
@@ -549,32 +679,32 @@ export const BASEFOLD_ADMISSIONS: readonly BaseAdmission[] = [
|
|
|
549
679
|
* +608, `synthetic:sizebound` +128, `synthetic:dmascope` +64, `kleod:SetupBG3WindowOverlay` and
|
|
550
680
|
* `synthetic:maskhome` +32 each, and +16 each on `dmafield`, `dmaflat` and `dmapoll`. Five of the
|
|
551
681
|
* eight are MATCH and two are `noncompile`, where extra candidates cannot help; the one that
|
|
552
|
-
* could, `synthetic:sizebound`, scores diff:8 with the
|
|
682
|
+
* could, `synthetic:sizebound`, scores diff:8 with the pairings on and diff:8 with them off. So
|
|
553
683
|
* the `false` buys 1.92% of the agbcc fan for a measured zero, on the whole corpus rather than on
|
|
554
684
|
* the row that earned the entry. Flip it when a row scores better with it, and re-run that
|
|
555
685
|
* census when one does. */
|
|
556
|
-
export const
|
|
557
|
-
{
|
|
686
|
+
export const UNFOLDED_HOISTS: readonly BaseHoist[] = [
|
|
687
|
+
{ variations: ['unfolded'], gates: UNFOLDED_GATES, placement: 'first-use', pairings: false },
|
|
558
688
|
];
|
|
559
689
|
|
|
560
|
-
/** The sixth
|
|
690
|
+
/** The sixth hoist, and the only one whose evidence is the INSTRUCTION ORDER rather than the
|
|
561
691
|
* shape of the accesses (l3/basecse.ts, ORDERBASE_GATES). It binds a base the assembly says was
|
|
562
692
|
* materialized before the index was scaled — including the `(struct S *)&gSym` of an
|
|
563
693
|
* array-of-struct element, which no other table on this roster can even see.
|
|
564
694
|
*
|
|
565
695
|
* LAST, so `sameBases` can shadow it and it can shadow nothing: on a function whose licensed base
|
|
566
696
|
* is a plain leaf reached twice, `/livebase` already binds exactly that set at this placement and
|
|
567
|
-
* this
|
|
697
|
+
* this hoist declines rather than restating it under different variations.
|
|
568
698
|
*
|
|
569
699
|
* TWO PLACEMENTS, and the FLAT second one is a measured zero. `synthetic:bgarr` emits the identical
|
|
570
700
|
* source at `head` and `first-use` (the hoist has nothing to sit above) and that one row
|
|
571
701
|
* generalizes to nothing: over the artifact's agbcc rows the two emit DIFFERENT source on 3 of the
|
|
572
|
-
* 8 rows this
|
|
702
|
+
* 8 rows this hoist binds map-less and 4 of the 10 map-ful — `kleod:SetupBG3WindowOverlay`,
|
|
573
703
|
* `kleod:UpdateCameraScroll`, `pokeemerald:TrySetCantSelectMoveBattleScript`, and map-ful
|
|
574
704
|
* `kleod:StreamCmd_SetBGScroll`. Run through the harness on all four, an entry at
|
|
575
705
|
* `placement: 'first-use'` scores nothing: 146 → 146, noncompile → noncompile, MATCH → MATCH,
|
|
576
706
|
* noncompile → noncompile, against +1129 candidates over those rows' 15167 (+7.4%) and
|
|
577
|
-
* `kleod:UpdateCameraScroll` 224 s → 278 s. That
|
|
707
|
+
* `kleod:UpdateCameraScroll` 224 s → 278 s. That hoist stays withheld.
|
|
578
708
|
*
|
|
579
709
|
* `scope` is a DIFFERENT question and a row demanded it. `first-use` reaches only the top-level
|
|
580
710
|
* statement list, so on a function whose licensed base is used solely inside a guarded loop it
|
|
@@ -586,20 +716,20 @@ export const UNFOLDED_ADMISSIONS: readonly BaseAdmission[] = [
|
|
|
586
716
|
*
|
|
587
717
|
* AND THE WITHHOLDING ABOVE IS ENFORCED BY THE PLACEMENT, not by this row's absence. `scope`
|
|
588
718
|
* reproduces `first-use` on every function where no nested list holds all of a base's uses, so a
|
|
589
|
-
* scoped
|
|
719
|
+
* scoped hoist that answered there would ship the withheld candidate under this hoist's variations — and it
|
|
590
720
|
* is the COMMON case, not the corner: over each project's whole `asm` tree, map-ful, of the 48
|
|
591
721
|
* functions this gate table admits, 41 place every init in the top-level list and only 7 reach a
|
|
592
722
|
* nested one. `hoistBaseLocals` DECLINES at `scope` in exactly that case (l3/basecse.ts) — a
|
|
593
723
|
* WITHDRAWAL and not a dedup, because on 29 of the 41 the flat spelling is one no other row here
|
|
594
724
|
* produces, which that file's header prices. Measured on
|
|
595
725
|
* `kleod:UpdateCameraScroll` map-ful, the row that priced the withheld one: 512 of its 512
|
|
596
|
-
* `/orderbase
|
|
726
|
+
* `/orderbase-scoped` sources placed the init at the top level, and all 512 are gone.
|
|
597
727
|
*
|
|
598
|
-
* `pairings: false` on both for the field's own reason — a
|
|
728
|
+
* `pairings: false` on both for the field's own reason — a pairing is added for a row that demands
|
|
599
729
|
* the joint spelling, and neither row here demands one. */
|
|
600
|
-
export const
|
|
601
|
-
{
|
|
602
|
-
{
|
|
730
|
+
export const ORDERBASE_HOISTS: readonly BaseHoist[] = [
|
|
731
|
+
{ variations: ['orderbase'], gates: ORDERBASE_GATES, placement: 'head', pairings: false },
|
|
732
|
+
{ variations: ['orderbase-scoped'], gates: ORDERBASE_GATES, placement: 'scope', pairings: false },
|
|
603
733
|
];
|
|
604
734
|
|
|
605
735
|
export const sameBases = (a: readonly string[], b: readonly string[]): boolean =>
|
|
@@ -615,16 +745,16 @@ export const sameBases = (a: readonly string[], b: readonly string[]): boolean =
|
|
|
615
745
|
* the field offset into the pool literal (tree reassociation) where a COMPONENT_REF leaves it in
|
|
616
746
|
* the load displacement. `synthetic:dmaptrsrc` is a second counterexample on the field's TYPE.
|
|
617
747
|
*
|
|
618
|
-
* What is true is that no candidate is enumerated for the
|
|
748
|
+
* What is true is that no candidate is enumerated for the question, and none is NEEDED: the recovery
|
|
619
749
|
* reads the base from the observed pool word and the field offset from the observed load
|
|
620
750
|
* displacement, so it reproduces the target's own split by construction. The measurements and the
|
|
621
751
|
* conditions are in `raise/structs.ts`; nothing about them belongs in a roster comment. */
|
|
622
|
-
export const
|
|
623
|
-
{
|
|
624
|
-
{
|
|
752
|
+
export const SIGNEDNESS: readonly { variation: VariationName; signed: boolean }[] = [
|
|
753
|
+
{ variation: 'unsigned', signed: false },
|
|
754
|
+
{ variation: 'signed', signed: true },
|
|
625
755
|
];
|
|
626
756
|
|
|
627
757
|
// A recovered POINTER/aggregate param must NOT be signedness-pinned: pinning a still-`unknown`
|
|
628
758
|
// pointer param to a scalar int BEFORE recovery blocks pointer recovery and emits uncompilable
|
|
629
|
-
// `*(s32)`. Only genuine scalars carry the signedness
|
|
759
|
+
// `*(s32)`. Only genuine scalars carry the signedness variation.
|
|
630
760
|
export const NO_PIN_KINDS = new Set(['ptr', 'struct', 'array']);
|