@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/trace.ts
CHANGED
|
@@ -2,20 +2,20 @@
|
|
|
2
2
|
// trail), browser-pure. Runs the SAME stage sequence as pipeline.ts and captures a TraceReport:
|
|
3
3
|
// per-stage IR dumps (each post-verify) and per-pattern before/after events. Scoring lives on
|
|
4
4
|
// the other side of the seam: @asmlift/cli/report enriches this into the full DecompileReport
|
|
5
|
-
// (objdiff score, per-pattern score deltas
|
|
5
|
+
// (objdiff score, per-pattern score deltas over the `probeSource` hook's sources, ranked candidates) when a
|
|
6
6
|
// target object is available; the web playground renders the TraceReport as-is.
|
|
7
7
|
import { cBackend } from './backend/c';
|
|
8
8
|
import type { CodegenProfile } from './codegen-flags';
|
|
9
9
|
import type { AsmData } from './frontend/asmdata';
|
|
10
|
-
import { frontendFor } from './frontend/registry';
|
|
11
10
|
import type { Fn } from './ir/core';
|
|
12
11
|
import { print } from './ir/print';
|
|
13
12
|
import { verify } from './ir/verify';
|
|
14
13
|
import type { LanguageBackend } from './l3/ast';
|
|
15
14
|
import { DEFAULT_IDIOM_PATTERNS, RewritePattern, applyPattern, dce, patternApplies } from './pattern/engine';
|
|
16
|
-
import { type OnGap, raiseRecovered, structureChecked, stubResult } from './pipeline';
|
|
17
|
-
import { type Prototypes, prototypesFromSymbols } from './proto';
|
|
15
|
+
import { type OnGap, liftStamped, raiseRecovered, structureChecked, stubResult } from './pipeline';
|
|
16
|
+
import { type Prototypes, declaresVoidReturn, prototypesFromSymbols } from './proto';
|
|
18
17
|
import { assumedShapes, inferGlobalArrays, orderLicensedGlobals } from './raise/globalshape';
|
|
18
|
+
import { OFFSET_NAME_PASS } from './raise/offsetnames';
|
|
19
19
|
import { type SymbolInfo, type SymbolMap, symbolsByName } from './symbols';
|
|
20
20
|
import { type ResolvedTarget, type TargetDescription, type ToolchainId, structureOptionsFor } from './target';
|
|
21
21
|
|
|
@@ -39,9 +39,9 @@ export interface PatternEvent {
|
|
|
39
39
|
hits: number;
|
|
40
40
|
beforeIr: string;
|
|
41
41
|
afterIr: string;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
/** The source a `probeSource` hook emitted at this pattern's boundaries (the cli report scores them). */
|
|
43
|
+
sourceBefore?: string;
|
|
44
|
+
sourceAfter?: string;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
export interface TraceReport {
|
|
@@ -93,14 +93,15 @@ export interface TraceOptions {
|
|
|
93
93
|
asmData?: AsmData; // data-section side table (Regime-B jump tables), as in decompile()
|
|
94
94
|
symbols?: SymbolMap; // address→symbol map (symbols.ts), as in decompile(); absent ⇒ inert
|
|
95
95
|
onGap?: OnGap; // "strict" (default) | "annotate", as in decompile()
|
|
96
|
-
/**
|
|
97
|
-
* pattern N's after-
|
|
96
|
+
/** Source probe at pattern boundaries: the source asmlift would emit there, which the cli report
|
|
97
|
+
* scores. One call per boundary: pattern N's after-source is pattern N+1's before-source. Absent ⇒
|
|
98
|
+
* the source fields stay unset.
|
|
98
99
|
*
|
|
99
100
|
* THE DERIVED ARRAY SHAPES TRAVEL WITH THE CLONE: they are read once off the lift
|
|
100
101
|
* (`stage:globalshape`) and change the default spelling of every indexed global, so a probe
|
|
101
102
|
* that structures without them attributes its per-pattern delta to a source the main path does
|
|
102
103
|
* not emit. */
|
|
103
|
-
|
|
104
|
+
probeSource?: (fn: Fn, inferredSymbols: Map<string, SymbolInfo>) => string | undefined;
|
|
104
105
|
}
|
|
105
106
|
|
|
106
107
|
// The report's per-pass trace stage id + title, keyed by the shared PreRecoveryPass.id. Kept HERE
|
|
@@ -114,6 +115,12 @@ const PRE_RECOVERY_TRACE: Record<string, { stage: string; title: (result: number
|
|
|
114
115
|
const: { stage: 'stage:const', title: () => 'Const materialize (lui;ori → one 32-bit const)' },
|
|
115
116
|
magicdiv: { stage: 'stage:magicdiv', title: () => 'Magic-number division recovery (mulh/mulhu → sdiv/udiv)' },
|
|
116
117
|
softdiv: { stage: 'stage:softdiv', title: () => 'Soft-division lower (bl __divsi3 → division op)' },
|
|
118
|
+
floathelpers: {
|
|
119
|
+
stage: 'stage:floathelpers',
|
|
120
|
+
title: () => 'Soft-float lower (bl __adddf3 → float op, a declared double, a literal)',
|
|
121
|
+
},
|
|
122
|
+
widehelpers: { stage: 'stage:widehelpers', title: () => '64-bit helper lower (bl __muldi3 → 64-bit op)' },
|
|
123
|
+
pairparams: { stage: 'stage:pairparams', title: () => 'Parameter pairs (two argument registers → one s64)' },
|
|
117
124
|
arrays: { stage: 'stage:legalize', title: (r) => `Array legalize (${r} scaled access(es) → aload/astore)` },
|
|
118
125
|
structs: { stage: 'stage:structs', title: () => 'Struct-pointer recovery (access-pattern evidence)' },
|
|
119
126
|
shortcircuit: { stage: 'stage:shortcircuit', title: () => 'Short-circuit recovery (boolean && / ||)' },
|
|
@@ -183,13 +190,12 @@ function traceTower(
|
|
|
183
190
|
// did not state. A trace that lifted from a different table would explain a run that never
|
|
184
191
|
// happened.
|
|
185
192
|
const prototypes = prototypesFromSymbols(opts.symbols, opts.prototypes ?? {});
|
|
186
|
-
const returnsVoid = prototypes[name]
|
|
193
|
+
const returnsVoid = declaresVoidReturn(prototypes[name]);
|
|
187
194
|
const trace: StageTrace[] = [];
|
|
188
195
|
const patternEvents: PatternEvent[] = [];
|
|
189
196
|
|
|
190
197
|
// (1) lift → typed-SSA IR
|
|
191
|
-
const fn =
|
|
192
|
-
verify(fn);
|
|
198
|
+
const fn = liftStamped(name, asm, target, prototypes, opts.asmData, opts.symbols);
|
|
193
199
|
trace.push({ id: 'stage:lift', title: 'Lift (ISA frontend → typed-SSA IR)', irDump: irDump(fn), verified: true });
|
|
194
200
|
// The array shapes the assembly evidences, off the LIFTED fn — same reading, same reason, as
|
|
195
201
|
// pipeline.ts's runTower: the fold and the tower below destroy the order the licence reads.
|
|
@@ -212,13 +218,28 @@ function traceTower(
|
|
|
212
218
|
.join('; '),
|
|
213
219
|
});
|
|
214
220
|
|
|
215
|
-
//
|
|
221
|
+
// The names the map holds for addresses this machine code built by arithmetic off a named one
|
|
222
|
+
// (raise/offsetnames.ts), off the same lifted fn and for the same reason as pipeline.ts's
|
|
223
|
+
// runTower.
|
|
224
|
+
const named = opts.symbols ? OFFSET_NAME_PASS.run(fn, opts.symbols, target.compilerBehaviors) : [];
|
|
225
|
+
trace.push({
|
|
226
|
+
id: 'stage:offsetnames',
|
|
227
|
+
title: 'Name a walked-to address the symbol map knows',
|
|
228
|
+
irDump: irDump(fn),
|
|
229
|
+
verified: true,
|
|
230
|
+
note:
|
|
231
|
+
named.length === 0
|
|
232
|
+
? 'no address built off a named one lands on a symbol the map names'
|
|
233
|
+
: `${named.length} address${named.length === 1 ? '' : 'es'} named: ${named.join(', ')}`,
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// (2) idiom fold (capability-gated), with an optional probed source per pattern boundary —
|
|
216
237
|
// the SAME default set as decompile()/decompileRanked
|
|
217
238
|
const active = (opts.patterns ?? DEFAULT_IDIOM_PATTERNS).filter((p) => patternApplies(p, target));
|
|
218
|
-
// Probe economy: ONE probe per pattern boundary — pattern N's after-
|
|
219
|
-
// before-
|
|
220
|
-
//
|
|
221
|
-
let
|
|
239
|
+
// Probe economy: ONE probe per pattern boundary — pattern N's after-source IS pattern N+1's
|
|
240
|
+
// before-source (the state is identical), and each probe costs a full clone + tower. Zero-hit
|
|
241
|
+
// patterns emit NO event (the IR is unchanged).
|
|
242
|
+
let sourceBefore = opts.probeSource?.(fn, inferredSymbols);
|
|
222
243
|
for (const p of active) {
|
|
223
244
|
const beforeIr = irDump(fn);
|
|
224
245
|
const hits = applyPattern(fn, p, target);
|
|
@@ -228,18 +249,17 @@ function traceTower(
|
|
|
228
249
|
continue;
|
|
229
250
|
}
|
|
230
251
|
const afterIr = irDump(fn);
|
|
231
|
-
const
|
|
252
|
+
const sourceAfter = opts.probeSource?.(fn, inferredSymbols);
|
|
232
253
|
patternEvents.push({
|
|
233
254
|
id: `pattern:${p.id}`,
|
|
234
255
|
patternId: p.id,
|
|
235
256
|
hits,
|
|
236
257
|
beforeIr,
|
|
237
258
|
afterIr,
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
scoreDelta: scoreBefore !== undefined && scoreAfter !== undefined ? scoreAfter - scoreBefore : undefined,
|
|
259
|
+
sourceBefore,
|
|
260
|
+
sourceAfter,
|
|
241
261
|
});
|
|
242
|
-
|
|
262
|
+
sourceBefore = sourceAfter;
|
|
243
263
|
}
|
|
244
264
|
if (active.length) {
|
|
245
265
|
trace.push({
|
|
@@ -297,7 +317,7 @@ function traceTower(
|
|
|
297
317
|
// spelled as a loud ASMLIFT_ERROR marker (annotate) — same onGap option as decompile()
|
|
298
318
|
const mapSymbols = opts.symbols ? symbolsByName(opts.symbols) : undefined;
|
|
299
319
|
const sfn = structureChecked(fn, {
|
|
300
|
-
...structureOptionsFor(target, returnsVoid),
|
|
320
|
+
...structureOptionsFor(target, returnsVoid, prototypes),
|
|
301
321
|
spellSwitchFallthrough: (opts.backend ?? cBackend).spellsSwitchFallthrough,
|
|
302
322
|
onGap: opts.onGap ?? 'strict',
|
|
303
323
|
...(mapSymbols ? { symbols: mapSymbols } : {}),
|
|
@@ -64,6 +64,11 @@ export const READER_WORDS: readonly ReaderWord[] = [
|
|
|
64
64
|
word: 'withheld',
|
|
65
65
|
meaning: 'A candidate that compiled and scored, but was refused publication for want of a byte-exact proof.',
|
|
66
66
|
},
|
|
67
|
+
{
|
|
68
|
+
word: 'not compiled',
|
|
69
|
+
meaning:
|
|
70
|
+
"A candidate never handed to the scorer: its fan was stillborn — in each signedness half, the default candidate and one probe per variation were all rejected, every probe keeping one of its default's errors, so the rest was not compiled.",
|
|
71
|
+
},
|
|
67
72
|
];
|
|
68
73
|
|
|
69
74
|
export interface VariationKindDefinition {
|
|
@@ -195,6 +200,18 @@ export const TARGET_BEHAVIOR_READINGS: { readonly [B in GatingBehavior]: TargetB
|
|
|
195
200
|
'the span is how far one instruction can add to a base register, a fact of the instruction set that no pair of spellings decides',
|
|
196
201
|
},
|
|
197
202
|
},
|
|
203
|
+
pointerIntConversionIsFree: {
|
|
204
|
+
reads: 'builds a word global read or stored through a pointer conversion into the integer spelling',
|
|
205
|
+
witness: {
|
|
206
|
+
compiler: 'agbcc',
|
|
207
|
+
unit: 'extern u32 g; extern u32 gLimit; void sink(void); u32 example(u32 a) { @ }',
|
|
208
|
+
spellings: [
|
|
209
|
+
'*(s32 *)g = a; g = (void *)((u8 *)g + 4); sink(); return (gLimit ^ (u32)g) < 1;',
|
|
210
|
+
'*(s32 *)g = a; g = g + 4; sink(); return (gLimit ^ g) < 1;',
|
|
211
|
+
],
|
|
212
|
+
compiles: 'same',
|
|
213
|
+
},
|
|
214
|
+
},
|
|
198
215
|
foldsPointerAdvance: {
|
|
199
216
|
reads: 'folds a stepped pointer back into an offset load',
|
|
200
217
|
witness: {
|
|
@@ -729,6 +746,32 @@ export const VARIATION_DEFINITIONS: { readonly [N in VariationName]: VariationDe
|
|
|
729
746
|
implementedIn: ANALYSIS,
|
|
730
747
|
seeAlso: ['expr-home', 'fresh-merge'],
|
|
731
748
|
},
|
|
749
|
+
'escape-home': {
|
|
750
|
+
title: 'Narrowed value named where it was narrowed',
|
|
751
|
+
summary: 'a widening or narrowing whose readers are all elsewhere is computed once, at its own place',
|
|
752
|
+
detail:
|
|
753
|
+
'A value narrowed or widened from another, read two or more times and never in the place it was ' +
|
|
754
|
+
'computed, gets a local there — the register the machine narrowed into once and every later block ' +
|
|
755
|
+
'read. By default the cast is written out again at each reader.',
|
|
756
|
+
offeredWhen: {
|
|
757
|
+
when: "A symbol-map setting's own lift narrows or widens a value whose two or more readers are all elsewhere.",
|
|
758
|
+
decidedBy: { symbol: 'hasEscapingExtension', file: ANALYSIS },
|
|
759
|
+
},
|
|
760
|
+
example: {
|
|
761
|
+
compiler: 'agbcc',
|
|
762
|
+
unit: CALLS + 's32 example(void) { @ }',
|
|
763
|
+
before:
|
|
764
|
+
's32 v; u32 n; u32 i; n = 0; ' +
|
|
765
|
+
'do { i = 0; do { v = f(); i = i + 1; } while (i <= 63); } while ((u16)v != 0 && n++ <= 9); ' +
|
|
766
|
+
'return (u16)v;',
|
|
767
|
+
after:
|
|
768
|
+
's32 v; s32 t; u32 n; u32 i; n = 0; ' +
|
|
769
|
+
'do { i = 0; do { v = f(); t = (u16)v; i = i + 1; } while (i <= 63); } while (t != 0 && n++ <= 9); ' +
|
|
770
|
+
'return t;',
|
|
771
|
+
},
|
|
772
|
+
implementedIn: ANALYSIS,
|
|
773
|
+
seeAlso: ['expr-home', 'derived-home'],
|
|
774
|
+
},
|
|
732
775
|
'uns-cmp': {
|
|
733
776
|
title: 'Unsigned compare spelled unsigned',
|
|
734
777
|
summary: 'an unsigned compare carries a `(u32)` cast where its operands do not already say so',
|
|
@@ -794,7 +837,7 @@ export const VARIATION_DEFINITIONS: { readonly [N in VariationName]: VariationDe
|
|
|
794
837
|
compiler: 'agbcc',
|
|
795
838
|
unit:
|
|
796
839
|
CALLS +
|
|
797
|
-
's32 example(s32 a0, s32 a1, s32 c) { s32 v1; s32 v2; if (c) { v1 = f(); v2 = g(); } else { @ } return h(v1, v2); }',
|
|
840
|
+
's32 example(s32 a0, s32 a1, s32 c) { s32 v1; s32 v2; if (c) { v1 = f() + 1; v2 = g(); } else { @ } return h(v1, v2); }',
|
|
798
841
|
before: 'v2 = a1; v1 = a0;',
|
|
799
842
|
after: 'v1 = a0; v2 = a1;',
|
|
800
843
|
note: 'the copies at the end of one path into a merge',
|
|
@@ -1564,6 +1607,81 @@ export const VARIATION_DEFINITIONS: { readonly [N in VariationName]: VariationDe
|
|
|
1564
1607
|
implementedIn: l3('pollguard'),
|
|
1565
1608
|
seeAlso: ['pollguard'],
|
|
1566
1609
|
},
|
|
1610
|
+
'narrow-decl': {
|
|
1611
|
+
title: 'Narrow value declared at its width',
|
|
1612
|
+
summary: 'a local holding one narrowed value is declared at that width instead of as an `s32` with a cast',
|
|
1613
|
+
detail:
|
|
1614
|
+
'A value narrowed once and read as a local can be spelled two ways: an `s32` assigned the narrowing ' +
|
|
1615
|
+
'cast, or a local declared at the narrow width. Every read is an `int` either way, so the value is the ' +
|
|
1616
|
+
"same; which one the source declared is not in the assembly. Applied on top of every other candidate's source.",
|
|
1617
|
+
compilerBehavior:
|
|
1618
|
+
"gcc 2.9's front end sees through the promotion of a narrow variable and not through a cast assigned " +
|
|
1619
|
+
'to an `int`, so compares, divisions and right shifts of an unsigned narrow one become unsigned; ' +
|
|
1620
|
+
'where none reads it, agbcc can still allocate registers differently. IDO, KMC gcc and gcc 2.7.2 ' +
|
|
1621
|
+
'each emit different code for the example too, and every mwcc build does for a compare of it.',
|
|
1622
|
+
offeredWhen: {
|
|
1623
|
+
when: 'An `s32` local written once, outside a `for` init, by an integer narrowed to a narrower integer, that lives in no memory.',
|
|
1624
|
+
decidedBy: { symbol: 'narrowDeclarations', file: l3('narrowdecl') },
|
|
1625
|
+
},
|
|
1626
|
+
example: {
|
|
1627
|
+
compiler: 'agbcc',
|
|
1628
|
+
unit: CALLS + 'extern u8 gA[]; extern u8 gB[]; void example(void) { @ }',
|
|
1629
|
+
before: 's32 v; v = (u8)(gA[12] - 1); gB[14] = (v & 1) + f() % (5 - v) + 1;',
|
|
1630
|
+
after: 'u8 v; v = gA[12] - 1; gB[14] = (v & 1) + f() % (5 - v) + 1;',
|
|
1631
|
+
},
|
|
1632
|
+
implementedIn: l3('narrowdecl'),
|
|
1633
|
+
seeAlso: ['narrow-read', 'derived-home', 'escape-home'],
|
|
1634
|
+
},
|
|
1635
|
+
'narrow-read': {
|
|
1636
|
+
title: 'Narrow value declared at its width, read bare',
|
|
1637
|
+
summary: 'a local read only through one narrowing cast is declared at that width instead of at 32 bits',
|
|
1638
|
+
detail:
|
|
1639
|
+
'A value stored whole and narrowed wherever it is read can be spelled two ways: a 32-bit local cast at every ' +
|
|
1640
|
+
'read, or a local declared at the narrow width and read bare. Every read is the same value either way; ' +
|
|
1641
|
+
"which one the source declared is not in the assembly. Applied on top of every other candidate's source.",
|
|
1642
|
+
compilerBehavior:
|
|
1643
|
+
'A narrow local is extended as it is stored, where the cast extends at each read, after whatever ran in ' +
|
|
1644
|
+
'between: agbcc moves the extension of a call result held across a second call.',
|
|
1645
|
+
offeredWhen: {
|
|
1646
|
+
when: 'A 32-bit local in no memory, written by integers or calls but not in a `for` header, and narrowed by one cast at every read.',
|
|
1647
|
+
decidedBy: { symbol: 'narrowReadDeclarations', file: l3('narrowdecl') },
|
|
1648
|
+
},
|
|
1649
|
+
example: {
|
|
1650
|
+
compiler: 'agbcc',
|
|
1651
|
+
unit: 'u32 cv(u8); u16 dc(u8, u8, u8);\nu16 example(u8 *p) { @ }',
|
|
1652
|
+
before: 's32 y; s32 m; y = cv(p[0]); m = cv(p[1]); return dc((u8)y, (u8)m, (u8)cv(p[2]));',
|
|
1653
|
+
after: 'u8 y; u8 m; y = cv(p[0]); m = cv(p[1]); return dc(y, m, (u8)cv(p[2]));',
|
|
1654
|
+
},
|
|
1655
|
+
implementedIn: l3('narrowdecl'),
|
|
1656
|
+
seeAlso: ['narrow-decl'],
|
|
1657
|
+
},
|
|
1658
|
+
'int-cell': {
|
|
1659
|
+
title: 'Global stored a pointer, spelled as an integer',
|
|
1660
|
+
summary: 'a global the function stores a pointer into is written as an integer: `g = g + K`, no conversions',
|
|
1661
|
+
detail:
|
|
1662
|
+
'A global the function stores a pointer into is written as a pointer by default: `g = (void *)((u8 *)g + K)` ' +
|
|
1663
|
+
'for the byte the assembly addressed, `(u32)g` where the value is used as an integer, and a `void *` ' +
|
|
1664
|
+
'declaration. A project that declares the global an integer wrote `g = g + K` and no conversion, and a ' +
|
|
1665
|
+
'pointer declaration scales that offset instead, so which one is right depends on a declaration the ' +
|
|
1666
|
+
'assembly does not show. Published only when the candidate matches byte for byte; otherwise it is withheld.',
|
|
1667
|
+
compilerBehavior:
|
|
1668
|
+
'CodeWarrior rejects a pointer stored into an integer global; IDO allocates registers differently around ' +
|
|
1669
|
+
'a `(u32)` conversion of one.',
|
|
1670
|
+
offeredWhen: {
|
|
1671
|
+
when:
|
|
1672
|
+
'A store of a `void *` value into a global no declaration in the function types and the symbol map ' +
|
|
1673
|
+
'declares, if at all, an integer.',
|
|
1674
|
+
decidedBy: { symbol: 'integerCells', file: l3('intcell') },
|
|
1675
|
+
},
|
|
1676
|
+
example: {
|
|
1677
|
+
compiler: 'ido',
|
|
1678
|
+
unit: 'extern u32 g; extern u32 gLimit; void sink(void);\nu32 example(u32 a) { @ }',
|
|
1679
|
+
before: '*(s32 *)g = a; g = (void *)((u8 *)g + 4); sink(); return (gLimit ^ (u32)g) < 1;',
|
|
1680
|
+
after: '*(s32 *)g = a; g = g + 4; sink(); return (gLimit ^ g) < 1;',
|
|
1681
|
+
note: 'under the integer declaration the `after` spelling was written against',
|
|
1682
|
+
},
|
|
1683
|
+
implementedIn: l3('intcell'),
|
|
1684
|
+
},
|
|
1567
1685
|
|
|
1568
1686
|
// ── symbol map ──────────────────────────────────────────────────────────────────────────────
|
|
1569
1687
|
'raw-globals': {
|
package/src/variation-tokens.ts
CHANGED
|
@@ -85,6 +85,7 @@ const TOKENS = [
|
|
|
85
85
|
{ name: 'expr-home', variationKind: 'structure' },
|
|
86
86
|
{ name: 'derived-home', variationKind: 'structure' },
|
|
87
87
|
{ name: 'merge-home', variationKind: 'structure' },
|
|
88
|
+
{ name: 'escape-home', variationKind: 'structure' },
|
|
88
89
|
{ name: 'uns-cmp', variationKind: 'structure' },
|
|
89
90
|
{ name: 'fresh-merge', variationKind: 'structure' },
|
|
90
91
|
{ name: 'copy-defpos', variationKind: 'structure' },
|
|
@@ -129,6 +130,13 @@ const TOKENS = [
|
|
|
129
130
|
{ name: 'initfirst', variationKind: 'respell' },
|
|
130
131
|
{ name: 'pollguard', variationKind: 'respell' },
|
|
131
132
|
{ name: 'pollread', variationKind: 'respell' },
|
|
133
|
+
{ name: 'narrow-decl', variationKind: 'respell' },
|
|
134
|
+
{ name: 'narrow-read', variationKind: 'respell' },
|
|
135
|
+
{
|
|
136
|
+
name: 'int-cell',
|
|
137
|
+
variationKind: 'respell',
|
|
138
|
+
target: { behavior: 'pointerIntConversionIsFree', declared: false },
|
|
139
|
+
},
|
|
132
140
|
// symbol map: the map's shaped spellings withheld; always the last part
|
|
133
141
|
{ name: 'raw-globals', variationKind: 'symbol-map' },
|
|
134
142
|
] as const satisfies readonly VariationToken[];
|
|
@@ -282,12 +290,15 @@ export function splitVariations(name: string): string[] {
|
|
|
282
290
|
}
|
|
283
291
|
|
|
284
292
|
/** How many of one fan's candidates carry one variation. `candidates` counts the whole fan —
|
|
285
|
-
* scored, dropped and
|
|
286
|
-
* count, each absent when 0.
|
|
293
|
+
* scored, dropped, withheld and never compiled alike — and `dropped`, `withheld` and
|
|
294
|
+
* `notCompiled` are the parts of that count the ranking did not score, each absent when 0.
|
|
295
|
+
* `notCompiled` is not a refusal: nothing was compiled (core stillborn.ts), and a tally that
|
|
296
|
+
* folded it into `dropped` would report compile failures that never ran. */
|
|
287
297
|
export interface VariationTally {
|
|
288
298
|
candidates: number;
|
|
289
299
|
dropped?: number;
|
|
290
300
|
withheld?: number;
|
|
301
|
+
notCompiled?: number;
|
|
291
302
|
}
|
|
292
303
|
|
|
293
304
|
interface NamedCandidate {
|
|
@@ -299,7 +310,7 @@ interface NamedCandidate {
|
|
|
299
310
|
* (`coalesce-v0-v1` and `coalesce-v2-v3` are both `coalesce`), and a candidate counts once under
|
|
300
311
|
* each name it carries however many subjects it applies it to.
|
|
301
312
|
*
|
|
302
|
-
* `fan` is ranking's
|
|
313
|
+
* `fan` is ranking's four-way partition, which puts every enumerated candidate in exactly one
|
|
303
314
|
* list, so each signedness entry's `candidates` sums with the other's to the fan size.
|
|
304
315
|
*
|
|
305
316
|
* A tally, not a factorisation: enumeration gates prune the fan, so the counts do not multiply to
|
|
@@ -309,6 +320,7 @@ export function tallyFanVariations(fan: {
|
|
|
309
320
|
candidates: readonly NamedCandidate[];
|
|
310
321
|
dropped: readonly NamedCandidate[];
|
|
311
322
|
withheld: readonly NamedCandidate[];
|
|
323
|
+
notCompiled: readonly NamedCandidate[];
|
|
312
324
|
}): Record<string, VariationTally> {
|
|
313
325
|
const registeredName = new Map<string, string>();
|
|
314
326
|
const nameOf = (part: string): string => {
|
|
@@ -319,13 +331,13 @@ export function tallyFanVariations(fan: {
|
|
|
319
331
|
}
|
|
320
332
|
return name;
|
|
321
333
|
};
|
|
322
|
-
const counts = new Map<string, { candidates: number; dropped: number; withheld: number }>();
|
|
323
|
-
const add = (list: readonly NamedCandidate[], refusal: 'dropped' | 'withheld' | undefined): void => {
|
|
334
|
+
const counts = new Map<string, { candidates: number; dropped: number; withheld: number; notCompiled: number }>();
|
|
335
|
+
const add = (list: readonly NamedCandidate[], refusal: 'dropped' | 'withheld' | 'notCompiled' | undefined): void => {
|
|
324
336
|
for (const c of list) {
|
|
325
337
|
for (const name of new Set(c.variations.map(nameOf))) {
|
|
326
338
|
let n = counts.get(name);
|
|
327
339
|
if (n === undefined) {
|
|
328
|
-
n = { candidates: 0, dropped: 0, withheld: 0 };
|
|
340
|
+
n = { candidates: 0, dropped: 0, withheld: 0, notCompiled: 0 };
|
|
329
341
|
counts.set(name, n);
|
|
330
342
|
}
|
|
331
343
|
n.candidates++;
|
|
@@ -338,6 +350,7 @@ export function tallyFanVariations(fan: {
|
|
|
338
350
|
add(fan.candidates, undefined);
|
|
339
351
|
add(fan.dropped, 'dropped');
|
|
340
352
|
add(fan.withheld, 'withheld');
|
|
353
|
+
add(fan.notCompiled, 'notCompiled');
|
|
341
354
|
const kindIndex = (name: string): number => VARIATION_KINDS.indexOf(variationToken(name).variationKind);
|
|
342
355
|
const names = [...counts.keys()].sort((a, b) => kindIndex(a) - kindIndex(b) || (a < b ? -1 : a > b ? 1 : 0));
|
|
343
356
|
return Object.fromEntries(
|
|
@@ -349,6 +362,7 @@ export function tallyFanVariations(fan: {
|
|
|
349
362
|
candidates: n.candidates,
|
|
350
363
|
...(n.dropped ? { dropped: n.dropped } : {}),
|
|
351
364
|
...(n.withheld ? { withheld: n.withheld } : {}),
|
|
365
|
+
...(n.notCompiled ? { notCompiled: n.notCompiled } : {}),
|
|
352
366
|
},
|
|
353
367
|
];
|
|
354
368
|
}),
|