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 (the callee may clobber them)
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
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.

74check_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.

90check_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.

109allocatableR ←
110allocatabler10
111allocatabler11
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.

123valid_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
140apart[]
141apart_
142apartA, B|Os ←
B−A ≥ 8
apartB|Os
144pairs_offsets[], []
145pairs_offsetsD−_|Ps, D|Ds ←
integerD
impl_body+Impl, +Lines, -Pre, -Bodysemidet

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

149impl_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.

158preflight_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.

164preflight_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
170preflight_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.

182named_registerO ←
183named_registerO ←
184named_registermem_, rbp, _
186rbp_registerregR ←
registerR, rbp, _
187rbp_registerindregR ←
registerR, rbp, _
189touchesD, N, Mapped, Saves ←
mapped_overlapD, N, Mapped
→
true
else
memberS, Saves
D < S + 8
S < D + N
192mapped_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).

199rbp_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).

206effect_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.

212substituteinsmov, S, memD, rbp, regX, Mapped, insmov, S, regN, regX ←
get_assocD, Mapped, R
registerN, R, W
214substituteinsmov, 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.

218substituteinsmov, 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
221substituteinsOp, S, memD, rbp, regX, Mapped, insOp, S, regN, regX ←
get_assocD, Mapped, R
registerN, R, W
move_bits
225b8
225w16
225l32
225q64
extension_bits
226movzb8
226movsb8
227movzw16
227movsw16
227movsl32
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.

236allocated_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
246straight_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.

252expected_rest[], _, _, []
253expected_restinsleave, none, [], insret, none, []|Ls, Mapped, Loads, Out ←
!
appendLoads, insleave, none, [], insret, none, []|Out1, Out
expected_restLs, Mapped, Loads, Out1
256expected_restL|Ls, Mapped, Loads, L1|Out ←
L ≠ insleave, _, _
L ≠ insret, _, _
expected_lineMapped, L, L1
expected_restLs, Mapped, Loads, Out
261expected_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.

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

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

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

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

283successorsBody, 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
290numlist_fromI, N, [] ←
I ≥ N
!
291numlist_fromI, N, I|Is ←
J is I + 1
numlist_fromJ, N, Is
293line_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.

302targetLabels−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
313targetLabels−_, Name, T ←
get_assocName, Labels, T
316digitC ←
memberchkC, '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
318flow_successorsnext, _, _, J, J
319flow_successorsframe, _, _, J, J

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

321flow_successorscall, inscall, _, T, Labels−_, J, J ←
¬
T = symL
get_assocL, Labels, _
323flow_successorslabel, _, _, J, J
324flow_successorsdirective, _, _, J, J
325flow_successorsterminal, _, _, _, []
326flow_successorsbranch, insjmp, none, symL, Labels, _, T ←
!
targetLabels, L, T
328flow_successorsbranch, insOp, none, symL, Labels, J, T, J ←
Op ≢ jmp
!
targetLabels, L, T
330flow_successorsbranch, insOp, none, pltS, _, J, J ←
Op ≢ jmp
333noreturnfilc_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.

342home_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
347and_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.

354home_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.

366body_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.

372body_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
390numbered[], _, []
391numberedH|Hs, K, H−K|Ps ←
K1 is K + 1
numberedHs, K1, Ps
393lines[], _, [], [], []
394linesL|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.

399blocks[], []
400blocksL|Ls, blockI, L|More, Succs|Bs ←
L = lineI, _, _, _
block_restL, Ls, More, Succs, Rest
blocksRest, Bs
405block_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.

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

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

424fixpointNB, Sum, In0, In ←
passNB, Sum, In0, In1, false, Changed
Changed ≡ true
→
fixpointNB, Sum, In1, In
else
In = In1
428pass0, _, In, In, C, C ←
!
429passB1, 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
437or_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.

440block_live_outIds, In, block_, Lines, Succs, Outs ←
maplistblock_idIds, Succs, SuccIds
foldlor_liveIn, SuccIds, 0, Out
reverseLines, Rev
line_outsRev, Out, [], Outs
445line_outs[], _, Outs, Outs
446line_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.

452line_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
462rax0
462rcx1
462rdx2
463rsi3
463rdi4
463r85
464r96
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.

