rocq/backend/Promote.v
# Promotion of memory slots to temporaries
As promote.pl's promote_module/2, on terms. A slot is one offset and width, acc(Off, W), of an alloc whose address does not escape (Escape.v). An alloc is promoted, all its slots at once, when its size is a constant, its slots lie inside it and do not overlap, and its float slots are stored before they are read. Each load of a slot then becomes a copy or an extension of the slot's value, each store goes, and phis named pr.N join the values where paths meet (pruned SSA, Cytron et al.).
An l access that another access overlaps is first split into two w accesses, when the alloc then promotes.
A function that is not in strict SSA form, or that calls setjmp, is left as it is. What promote.pl does besides is left out, as in Copy.v: the context records and their sites. promote_module gives None where promote_module/2 fails or throws. The gate (tests/promote.sh) holds the result to the term nanofilpromote − terms prints.
String equality, written as Prolog's ==.
Terms
The instruction let(D, K, Op, Args).
The instruction store(W, V, A).
The value const(N).
The width, value and address when the term i is store(W, V, A).
The destination, class, type and address name when the term i is let(D, K, load(T), [tmp(X)]).
Whether x is in the ordset xs.
A slot of an alloc as a name, the term A − Off.
Which allocs
As stored_accs(F, Addrs, Stored): for each alloc, the accesses of the stores to its addresses.
As acc_inside(Acc, Outer): the bytes of the access a are bytes of the access outer.
As slots_disjoint/1: each of the slots, sorted, ends before the next begins.
As slots_ok(Mode, Size, Slots): an alloc of size bytes can go for its slots. In filc mode its size is one an alloc cannot trap on, at most 2^48 - 1. Each slot lies inside the alloc, a p slot is 8-aligned in filc mode, so no access could trap, and the slots do not overlap. No slot has the width x.
A promoted alloc: its temporary and its slots.
Whether w is a float width, s or d.
As stored_transfer(Addrs, A-Os, I, S0, S): the offsets among os of the alloc a that are stored after the instruction i, given those stored before, s. The alloc itself stores none.
As float_slots_stored(Cfg, ByLabel, Addrs, A-Slots): every load of a float slot of the alloc a comes after a store to it, on every path from the alloc. The stored offsets at each block's end are the greatest solution of a forward analysis over the reachable blocks: a block starts with what all its predecessors with a set so far end with, the entry with nothing.
What promotable/4 finds of a function: the allocs to promote, with the classes, address temporaries and definition counts they come from.
| an_roots | : listroot | the allocs to promote |
| an_classes | each alloc's size and class | |
| an_addrs | the address temporaries | |
| an_counts | : nmapnat | how often each temporary is defined |
As promotable(Mode, F, Roots, An), for a function with an alloc that does not call setjmp: the allocs of constant size whose slots are fit (punned_slots, slots_ok) and whose float slots are stored before they are read.
Splitting words
As split_offsets(Accs, Offs): the ordset of the offsets of the l accesses among accs that another access overlaps.
As split_accs(Offs, Accs0, Accs): the accesses accs with each l access at an offset of offs as its two words.
As split_roots(Mode, F, Roots, Classes, Addrs, Splits): the allocs to split, each with the offsets of the l accesses to split. They are the allocs of constant size with an l access that are no roots but would have fit slots once split. An alloc with a store of an l that is neither a temporary nor a constant is not split.
As fresh_temps/4 for one name: the name sw.I for the least I from n on that counts has no definition of, and I + 1. Each name passed over is another key of counts, so a fuel of their number is enough.
As split_inst/5: the instruction i, with a store or a load of an l at a split address as two of a w, the low word at the address and the high word 4 bytes on; n counts the fresh names. The address 4 bytes on has the class k, p in filc mode and else l.
As split_words/8: the blocks bs with the l accesses of splits as words, and whether any was split.
The rewrite
As the AddrShifts of promote_roots/11: the slot an address at the offset off of the alloc a names, and the byte of the slot it starts at. That is the first of slots with the byte at off, or a slot at off itself.
The slots the instruction i sets: those of a root it allocates, or the one it stores to.
As slot_gen_kill/4: the slots the instructions read before they set them, and those they set.
As iterated_frontier/6: the labels where the slot var needs a phi, an ordset: those of the dominance frontiers of the labels of work, and of the labels so found, where var is live. Each turn takes a label off work, and a label is put on it once, so a fuel of the labels of work and of the graph is enough.
As unused_temp/4: the name pr.I for the least I above n that counts has no definition of, and I.
As load_op(T, K, Op): the op that makes of a slot's value what a load of the type t gives at the class k.
As slot_class(Width, K): the class of the phi of a slot of the width w.
As inst_renamed/4: what the instruction i becomes, and the slots' values after it, given those before, cur.
- The alloc of a root goes; its slots are 0.
- An instruction that defines an address of a root goes.
- A load of a slot is the
load_opon the slot's value, or, for a punned read at a byte past the slot's first, on the value shifted right by 8 bits a byte, in the temporarypun.D. - A store to a slot goes; the slot has its value.
None where promote.pl fails: for a slot with no value, a load with no op, or a punned read into something that is no named temporary.
A renamed block, with the phi arguments it gives its successors: under each Succ − Slot, the term Pred − Value.
The slots with a phi at the label l, each with the phi's temporary.
As block_renamed/5: the block at the label l renamed from the slots' values cur at its start, and their values at its end. Its slot phis' temporaries are its slots' first values.
As subtree_renamed//3: the blocks of the dominator tree at the label l renamed, each from the values at its parent's end, put before those of done. The tree is no deeper than the graph has labels, the fuel.
As promote_roots/11: the blocks bs with the slots of roots as temporaries. The graph g, with the immediate dominators idom, is that of bs, which are in strict SSA form; addrs and counts are their address temporaries and definition counts, and fuel is at least the number of defined temporaries.
A slot's phis are on the iterated dominance frontier of the entry and the reachable blocks that set the slot, where the slot is live. The blocks are renamed down the dominator tree from the entry, where every slot is 0, and each unreachable block from all 0.
Functions and modules
As promote_context/5, without the records: the blocks bs of a function with the given params, in filc mode or not, its promotable allocs gone. The words of the allocs of split_roots are split first, if the function is in strict SSA form and every such alloc then promotes.
As promote_module/2, without the context items.