Fil-C on Nix

Filnix wiki

mbrock/filnix/nanofil

amd64_regs.pl

Whole-function register homes for scalar temporaries (docs/perf.md item 3, step 3a), with step 3b's operand rewrite and 5a-loads' frame cells: --registers=temps, and --registers=pointers, the -O default. A fixed map puts frame homes in registers for the whole function: homes live across a call in the callee-saved rbx and r12-r15, the others also in r10/r11. Accesses stay the moves the reference emits, with a register in place of the slot; no instruction is selected differently.

A home is a candidate when it is a scalar (w/l) home, or under pointers a pointer's address word (class a), every access to it is a whole move of that home (amd64_regs_check:substitute/3), its address is never taken, it is not live at entry, and no 32-bit store to it leaves its high half live. Homes interfere when one is written while the other is live; a greedy colouring by estimated execution count (references weighted by the depth of the backward jumps around them) assigns registers. Homes that get none stay in their slots.

The emitter reserves a save slot per callee-saved register in this mode; the registers the map uses are saved after the stack check and restored before every leave. amd64_regs_check validates the result; a failed check is an error, never a silent fallback.

moduleamd64_regs
exportsregs_asm/6
usesamd64_contractinstruction_effects/2, register/3, explicit_registers/2
usesassoc
useslists
usespairs
regs_asm+Mode, +Metadata, +Before, -After, -Checked, -Reportdet

Mode is map (3a's map alone, unchecked, for a layout choice; Final is then not to be emitted) or frame(Cells) (step 3a, then step 3b's operand rewrite with the frame cells of docs/perf.md 5a-loads). Metadata is regs_metadata(Setjmp, Impl, Homes, Saves) with a save slot for every callee-saved register. Checked is the metadata and map the checker validated: checked(Metadata1, Map). Report is excluded(setjmp|unsupported) or allocated(Homes, Registers).

39regs_asmmap, regs_metadataSetjmp, Impl, Homes, _, Before, Before, checkednone, Map, map ←
!
Map mode only proposes the map, for a layout choice (temps mode's
first lowering): its stream is never emitted, so neither the
preflight nor the checker runs. A body the real run would exclude
still gets one; the run on the final layout excludes it.
Setjmp ≢ true
impl_bodyImpl, Before, _, Body0
maplistline_effect, Body0, Effects0
home_linesBody0, Effects0, Body, Effects
allocationBody, Effects, Homes, Map, _
→
true
else
Map = []
53regs_asmMode, regs_metadataSetjmp, Impl, Homes, Saves, Before, Final, checkedMeta, Map, Report ←
empty_assocNone
Setjmp ≡ true
→
After = Before
Map = []
Used = []
Report = excludedsetjmp
Analysis = none
impl_bodyImpl, Before, Pre, Body
maplistline_effect, Body, Effects
findallS, membersave_, S, Saves, Ss
preflight_regsBody, None, Ss, Effects
onceallocated_streamBody, None, [], _
successorsBody, Effects, _
→
allocationBody, Effects, Homes, Map, Analysis0
findallsaveR, S,
membersaveR, S, Saves
memberchk_−R, Map
,
Used
Map ≡ []
→
After = Before
else
list_to_assocMap, Mapped
onceallocated_streamBody, Mapped, Used, Body1
appendPre, labelImpl|Body1, After
lengthMap, NH
lengthUsed, NR
Report = allocatedNH, NR
Analysis0 ≡ none
→
body_analysisBody, Effects, [], Analysis
else
Analysis = Analysis0
else
After = Before
Map = []
Used = []
Report = excludedunsupported
Analysis = none
Meta = regs_metadataSetjmp, Impl, Homes, Used
check_regsMeta, Map, Before, After, Analysis
→
true
else
thrownanofil_error'register home translation check failed'
operand_cellsMode, Cells0
Report = allocated_, _
→
impl_bodyImpl, After, PreA, A
operand_livesAnalysis, Map, Used, A, Lives
→
true
else
thrownanofil_error'register operand liveness failed'
valid_cellsCells0, A, Cells
findallR, membersaveR, _, Used, Saved
appendrax, rcx, rdx, rsi, rdi, r8, r9, r10, r11, Saved, Usable
→
alloc_rewriteA, Lives, Cells, Usable, B
else
operands_rewriteA, Lives, Cells, B
check_operandsA, B, Lives, Cells0
→
true
else
thrownanofil_error'register operand translation check failed'
appendPreA, labelImpl|B, Final
else
Final = After
home_lines+Body0, +Effects0, -Body, -Effects

the lines the map depends on: labels, control flow, calls, and lines with a memory operand or a lea. A line left out falls through and touches no home, so home liveness, interference, loop depths and references are the same as on the whole body; only scratch and flags liveness, which the allocation masks off, differ.

110home_lines[], [], [], []
111home_linesL|Ls, E|Es, Body, Effects ←
E = effect_, _, _, Ms, Flow
Ms ≡ []
memberchkFlow, next, frame, directive
L ≠ inslea, _, _
L ≠ inscall, _, _
→
Body = Body1
Effects = Effects1
else
Body = L|Body1
Effects = E|Effects1
home_linesLs, Es, Body1, Effects1
allocation+Body, +Effects, +Homes, -Map, -Analysis

Map assigns candidate homes registers. Analysis is body_analysis/4's result for the candidates (a superset of Map's homes), or none when there are none.

123allocationBody, Effects, Homes, Map, Analysis ←
findallD,
memberhomeD, Ks, Homes
Ks = _|_
forallmemberK, Ks, memberchkK, w, l, a
,
Scalar
findallW−H,
memberH, Scalar
W is H >> 3
,
WHs0
keysortWHs0, WHs
group_pairs_by_keyWHs, Words
list_to_assocWords, ScalarSet
depthsBody, Depths
foldlhome_refsScalarSet, Body, Effects, Depths, [], Refs
keysortRefs, Sorted
group_pairs_by_keySorted, Groups
findallD−W,
memberD−Ws, Groups
¬memberchkbad, Ws
sum_listWs, W
,
Weighted
lengthBody, NL
heaviestWeighted, NL, Candidates0
Candidates0 ≡ []
→
Map = []
Analysis = none
else
body_analysisBody, Effects, Candidates0, Analysis
Analysis = analysis_, _, _, _, Outs, In0, _
numberedCandidates0, 0, Numbered
list_to_assocNumbered, Index
list_to_assocWeighted, Weights
lengthCandidates0, NC
HomeBits is 1 << 2 × NC−1 << 8
empty_assocRows0
foldlhome_factsIndex, HomeBits, Body, Outs, f0, 0−Rows0, fCross, Wide−Rows
Excluded is In0 \/ Wide
findallW−D,
memberD−K, Numbered
Excluded /\ 3 << S =:= 0
get_assocD, Weights, W
,
ByWeight0
keysortByWeight0, Ascending
reverseAscending, ByWeight
empty_assocColours0
findallR−taken0, 0, memberR, r10, r11, rbx, r12, r13, r14, r15, Taken0
foldlcolourIndex, Cross, Rows, ByWeight, Colours0−Taken0, Colours−_
assoc_to_listColours, Map
155numbered[], _, []
156numberedH|Hs, K, H−K|Ps ←
K1 is K + 1
numberedHs, K1, Ps
heaviest+Weighted, +Lines, -Candidates

