Fil-C on Nix

Filnix wiki

mbrock/filnix/nanofil

amd64_regs_check.pl

Translation validation for whole-function scalar register homes (docs/perf.md item 3, run by --registers=temps). The allocator proposes a fixed map from frame homes to registers; nothing about how it chose the map is trusted here.

The allocated stream is determined by the reference stream, the map and the save slots: every access to a mapped home is the same move with the home's register in place of its slot (substitute/3), the callee-saved registers the map uses are stored to their save slots right after the stack check and reloaded before every leave, and nothing else changes. The checker rebuilds that stream and requires the proposal to equal it, so flags, failure arguments, control flow and every unmapped location are unchanged by construction.

What remains to prove is that each mapped register holds its home's value wherever the reference could read it. The invariant: at every point, every live half (low or high 32 bits) of a mapped home equals the same half of its register. Liveness is computed here, per half, on the reference stream's control-flow graph, which must be complete: every jump names a label of the function or the stack-overflow exit, and no instruction falls off the end. The obligations:

EventObligation
function entryno mapped half is live
a write to home H in register Revery other home in R is dead after it
a 32-bit write to HH's high half is dead after it (a register write zero-extends)
a callhomes in r10/r11 are dead after it, except at the three authenticated save stubs
a leaveno mapped half is live (the restores replace the registers)

Byte and word writes preserve the rest of a register as they do of memory; liveness treats them as reads. Callee-saved registers keep their values across calls by the SysV ABI, which every callee obeys: C runtime functions, clang code and nanofil functions, which save what they use here. The reference stream must not name an allocatable register or %rbp outside the prologue, must not take the address of a mapped home or save slot, and must not touch a save slot.

Trusted, as in M6's retired cache checker: the emitter's home classes and root ranges (the frame record and local objects, from the layout that places them), and that only %rbp-relative operands reach a home (homes and save slots never escape). Checked: no mapped home overlaps a root range, so every GC root keeps the reference's stores, and a pointer's address word (class a) may be a home but its lower never is (docs/perf.md 5b-2).

usesamd64_contractinstruction_effects/2, register/3, operand_register/2, preserving_call/1
usesassoc
useslists
usespairs
check_regs+Metadata, +Map, +Before, +Aftersemidet

Metadata is regs_metadata(Setjmp, Impl, Homes, Saves): Homes lists home(Offset, Classes) as for the scalar cache, with class a for a pointer's address word (docs/perf.md 5b-2), and root(Offset, Bytes) for each frame range holding GC roots, which no mapped home may overlap: the frame record and local objects. Saves lists save(R, Offset) for each callee-saved register in Map, in callee_saved/1 order. Map lists Offset-Register. An empty map or a setjmp function admits only identity.

75check_regsMeta, Map, Before, After ←
Meta = regs_metadata_, Impl, _, _
impl_bodyImpl, Before, _, Body
pairs_offsetsMap, Homes
body_analysisBody, Homes, Analysis
→
true
else
Analysis = none
check_regsMeta, Map, Before, After, Analysis
check_regs+Metadata, +Map, +Before, +After, +Analysissemidet

As check_regs/4, with the effects, CFG and home liveness of Before's implementation body already computed by body_analysis/3 (the driver computes it once and shares it with the allocator). The analysis must be of that very body and cover every mapped home; liveness bits are independent per home, so extra homes change nothing.

91check_regsregs_metadataSetjmp, Impl, Homes, Saves, Map, Before, After, Analysis ←
groundSetjmp−Impl−Homes−Saves−Map−Before−After
Setjmp ≡ true
or
Map ≡ []
→
Before ≡ After
else
valid_mapMap, Homes, Saves
impl_bodyImpl, Before, Pre, Body
Analysis = analysisBody0, Effects, _, _, _, _, _
Body0 ≡ Body
list_to_assocMap, Mapped
preflight_regsBody, Mapped, Saves, Effects
allocated_streamBody, Mapped, Saves, Expected
appendPre, labelImpl|Expected, After
obligationsBody, Mapped, Analysis
allocatable?Rnondet

R may hold a home: a callee-saved register, or r10 or r11, which only a home dead across every call may take.

110allocatableR ←
111allocatabler10
112allocatabler11
callee_saved?Rnondet

R is a SysV callee-saved register a home live across a call may take: rbx or r12 to r15. The emitter reserves a save slot for each.

callee_savedrbxr12r13r14r15

Mapped homes are disjoint 8-byte homes, every description of each one a scalar (w, l) or a pointer's address word (a), never a pointer (p); no mapped home overlaps a GC root range; save slots match the callee-saved registers used, in order, and lie outside every home.

124valid_mapMap, Homes, Saves ←
is_listMap
Map = _|_
pairs_offsetsMap, Offsets
msortOffsets, Sorted
sortOffsets, Sorted
apartSorted
forallmemberD−R, Map,
memberchkhomeD, _, Homes
forallmemberhomeD, Ks, Homes,
Ks = _|_
forallmemberK, Ks, memberchkK, w, l, a
¬
memberrootB, N, Homes
D < B + N
B < D + 8
findallR,
memberchk_−R, Map
,
Used
findallR, membersaveR, _, Saves, Used
forallmembersave_, S, Saves,
integerS
S < 0
S mod 8 =:= 0
¬
memberhomeH, _, Homes
S < H + 8
H < S + 8
findallS, membersave_, S, Saves, Ss
sortSs, SSet
lengthSs, N
lengthSSet, N
141apart[]
142apart_
143apartA, B|Os ←
B−A ≥ 8
apartB|Os
145pairs_offsets[], []
146pairs_offsetsD−_|Ps, D|Ds ←
integerD
impl_body+Impl, +Lines, -Pre, -Bodysemidet

Body follows label(Impl) in Lines; Pre is everything before it.

150impl_bodyImpl, Lines, Pre, Body ←
appendPre, labelImpl|Body, Lines
!
preflight_regs+Body, +Mapped, +Savessemidet

Every line has audited effects; no allocatable register is named; %rbp appears as a register only in the prologue; no indexed %rbp operand; no lea of a mapped home or save slot; no access to a save slot; an access that touches a mapped home is a substitutable move of that exact home.

159preflight_regsBody, Mapped, Saves ←
maplistline_effect, Body, Effects
preflight_regsBody, Mapped, Saves, Effects
preflight_regs+Body, +Mapped, +Saves, +Effectssemidet

As preflight_regs/3, with Effects the lines' audited effects.

165preflight_regsBody, Mapped, Saves, Effects ←
Body = inspush, q, regrbp, insmov, q, regrsp, regrbp|Rest
Effects = _, _|RestEffects
findallS, membersave_, S, Saves, SaveOffsets
maplistpreflight_lineMapped, SaveOffsets, Rest, RestEffects
171preflight_lineMapped, Saves, Line, effect_, _, _, Ms, _ ←
¬
Line = ins_, _, Os
memberO, Os
¬
Line = inslea, _, memD, rbp, _
touchesD, 1, Mapped, Saves
¬
memberS, Saves
D < S + 8
S < D + N
Line ≠ inslea, _, _
mapped_overlapD, N, Mapped
→
oncesubstituteLine, Mapped, _
else
true
named_register+O

