Fil-C on Nix

Filnix wiki

mbrock/filnix/nanofil

frame_slots.pl

Conservative stack-slot allocation for the Fil-C backend: temp_slots/6 assigns each temporary a frame slot, and temporaries whose live intervals are disjoint may share one.

A temporary's interval is closed and covers every write (even of a dead result), every use by an instruction and every block boundary where it is live in or out. One lowered instruction, including all its helper calls, is thus indivisible. Two temporaries share a slot only when their intervals are strictly disjoint, so a result never overwrites its operands.

PoolHolds
pointerp temporaries: each slot is a payload slot and its lower, allocated together
addressderived p temporaries (lower_roots.pl): a payload slot only; the lower is their root's
scalarw and l temporaries, and s and d ones when the pools are shared; they never use the pointer pool. With pools own, never reused
floats and d temporaries when the pools are own
widex temporaries: 16-byte slots for the 10 bytes of a long double

A variadic function's hidden va_snapshot parameter is kept live for the whole invocation. Functions that call setjmp must not use this allocator (docs/setjmp.md).

useslists
usesassoc
usesordsets
usespairs
usescfg
temp_slots+Params, +Blocks, +Temps, -Aliases, -ScalarCount, -PtrCount, -WideCountdet

Aliases maps each temp name to physical storage(Pool, Index). The pools are shared: s and d temporaries use scalar slots.

36temp_slotsParams, Blocks, Temps, Aliases, NS, NP, NW ←
temp_slotsshared, Params, Blocks, Temps, Aliases, NS, NP, NW, 0
temp_slots+Pools, +Params, +Blocks, +Temps, -Aliases, -ScalarCount, -PtrCount, -WideCount, -FloatCountdet

As temp_slots/7, with Pools shared, own, own(Groups) or own(Groups, Ranges). Own gives s and d temporaries a float pool of their own, so no scalar slot holds both an integer and a float temporary (register allocation, docs/perf.md item 3, takes only w/l homes), and reuses no scalar slot: each w/l temporary has its own home, so a home is one live range (docs/perf.md 3d). Own(Groups), with Groups a list of Temporary-Key pairs, pools each key's w/l temporaries among themselves, as shared pools them; key frame first. (A split choice, own's float pool with shared scalar slots, served the retired function, operands and cells modes.) Own(Groups, Ranges) is own(Groups) that binds Ranges to the live ranges, or takes them when Ranges is bound (temp_ranges/6).

53temp_slotsPools, Params, Blocks, Temps, Aliases, NS, NP, NW, NF ←
empty_assocDerived
temp_slotsPools, Derived, Params, Blocks, Temps, Aliases, NS, NP, NW, NF, _
temp_slots+Pools, +Derived, +Params, +Blocks, +Temps, -Aliases, -ScalarCount, -PtrCount, -WideCount, -FloatCount, -AddressCountdet

As temp_slots/9, with Derived mapping each derived pointer D to its lower root R (lower_roots:derived_lowers/3): D takes an address slot, storage(address, I), with no lower of its own, and R's pointer slot, address and lower together, stays R's until D's live range ends too, so R's lowers[] entry is never reused while D is live (docs/lowers.md).

64temp_slotsPools, Derived, Params, Blocks, Temps, Aliases, NS, NP, NW, NF, NA ←
Pools = own_, Ranges
nonvarRanges
→
Ranges = rangesWides, Intervals
else
temp_rangesPools, Derived, Params, Blocks, Temps, Wides, Intervals
Pools = own_, Ranges
→
Ranges = rangesWides, Intervals
else
true
own_groupsPools, Groups
The frame group takes the first scalar slots, near %rbp, so the
homes that stay in the frame sit together; each other group's
temporaries share slots among themselves, and ungrouped ones
(own) have homes of their own.
partitionframe_rangeGroups, Intervals, Frame, Rest0
allocateFrame, all, st[]−0, []−0, []−0, []−0, St1, Pairs, Pairs1
group_rangesRest0, Groups, Grouped, Owns
foldlgroup_allocate, Grouped, St1−Pairs1, St2−Pairs2
allocateOwns, own, St2, st_−NS, _−NP, _−NF, _−NA, Pairs2, WidePairs
foldlwide_slot, Wides, []−0−WidePairs, _−NW−[]
Each temporary has one interval, so the pairs have distinct keys
and make the assoc at once, not by an insertion per temporary.
list_to_assocPairs, Aliases
temp_ranges+Pools, +Derived, +Params, +Blocks, +Temps, -Wides, -Intervals