474extra_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
491read_bitF, U0, U ←
→
U is U0 \/ 1 << B
else
U = U0
492write_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.

501home_lookupPairs, wordsWords ←
findallW−D−K,
memberD−K, Pairs
W is D >> 3
,
WPs0
keysortWPs0, WPs
group_pairs_by_keyWPs, Grouped
list_to_assocGrouped, Words
506memory_use_defLookup, readmemD, rbp, N, U0−D0, U−D0 ←
!
halvesLookup, D, N, _, Touched
U is U0 \/ Touched
508memory_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
511memory_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.

515halveswordsWords, 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
518halves_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.

530home_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
537half_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

547obligationsBody, 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.

558register_masksPairs, Index, Masks ←
findallR−M,
findallB,
memberH−R, Pairs
get_assocH, Index, K
B is 3 << S
,
Bs
foldlor_bits, Bs, 0, M
,
Masks
563scratch_maskPairs, Index, M ←
findallB,
memberH−R, Pairs
memberchkR, r10, r11
get_assocH, Index, K
B is 3 << S
,
Bs
foldlor_bits, Bs, 0, M
567or_bitsB, A0, A ←
A is A0 \/ B
569line_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, _, _
→
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.

600operand_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).

615operand_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
635rbx8
635r109
635r1110
636r1211
636r1312
636r1413
637r1514
639ref_lives[], [], [], [], _, _, []
640ref_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.

645line_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
650home_bits[], _, H, H
651home_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.

659a_lives[], [], _, _, _, []
660a_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
665a_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
676save_moveinsmov, q, regR, memS, rbp, Saves ←
memberchksaveR, S, Saves
677save_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.

691check_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.

706check_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.

717valid_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
726lea_basememE, rbp, E
727lea_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.

732operands_walk[], [], [], _, _, _, _, _ ←
!
733operands_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
737operands_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
741operands_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.

764stream_moveLine, _, _, M ←
!
765stream_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.

773cell_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.

784cell_writeseffect_, _, _, Ms, _, Line, J, SA0, Cells, CS0, CS ←
foldlcell_writeLine, J, SA0, Cells, Ms, CS0, CS
787cell_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
790cell_write_, _, _, _, _, CS, CS
792cell_storeinsmov, q, Src, memD, rbp, _, SA0, D, 8, D, CS0, CS ←
store_sourceSrc, 64, SA0, Lo, Hi
!
put_assocD, CS0, vLo, Hi, CS
795cell_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
799cell_store_, J, _, _, _, C, CS0, CS ←
put_assocC, CS0, vresJ, cellC, lo, resJ, cellC, hi, CS
802store_sourceregN, W, SA0, V ←
registerN, F, W
read_valueF, W, SA0, V
803store_sourceimmI, W, _, V ←
integerI
imm_valueI, W, V
no_fallthrough+Line, +Effectsemidet

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

807no_fallthrough_, effect_, _, _, _, terminal
808no_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).

814operand_moveinsOp, S, Os, Move ←
move_formOp, S, Os, Move
816move_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
822move_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.

832imm_range8, I ←
I ≥ -128
I < 256
833imm_range16, I ←
I ≥ -32768
I < 65536
834imm_range32, I ←
I ≥ −1 << 31
I < 1 << 32
835imm_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).

840move_state_, none, none ←
!
841move_statemoveF, regG, 64, stR0, X, Fl, stR, X, Fl ←
!
reg_getG, R0, V
reg_putF, R0, V, R
843move_statemoveF, regG, 32, stR0, X, Fl, stR, X, Fl ←
!
reg_getG, R0, vLo, _
reg_putF, R0, vLo, const0, R
845move_statemoveF, valuevLo, Hi, 64, stR0, X, Fl, stR, X, Fl ←
!
reg_putF, R0, vLo, Hi, R
847move_statemoveF, valuevLo, _, 32, stR0, X, Fl, stR, X, Fl ←
!
reg_putF, R0, vLo, const0, R
849move_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.

853imm_halves32, I, constLo, const0 ←
Lo is I mod 1 << 32
854imm_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).

861boundary_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.

869shared_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.

