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 the entry block, so it runs exactly once per call (the validator forbids jumps to the entry block) and its fresh contents are the only initial state;
  • its size is a constant and every slot lies inside it, so no access could trap or be undefined;
  • its slots do not overlap and each offset has one width, so every load reads exactly the bytes one earlier store wrote;
  • in filc mode, its p slots are 8-aligned (allocs are 16-aligned), so no loadp or storep, V, A could trap as misaligned.

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, where the slot is live (pruned SSA, Cytron et al.); their temps are named pr.N. 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 or pcopy
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 or p, otherwise.

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.

68promote_moduleM0, moduleItems ←
legacy_contextM0, moduleItems0
module_modemoduleItems0, Mode
code_itemsItems0, Code
maplistpromote_itemMode, Items0, Code, Groups
appendGroups, Items
promote_item+Mode, +Item0, -Item

Item is the module item Item0, promoted if it is a function.

76promote_itemMode, All, F0, Items ←
F0 = funcName, _, _, _, _
context_recordsAll, Name, Rs0
promote_contextMode, F0, Rs0, F, Rs
with_contextF, Rs, Items
80promote_item_, _, dataN, L, A, Is, dataN, L, A, Is
alloc_count+M, -Ndet

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

86alloc_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.

100promote_funcMode, F0, F ←
promote_contextMode, F0, [], F, _
102promote_context_, F, Rs, F, Rs ←
103promote_contextMode, F0, Rs0, F, Rs ←
F0 = funcName, Linkage, Ret, Params, Blocks0
func_cfgBlocks0, Cfg
cfg_idomsCfg, Idom
promotableMode, F0, Roots
Roots ≢ []
strict_ssaF0, Cfg, Idom
→
promote_rootsRoots, F0, 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
135slot_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.

139slot_records[], _, _, Ids, Ids, [], []
140slot_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
149phi_records[], _, _, _, Ids, Ids, []
150phi_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.

160type_slotqualified_, T, W ←
161type_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
163type_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).

170promotable_, F, [] ←
171promotableMode, F, Roots ←
F = func_, _, _, _, block_, _, EntryInsts, _|_
alloc_classesF, Classes
findallA−Accs,
memberA−localconstSize, Accs, Classes
memberchklettmpA, _, alloc_, _, EntryInsts
slots_okMode, Size, Accs
,
Roots
180slots_okMode, Size, Accs ←
maplistslot_okMode, Size, Accs
184slot_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.

192width_mode_alignedb, _, _
193width_mode_alignedh, _, _
194width_mode_alignedw, _, _
195width_mode_alignedl, _, _
196width_mode_alignedp, Mode, Off ←
199mode_pointer_alignedplain, _
200mode_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.

206slots_disjoint[]
207slots_disjointAcc|Accs ←
slots_disjoint_+Accs, +Acc

Acc, then Accs, do not overlap.

211slots_disjoint_[], _
212slots_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.

224strict_ssaF, cfg_, Rpo, _, _, Idom ←
def_countsF, Counts
¬
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
list_to_assoc[], Empty
foldllabel_reach, Rpo, Empty, 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
253block_reachedReach, L−_−_−_ ←
get_assocL, Reach, _
256use_reachedReach, use_, L, _ ←
get_assocL, Reach, _
259label_reachL, Reach0, Reach ←
put_assocL, Reach0, t, 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.

266block_use_−Phis−_−_, useT, P, end ←
memberphi_, _, Args, Phis
memberP−tmpT, Args
269block_useL−_−Insts−_, useT, L, atPos ←
nth1Pos, Insts, Ins
inst_usesIns, Us
memberT, Us
273block_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.

281defined_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.

289at_afterend, _
290at_afteratN, Pos ←
Pos < N

Rewriting

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

Blocks are the blocks of F0 with every slot of Roots promoted.

297promote_rootsRoots, F0, Cfg, Idom, Blocks, Map, Placed0, Addrs, Widths ←
F0 = func_, _, _, _, Blocks0
address_tempsF0, AllAddrs
Only addresses of promoted allocs matter from here on.
findallX−A−Off,
gen_assocX, AllAddrs, A−Off
memberchkA−_, Roots
,
AddrPairs
list_to_assocAddrPairs, Addrs
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.
findallL−GenKill,
memberB, Blocks0
B = blockL, _, _, _
slot_gen_killB, Addrs, GenKill
,
GenKillPairs
list_to_assocGenKillPairs, GenKill
empty_assocNoEdges
livenessCfg, GenKill, NoEdges, LiveIn, _
cfg_frontiersCfg, Idom, DF
Cfg = cfgEntry, _, Succs, _
def_countsF0, Counts
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
,
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, 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
365slot_gen_killblock_, _, Insts, _, Addrs, Gen−Kill ←
foldlslot_inst_gen_killAddrs, Insts, []−[], Gen−Kill
368slot_inst_gen_killAddrs, I, Gen0−Kill0, Gen−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.

388slot_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.

398iterated_frontier[], _, _, _, Placed, Placed
399iterated_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.

408label_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.

420slots_phis[], [] ⟶
[]
421slots_phisVar|Vars, Ls|Lss ⟶
slot_phisLs, Var
slots_phisVars, Lss
425slot_phis[], _ ⟶
[]
426slot_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.

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

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

443unused_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
453blocks_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).

460subtree_renamedL, Cur0, Env ⟶
block_renamedL, Cur0, Env, Cur, Renamed
Env = env_, _, _, Kids, _
get_assocL, Kids, Ks
Renamed
kids_renamedKs, Cur, Env
467kids_renamed[], _, _ ⟶
[]
468kids_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.

474blocks_renamed[], _, _ ⟶
[]
475blocks_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.

484block_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
498renamed_block_argsrenamedLBlk, Args, Map, LBlk, Args, Map
500phi_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.

506insts_renamed[], _, Cur, Cur, _, _, _, [] ⟶
[]
507insts_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
513inst_list[] ⟶
[]
514inst_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.

520inst_renamedI, Addrs, Cur0, Cur ⟶
I = lettmpD, _, _, _
get_assocD, Addrs, _
→
Cur = Cur0
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

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

536load_opl, _, copy
537load_opp, _, copy
538load_opsw, K, Op ←
540load_opuw, K, Op ←
542load_opsh, _, extsh
543load_opuh, _, extuh
544load_opsb, _, extsb
545load_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.

549class_word_load_opw, _, copy
550class_word_load_opl, T, extT

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

slot_class
553ll
554pp
555ww
556hw
557bw