Fil-C on Nix

Filnix wiki

mbrock/filnix/nanofil

escape.pl

Escape classification of allocs: alloc_classes/2 tells, for each alloc of a function, whether its address is only used for loads and stores at known offsets. Prior art: QBE's ALoc/AEsc.

The module also exports the relations alloc_classes/2 is built from: address_temps/2, load_width/2 and def_counts/2.

The analysis rests on one fact: a local alloc can only be reached through its own address, so no other pointer or integer can name it.

Offsets are exact integers. An IR add wraps modulo 2^64, but here a huge constant gives a huge offset, which promote's bounds check refuses.

useslists
usesassoc
usespairs
usesirop_kind/2
load_width?LoadType, ?Widthnondet

A supported integer or pointer load reads a slot of width Width (b h w l p). Other load types must make the allocation escape.

load_width
28ll
29sww
30uww
31shh
32uhh
33sbb
34ubb
35pp
slot_load_width?LoadType, ?Width

as load_width/2, and a float load reads a slot of its own width, s or d. local_storage.pl keeps float loads out of named locals through load_width/2; promotion takes them.

40slot_load_widthT, W ←
41slot_load_widths, s
42slot_load_widthd, d
alloc_classes+Func, -Classesdet

Classes is [A-Class] for every alloc temp A of Func, in order, with Class one of:

- local(Size, Accesses): every use of A's address is the address of
  a load or store at a fixed offset, possibly through add/sub of
  constants or copy. Accesses is an ordset of acc(Off, Width),
  Width in b h w l p s d. Size is the alloc's operand.
- escapes(Why): the address is used any other way (stored, passed,
  returned, compared, converted, merged in a phi, offset by a
  variable, loaded with an unsupported type), or it or a temp
  derived from it is defined more than once, so offsets are unknown.
58alloc_classesF, Classes ←
def_countsF, DefCounts
alloc_classesF, Addrs, DefCounts, Classes
alloc_classes+Func, +Addrs, +DefCounts, -Classesdet
66alloc_classesF, Addrs, DefCounts, Classes ←
F = func_, _, _, _, Blocks
findallI,
memberblock_, _, Is, _, Blocks
memberI, Is
,
Insts
findallA−Size, memberlettmpA, _, alloc_, Size, Insts, Allocs
findallRoot−E,
memberI, Insts
inst_eventI, Addrs, Root−E
,
InstEvents
findallRoot−escphi,
memberblock_, Phis, _, _, Blocks
memberphi_, _, Args, Phis
member_−tmpX, Args
get_assocX, Addrs, Root−_
,
PhiEscapes
findallRoot−escjump,
memberblock_, _, _, Jump, Blocks
jump_valueJump, tmpX
get_assocX, Addrs, Root−_
,
JumpEscapes
A temp derived from A that is also defined some other way.
findallRoot−escredefined,
gen_assocX, Addrs, Root−_
get_assocX, DefCounts, N
N > 1
,
Redefined
appendInstEvents, PhiEscapes, JumpEscapes, Redefined, Events0
Group once by allocation, preserving event order (and hence the
first escape reason), rather than scanning every event per alloc.
keysortEvents0, Sorted
group_pairs_by_keySorted, Pairs
list_to_assocPairs, Events
maplistevents_alloc_classEvents, Allocs, Classes
events_alloc_class+Events, +A−Size, −A−Class

Class is the class of the alloc A, of operand Size, given the assoc Events of each alloc's events in order: escapes for the first escape, or else local.

101events_alloc_classEvents, A−Size, A−Class ←
get_assocA, Events, Es
→
true
else
Es = []
memberchkescWhy, Es
→
Class = escapesWhy
else
findallAcc, memberaccAcc, Es, Accs0
sortAccs0, Accs
Class = localSize, Accs
inst_event+Ins, +Addrs, −Root−Eventnondet

Event is what Ins does with the address of alloc Root: an access at a known offset, or an escape of every alloc whose address it reads. A constant offset (a derived address, seen by address_temps) is neither.