the homes of Weighted (D-W, by offset) that the allocator considers, by offset. The liveness sets cost a bit per candidate half per line, so a function whose candidates times lines exceeds the budget (2^29 bits, a 64 MB set) keeps only its heaviest homes, ties going to the lower offset. At most seven registers go round, so the light ones seldom get one anyway. No bench function reaches the budget (sqlite3VdbeExec, the largest, is at 0.68); netcdf's tst_h_atts.c main (14156 homes, 285k lines) and openssh's libcrux (3054, 116k) do.

167heaviestWeighted, Lines, Candidates ←
lengthWeighted, N
Cap is max256, 1 << 29//max1, 2 × Lines
N ≤ Cap
→
pairs_keysWeighted, Candidates
else
findallNW−D,
memberD−W, Weighted
NW is −W
,
ByWeight0
keysortByWeight0, ByWeight
lengthTop, Cap
appendTop, _, ByWeight
pairs_valuesTop, Ds
msortDs, Candidates
home_refs+Scalar, +Line, +Effect, +Depth, +Refs0, -Refs

each reference to a scalar home gives Home-Weight, or Home-bad when the line is not a whole move of exactly that home.

179home_refsScalar, Line, effect_, _, _, Ms, _, Depth, Refs0, Refs ←
Ms ≡ []
Line ≠ inslea, _, _
→
Refs = Refs0
else
findallH−Ref, line_home_refScalar, Line, Ms, Depth, H, Ref, New
appendNew, Refs0, Refs
scalar_home+Words, +H0, +H1, -H

a scalar home H within H0..H1; Words buckets the homes by 8-byte word.

188scalar_homeWords, H0, H1, H ←
W0 is H0 >> 3
W1 is H1 >> 3
betweenW0, W1, W
get_assocW, Words, Hs
memberH, Hs
H ≥ H0
H ≤ H1
192line_home_refScalar, Line, Ms, Depth, H, Ref ←
H0 is D−7
H1 is D + N−1
scalar_homeScalar, H0, H1, H
Line ≠ inslea, _, _
H =:= D
singleH, Map
oncesubstituteLine, Map, _
→
Ref is 1 << 3 × minDepth, 5
else
Ref = bad
200line_home_refScalar, inslea, _, memD, rbp, _, _, _, H, bad ←
H0 is D−7
scalar_homeScalar, H0, D, H
203singleH, Map ←
empty_assocE
put_assocH, E, rbx, Map
depths+Body, -Depths

for each line, how many backward jumps span it.

206depthsBody, Depths ←
GNU as numeric labels repeat; their jumps are short and ignored here.
findallL−I,
nth0I, Body, labelL
,
Ls
list_to_assocLs, Labels
findallT−I,
nth0I, Body, insOp, none, symL
atom_concatj, _, Op
get_assocL, Labels, T
T < I
,
Loops
findallT−1, memberT−_, Loops, Opens
findallE−-1,
member_−I, Loops
E is I + 1
,
Closes
appendOpens, Closes, Events0
keysortEvents0, Events
lengthBody, N
running0, N, Events, 0, Depths

Numeric GNU as labels are nonempty decimal digit sequences, not arbitrary Prolog numbers. Trealla's atom_number/2 throws on symbols.

220numeric_labelL ←
atom_codesL, C|Cs
forallmemberD, C|Cs,
D ≥ 0'0
D ≤ 0'9
224runningI, N, _, _, [] ←
I ≥ N
!
225runningI, N, Events, D0, D|Ds ←
take_eventsEvents, I, D0, D, Rest
J is I + 1
runningJ, N, Rest, D, Ds
228take_eventsE−X|Es, I, D0, D, Rest ←
E ≤ I
!
D1 is D0 + X
take_eventsEs, I, D1, D, Rest
230take_eventsEs, _, D, D, Es

home_facts: homes live across a call (Cross), homes whose 32-bit store leaves the high half live (Wide), and each home's interference row: an assoc from home number K to the halves live at any write of K, K's own excluded. HomeBits masks off the analysis's scratch and flags bits.

237home_factsIndex, HomeBits, Line, Out, fC0, W0−E0, fC, W−E ←
Line = inscall, _, _
→
C is C0 \/ Out
W = W0
E = E0
Line = insmov, S, _, memD, rbp
rbp_accessLine, D, _
get_assocD, Index, K
→
C = C0
Own is 3 << Sh
S ≡ l
Out /\ 2 << Sh ≠ 0
→
W is W0 \/ Own
else
W = W0
Others is Out /\ HomeBits xor HomeBits /\ Own
Others =:= 0
→
E = E0
else
get_assocK, E0, Row0
→
true
else
Row0 = 0
Row is Row0 \/ Others
put_assocK, E0, Row, E
else
C = C0
W = W0
E = E0
colour+Index, +Cross, +Rows, +W−D, +Colours0−Taken0, −Colours−Taken

home D gets the first register of its list that no interfering home has. Homes J and K interfere when one's row has a half of the other. Taken holds, per register, the halves of the homes it was given and the union of their rows, so neither direction lists pairs: those are quadratic in the homes (netcdf's tst_h_atts.c had 12 million).

259colourIndex, Cross, Rows, _−D, Colours0−Taken0, Colours−Taken ←
get_assocD, Index, K
Own is 3 << S
Cross /\ Own ≠ 0
→
Registers = rbx, r12, r13, r14, r15
else
Registers = r10, r11, rbx, r12, r13, r14, r15
get_assocK, Rows, Row
→
true
else
Row = 0
memberR, Registers
memberchkR−takenSet, Union, Taken0
Row /\ Set =:= 0
Union /\ Own =:= 0
→
put_assocD, Colours0, R, Colours
Set1 is Set \/ Own
Union1 is Union \/ Row
giveTaken0, R, takenSet1, Union1, Taken
else
Colours = Colours0
Taken = Taken0
273giveR0−T0|Ts0, R, T, Ts ←
R0 ≡ R
→
Ts = R−T|Ts0
else
Ts = R0−T0|Ts1
giveTs0, R, T, Ts1

Step 3b: register operands (docs/perf.md)

Untrusted: check_operands/3 validates the result against A. Within each region (lines between boundaries), a forward pass propagates register and immediate copies into the moves and source operands that read them; then moves into dead scratch registers are deleted, a scratch result copied into a register is written there directly, and dead moves are deleted again. Liveness at a region's end is the reference's (operand_lives/4); inside, it is recomputed on the rewritten lines. The passes carry each line as Line-inf(Effect, Use, Def, Move): its audited effects, the scratch and flags sets it uses and defines, and its operand_move/2 reading or none, computed once.

operand_cells+Mode, -Cells

the frame cells whose loads the rewrite may forward, the emitter's (docs/perf.md, 5a-loads).