the operand O names an allocatable register (as amd64_contract's explicit_registers/2 reads operands), or %rbp other than as the base of a mem(D, rbp) operand.

183named_registerO ←
184named_registerO ←
185named_registermem_, rbp, _
187rbp_registerregR ←
registerR, rbp, _
188rbp_registerindregR ←
registerR, rbp, _
190touchesD, N, Mapped, Saves ←
mapped_overlapD, N, Mapped
→
true
else
memberS, Saves
D < S + 8
S < D + N
193mapped_overlapD, N, Mapped ←
¬empty_assocMapped
H0 is D−7
H1 is D + N−1
betweenH0, H1, H
get_assocH, Mapped, _
!
rbp_access+Line, -D, -Bytesnondet

Line reads or writes Bytes at D(%rbp).

200rbp_accessLine, D, N ←
onceinstruction_effectsLine, effect_, _, _, Ms, _
effect_access+Memory, -D, -Bytesnondet

One of the memory effects Memory (instruction_effects/2) reads or writes Bytes at D(%rbp).

207effect_accessMs, D, N ←
memberM, Ms
M = readmemD, rbp, N
or
M = writememD, rbp, N
substitute+Line, +Mapped, -Newsemidet

New is the move Line with its mapped home operand replaced by the home's register at the same width. Only moves are substituted.

213substituteinsmov, S, memD, rbp, regX, Mapped, insmov, S, regN, regX ←
get_assocD, Mapped, R
registerN, R, W
215substituteinsmov, S, regX, memD, rbp, Mapped, insmov, S, regX, regN ←
get_assocD, Mapped, R
registerN, R, W

A q immediate must fit a sign-extended imm32, as the memory form must; the register form would otherwise assemble as movabs.

219substituteinsmov, S, immI, memD, rbp, Mapped, insmov, S, immI, regN ←
integerI
S ≡ q
→
I ≥ −1 << 31
I < 1 << 31
else
true
get_assocD, Mapped, R
registerN, R, W
222substituteinsOp, S, memD, rbp, regX, Mapped, insOp, S, regN, regX ←
get_assocD, Mapped, R
registerN, R, W
move_bits
226b8
226w16
226l32
226q64
extension_bits
227movzb8
227movsb8
228movzw16
228movsw16
228movsl32
allocated_stream+Body, +Mapped, +Saves, -Aftersemidet

After is the implementation body Body with the map applied: mapped moves substituted, saves after the stack check, restores before every leave. Fails if Body has no stack check, if anything before it is a label, a transfer or an access to a mapped home (the saves must run once, on every path, before any mapped register changes), or if a leave is not followed by ret.

237allocated_streamBody, Mapped, Saves, Expected ←
appendPrologue, Check|Rest, Body
Check ≡ insjae, none, pltfilc_stack_overflow_failure
!
mapliststraight_prologueMapped, Prologue
findallinsmov, q, regR, memS, rbp, membersaveR, S, Saves, Stores
findallinsmov, q, memS, rbp, regR, membersaveR, S, Saves, Loads
maplistexpected_lineMapped, Prologue, Prologue1
expected_restRest, Mapped, Loads, Rest1
appendPrologue1, Check, Stores, Rest1, Expected
247straight_prologueMapped, Line ←
onceinstruction_effectsLine, effect_, _, _, _, Flow
memberchkFlow, next, frame
¬
rbp_accessLine, D, N
mapped_overlapD, N, Mapped

Every leave is followed by ret; the restores come before the leave.

253expected_rest[], _, _, []
254expected_restinsleave, none, [], insret, none, []|Ls, Mapped, Loads, Out ←
!
appendLoads, insleave, none, [], insret, none, []|Out1, Out
expected_restLs, Mapped, Loads, Out1
257expected_restL|Ls, Mapped, Loads, L1|Out ←
L ≠ insleave, _, _
L ≠ insret, _, _
expected_lineMapped, L, L1
expected_restLs, Mapped, Loads, Out
262expected_lineMapped, L, L1 ←
substituteL, Mapped, New
→
L1 = New
else
L1 = L

Control flow and liveness

successors+Body, -Succssemidet

Succs are the successors of each line of Body by index. Fails when a transfer is unknown or the last line falls through: the checker's liveness needs the complete control-flow graph.

272successorsBody, Succs ←
maplistline_effect, Body, Effects
successorsBody, Effects, Succs
line_effect+Line, -Effectsemidet

Effect is instruction_effects/2's first answer for Line.

279line_effectLine, Effect ←
onceinstruction_effectsLine, Effect
successors+Body, +Effects, -Succssemidet

As successors/2, given each line's line_effect/2.

284successorsBody, Effects, Succs ←
lengthBody, N
findallL−I, nth0I, Body, labelL, Ls0
keysortLs0, Ls1
group_pairs_by_keyLs1, Ls
list_to_assocLs, Labels
numlist_from0, N, Is
maplistline_successorsLabels, N, Is, Body, Effects, Succs
291numlist_fromI, N, [] ←
I ≥ N
!
292numlist_fromI, N, I|Is ←
J is I + 1
numlist_fromJ, N, Is
294line_successorsLabels, N, I, Line, effect_, _, _, _, Flow, Ss ←
J is I + 1
flow_successorsFlow, Line, Labels−I, J, Ss0
¬
memberS, Ss0
S ≥ N
Ss = Ss0
target+Labels−I, +Name, -T

T is the index of the label Name jumped to from line I. GNU as numeric labels: 1f is the next 1, 1b the previous one.

303targetLabels−I, Name, T ←
The last character first: splitting every name with atom_concat/3
costs its length per jump.
sub_atomName, _, 1, 0, Dir
memberchkDir, f, b
sub_atomName, 0, _, 1, Digits
atom_charsDigits, C|Cs
maplistdigit, C|Cs
!
get_assocDigits, Labels, Is
Dir ≡ f
→
once
memberT, Is
T > I
else
reverseIs, Rs
once
memberT, Rs
T < I
314targetLabels−_, Name, T ←
get_assocName, Labels, T
317digitC ←
memberchkC, '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
319flow_successorsnext, _, _, J, J
320flow_successorsframe, _, _, J, J

A call into the body itself would return into the wrong context.

322flow_successorscall, inscall, _, T, Labels−_, J, J ←
¬
T = symL
get_assocL, Labels, _
324flow_successorslabel, _, _, J, J
325flow_successorsdirective, _, _, J, J
326flow_successorsterminal, _, _, _, []
327flow_successorsbranch, insjmp, none, symL, Labels, _, T ←
!
targetLabels, L, T
329flow_successorsbranch, insOp, none, symL, Labels, J, T, J ←
Op ≢ jmp
!
targetLabels, L, T
331flow_successorsbranch, insOp, none, pltS, _, J, J ←
Op ≢ jmp
334noreturnfilc_stack_overflow_failure
home_liveness+Body, +Homes, -LiveOut, -LiveIn0semidet

Homes is a list of home offsets. LiveOut lists, per line of Body, the set of live halves after it as a bitset: bit 2K is home K's low half, 2K+1 its high half (body_analysis/3's sets shifted down by the eight scratch and flags bits). LiveIn0 is the set live at Body's first line. Dataflow runs on basic blocks: a block ends at a line whose only successor is not the next line, and starts at a label.

343home_livenessBody, Homes, LiveOut, LiveIn0 ←
body_analysisBody, Homes, analysis_, _, _, _, LiveOut0, LiveIn00, _
lengthHomes, N
Mask is 1 << 2 × N−1
maplistand_maskMask, LiveOut0, LiveOut
LiveIn0 is LiveIn00 >> 8 /\ Mask
348and_maskM, X, Y ←
Y is X >> 8 /\ M
home_shift+K, -Sdet

Home K's low half is bit S of the analysis's sets, its high half S+1. Bits 0-7 are the scratch families and the flags, below the homes, so a set is only as wide as the highest home live in it: with them on top, every line's set was as wide as all the function's homes.

355home_shiftK, S ←
S is 8 + 2 × K
body_analysis+Body, +Homes, -Analysissemidet

Analysis = analysis(Body, Effects, Homes, Index, LiveOut, LiveIn0, UDs): each line's audited effects, the home offsets and their bit numbers (Index), the live sets after each line (LiveOut) and at the first, and each line's ud(Use, Def). Bits 0-6 are the scratch families (scratch_family/2) and bit 7 the flags, which only the operand checker reads (docs/perf.md, step 3b); home K's halves are the two bits from home_shift/2. Fails when a line has no audited effects or the CFG is incomplete. The one whole-function analysis the allocator and both checkers share.

367body_analysisBody, Homes, Analysis ←
maplistline_effect, Body, Effects
body_analysisBody, Effects, Homes, Analysis
body_analysis+Body, +Effects, +Homes, -Analysissemidet

As body_analysis/3, with Effects the lines' audited effects.

