@asmlift/core 0.8.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -31
- package/package.json +1 -1
- package/src/aggregate.ts +397 -0
- package/src/backend/c.ts +6 -2
- package/src/backend/cfamily.ts +370 -56
- package/src/backend/cpp.ts +116 -10
- package/src/backend/pascal.ts +69 -11
- package/src/cdecl/constant.ts +59 -0
- package/src/cdecl/lex.ts +209 -0
- package/src/cdecl/parse.ts +975 -0
- package/src/cdecl/spell.ts +69 -0
- package/src/codegen-flags.ts +38 -6
- package/src/compiler-diagnostics.ts +194 -0
- package/src/contracts.ts +777 -84
- package/src/declare.ts +132 -13
- package/src/frontend/asmdata.ts +26 -6
- package/src/frontend/call-plan.ts +1590 -0
- package/src/frontend/dead-reads.ts +628 -0
- package/src/frontend/device-pins.ts +296 -0
- package/src/frontend/emit.ts +4 -3
- package/src/frontend/errors.ts +5 -0
- package/src/frontend/flags-edge.ts +101 -0
- package/src/frontend/fpu.ts +374 -0
- package/src/frontend/frame-objects.ts +3171 -0
- package/src/frontend/frontend.ts +64 -3
- package/src/frontend/high-half.ts +55 -5
- package/src/frontend/jump-table.ts +530 -0
- package/src/frontend/local-object.ts +616 -0
- package/src/frontend/mips.ts +2392 -208
- package/src/frontend/opaque.ts +97 -18
- package/src/frontend/own-struct-return.ts +500 -0
- package/src/frontend/ppc.ts +3137 -267
- package/src/frontend/register-struct-call.ts +484 -0
- package/src/frontend/reloc-symbol.ts +109 -13
- package/src/frontend/splat.ts +108 -5
- package/src/frontend/ssa.ts +2098 -89
- package/src/frontend/stackargs.ts +172 -42
- package/src/frontend/thumb.ts +4563 -2091
- package/src/ir/alias.ts +71 -0
- package/src/ir/bits.ts +681 -1
- package/src/ir/core.ts +199 -6
- package/src/ir/discipline.ts +313 -0
- package/src/ir/entry.ts +159 -0
- package/src/ir/float-bits.ts +47 -0
- package/src/{structure → ir}/loops.ts +6 -2
- package/src/ir/opcodes.ts +193 -121
- package/src/ir/parse.ts +1 -0
- package/src/ir/types.ts +202 -2
- package/src/ir/verify.ts +219 -3
- package/src/l3/address.ts +5 -0
- package/src/l3/argcopy.ts +38 -6
- package/src/l3/ast.ts +467 -23
- package/src/l3/basecse.ts +33 -5
- package/src/l3/coalesce.ts +21 -13
- package/src/l3/constinit.ts +382 -0
- package/src/l3/dce.ts +34 -12
- package/src/l3/gates.ts +9 -3
- package/src/l3/hoist.ts +8 -7
- package/src/l3/initfirst.ts +68 -19
- package/src/l3/intcell.ts +98 -0
- package/src/l3/mentions.ts +10 -4
- package/src/l3/narrowdecl.ts +259 -0
- package/src/l3/nearbase.ts +10 -5
- package/src/l3/parkfirst.ts +8 -5
- package/src/l3/pollguard.ts +11 -4
- package/src/l3/ptrcell.ts +354 -0
- package/src/l3/ptrfield.ts +123 -8
- package/src/l3/regspell.ts +26 -7
- package/src/l3/reindex.ts +293 -103
- package/src/l3/storage.ts +98 -1
- package/src/l3/symbol-refs.ts +182 -23
- package/src/l3/tailmerge.ts +9 -20
- package/src/l3/typing.ts +237 -32
- package/src/l3/unreduce.ts +10 -9
- package/src/l3/volatileptr.ts +17 -54
- package/src/l3/volstore.ts +96 -7
- package/src/mangle.ts +41 -1
- package/src/pattern/engine.ts +373 -82
- package/src/pipeline.ts +589 -27
- package/src/proto-context.ts +1648 -0
- package/src/proto.ts +1203 -49
- package/src/raise/aggmembers.ts +290 -0
- package/src/raise/arrays.ts +8 -3
- package/src/raise/declared-volatile.ts +307 -0
- package/src/raise/divpow2.ts +2 -2
- package/src/raise/errors.ts +17 -0
- package/src/raise/floathelpers.ts +464 -0
- package/src/raise/globalshape.ts +11 -5
- package/src/raise/gvn.ts +29 -8
- package/src/raise/latch.ts +162 -6
- package/src/raise/memberarrays.ts +10 -4
- package/src/raise/narrow.ts +29 -2
- package/src/raise/narrowlocal.ts +11 -6
- package/src/raise/offsetnames.ts +494 -0
- package/src/raise/pairparams.ts +114 -0
- package/src/raise/paramwidth.ts +203 -6
- package/src/raise/pre-recovery.ts +174 -17
- package/src/raise/pre-structure.ts +54 -0
- package/src/raise/recover.ts +26 -15
- package/src/raise/reroll.ts +694 -0
- package/src/raise/retsink.ts +1 -1
- package/src/raise/shortcircuit.ts +66 -30
- package/src/raise/softdiv.ts +21 -23
- package/src/raise/storesign.ts +153 -0
- package/src/raise/struct-arrays.ts +34 -14
- package/src/raise/structs.ts +674 -98
- package/src/raise/truncload.ts +367 -0
- package/src/raise/unthread.ts +400 -0
- package/src/raise/widehelpers.ts +106 -0
- package/src/rank-declare.ts +143 -12
- package/src/rank-variations.ts +117 -31
- package/src/rank.ts +563 -122
- package/src/runtime-helpers.ts +405 -0
- package/src/stillborn.ts +289 -0
- package/src/structure/analysis.ts +1449 -449
- package/src/structure/arith-ops.ts +23 -0
- package/src/structure/globalaccess.ts +111 -9
- package/src/structure/hazards.ts +641 -114
- package/src/structure/local-statics.ts +517 -0
- package/src/structure/namecoalesce.ts +2 -2
- package/src/structure/pointer-spelling.ts +1106 -0
- package/src/structure/redundant-test.ts +99 -0
- package/src/structure/structure.ts +3478 -975
- package/src/structure/switch-recover.ts +403 -178
- package/src/symbols.ts +44 -3
- package/src/target.ts +1387 -71
- package/src/trace.ts +66 -28
- package/src/variation-definitions.ts +243 -13
- package/src/variation-gates.ts +2 -0
- package/src/variation-tokens.ts +25 -7
package/src/l3/ast.ts
CHANGED
|
@@ -6,15 +6,49 @@ import { type IrType, typeEquals } from '../ir/types';
|
|
|
6
6
|
export type Expr =
|
|
7
7
|
| { k: 'var'; name: string }
|
|
8
8
|
| { k: 'const'; value: number }
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
// A `double` literal, carried as its IEEE bit pattern (ir/float-bits.ts) — a kind apart from the
|
|
10
|
+
// integer `const`, so no integer fold, range test or signedness reading ever takes it for one.
|
|
11
|
+
| { k: 'fconst'; bits: string }
|
|
12
|
+
// `unsignedCmp` is EVIDENCE, not spelling: an ordering compare (`<` `<=` `>` `>=`) lowered from an
|
|
13
|
+
// `icmp_u*`, which C spells with the same token as the signed one and decides from the operand
|
|
14
|
+
// types. The printer never reads it; `signedOverUnsignedCompare` (l3/typing.ts) does, over the
|
|
15
|
+
// FINISHED tree, so a pass that substitutes into a compare cannot re-spell it signed unseen.
|
|
16
|
+
// `exprEquals` ignores it, as it ignores `index.operandOff`: two nodes that print alike are
|
|
17
|
+
// alike to a CSE. A merge that keeps one of two equal nodes keeps it through `unionEvidence`, so
|
|
18
|
+
// the evidence of the one it drops is not lost with it.
|
|
19
|
+
//
|
|
20
|
+
// `helperRel` is evidence too: on a float `==`/`!=`, the relation the SOURCE spelled, where the
|
|
21
|
+
// compiler picked a runtime helper by it (runtime-helpers.ts `RuntimeHelper.relation` — agbcc's
|
|
22
|
+
// `__eqsf2` and `__nesf2`). It rides through `negateCond` unchanged, so a node whose `op` is not
|
|
23
|
+
// its `helperRel` is one a negation moved off the helper, and `keepsHelperRel` /
|
|
24
|
+
// `regainsHelperRel` below are what a pass choosing between `if (c) A else B` and
|
|
25
|
+
// `if (!c) B else A` asks. Ignored by `exprEquals` and by the printer, like `unsignedCmp`.
|
|
26
|
+
| { k: 'bin'; op: BinOp; l: Expr; r: Expr; unsignedCmp?: true; helperRel?: BinOp }
|
|
27
|
+
| { k: 'un'; op: '-' | '~' | '!' | 'f-'; e: Expr }
|
|
28
|
+
// A post-increment `name++` (`by` 1) or post-decrement `name--` (`by` -1): the value the local
|
|
29
|
+
// held BEFORE the update, with the update as a side effect. The only expression form here that
|
|
30
|
+
// WRITES anything, so `exprHasEffect` answers for it alongside `call` and `marker` — that is what
|
|
31
|
+
// every pass asking "may I move or duplicate this" already consults. It also NAMES a local, so
|
|
32
|
+
// `mentionedName` answers for it beside `var` and `addr`, and the two walks that classify their
|
|
33
|
+
// own leaves — `l3/mentions.ts` and `contracts.ts`'s `assertLocalsWritten` — count it on both
|
|
34
|
+
// sides, because `v++` reads the name and writes it.
|
|
35
|
+
//
|
|
36
|
+
// The target is a NAME rather than an Expr because its one producer has one: structure.ts's
|
|
37
|
+
// `emitDoWhile`, folding a loop update into a bottom test that reads the variable at its
|
|
38
|
+
// pre-update value (`while (v0 != 0 && v1++ <= 9)`). A general lvalue has no inhabitant.
|
|
39
|
+
//
|
|
40
|
+
// WHERE IT MAY STAND IS THE PRODUCER'S OBLIGATION, not a property of the node: C89 leaves the
|
|
41
|
+
// program undefined when the same object is also read elsewhere between two sequence points, and
|
|
42
|
+
// short-circuit operators decide whether the update happens at all. `PREUPDATE_COND_GATES`
|
|
43
|
+
// (structure/hazards.ts) holds both rules for the one producer.
|
|
44
|
+
| { k: 'postincr'; name: string; by: 1 | -1 }
|
|
11
45
|
// A C-style value cast `(T)e`. Tree-level producers: a width-narrowing cast `(u8)e` (the
|
|
12
46
|
// recovered form of a byte/half extend idiom — zext/sext IR ops), the STRUCT-pointer cast
|
|
13
47
|
// (structure.ts memAccess/arrayAccess struct paths — see the note on `field` below), and the
|
|
14
|
-
// integer legalization of a pointer operand under an operator C rejects (
|
|
15
|
-
// `3 & (s32)p`). Scalar deref casts are backend-owned — the
|
|
16
|
-
// them from the `index` node's width. Each backend spells the cast
|
|
17
|
-
// (C: `(u8)e`; Pascal: no spelling yet → fails loud).
|
|
48
|
+
// integer legalization of a pointer operand under an operator C rejects (the arithmetic table,
|
|
49
|
+
// structure/pointer-spelling.ts: `3 & (s32)p`). Scalar deref casts are backend-owned — the
|
|
50
|
+
// C-family printer synthesizes them from the `index` node's width. Each backend spells the cast
|
|
51
|
+
// in its own syntax (C: `(u8)e`; Pascal: no spelling yet → fails loud).
|
|
18
52
|
//
|
|
19
53
|
// `volatile` qualifies the POINTEE of a pointer cast (`(volatile u16 *)0x4000208`) — the one
|
|
20
54
|
// place asmlift can say "this access is to a volatile object" when there is no declaration to
|
|
@@ -22,12 +56,58 @@ export type Expr =
|
|
|
22
56
|
// cv-qualifier, deliberately: volatility is a SPELLING, carried at the declaration or the
|
|
23
57
|
// cast, the same split SFn.locals makes for `volatile`/`pointeeVolatile`.
|
|
24
58
|
| { k: 'cast'; to: IrType; e: Expr; volatile?: true }
|
|
25
|
-
|
|
59
|
+
// A call. `wide64` is the INTEGER RANK the callee's return renders with, and it is carried on the
|
|
60
|
+
// node because nothing downstream can derive it: a callee's return type comes from a prototype
|
|
61
|
+
// outside the emitted function, so `exprCType` answers `undefined` for a call and every reader of
|
|
62
|
+
// the rendered rank would otherwise read C's implicit `int`.
|
|
63
|
+
//
|
|
64
|
+
// IT IS TRUE OF THE C THAT IS COMPILED BECAUSE THE SOURCES AGREE, and that agreement is a
|
|
65
|
+
// property of one lookup rather than of two modules happening to match. The width is set where
|
|
66
|
+
// the frontend was TOLD it — a compiler's own runtime-helper signature, whose callee the
|
|
67
|
+
// candidate never declares because the helper table IS the declaration, or a project's
|
|
68
|
+
// `FnProto.returns`, which `proto.ts` `spellableProto` admits only when the same entry can be
|
|
69
|
+
// PRINTED as the callee's own prototype. `prototypesFromSymbols` resolves that table once, so
|
|
70
|
+
// the frontend that stamps this and the collector that prints the declaration read one answer.
|
|
71
|
+
// The filters in `rank-declare.ts` can still withhold a printed declaration (a name the emitted
|
|
72
|
+
// C uses for its own storage, a non-identifier, a reserved name) — every one of them kills the
|
|
73
|
+
// SPELLING or the whole row rather than leaving this stamp standing over an implicitly-`int`
|
|
74
|
+
// callee.
|
|
75
|
+
//
|
|
76
|
+
// `sret` is the type of a struct the callee returns BY VALUE through memory. `args[0]` is then the
|
|
77
|
+
// hidden pointer the frontend read from the first argument register — the address of the struct
|
|
78
|
+
// the call fills — and the rest are the declared arguments. The only C spelling is a statement,
|
|
79
|
+
// `dest = fn(rest)`, so such a call stands alone. `returns` is the type of a struct the callee
|
|
80
|
+
// returns BY VALUE in registers, where the call is that value itself, `return mk(x);`
|
|
81
|
+
// (frontend/register-struct-call.ts); the declarations block prints its prototype by it.
|
|
82
|
+
//
|
|
83
|
+
// `byValue` names the arguments that are a struct passed BY VALUE — a local or a global spelled
|
|
84
|
+
// bare, `take(s)` — by their place in `args` and the declared parameter each is, with the struct
|
|
85
|
+
// the declaration passes
|
|
86
|
+
// (frontend/call-plan.ts `foldByValueArgs`); the declarations block defines each beside the
|
|
87
|
+
// callee's prototype.
|
|
88
|
+
//
|
|
89
|
+
// `fn` is the callee's NAME, or for an indirect call the EXPRESSION whose value is the address
|
|
90
|
+
// called (ir/core.ts `calleeValue`) — a child of the node like its arguments, listed first in
|
|
91
|
+
// `exprChildren`. That is no evaluation order: C leaves it unspecified, and agbcc loads the callee
|
|
92
|
+
// after the arguments.
|
|
93
|
+
| {
|
|
94
|
+
k: 'call';
|
|
95
|
+
fn: string | Expr;
|
|
96
|
+
args: Expr[];
|
|
97
|
+
wide64?: true;
|
|
98
|
+
sret?: IrType;
|
|
99
|
+
returns?: IrType;
|
|
100
|
+
byValue?: readonly { readonly at: number; readonly param: number; readonly type: IrType }[];
|
|
101
|
+
}
|
|
26
102
|
// The ADDRESS of a named global, `&gSym` (agbcc pool `.word gSym`, frontend `gaddr` op). A
|
|
27
103
|
// DEREF of it collapses to the bare global: memAccess/arrayAccess spell `*(&gSym)` as `gSym`
|
|
28
104
|
// and `(&gSym)[i]` as `gSym[i]` (a global name decays to a pointer). Only a genuinely
|
|
29
105
|
// address-TAKEN global (passed by address, `&gSym` as a call arg) prints the `&` form. The
|
|
30
106
|
// global's type comes from the project headers, so it is never declared as a local.
|
|
107
|
+
//
|
|
108
|
+
// An ARRAY's address is spelled by the bare name, and that is a fact of C's declarator grammar
|
|
109
|
+
// rather than of the address — so the C printer decides it from the name's declared type, and
|
|
110
|
+
// this node carries nothing about it (backend/cfamily.ts, `case 'addr'`).
|
|
31
111
|
| { k: 'addr'; name: string }
|
|
32
112
|
// A memory access `base[idx]` (printed `*base` when idx is the constant 0), CARRYING the
|
|
33
113
|
// access's element width (bytes) and signedness. `idx` counts elements of `width` bytes.
|
|
@@ -142,6 +222,15 @@ export type Expr =
|
|
|
142
222
|
operandOff?: number;
|
|
143
223
|
baseOrdered?: true;
|
|
144
224
|
baseAdvanced?: number;
|
|
225
|
+
/** Spell element 0 as `base[0]`, never `*base`. The two are one lvalue to C and NOT to
|
|
226
|
+
* agbcc's alias analysis: `u.half[0]` is an ARRAY_REF of a union member, alias set 0
|
|
227
|
+
* (c-common.c `c_get_alias_set`), while `*u.half` dereferences the member's decay — an
|
|
228
|
+
* INDIRECT_REF typed by its pointee, which a compiled `*u.half = 1; *u.word = x; return
|
|
229
|
+
* *u.half;` returns stale (`mov r0, #0x1`) where the subscript reloads. Set on a view of a
|
|
230
|
+
* frame union (structure.ts `frameUnionAccess`), the one access that rests on that set,
|
|
231
|
+
* and on an element of a frame array, where it only keeps the element spelling at index 0
|
|
232
|
+
* (structure.ts `frameArrayElement`, l3/constinit.ts `asElement`). */
|
|
233
|
+
subscript?: true;
|
|
145
234
|
}
|
|
146
235
|
// A named struct-field access `base->name` (raise/structs.ts recovered `base` as a struct
|
|
147
236
|
// pointer, so the byte offset resolves to a named field instead of a scaled array index).
|
|
@@ -176,15 +265,36 @@ export type Expr =
|
|
|
176
265
|
// The divides earned it second, on pokeemerald:GetAnchorCoord — `(u32)(coord * a1) / (u32)a0`
|
|
177
266
|
// standing beside two arithmetic shifts of the same values. Their only other channel is the operand
|
|
178
267
|
// TYPES, reached by flipping a declaration, and there that flip is unreachable and unsound at
|
|
179
|
-
// once: the divisor also feeds a signed compare, so the
|
|
180
|
-
// it, and forcing it anyway makes agbcc
|
|
181
|
-
// the only spelling that says "this
|
|
268
|
+
// once: the divisor also feeds a signed compare, so the unsigned-compare reconciliation
|
|
269
|
+
// (structure.ts unsignedCompareSpelling) correctly refuses it, and forcing it anyway makes agbcc
|
|
270
|
+
// delete the comparison as always-false. A per-operand pin is the only spelling that says "this
|
|
271
|
+
// division alone is unsigned".
|
|
182
272
|
//
|
|
183
273
|
// The COMPARISONS stay collapsed, and that asymmetry is the rule applying rather than an omission:
|
|
184
274
|
// which side a compare was spelled from genuinely underdetermines — a signed spelling that
|
|
185
275
|
// byte-matched was proved non-negative by the compiler — so it is refereed as a variation, while a
|
|
186
276
|
// division helper is a pure function of the expression's C type with no such proof available.
|
|
277
|
+
//
|
|
278
|
+
// THE FLOAT OPERATORS (`f+` `f-` `f*` `f/`) are the third split, and they are earned by what an
|
|
279
|
+
// integer operator MEANS to the passes that read one rather than by a spelling: C writes both with
|
|
280
|
+
// the same token, but `/` here is the SIGNED divide the C-family backend pins with `(s32)` casts,
|
|
281
|
+
// and `+`/`-` are the pointer walks, reassociations and constant folds of a dozen L3 passes. A float
|
|
282
|
+
// under any of those is a wrong program, so it gets operators none of them match — the same reason
|
|
283
|
+
// the IR has `fadd` rather than a float-typed `add`. The unary minus splits the same way (`f-` on
|
|
284
|
+
// `un`). The float COMPARES split for the same reason and one more: NaN makes them a partial order,
|
|
285
|
+
// so `negateCond` may flip `f==` and `f!=` into each other and no other — `!(a f> b)` is not
|
|
286
|
+
// `a f<= b`, and it prints as the `!` it is.
|
|
187
287
|
export type BinOp =
|
|
288
|
+
| 'f+'
|
|
289
|
+
| 'f-'
|
|
290
|
+
| 'f*'
|
|
291
|
+
| 'f/'
|
|
292
|
+
| 'f<'
|
|
293
|
+
| 'f<='
|
|
294
|
+
| 'f>'
|
|
295
|
+
| 'f>='
|
|
296
|
+
| 'f=='
|
|
297
|
+
| 'f!='
|
|
188
298
|
| '+'
|
|
189
299
|
| '-'
|
|
190
300
|
| '*'
|
|
@@ -234,7 +344,15 @@ export type Stmt =
|
|
|
234
344
|
// `break;` / `continue;` — a loop early-exit / next-iteration jump. Emitted ONLY when the target is
|
|
235
345
|
// the innermost enclosing loop (bare C break/continue cannot express a multi-level exit; a deeper
|
|
236
346
|
// target declines). SGI/IDO Pascal has neither, so its backend loud-fails them (like `field`/`cast`).
|
|
237
|
-
|
|
347
|
+
//
|
|
348
|
+
// `ofSwitch` makes the `break` the innermost enclosing SWITCH's instead: it leaves the arm it sits
|
|
349
|
+
// in for the code after the switch (`case 1: if (c) break; case 2: …`). Legal only inside a switch
|
|
350
|
+
// arm with no loop or switch between it and that arm — the producer's contract (structure.ts
|
|
351
|
+
// `switchBreakAt`). The C family prints both as `break;`, so its printer refuses each where C
|
|
352
|
+
// would bind it to the other kind — a plain one between `case` labels, this one in a loop body
|
|
353
|
+
// (backend/cfamily.ts `refuseMisboundBreak`). A pass that reads `break` as a loop exit must skip
|
|
354
|
+
// this one (l3/reindex.ts `hasLoopExit`).
|
|
355
|
+
| { k: 'break'; ofSwitch?: true }
|
|
238
356
|
| { k: 'continue' }
|
|
239
357
|
// A multi-way `switch` over an integer scrutinee (recovered from a comparison tree — Regime A — or
|
|
240
358
|
// a jump-table `switch_br` — Regime B). `cases` are emitted IN ARRAY ORDER; `default` (if present)
|
|
@@ -337,11 +455,27 @@ export interface SFn {
|
|
|
337
455
|
* one are deliberately left unstamped even where the offset is in hand — see the refusal at
|
|
338
456
|
* the structurer's build site for the measurement that decided it. */
|
|
339
457
|
slots?: number[];
|
|
458
|
+
/** The declaration's initializer, element by element, null where the source left one out
|
|
459
|
+
* (`SImage.init`) — present only on the copy the C-family emitter prints, where
|
|
460
|
+
* l3/constinit.ts folded the block copy of the compiler's anonymous constant image into it
|
|
461
|
+
* (`u8 sp0[16] = { … };`). Never on a tree a pass sees. */
|
|
462
|
+
init?: (number | InitAddress | null)[];
|
|
463
|
+
/** that initializer is spelled as a string literal — the image was written as one */
|
|
464
|
+
initString?: true;
|
|
340
465
|
}[];
|
|
341
466
|
/** project globals referenced with a known declaration shape (symbol map) — typed for the
|
|
342
467
|
* legalization env (exprCType) but NEVER declared by a backend: the project's own headers
|
|
343
468
|
* declare them, exactly like every other global name asmlift emits. */
|
|
344
469
|
globals?: { name: string; type: IrType }[];
|
|
470
|
+
/** The function-scope statics this function DEFINES (structure/local-statics.ts), declared at
|
|
471
|
+
* the top of its body ahead of the locals. Unlike `globals`, the backend declares these: no
|
|
472
|
+
* header does, because the source defined them here. */
|
|
473
|
+
statics?: SStatic[];
|
|
474
|
+
/** The compiler's own constant images this function copies a local's initializer from
|
|
475
|
+
* (structure/local-statics.ts), under labels the source never wrote. Not statics: no declaration
|
|
476
|
+
* may spell one, so the C-family emitter folds each into the initializer of the local its copy
|
|
477
|
+
* fills (l3/constinit.ts), and a backend that cannot refuses. */
|
|
478
|
+
images?: SImage[];
|
|
345
479
|
retType: IrType;
|
|
346
480
|
body: Stmt[];
|
|
347
481
|
/** Struct types this function's fields reference, declared above it by the backend. Empty
|
|
@@ -359,6 +493,63 @@ export interface SFn {
|
|
|
359
493
|
* `TargetDescription.compilerBehaviors`. ABSENT means the direction is unknown for this target
|
|
360
494
|
* and `l3/slotorder.ts` is the identity — never "ascending by default". */
|
|
361
495
|
slotOrder?: 'ascending' | 'descending';
|
|
496
|
+
/** Each declared callee's parameter types (an entry the printer cannot spell is `undefined`):
|
|
497
|
+
* what an argument converts to. Read by the C-family printer where `dialect` says C++ or
|
|
498
|
+
* `strictConversions` is set, either of which refuses three conversions C makes implicitly
|
|
499
|
+
* (backend/cfamily.ts `argConversion`), and by the pointer-cell conversions in every dialect
|
|
500
|
+
* (l3/ptrcell.ts). */
|
|
501
|
+
declaredArgs?: Readonly<Record<string, readonly (string | undefined)[]>>;
|
|
502
|
+
/** Each declared callee's return type as the project spells it, never printed: whether a call a
|
|
503
|
+
* pointer cell is compared with returns a pointer (l3/ptrcell.ts). */
|
|
504
|
+
declaredReturns?: Readonly<Record<string, string>>;
|
|
505
|
+
/** The emitted C is compiled AS C++. An emission policy like `slotOrder`. */
|
|
506
|
+
dialect?: 'c++';
|
|
507
|
+
/** The compiler parses a designated element in an initializer (`[8] = 2`) — a fact of its
|
|
508
|
+
* dialect, carried from `compilerBehaviors.designatedInitializers`. An emission policy: the
|
|
509
|
+
* C-family printer spells an initializer's left-out run by index where present, as zeros where
|
|
510
|
+
* not. */
|
|
511
|
+
designators?: true;
|
|
512
|
+
/** The compiler refuses C's implicit pointer conversions in C as well, as C++ does — carried from
|
|
513
|
+
* `compilerBehaviors.strictPointerConversions`. An emission policy: the C-family printer casts an
|
|
514
|
+
* argument to its declared parameter (`declaredArgs`), and a pointer of another pointee written
|
|
515
|
+
* into a pointer slot, wherever it would for C++. */
|
|
516
|
+
strictConversions?: true;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/** An initial element that is an ADDRESS CONSTANT (ir/core.ts `LocalObject.relocs`): `symbol`'s
|
|
520
|
+
* address plus `addend` bytes, held in the element's integer type — a global's, which a
|
|
521
|
+
* declaration outside the function names. */
|
|
522
|
+
export interface InitAddress {
|
|
523
|
+
symbol: string;
|
|
524
|
+
addend: number;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/** One function-scope static's definition: `static [const] T name[count] = { init };`. `init` is
|
|
528
|
+
* the initial elements in order, absent for a static with no initializer (the target's bss); an
|
|
529
|
+
* element the target relocates is the {@link InitAddress} it holds. `align` is an alignment in
|
|
530
|
+
* bytes wider than the type's own, which the definition must state. */
|
|
531
|
+
export interface SStatic {
|
|
532
|
+
name: string;
|
|
533
|
+
type: IrType;
|
|
534
|
+
const?: true;
|
|
535
|
+
align?: number;
|
|
536
|
+
init?: (number | InitAddress)[];
|
|
537
|
+
/** the initializer is spelled as a string literal — a byte array the compiler aligns as a
|
|
538
|
+
* string (structure/local-statics.ts) */
|
|
539
|
+
string?: true;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/** One constant image of the compiler's own (ir/core.ts `LocalObject.anonymous`), named by its
|
|
543
|
+
* label: an integer array typed by the directives that wrote it, and its initializer element by
|
|
544
|
+
* element — null where the source left one out (padding in the image, which C supplies as zero),
|
|
545
|
+
* the {@link InitAddress} an element the target relocates holds, and ending at the last one it
|
|
546
|
+
* spelled, so it may be shorter than `type`. */
|
|
547
|
+
export interface SImage {
|
|
548
|
+
name: string;
|
|
549
|
+
type: IrType;
|
|
550
|
+
init: (number | InitAddress | null)[];
|
|
551
|
+
/** the image was written as a string literal (`.ascii`) */
|
|
552
|
+
string?: true;
|
|
362
553
|
}
|
|
363
554
|
|
|
364
555
|
/** A struct declaration surfaced to the backend (name + field list). Mirrors the IR struct
|
|
@@ -381,6 +572,13 @@ export interface LanguageBackend {
|
|
|
381
572
|
* cannot print it turns a whole function that used to decompile into a loud stub. The
|
|
382
573
|
* structurer reads it through `StructureOptions.spellSwitchFallthrough`. */
|
|
383
574
|
readonly spellsSwitchFallthrough: boolean;
|
|
575
|
+
/** Can this language spell a `break` that leaves a `switch` arm before its end
|
|
576
|
+
* (`{ k: 'break', ofSwitch: true }`)? C and C++ can; Pascal has no `break` at all, and its
|
|
577
|
+
* backend loud-fails the node. Asked for the same reason as `spellsSwitchFallthrough`: a
|
|
578
|
+
* closed arm with a mid-arm exit (`case 10: if (a) { if (b) break; g(1); } h(2); break;`) also
|
|
579
|
+
* spells without one, by copying the code the arm meets at into each path, which Pascal prints.
|
|
580
|
+
* The structurer reads it through `StructureOptions.spellSwitchBreak`. */
|
|
581
|
+
readonly spellsSwitchBreak: boolean;
|
|
384
582
|
emit(fn: SFn): string;
|
|
385
583
|
// Spell ONE LINE of text as a comment in this language (C block comments, Pascal `(* … *)`).
|
|
386
584
|
// Used by the annotate-mode stub path to carry the failure reason + the original asm
|
|
@@ -427,6 +625,8 @@ export function exprEquals(a: Expr, b: Expr): boolean {
|
|
|
427
625
|
return a.name === (b as typeof a).name;
|
|
428
626
|
case 'const':
|
|
429
627
|
return a.value === (b as typeof a).value;
|
|
628
|
+
case 'fconst':
|
|
629
|
+
return a.bits === (b as typeof a).bits;
|
|
430
630
|
case 'bin': {
|
|
431
631
|
const bb = b as typeof a;
|
|
432
632
|
return a.op === bb.op && exprEquals(a.l, bb.l) && exprEquals(a.r, bb.r);
|
|
@@ -435,6 +635,10 @@ export function exprEquals(a: Expr, b: Expr): boolean {
|
|
|
435
635
|
const bb = b as typeof a;
|
|
436
636
|
return a.op === bb.op && exprEquals(a.e, bb.e);
|
|
437
637
|
}
|
|
638
|
+
case 'postincr': {
|
|
639
|
+
const bb = b as typeof a;
|
|
640
|
+
return a.name === bb.name && a.by === bb.by;
|
|
641
|
+
}
|
|
438
642
|
case 'cast': {
|
|
439
643
|
const bb = b as typeof a;
|
|
440
644
|
// `volatile` is part of the SPELLING, compared for the same reason `lead` and `dot` are: a
|
|
@@ -448,7 +652,29 @@ export function exprEquals(a: Expr, b: Expr): boolean {
|
|
|
448
652
|
}
|
|
449
653
|
case 'call': {
|
|
450
654
|
const bb = b as typeof a;
|
|
451
|
-
|
|
655
|
+
// `wide64` is part of the node's IDENTITY, compared for the same reason `volatile` is one
|
|
656
|
+
// cast up: it decides the rank every operand pin is taken at, so two calls that differ in it
|
|
657
|
+
// are two different expressions and a dedup that kept one would respell the other's
|
|
658
|
+
// arithmetic.
|
|
659
|
+
//
|
|
660
|
+
// NO PIPELINE INPUT REACHES THIS TERM TODAY, and saying so is cheaper than a witness that
|
|
661
|
+
// cannot exist: the stamp is a pure function of the callee name (`structure.ts` reads the
|
|
662
|
+
// call result's IR width, which the frontend set from the callee's declared return), so two
|
|
663
|
+
// `call` nodes sharing `fn` and `args` always share `wide64` and deleting the term leaves
|
|
664
|
+
// the whole core suite green. What pins it is a direct test of THIS function's contract in
|
|
665
|
+
// test/int64-repr.test.ts, over two nodes built by hand — which is the honest bar, because
|
|
666
|
+
// the claim being made is about `exprEquals` and not about a path through the pipeline.
|
|
667
|
+
return (
|
|
668
|
+
(typeof a.fn === 'string' || typeof bb.fn === 'string' ? a.fn === bb.fn : exprEquals(a.fn, bb.fn)) &&
|
|
669
|
+
(a.wide64 ?? false) === (bb.wide64 ?? false) &&
|
|
670
|
+
(a.sret === undefined ? bb.sret === undefined : bb.sret !== undefined && typeEquals(a.sret, bb.sret)) &&
|
|
671
|
+
(a.returns === undefined
|
|
672
|
+
? bb.returns === undefined
|
|
673
|
+
: bb.returns !== undefined && typeEquals(a.returns, bb.returns)) &&
|
|
674
|
+
(a.byValue ?? []).map((x) => x.at).join() === (bb.byValue ?? []).map((x) => x.at).join() &&
|
|
675
|
+
a.args.length === bb.args.length &&
|
|
676
|
+
a.args.every((x, i) => exprEquals(x, bb.args[i]))
|
|
677
|
+
);
|
|
452
678
|
}
|
|
453
679
|
case 'index': {
|
|
454
680
|
const bb = b as typeof a;
|
|
@@ -493,12 +719,103 @@ export function exprEquals(a: Expr, b: Expr): boolean {
|
|
|
493
719
|
}
|
|
494
720
|
}
|
|
495
721
|
|
|
496
|
-
/**
|
|
497
|
-
*
|
|
722
|
+
/** Do two statements print the same C? The leaf statements compare through {@link exprEquals},
|
|
723
|
+
* and an `if` arm by arm; every other kind answers no, which can only keep apart two statements a
|
|
724
|
+
* caller might have merged. */
|
|
725
|
+
export function stmtEquals(a: Stmt, b: Stmt): boolean {
|
|
726
|
+
switch (a.k) {
|
|
727
|
+
case 'assign':
|
|
728
|
+
return b.k === 'assign' && a.name === b.name && exprEquals(a.value, b.value);
|
|
729
|
+
case 'store':
|
|
730
|
+
return b.k === 'store' && exprEquals(a.lval, b.lval) && exprEquals(a.value, b.value);
|
|
731
|
+
case 'exprstmt':
|
|
732
|
+
return b.k === 'exprstmt' && exprEquals(a.value, b.value);
|
|
733
|
+
case 'return':
|
|
734
|
+
return (
|
|
735
|
+
b.k === 'return' &&
|
|
736
|
+
(a.unspelled ?? false) === (b.unspelled ?? false) &&
|
|
737
|
+
(a.value === undefined ? b.value === undefined : b.value !== undefined && exprEquals(a.value, b.value))
|
|
738
|
+
);
|
|
739
|
+
case 'if':
|
|
740
|
+
return b.k === 'if' && exprEquals(a.cond, b.cond) && stmtsEqual(a.then, b.then) && stmtsEqual(a.else, b.else);
|
|
741
|
+
default:
|
|
742
|
+
return false;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
/** {@link stmtEquals} over two lists, in order. */
|
|
747
|
+
export function stmtsEqual(a: readonly Stmt[], b: readonly Stmt[]): boolean {
|
|
748
|
+
return a.length === b.length && a.every((s, i) => stmtEquals(s, b[i]));
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/** The node a merge KEEPS when it found `a` and `b` equal ({@link exprEquals}) and drops one: `a`,
|
|
752
|
+
* with every compare carrying `unsignedCmp` if it did in EITHER. Equality ignores the evidence
|
|
753
|
+
* because two compares that print alike are one expression to C; but when one of them was lowered
|
|
754
|
+
* from an `icmp_u` and the other from a signed compare, the kept node now stands for both paths,
|
|
755
|
+
* and the finished-tree judgment (l3/typing.ts `signedOverUnsignedCompare`) can only see the
|
|
756
|
+
* unsigned one's obligation if the node still says so. Keeping `a` bare would publish a signed
|
|
757
|
+
* `x < y` on the path that compared unsigned (an slt arm and an sltu arm merged by l3/tailmerge.ts
|
|
758
|
+
* under `/signed-cmp`). Precondition: `exprEquals(a, b)`, so the two trees walk in step. */
|
|
759
|
+
export function unionEvidence(a: Expr, b: Expr): Expr {
|
|
760
|
+
// `mapExprChildren` calls `f` in `exprChildren` order (an object literal evaluates its
|
|
761
|
+
// properties in source order), so the i-th call pairs with b's i-th child. Not by identity: one
|
|
762
|
+
// child object may sit in two positions, each with its own counterpart in `b`.
|
|
763
|
+
const bs = exprChildren(b);
|
|
764
|
+
let i = 0;
|
|
765
|
+
const out = mapExprChildren(a, (c) => unionEvidence(c, bs[i++]));
|
|
766
|
+
return out.k === 'bin' && b.k === 'bin' && b.unsignedCmp === true && out.unsignedCmp !== true
|
|
767
|
+
? { ...out, unsignedCmp: true }
|
|
768
|
+
: out;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/** {@link unionEvidence} over two statements {@link stmtEquals} found equal. */
|
|
772
|
+
export function unionStmtEvidence(a: Stmt, b: Stmt): Stmt {
|
|
773
|
+
switch (a.k) {
|
|
774
|
+
case 'assign':
|
|
775
|
+
return b.k === 'assign' ? { ...a, value: unionEvidence(a.value, b.value) } : a;
|
|
776
|
+
case 'store':
|
|
777
|
+
return b.k === 'store'
|
|
778
|
+
? { ...a, lval: unionEvidence(a.lval, b.lval), value: unionEvidence(a.value, b.value) }
|
|
779
|
+
: a;
|
|
780
|
+
case 'exprstmt':
|
|
781
|
+
return b.k === 'exprstmt' ? { ...a, value: unionEvidence(a.value, b.value) } : a;
|
|
782
|
+
case 'return':
|
|
783
|
+
return b.k === 'return' && a.value !== undefined && b.value !== undefined
|
|
784
|
+
? { ...a, value: unionEvidence(a.value, b.value) }
|
|
785
|
+
: a;
|
|
786
|
+
case 'if':
|
|
787
|
+
return b.k === 'if'
|
|
788
|
+
? {
|
|
789
|
+
...a,
|
|
790
|
+
cond: unionEvidence(a.cond, b.cond),
|
|
791
|
+
then: unionStmtsEvidence(a.then, b.then),
|
|
792
|
+
else: unionStmtsEvidence(a.else, b.else),
|
|
793
|
+
}
|
|
794
|
+
: a;
|
|
795
|
+
default:
|
|
796
|
+
return a;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
/** {@link unionStmtEvidence} over two lists {@link stmtsEqual} found equal. */
|
|
801
|
+
export function unionStmtsEvidence(a: readonly Stmt[], b: readonly Stmt[]): Stmt[] {
|
|
802
|
+
return a.map((s, i) => unionStmtEvidence(s, b[i]));
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
/** THE spelling of an `opaque`'s gap reason, in one place: `structure.ts` writes it into the
|
|
806
|
+
* marker, `contracts.ts` matches on it to prove the gap was not dropped, and the benchmark
|
|
498
807
|
* classifies declines by it. Two spellings make that contract silently vacuous — enforced-looking
|
|
499
|
-
* and never firing
|
|
500
|
-
|
|
501
|
-
|
|
808
|
+
* and never firing, since it is an EQUALITY between what the IR carries and what the output shows.
|
|
809
|
+
*
|
|
810
|
+
* TWO PRODUCERS, TWO SENTENCES, and they name different capabilities: an INSTRUCTION no frontend
|
|
811
|
+
* decodes (`adc`), and a call into the compiler's own RUNTIME that no recogniser folded
|
|
812
|
+
* (`__div2i`). The attrs decide which, so a producer chooses by what it stamps rather than by
|
|
813
|
+
* spelling a string. `?` when a frontend stamps neither. */
|
|
814
|
+
export function gapReasonFor(attrs: { mnemonic?: unknown; helper?: unknown }): string {
|
|
815
|
+
if (typeof attrs.helper === 'string') {
|
|
816
|
+
return `no model for the runtime helper '${attrs.helper}'`;
|
|
817
|
+
}
|
|
818
|
+
return `unmodelled instruction '${typeof attrs.mnemonic === 'string' ? attrs.mnemonic : '?'}'`;
|
|
502
819
|
}
|
|
503
820
|
|
|
504
821
|
// ── the ONE traversal vocabulary ───────────────────────────────────────────────────────────────
|
|
@@ -513,7 +830,9 @@ export function exprChildren(e: Expr): Expr[] {
|
|
|
513
830
|
switch (e.k) {
|
|
514
831
|
case 'var':
|
|
515
832
|
case 'const':
|
|
833
|
+
case 'fconst':
|
|
516
834
|
case 'addr':
|
|
835
|
+
case 'postincr':
|
|
517
836
|
return [];
|
|
518
837
|
case 'bin':
|
|
519
838
|
return [e.l, e.r];
|
|
@@ -521,7 +840,7 @@ export function exprChildren(e: Expr): Expr[] {
|
|
|
521
840
|
case 'cast':
|
|
522
841
|
return [e.e];
|
|
523
842
|
case 'call':
|
|
524
|
-
return e.args;
|
|
843
|
+
return typeof e.fn === 'string' ? e.args : [e.fn, ...e.args];
|
|
525
844
|
case 'index':
|
|
526
845
|
return [e.base, ...(e.lead ?? []), e.idx];
|
|
527
846
|
case 'field':
|
|
@@ -536,7 +855,9 @@ export function mapExprChildren(e: Expr, f: (c: Expr) => Expr): Expr {
|
|
|
536
855
|
switch (e.k) {
|
|
537
856
|
case 'var':
|
|
538
857
|
case 'const':
|
|
858
|
+
case 'fconst':
|
|
539
859
|
case 'addr':
|
|
860
|
+
case 'postincr':
|
|
540
861
|
return e;
|
|
541
862
|
case 'bin':
|
|
542
863
|
return { ...e, l: f(e.l), r: f(e.r) };
|
|
@@ -544,7 +865,7 @@ export function mapExprChildren(e: Expr, f: (c: Expr) => Expr): Expr {
|
|
|
544
865
|
case 'cast':
|
|
545
866
|
return { ...e, e: f(e.e) };
|
|
546
867
|
case 'call':
|
|
547
|
-
return { ...e, args: e.args.map(f) };
|
|
868
|
+
return { ...e, ...(typeof e.fn === 'string' ? {} : { fn: f(e.fn) }), args: e.args.map(f) };
|
|
548
869
|
case 'index':
|
|
549
870
|
return { ...e, base: f(e.base), ...(e.lead ? { lead: e.lead.map(f) } : {}), idx: f(e.idx) };
|
|
550
871
|
case 'field':
|
|
@@ -654,7 +975,7 @@ export function rematerializableAddress(e: Expr): boolean {
|
|
|
654
975
|
case 'un':
|
|
655
976
|
break;
|
|
656
977
|
default:
|
|
657
|
-
ok = false; // var, addr, index, field, call, marker
|
|
978
|
+
ok = false; // var, addr, postincr, index, field, call, marker
|
|
658
979
|
return;
|
|
659
980
|
}
|
|
660
981
|
for (const c of exprChildren(x)) {
|
|
@@ -665,10 +986,27 @@ export function rematerializableAddress(e: Expr): boolean {
|
|
|
665
986
|
return ok && nonZero;
|
|
666
987
|
}
|
|
667
988
|
|
|
668
|
-
/**
|
|
669
|
-
*
|
|
989
|
+
/** The local or global a node NAMES, or undefined for one that names none — the whole Expr
|
|
990
|
+
* vocabulary a "which names does this tree mention" walk has to know about, in one place, because
|
|
991
|
+
* a collector that misses a kind reports a SHORT live range and a short range is a clobber where a
|
|
992
|
+
* long one is only a missed merge.
|
|
993
|
+
*
|
|
994
|
+
* `call`'s `fn` is not one of them: it names a function, and the walks that care about that ask
|
|
995
|
+
* for it beside this (l3/hoist.ts). A walk that must tell the kinds APART — `&v` is a write where
|
|
996
|
+
* a bare `v` is a read — keeps its own arms; this answers the name, not what is done to it. */
|
|
997
|
+
export function mentionedName(e: Expr): string | undefined {
|
|
998
|
+
return e.k === 'var' || e.k === 'addr' || e.k === 'postincr' ? e.name : undefined;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
/** The callee a call names, or undefined for an indirect one. */
|
|
1002
|
+
export function calleeOf(e: Extract<Expr, { k: 'call' }>): string | undefined {
|
|
1003
|
+
return typeof e.fn === 'string' ? e.fn : undefined;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
/** Whether the tree contains a node with an EFFECT no re-ordering may move: a call, a marker
|
|
1007
|
+
* standing in for an unmodelled instruction (annotate mode), or a post-increment's write. */
|
|
670
1008
|
export function exprHasEffect(e: Expr): boolean {
|
|
671
|
-
return e.k === 'call' || e.k === 'marker' || exprChildren(e).some(exprHasEffect);
|
|
1009
|
+
return e.k === 'call' || e.k === 'marker' || e.k === 'postincr' || exprChildren(e).some(exprHasEffect);
|
|
672
1010
|
}
|
|
673
1011
|
|
|
674
1012
|
/** Whether the tree performs an access to a `volatile` object — the OTHER thing no re-ordering may
|
|
@@ -703,6 +1041,44 @@ function* subterms(e: Expr): Generator<Expr> {
|
|
|
703
1041
|
}
|
|
704
1042
|
}
|
|
705
1043
|
|
|
1044
|
+
export type Assign = Extract<Stmt, { k: 'assign' }>;
|
|
1045
|
+
|
|
1046
|
+
/** Every `assign` in the tree, at any nesting. ORDER-FREE: its callers ask what a name is ever
|
|
1047
|
+
* assigned, never which assignment comes first, so a switch default's position does not matter. */
|
|
1048
|
+
export function assignsIn(body: readonly Stmt[]): Assign[] {
|
|
1049
|
+
const out: Assign[] = [];
|
|
1050
|
+
const visit = (s: Stmt): void => {
|
|
1051
|
+
if (s.k === 'assign') {
|
|
1052
|
+
out.push(s);
|
|
1053
|
+
}
|
|
1054
|
+
stmtChildren(s).forEach(visit);
|
|
1055
|
+
};
|
|
1056
|
+
body.forEach(visit);
|
|
1057
|
+
return out;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
/** Grow `seed` over assignments to a fixpoint: an assignment adds its target when `propagates`
|
|
1061
|
+
* says its value carries what the set holds so far. A FLOW-INSENSITIVE taint, which is what each
|
|
1062
|
+
* caller wants — the question is what a name may EVER hold, so one assignment anywhere is
|
|
1063
|
+
* enough. Callers own the seed and the rule; this owns only the iteration. */
|
|
1064
|
+
export function taintOverAssigns(
|
|
1065
|
+
assigns: readonly Assign[],
|
|
1066
|
+
seed: Iterable<string>,
|
|
1067
|
+
propagates: (a: Assign, tainted: ReadonlySet<string>) => boolean,
|
|
1068
|
+
): Set<string> {
|
|
1069
|
+
const tainted = new Set(seed);
|
|
1070
|
+
for (let grew = true; grew;) {
|
|
1071
|
+
grew = false;
|
|
1072
|
+
for (const a of assigns) {
|
|
1073
|
+
if (!tainted.has(a.name) && propagates(a, tainted)) {
|
|
1074
|
+
tainted.add(a.name);
|
|
1075
|
+
grew = true;
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
return tainted;
|
|
1080
|
+
}
|
|
1081
|
+
|
|
706
1082
|
/** The statements a statement DIRECTLY contains, in the order a backend prints them — a `switch`
|
|
707
1083
|
* splices its default in at `defaultAt` for that reason. NOTE for document-order walks: a `for`'s
|
|
708
1084
|
* init/inc are listed here while its cond is in stmtExprs — a walker visiting exprs-then-stmts
|
|
@@ -905,8 +1281,76 @@ export const NEGATE_REL: Partial<Record<BinOp, BinOp>> = {
|
|
|
905
1281
|
'<=': '>',
|
|
906
1282
|
'==': '!=',
|
|
907
1283
|
'!=': '==',
|
|
1284
|
+
// C's float `==` is false on a NaN and `!=` true, so these two are each other's negation; the
|
|
1285
|
+
// ordered four have none in the operator vocabulary (the BinOp note).
|
|
1286
|
+
'f==': 'f!=',
|
|
1287
|
+
'f!=': 'f==',
|
|
908
1288
|
};
|
|
909
1289
|
|
|
1290
|
+
/** The compares under `e`'s connectives whose runtime helper names the relation the source spelled
|
|
1291
|
+
* (`helperRel`), each answering whether it spells that relation now. `negateCond` flips every one
|
|
1292
|
+
* of them at once, through De Morgan. */
|
|
1293
|
+
function helperRelLeaves(e: Expr, out: boolean[] = []): boolean[] {
|
|
1294
|
+
if (e.k === 'bin' && (e.op === '&&' || e.op === '||')) {
|
|
1295
|
+
helperRelLeaves(e.l, out);
|
|
1296
|
+
helperRelLeaves(e.r, out);
|
|
1297
|
+
} else if (e.k === 'bin' && e.helperRel !== undefined) {
|
|
1298
|
+
out.push(e.op === e.helperRel);
|
|
1299
|
+
}
|
|
1300
|
+
return out;
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
/** Whether `e` spells, at every compare of it whose runtime helper names the relation the source
|
|
1304
|
+
* spelled, that relation — so that negating `e` recompiles to the other helper at each of them:
|
|
1305
|
+
* the relation's twin and its `!` alike, since gcc folds `!(a == b)` into `a != b`. An arm swap
|
|
1306
|
+
* that would negate it keeps `e` and its arm order instead. */
|
|
1307
|
+
export function keepsHelperRel(e: Expr): boolean {
|
|
1308
|
+
const leaves = helperRelLeaves(e);
|
|
1309
|
+
return leaves.length > 0 && leaves.every((own) => own);
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
/** Whether negating `e` puts every such compare back on its helper's relation: the swap a pass
|
|
1313
|
+
* would otherwise leave alone is the one that spells the helpers. A condition mixing the two has
|
|
1314
|
+
* neither answer, and no arm order spells it. */
|
|
1315
|
+
export function regainsHelperRel(e: Expr): boolean {
|
|
1316
|
+
const leaves = helperRelLeaves(e);
|
|
1317
|
+
return leaves.length > 0 && leaves.every((own) => !own);
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
/** A loop's stay condition `P && S` whose compares under `S` are all negated off their helper's
|
|
1321
|
+
* relation, and whose `P` negates none: `{ head: P, guard: !S }`, which the loop spells
|
|
1322
|
+
* `while (P) { if (!S) break; … }` — the same tests in the same order, calling the helpers the
|
|
1323
|
+
* source called. `while (P && S)` has no spelling of that `S` that calls the helper (gcc folds
|
|
1324
|
+
* `!(a == b)` into `a != b`); the break guard is how the source wrote it before the branch fold
|
|
1325
|
+
* joined the two tests. Null when the condition already spells every helper's relation, or when no
|
|
1326
|
+
* split of its `&&` chain separates the two kinds of compare. */
|
|
1327
|
+
export function splitHelperBreak(cond: Expr): { head: Expr; guard: Expr } | null {
|
|
1328
|
+
if (keepsHelperRel(cond) || helperRelLeaves(cond).length === 0) {
|
|
1329
|
+
return null;
|
|
1330
|
+
}
|
|
1331
|
+
const conj: Expr[] = [];
|
|
1332
|
+
const flatten = (e: Expr): void => {
|
|
1333
|
+
if (e.k === 'bin' && e.op === '&&') {
|
|
1334
|
+
flatten(e.l);
|
|
1335
|
+
flatten(e.r);
|
|
1336
|
+
} else {
|
|
1337
|
+
conj.push(e);
|
|
1338
|
+
}
|
|
1339
|
+
};
|
|
1340
|
+
flatten(cond);
|
|
1341
|
+
const and = (xs: Expr[]): Expr => xs.reduce((l, r) => ({ k: 'bin', op: '&&', l, r }));
|
|
1342
|
+
// The longest prefix that negates no helper leaf; the rest must negate every one it holds.
|
|
1343
|
+
let k = 0;
|
|
1344
|
+
while (k < conj.length && helperRelLeaves(conj[k]).every((own) => own)) {
|
|
1345
|
+
k++;
|
|
1346
|
+
}
|
|
1347
|
+
if (k === 0 || k === conj.length) {
|
|
1348
|
+
return null;
|
|
1349
|
+
}
|
|
1350
|
+
const rest = and(conj.slice(k));
|
|
1351
|
+
return regainsHelperRel(rest) ? { head: and(conj.slice(0, k)), guard: negateCond(rest) } : null;
|
|
1352
|
+
}
|
|
1353
|
+
|
|
910
1354
|
export function negateCond(e: Expr): Expr {
|
|
911
1355
|
if (e.k === 'bin') {
|
|
912
1356
|
const flipped = NEGATE_REL[e.op];
|