Fil-C on Nix

Filnix wiki

mbrock/filnix/nanofil

promote.pl

Promotion of memory slots to SSA temporaries, by promote_module/2 and promote_func/3. Prior art: QBE's promote and ssa passes, LLVM's mem2reg.

Which allocs

A slot is one fixed offset and width, accOff, Width, of an alloc that escape.pl calls local. An alloc is promoted, all its slots at once, when:

  • it is in any block, its every execution a fresh zeroed object: the alloc itself sets each slot to 0, and its block is a definition block for phi placement. Strict SSA makes every access go through the latest execution's address (an inlined callee's local, perf.md item 4, in a caller's loop is an alloc in a middle block);
  • its size is a constant and every slot lies inside it, so no access could trap or be undefined; in filc mode the size is at most 2^48 - 1, so the alloc itself could not trap too_big (interp:alloc_res/6), which removing it would hide;
  • its slots do not overlap and each offset has one width, so every load reads exactly the bytes one earlier store wrote, except for punned reads: a b or h load, at an access never stored, inside a slot of width h or w (a union read through a narrower member, lz4's LZ4_isLittleEndian). It reads the slot's value shifted right by 8 times its byte offset (little-endian), then extended as any narrow load. l and p slots are never punned;
  • in filc mode, its p slots are 8-aligned (allocs are 16-aligned), so no loadp or storep, V, A could trap as misaligned;
  • its float (s or d) slots are read only after a store to them on every path from the alloc (float_roots_stored/4): a slot read where the CFG, not the program, allows its initial zero would carry phis round a loop (Lua's luaV_execute).

An l access that another access overlaps (a struct of words copied as a long, read as words) is first split into two w accesses, the low word at its offset (split_words/8), when the alloc then promotes. The memory read and written is the same either way.

The function must also be in strict SSA form: each temp defined once, every definition dominating its uses. Otherwise a stored temp could be redefined before the load that promote would turn into its copy. A function that is not, or that calls setjmp, is left alone.

The rewrite

Loads become copies or extensions of the slot's current value, and stores disappear. Phis are placed on the iterated dominance frontier of the entry and the reachable blocks that store to the slot or hold its alloc, where the slot is live (pruned SSA, Cytron et al.); their temps are named pr.N. After its alloc and before any store a slot holds 0: Fil-C allocations are zeroed, and in plain mode the bytes are unknown, so 0 is one of the values they could have. Unreachable blocks read 0 too.

Values keep the class of the store: a storeb, V, A of a w value keeps the whole value, and each load narrows or extends it again, exactly as the bytes would.

A load ofBecomes, on the slot's value
l, p, s or dcopy
sw or uw, at class wcopy
sw or uw, at class lextsw or extuw
sh, uh, sb or ubthe extension of that type, such as extsb

A slot's phi has class w for widths b, h and w, and the slot's width, l, p, s or d, otherwise. A float slot's 0 is the bit pattern of +0.0, which is what a zeroed object reads.

useslists
usesassoc
usesordsets
usespairs
usesdcgs
usesir
usescfg
usesescape
promote_module+M0, -Mdet

M is the module M0 with the promotable allocs of each function promoted.

90promote_moduleM0, moduleItems ←
legacy_contextM0, moduleItems0
module_modemoduleItems0, Mode
code_itemsItems0, Code
context_indexItems0, Contexts
maplistpromote_itemMode, Contexts, Code, Groups
appendGroups, Items
promote_item+Mode, +Item0, -Item

Item is the module item Item0, promoted if it is a function. The cuts: the item's clause is not indexed (Mode comes first), and a choicepoint per function or data item would pin the module's trail until the pass ends (SQLite's 2,376 data items overflowed 1 GB).

102promote_itemMode, All, F0, Items ←
F0 = funcName, _, _, _, _
!
context_recordsAll, Name, Rs0
promote_contextMode, F0, Rs0, F, Rs
with_contextF, Rs, Items
106promote_item_, _, dataN, L, A, Is, dataN, L, A, Is ←
!
107promote_item_, _, aliasN, L, T, aliasN, L, T
alloc_count+M, -Ndet

N is the number of alloc instructions in the functions of module M.

113alloc_countmoduleItems, N ←
findallx,
memberfunc_, _, _, _, Blocks, Items
memberblock_, _, Insts, _, Blocks
memberlet_, _, alloc_, _, Insts
,
Allocs
lengthAllocs, N
promote_func+Mode, +F0, -Fdet

F is the function F0 with its promotable allocs promoted, or F0 if it has none or is not in strict SSA form. Mode is module_mode/2 of the module being promoted.

127promote_funcMode, F0, F ←
promote_contextMode, F0, [], F, _
promote_context+Mode, +F0, +Records0, -F, -Recordsdet

As promote_func/3, carrying F0's context Records0 along (provenance.pl): the removed loads and stores' values become aliases of the slot's value, each slot's phis get generated origins citing its declaration and stores, and instruction sites are re-indexed. A setjmp function is left alone.

136promote_context_, F, Rs, F, Rs ←
137promote_contextMode, F00, Rs00, F, Rs ←
promotableMode, F00, Roots0, An0
An alloc whose l accesses w accesses overlap promotes once those l
accesses are split into words (split_words/8).
An0 = anClasses, Addrs0, Counts0
split_rootsMode, F00, Roots0, Classes, Addrs0, Splits
Splits ≢ []
F00 = func_, _, _, _, Blocks00
func_cfgBlocks00, Cfg0
cfg_idomsCfg0, Idom0
strict_ssaF00, Counts0, Cfg0, Idom0
→
split_wordsMode, Splits, Addrs0, Counts0, F00, Rs00, F01, Rs01
promotableMode, F01, Roots1, An1
else
F01 = F00
The split stays only if every split alloc then promotes: an alloc
left in memory would take two checked accesses for each one.
F01 ≢ F00
forallmemberA−_, Splits, memberchkA−_, Roots1
→
F0 = F01
Rs0 = Rs01
Roots = Roots1
An = An1
else
F0 = F00
Rs0 = Rs00
Roots = Roots0
An = An0
F0 = funcName, Linkage, Ret, Params, Blocks0
Most functions have nothing to promote: look before building the
dominator tree.
An = an_, AllAddrs, Counts
Roots ≢ []
func_cfgBlocks0, Cfg
cfg_idomsCfg, Idom
strict_ssaF0, Counts, Cfg, Idom
→
promote_rootsRoots, F0, AllAddrs, Counts, Cfg, Idom, Blocks, Map, Placed, Addrs, Widths
F = funcName, Linkage, Ret, Params, Blocks
remap_sitesRs0, Map, Rs1
promoted_contextRs0, Rs1, Blocks0, Placed, Addrs, Widths, Rs
else
F = F0
Rs = Rs0

Word splitting

split_roots+Mode, +F, +Roots, +Classes, +Addrs, -Splits

Splits is A-Offs for each local alloc A that Roots lacks and that would promote if each of its l accesses at an offset of Offs, which another access overlaps, were two w accesses, at the offset and 4 past it (a struct of words copied as a long and read as words: lz4's LZ4HC_match_t).

179split_rootsMode, F, Roots, Classes, Addrs, Splits ←
findallA−Size−Accs,
memberA−localconstSize, Accs, Classes
¬memberchkA−_, Roots
memberchkacc_, l, Accs
,
Cands
Cands ≢ []
stored_accsF, Addrs, Stored
split_values_okF, Addrs, Bad
findallA−Offs,
memberA−Size−Accs, Cands
¬memberchkA, Bad
split_offsetsAccs, Offs
Offs ≢ []
split_accsOffs, Accs, Accs1
get_assocA, Stored, Ws0
→
split_accsOffs, Ws0, Ws
else
Ws = []
punned_slotsAccs1, Ws, Slots
slots_okMode, Size, Slots
,
Splits
split_values_ok+F, +Addrs, -Bad

Bad lists the allocs that an l store of a value other than a temp or a constant writes (in plain mode a global(G) is read only at l, so its low word cannot be stored at w).

199split_values_okfunc_, _, _, _, Blocks, Addrs, Bad ←
findallA,
memberblock_, _, Is, _, Blocks
memberstorel, V, tmpX, Is
V ≠ tmp_
V ≠ const_
get_assocX, Addrs, A−_
,
Bad0
sortBad0, Bad
204split_offsetsAccs, Offs ←
findallO,
memberaccO, l, Accs
memberOther, Accs
Other ≢ accO, l
accs_overlapaccO, l, Other
,
Os
sortOs, Offs
209accs_overlapaccO1, W1, accO2, W2 ←
O1 < O2 + B2
O2 < O1 + B1
213split_accsOffs, Accs0, Accs ←
findallAcc,
memberAcc0, Accs0
Acc0 = accO, l
memberchkO, Offs
→
Acc = accO, w
or
O4 is O + 4
Acc = accO4, w
else
Acc = Acc0
,
Accs1
sortAccs1, Accs
split_words+Mode, +Splits, +Addrs, +Counts, +F0, +Rs0, -F, -Rs

F is F0 with each l access of Splits split, little-endian: a store of V stores V's low word (a w store reads V at w) and then V shifted right by 32 at 4 past it; a load reads both words unsigned and joins them. The memory written and read is the same, so F is F0 whether or not promotion follows. New temps are sw.N, fresh against Counts.

229split_wordsMode, Splits, Addrs, Counts, funcN, L, R, Ps, Bs0, Rs0, funcN, L, R, Ps, Bs, Rs ←
Mode ≡ filc
→
K = p
else
K = l
foldlsplit_blockspSplits, Addrs, Counts, K, Bs0, Bs, Maps, 0, _
appendMaps, Map
remap_sitesRs0, Map, Rs
236split_blockEnv, blockL, Phis, Is0, J, blockL, Phis, Is, J, Map, N0, N ←
split_instsIs0, Env, L, 0, 0, Is, Map, N0, N
239split_insts[], _, _, _, _, [], [], N, N
240split_instsI|Is0, Env, L, Old, New, Is, Map, N0, N ←
split_instI, Env, Out, N0, N1
appendOut, Rest, Is
emission_sitesL, Old, New, Out, Map, Maps, Next
Old1 is Old + 1
split_instsIs0, Env, L, Old1, Next, Rest, Maps, N1, N
246split_instI, spSplits, Addrs, Counts, K, Out, N0, N ←
I = storel, V, tmpX
split_addressX, Splits, Addrs
→
fresh_tempsCounts, H, X4, N0, N
Out = lettmpH, l, shr, V, const32, storew, V, tmpX, lettmpX4, K, add, tmpX, const4, storew, tmpH, tmpX4
I = letD, l, loadl, tmpX
split_addressX, Splits, Addrs
→
fresh_tempsCounts, Lo, X4, Hi, Hs, N0, N
Out = lettmpLo, l, loaduw, tmpX, lettmpX4, K, add, tmpX, const4, lettmpHi, l, loaduw, tmpX4, lettmpHs, l, shl, tmpHi, const32, letD, l, or, tmpHs, tmpLo
else
Out = I
N = N0
263split_addressX, Splits, Addrs ←
get_assocX, Addrs, A−O
memberchkA−Offs, Splits
memberchkO, Offs
fresh_temps+Counts, -Names, +N0, -N

Names are sw.K for the next K from N0 that Counts does not define.

268fresh_temps_, [], N, N
269fresh_tempsCounts, T|Ts, N0, N ←
formatatomT0, 'sw.~w', N0
N1 is N0 + 1
get_assocT0, Counts, _
→
fresh_tempsCounts, T|Ts, N1, N
else
T = T0
fresh_tempsCounts, Ts, N1, N
293slot_parentsSlot−Lists, Slot−Parents ←
appendLists, Os
sortOs, Parents

A single shared summary per slot avoids repeating all stores for every phi. These are conservative contributors, not reaching-definition facts.

297slot_records[], _, _, Ids, Ids, [], []
298slot_recordsA−Off|Slots, Values, Stores, Ids0, Ids, A−Off−Summary|Pairs, originInitial, none, generatedpromote, initial_zero, AllocOs, originSummary, none, generatedpromote, slot_storesA, Off, Initial|Parents|Rs ←
get_assocA, Values, AllocOs
→
true
else
AllocOs = []
get_assocA−Off, Stores, Parents
→
true
else
Parents = []
fresh_origininitialA, Off, Ids0, Ids1, Initial
fresh_originstoresA, Off, Ids1, Ids2, Summary
slot_recordsSlots, Values, Stores, Ids2, Ids, Pairs, Rs
307phi_records[], _, _, _, Ids, Ids, []
308phi_records_−A−Off−T|Placed, Bindings, Widths, Summaries, Ids0, Ids, originId, none, generatedpromote, slotA, Off, Width, Summary, valueT, Hint, Id, sitephiT, Id|Rs ←
get_assocA−Off, Widths, Width
get_assocA−Off, Summaries, Summary
Off =:= 0
get_assocA, Bindings, Type
type_slotType, Width
→
Hint = namedA, value
else
Hint = none
fresh_originpromoteT, Ids0, Ids1, Id
phi_recordsPlaced, Bindings, Widths, Summaries, Ids1, Ids, Rs

Only a scalar whole-object slot inherits its declaration's name.

318type_slotqualified_, T, W ←
319type_slotT, W ←
memberchkT−W, char−b, schar−b, uchar−b, bool−b, short−h, ushort−h, int−w, uint−w, long−l, ulong−l, llong−l, ullong−l
321type_slotptr_, p
promotable+Mode, +F, -Rootsdet

Roots is [A-Slots] for the allocs A of F that can be promoted, in order, Slots an ordset of acc(Off, Width).

328promotableMode, F, Roots ←
promotableMode, F, Roots, _
promotable+Mode, +F, -Roots, -Andet

As promotable/3, with An = an(Classes, Addrs, Counts): F's alloc_classes/2, address_temps/2 and def_counts/2, computed once for escape analysis and the rewrite, or an([], none, none) when F has no alloc or calls setjmp.

336promotable_, F, [], an[], none, none ←

Without an alloc, skip escape analysis.

338promotable_, func_, _, _, _, Blocks, [], an[], none, none ←
¬
memberblock_, _, Insts, _, Blocks
memberchklet_, _, alloc_, _, Insts
!
341promotableMode, F, Roots, anClasses, Addrs, Counts ←
def_countsF, Counts
alloc_classesF, Addrs, Counts, Classes
stored_accsF, Addrs, Stored
findallA−Slots,
memberA−localconstSize, Accs, Classes
get_assocA, Stored, Ws
→
true
else
Ws = []
punned_slotsAccs, Ws, Slots
slots_okMode, Size, Slots
,
Roots0
float_roots_storedF, Addrs, Roots0, Roots
float_roots_stored+F, +Addrs, +Roots0, -Roots

Roots is Roots0 less the allocs with a float (s or d) slot that a load may read before any store to it, along some path from its alloc. Promotion reads such a slot's initial zero through phis, and a path the program never takes (a load guarded by the test that a store happened, as in Lua's op_arithf) would carry the slot round a loop: luaV_execute took 166 d phis at its dispatch and doubled in size. Integer slots are promoted as before.

362float_roots_storedF, Addrs, Roots0, Roots ←
member_−Slots, Roots0
memberacc_, W, Slots
→
F = func_, _, _, _, Blocks
func_cfgBlocks, Cfg
blocks_by_labelBlocks, ByLabel
includefloat_slots_storedCfg, ByLabel, Addrs, Roots0, Roots
else
Roots = Roots0
371float_widths
372float_widthd
float_slots_stored+Cfg, +ByLabel, +Addrs, +A−Slots

every load of a float slot of A, in a reachable block, follows a store to that slot on every path from the entry: a forward must analysis whose state is the ordset of A's float offsets stored, emptied by A's alloc.

378float_slots_storedCfg, ByLabel, Addrs, A−Slots ←
findallO,
memberaccO, W, Slots
,
Os0
sortOs0, Os
Os ≡ []
→
true
else
Cfg = cfgEntry, Rpo, _, Preds
empty_assocOut0
stored_fixpointRpo, Entry, Preds, ByLabel, Addrs, A−Os, Out0, Out
forallmemberL, Rpo,
block_inL, Entry, Preds, Out, In
get_assocL, ByLabel, block_, _, Is, _
foldlstored_checkedAddrs, A−Os, Is, In, _
390stored_fixpointRpo, Entry, Preds, ByLabel, Addrs, AOs, Out0, Out ←
foldlstored_blockEntry, Preds, ByLabel, Addrs, AOs, Rpo, Out0−same, Out1−Changed
Changed ≡ same
→
Out = Out1
else
stored_fixpointRpo, Entry, Preds, ByLabel, Addrs, AOs, Out1, Out
396stored_blockEntry, Preds, ByLabel, Addrs, AOs, L, Out0−C0, Out−C ←
block_inL, Entry, Preds, Out0, In
get_assocL, ByLabel, block_, _, Is, _
foldlstored_transferAddrs, AOs, Is, In, New
get_assocL, Out0, Old
Old ≡ New
→
Out = Out0
C = C0
else
put_assocL, Out0, New, Out
C = changed
block_in+L, +Entry, +Preds, +Out, -In

the offsets stored on every path into L, over the predecessors analysed so far (an unanalysed one is optimistic: it constrains nothing until it is).

407block_inEntry, Entry, _, _, [] ←
!
408block_inL, _, Preds, Out, In ←
get_assocL, Preds, Ps
findallS,
memberP, Ps
get_assocP, Out, S
,
Ss
Ss = S0|Rest
→
foldlord_intersection_, Rest, S0, In
else
In = []
413ord_intersection_S, Acc0, Acc ←
ord_intersectionAcc0, S, Acc
415stored_transferAddrs, A−Os, I, S0, S ←
I = lettmpA, _, alloc_, _
→
S = []
I = store_, _, tmpX
get_assocX, Addrs, A−O
ord_memberchkO, Os
→
ord_add_elementS0, O, S
else
S = S0
422stored_checkedAddrs, A−Os, I, S0, S ←
I = let_, _, load_, tmpX
get_assocX, Addrs, A−O
ord_memberchkO, Os
→
ord_memberchkO, S0
else
true
stored_transferAddrs, A−Os, I, S0, S
stored_accs+F, +Addrs, -Stored

Stored maps each alloc to the accesses that store to it.

431stored_accsF, Addrs, Stored ←
F = func_, _, _, _, Blocks
findallA−accOff, W,
memberblock_, _, Is, _, Blocks
memberstoreW, _, tmpX, Is
get_assocX, Addrs, A−Off
,
Ps0
keysortPs0, Ps1
group_pairs_by_keyPs1, Ps
list_to_assocPs, Stored
punned_slots+Accs, +Stored, -Slots

Slots are the accesses of Accs that no other one contains. Each other access is a punned read: a load of b or h, never stored, inside a slot of width h or w (a union read through a narrower member; its bytes are a shift and an extension of the slot's value, little-endian). Fails otherwise.

445punned_slotsAccs, Stored, Slots ←
partitioninner_accAccs, Accs, Inner, Slots
forallmemberAcc, Inner, punned_readAcc, Slots, Stored
449inner_accAccs, Acc ←
memberOuter, Accs
Outer ≢ Acc
acc_insideAcc, Outer
!
451acc_insideaccO, W, accSO, SW ←
SO ≤ O
O + B ≤ SO + SB
455punned_readaccO, W, Slots, Stored ←
memberchkW, b, h
¬memberchkaccO, W, Stored
memberaccSO, SW, Slots
acc_insideaccO, W, accSO, SW
!
memberchkSW, h, w
461slots_okMode, Size, Accs ←
size_allocatesMode, Size
maplistslot_okMode, Size, Accs
size_allocates+Mode, +Size

an alloc of Size bytes does not trap too_big: Fil-C's limit is 2^48 - 1 bytes (FILC_MAX_ALLOCATION_SIZE, interp:alloc_res/6). A negative constant is a size of 2^64 or more, read at l. Plain mode has none.

470size_allocatesplain, _
471size_allocatesfilc, Size ←
Size ≥ 0
Size ≤ 281474976710655
473slot_okMode, Size, accOff, Width ←
width_bytesWidth, Bytes
Off ≥ 0
Off + Bytes ≤ Size
width_mode_alignedWidth, Mode, Off
width_mode_aligned+Width, +Mode, +Off

a slot of Width at Off is aligned enough in Mode: only p slots in filc mode need 8.

481width_mode_alignedb, _, _
482width_mode_alignedh, _, _
483width_mode_alignedw, _, _
484width_mode_alignedl, _, _
485width_mode_aligneds, _, _
486width_mode_alignedd, _, _
487width_mode_alignedp, Mode, Off ←
490mode_pointer_alignedplain, _
491mode_pointer_alignedfilc, Off ←
Off mod 8 =:= 0
slots_disjoint+Accs

no two of Accs overlap. Accs is sorted by offset, so it is enough to compare neighbours. Two widths at one offset overlap too.

497slots_disjoint[]
498slots_disjointAcc|Accs ←
slots_disjoint_+Accs, +Acc

Acc, then Accs, do not overlap.

502slots_disjoint_[], _
503slots_disjoint_accOff2, Width2|Accs, accOff1, Width1 ←
width_bytesWidth1, Bytes1
Off1 + Bytes1 ≤ Off2
slots_disjoint_Accs, accOff2, Width2
strict_ssa+F, +Cfg, +Idomsemidet

F is in strict SSA form: every temp has one definition, and in the blocks reachable from the entry each definition dominates each use (a phi argument is used at the end of its predecessor). Cfg and Idom are those of F.

515strict_ssaF, Cfg, Idom ←
def_countsF, Counts
strict_ssaF, Counts, Cfg, Idom
strict_ssa+F, +Counts, +Cfg, +Idomsemidet

As strict_ssa/3, given F's def_counts/2.

522strict_ssaF, Counts, cfg_, Rpo, _, _, Idom ←
¬
gen_assoc_, Counts, N
N > 1
F = func_, _, _, Params, Blocks
Rpo = Entry|_
def(Label, Pos): defined at position Pos of block Label (0 for
parameters and phis).
findallT−defEntry, 0, memberparam_, tmpT, Params, ParamDefs
findallT−defL, Pos,
memberblockL, Phis, Insts, _, Blocks
memberphitmpT, _, _, Phis
Pos = 0
or
nth1Pos, Insts, Ins
inst_defIns, T
,
BlockDefs
appendParamDefs, BlockDefs, DefPairs
list_to_assocDefPairs, Defs
findallL−t, memberL, Rpo, ReachPairs
list_to_assocReachPairs, Reach
dom_intervalscfgEntry, Rpo, x, x, Idom, Intervals
Collect the uses first: a goal that mentions the big assocs, run by
forall/2 once per block, copies them every time.
findallL−Phis−Insts−Jump, memberblockL, Phis, Insts, Jump, Blocks, Parts
includeblock_reachedReach, Parts, Reached
findallU,
memberB, Reached
,
Uses
includeuse_reachedReach, Uses, Checked
maplistdefined_atDefs, Intervals, Checked
549block_reachedReach, L−_−_−_ ←
get_assocL, Reach, _
552use_reachedReach, use_, L, _ ←
get_assocL, Reach, _
block_use+Block, -Use

Block, given as L-Phis-Insts-Jump, reads a temp as Use = use(T, L, At): T is read in block L at At, which is at(Pos) for position Pos, or end for the end of the block; a phi argument is read at the end of its predecessor.

559block_use_−Phis−_−_, useT, P, end ←
memberphi_, _, Args, Phis
memberP−tmpT, Args
562block_useL−_−Insts−_, useT, L, atPos ←
nth1Pos, Insts, Ins
inst_usesIns, Us
memberT, Us
566block_useL−_−Insts−Jump, useT, L, atJumpPos ←
jump_usesJump, Us
memberT, Us
lengthInsts, N
JumpPos is N + 1
defined_at+Defs, +Intervals, +Use

the temp of Use = use(T, L, At) is defined before At in block L, or in a block that dominates L.

574defined_atDefs, Intervals, useT, L, At ←
get_assocT, Defs, defDefL, DefPos
DefL ≡ L
→
at_afterAt, DefPos
else
dominates_by_intervalIntervals, DefL, L
at_after+At, +Pos

At (at(N) or end) is after position Pos.

582at_afterend, _
583at_afteratN, Pos ←
Pos < N

Rewriting

promote_roots+Roots, +F0, +Addrs, +Counts, +Cfg, +Idom, -Blocks

Blocks are the blocks of F0 with every slot of Roots promoted; Addrs and Counts are F0's address_temps/2 and def_counts/2.

591promote_rootsRoots, F0, AllAddrs, Counts, Cfg, Idom, Blocks, Map, Placed0, Addrs, Widths ←
F0 = func_, _, _, _, Blocks0
Only addresses of promoted allocs matter from here on.
A punned read's address names its slot; Shifts holds its byte
offset in the slot when that is not 0.
findallX−A−SOff−Shift,
gen_assocX, AllAddrs, A−Off
memberchkA−Slots, Roots
memberaccSOff, SW, Slots
SOff ≤ Off
Off < SOff + SB
→
Shift is Off−SOff
else
SOff = Off
Shift = 0
,
AddrShifts
findallX−Var, memberX−Var−_, AddrShifts, AddrPairs
list_to_assocAddrPairs, Addrs
findallX−Shift,
memberX−_−Shift, AddrShifts
Shift ≠ 0
,
ShiftPairs
list_to_assocShiftPairs, Shifts
A slot (Var) is A-Off; Widths maps it to its access width.
findallA−Off−Width,
memberA−Accs, Roots
memberaccOff, Width, Accs
,
VarWidths
list_to_assocVarWidths, Widths
pairs_keysVarWidths, Vars
Liveness of slots: a load reads one, a store writes it.
An alloc writes 0 to each of its slots.
findallA−AVars,
memberA−Accs, Roots
alloc_varsA, Accs, AVars
,
AllocVarPairs
list_to_assocAllocVarPairs, AllocVars
findallL−GenKill,
memberB, Blocks0
B = blockL, _, _, _
slot_gen_killB, Addrs, AllocVars, GenKill
,
GenKillPairs
list_to_assocGenKillPairs, GenKill
empty_assocNoEdges
livenessCfg, GenKill, NoEdges, LiveIn, _
cfg_frontiersCfg, Idom, DF
Cfg = cfgEntry, _, Succs, _
assoc_to_keysCounts, Used
Index the reachable stores once, rather than scanning all blocks
and instructions again for every slot.
findallVar−L,
memberblockL, _, Insts, _, Blocks0
get_assocL, DF, _
memberstore_, _, tmpX, Insts
get_assocX, Addrs, Var
or
memberlettmpA, _, alloc_, _, Insts
get_assocA, AllocVars, AVs
memberVar, AVs
,
Stores0
keysortStores0, Stores1
group_pairs_by_keyStores1, StorePairs
list_to_assocStorePairs, Stores
maplistslot_phi_labelsStores, DF, LiveIn, Entry, Vars, PhiLabels
phraseslots_phisVars, PhiLabels, Phis
The semicontext of phis_temps//3 is the last fresh temp number.
phrasephis_tempsPhis, Used, Placed0, 0, _
reversePlaced0, Placed1
keysortPlaced1, Placed2
group_pairs_by_keyPlaced2, PlacedLabel -> [Var-Temp]
list_to_assocPlaced, PhiAt
findallVar−const0, memberVar, Vars, Zeros
list_to_assocZeros, Zero
blocks_by_labelBlocks0, ByLabel
dom_childrenCfg, Idom, Kids
Env = envAddrs−AllocVars−Shifts, PhiAt, ByLabel, Kids, Succs
Unreachable blocks never run; their slot reads see 0 so that they
stay valid, and they still give each successor's phis an argument.
Cfg = cfg_, Rpo, _, _
findallL,
memberblockL, _, _, _, Blocks0
¬memberchkL, Rpo
,
Dead
phrasesubtree_renamedEntry, Zero, Env, Renamed, Unreached
phraseblocks_renamedDead, Zero, Env, Unreached
maplistrenamed_block_args, Renamed, New, PhiArgss, Maps
appendMaps, Map
appendPhiArgss, PhiArgs0
list_to_assocNew, NewBlocks
keysortPhiArgs0, PhiArgs1
group_pairs_by_keyPhiArgs1, PhiArgs(Label-Var) -> [Pred-Value]
list_to_assocPhiArgs, ArgsAt
maplistpromoted_blockNewBlocks, PhiAt, ArgsAt, Widths, Blocks0, Blocks
672alloc_varsA, Accs, Vars ←
findallA−Off, memberaccOff, _, Accs, Vars
674slot_gen_killblock_, _, Insts, _, Addrs, AllocVars, Gen−Kill ←
foldlslot_inst_gen_killAddrs, AllocVars, Insts, []−[], Gen−Kill
677slot_inst_gen_killAddrs, AllocVars, I, Gen0−Kill0, Gen−Kill ←
I = lettmpA, _, alloc_, _
get_assocA, AllocVars, AVs
→
Gen = Gen0
list_to_ord_setAVs, AVSet
ord_unionKill0, AVSet, Kill
I = let_, _, load_, tmpX
get_assocX, Addrs, Var
→
ord_memberchkVar, Kill0
→
Gen = Gen0
else
ord_add_elementGen0, Var, Gen
Kill = Kill0
I = store_, _, tmpX
get_assocX, Addrs, Var
→
Gen = Gen0
ord_add_elementKill0, Var, Kill
else
Gen = Gen0
Kill = Kill0
slot_phi_labels+Stores, +DF, +LiveIn, +Entry, +Var, -Labels

Labels is the ordset of the labels where Var needs a phi: the iterated dominance frontier of the entry and the blocks that store to Var, keeping only blocks where Var is live on entry. Each placed phi counts as a store for the iteration. Unreachable stores are not in Stores: they never run.

700slot_phi_labelsStores, DF, LiveIn, Entry, Var, PhiLabels ←
get_assocVar, Stores, Labels0
→
true
else
Labels0 = []
sortEntry|Labels0, Labels
iterated_frontierLabels, Var, DF, LiveIn, [], PhiLabels
iterated_frontier+Work, +Var, +DF, +LiveIn, +Placed0, -Placed

Placed is Placed0 with the frontier labels where Var is live, of the labels in Work and, in turn, of each new one. Invariant: Placed0 is the ordset of those labels found so far, and each of them has had its frontier scanned or is still in Work.

710iterated_frontier[], _, _, _, Placed, Placed
711iterated_frontierL|Ls, Var, DF, LiveIn, Placed0, Placed ←
get_assocL, DF, Frontier
foldllabel_placedVar, LiveIn, Frontier, Placed0−Ls, Placed1−Ls1
iterated_frontierLs1, Var, DF, LiveIn, Placed1, Placed
label_placed+Var, +LiveIn, +Y, +Placed0−Work0, −Placed−Work

if Var is live on entry to Y and Y is not in Placed0, Y is in Placed and in front of Work0.

720label_placedVar, LiveIn, Y, Placed0−Work0, Placed−Work ←
¬ord_memberchkY, Placed0
get_assocY, LiveIn, In
ord_memberchkVar, In
→
ord_add_elementPlaced0, Y, Placed
Work = Y|Work0
else
Placed = Placed0
Work = Work0
slots_phis+Vars, +Labelss//

the phis L-Var for each Var of Vars, in order, and each L of its ordset of labels in Labelss.

732slots_phis[], [] ⟶
[]
733slots_phisVar|Vars, Ls|Lss ⟶
slot_phisLs, Var
slots_phisVars, Lss
737slot_phis[], _ ⟶
[]
738slot_phisL|Ls, Var ⟶
L−Var
slot_phisLs, Var
phis_temps+Phis, +Used, -Placed//

Placed is Phis with each L-Var as L-(Var-T), T a fresh temp. The semicontext is the number of the last fresh temp.

745phis_temps[], _, [] ⟶
[]
746phis_tempsL−Var|Phis, Used, L−Var−T|Placed ⟶
stateN0, N
unused_tempUsed, N0, T, N
phis_tempsPhis, Used, Placed
751stateS0, S, S ⟶
S0
unused_temp+Used, +N0, -T, -N

T is pr.N, the first such name after N0 that is not in Used.

755unused_tempUsed, N0, T, N ←
N1 is N0 + 1
number_codesN1, Ds
atom_codesT0, 0'p, 0'r, 0'.|Ds
ord_memberchkT0, Used
→
unused_tempUsed, N1, T, N
else
T = T0
N = N1
765blocks_by_labelBlocks, ByLabel ←
findallL−B,
memberB, Blocks
B = blockL, _, _, _
,
Pairs
list_to_assocPairs, ByLabel
subtree_renamed+L, +Cur0, +Env//

the renamed blocks of the dominator subtree at L, Cur0 being the slots' values on entry to L. Env is env(Addrs, PhiAt, ByLabel, Kids, Succs).

772subtree_renamedL, Cur0, Env ⟶
block_renamedL, Cur0, Env, Cur, Renamed
Env = env_, _, _, Kids, _
get_assocL, Kids, Ks
Renamed
kids_renamedKs, Cur, Env
779kids_renamed[], _, _ ⟶
[]
780kids_renamedK|Ks, Cur, Env ⟶
subtree_renamedK, Cur, Env
kids_renamedKs, Cur, Env
blocks_renamed+Ls, +Cur0, +Env//

the renamed blocks Ls, each with the slots' values Cur0 on entry.

786blocks_renamed[], _, _ ⟶
[]
787blocks_renamedL|Ls, Cur0, Env ⟶
block_renamedL, Cur0, Env, _, Renamed
Renamed
blocks_renamedLs, Cur0, Env
block_renamed+L, +Cur0, +Env, -Cur, -Renamed

Renamed is renamed(L-Blk, Args): Blk is block L with its slot accesses rewritten, given the slots' values Cur0 on entry (Cur on exit), and Args the (Succ-Var)-(L-Value) arguments it gives its successors' slot phis.

796block_renamedL, Cur0, envAddrs, PhiAt, ByLabel, _, Succs, Cur, renamedL−blockL, Phis, Insts, Jump, Args, Map ←
get_assocL, ByLabel, blockL, Phis, Insts0, Jump
get_assocL, PhiAt, VarTemps
→
true
else
VarTemps = []
foldlphi_cur, VarTemps, Cur0, Cur1
phraseinsts_renamedInsts0, Addrs, Cur1, Cur, L, 0, 0, Map, Insts
get_assocL, Succs, Ss
findallS−Var−L−Val,
memberS, Ss
get_assocS, PhiAt, SuccVarTemps
memberVar−_, SuccVarTemps
get_assocVar, Cur, Val
,
Args
810renamed_block_argsrenamedLBlk, Args, Map, LBlk, Args, Map
812phi_curVar−T, Cur0, Cur ←
put_assocVar, Cur0, tmpT, Cur
insts_renamed+Insts0, +Addrs, +Cur0, -Cur//

the instructions Insts0 with their slot accesses rewritten, Cur0 and Cur being the slots' values before and after them.

818insts_renamed[], _, Cur, Cur, _, _, _, [] ⟶
[]
819insts_renamedI|Is, Addrs, Cur0, Cur, L, Old, New, Map ⟶
phraseinst_renamedI, Addrs, Cur0, Cur1, Out
emission_sitesL, Old, New, Out, Map, Rest, Next
Old1 is Old + 1
insts_renamedIs, Addrs, Cur1, Cur, L, Old1, Next, Rest
825inst_list[] ⟶
[]
826inst_listI|Is ⟶
inst_renamed+I, +Addrs, +Cur0, -Cur//

the alloc and the derived addresses are no instructions, a load of a slot is an operation on its current value, a store to a slot is none and sets its value, and any other instruction is itself.

832inst_renamedI, Addrs−AllocVars−Shifts, Cur0, Cur ⟶
I = lettmpA, _, alloc_, _
get_assocA, AllocVars, AVs
→
foldlslot_zero, AVs, Cur0, Cur
I = lettmpD, _, _, _
get_assocD, Addrs, _
→
Cur = Cur0
I = letD, K, loadT, tmpX
get_assocX, Addrs, Var
get_assocX, Shifts, Shift
→
A punned read of the slot's bytes at Shift: the value shifted
right by 8*Shift, then extended as any narrow load (punned_slots/3).
get_assocVar, Cur0, V
load_opT, K, Op
D = tmpN
atom_concat'pun.', N, P
Bits is 8 × Shift
Cur = Cur0
lettmpP, w, shr, V, constBits, letD, K, Op, tmpP
I = letD, K, loadT, tmpX
get_assocX, Addrs, Var
→
get_assocVar, Cur0, V
load_opT, K, Op
Cur = Cur0
letD, K, Op, V
I = store_, V, tmpX
get_assocX, Addrs, Var
→
put_assocVar, Cur0, V, Cur
else
Cur = Cur0
I
857slot_zeroVar, Cur0, Cur ←
put_assocVar, Cur0, const0, Cur

What a load at class K of type T computes from the value last stored at its width (see the header).

861load_opl, _, copy
862load_opp, _, copy
863load_ops, _, copy
864load_opd, _, copy
865load_opsw, K, Op ←
867load_opuw, K, Op ←
869load_opsh, _, extsh
870load_opuh, _, extuh
871load_opsb, _, extsb
872load_opub, _, extub
class_word_load_op+K, +T, -Op

a load of the word type T at class K (w or l, as ir.pl requires) is Op of the stored word.

876class_word_load_opw, _, copy
877class_word_load_opl, T, extT

The class of a slot's phi, from the slot's width.

slot_class
880ll
881pp
882ss
883dd
884ww
885hw
886bw