293operand_cellsframeCells, Cells
operands_rewrite+A, +Lives, +Cells, -Bdet
296operands_rewriteA, Lives, Cells, B ←
operand_regionsA, Lives, Regions
maplistregion_rewriteCells, Regions, Parts
appendParts, B
301operand_regions[], [], [] ←
!
302operand_regionsAs, Ls, regionLines, End, Boundary|Rs ←
region_linesAs, Ls, Lines, End, Boundary, As1, Ls1
operand_regionsAs1, Ls1, Rs
306region_lines[], [], [], 0, [], [], []
307region_linesL|As, liveIn, _, E|Ls, Lines, End, Boundary, As1, Ls1 ←
→
Lines = []
End is In /\ 127
Boundary = L
As1 = As
Ls1 = Ls
else
Lines = L−I|Lines1
region_linesAs, Ls, Lines1, End, Boundary, As1, Ls1
314region_rewriteCells, regionLines0, End, Boundary, Out ←
empty_assocC0
empty_assocCells
→
Lines = Lines0
else
cell_forwardLines0, Cells, C0, Lines
forwardLines, C0, L1
dead_movesL1, End, L2
retargetL2, End, L3
dead_movesL3, End, L4
pairs_keysL4, Ls4
appendLs4, Boundary, Out
326with_effectL, L−I ←
cell_forward+Lines, +Cells, +Held, -Out

a load of a cell that a register still holds whole (Held maps the cell to the register family that loaded or stored it, unchanged since) reads that register instead. forward/3 then propagates the copy like any other.

332cell_forward[], _, _, []
333cell_forwardL−I|Ls, Cells, H0, Out ←
L = insmov, S, memD, rbp, regY
get_assocD, Cells, _
memberchkS−W, l−32, q−64
registerY, F, W
¬memberchkF, rsp, rbp
→
get_assocD, H0, G
→
S ≡ q
G ≡ F
→
Emit = []
H = H0
else
registerGN, G, W
L1 = insmov, S, regGN, regY
Emit = LE
drop_heldF, H0, H
else
Emit = L−I
drop_heldF, H0, H1
S ≡ q
→
put_assocD, H1, F, H
else
H = H1
L = insmov, q, regX, memD, rbp
get_assocD, Cells, _
registerX, G, 64
¬memberchkG, rsp, rbp
→
Emit = L−I
put_assocD, H0, G, H
else
Emit = L−I
I = infeffect_, Ws, _, Ms, _, _, _, _
foldldrop_written, Ws, H0, H1
foldldrop_stored, Ms, H1, H
appendEmit, Rest, Out
cell_forwardLs, Cells, H, Rest
drop_held+F, +H0, -H

F changes; no cell is held in it any more.

355drop_heldF, H0, H ←
assoc_to_listH0, Ps
memberchk_−F, Ps
→
excludeheld_inF, Ps, Kept
list_to_assocKept, H
else
H = H0
360held_inF, _−G ←
G ≡ F
361drop_writtenwriteF, _, H0, H ←
!
drop_heldF, H0, H
362drop_written_, H, H

A store overlapping a cell drops it.

364drop_storedwritememD0, rbp, N, H0, H ←
!
assoc_to_listH0, Ps
memberP, Ps
overlapsD0, N, P
→
excludeoverlapsD0, N, Ps, Kept
list_to_assocKept, H
else
H = H0
370drop_stored_, H, H
371overlapsD0, N, D−_ ←
D < D0 + N
D0 < D + 8
373effect_infoL, E, infE, U, D, M ←
extra_use_defL, E, U, D
→
M = M0
else
M = none
forward+Lines, +Contents, -Out

Contents maps a register family to what it holds: copy(G, W), G's value (W = 64) or its low half zero-extended (W = 32), or imm(V), the 64-bit value V.

380forward[], _, []
381forwardL−I|Ls, C0, Out ←
I = inf_, _, _, moveF, Src, W
→
move_contentSrc, W, C0, Content
forgetF, C0, C1
Content = copyF, 64
→
Emit = []
Src = regG
get_assocG, C0, _
→
materializeContent, F, Line
with_effectLine, LE
Emit = LE
else
Emit = L−I
Content ≡ none
or
Content = copyF, _
→
C = C1
else
put_assocF, C1, Content, C
else
forward_lineL, C0, L1
L1 ≡ L
→
I1 = I
else
with_effectL1, L1−I1
Emit = L1−I1
I1 = infeffect_, Ws, _, _, _, _, _, _
foldlforget_written, Ws, C0, C
appendEmit, Rest, Out
forwardLs, C, Rest
401move_contentimmI, W, _, immV ←
V is I mod 1 << W
402move_contentregG, W, C0, Content ←
get_assocG, C0, C
→
composeC, W, Content
memberchkG, rsp, rbp
→
Content = none
else
Content = copyG, W

A home register's name is never replaced by a scratch one: that would keep the scratch register live, and its producer unretargeted.

410renames_, imm_
411renamesG, copyH, _ ←
else
true
413composecopyH, W0, W, copyH, W1 ←
W1 is minW0, W
414composeimmV, W, immV1 ←
V1 is V /\ 1 << W−1
416materializecopyG, W, F, insmov, S, regGN, regFN ←
memberchkS−W, l−32, q−64
registerGN, G, W
registerFN, F, W
418materializeimmV, F, Line ←
V < 1 << 32
→
registerFN, F, 32
signedV, 32, I
Line = insmov, l, immI, regFN
else
registerFN, F, 64
signedV, 64, I
I ≥ −1 << 31
I < 1 << 31
→
Line = insmov, q, immI, regFN
else
Line = insmovabs, q, immI, regFN
428signedV, W, I ←
V ≥ 1 << W−1
→
I is V−1 << W
else
I = V
430forget_writtenwriteF, _, C0, C ←
!
forgetF, C0, C
431forget_written_, C, C
forget+F, +C0, -C

F changes; drop what it held and every copy of it.

434forget_, C0, C ←
empty_assocC0
!
C = C0
435forgetF, C0, C ←
assoc_to_listC0, Ps
memberP, Ps
→
excludedepends_onF, Ps, Kept
list_to_assocKept, C
else
C = C0
441depends_onF, F−_
442depends_onF, _−copyF, _
forward_line+L, +C, -L1

source operands that read a copy or an immediate read the original; memory operands address through the original register. Each replacement must leave an audited form.