878reg_getF, T, V ←
argI, T, V
879reg_putF, T0, V, T ←
put_slotI, T0, V, T
880put_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
881put_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
882put_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
883put_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
884put_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
885put_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
886put_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
887put_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
888put_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
889put_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
890put_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
891put_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
892put_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
893put_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
894put_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
895put_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
8961x_, A2, A3, A4, A5, A6, A7, A8VxV, A2, A3, A4, A5, A6, A7, A8
8972xA1, _, A3, A4, A5, A6, A7, A8VxA1, V, A3, A4, A5, A6, A7, A8
8983xA1, A2, _, A4, A5, A6, A7, A8VxA1, A2, V, A4, A5, A6, A7, A8
8994xA1, A2, A3, _, A5, A6, A7, A8VxA1, A2, A3, V, A5, A6, A7, A8
9005xA1, A2, A3, A4, _, A6, A7, A8VxA1, A2, A3, A4, V, A6, A7, A8
9016xA1, A2, A3, A4, A5, _, A7, A8VxA1, A2, A3, A4, A5, V, A7, A8
9027xA1, A2, A3, A4, A5, A6, _, A8VxA1, A2, A3, A4, A5, A6, V, A8
9038xA1, A2, A3, A4, A5, A6, A7, _VxA1, A2, A3, A4, A5, A6, A7, V
reg_index
905rax1
905rcx2
905rdx3
905rbx4
906rsp5
906rbp6
906rsi7
906rdi8
907r89
907r910
907r1011
907r1112
908r1213
908r1314
908r1415
908r1516
909xmm01
909xmm12
909xmm23
909xmm34
910xmm45
910xmm56
910xmm67
910xmm78
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.

916region_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
923dead_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
932rax0
932rcx1
932rdx2
932rsi3
933rdi4
933r85
933r96
934rbx8
934r109
934r1110
934r1211
935r1312
935r1413
935r1514
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.

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

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

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

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

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

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

implicit
1104cltd_rax−32rdx−32
1105cqto_rax−64rdx−64
1106divbrax−8, rdx−8rax−8, rdx−8
1107divwrax−16, rdx−16rax−16, rdx−16
1108divlrax−32, rdx−32rax−32, rdx−32
1109divqrax−64, rdx−64rax−64, rdx−64
1110idivbrax−8, rdx−8rax−8, rdx−8
1111idivwrax−16, rdx−16rax−16, rdx−16
1112idivlrax−32, rdx−32rax−32, rdx−32
1113idivqrax−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.

1116'lock cmpxchg'brax−8rax−8
1117'lock cmpxchg'wrax−16rax−16
1118'lock cmpxchg'lrax−32rax−64
1119'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
1125addbdefined
1126addwdefined
1127addldefined
1128addqdefined
1129subbdefined
1130subwdefined
1131subldefined
1132subqdefined
1133andbdefined
1134andwdefined
1135andldefined
1136andqdefined
1137orbdefined
1138orwdefined
1139orldefined
1140orqdefined
1141xorbdefined
1142xorwdefined
1143xorldefined
1144xorqdefined
1145cmpbdefined
1146cmpwdefined
1147cmpldefined
1148cmpqdefined
1149testbdefined
1150testwdefined
1151testldefined
1152testqdefined
1153negbdefined
1154negwdefined
1155negldefined
1156negqdefined
1157ucomissnonedefined
1158ucomisdnonedefined
1159shlbshift
1160shlwshift
1161shllshift
1162shlqshift
1163shrbshift
1164shrwshift
1165shrlshift
1166shrqshift
1167sarbshift
1168sarwshift
1169sarlshift
1170sarqshift
1171imulbundefined
1172imulwundefined
1173imullundefined
1174imulqundefined
1175btcbundefined
1176btcwundefined
1177btclundefined
1178btcqundefined
1179divbundefined
1180divwundefined
1181divlundefined
1182divqundefined
1183idivbundefined
1184idivwundefined
1185idivlundefined
1186idivqundefined
1187'lock cmpxchg'bdefined
1188'lock cmpxchg'wdefined
1189'lock cmpxchg'ldefined
1190'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.
1208pairedLA, 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
1216zero_idiominsOp, S, regX, regX, Op, S, F ←
memberchkOp, xor, sub
memberchkS−W, l−32, q−64
registerX, F, W
¬memberchkF, rsp, rbp
1220zero_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.