373body_analysisBody, Effects, Homes, analysisBody, Effects, Homes, Index, LiveOut, LiveIn0, UDs ←
successorsBody, Effects, Succs
numberedHomes, 0, Pairs
list_to_assocPairs, Index
home_lookupPairs, Lookup
maplistline_use_defLookup, Body, Effects, UDs
linesBody, 0, Succs, UDs, Lines
blocksLines, Blocks
findallS−B, nth0B, Blocks, blockS, _, _, Starts
list_to_assocStarts, Ids
maplistblock_summaryIds, Blocks, Summaries
Sum =.. b|Summaries
lengthBlocks, NB
empty_assocIn0
fixpointNB, Sum, In0, In
maplistblock_live_outIds, In, Blocks, PerBlock
appendPerBlock, LiveOut
get_assoc0, In, LiveIn0
→
true
else
LiveIn0 = 0
391numbered[], _, []
392numberedH|Hs, K, H−K|Ps ←
K1 is K + 1
numberedHs, K1, Ps
394lines[], _, [], [], []
395linesL|Ls, I, S|Ss, UD|UDs, lineI, L, S, UD|Rest ←
J is I + 1
linesLs, J, Ss, UDs, Rest
blocks+Lines, -Blocks

block(Start, Lines, Succs) with the successor line indices of its last line.

400blocks[], []
401blocksL|Ls, blockI, L|More, Succs|Bs ←
L = lineI, _, _, _
block_restL, Ls, More, Succs, Rest
blocksRest, Bs
406block_restlineI, _, Ss, _, Ls, More, Succs, Rest ←
J is I + 1
Ss ≡ J
Ls = N|Ls1
N = lineJ, Line, _, _
Line ≠ label_
→
More = N|More1
block_restN, Ls1, More1, Succs, Rest
else
More = []
Succs = Ss
Rest = Ls

A block's gen and kill sets, composed backward over its lines. Set difference is xor(A, A /\ B), not A /\ \ B: pinned Trealla truncates a bigint intersected with a negative machine integer.

416block_summaryIds, block_, Lines, Succs, sumG, K, SuccIds ←
reverseLines, Rev
foldlcompose, Rev, 0−0, G−K
maplistblock_idIds, Succs, SuccIds
420composeline_, _, _, udU, D, G0−K0, G−K ←
G is U \/ G0 xor G0 /\ D
K is K0 \/ D
422block_idIds, S, B ←
get_assocS, Ids, B

Backward passes over the blocks until no live-in set changes.

425fixpointNB, Sum, In0, In ←
passNB, Sum, In0, In1, false, Changed
Changed ≡ true
→
fixpointNB, Sum, In1, In
else
In = In1
429pass0, _, In, In, C, C ←
!
430passB1, Sum, In0, In, C0, C ←
B is B1−1
argB1, Sum, sumG, K, Ss
foldlor_liveIn0, Ss, 0, Out
New is G \/ Out xor Out /\ K
get_assocB, In0, Old
→
true
else
Old = 0
New =:= Old
→
In1 = In0
C1 = C0
else
put_assocB, In0, New, In1
C1 = true
passB, Sum, In1, In, C1, C
438or_liveIn, B, A0, A ←
get_assocB, In, X
→
true
else
X = 0
A is A0 \/ X

The live-out set of each line of a block, in order.

441block_live_outIds, In, block_, Lines, Succs, Outs ←
maplistblock_idIds, Succs, SuccIds
foldlor_liveIn, SuccIds, 0, Out
reverseLines, Rev
line_outsRev, Out, [], Outs
446line_outs[], _, Outs, Outs
447line_outsline_, _, _, udU, D|Ls, Out, Acc, Outs ←
In is U \/ Out xor Out /\ D
line_outsLs, In, Out|Acc, Outs

Uses and definitions of home halves by one line. A write covering a whole half defines it; a partial write reads it too.

453line_use_defLookup, Line, Effect, udUse, Def ←
Effect = effect_, _, _, Ms, _
foldlmemory_use_defLookup, Ms, 0−0, Use0−Def0
extra_use_defLine, Effect, EU, ED
Use is Use0 \/ EU
Def1 is Def0 \/ ED
Def is Def1 xor Def1 /\ Use
scratch_family?Family, ?Bitnondet

The scratch families whose liveness the analysis tracks, by bit; the flags are bit 7.

scratch_family
463rax0
463rcx1
463rdx2
464rsi3
464rdi4
464r85
465r96
extra_use_def+Line, +Effect, -Use, -Defdet

Use and Def are the scratch families and flags Line uses and defines, as 8-bit sets. A read at any width uses a family, a 32- or 64-bit write defines it, a narrower one uses and defines it. A flags read uses bit 7 and a flags write defines it. A call, the stack-overflow jump and an asm_fixed line use every scratch family (their arity is not known here); ret uses rax, rcx and rdx, the return registers.

475extra_use_defLine, effectRs, Ws, Fl, _, _, Use, Def ←
foldlread_bit, Rs, 0, U0
foldlwrite_bits, Ws, U0−0, U1−D0
A shift's flags output (read_write: a zero count keeps them) is
shared only if its inputs are (flags_after/8), so it need not keep
them live: unequal inputs give unequal outputs, which a later read
rejects.
Fl ≡ read
→
U2 is U1 \/ 128
else
U2 = U1
memberchkFl, write, read_write
→
Def is D0 \/ 128
else
Def = D0
Line = inscall, _, _
or
Line = insjae, none, pltfilc_stack_overflow_failure
or
Line = asm_fixed_
→
Use is U2 \/ 127
Line = insret, none, []
→
Use is U2 \/ 7
else
Use = U2
492read_bitF, U0, U ←
→
U is U0 \/ 1 << B
else
U = U0
493write_bitswriteF, W, U0−D0, U−D ←
→
D is D0 \/ 1 << B
W < 32
→
U is U0 \/ 1 << B
else
U = U0
else
U = U0
D = D0
home_lookup+Pairs, -Lookup

the homes' offset-to-number pairs bucketed by 8-byte word, so halves/5 looks up the two or three words an access can overlap rather than each of its byte offsets.

502home_lookupPairs, wordsWords ←
findallW−D−K,
memberD−K, Pairs
W is D >> 3
,
WPs0
keysortWPs0, WPs
group_pairs_by_keyWPs, Grouped
list_to_assocGrouped, Words
507memory_use_defLookup, readmemD, rbp, N, U0−D0, U−D0 ←
!
halvesLookup, D, N, _, Touched
U is U0 \/ Touched
509memory_use_defLookup, writememD, rbp, N, U0−D0, U−Df ←
!
halvesLookup, D, N, Covered, Touched
U is U0 \/ Touched xor Touched /\ Covered
Df is D0 \/ Covered
512memory_use_def_, _, UD, UD
halves+Lookup, +D, +N, -Covered, -Touched

bitsets of the home halves that bytes [D, D+N) cover entirely and touch at all.

516halveswordsWords, D, N, Covered, Touched ←
E is D + N
W0 is D−7 >> 3
W1 is E−1 >> 3
halves_wordsW0, W1, Words, D, E, 0, Touched, 0, Covered
519halves_wordsW, W1, Words, D, E, T0, T, C0, C ←
W > W1
→
T = T0
C = C0
else
get_assocW, Words, Hs
→
foldlhome_halvesD, E, Hs, T0−C0, T1−C1
else
T1 = T0
C1 = C0
W2 is W + 1
halves_wordsW2, W1, Words, D, E, T1, T, C1, C

A home at H overlaps [D, E) when H is within D-7..E-1.

531home_halvesD, E, H−K, T0−C0, T−C ←
H ≥ D−7
H < E
→
half_bits0, H, K, D, E, T0, T1, C0, C1
half_bits1, H, K, D, E, T1, T, C1, C
else
T = T0
C = C0
538half_bitsHalf, H, K, D, E, T0, T, C0, C ←
S is H + 4 × Half
S < E
D < S + 4
→
B is 1 << S0 + Half
T is T0 \/ B
D ≤ S
S + 4 ≤ E
→
C is C0 \/ B
else
C = C0
else
T = T0
C = C0

The obligations

548obligationsBody, Mapped, analysis_, _, _, Index, LiveOut, LiveIn0, _ ←
assoc_to_listMapped, Pairs
forallmemberH−_, Pairs, get_assocH, Index, _
findallB,
memberH−_, Pairs
get_assocH, Index, K
B is 3 << S
,
Bs
foldlor_bits, Bs, 0, All
LiveIn0 /\ All =:= 0
register_masksPairs, Index, Masks
scratch_maskPairs, Index, Scratch
maplistline_obligationMapped, Index, Masks, Scratch, All, Body, LiveOut