447forward_lineL, C, L1 ←
¬empty_assocC
L = insOp, S, Os
onceoperand_rolesOp, S, Roles
→
forward_operandsRoles, Os, 1, C, Op, L, L1
else
L1 = L
453forward_operands[], _, _, _, _, L, L
454forward_operandsRole|Roles, O|Os, K, C, Op, L0, L ←
forwardedRole, Op, C, O, O1
O1 ≢ O
L0 = insOp, S, Os0
replace_nthK, Os0, O1, Os1
L1 = insOp, S, Os1
→
true
else
L1 = L0
K1 is K + 1
forward_operandsRoles, Os, K1, C, Op, L1, L
464forwardedsrc_, Op, C, regN, O ←
registerN, F, W
W < 128
get_assocF, C, Content
renamesF, Content
Content = copyG, Wc
→
W ≤ 32
or
Wc =:= 64
registerN1, G, W
O = regN1
else
Content = immV
Op ≢ btc
imm_forV, W, I
O = immI
469forwarded_, _, C, memD, B, memD, B1 ←
470forwarded_, _, C, memD, B, X, memD, B1, X1 ←
473base_originalC, B, B1 ←
registerB, F, 64
get_assocF, C, copyG, 64
renamesF, copyG, 64
→
registerB1, G, 64
else
B1 = B
479imm_forV, 64, I ←
signedV, 64, I
I ≥ −1 << 31
I < 1 << 31
480imm_forV, 32, I ←
V32 is V /\ 0xffffffff
signedV32, 32, I
481imm_forV, 16, I ←
I is V /\ 0xffff
482imm_forV, 8, I ←
I is V /\ 0xff
484replace_nth1, _|Xs, Y, Y|Xs ←
!
485replace_nthK, X|Xs, Y, X|Ys ←
K1 is K−1
replace_nthK1, Xs, Y, Ys
dead_moves+Lines, +End, -Out

moves into scratch registers dead after them are deleted.

489dead_movesLines, End, Out ←
reverseLines, Rev
foldldead_move, Rev, End−[], _−Out
492dead_moveL−I, Live0−Acc, Live−Acc1 ←
I = inf_, _, _, moveF, _, _
Live0 /\ 1 << Bit =:= 0
→
Live = Live0
Acc1 = Acc
else
live_beforeL−I, Live0, Live
Acc1 = L−I|Acc
498live_before_−inf_, U, D, _, Out, In ←
In is U \/ Out xor Out /\ D /\ 127
live_after+Lines, +End, -Pairs

each line with the scratch families live after it.

503live_afterLines, End, Pairs ←
reverseLines, Rev
foldlafter_pair, Rev, End−[], _−Pairs
506after_pairL, Out−Acc, In−L−Out|Acc ←
live_beforeL, Out, In
retarget+Lines, +End, -Out

a scratch result copied into a register H is written to H.

510retargetLines, End, Out ←
live_afterLines, End, Pairs
retarget_pairsPairs, Out
514retarget_pairs[], []
515retarget_pairsL1−_−_, L2−_−_, L3−A3|Ps, Out ←
copy_outsL3−A3|Ps, L2, Outs, A, Ps1
two_addressL1, L2, Outs, A, New
!
maplistwith_effect, New, NewLEs
appendNewLEs, Out1, Out
520retarget_pairsL1−_−_, L2−A2|Ps, Out ←
copy_outsL2−A2|Ps, L1, Outs, A, Ps1
direct_resultL1, Outs, A, New
!
maplistwith_effect, New, NewLEs
appendNewLEs, Out1, Out
525retarget_pairsLE−_|Ps, LE|Out ←
copy_outs+Pairs, +Producer, -Moves, -After, -Rest

the moves that follow, each copying the producer's last operand register (S) into another one, and the scratch set live after the last. The later moves read the first one's destination H instead, so none may write H.

531copy_outsPairs, ins_, _, Os, M|Ms, After, Rest ←
lastOs, regN
registerN, S, _
Pairs = M−_−A0|Ps
operand_moveM, moveH, regS, _
H ≢ S
more_copy_outsPs, S, H, Ms, A0, After, Rest
535more_copy_outsM−_−A|Ps, S, H, M|Ms, _, After, Rest ←
operand_moveM, moveD, regS, _
D ≢ S
D ≢ H
!
more_copy_outsPs, S, H, Ms, A, After, Rest
538more_copy_outsPs, _, _, [], After, After, Ps

The later copies, reading H at their width.

541copies_fromH, Ms, New ←
maplistcopy_fromH, Ms, New
542copy_fromH, insmov, Sz, reg_, regD, insmov, Sz, regHN, regD ←
memberchkSz−W, l−32, q−64
registerHN, H, W

mov Src,%S; op X,%S; mov %S,%H -> mov Src,%H; op X,%H

546two_addressL1, L2, L3|More, A3, New ←
operand_moveL1, moveS, Src, W1
L2 = insOp, Sz, Os
memberchkOp, add, sub, and, or, xor, imul, neg, shl, shr, sar
onceoperand_rolesOp, Sz, Roles
nth1K, Roles, srcdstW
memberchkW, 32, 64
nth1K, Os, regN
registerN, S, W
operand_moveL3, moveH, regS, W3
H ≢ S
W3 =:= W
or
W =:= 32
A3 /\ 1 << Bit =:= 0
¬memberchkH, Rs
selectchkS, Rs, Others
¬memberchkS, Others
registerHN, H, W
replace_nthK, Os, regHN, Os1
L2a = insOp, Sz, Os1
copies_fromH, More, Copies
Src ≡ regH
W1 =:= 64
or
W =:= 32
→
New = L2a|Copies
Src = regG
→
materializecopyG, W1, H, L1a
New = L1a, L2a|Copies
else
L1 = insMv, Sz1, immI, _
registerHN1, H, W1
New = insMv, Sz1, immI, regHN1, L2a|Copies

op ...,%S; mov %S,%H -> op ...,%H, for a form whose last operand is a pure 32- or 64-bit destination.

569direct_resultL1, L2|More, A2, L|Copies ←
L1 = insOp, Sz, Os
onceoperand_rolesOp, Sz, Roles
lastRoles, dst_
lastOs, regN
registerN, S, Wop
memberchkWop, 32, 64
operand_moveL2, moveH, regS, Wc
H ≢ S
Wc =:= Wop
or
Wop =:= 32
A2 /\ 1 << Bit =:= 0
registerHN, H, Wop
appendFront, _, Os
appendFront, regHN, Os1
L = insOp, Sz, Os1
copies_fromH, More, Copies

Region allocation (docs/perf.md item 19, lever 1)

operand_selection-Selectiondet