114inst_eventletD, K, Op, Args, Addrs, Event ←
op_kindOp, Kind
kind_eventKind, letD, K, Op, Args, Addrs, Event
117inst_eventstoreW, V, A, Addrs, Event ←
V = tmpX
get_assocX, Addrs, Root−_
Event = Root−escstored
or
A = tmpX
get_assocX, Addrs, Root−Off
Event = Root−accaccOff, W

Atomic accesses are not slot accesses: their objects escape, so they stay heap objects, which Fil-C's atomic helpers require (atomic boxes live in a heap object's aux).

128inst_eventlet_, _, atomic_load_, tmpX, Addrs, Root−escatomic ←
get_assocX, Addrs, Root−_
130inst_eventatomic_store_, V, A, Addrs, Root−escatomic ←
membertmpX, V, A
get_assocX, Addrs, Root−_

A cas, of an integer as of a pointer, lets every address it reads escape: its object stays on the heap, which lock cmpxchg on a shared object and Fil-C's pointer helper both need, and promotion never deletes the object of a cas it would leave behind.

137inst_eventlet_, _, cas_, Args, Addrs, Root−escatomic ←
membertmpX, Args
get_assocX, Addrs, Root−_

A full-width vector check (docs/vector-access.md) keeps its object on the heap: promoting it would need the static bounds to discharge it.

142inst_eventaccess_, tmpX, _, _, Addrs, Root−escaccess ←
get_assocX, Addrs, Root−_

A fixed-register asm reads its inputs' values: in plain mode a w input can be a derived address, whose object then stays (docs/asm-fixed.md).

146inst_eventasm_fixed_, Ins, _, Addrs, Root−escasm ←
member_−tmpX, Ins
get_assocX, Addrs, Root−_
149inst_eventcall_, F, Args, Addrs, Root−esccall ←
findallV, memberarg_, V, Args, Vs
membertmpX, F|Vs
get_assocX, Addrs, Root−_
kind_event+Kind, +Ins, +Addrs, −Root−Eventnondet

as inst_event/3, for the instruction Ins of an Op of Kind. A load accesses its address only when its slot width is supported; otherwise it escapes, so promotion cannot mistake an unknown access for no use. Every other op lets the addresses it reads escape.

159kind_eventload, let_, _, loadT, tmpX, Addrs, Root−Event ←
get_assocX, Addrs, Root−Off
→
Event = accaccOff, W
else
Event = escloadT
166kind_eventadditive, Ins, Addrs, Event ←
ins_escapeIns, Addrs, Event
167kind_eventalu, Ins, Addrs, Event ←
ins_escapeIns, Addrs, Event
168kind_eventshift, Ins, Addrs, Event ←
ins_escapeIns, Addrs, Event
169kind_eventneg, Ins, Addrs, Event ←
ins_escapeIns, Addrs, Event
170kind_eventcopy, Ins, Addrs, Event ←
ins_escapeIns, Addrs, Event
171kind_eventptoi, Ins, Addrs, Event ←
ins_escapeIns, Addrs, Event
172kind_eventitop, Ins, Addrs, Event ←
ins_escapeIns, Addrs, Event
173kind_eventva_args, Ins, Addrs, Event ←
ins_escapeIns, Addrs, Event
174kind_eventva_arg, Ins, Addrs, Event ←
ins_escapeIns, Addrs, Event
175kind_eventsetjmp, Ins, Addrs, Event ←
ins_escapeIns, Addrs, Event
176kind_eventva_arg, let_, _, _, tmpX, Addrs, Root−accaccOff, p ←
get_assocX, Addrs, Root−Off
178kind_eventcmp, Ins, Addrs, Event ←
ins_escapeIns, Addrs, Event
179kind_eventext, Ins, Addrs, Event ←
ins_escapeIns, Addrs, Event
180kind_eventalloc, Ins, Addrs, Event ←
ins_escapeIns, Addrs, Event
ins_escape+Ins, +Addrs, −Root−escOpnondet

Ins, an instruction of Op that is not a derived address, reads the address of alloc Root.