Each register's mask: both halves of every home mapped to it.

559register_masksPairs, Index, Masks ←
findallR−M,
findallB,
memberH−R, Pairs
get_assocH, Index, K
B is 3 << S
,
Bs
foldlor_bits, Bs, 0, M
,
Masks
564scratch_maskPairs, Index, M ←
findallB,
memberH−R, Pairs
memberchkR, r10, r11
get_assocH, Index, K
B is 3 << S
,
Bs
foldlor_bits, Bs, 0, M
568or_bitsB, A0, A ←
A is A0 \/ B
570line_obligationMapped, Index, Masks, Scratch, All, Line, Out ←
substituteLine, Mapped, _
Line = ins_, S, _, memD, rbp
→
get_assocD, Mapped, R
get_assocD, Index, K
memberchkR−M, Masks
Own is 3 << Sh
Out /\ M xor M /\ Own =:= 0
S ≡ l
→
Out /\ 2 << Sh =:= 0
else
true
Line = inscall, _, _
→
→
true
else
Out /\ Scratch =:= 0
Line = insleave, none, []
→
Out /\ All =:= 0
Line = asm_fixed_
→
Hardening (docs/asm-fixed.md): its audited effect writes no
allocatable register, so no home can be clobbered.
line_effectLine, effect_, Ws, _, _, _
¬
memberwriteF, _, Ws
else
true

The operand checker (docs/perf.md, step 3b)

operand_lives+Analysis, +Saves, +A, -Livesdet