1225lea_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.

1238lea_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.

1243lea_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
1248lea_inputsadd, immD, memD, B, W, _, SB0, VY ←
integerD
D ≥ −1 << 31
D < 1 << 31
lea_registerB, W, SB0, V
V ≡ VY
1251lea_inputssub, immD, memND, B, W, _, SB0, VY ←
integerD
integerND
ND =:= −D
ND ≥ −1 << 31
ND < 1 << 31
lea_registerB, W, SB0, V
V ≡ VY
1255lea_registerN, W, S, V ←
registerN, F, 64
¬memberchkF, rsp, rbp
read_valueF, W, S, V
commutativeaddandorxorimul
1260paired_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.

1282sources[], [], [], _, _, _
1283sourcesR|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).

1289writes[], [], _, _, S, S
1290writesR|Rs, O|Os, P, J, S0, S ←
role_writeR, O, P, J, S0, S1
P1 is P + 1
writesRs, Os, P1, J, S1, S
1295role_writedst_, O, P, J, S0, S ←
!
register_writeO, P, J, S0, S
1296role_writesrcdst_, O, P, J, S0, S ←
!
register_writeO, P, J, S0, S
1297role_write_, _, _, _, S, S
1299register_writeregN, P, J, S0, S ←
!
registerN, F, W
write_valueF, W, resJ, P, S0, S
1301register_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.

1305flags_readOp ←
atom_concatset, _, Op
!
1306flags_readOp ←
atom_concatcmov, _, Op
1308flags_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.

1320role_sourcesdstW, _, SA, SB, OA, OB ←
place_matchW, SA, SB, OA, OB
1321role_sourcesaddr, _, SA, SB, OA, OB ←
memory_matchSA, SB, OA, OB
1322role_sourcessrcW, Op, SA, SB, OA, OB ←
source_matchOp, W, SA, SB, OA, OB
1323role_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.

1328place_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
1335source_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
1340operand_valueregN, _, W, S, V ←
registerN, F, W
read_valueF, W, S, V
1341operand_valueimmI, Op, W, _, V ←
integerW
integerI
→
Op ≡ movabs
→
I ≥ −1 << 63
I < 1 << 64
else
imm_valueI, W, V
else
V = symI
1349imm_valueI, 64, constLo, constHi ←
!
U is I mod 1 << 64
Lo is U /\ 0xffffffff
Hi is U >> 32
1351imm_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.

1355read_valueF, W, S, V ←
width_readW, F, S, V
1357width_read128, F, st_, X, _, V ←
reg_getF, X, V
1358width_read64, F, stR, _, _, Lo, Hi ←
reg_getF, R, vLo, Hi
1359width_read32, F, stR, _, _, Lo ←
reg_getF, R, vLo, _
1360width_read16, F, stR, _, _, V ←
reg_getF, R, vLo, _
narrowword, Lo, V
1361width_read8, F, stR, _, _, V ←
reg_getF, R, vLo, _
narrowbyte, Lo, V
1363narrowbyte, constN, constM ←
!
M is N /\ 255
1364narrowword, constN, constM ←
!
M is N /\ 65535
1365narrowbyte, merge8_, N, N ←
!
1366narrowword, merge16_, N, N ←
!
1367narrowbyte, merge16_, N, V ←
!
narrowbyte, N, V
1368narrowK, 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.

1372memory_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
1379same_baseSA, SB, A, B ←
registerA, FA, 64
registerB, FB, 64
read_valueFA, 64, SA, V
read_valueFB, 64, SB, W
V ≡ W
1383implicit_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.

1386write_valueF, 128, V, stR, X0, Fl, stR, X, Fl ←
!
put_xslotI, X0, V, X
1387write_valueF, W, V, stR0, X, Fl, stR, X, Fl ←
argI, R0, Old
writtenW, V, Old, New
put_slotI, R0, New, R
written
139264V_vloV, hiV
139332V_vloV, const0
139416VvLo0, Hi0vmerge16Lo0, loV, Hi0
13958VvLo0, Hi0vmerge8Lo0, loV, Hi0