184ins_escapeletD, K, Op, Args, Addrs, Root−escOp ←
¬
offset_ofletD, K, Op, Args, Y, _derived address
get_assocY, Addrs, _
membertmpX, Args
get_assocX, Addrs, Root−_
190jump_valuebranchV, _, _, V
191jump_valueretV, V
192jump_valueretsVs, V ←
memberV, Vs
offset_of+Ins, -Src, -Delta

Ins computes Src + Delta.

195offset_oflet_, _, add, tmpX, constN, X, N
196offset_oflet_, _, add, constN, tmpX, X, N
197offset_oflet_, _, sub, tmpX, constN, X, D ←
D is −N
198offset_oflet_, _, copy, tmpX, X, 0
address_temps+Func, -Addrsdet

Addrs is the assoc Temp -> A-Off for each alloc temp A (Off 0) and each temp derived from one by constant offsets, whatever the class.

205address_tempsfunc_, _, _, _, Blocks, Addrs ←
findallI,
memberblock_, _, Is, _, Blocks
memberI, Is
,
Insts
findallA−A−0, memberlettmpA, _, alloc_, _, Insts, AllocPairs
list_to_assocAllocPairs, Addrs0
edge(Pos, D, N): instruction Pos defines D as the source plus N.
findallX−edgePos, D, N,
nth0Pos, Insts, Ins
Ins = lettmpD, _, _, _
offset_ofIns, X, N
,
Edges0
keysortEdges0, Edges1
group_pairs_by_keyEdges1, Edges
list_to_assocEdges, BySource
assoc_to_listAddrs0, Seeds
layers_addrsSeeds, BySource, Addrs0, Addrs
layers_addrs+Layer, +BySource, +Addrs0, -Addrs

Addrs is Addrs0 with the temps derived, directly or not, from the X-(Root-Off) pairs of Layer and not yet in Addrs0. A dependency is followed only when its source is first resolved, a whole layer at a time: as in the old fixed point, a redefined temp takes its earliest-resolved definition, then instruction order breaks ties. Cycles without an allocation seed remain unresolved.

228layers_addrs[], _, Addrs, Addrs
229layers_addrsS|Ss, BySource, Addrs0, Addrs ←
findallPos−D−Root−Off,
memberX−Root−Off0, S|Ss
get_assocX, BySource, Edges
memberedgePos, D, N, Edges
¬get_assocD, Addrs0, _
Off is Off0 + N
,
Found
keysortFound, Ordered
pairs_valuesOrdered, Pairs
One entry per temp; redefinitions are caught later.
sort1, @<, Pairs, New
foldlpair_assoc, New, Addrs0, Addrs1
layers_addrsNew, BySource, Addrs1, Addrs
244pair_assocK−V, Assoc0, Assoc ←
put_assocK, Assoc0, V, Assoc
def_counts+Func, -Countsdet

Counts is the assoc Temp -> number of definitions of Temp in Func (parameters, phis, instructions and call results).

251def_countsfunc_, _, _, Params, Blocks, Counts ←
findallN, memberparam_, tmpN, Params, ParamDefs
findallN,
memberblock_, Phis, Insts, _, Blocks
memberphitmpN, _, _, Phis
or
memberlettmpN, _, _, _, Insts
or
membercallresulttmpN, _, _, _, Insts
or
membercalltupleRs, _, _, Insts
memberresulttmpN, _, Rs
or
memberasm_fixed_, _, Outs, Insts
member_−tmpN, Outs
,
BlockDefs
appendParamDefs, BlockDefs, Defs
msortDefs, Sorted
clumped_pairsSorted, Pairs
list_to_assocPairs, Counts
clumped_pairs+Sorted, -Pairs

Pairs is X-N for each run of N equal elements X of Sorted.

271clumped_pairs[], []
272clumped_pairsX|Xs, X−N|Ps ←
run_ofX, Xs, 1, N, Rest
run_of+X, +Xs, +N0, -N, -Rest

Xs is a run of N - N0 copies of X followed by Rest, which does not start with X.

278run_ofX, Y|Ys, N0, N, Rest ←
X ≡ Y
!
N1 is N0 + 1
run_ofX, Ys, N1, N, Rest
283run_of_, Rest, N, N, Rest