Selection is how operands are chosen after step 3a (main.pl's --operands=, NANOFIL_OPERANDS): rewrite, step 3b's copy forwarding, or regions, the default, alloc_rewrite/5.

588operand_selectionS ←
getenv'NANOFIL_OPERANDS', S0
memberchkS0, rewrite, regions
→
S = S0
else
S = regions

Untrusted, like step 3b's rewrite, and validated by the same check_operands/4. Within a region, A's moves are not executed at all: the checker only needs B's paired lines to read A's values and B's registers to hold A's at the region's end where they are live. So each region is allocated again from A's symbolic values, computed by the checker's own step relations (paired/7, move_state/3):

  • every paired line's register operands read any register that holds A's value, or an immediate where A's form takes one;
  • a destination is the register where the result is needed at the region's end, else A's register, else any register whose value is not needed after the line (a value is needed while a later line or the region's end reads it and no other register holds it);
  • a two-address destination is a register that already holds the input and can be overwritten, or gets a copy of it first;
  • lines this does not model (division, partial register writes, SSE, shifts by %cl, setcc) keep A's registers, which first get A's values;
  • at the region's end, live registers get A's values by a parallel move.

Home registers are free inside a region and dead ones at its end too (operand_lives/5). Moves are the only lines it inserts, so the flags are never touched. A region it cannot allocate, or allocates into more lines than step 3b's rewrite, gets step 3b's rewrite.

alloc_rewrite+A, +Lives, +Cells, +Usable, -Bdet

B is the implementation body A with each region allocated again (above). Lives are operand_lives/5's sets for A's lines, Cells the valid frame cells, Usable the registers B may write: the scratch families, r10, r11 and the callee-saved registers the map saves.

625alloc_rewriteA, Lives, Cells, Usable, B ←
empty_assocNone
alloc_regionsA, Lives, envS0, Cells, None, Usable, shared, B
630alloc_regions[], [], _, _, _, [] ←
!
631alloc_regionsAs, Ls, Env, Usable, Start, Out ←
region_splitAs, Ls, Lines, LLs, Bound, As1, Ls1
Bound = BL−liveIn, BOut, BE
→
End = In
Bs = BL
→
Start1 = none
else
Start1 = liveBOut
else
End = 0
Bs = []
Start1 = none
region_choiceStart, Lines, LLs, End, Env, Usable, New
appendNew, Bs, Part
appendPart, Rest, Out
alloc_regionsAs1, Ls1, Env, Usable, Start1, Rest
642region_split[], [], [], [], [], [], []
643region_splitL|As, Lv|Ls, Lines, LLs, Bound, As1, Ls1 ←
Lv = live_, _, E
→
Lines = []
LLs = []
Bound = L−Lv
As1 = As
Ls1 = Ls
else
Lines = L|Lines1
LLs = Lv|LLs1
region_splitAs, Ls, Lines1, LLs1, Bound, As1, Ls1
region_choice+Start, +Lines, +LLs, +End, +Env, +Usable, -New

unreachable lines stay, and so does a region without moves (nothing to remove); otherwise the allocation, unless it fails or step 3b's rewrite is shorter. An allocation of only A's paired lines is shortest, so the rewrite is not built for it.

656region_choicenone, Lines, _, _, _, _, Lines ←
!
657region_choiceStart, Lines, LLs, End, Env, Usable, New ←
Env = envS0, Cells, _
includemove_like, Lines, Moves
lengthMoves, NM
NM =:= 0
→
New = Lines
start_statesStart, S0, SA, SB
oncealloc_regionLines, LLs, End, Env, Usable, SA, SB, Alloc
→
lengthAlloc, NA
lengthLines, NL
NA ≤ NL−NM
→
New = Alloc
else
region_rewrite_ofCells, Lines, LLs, End, Old
lengthOld, NO
NA ≤ NO
→
New = Alloc
else
New = Old
else
region_rewrite_ofCells, Lines, LLs, End, New
673region_rewrite_ofCells, Lines, LLs, End, Out ←
maplistlive_info, Lines, LLs, Infos
End3b is End /\ 127
region_rewriteCells, regionInfos, End3b, [], Out
move_like+Line

a register or immediate move, or a load from the frame, which a cell may make one.

680move_likeL ←
681move_likeinsmov, S, mem_, rbp, reg_ ←
memberchkS, l, q
683live_infoL, live_, _, E, L−I ←
685start_statesshared, S0, S0, S0
686start_statesliveOut, S0, SA, SB ←
region_startOut, S0, SA, SB
688alloc_regionLines, LLs, End, Env, Usable, SA0, SB0, Out ←
empty_assocCS0
a_simLines, LLs, Env, SA0, CS0, 0, Ps0, SAend, CSend
Flags0 is End /\ 128
flags_afterPs0, Flags0, Ps
end_targetsEnd, SAend, Targets
empty_assocCnt00
foldlcount_line, Ps, Cnt00, Cnt01
foldlcount_target, Targets, Cnt01, Cnt0
Ctx = ctxUsable, Env, Targets
b_walkPs, Ctx, SB0, Cnt0, Out, Tail, SB1, Cnt1
resolveTargets, Ctx, CSend, Cnt1, SB1, _, Tail, []

a_sim: A's region on the checker's symbolic values. Each paired line is p(J, Line, Effect, Mode, Pre, Post, Cells, Demands, FlagsLive), the last bound by flags_after/3.

704a_sim[], [], _, SA, CS, _, [], SA, CS
705a_simL|Ls, live_, _, E|LLs, Env, SA, CS, J, Ps, SAe, CSe ←
stream_moveL, Env, CS, M
→
move_stateM, SA, SA1
CS1 = CS
J1 = J
Ps = Ps1
else
pairedL, L, J, SA, SA, SA1, _
Env = env_, Cells, _
cell_writesE, L, J, SA, Cells, CS, CS1
line_modeL, Mode
line_demandsMode, L, E, SA, Ds
Ps = pJ, L, E, Mode, SA, SA1, CS, Ds, _|Ps1
J1 is J + 1
a_simLs, LLs, Env, SA1, CS1, J1, Ps1, SAe, CSe

The registers B may hold values in, in the order they are tried.

718gprsrax, rcx, rdx, rsi, rdi, r8, r9, r10, r11, rbx, r12, r13, r14, r15
720end_targetsEnd, stR, _, _, Targets ←
findallF−V,
memberF, Gs
End /\ 1 << Bit ≠ 0
reg_getF, R, V
,
Targets
725alloc_live_bitF, Bit ←
726alloc_live_bitF, Bit ←
line_mode+Line, -Mode

flex(Roles) when every register operand is a general register other than rsp and rbp, every register destination is written whole (32 or 64 bits) and the form has no implicit or fixed register; otherwise fixed.

732line_modeinsOp, S, regX, regX, zeroW ←
memberchkOp, xor, sub
memberchkS−W, l−32, q−64
registerX, F, W
¬memberchkF, rsp, rbp
!
735line_modeinsOp, S, Os, flexRoles ←
¬memberchkOp, div, idiv, cltd, cqto, 'lock cmpxchg', btc, mfence, movd, movq, ucomiss, ucomisd, cvtss2sd, cvtsd2ss, cvttss2si, cvttsd2si, cvtsi2ss, cvtsi2sd
¬atom_concatset, _, Op
¬memberchkS, ss, sd
onceoperand_rolesOp, S, Roles
¬
memberchkOp, shl, shr, sar
Os = reg_|_
maplistflex_operand, Roles, Os
!
744line_mode_, fixed
746flex_operand_, imm_ ←
!
747flex_operand_, M ←
748flex_operandsrcW, regN ←
registerN, F, W
W < 128
¬memberchkF, rsp, rbp
749flex_operanddstW, regN ←
memberchkW, 32, 64
registerN, F, W
¬memberchkF, rsp, rbp
750flex_operandsrcdstW, regN ←
memberchkW, 32, 64
registerN, F, W
¬memberchkF, rsp, rbp
mem_operandmem_, _mem_, _, _rip_gotpcrel_
line_demands+Mode, +Line, +Effect, +Pre, -Keys

the values Line reads from registers: lo(Lo) for a read of 32 bits or less, full(Lo, Hi) for 64 bits and addressing. A fixed line needs every register it reads or partly writes whole.

758line_demandsfixed, _, effectRs, Ws, _, _, _, stR, _, _, Ks ←
findallF,
memberF, Rs
,
Fs1
findallF,
memberwriteF, W, Ws
W < 32
,
Fs2
appendFs1, Fs2, Fs0
sortFs0, Fs
findallfullLo, Hi,
memberF, Fs
reg_getF, R, vLo, Hi
,
Ks
763line_demandszero_, _, _, _, []
764line_demandsflexRoles, ins_, _, Os, _, stR, _, _, Ks ←
foldloperand_demandsR, Roles, Os, [], Ks
767operand_demandsR, Role, regN, Ks0, Ks ←
memberchkRole, src_, srcdst_
!
registerN, F, W
reg_getF, R, V
key_atW, V, K
Ks = K|Ks0
770operand_demandsR, _, M, Ks0, Ks ←
findallfullLo, Hi,
¬memberchkF, rsp, rbp
reg_getF, R, vLo, Hi
,
New
appendNew, Ks0, Ks
775address_registermem_, B, F ←
registerB, F, 64
776address_registermem_, B, _, F ←
registerB, F, 64
777address_registermem_, _, I, F ←
registerI, F, 64
779alloc_gprF ←
memberchkF, Gs
781key_at64, vL, H, fullL, H ←
!
782key_at_, vL, _, loL
784count_linep_, _, _, _, _, _, _, Ds, _, C0, C ←
foldlinc, Ds, C0, C
785count_target_−vL, H, C0, C ←
incfullL, H, C0, C
786incK, C0, C ←
get_assocK, C0, N
→
N1 is N + 1
else
N1 = 1
put_assocK, C0, N1, C
787decK, C0, C ←
get_assocK, C0, N
N1 is N−1
put_assocK, C0, N1, C
788neededK, C ←
get_assocK, C, N
N > 0
provides+V, +K

register value V gives K directly or by one move.

791providesvL2, _, loL ←
L2 ≡ L
792providesvL2, H2, fullL, H ←
L2 ≡ L
H2 ≡ H
→
true
else
H ≡ const0
793constant_keyloconst_
794constant_keyfullconst_, const_
dispensable+T, +Clobbered, +Cnt, +S

no value T holds is needed (Cnt) unless a register outside Clobbered or a constant also gives it.

798dispensableT, Clob, Cnt, stR, _, _ ←
reg_getT, R, vLo, Hi
¬
held_keyLo, Hi, K
neededK, Cnt
¬
memberG, Gs
¬memberchkG, Clob
reg_getG, R, VG
803held_keyLo, _, loLo
804held_keyLo, Hi, fullLo, Hi
805held_keyLo, Hi, fullLo, const0 ←
Hi ≢ const0
obtain_line+K, +T, +Env, +CS, +S, -Line

a move that puts K in T.

808obtain_lineK, T, Env, CS, stR, _, _, Line ←
memberG, Gs
G ≢ T
reg_getG, R, VG
exactVG, K, Size
→
copy_lineSize, G, T, Line
K = fullL, const0
memberG, Gs
G ≢ T
reg_getG, R, vL2, _
L2 ≡ L
→
copy_linel, G, T, Line
→
materializeimmV, T, Line
key_cellK, Env, CS, D, Size
→
copy_lineSize, memD, rbp, T, Line
key_cell+K, +Env, +CS, -D, -Size

frame cell D holds K. A cell not stored in this region holds its start value, which names the cell; a stored one is in CS.

823key_cellK, env_, Cells, _, CS, D, Size ←
K = loL
or
K = fullL, _
nonvarL
L = incellD, lo, shared
get_assocD, Cells, _
¬get_assocD, CS, _
cell_holdsD, CS, V
cell_givesV, K, Size
!
828key_cellK, _, CS, D, Size ←
assoc_to_listCS, Ps
memberD−V, Ps
cell_givesV, K, Size
!
831exactvL2, H2, fullL, H, q ←
L2 ≡ L
H2 ≡ H
832exactvL2, _, loL, l ←
L2 ≡ L
834cell_holdsD, CS, V ←
get_assocD, CS, V0
→
V = V0
else
V = vincellD, lo, shared, incellD, hi, shared
838cell_givesV, K, q ←
exactV, K, q
!
839cell_givesvL2, _, K, l ←
K = loL
or
K = fullL, const0
L2 ≡ L
841copy_lineq, regG, T, insmov, q, regG, regT ←
!
842copy_lineq, memD, rbp, T, insmov, q, memD, rbp, regT ←
!
843copy_lineq, G, T, insmov, q, regG, regT ←
atomG
!
844copy_linel, memD, rbp, T, insmov, l, memD, rbp, regTN ←
!
registerTN, T, 32
845copy_linel, G, T, insmov, l, regGN, regTN ←
registerGN, G, 32
registerTN, T, 32
847key_immloconstL, L
848key_immfullconstL, constH, V ←
V is H << 32 \/ L
flags_after+Ps0, +Live, -Ps

binds each line's FlagsLive, whether a later line or the region's end reads the flags it leaves.

852flags_afterPs0, Live, Ps ←
reversePs0, Rev
foldlflags_line, Rev, Live, _
Ps = Ps0
856flags_linep_, _, effect_, _, Fl, _, _, _, _, _, _, _, After, After, Before ←
Fl ≡ write
→
Before = 0
Fl ≡ read
→
Before = 128
else
Before = After
emit+Line, +Env, +CS, +S0, -S, ?Out0, ?Out

Line is a move; S is S0 after it.

864emitLine, Env, CS, S0, S, Line|Out, Out ←
stream_moveLine, Env, CS, M
move_stateM, S0, S
resolve+Targets, +Ctx, +CS, +Cnt, +S0, -S, ?Out0, ?Out

moves that give each register F of Targets (F-V) the value V, keeping every value Cnt needs. A cycle goes through a free register.

870resolveTs, Ctx, CS, Cnt, S0, S, Out0, Out ←
resolveTs, Ctx, CS, Cnt, 64, S0, S, Out0, Out
872resolveTs, Ctx, CS, Cnt, N, S0, S, Out0, Out ←
S0 = stR, _, _
findallF−V,
memberF−V, Ts
reg_getF, R, V0
V0 ≢ V
,
Pend
Pend ≡ []
→
S = S0
Out0 = Out
else
N > 0
N1 is N−1
Ctx = ctxUsable, Env, _
foldlcount_target, Pend, Cnt, CntP
memberF−vL, H, Pend
dispensableF, F, CntP, S0
obtain_linefullL, H, F, Env, CS, S0, Line
→
true
Pend = F−_|_
memberT, Usable
¬memberchkT−_, Ts
dispensableT, T, CntP, S0
→
Line = insmov, q, regF, regT
emitLine, Env, CS, S0, S1, Out0, Out1
resolveTs, Ctx, CS, Cnt, N1, S1, S, Out1, Out
891b_walk[], _, SB, Cnt, Out, Out, SB, Cnt
892b_walkP|Ps, Ctx, SB0, Cnt0, Out0, Out, SB, Cnt ←
P = p_, _, _, _, _, _, _, Ds, _
foldldec, Ds, Cnt0, Cnt1
b_lineP, Ctx, SB0, Cnt0, Cnt1, Out0, Out1, SB1
b_walkPs, Ctx, SB1, Cnt1, Out1, Out, SB, Cnt

A fixed line: its registers get A's values, values it overwrites and that are still needed are copied away, then it is A's line.

900b_linepJ, LA, E, fixed, SA, _, CS, _, _, Ctx, SB0, _, Cnt1, Out0, Out, SB ←
Ctx = ctxUsable, Env, _
E = effectRs, Ws, _, _, _
SA = stRA, _, _
findallF,
memberF, Rs
,
Fs1
findallF,
memberwriteF, W, Ws
W < 32
,
Fs2
appendFs1, Fs2, Fs0
sortFs0, Fs
findallF−V,
memberF, Fs
reg_getF, RA, V
,
Need
resolveNeed, Ctx, CS, Cnt1, SB0, SB1, Out0, Out1
findallF,
memberwriteF, _, Ws
,
Cl0
sortCl0, Clob
foldlevictClob, Fs, Usable, Env, CS, Cnt1, Clob, SB1−Out1, SB2−Out2
pairedLA, LA, J, SA, SB2, _, SB
Out2 = LA|Out

A zero idiom: any register that can be overwritten.

915b_linepJ, LA, _, zeroW, SA, _, _, _, _, ctxUsable, _, Targets, SB0, _, Cnt1, Out0, Out, SB ←
LA = insOp, S, regNA, _
registerNA, FA, W
dest_orderFA, const0, Usable, Targets, SB0, Order
memberT, Order
dispensableT, T, Cnt1, SB0
!
registerTN, T, W
LB = insOp, S, regTN, regTN
pairedLA, LB, J, SA, SB0, _, SB
Out0 = LB|Out

A flex line: its destination, then its sources. A two-address destination is, by the first register of dest_order/6 that allows one: a register holding the input (or, for a commutative form, the other input) that can be overwritten, or a lea of an add or a sub of an immediate into it when the flags it leaves are dead; failing all, a register given the input by a move.

929b_linepJ, LA, _, flexRoles, SA, SA1, CS, _, Flags, Ctx, SB00, Cnt0, Cnt1, Out00, Out, SB ←
Ctx = ctxUsable, Env, Targets
LA = insOp, S, OsA
nth1K, Roles, Role
memberchkRole, dst_, srcdst_
nth1K, OsA, regNA
→
registerNA, FA, W
SA1 = stRA1, _, _
reg_getFA, RA1, vLoR, _
dest_orderFA, LoR, Usable, Targets, SB00, Order
make_roomOrder, LoR, Ctx, CS, Cnt1, SB00, SB0, Out00, Out0
SB0 = stRB, _, _
Role = srcdst_
→
SA = stRA, _, _
reg_getFA, RA, VIn
key_atW, VIn, Kin
memberT, Order
dispensableT, T, Cnt1, SB0
two_addressT, RB, Kin, W, LA, SA, Flags, Form
→
SB1 = SB0
Out1 = Out0
memberT, Order
dispensableT, T, Cnt0, SB0
obtain_lineKin, T, Env, CS, SB0, Copy
→
emitCopy, Env, CS, SB0, SB1, Out0, Out1
Form = same
memberT, Order
dispensableT, T, Cnt1, SB0
→
SB1 = SB0
Out1 = Out0
Form = same
registerTN, T, W
Taken0 = T
else
Form = same
SB1 = SB00
Out1 = Out00
Taken0 = []
form_lineForm, LA, SA, Roles, TN, K, Op, S, LA1, Roles1, Dest
LA1 = insOp1, S1, OsA1
sourcesRoles1, OsA1, 1, Dest, Op1, SA, Ctx, CS, Cnt0, Taken0, SB1, SB2, Out1, Out2, OsB
LB = insOp1, S1, OsB
pairedLA, LB, J, SA, SB2, _, SB
Out2 = LB|Out
two_address+T, +RB, +Kin, +W, +LA, +SA, +Flags, -Form

T can take LA's result without a move: it holds the input (same), the other input of a commutative form (swap), or a lea computes it (lea).

964two_addressT, RB, Kin, _, _, _, _, same ←
reg_getT, RB, VT
exactVT, Kin, _
!
966two_addressT, RB, _, W, insOp, S, regX, _, stRA, _, _, _, swap ←
memberchkOp, add, and, or, xor, imul
memberchkS, l, q
registerX, FX, W
reg_getFX, RA, VX
key_atW, VX, Kx
reg_getT, RB, VT
exactVT, Kx, _
!
970two_address_, _, _, _, insOp, S, Src, _, _, 0, lea ←
memberchkS, l, q
Op ≡ add
Src = reg_
or
Src = immD
integerD
D ≥ −1 << 31
D < 1 << 31
or
Op ≡ sub
Src = immD
integerD
D > −1 << 31
D ≤ 1 << 31
form_line+Form, +LA, +Roles, +TN, +K, +Op, +S, -LA1, -Roles1, -Dest

the line whose operands sources/15 finds in B, and where the destination goes. A swap reads A's second operand first; a lea reads A's two addends as an address.

980form_linesame, LA, _, Roles, TN, K, _, _, LA, Roles, Dest ←
varTN
→
Dest = none
else
Dest = K−regTN
982form_lineswap, insOp, S, X, Y, _, Roles, TN, K, _, _, insOp, S, Y, X, Roles, K−regTN

A register addend that holds a constant becomes the displacement.

984form_linelea, insadd, S, regX, regY, SA, _, TN, _, _, _, inslea, S, memD, Y, regTN, leaW, dstW, 2−regTN ←
memberchkS−W, l−32, q−64
registerX, FX, W
read_valueFX, W, SA, V
imm_ofV, W, D
!
988form_linelea, insadd, S, regX, regY, _, _, TN, _, _, _, inslea, S, mem0, X, Y, regTN, leaW, dstW, 2−regTN ←
!
memberchkS−W, l−32, q−64
991form_linelea, insadd, S, immD, regY, _, _, TN, _, _, _, inslea, S, memD, Y, regTN, leaW, dstW, 2−regTN ←
!
memberchkS−W, l−32, q−64
994form_linelea, inssub, S, immD, regY, _, _, TN, _, _, _, inslea, S, memND, Y, regTN, leaW, dstW, 2−regTN ←
ND is −D
memberchkS−W, l−32, q−64
dest_order+FA, +LoR, +Usable, +Targets, +S, -Order

where a result goes: the register it is needed in at the region's end, A's register, then the others; registers that already hold their end value last.

1001dest_orderFA, LoR, Usable, Targets, stR, _, _, Order ←
findallG,
memberG−vL, _, Targets
L ≡ LoR
,
Homes
appendHomes, FA, Gs, All0
includeusableUsable, All0, All1
list_to_setAll1, All
partitionholds_endTargets, R, All, Done, Free
appendFree, Done, Order
1009usableUsable, G ←
memberchkG, Usable
1010holds_endTargets, R, G ←
memberchkG−V, Targets
reg_getG, R, V0
V0 ≡ V
sources...

each operand of B. A register source reads a register holding A's value, else an immediate, else a register given the value by a move; an address's registers likewise hold A's whole values.

1015sources[], [], _, _, _, _, _, _, _, _, S, S, Out, Out, []
1016sourcesRole|Roles, OA|OsA, K, Dest, Op, SA, Ctx, CS, Cnt, Taken0, S0, S, Out0, Out, OB|OsB ←
Dest = K−OB0
→
OB = OB0
S1 = S0
Out1 = Out0
Taken1 = Taken0
Role = leaW
OA = memD, X
→
lea_sourceX, W, SA, Ctx, CS, Cnt, Taken0, S0, S1, Out0, Out1, G, Taken1
OB = memD, G
Role = leaW
OA = memD, X, Y
→
lea_sourceX, W, SA, Ctx, CS, Cnt, Taken0, S0, S2, Out0, Out2, G1, Taken2
lea_sourceY, W, SA, Ctx, CS, Cnt, Taken2, S2, S1, Out2, Out1, G2, Taken1
OB = memD, G1, G2
OA = regN
memberchkRole, src_
→
registerN, FA, W
read_valueFA, W, SA, VA
source_registerFA, W, VA, K, Op, SA, Ctx, CS, Cnt, Taken0, S0, S1, Out0, Out1, OB, Taken1
OA = memD, B
→
base_registerB, SA, Ctx, CS, Cnt, Taken0, S0, S1, Out0, Out1, B1, Taken1
OB = memD, B1
OA = memD, B, I
→
base_registerB, SA, Ctx, CS, Cnt, Taken0, S0, S2, Out0, Out2, B1, Taken2
base_registerI, SA, Ctx, CS, Cnt, Taken2, S2, S1, Out2, Out1, I1, Taken1
OB = memD, B1, I1
else
OB = OA
S1 = S0
Out1 = Out0
Taken1 = Taken0
K1 is K + 1
sourcesRoles, OsA, K1, Dest, Op, SA, Ctx, CS, Cnt, Taken1, S1, S, Out1, Out, OsB
1041source_registerFA, W, VA, K, Op, SA, Ctx, CS, Cnt, Taken, S0, S, Out0, Out, OB, G|Taken ←
memberG, FA|Gs
read_valueG, W, S0, VB
VB ≡ VA
→
registerGN, G, W
OB = regGN
S = S0
Out = Out0
K =:= 1
memberchkOp, add, sub, and, or, xor, imul, cmp, test, mov
imm_ofVA, W, I
→
OB = immI
S = S0
Out = Out0
G = none
SA = stRA, _, _
reg_getFA, RA, V
key_atW, V, Key0
W ≤ 32
→
Key = Key0
else
Key = Key0
Ctx = ctxUsable, Env, _
memberG, Usable
¬memberchkG, Taken
dispensableG, G, Cnt, S0
obtain_lineKey, G, Env, CS, S0, Line
→
emitLine, Env, CS, S0, S, Out0, Out
registerGN, G, W
OB = regGN
1057imm_ofconstL, constH, 64, I ←
!
V is H << 32 \/ L
imm_forV, 64, I
1058imm_ofconstV, W, I ←
imm_forV, W, I
1060base_registerB, SA, Ctx, CS, Cnt, Taken, S0, S, Out0, Out, B1, G|Taken ←
registerB, FA, 64
memberchkFA, rsp, rbp
→
G = FA
B1 = B
S = S0
Out = Out0
else
SA = stRA, _, _
reg_getFA, RA, VA
S0 = stRB, _, _
memberG, FA|Gs
reg_getG, RB, VB
VB ≡ VA
→
B1 = G
S = S0
Out = Out0
Ctx = ctxUsable, Env, _
VA = vL, H
memberG, Usable
¬memberchkG, Taken
dispensableG, G, Cnt, S0
obtain_linefullL, H, G, Env, CS, S0, Line
→
emitLine, Env, CS, S0, S, Out0, Out
B1 = G
1074evictClob, Reads, Usable, Env, CS, Cnt, F, S0−Out0, S−Out ←
dispensableF, Clob, Cnt, S0
→
S = S0
Out = Out0
memberT, Usable
¬memberchkT, Clob
¬memberchkT, Reads
dispensableT, T, Cnt, S0
→
emitinsmov, q, regF, regT, Env, CS, S0, S, Out0, Out
lea_source+N, +W, ...

a 64-bit register whose low W bits are A's register N's, for a lea that reads them as an address.

1084lea_sourceN, W, SA, Ctx, CS, Cnt, Taken, S0, S, Out0, Out, G, G|Taken ←
registerN, FA, _
read_valueFA, W, SA, VA
memberG, FA|Gs
read_valueG, W, S0, VB
VB ≡ VA
→
S = S0
Out = Out0
SA = stRA, _, _
reg_getFA, RA, V
key_atW, V, Key
Ctx = ctxUsable, Env, _
memberG, Usable
¬memberchkG, Taken
dispensableG, G, Cnt, S0
obtain_lineKey, G, Env, CS, S0, Line
→
emitLine, Env, CS, S0, S, Out0, Out
make_room+Order, +LoR, +Ctx, +CS, +Cnt, +S0, -S, ?Out0, ?Out

when the first register of Order is where the result LoR is needed at the region's end but still holds a needed value, that value moves on to the register it is needed in at the end, if that one is free or can be freed the same way (at most three deep). Otherwise nothing changes.

1100make_roomT|_, LoR, Ctx, CS, Cnt, S0, S, Out0, Out ←
Ctx = ctx_, _, Targets
memberT−vL, _, Targets
L ≡ LoR
¬dispensableT, T, Cnt, S0
roomT, 3, T, Ctx, CS, Cnt, S0, S1, Out0, Out1
dispensableT, T, Cnt, S1
!
S = S1
Out = Out1
1107make_room_, _, _, _, _, S, S, Out, Out
1109roomT, N, Seen, Ctx, CS, Cnt, S0, S, Out0, Out ←
N > 0
Ctx = ctxUsable, Env, Targets
S0 = stR, _, _
reg_getT, R, VT
memberG−VG, Targets
VG ≡ VT
G ≢ T
¬memberchkG, Seen
memberchkG, Usable
dispensableG, G, Cnt, S0
→
S1 = S0
Out1 = Out0
else
N1 is N−1
roomG, N1, G|Seen, Ctx, CS, Cnt, S0, S1, Out0, Out1
dispensableG, G, Cnt, S1
!
emitinsmov, q, regT, regG, Env, CS, S1, S, Out1, Out