Lives lists live(In, Out, Effect) for each line of A: the scratch and flags bits before and after it, and its audited effects (line_effect/2, shared with the analysis when the line is the reference's). A is the 3a stream of the reference body the shared Analysis is of. A is that body with mapped moves substituted, which changes no scratch or flags use, and with the save and restore moves of Saves inserted (check_regs/5 proved it is exactly that stream); an inserted line touches neither, so it takes the set live before the next reference line.

601operand_livesAnalysis, Saves, A, Lives ←
findallR, home_register_bitR, _, All
operand_livesAnalysis, allAll, Saves, A, Lives
operand_lives+Analysis, +Map, +Saves, +A, -Livesdet

As operand_lives/4, with the home registers' liveness in bits 8-14 of each set (home_register_bit/2) from the map 3a's checker validated (docs/perf.md item 19). A home register is live where a half of a home mapped to it is live in the reference. A callee-saved register is also live wherever it holds the caller's value: always when Saves does not save it, and, when it does, from the body's start through the saves and at every leave and ret (the restores write it just before). Map is Offset-Register pairs, or all(Registers) to keep those registers live everywhere (operand_lives/4).

616operand_livesanalysisBody, Effects, _, Index, LiveOut, _, UDs, Map, Saves, A, Lives ←
findallR, membersaveR, _, Saves, Used
findallB,
B is 1 << Bit
,
CBs
foldlor_bits, CBs, 0, Callee
Map = allRs
→
Masks = []
findallB,
memberR, Rs
B is 1 << Bit
,
Bs
foldlor_bits, Bs, 0, Always
else
findallB−M,
B is 1 << Bit
findallX,
memberH−R, Map
get_assocH, Index, K
X is 3 << S
,
Xs
foldlor_bits, Xs, 0, M
M ≠ 0
,
Masks
findallB,
¬memberchkR, Used
B is 1 << Bit
,
Bs
foldlor_bits, Bs, 0, Always
ref_livesBody, Effects, LiveOut, UDs, Masks, Always, RLives
a_livesA, RLives, Saves, Callee, pre, Lives
home_register_bit?R, ?Bitnondet

Bit of the operand checker's live sets that stands for home register R.

home_register_bit
636rbx8
636r109
636r1110
637r1211
637r1312
637r1413
638r1514
640ref_lives[], [], [], [], _, _, []
641ref_livesR|Rs, E|Es, O|Os, UD|UDs, Masks, Always, Lv|Lvs ←
line_livesR, E, O, UD, Masks, Always, Lv
ref_livesRs, Es, Os, UDs, Masks, Always, Lvs

live(In, Out, R, E): R is the reference line, E its effects.

646line_livesR, E, Out, udU, D, Masks, Always, liveI8, O8, R, E ←
In is U \/ Out xor Out /\ D
home_bitsMasks, In, Always, HI
home_bitsMasks, Out, Always, HO
I8 is In /\ 255 \/ HI
O8 is Out /\ 255 \/ HO
651home_bits[], _, H, H
652home_bitsB−M|Ms, Set, H0, H ←
Set /\ M ≠ 0
→
H1 is H0 \/ B
else
H1 = H0
home_bitsMs, Set, H1, H

Each line of A gets live(In, Out, Effect): a line equal to its reference line shares its effects; a substituted or inserted one gets its own. Phase is pre until the saves are done (the callee-saved registers hold the caller's values and are live), then post.

660a_lives[], [], _, _, _, []
661a_livesL|As, RLs, Saves, C, _, liveI1, I1, E|Ls ←
save_moveL, Saves
!
RLs = liveI, _, _, _|_
L = ins_, _, reg_, _
→
I1 is I \/ C
Phase = saves
else
I1 = I
Phase = post
a_livesAs, RLs, Saves, C, Phase, Ls
666a_livesL|As, liveI, O, R, E0|RLs, Saves, C, Phase0, liveI1, O1, E|Ls ←
L ≡ R
→
E = E0
else
Phase0 ≡ pre
→
Phase = pre
else
Phase = post
Phase ≡ pre
or
memberchkL, insleave, none, [], insret, none, []
→
I1 is I \/ C
O1 is O \/ C
else
I1 = I
O1 = O
a_livesAs, RLs, Saves, C, Phase, Ls
677save_moveinsmov, q, regR, memS, rbp, Saves ←
memberchksaveR, S, Saves
678save_moveinsmov, q, memS, rbp, regR, Saves ←
memberchksaveR, S, Saves
check_operands+A, +B, +Livessemidet

B, the operand rewrite of the implementation body A, makes the same paired lines in the same order on equal values (docs/perf.md, step 3b's contract). Lives lists live(In, Out, Effect) for each line of A: the scratch families and flags (8 bits, scratch_family/2) live before and after it, from the shared analysis of the reference (operand_lives/4), and its audited effects. The walk takes A's boundaries from those effects, and B's from its own unless the line is identical to A's. Moves (operand_move/2) execute in their own stream only; boundary lines must be identical, and at each boundary A and B agree on every register compared there.

692check_operandsA, B, Lives ←
check_operandsA, B, Lives, cells[], []
check_operands+A, +B, +Lives, +Cellssemidet

As check_operands/3, with frame cells (docs/perf.md, 5a-loads). Cells is cells(Ds, Others): the rbp displacements of the 8-byte frame words the emitter lists as cells, and the Disp-Bytes ranges of the other frame storage (locals, wide and save slots) they must stay apart from. Stores to cells stay paired lines, so frame memory is the same in A and B at every instruction; a whole 32- or 64-bit load of a cell into a general register is a move in its own stream, reading the cell's symbolic value. The value is shared by A and B, set by paired stores and reset at every region start. A word whose address a lea computes is not a cell.

707check_operandsA, B, Lives, Cells0 ←
valid_cellsCells0, A, Cells
empty_assocNone
operands_walkA, B, Lives, 0, S0, S0, None, envS0, Cells, None
valid_cells+Cells0, +A, -Cellssemidet

The cells of Cells0, cells(Ds, Others), are distinct, 8-aligned frame words outside every other range in Others; Cells maps those no lea in the stream A addresses, which the operand rewrite may then use.

718valid_cellscellsDs, Others, A, Cells ←
msortDs, Ds
sortDs, Ds
Distinct multiples of 8 are 8 apart.
forallmemberD, Ds,
integerD
D < 0
D mod 8 =:= 0
forall
memberD, Ds
memberO−N, Others
,
D + 8 ≤ O
or
O + N ≤ D
findallE,
memberinslea, _, M|_, A
,
Es0
sortEs0, Es
findallD−cell,
memberD, Ds
¬
memberE, Es
E < D + 8
D < E + 8
,
Ps
list_to_assocPs, Cells
727lea_basememE, rbp, E
728lea_basememE, rbp, _, E

Lines after a ret, ud2 or jmp and before the next boundary are unreachable (state none): they must pair, but nothing is compared. CS is the cells' values (cell_value/3), shared by A and B.

733operands_walk[], [], [], _, _, _, _, _ ←
!
734operands_walkLA|As, Bs, _|Ls, J, SA, SB, CS, Env ←
stream_moveLA, Env, CS, M
!
move_stateM, SA, SA1
operands_walkAs, Bs, Ls, J, SA1, SB, CS, Env
738operands_walkAs, LB|Bs, Ls, J, SA, SB, CS, Env ←
stream_moveLB, Env, CS, M
!
move_stateM, SB, SB1
operands_walkAs, Bs, Ls, J, SA, SB1, CS, Env
742operands_walkLA|As, LB|Bs, liveIn, Out, EA|Ls, J, SA, SB, CS, Env ←
Env = envS0, Cells, None
An identical line has A's effect; line_effect/2 is a function.
LA ≡ LB
→
EB = EA
else
or
LA ≢ LB
→
LA ≡ LB
CS1 = None
SA ≡ none
→
true
else
region_endIn, SA, SB
→
SA1 = none
SB1 = none
else
region_startOut, S0, SA1, SB1
SA ≡ none
→
LA = insOp, S, _
LB = insOp, S, _
SA1 = none
SB1 = none
CS1 = CS
else
pairedLA, LB, J, SA, SB, SA1, SB1
cell_writesEA, LA, J, SA, Cells, CS, CS1
J1 is J + 1
operands_walkAs, Bs, Ls, J1, SA1, SB1, CS1, Env
stream_move+Line, +Env, +CS, -Movesemidet
Line is a move (operand_move/2)

or a whole load of a cell into a general register other than rsp or rbp, as a move of the cell's value.

765stream_moveLine, _, _, M ←
!
766stream_moveinsmov, S, memD, rbp, regY, env_, Cells, _, CS, moveF, valueV, W ←
get_assocD, Cells, _
memberchkS−W, l−32, q−64
registerY, F, W
¬memberchkF, rsp, rbp
cell_valueD, CS, V
cell_value+D, +CS, -V

the cell's halves, v(Lo, Hi); a cell not written in this region holds its shared start value.

774cell_valueD, CS, V ←
get_assocD, CS, V0
→
V = V0
else
V = vincellD, lo, shared, incellD, hi, shared
cell_writes+Effect, +Line, +J, +SA0, +Cells, +CS0, -CSdet
The paired

line J writes the same memory in A and B. A whole store of a register or immediate sets the cell from A's source (equal to B's): 64 bits both halves, 32 bits the low half, keeping the high half. Any other write overlapping a cell gives it a fresh shared value.

785cell_writeseffect_, _, _, Ms, _, Line, J, SA0, Cells, CS0, CS ←
foldlcell_writeLine, J, SA0, Cells, Ms, CS0, CS
788cell_writeLine, J, SA0, Cells, writememD0, rbp, N, CS0, CS ←
!
findallC,
Lo is D0−7
Hi is D0 + N−1
betweenLo, Hi, C
get_assocC, Cells, _
,
Cs
foldlcell_storeLine, J, SA0, D0, N, Cs, CS0, CS
791cell_write_, _, _, _, _, CS, CS
793cell_storeinsmov, q, Src, memD, rbp, _, SA0, D, 8, D, CS0, CS ←
store_sourceSrc, 64, SA0, Lo, Hi
!
put_assocD, CS0, vLo, Hi, CS
796cell_storeinsmov, l, Src, memD, rbp, _, SA0, D, 4, D, CS0, CS ←
store_sourceSrc, 32, SA0, Lo
!
cell_valueD, CS0, v_, Hi
put_assocD, CS0, vLo, Hi, CS
800cell_store_, J, _, _, _, C, CS0, CS ←
put_assocC, CS0, vresJ, cellC, lo, resJ, cellC, hi, CS
803store_sourceregN, W, SA0, V ←
registerN, F, W
read_valueF, W, SA0, V
804store_sourceimmI, W, _, V ←
integerI
imm_valueI, W, V
no_fallthrough+Line, +Effectsemidet

Line (a boundary) does not fall through: ret, ud2 or jmp.

808no_fallthrough_, effect_, _, _, _, terminal
809no_fallthroughinsjmp, _, _, _
operand_move+Line, -Movesemidet

Line is a move: a 32- or 64-bit mov of a general register or an integer immediate into a general register other than rsp or rbp, or a movabs, with its immediate in range. Move is move(Dest, Src, Bits).

815operand_moveinsOp, S, Os, Move ←
move_formOp, S, Os, Move
817move_formmov, S, Src, regY, moveF, Src1, W ←
memberchkS−W, l−32, q−64
registerY, F, W
¬memberchkF, rsp, rbp
Src = regX
→
registerX, G, W
Src1 = regG
else
Src = immI
integerI
Src1 = immI
823move_formmovabs, q, immI, regY, moveF, immI, 64 ←
integerI
I ≥ −1 << 63
I < 1 << 64
registerY, F, 64
¬memberchkF, rsp, rbp

Facts, so a bound query is deterministic and an unbound one enumerates.

gprraxrcxrdxrbxrsprbprsirdir8r9r10r11r12r13r14r15
xmmxmm0xmm1xmm2xmm3xmm4xmm5xmm6xmm7

Immediate ranges by operation width; q is a sign-extended imm32.

833imm_range8, I ←
I ≥ -128
I < 256
834imm_range16, I ←
I ≥ -32768
I < 65536
835imm_range32, I ←
I ≥ −1 << 31
I < 1 << 32
836imm_range64, I ←
I ≥ −1 << 31
I < 1 << 31
move_state+Move, +S0, -Sdet

S is the symbolic state S0 after Move (operand_move/2, stream_move/4). The state is st(GPRs, XMMs, Flags); a GPR is v(Low, High).

841move_state_, none, none ←
!
842move_statemoveF, regG, 64, stR0, X, Fl, stR, X, Fl ←
!
reg_getG, R0, V
reg_putF, R0, V, R
844move_statemoveF, regG, 32, stR0, X, Fl, stR, X, Fl ←
!
reg_getG, R0, vLo, _
reg_putF, R0, vLo, const0, R
846move_statemoveF, valuevLo, Hi, 64, stR0, X, Fl, stR, X, Fl ←
!
reg_putF, R0, vLo, Hi, R
848move_statemoveF, valuevLo, _, 32, stR0, X, Fl, stR, X, Fl ←
!
reg_putF, R0, vLo, const0, R
850move_statemoveF, immI, W, stR0, X, Fl, stR, X, Fl ←
imm_halvesW, I, Lo, Hi
reg_putF, R0, vLo, Hi, R

A 32-bit immediate write zero-extends; a 64-bit one is I mod 2^64.

854imm_halves32, I, constLo, const0 ←
Lo is I mod 1 << 32
855imm_halves64, I, constLo, constHi ←
U is I mod 1 << 64
Lo is U /\ 0xffffffff
Hi is U >> 32
boundary_line+Line, +Effectsemidet

Line ends a straight-line run: it transfers control, writes rsp or rbp, or is not an instruction (a label or directive).

862boundary_lineLine, effect_, Ws, _, _, Flow ←
Flow ≢ next
→
true
memberwriteF, _, Ws
memberchkF, rsp, rbp
→
true
else
Line ≠ ins_, _, _
shared_state-Sdet

S is the state in which every register and the flags hold shared values.

870shared_statestR, X, flags in shared ←
findallvinF, lo, shared, inF, hi, shared, gprF, Rs
R =.. r|Rs
findallF in shared, xmmF, Xs
X =.. x|Xs

The registers of a state are the arguments of r/16 and x/8, in gpr/1's and xmm/1's order. put_slot/4 (r/16) and put_xslot/4 (x/8) are separate so that a bound index leaves no choicepoint.

reg_get+F, +Registers, -Vdet

V is register F's value in a state's r/16 (v(Lo,Hi)) or x/8 term.

879reg_getF, T, V ←
argI, T, V
880reg_putF, T0, V, T ←
put_slotI, T0, V, T
881put_slot1, r_, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, V, rV, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16
882put_slot2, rA1, _, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, V, rA1, V, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16
883put_slot3, rA1, A2, _, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, V, rA1, A2, V, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16
884put_slot4, rA1, A2, A3, _, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, V, rA1, A2, A3, V, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16
885put_slot5, rA1, A2, A3, A4, _, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, V, rA1, A2, A3, A4, V, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16
886put_slot6, rA1, A2, A3, A4, A5, _, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, V, rA1, A2, A3, A4, A5, V, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16
887put_slot7, rA1, A2, A3, A4, A5, A6, _, A8, A9, A10, A11, A12, A13, A14, A15, A16, V, rA1, A2, A3, A4, A5, A6, V, A8, A9, A10, A11, A12, A13, A14, A15, A16
888put_slot8, rA1, A2, A3, A4, A5, A6, A7, _, A9, A10, A11, A12, A13, A14, A15, A16, V, rA1, A2, A3, A4, A5, A6, A7, V, A9, A10, A11, A12, A13, A14, A15, A16
889put_slot9, rA1, A2, A3, A4, A5, A6, A7, A8, _, A10, A11, A12, A13, A14, A15, A16, V, rA1, A2, A3, A4, A5, A6, A7, A8, V, A10, A11, A12, A13, A14, A15, A16
890put_slot10, rA1, A2, A3, A4, A5, A6, A7, A8, A9, _, A11, A12, A13, A14, A15, A16, V, rA1, A2, A3, A4, A5, A6, A7, A8, A9, V, A11, A12, A13, A14, A15, A16
891put_slot11, rA1, A2, A3, A4, A5, A6, A7, A8, A9, A10, _, A12, A13, A14, A15, A16, V, rA1, A2, A3, A4, A5, A6, A7, A8, A9, A10, V, A12, A13, A14, A15, A16
892put_slot12, rA1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, _, A13, A14, A15, A16, V, rA1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, V, A13, A14, A15, A16
893put_slot13, rA1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, _, A14, A15, A16, V, rA1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, V, A14, A15, A16
894put_slot14, rA1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, _, A15, A16, V, rA1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, V, A15, A16
895put_slot15, rA1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, _, A16, V, rA1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, V, A16
896put_slot16, rA1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, _, V, rA1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, V
put_xslot
8971x_, A2, A3, A4, A5, A6, A7, A8VxV, A2, A3, A4, A5, A6, A7, A8
8982xA1, _, A3, A4, A5, A6, A7, A8VxA1, V, A3, A4, A5, A6, A7, A8
8993xA1, A2, _, A4, A5, A6, A7, A8VxA1, A2, V, A4, A5, A6, A7, A8
9004xA1, A2, A3, _, A5, A6, A7, A8VxA1, A2, A3, V, A5, A6, A7, A8
9015xA1, A2, A3, A4, _, A6, A7, A8VxA1, A2, A3, A4, V, A6, A7, A8
9026xA1, A2, A3, A4, A5, _, A7, A8VxA1, A2, A3, A4, A5, V, A7, A8
9037xA1, A2, A3, A4, A5, A6, _, A8VxA1, A2, A3, A4, A5, A6, V, A8
9048xA1, A2, A3, A4, A5, A6, A7, _VxA1, A2, A3, A4, A5, A6, A7, V
reg_index
906rax1
906rcx2
906rdx3
906rbx4
907rsp5
907rbp6
907rsi7
907rdi8
908r89
908r910
908r1011
908r1112
909r1213
909r1314
909r1415
909r1516
910xmm01
910xmm12
910xmm23
910xmm34
911xmm45
911xmm56
911xmm67
911xmm78
region_start+Out, +Shared, -SA, -SBdet
After a boundary, live scratch

families, live home registers (bits 8-14, home_register_bit/2) and live flags are shared, dead ones distinct per stream; the rest is shared.

917region_startOut, stR0, X, _, stRA, X, FA, stRB, X, FB ←
foldldead_scratchOut, rax, rcx, rdx, rsi, rdi, r8, r9, rbx, r10, r11, r12, r13, r14, r15, R0−R0, RA−RB
Out /\ 128 ≠ 0
→
FA = flags in shared
FB = FA
else
FA = flags in a
FB = flags in b
924dead_scratchOut, F, RA0−RB0, RA−RB ←
live_bitF, Bit
Out /\ 1 << Bit ≠ 0
→
RA = RA0
RB = RB0
else
reg_putF, RA0, vinF, lo, a, inF, hi, a, RA
reg_putF, RB0, vIn, Hi, RB
In = inF, lo, b
Hi = inF, hi, b

Facts, so a bound query is deterministic and an unbound one enumerates.

live_bit
933rax0
933rcx1
933rdx2
933rsi3
934rdi4
934r85
934r96
935rbx8
935r109
935r1110
935r1211
936r1312
936r1413
936r1514
region_end+In, +SA, +SB

at a boundary, A and B agree on both halves of rsp and rbp, every XMM register, each live scratch family and home register, and the flags if live.

941region_endIn, stRA, XA, FA, stRB, XB, FB ←
RA ≡ RB
XA ≡ XB
!
In /\ 128 ≠ 0
→
FA ≡ FB
else
true
944region_endIn, stRA, XA, FA, stRB, XB, FB ←
XA ≡ XB
rsp and rbp (arguments 5 and 6) always.
RA = r_, _, _, _, X5, X6, _, _, _, _, _, _, _, _, _, _
RB = r_, _, _, _, Y5, Y6, _, _, _, _, _, _, _, _, _, _
fX5, X6 ≡ fY5, Y6
forall
live_bitF, Bit
In /\ 1 << Bit ≠ 0
,
reg_getF, RA, V
reg_getF, RB, W
V ≡ W
In /\ 128 ≠ 0
→
FA ≡ FB
else
true
operand_roles+Op, +Size, -Rolessemidet
The role of each explicit operand of an audited non-boundary form:
src(W), dst(W), srcdst(W) (a two-address destination) or addr (a
memory operand not accessed). W is the operand's width in bits, or
unbound when its register gives it (SSE forms).

Facts, indexed on the mnemonic.

operand_roles
960movbsrc8, dst8
961movwsrc16, dst16
962movlsrc32, dst32
963movqsrc64, dst64
964movabsqsrc64, dst64
965movzbbsrc8, dst8
966movzbwsrc8, dst16
967movzblsrc8, dst32
968movzbqsrc8, dst64
969movsbbsrc8, dst8
970movsbwsrc8, dst16
971movsblsrc8, dst32
972movsbqsrc8, dst64
973movzwbsrc16, dst8
974movzwwsrc16, dst16
975movzwlsrc16, dst32
976movzwqsrc16, dst64
977movswbsrc16, dst8
978movswwsrc16, dst16
979movswlsrc16, dst32
980movswqsrc16, dst64
981movslbsrc32, dst8
982movslwsrc32, dst16
983movsllsrc32, dst32
984movslqsrc32, dst64
985leaqaddr, dst64
986lealaddr, dst32
987addbsrc8, srcdst8
988addwsrc16, srcdst16
989addlsrc32, srcdst32
990addqsrc64, srcdst64
991subbsrc8, srcdst8
992subwsrc16, srcdst16
993sublsrc32, srcdst32
994subqsrc64, srcdst64
995andbsrc8, srcdst8
996andwsrc16, srcdst16
997andlsrc32, srcdst32
998andqsrc64, srcdst64
999orbsrc8, srcdst8
1000orwsrc16, srcdst16
1001orlsrc32, srcdst32
1002orqsrc64, srcdst64
1003xorbsrc8, srcdst8
1004xorwsrc16, srcdst16
1005xorlsrc32, srcdst32
1006xorqsrc64, srcdst64
1007imulbsrc8, srcdst8
1008imulwsrc16, srcdst16
1009imullsrc32, srcdst32
1010imulqsrc64, srcdst64
1011btcbsrc8, srcdst8
1012btcwsrc16, srcdst16
1013btclsrc32, srcdst32
1014btcqsrc64, srcdst64
1015cmpbsrc8, src8
1016cmpwsrc16, src16
1017cmplsrc32, src32
1018cmpqsrc64, src64
1019testbsrc8, src8
1020testwsrc16, src16
1021testlsrc32, src32
1022testqsrc64, src64
1023negbsrcdst8
1024negwsrcdst16
1025neglsrcdst32
1026negqsrcdst64
1027shlbsrc8, srcdst8
1028shlwsrc8, srcdst16
1029shllsrc8, srcdst32
1030shlqsrc8, srcdst64
1031shrbsrc8, srcdst8
1032shrwsrc8, srcdst16
1033shrlsrc8, srcdst32
1034shrqsrc8, srcdst64
1035sarbsrc8, srcdst8
1036sarwsrc8, srcdst16
1037sarlsrc8, srcdst32
1038sarqsrc8, srcdst64
1039setenonedst8
1040setnenonedst8
1041setznonedst8
1042setnznonedst8
1043setbnonedst8
1044cmovbqsrc64, srcdst64
1045setbenonedst8
1046setanonedst8
1047setaenonedst8
1048setlnonedst8
1049setlenonedst8
1050setgnonedst8
1051setgenonedst8
1052setsnonedst8
1053setnsnonedst8
1054setpnonedst8
1055setnpnonedst8
1056cltdnone[]
1057cqtonone[]

A fence pairs with itself, in its place among the memory accesses.

1059mfencenone[]
1060divbsrc8
1061divwsrc16
1062divlsrc32
1063divqsrc64
1064idivbsrc8
1065idivwsrc16
1066idivlsrc32
1067idivqsrc64

lock cmpxchg: New, then the memory it compares and writes.

1069'lock cmpxchg'bsrc8, srcdst8
1070'lock cmpxchg'wsrc16, srcdst16
1071'lock cmpxchg'lsrc32, srcdst32
1072'lock cmpxchg'qsrc64, srcdst64
1073movdnonesrc_, dst_
1074movqnonesrc_, dst_
1075addsssrc128, srcdst128
1076addsdsrc128, srcdst128
1077subsssrc128, srcdst128
1078subsdsrc128, srcdst128
1079mulsssrc128, srcdst128
1080mulsdsrc128, srcdst128
1081divsssrc128, srcdst128
1082divsdsrc128, srcdst128
1083ucomissnonesrc128, src128
1084ucomisdnonesrc128, src128
1085cvtss2sdnonesrc128, srcdst128
1086cvtsd2ssnonesrc128, srcdst128
1087sqrtssnonesrc128, srcdst128
1088sqrtsdnonesrc128, srcdst128
1089cvttss2sibsrc128, dst8
1090cvttss2siwsrc128, dst16
1091cvttss2silsrc128, dst32
1092cvttss2siqsrc128, dst64
1093cvttsd2sibsrc128, dst8
1094cvttsd2siwsrc128, dst16
1095cvttsd2silsrc128, dst32
1096cvttsd2siqsrc128, dst64
1097cvtsi2ssbsrc8, srcdst128
1098cvtsi2sswsrc16, srcdst128
1099cvtsi2sslsrc32, srcdst128
1100cvtsi2ssqsrc64, srcdst128
1101cvtsi2sdbsrc8, srcdst128
1102cvtsi2sdwsrc16, srcdst128
1103cvtsi2sdlsrc32, srcdst128
1104cvtsi2sdqsrc64, srcdst128

Implicit operands, read and written at a width, by mnemonic.

implicit
1107cltd_rax−32rdx−32
1108cqto_rax−64rdx−64
1109divbrax−8, rdx−8rax−8, rdx−8
1110divwrax−16, rdx−16rax−16, rdx−16
1111divlrax−32, rdx−32rax−32, rdx−32
1112divqrax−64, rdx−64rax−64, rdx−64
1113idivbrax−8, rdx−8rax−8, rdx−8
1114idivwrax−16, rdx−16rax−16, rdx−16
1115idivlrax−32, rdx−32rax−32, rdx−32
1116idivqrax−64, rdx−64rax−64, rdx−64

lock cmpxchg reads %rax at its width and writes it: wholly at 32 and 64 bits (amd64_contract.pl), the width's part at 8 and 16.

1119'lock cmpxchg'brax−8rax−8
1120'lock cmpxchg'wrax−16rax−16
1121'lock cmpxchg'lrax−32rax−64
1122'lock cmpxchg'qrax−64rax−64

The flags a form defines from its inputs (res), or leaves partly undefined or preserved (distinct per stream). The SSE arithmetic forms (add, sub, mul, div with ss/sd) preserve them. Facts, indexed on the mnemonic.

flags_out
1128addbdefined
1129addwdefined
1130addldefined
1131addqdefined
1132subbdefined
1133subwdefined
1134subldefined
1135subqdefined
1136andbdefined
1137andwdefined
1138andldefined
1139andqdefined
1140orbdefined
1141orwdefined
1142orldefined
1143orqdefined
1144xorbdefined
1145xorwdefined
1146xorldefined
1147xorqdefined
1148cmpbdefined
1149cmpwdefined
1150cmpldefined
1151cmpqdefined
1152testbdefined
1153testwdefined
1154testldefined
1155testqdefined
1156negbdefined
1157negwdefined
1158negldefined
1159negqdefined
1160ucomissnonedefined
1161ucomisdnonedefined
1162shlbshift
1163shlwshift
1164shllshift
1165shlqshift
1166shrbshift
1167shrwshift
1168shrlshift
1169shrqshift
1170sarbshift
1171sarwshift
1172sarlshift
1173sarqshift
1174imulbundefined
1175imulwundefined
1176imullundefined
1177imulqundefined
1178btcbundefined
1179btcwundefined
1180btclundefined
1181btcqundefined
1182divbundefined
1183divwundefined
1184divlundefined
1185divqundefined
1186idivbundefined
1187idivwundefined
1188idivlundefined
1189idivqundefined
1190'lock cmpxchg'bdefined
1191'lock cmpxchg'wdefined
1192'lock cmpxchg'ldefined
1193'lock cmpxchg'qdefined
paired+LA, +LB, +J, +SA0, +SB0, -SA, -SBsemidet

A's line LA and B's line LB, the J-th paired line, compute the same outputs from the pre-states SA0 and SB0; SA and SB are the post-states. Besides the same form on equal operand values (docs/perf.md, step 3b), three pairings are accepted (item 19):

  • a zero idiom, xor or sub of a register with itself at 32 or 64 bits, in any register: both write 0 and the same flags, whatever the register held;
  • add, and, or, xor or imul of two registers with B's operands swapped: the result and the flags are symmetric;
  • add of a register or an immediate, or sub of an immediate, as a lea of the same width into any register (a register addend that holds a constant may be the displacement): the same result, but B's flags keep their old value, so A's differ from them after it.
1211pairedLA, LB, J, SA0, SB0, SA, SB ←
zero_idiomLA, Op, S, FA
zero_idiomLB, Op, S, FB
→
zero_writeFA, J, SA0, SA
zero_writeFB, J, SB0, SB
LA = insOp, S, _
LB = inslea, S, _
memberchkOp, add, sub
→
lea_pairLA, LB, J, SA0, SB0, SA, SB
else
paired_sameLA, LB, J, SA0, SB0, SA, SB
1219zero_idiominsOp, S, regX, regX, Op, S, F ←
memberchkOp, xor, sub
memberchkS−W, l−32, q−64
registerX, F, W
¬memberchkF, rsp, rbp
1223zero_writeF, J, stR0, X, _, stR, X, resJ, flags ←
reg_putF, R0, vconst0, const0, R

lea_pair: A's add or sub writes res(J,2) to its destination and defines the flags; B's lea writes res(J,2) to its own and keeps them.

1228lea_pairinsOp, S, Src, regY, inslea, S, M, regT, J, SA0, SB0, SA, SB ←
memberchkS−W, l−32, q−64
registerY, FY, W
registerT, FT, W
¬memberchkFY, rsp, rbp
¬memberchkFT, rsp, rbp
read_valueFY, W, SA0, VY
lea_inputsOp, Src, M, W, SA0, SB0, VY
write_valueFY, W, resJ, 2, SA0, stRA, XA, _
SA = stRA, XA, resJ, flags
write_valueFT, W, resJ, 2, SB0, SB

lea's base and index registers, read at the operation's width, are A's two addends in either order; a displacement is A's immediate (negated for sub) within disp32.

1241lea_inputsadd, regX, mem0, B, I, W, SA0, SB0, VY ←
registerX, FX, W
read_valueFX, W, SA0, VX
lea_registerB, W, SB0, V1
lea_registerI, W, SB0, V2
V1 ≡ VX
V2 ≡ VY
→
true
else
V1 ≡ VY
V2 ≡ VX

A register addend holding a constant (in A) may be the displacement.

1246lea_inputsadd, regX, memD, B, W, SA0, SB0, VY ←
integerD
D ≥ −1 << 31
D < 1 << 31
registerX, FX, W
read_valueFX, W, SA0, VX
imm_valueD, W, VD
VX ≡ VD
lea_registerB, W, SB0, V
V ≡ VY
1251lea_inputsadd, immD, memD, B, W, _, SB0, VY ←
integerD
D ≥ −1 << 31
D < 1 << 31
lea_registerB, W, SB0, V
V ≡ VY
1254lea_inputssub, immD, memND, B, W, _, SB0, VY ←
integerD
integerND
ND =:= −D
ND ≥ −1 << 31
ND < 1 << 31
lea_registerB, W, SB0, V
V ≡ VY
1258lea_registerN, W, S, V ←
registerN, F, 64
¬memberchkF, rsp, rbp
read_valueF, W, S, V
commutativeaddandorxorimul
1263paired_sameinsOp, S, OsA, insOp, S, OsB, J, SA0, SB0, SA, SB ←
onceoperand_rolesOp, S, Roles
sourcesRoles, OsA, OsB, Op, SA0, SB0
→
true
else
memberchkS, l, q
OsA = reg_, reg_
OsB = regB1, regB2
sourcesRoles, OsA, regB2, regB1, Op, SA0, SB0
implicitOp, S, Reads, _
→
forallmemberF−W, Reads,
read_valueF, W, SA0, V
read_valueF, W, SB0, V1
V ≡ V1
else
true
→
SA0 = st_, _, FA0
SB0 = st_, _, FB0
FA0 ≡ FB0
else
true
writesRoles, OsA, 1, J, SA0, SA1
writesRoles, OsB, 1, J, SB0, SB1
implicitOp, S, _, Writes
→
foldlimplicit_writeJ, Writes, SA1, SA2
foldlimplicit_writeJ, Writes, SB1, SB2
else
SA2 = SA1
SB2 = SB1
flags_afterOp, S, J, SA0, SB0, SA2, SB2, SA, SB
sources+Roles, +OsA, +OsB, +Op, +SA, +SB

one role per operand of both lines, and each operand pair matches in the pre-states.

1285sources[], [], [], _, _, _
1286sourcesR|Rs, OA|OsA, OB|OsB, Op, SA, SB ←
role_sourcesR, Op, SA, SB, OA, OB
sourcesRs, OsA, OsB, Op, SA, SB
writes+Roles, +Os, +P, +J, +S0, -S

register destinations at positions P.. get res(J,P).

1292writes[], [], _, _, S, S
1293writesR|Rs, O|Os, P, J, S0, S ←
role_writeR, O, P, J, S0, S1
P1 is P + 1
writesRs, Os, P1, J, S1, S
1298role_writedst_, O, P, J, S0, S ←
!
register_writeO, P, J, S0, S
1299role_writesrcdst_, O, P, J, S0, S ←
!
register_writeO, P, J, S0, S
1300role_write_, _, _, _, S, S
1302register_writeregN, P, J, S0, S ←
!
registerN, F, W
write_valueF, W, resJ, P, S0, S
1304register_write_, _, _, S, S

setcc and cmovcc read the flags; a shift's flags output depends on its input (flags_after/9), jcc is a boundary.

1308flags_readOp ←
atom_concatset, _, Op
!
1309flags_readOp ←
atom_concatcmov, _, Op
1311flags_afterOp, S, J, st_, _, FA0, st_, _, FB0, stRA, XA, _, stRB, XB, _, stRA, XA, FA, stRB, XB, FB ←
flags_outOp, S, K
→
true
else
K = preserve
K ≡ defined
→
FA = resJ, flags
FB = FA
K ≡ shift
FA0 ≡ FB0
→
FA = resJ, flags
FB = FA
K ≢ preserve
→
FA = resJ, flags, a
FB = resJ, flags, b
else
FA = FA0
FB = FB0

Sources: equal values at the role's width; memory operands of the same form on equal addressing registers; other operands identical.

1323role_sourcesdstW, _, SA, SB, OA, OB ←
place_matchW, SA, SB, OA, OB
1324role_sourcesaddr, _, SA, SB, OA, OB ←
memory_matchSA, SB, OA, OB
1325role_sourcessrcW, Op, SA, SB, OA, OB ←
source_matchOp, W, SA, SB, OA, OB
1326role_sourcessrcdstW, Op, SA, SB, OA, OB ←
source_matchOp, W, SA, SB, OA, OB
place_matchW, SA, SB, OA, OB

A destination: registers of the same width (any register), or the same memory operand.

1331place_matchW, SA, SB, OA, OB ←
OA = regA
OB = regB
→
registerA, FA, W
registerB, FB, W
¬memberchkFA, rsp, rbp
¬memberchkFB, rsp, rbp
else
memory_matchSA, SB, OA, OB
1338source_matchOp, W, SA, SB, OA, OB ←
memory_matchSA, SB, OA, OB
→
true
else
operand_valueOA, Op, W, SA, VA
operand_valueOB, Op, W, SB, VB
VA ≡ VB
1343operand_valueregN, _, W, S, V ←
registerN, F, W
read_valueF, W, S, V
1344operand_valueimmI, Op, W, _, V ←
integerW
integerI
→
Op ≡ movabs
→
I ≥ −1 << 63
I < 1 << 64
else
imm_valueI, W, V
else
V = symI
1352imm_valueI, 64, constLo, constHi ←
!
U is I mod 1 << 64
Lo is U /\ 0xffffffff
Hi is U >> 32
1354imm_valueI, W, constV ←
V is I mod 1 << W
read_value+F, +W, +S, -Vsemidet

V is register family F read at W bits in state S.

1358read_valueF, W, S, V ←
width_readW, F, S, V
1360width_read128, F, st_, X, _, V ←
reg_getF, X, V
1361width_read64, F, stR, _, _, Lo, Hi ←
reg_getF, R, vLo, Hi
1362width_read32, F, stR, _, _, Lo ←
reg_getF, R, vLo, _
1363width_read16, F, stR, _, _, V ←
reg_getF, R, vLo, _
narrowword, Lo, V
1364width_read8, F, stR, _, _, V ←
reg_getF, R, vLo, _
narrowbyte, Lo, V
1366narrowbyte, constN, constM ←
!
M is N /\ 255
1367narrowword, constN, constM ←
!
M is N /\ 65535
1368narrowbyte, merge8_, N, N ←
!
1369narrowword, merge16_, N, N ←
!
1370narrowbyte, merge16_, N, V ←
!
narrowbyte, N, V
1371narrowK, X, V ←
V =.. K, X
memory_match+SA, +SB, +OA, +OB

the same memory operand on equal addressing registers, or the same non-register, non-immediate operand.

1375memory_matchSA, SB, OA, OB ←
OA = memD, BA
OB = memD, BB
→
same_baseSA, SB, BA, BB
OA = memD, BA, IA
OB = memD, BB, IB
→
same_baseSA, SB, BA, BB
same_baseSA, SB, IA, IB
else
OA ≠ reg_
OA ≠ imm_
OA ≠ mem_, _
OA ≠ mem_, _, _
OA ≡ OB
1382same_baseSA, SB, A, B ←
registerA, FA, 64
registerB, FB, 64
read_valueFA, 64, SA, V
read_valueFB, 64, SB, W
V ≡ W
1386implicit_writeJ, F−W, S0, S ←
write_valueF, W, resJ, F, S0, S
write_value+F, +W, +V, +S0, -S

V written to F at W bits.

1389write_valueF, 128, V, stR, X0, Fl, stR, X, Fl ←
!
put_xslotI, X0, V, X
1390write_valueF, W, V, stR0, X, Fl, stR, X, Fl ←
argI, R0, Old
writtenW, V, Old, New
put_slotI, R0, New, R
written
139564V_vloV, hiV
139632V_vloV, const0
139716VvLo0, Hi0vmerge16Lo0, loV, Hi0
13988VvLo0, Hi0vmerge8Lo0, loV, Hi0