each temporary's live range, wide ones apart. They do not depend on the scalar groups, so own(Groups, Ranges) lowers again with the ranges of an earlier lowering (temps mode, docs/perf.md 3d).

89temp_rangesPools, Derived, Params, Blocks, Temps, Wides, Intervals ←
temp_livenessBlocks, In, Out
findallN, memberparam_, tmpN, Params, ParamNs
block_rangesBlocks, In, Out, [], 1, End, Touches1, Touches2
va_args reads this backend-only parameter implicitly. Reserve both
its address and lower for the entire invocation.
memberchkparamp, tmphiddenva_snapshot, Params
→
Touches2 = hiddenva_snapshot−End
else
Touches2 = []
Touches = ParamNs−0|Touches1
Touches are groups Ns-P in increasing P, sharing the liveness sets
rather than copying a point per temporary and block (1.7 million
in SQLite's sqlite3VdbeExec). A temporary's convex hull runs from
its first touch to its last, so each bound is one insertion per
temporary, not a tree update per use, which Trealla would retain.
Liveness gives In = Gen ∪ (Out − Kill) for every block, so a
live-out temporary is live in or written in the block, at a point
before its exit, and a live-in one is read or live out at or after
its entry: live-out sets never move a first touch, nor live-in
sets a last one, and each pass skips them. Nor does a live-in
temporary that was live out of the block before, or a live-out
one live into the block after: block_ranges/8 leaves them out.
first_touchesout, Touches, Lo
reverseTouches, Backwards
first_touchesin, Backwards, Hi
maplisttemp_intervalPools, Lo, Hi, Temps, Intervals00
derived_rangesDerived, Intervals00, Intervals0
sortIntervals0, Intervals1
partitionwide_range, Intervals1, Wides, Intervals
derived_ranges+Derived, +Ranges0, -Ranges

each derived pointer's range moves to the address pool, and each root's pointer range grows to the hull of its own and its derived pointers' ranges. A derived pointer is live only inside its range, so no other temporary shares the root's slot, and so writes its lowers[] entry, while it is live.

124derived_rangesDerived, Rs0, Rs ←
empty_assocDerived
→
Rs = Rs0
else
findallR−S−E,
memberrangeS, E, _, D, Rs0
get_assocD, Derived, R
,
Ext0
keysortExt0, Ext1
group_pairs_by_keyExt1, Ext2
list_to_assocExt2, Ext
maplistderived_rangeDerived, Ext, Rs0, Rs
131derived_rangeDerived, _, rangeS, E, pointer, D, rangeS, E, address, D ←
get_assocD, Derived, _
!
133derived_range_, Ext, rangeS0, E0, pointer, R, rangeS, E, pointer, R ←
get_assocR, Ext, SEs
!
foldlhull, SEs, S0−E0, S−E
136derived_range_, _, Range, Range
138hullS1−E1, S0−E0, S−E ←
S is minS0, S1
E is maxE0, E1
own_groups+Pools, -Groups

all when every scalar temporary is pooled (shared), else an assoc from temporary to its group (own(Pairs); own has no groups, so every scalar home is its own).

143own_groupsown, G ←
!
empty_assocG
144own_groupsownPs, G ←
!
list_to_assocPs, G
145own_groupsownPs, _, G ←
!
list_to_assocPs, G
146own_groups_, all

Everything but grouped or own scalar temporaries is in the frame group.

149frame_rangeall, _ ←
!
150frame_range_, range_, _, Pool, _ ←
Pool ≢ scalar
!
151frame_rangeG, range_, _, scalar, N ←
get_assocN, G, frame
group_ranges+Ranges, +Groups, -Grouped, -Owns

Grouped lists Key-Ranges for each group but frame, in key order; Owns are the scalar ranges of temporaries in no group.

156group_rangesRs, all, [], [] ←
!
Rs = []
157group_rangesRs, G, Grouped, Owns ←
findallK−R,
memberR, Rs
R = range_, _, scalar, N
get_assocN, G, K
,
KRs
findallR,
memberR, Rs
R = range_, _, scalar, N
¬get_assocN, G, _
,
Owns
keysortKRs, Sorted
group_pairs_by_keySorted, Grouped

A group's temporaries reuse each other's slots, not other groups'.

163group_allocate_−Rs, st_−N0, P, F, A−Ps0, St−Ps ←
allocateRs, all, st[]−N0, P, F, A, St, Ps0, Ps
166wide_rangerange_, _, wide, _

Wide slots are a pool of their own, reused as scalar slots are.

169wide_slotrangeS, E, wide, N, Ss0−N0−N−storagewide, I|Ps, Ss−N1−Ps ←
reuse_slotSs0, S, E, N0, N1, Ss, I
first_touches+Skip, +Touches, -First

First maps each temporary to the point of its first touch in Touches, leaving out the live sets tagged Skip (in or out).

175first_touchesSkip, Touches, First ←
empty_assocA0
foldlfirst_touch_groupSkip, Touches, A0, First
178first_touch_groupSkip, Group−P, A0, A ←
Group = liveSkip, _
→
A = A0
Group = live_, Ns
→
foldlfirst_touchP, Ns, A0, A
else
foldlfirst_touchP, Group, A0, A
184first_touchP, N, A0, A ←
get_assocN, A0, _
→
A = A0
else
put_assocN, A0, P, A
block_ranges+Blocks, +In, +Out, +PrevOut, +P0, -End, -Touches, ?Tail

the touch groups of Blocks from point P0: live-in and phis at the block's point, each instruction's uses and definitions at its own, and the jump's uses and live-out at the exit. PrevOut is the live-out set of the block before; the live sets keep only what can move a bound (above): the live-in set less PrevOut, the live-out set less the next block's live-in set.

194block_ranges[], _, _, _, P, P, T, T
195block_rangesblockL, Phis, Insts, Jump|Bs, In, Out, PrevOut, P, End, livein, NewIn−P, PhiNs−P|T0, T ←
get_assocL, In, LiveIn
ord_subtractLiveIn, PrevOut, NewIn
findallN, memberphitmpN, _, _, Phis, PhiNs
P1 is P + 1
inst_rangesInsts, P1, Exit, T0, Uses−Exit, liveout, LastOut−Exit|T1
jump_usesJump, Uses
get_assocL, Out, LiveOut
Bs = blockNext, _, _, _|_
→
get_assocNext, In, NextIn
else
NextIn = []
ord_subtractLiveOut, NextIn, LastOut
Next1 is Exit + 1
block_rangesBs, In, Out, LiveOut, Next1, End, T1, T
209inst_ranges[], P, P, T, T
210inst_rangesI|Is, P, End, Uses−P, Defs−P|T0, T ←
inst_usesI, Uses
findallN, inst_defI, N, Defs
Next is P + 1
inst_rangesIs, Next, End, T0, T
216temp_intervalPools, Lo, Hi, N−K, rangeStart, End, Pool, N ←
get_assocN, Lo, Start
get_assocN, Hi, End
class_poolK, Pools, Pool
class_pool
220p_pointer
221w_scalar
222l_scalar
223ssharedscalar
224dsharedscalar
225sownfloat
226downfloat
227sown_float
228down_float
229sown_, _float
230down_, _float
231x_wide

The pools' states st(Scalar, Pointer, Float, Address), each Slots-Count.

234allocate[], _, St, St, Ps, Ps
235allocaterangeStart, End, Pool, N|Rs, Pooled, St0, St, N−storagePool, I|Ps0, Ps ←
pool_slotPool, Pooled, Start, End, St0, St1, I
allocateRs, Pooled, St1, St, Ps0, Ps

An own scalar temporary gets a new slot that nothing reuses: only pooled temporaries are entered in the reuse list.

242pool_slotscalar, own, _, _, stSS−N0, P, F, A, stSS−N, P, F, A, N0 ←
!
N is N0 + 1
244pool_slotscalar, all, S, E, stSS0−N0, P, F, A, stSS−N, P, F, A, I ←
reuse_slotSS0, S, E, N0, N, SS, I
246pool_slotpointer, _, S, E, stC, PS0−N0, F, A, stC, PS−N, F, A, I ←
reuse_slotPS0, S, E, N0, N, PS, I
248pool_slotfloat, _, S, E, stC, P, FS0−N0, A, stC, P, FS−N, A, I ←
reuse_slotFS0, S, E, N0, N, FS, I
250pool_slotaddress, _, S, E, stC, P, F, AS0−N0, stC, P, F, AS−N, I ←
reuse_slotAS0, S, E, N0, N, AS, I

One end point per physical slot, not an interference graph. Reuse only strictly disjoint intervals: results cannot overwrite their operands.

255reuse_slot[], _, E, N0, N, slotN0, E, N0 ←
N is N0 + 1
256reuse_slotslotI0, E0|Ss0, S, E, N0, N, Ss, I ←
E0 < S
→
Ss = slotI0, E|Ss0
I = I0
N = N0
else
Ss = slotI0, E0|Tail
reuse_slotSs0, S, E, N0, N, Tail, I