@asmlift/core 0.8.1 → 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 +267 -79
- 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
|
@@ -27,16 +27,20 @@ import {
|
|
|
27
27
|
poolOrderOf,
|
|
28
28
|
restoreUnclaimedScales,
|
|
29
29
|
} from './extscale';
|
|
30
|
+
import { recognizeFloatHelpers } from './floathelpers';
|
|
30
31
|
import { numberPureValues } from './gvn';
|
|
31
32
|
import { recognizeMagicDivision } from './magicdiv';
|
|
32
33
|
import { recognizeMemberArrays } from './memberarrays';
|
|
33
34
|
import { rerootNarrowReads } from './narrow';
|
|
34
35
|
import { type MergeShape, mergeShapes, narrowBlockLocals } from './narrowlocal';
|
|
36
|
+
import { fuseParamPairs } from './pairparams';
|
|
35
37
|
import { PARAM_WIDTH_GATES, narrowEntryParams } from './paramwidth';
|
|
36
38
|
import { type BranchShortCircuitOptions, recognizeBranchShortCircuit, recognizeShortCircuit } from './shortcircuit';
|
|
37
39
|
import { recognizeSoftDiv } from './softdiv';
|
|
38
40
|
import { recognizeStructArrays } from './struct-arrays';
|
|
39
41
|
import { recognizeStructs } from './structs';
|
|
42
|
+
import { TRUNC_LOAD_GATES, foldTruncatedLoads } from './truncload';
|
|
43
|
+
import { recognizeWideHelpers, refuseUnmodelledHelpers } from './widehelpers';
|
|
40
44
|
|
|
41
45
|
/** Per-call options for the pass list — ONE field per pass that takes any, named for the pass, so
|
|
42
46
|
* a caller reads which recognizer it is steering and a pass that takes none says so by absence.
|
|
@@ -91,8 +95,9 @@ export interface PreRecoveryPass {
|
|
|
91
95
|
|
|
92
96
|
/** THE ordered pre-recovery pass list — the single source of truth shared by pipeline / rank / report.
|
|
93
97
|
* address-numbering → const-materialize → magic-division → pow2-division → soft-division →
|
|
94
|
-
* scaled-extension → array-legalize → struct-array → member-array → struct-pointer →
|
|
95
|
-
* branch-short-circuit → narrow-reads → narrow-local → parameter-width →
|
|
98
|
+
* scaled-extension → array-legalize → truncated-load → struct-array → member-array → struct-pointer →
|
|
99
|
+
* short-circuit → branch-short-circuit → narrow-reads → narrow-local → parameter-width →
|
|
100
|
+
* scaled-extension-restore.
|
|
96
101
|
* See each recognizer's file for the rationale. */
|
|
97
102
|
export const PRE_RECOVERY_PASSES: PreRecoveryPass[] = [
|
|
98
103
|
// FIRST: collapsing duplicate address definitions removes block params every later recognizer
|
|
@@ -121,7 +126,47 @@ export const PRE_RECOVERY_PASSES: PreRecoveryPass[] = [
|
|
|
121
126
|
// ending in `br`. Verified by running it before and after each of them — same result. It sits
|
|
122
127
|
// beside magicdiv so that a reader looking for division recovery finds both together.
|
|
123
128
|
{ id: 'divpow2', run: recognizeDivPow2, dce: true },
|
|
124
|
-
{
|
|
129
|
+
{
|
|
130
|
+
id: 'softdiv',
|
|
131
|
+
run: (fn, _self, _opts, target) => recognizeSoftDiv(fn, target),
|
|
132
|
+
dce: false,
|
|
133
|
+
gate: (t) => !t.capabilities.hwDivide,
|
|
134
|
+
},
|
|
135
|
+
// NOT gated, where `softdiv` is: a float helper computes on floats, which an ISA with no FPU has no
|
|
136
|
+
// instruction for, and the target's runtime table is what says whether it calls one. A pass apart
|
|
137
|
+
// from `widehelpers` because it re-types words as floats — a helper's, a parameter a callee
|
|
138
|
+
// declares `double`, a literal — with its own closed-set refusal, and a single-precision helper
|
|
139
|
+
// is a float helper that is not wide.
|
|
140
|
+
{
|
|
141
|
+
id: 'floathelpers',
|
|
142
|
+
run: (fn, _self, _opts, target) => recognizeFloatHelpers(fn, target),
|
|
143
|
+
dce: false,
|
|
144
|
+
},
|
|
145
|
+
// NOT gated, where `softdiv` is, and the asymmetry is the argument: a soft DIVISION is
|
|
146
|
+
// a division the ISA has no instruction for, so a target with a divider never emits one — while
|
|
147
|
+
// no ISA this repo targets has 64-bit integer arithmetic at all, so a 64-bit helper is software
|
|
148
|
+
// on every one of them whatever `hwDivide` says.
|
|
149
|
+
{
|
|
150
|
+
id: 'widehelpers',
|
|
151
|
+
run: (fn, _self, _opts, target) => recognizeWideHelpers(fn, target),
|
|
152
|
+
dce: false,
|
|
153
|
+
},
|
|
154
|
+
// AFTER the three helper recognizers, because what it refuses is precisely what they declined: a
|
|
155
|
+
// call to a name the target's runtime table carries that none of `softdiv`, `floathelpers` and
|
|
156
|
+
// `widehelpers` folded.
|
|
157
|
+
// Re-emitting the compiler's own runtime call as source is the one failure that MATCHES, so the
|
|
158
|
+
// gap has to be written here rather than left to the backend. See raise/widehelpers.ts.
|
|
159
|
+
//
|
|
160
|
+
// `dce: false` — an `opaque` carries `effects`, so nothing it replaces becomes dead.
|
|
161
|
+
{
|
|
162
|
+
id: 'helperrefuse',
|
|
163
|
+
run: (fn, _self, _opts, target) => refuseUnmodelledHelpers(fn, target),
|
|
164
|
+
dce: false,
|
|
165
|
+
},
|
|
166
|
+
// AFTER it, because the `concat`s it reads are the ones the frontend built at the call the pass
|
|
167
|
+
// above has just rewritten — and because a pair the rewrite refused is a pair this must not fuse
|
|
168
|
+
// into a signature either.
|
|
169
|
+
{ id: 'pairparams', run: (fn) => fuseParamPairs(fn), dce: false },
|
|
125
170
|
// AFTER `const`, which folds a shift pair over a constant to the constant it computes, and BEFORE
|
|
126
171
|
// the three array recognizers, whose input this pass produces: `shl(ext(x), k)` is an element
|
|
127
172
|
// scale they legalize and the fused pair is not. `dce: true` — the `shl` a fold leaves readerless.
|
|
@@ -134,6 +179,31 @@ export const PRE_RECOVERY_PASSES: PreRecoveryPass[] = [
|
|
|
134
179
|
gate: foldsShiftPairCasts,
|
|
135
180
|
},
|
|
136
181
|
{ id: 'arrays', run: recognizeArrays, dce: true },
|
|
182
|
+
// BEFORE the three struct synthesizers, and that is what the pass is FOR: `raise/structs.ts` is
|
|
183
|
+
// the pass that refuses one offset read at two widths, and it can only see one width per offset
|
|
184
|
+
// if the fold has already run. Below it the fold is dead — moved after `structs`,
|
|
185
|
+
// `pokeemerald:AnimTask_FlashHealthboxOnLevelUp_Step:agbcc` declines again on the same throw.
|
|
186
|
+
//
|
|
187
|
+
// EVERY POSITION ABOVE `structs` IS FREE, and that is measured rather than argued: two full
|
|
188
|
+
// `bench sweep --json` runs, this seat against the entry moved to index 0 — above `addrnum`,
|
|
189
|
+
// `const`, the three division recognizers, `extscale` and `arrays` — agree on all 2,430 records
|
|
190
|
+
// over 1,215 rows, compared by ROW IDENTITY on `src`/`len`/`diag`/`marks`/`asm`. (The 84
|
|
191
|
+
// `af:*:ido7.1` records whose `opts` digest churns between any two sweeps are the corpus-wide
|
|
192
|
+
// defect, which moves whatever anyone does; a LINE diff would report them as disagreement.)
|
|
193
|
+
//
|
|
194
|
+
// So the seat is a READING ORDER rather than a constraint: the three struct synthesizers follow
|
|
195
|
+
// each other here, and a reader looking for what happens to a base's access set finds them
|
|
196
|
+
// together. The one position that LOOKS like a prerequisite is `addrnum` — the fold groups
|
|
197
|
+
// accesses by base VALUE identity, so collapsing duplicate address definitions first reads as
|
|
198
|
+
// one. It is not, and index 0 is the run that shows it.
|
|
199
|
+
//
|
|
200
|
+
// `dce: false` — the rewrite retires nothing (the base keeps its use in the load the fold makes).
|
|
201
|
+
{
|
|
202
|
+
id: 'truncload',
|
|
203
|
+
run: (fn, _self, _opts, target) =>
|
|
204
|
+
foldTruncatedLoads(fn, target.capabilities.endianness === 'little', TRUNC_LOAD_GATES),
|
|
205
|
+
dce: false,
|
|
206
|
+
},
|
|
137
207
|
// struct-arrays AFTER arrays (scalar stride==width shapes are claimed first — see the
|
|
138
208
|
// discriminator note in raise/struct-arrays.ts) and BEFORE structs (an element's field
|
|
139
209
|
// accesses must not be re-derived as constant-offset struct-pointer accesses).
|
|
@@ -147,7 +217,11 @@ export const PRE_RECOVERY_PASSES: PreRecoveryPass[] = [
|
|
|
147
217
|
// sits there so the three struct synthesizers read in the order of their evidence. `narrowlocal`
|
|
148
218
|
// reads the carrier's extension chain rather than any address, so nothing here orders against it.
|
|
149
219
|
{ id: 'member-arrays', run: (fn) => recognizeMemberArrays(fn), dce: true },
|
|
150
|
-
{
|
|
220
|
+
{
|
|
221
|
+
id: 'structs',
|
|
222
|
+
run: (fn, _self, _opts, target) => recognizeStructs(fn, target.compilerBehaviors.aggregateBoundary),
|
|
223
|
+
dce: false,
|
|
224
|
+
},
|
|
151
225
|
{ id: 'shortcircuit', run: recognizeShortCircuit, dce: true },
|
|
152
226
|
// The control-flow sibling, value form FIRST.
|
|
153
227
|
//
|
package/src/raise/recover.ts
CHANGED
|
@@ -30,7 +30,7 @@ export function recoverTypes(fn: Fn): void {
|
|
|
30
30
|
seedSignednessFromOpcodes(fn);
|
|
31
31
|
typeDerefBases(fn);
|
|
32
32
|
propagatePointers(fn);
|
|
33
|
-
|
|
33
|
+
defaultUnknowns(fn);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
/** PHASE 1 — signedness from op semantics: the operands of a signed comparison are signed integers,
|
|
@@ -115,20 +115,24 @@ function typeDerefBases(fn: Fn): void {
|
|
|
115
115
|
// incoming `a0`) and a `p = p + stride` walk, so those values stay `unknown` and phase 4 would
|
|
116
116
|
// spell them `int` — an `int→int*` assignment mwcc/agbcc REJECT (gcc warns).
|
|
117
117
|
|
|
118
|
-
/** PHASE 4 — default every still-unknown value to
|
|
119
|
-
* all take plain `int` as the integer default), not a hardware
|
|
120
|
-
|
|
118
|
+
/** PHASE 4 — default every still-unknown value to SIGNED, at its own width. Signedness is a
|
|
119
|
+
* COMPILER default (agbcc/IDO/GCC all take plain `int` as the integer default), not a hardware
|
|
120
|
+
* fact, and it is applied uniformly. The WIDTH is not a default at all: it is what the value was
|
|
121
|
+
* built at, and a 64-bit value retyped `s32` here would be silently truncated by the first pass
|
|
122
|
+
* that believed the type. `setInt` above already defaults this way. */
|
|
123
|
+
function defaultUnknowns(fn: Fn): void {
|
|
124
|
+
const settle = (v: { type: IrType }) => {
|
|
125
|
+
if (v.type.kind === 'unknown') {
|
|
126
|
+
v.type = T.int(v.type.width, true);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
121
129
|
for (const b of fn.blocks) {
|
|
122
130
|
for (const p of b.params) {
|
|
123
|
-
|
|
124
|
-
p.type = T.s(32);
|
|
125
|
-
}
|
|
131
|
+
settle(p);
|
|
126
132
|
}
|
|
127
133
|
for (const op of b.ops) {
|
|
128
134
|
for (const r of op.results) {
|
|
129
|
-
|
|
130
|
-
r.type = T.s(32);
|
|
131
|
-
}
|
|
135
|
+
settle(r);
|
|
132
136
|
}
|
|
133
137
|
}
|
|
134
138
|
}
|
package/src/raise/retsink.ts
CHANGED
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
// retired 2026-09-13).
|
|
33
33
|
//
|
|
34
34
|
// ONE MODEL, NOT TWO. The predicate is `narrowlocal.ts`'s exported `armIsOneSet` — no op that
|
|
35
|
-
// `
|
|
35
|
+
// `reevalUnsafe` calls unsafe, and EXACTLY one result-producing op — read here by
|
|
36
36
|
// `arms-are-one-set`. It is cited there line by line to `jump.c:474/:480/:482/:483` and
|
|
37
37
|
// `rtlanal.c:1770-1784`, with the three things an op count alone gets wrong, and a reach census over
|
|
38
38
|
// 13733 blocks. It is SHARED rather than re-derived here because it is one optimizer guard and a
|
|
@@ -58,7 +58,8 @@ import {
|
|
|
58
58
|
replaceAllUsesWith,
|
|
59
59
|
successorsOf,
|
|
60
60
|
} from '../ir/core';
|
|
61
|
-
import {
|
|
61
|
+
import { effectful, forgetHelperPlacement, orderSensitive, placedAt, speculationUnsafe } from '../ir/discipline';
|
|
62
|
+
import { NEGATED_ICMP } from '../ir/opcodes';
|
|
62
63
|
import { T } from '../ir/types';
|
|
63
64
|
import { type Gate, firstRejection } from '../l3/gates';
|
|
64
65
|
|
|
@@ -226,8 +227,11 @@ export function recognizeShortCircuit(fn: Fn): boolean {
|
|
|
226
227
|
// value ops (arith, loads, icmp) are safe: the structurer inlines them back into the `&&`/`||` RHS
|
|
227
228
|
// expression, where C's own short-circuit re-guards them. Any side effect ⇒ DECLINE the fold — the
|
|
228
229
|
// merge-variable spelling the fall-through leaves is correct (the side effect stays in B's block),
|
|
229
|
-
// just possibly non-matching.
|
|
230
|
-
|
|
230
|
+
// just possibly non-matching. A qualified read is an access, not a value, and stays in its
|
|
231
|
+
// arm the same way. structure/analysis.ts rests on this refusal: it names a counted op it
|
|
232
|
+
// finds in a connective's guarded operand at its def, above the branch, because no fold
|
|
233
|
+
// put one there.
|
|
234
|
+
if (bfeed.ops.slice(0, -1).some(speculationUnsafe)) {
|
|
231
235
|
continue;
|
|
232
236
|
}
|
|
233
237
|
// NEGATABLE only when the orientation actually inverts the head — asked here rather than up
|
|
@@ -244,7 +248,9 @@ export function recognizeShortCircuit(fn: Fn): boolean {
|
|
|
244
248
|
if (wantNeg && !negation) {
|
|
245
249
|
continue;
|
|
246
250
|
}
|
|
247
|
-
|
|
251
|
+
// hoist B's pure body (defines Vb; harmless if a dead const). A helper op it moves no longer
|
|
252
|
+
// sits where the asm called it (ir/discipline.ts `forgetHelperPlacement`).
|
|
253
|
+
bfeed.ops.slice(0, -1).forEach((op) => before(forgetHelperPlacement(op)));
|
|
248
254
|
foldWriteOrder(fn.writeOrder, bfeed, h); // …and its writes now follow H's (ir/core.ts)
|
|
249
255
|
let condSide = cond;
|
|
250
256
|
if (negation) {
|
|
@@ -577,10 +583,11 @@ export function recognizeBranchShortCircuit(fn: Fn, opts: BranchShortCircuitOpti
|
|
|
577
583
|
continue;
|
|
578
584
|
}
|
|
579
585
|
// ^g's body must be pure — see the REFUSALS note.
|
|
580
|
-
//
|
|
586
|
+
// `speculationUnsafe` answers yes for `opaque`: an instruction asmlift could not model, and moving it
|
|
581
587
|
// out of the arm that guards it is the reordering this refuses. Loud either way today — a
|
|
582
588
|
// decline under `onGap: 'strict'`, an ASMLIFT_ERROR marker under `annotate`.
|
|
583
|
-
|
|
589
|
+
// A qualified read stays in its arm too, as in the value form above, and for the same reader.
|
|
590
|
+
if (g.ops.slice(0, -1).some(speculationUnsafe)) {
|
|
584
591
|
continue;
|
|
585
592
|
}
|
|
586
593
|
// Which of ^g's edges rejoins ^h's other successor? That is the shared block. A DIRECT edge
|
|
@@ -648,7 +655,7 @@ export function recognizeBranchShortCircuit(fn: Fn, opts: BranchShortCircuitOpti
|
|
|
648
655
|
}
|
|
649
656
|
}
|
|
650
657
|
rereadInArm(fn, g, otherEdge.block, reread);
|
|
651
|
-
const body = g.ops.slice(0, -1);
|
|
658
|
+
const body = g.ops.slice(0, -1).map(forgetHelperPlacement);
|
|
652
659
|
const second = negation ? negation.result : c2;
|
|
653
660
|
const negated: Op[] = negation ? negation.ops : [];
|
|
654
661
|
const res = mkValue(T.unk(32));
|
|
@@ -938,7 +945,7 @@ const readsOf = (op: Op): Value[] => [...op.operands, ...op.successors.flatMap((
|
|
|
938
945
|
* gate in `opts.armReread` (default {@link ARM_REREAD_GATES}) refuses the site.
|
|
939
946
|
*
|
|
940
947
|
* The copy is exact at the arm's head because nothing runs between: `g`'s body is pure (its
|
|
941
|
-
* caller refuses anything
|
|
948
|
+
* caller refuses anything `speculationUnsafe`), so a load copied there reads what `g`'s own load
|
|
942
949
|
* read, and `g` is the arm's ONLY predecessor, so the copy runs exactly when the original's
|
|
943
950
|
* value would have been live there. On verified IR that also places every escaping read under
|
|
944
951
|
* the arm: `g` dominates nothing else, because the shared block is reached from ^h directly.
|
|
@@ -1086,17 +1093,20 @@ function leavesALoop(fn: Fn, g: Block, arm: Block, preds: ReadonlyMap<Block, rea
|
|
|
1086
1093
|
* - it renders nowhere single: some PURE value between it and a statement has two consumers, so
|
|
1087
1094
|
* `emitPos` is null and analysis.ts materializes it whatever stands in the way
|
|
1088
1095
|
* (`p[2] = p[1] & 0x80; p[3] = p[1] & 0x80;` — one `and`, two stores);
|
|
1089
|
-
* - it has MORE THAN ONE direct reader, and an effect
|
|
1090
|
-
* the multi-render rule,
|
|
1091
|
-
* - it has ONE, and an
|
|
1092
|
-
*
|
|
1093
|
-
* and a `store` UNLESS it is to a provably disjoint slot of the read's own
|
|
1094
|
-
* (`disjointConstSlots`, ir/alias.ts — the helper `isBarrier` calls).
|
|
1096
|
+
* - it has MORE THAN ONE direct reader, and an effect or a helper op analysis.ts names precedes
|
|
1097
|
+
* one of their render positions — the multi-render rule, which exempts nothing;
|
|
1098
|
+
* - it has ONE, and an op precedes its render position that analysis.ts's `isBarrier` counts:
|
|
1099
|
+
* any `astore`/`opaque`, a helper op analysis.ts names, a `call` that is not rendered inside
|
|
1100
|
+
* that same statement, and a `store` UNLESS it is to a provably disjoint slot of the read's own
|
|
1101
|
+
* base (`disjointConstSlots`, ir/alias.ts — the helper `isBarrier` calls).
|
|
1102
|
+
*
|
|
1103
|
+
* `isBarrier`'s pinned-read clause has nothing to mirror: ^g holds no pinned read, since the fold
|
|
1104
|
+
* refuses a ^g with anything `speculationUnsafe` in it.
|
|
1095
1105
|
*
|
|
1096
1106
|
* A reader in any block other than the arm answers yes — the copy then outlives the arm's first
|
|
1097
1107
|
* block. What this does NOT mirror, and which way each gap errs, is ARM_REREAD_GATES' RESIDUE. */
|
|
1098
1108
|
function readHeldAcrossEffect(c: ArmRereadSite): boolean {
|
|
1099
|
-
const reads = [...c.copy].filter(
|
|
1109
|
+
const reads = [...c.copy].filter(orderSensitive);
|
|
1100
1110
|
if (reads.length === 0) {
|
|
1101
1111
|
return false;
|
|
1102
1112
|
}
|
|
@@ -1127,8 +1137,17 @@ function readHeldAcrossEffect(c: ArmRereadSite): boolean {
|
|
|
1127
1137
|
if (cs.length === 1) {
|
|
1128
1138
|
return at(cs[0]);
|
|
1129
1139
|
}
|
|
1130
|
-
return
|
|
1140
|
+
return orderSensitive(op) ? posOf(op) : null;
|
|
1131
1141
|
};
|
|
1142
|
+
// analysis.ts's `namedHelper`: a helper op it names where the asm ran it, which bars a read as a
|
|
1143
|
+
// named call does. It names one whose value renders in another block, or past an effect or
|
|
1144
|
+
// another named helper.
|
|
1145
|
+
const namedHelper = (x: Op): boolean =>
|
|
1146
|
+
placedAt(x) === 'helper' &&
|
|
1147
|
+
(escapes(x.results) ||
|
|
1148
|
+
consumersOf(x.results)
|
|
1149
|
+
.map(at)
|
|
1150
|
+
.some((p) => p === null || c.arm.ops.slice(posOf(x) + 1, p).some((y) => effectful(y) || namedHelper(y))));
|
|
1132
1151
|
for (const l of reads) {
|
|
1133
1152
|
if (escapes(l.results)) {
|
|
1134
1153
|
return true;
|
|
@@ -1139,7 +1158,10 @@ function readHeldAcrossEffect(c: ArmRereadSite): boolean {
|
|
|
1139
1158
|
}
|
|
1140
1159
|
const multi = renders.length > 1;
|
|
1141
1160
|
const bars = (x: Op, pos: number): boolean => {
|
|
1142
|
-
if (
|
|
1161
|
+
if (namedHelper(x)) {
|
|
1162
|
+
return true;
|
|
1163
|
+
}
|
|
1164
|
+
if (!effectful(x)) {
|
|
1143
1165
|
return false;
|
|
1144
1166
|
}
|
|
1145
1167
|
if (multi) {
|
|
@@ -1280,7 +1302,7 @@ export const ARM_REREAD_GATES: readonly Gate<ArmRereadSite>[] = [
|
|
|
1280
1302
|
why: 'the target read it before the second test on both of its exits; moved, it runs on one',
|
|
1281
1303
|
sound: false,
|
|
1282
1304
|
guardedBy: 'branch-shortcircuit.test.ts: REFUSED: a READ only the arm consumes would move under the second test',
|
|
1283
|
-
rejects: (c) => [...c.drop].some(
|
|
1305
|
+
rejects: (c) => [...c.drop].some(orderSensitive),
|
|
1284
1306
|
},
|
|
1285
1307
|
{
|
|
1286
1308
|
// THE BYTES HALF of the copy (see `armRereadCone`), and a claim about ONE compiler. The arm of
|
|
@@ -1323,7 +1345,7 @@ export const ARM_REREAD_GATES: readonly Gate<ArmRereadSite>[] = [
|
|
|
1323
1345
|
|
|
1324
1346
|
/** Copy `copy` to the head of `arm`, point every read outside `g` at the copies, and delete `drop`.
|
|
1325
1347
|
* A dropped original is deleted rather than left for the hoist: the arm now performs it, and a
|
|
1326
|
-
* dead READ left in ^h is one the structurer may still spell (
|
|
1348
|
+
* dead READ left in ^h is one the structurer may still spell (ir/discipline.ts `spelledWhenDead`). */
|
|
1327
1349
|
function rereadInArm(fn: Fn, g: Block, arm: Block, { copy, drop }: ArmReread): void {
|
|
1328
1350
|
const copyOf = new Map<Value, Value>();
|
|
1329
1351
|
const copies = g.ops
|
package/src/raise/softdiv.ts
CHANGED
|
@@ -15,27 +15,27 @@
|
|
|
15
15
|
// cannot state: its match keys on a `call`'s STRING `target` attr, which the numeric `attrEquals`
|
|
16
16
|
// cannot express. It is naturally inert on hardware-divide targets (which emit `div`/`divu`,
|
|
17
17
|
// never `bl __divsi3`).
|
|
18
|
-
import { Fn
|
|
19
|
-
import
|
|
20
|
-
import type
|
|
18
|
+
import type { Fn } from '../ir/core';
|
|
19
|
+
import { wordsOf } from '../proto';
|
|
20
|
+
import { type RuntimeHelper, helperOp, isFloatHelper, isWideHelper } from '../runtime-helpers';
|
|
21
|
+
import type { TargetDescription } from '../target';
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
Object.entries(SOFT_DIV).map(([sym, h]) => [sym, { params: h.params }]),
|
|
34
|
-
);
|
|
23
|
+
/** The 32-bit software divisions, off the target's own helper table. WHICH helpers a compiler emits
|
|
24
|
+
* is a compiler fact and lives there (runtime-helpers.ts); which of them THIS pass answers for is
|
|
25
|
+
* the question here, and it is the narrow one: a division the ISA has no instruction for. A helper
|
|
26
|
+
* that computes on a value wider than a register is a different question — no hardware capability
|
|
27
|
+
* can make one unnecessary — and `raise/widehelpers.ts` answers it, ungated. So is a float helper
|
|
28
|
+
* of any width, which computes on floats the ISA has no instruction for either, and
|
|
29
|
+
* `raise/floathelpers.ts` answers it. */
|
|
30
|
+
const softDivisions = (target: TargetDescription): Record<string, RuntimeHelper> =>
|
|
31
|
+
Object.fromEntries(
|
|
32
|
+
Object.entries(target.runtimeHelpers ?? {}).filter(([, h]) => h.op && !isWideHelper(h) && !isFloatHelper(h)),
|
|
33
|
+
);
|
|
35
34
|
|
|
36
35
|
/** Rewrite each recognised soft-division helper call to its division op, in place. Returns whether
|
|
37
36
|
* anything changed. Runs BEFORE type recovery so the new op's operands get signed/unsigned typing. */
|
|
38
|
-
export function recognizeSoftDiv(fn: Fn): boolean {
|
|
37
|
+
export function recognizeSoftDiv(fn: Fn, target: TargetDescription): boolean {
|
|
38
|
+
const table = softDivisions(target);
|
|
39
39
|
let changed = false;
|
|
40
40
|
for (const b of fn.blocks) {
|
|
41
41
|
for (let i = 0; i < b.ops.length; i++) {
|
|
@@ -43,18 +43,16 @@ export function recognizeSoftDiv(fn: Fn): boolean {
|
|
|
43
43
|
if (op.opcode !== 'call') {
|
|
44
44
|
continue;
|
|
45
45
|
}
|
|
46
|
-
const helper =
|
|
47
|
-
if (!helper) {
|
|
46
|
+
const helper = table[op.attrs.target as string];
|
|
47
|
+
if (!helper?.op) {
|
|
48
48
|
continue;
|
|
49
49
|
}
|
|
50
50
|
// Fold only when BOTH arguments were recovered (the signature makes this the norm). A
|
|
51
51
|
// mis-recovered arity leaves the call untouched rather than fabricating a wrong divide.
|
|
52
|
-
if (op.operands.length !== helper.params || op.results.length !== 1) {
|
|
52
|
+
if (op.operands.length !== wordsOf(helper.params) || op.results.length !== 1) {
|
|
53
53
|
continue;
|
|
54
54
|
}
|
|
55
|
-
|
|
56
|
-
const div = mkOp(helper.op, { operands: [...op.operands], results: [op.results[0]] });
|
|
57
|
-
b.ops.splice(i, 1, div);
|
|
55
|
+
b.ops.splice(i, 1, helperOp(helper.op, op.attrs.target as string, op.operands, op.results[0]));
|
|
58
56
|
changed = true;
|
|
59
57
|
}
|
|
60
58
|
}
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
// UNAMBIGUOUS here (the scaled operand is the index, read from the machine code) — the unscaled
|
|
19
19
|
// `add(x, y)` byte form stays out of scope (genuinely ambiguous without types).
|
|
20
20
|
import { Fn, Op, Value, defOpMap, mkOp } from '../ir/core';
|
|
21
|
+
import { carryDiscipline } from '../ir/discipline';
|
|
21
22
|
import { nextStructIndex } from '../ir/struct-names';
|
|
22
23
|
import { IrType, StructField, T, scalarTypeForAccess } from '../ir/types';
|
|
23
24
|
import { collectStructs } from './structs';
|
|
@@ -102,7 +103,7 @@ export function recognizeStructArrays(fn: Fn): number {
|
|
|
102
103
|
// time, because this pass runs once per function and nothing else mints the prefix, and
|
|
103
104
|
// ir/struct-names.ts states why the computed 0 is kept over the assumed one.
|
|
104
105
|
let name = nextStructIndex(
|
|
105
|
-
[...collectStructs(fn)].map((s) => s.name),
|
|
106
|
+
[...collectStructs(fn, true)].map((s) => s.name),
|
|
106
107
|
'Elem',
|
|
107
108
|
);
|
|
108
109
|
|
|
@@ -254,17 +255,17 @@ export function recognizeStructArrays(fn: Fn): number {
|
|
|
254
255
|
operands: [base, index],
|
|
255
256
|
results: [op.results[0]],
|
|
256
257
|
// listOrder rides along: an ldmia-expanded load keeps its stream-order caveat as an aload
|
|
257
|
-
attrs: {
|
|
258
|
+
attrs: carryDiscipline(op, {
|
|
258
259
|
elemSize: stride,
|
|
259
260
|
signed: op.attrs.signed as boolean,
|
|
260
261
|
fieldOff: op.attrs.off as number,
|
|
261
262
|
...(op.attrs.listOrder === true && { listOrder: true }),
|
|
262
|
-
},
|
|
263
|
+
}),
|
|
263
264
|
});
|
|
264
265
|
} else if (op.opcode === 'store') {
|
|
265
266
|
bb.ops[i] = mkOp('astore', {
|
|
266
267
|
operands: [base, index, op.operands[1]],
|
|
267
|
-
attrs: { elemSize: stride, fieldOff: op.attrs.off as number },
|
|
268
|
+
attrs: carryDiscipline(op, { elemSize: stride, fieldOff: op.attrs.off as number }),
|
|
268
269
|
});
|
|
269
270
|
}
|
|
270
271
|
}
|