Fil-C on Nix

Filnix wiki

mbrock/filnix/nanofil

launder.pl

Pointer-as-integer laundering: an integer converted back to a pointer (itop) keeps the capability of the one pointer it was computed from, by launder_module/2. Fil-C's FilPizlonator does this with inferPointerAsIntLaunderingForFunction; this pass states the same abstract interpretation on nanofil's IR, before any optimization, so that the result does not depend on -O.

The analysis

Every w or l value has one of three abstract values:

ValueMeans
botno capability source: a constant, a parameter, a load from memory, a call, a comparison
defIdcomputed from the one capability Id
topcomputed from two different capabilities
DefinitionAbstract value
letD, l, ptoi, PdefP; bot when P is a constant plus offsets (&struct s *0→f), whose conversion clang folds
letD, K, Op, Args, Op arithmetic, bitwise, a shift, copy, ext_ or castthe merge of Args' values: bot is neutral, equal defs stay, anything else is top
phiD, K, InsdefphiD when some incoming value is not bot, else bot
anything elsebot

Fil-C runs its analysis on LLVM's SSA after clang's optimizer, so what it finds depends on the optimization level: at -O0 an integer stored in a local variable comes back from a load, which is bot. This pass evaluates the rules on the SSA form that promote.pl gives the function (promote_func/3, whatever the level): a load of a promotable local is the value last stored to it, a phi where stores merge. Ids are SSA values too, so a pointer variable read twice is one source; so is a member or element read twice, its address computed twice from the same values, with no write between the reads in one block (block_loads/3, an optimizer's value numbering). The rules are Fil-C's; only the program they read is fixed. Clang's idiom recognition, which turns some selects into llvm.umin calls (bot to Fil-C), plays no part.

A local the frontend declares volatile is memory, as it is to Fil-C: its reads are bot (volatile_allocs/2).

A capability chosen this way is always one the program held: the rewrite never creates authority, and every access through the result is checked against that capability's bounds as any other.

The rewrite

The function is rewritten as it came from the frontend, and each capability the analysis names is computed where it is needed:

InstructionBecomes
letD, p, itop, X, X defI = ptoi C, Off = X - I, D = C + Off, C the capability of X
the same, X bot or topunchanged: no capability
an alloc with integer slots that ever hold a non-bot valuealso a pointer slot, the slot's shadow, per such slot
a store to such a slotalso a store of the value's capability (or null) to the shadow
a def load of such a slotalso a load of the shadow
a def phi whose capability is neededalso a p phi of its incoming values' capabilities (null for bot and top)

The shadow holds what Fil-C's capability phi would select: the capability of the value last stored, null for a top or bot one. Under -O, promote turns each shadow into exactly that phi. Functions without itop, and functions that are not in strict SSA form, are unchanged.

modulelaunder
useslists
usesassoc
usesordsets
usescfgfunc_cfg/2, cfg_idoms/2
usespromotepromote_func/3, promotable/4, strict_ssa/3
usesprovenanceremap_sites/3, emission_sites/7
launder_module+Items0, -Itemsdet

Items are the module items Items0 (functions, their context/2 items and data) with each function laundered by launder_func/4.

79launder_moduleItems0, Items ←
findallName−Rs, membercontextName, Rs, Items0, CPairs0
sort1, @<, CPairs0, CPairs
list_to_assocCPairs, Contexts0
foldllaunder_item, Items0, Items1, Contexts0, Contexts
maplistrelabel_contextContexts, Items1, Items
86launder_itemF0, F, Cs0, Cs ←
F0 = funcName, _, _, _, _
!
get_assocName, Cs0, Rs0
→
true
else
Rs0 = []
launder_funcF0, Rs0, F, Rs
Rs0 ≡ Rs
→
Cs = Cs0
else
put_assocName, Cs0, Rs, Cs
91launder_itemItem, Item, Cs, Cs
93relabel_contextCs, contextName, _, contextName, Rs ←
!
get_assocName, Cs, Rs
94relabel_context_, Item, Item
launder_func+F0, +Records0, -F, -Recordsdet

F is the function F0 with each itop of an integer computed from one capability given that capability (this module's header), and Records is its context Records0 with instruction sites moved accordingly.

101launder_funcF0, Rs0, F, Rs ←
F0 = funcName, Linkage, Ret, Params, Blocks0
memberblock_, _, Is, _, Blocks0
memberchklet_, p, itop, _, Is
!
func_cfgBlocks0, Cfg
cfg_idomsCfg, Idom
strict_ssaF0, Cfg, Idom
→
promote_funcfilc, F0, FP
analysisF0, Rs0, FP, An
demandsF0, An, St
rewrite_blocksBlocks0, An, St, Blocks, Map
F = funcName, Linkage, Ret, Params, Blocks
remap_sitesRs0, Map, Rs
else
F = F0
Rs = Rs0

The analysis

analysis+F0, +Records, +FP, -An

An is an(Copies, States, Defs, Addrs, Shadows): FP's copies T-V, the abstract values of FP's temps (bot left out), F0's definitions T-Inst, F0's address temps X-(A-Off) and the shadow temp of each tracked slot A-Off.

121analysisF0, Rs, FP, anCopies, States, Defs, Addrs, Shadows ←
F0 = func_, _, _, _, Blocks0
promotablefilc, F0, Roots0, an_, Addrs0, _
Addrs0 ≡ none
→
empty_assocAddrs
else
Addrs = Addrs0
volatile_allocsRs, Volatile
excludevolatile_rootVolatile, Roots0, Roots
findallT−true,
memberblock_, _, Is, _, Blocks0
memberlettmpT, _, load_, tmpX, Is
get_assocX, Addrs, A−_
ord_memberchkA, Volatile
,
VPs
list_to_assocVPs, VolatileLoads
FP = func_, _, _, _, BlocksP
findallT−V,
memberblock_, _, Is, _, BlocksP
memberlettmpT, _, copy, V, Is
¬get_assocT, VolatileLoads, _
,
CPs
list_to_assocCPs, Copies0
foldlblock_loads, BlocksP, Copies0, Copies
findallEq,
memberblock_, Phis, Is, _, BlocksP
memberI, Phis
or
memberI, Is
equationI, Copies, Eq
Eq = D−_
¬get_assocD, VolatileLoads, _
,
Eqs
empty_assocS0
fixpointEqs, Copies, S0, States
findallT−I,
memberblock_, Phis, Is, _, Blocks0
memberI, Phis
or
memberI, Is
,
DPs
list_to_assocDPs, Defs
findallA−Off,
memberblock_, _, Is, _, Blocks0
memberstoreW, V, tmpX, Is
W ≢ p
get_assocX, Addrs, A−Off
memberA−Slots, Roots
memberchkaccOff, W, Slots
value_stateCopies, States, V, Av
Av ≢ bot
,
Tracked0
sortTracked0, Tracked
used_namesF0, Used0
shadow_namesTracked, Used0, 0, Shadows0
list_to_assocShadows0, Shadows
volatile_allocs+Records, -Allocs

Allocs is the ordset of the alloc temps of locals the frontend's context declares volatile. promote.pl makes such a local a value, but its reads are memory reads to Fil-C, so they are bot and the local has no shadow. The IR does not mark volatility; context only ever takes a capability away here.

162volatile_allocsRs, Allocs ←
findallA,
memberbindingB, _, qualifiedQs, _, _, Rs
memberchk(volatile), Qs
membervalueA, namedB, address, _, Rs
,
As
sortAs, Allocs
168volatile_rootVolatile, A−_ ←
ord_memberchkA, Volatile
block_loads+Block, +Copies0, -Copies

Copies is Copies0 with each pure computation of Block that repeats an earlier one on the same values, and each pointer load that repeats an earlier one from the same address with no write between them, a copy of the earlier: one value, as an optimizer's value numbering makes it (s->p read twice computes its address twice).

176block_loadsblock_, _, Is, _, Copies0, Copies ←
empty_assocSeen
foldlrepeated_load, Is, Copies0−Seen, Copies−_
180repeated_loadlettmpT, p, loadp, A, Cs0−Seen0, Cs−Seen ←
!
rootCs0, A, R
get_assocR, Seen0, T0
→
put_assocT, Cs0, tmpT0, Cs
Seen = Seen0
else
Cs = Cs0
put_assocR, Seen0, T, Seen
186repeated_loadlettmpT, K, Op, Args, Cs0−Seen0, Cs−Seen ←
!
maplistrootCs0, Args, Rs
Key = opK, Op, Rs
get_assocKey, Seen0, T0
→
put_assocT, Cs0, tmpT0, Cs1
Seen = Seen0
else
Cs1 = Cs0
put_assocKey, Seen0, T, Seen
K ≡ p
Args = B|_
→
put_assocbaseT, Cs1, B, Cs
else
Cs = Cs1
198repeated_loadlet_, _, Op, _, State, State ←
199repeated_load_, Cs−_, Cs−Seen ←
empty_assocSeen
pure_value_op+K, +Op

a let of class K and Op computes its value from its arguments alone, so two with equal arguments are one value.

203pure_value_opp, Op ←
!
memberchkOp, add, sub
204pure_value_opK, Op ←
Op ≢ copy
206writes_memoryOp ←
¬atomOp
¬Op = load_
¬Op = ext_
¬Op = cmp_, _
¬Op = alloc_
!
208writes_memoryOp ←
memberchkOp, va_arg
210definesphitmpT, _, _, T
211defineslettmpT, _, _, _, T
equation+Inst, +Copies, -Eq

FP's Inst defines an integer by one of the header's rules.

215equationlettmpD, l, ptoi, P, Copies, D−ptoiId ←
rootCopies, P, Id
¬constant_baseCopies, Id
!
217equationlettmpD, l, ptoi, _, _, D−merge[] ←
!
218equationphitmpD, K, Ins, _, D−phiVs ←
!
findallV, member_−V, Ins, Vs
220equationlettmpD, K, Op, Args, _, D−mergeArgs ←
223int_classw
224int_classl
226merge_opOp ←
memberchkOp, add, sub, mul, div, udiv, rem, urem, and, or, xor, sar, shr, shl, neg, copy, cast
!
229merge_opext_
231fixpointEqs, Copies, S0, S ←
foldlapply_equationCopies, Eqs, S0−false, S1−Changed
Changed ≡ true
→
fixpointEqs, Copies, S1, S
else
S = S1
235apply_equationCopies, D−Rule, S0−C0, S−C ←
rule_valueRule, D, Copies, S0, New
get_assocD, S0, Old
→
true
else
Old = bot
New ≡ Old
→
S = S0
C = C0
New ≡ bot
→
S = S0
C = C0
else
put_assocD, S0, New, S
C = true
243rule_valueptoiId, _, _, _, defId
244rule_valuemergeArgs, _, Copies, S, Av ←
foldlmerge_argCopies, S, Args, bot, Av
246rule_valuephiVs, D, Copies, S, Av ←
memberV, Vs
value_stateCopies, S, V, VAv
VAv ≢ bot
→
Av = defphiD
else
Av = bot
252merge_argCopies, S, V, Av0, Av ←
value_stateCopies, S, V, VAv
mergeAv0, VAv, Av
merge+A, +B, -Cdet
257mergebot, X, X ←
!
258mergeX, bot, X ←
!
259mergedefA, defB, defA ←
A ≡ B
!
260merge_, _, top
constant_base+Copies, +P

the pointer P is a constant plus offsets, as a hand-written offsetof, &struct s *0→f, is: clang folds its conversion to an integer, so it is no capability source. Copies holds each p add or sub as base(T)-Base.

266constant_base_, const_ ←
!
267constant_baseCopies, tmpT ←
get_assocbaseT, Copies, B
rootCopies, B, R
constant_baseCopies, R
270rootCopies, tmpT, R ←
get_assocT, Copies, V
!
rootCopies, V, R
271root_, V, V
value_state+Copies, +States, +V, -Av

the abstract value of V, an operand of F0 or FP.

275value_stateCopies, S, V, Av ←
rootCopies, V, R
R = tmpT
get_assocT, S, Av0
→
Av = Av0
else
Av = bot
279an_stateanCopies, S, _, _, _, V, Av ←
value_stateCopies, S, V, Av
281used_namesfunc_, _, _, Params, Blocks, Used ←
findallT,
memberparam_, tmpT, Params
or
memberblock_, Phis, Is, _, Blocks
memberI, Phis
or
memberI, Is
,
Ts
sortTs, Used
287shadow_names[], _, _, []
288shadow_namesSlot|Slots, Used, N0, Slot−T|Ps ←
fresh_nameld, Used, N0, T, N
shadow_namesSlots, Used, N, Ps
fresh_name+Prefix, +Used, +N0, -T, -N

T is Prefix.N, the first such name after N0 that is not in Used, the function's own names. Shadows are ld.N and capabilities lc.N, each from its own counter, so a fresh name never needs adding to Used.

296fresh_namePrefix, Used, N0, T, N ←
N1 is N0 + 1
formatatomT0, '~w.~w', Prefix, N1
ord_memberchkT0, Used
→
fresh_namePrefix, Used, N1, T, N
else
T = T0
N = N1

Which capabilities are needed

demands+F0, +An, -St

St is st(N, Used, Reals, Loads, Phis, Itops) once every capability needed by an itop or a store to a tracked slot has a value: Reals maps a temp to its capability operand, Loads a load to its shadow load's temp, Phis a phi to C-Ins, its capability phi, Itops an itop to the two temps of its offset.

311demandsF0, An, St ←
F0 = func_, _, _, _, Blocks
used_namesF0, Used
empty_assocE
St0 = st0, Used, E, E, E, E
findallI,
memberblock_, _, Is, _, Blocks
memberI, Is
,
Insts
foldldemandAn, Insts, St0, St
319demandAn, lettmpD, p, itop, X, St0, St ←
an_stateAn, X, def_
!
realAn, X, R, St0, St1
R = const_
→
St = St1
else
freshI1, St1, St2
freshI2, St2, St3
St3 = stN, U, Re, Lo, Ph, It0
put_assocD, It0, I1−I2, It
St = stN, U, Re, Lo, Ph, It
329demandAn, storeW, V, tmpX, St0, St ←
W ≢ p
an_stateAn, V, def_
!
realAn, V, _, St0, St
333demand_, _, St, St
335tracked_shadowan_, _, _, Addrs, Shadows, X, Sh ←
get_assocX, Addrs, Slot
get_assocSlot, Shadows, Sh
338freshT, stN0, U, Re, Lo, Ph, It, stN, U, Re, Lo, Ph, It ←
fresh_namelc, U, N0, T, N
real+An, +V, -R, +St0, -St

R is the capability operand of V, whose abstract value is def(_).

343real_, tmpT, R, St, St ←
St = st_, _, Re, _, _, _
get_assocT, Re, R
!
345realAn, tmpT, R, St0, St ←
An = an_, _, Defs, _, _
get_assocT, Defs, Inst
real_defInst, An, T, R, St0, St
350real_deflet_, l, ptoi, P, _, T, P, St0, St ←
!
put_realT, P, St0, St
351real_deflet_, _, copy, V, An, T, R, St0, St ←
!
realAn, V, R, St0, St1
put_realT, R, St1, St
353real_deflet_, _, load_, tmpX, An, T, R, St0, St ←
!
→
freshC, St0, St1
R = tmpC
St1 = stN, U, Re, Lo0, Ph, It
put_assocT, Lo0, C, Lo
put_realT, R, stN, U, Re, Lo, Ph, It, St
A punned read of a narrower part of a slot.
else
R = const0
put_realT, R, St0, St
362real_defphi_, _, Ins, An, T, tmpC, St0, St ←
!
freshC, St0, St1
put_realT, tmpC, St1, St2
foldlreal_incomingAn, Ins, CapIns, St2, St3
St3 = stN, U, Re, Lo, Ph0, It
put_assocT, Ph0, C−CapIns, Ph
St = stN, U, Re, Lo, Ph, It
369real_deflet_, _, _, Args, An, T, R, St0, St ←
an_stateAn, tmpT, defId
memberA, Args
an_stateAn, A, defId1
Id1 ≡ Id
!
realAn, A, R, St0, St1
put_realT, R, St1, St
374real_incomingAn, P−V, P−R, St0, St ←
an_stateAn, V, def_
→
realAn, V, R, St0, St
else
R = const0
St = St0
380put_realT, R, stN, U, Re0, Lo, Ph, It, stN, U, Re, Lo, Ph, It ←
put_assocT, Re0, R, Re

The rewrite

385rewrite_blocks[], _, _, [], []
386rewrite_blocksblockL, Phis0, Is0, J|Bs0, An, St, blockL, Phis, Is, J|Bs, Map ←
St = st_, _, _, _, CapPhis, _
findallphitmpC, p, CapIns,
memberphitmpT, _, _, Phis0
get_assocT, CapPhis, C−CapIns
,
New
appendPhis0, New, Phis
rewrite_instsIs0, An, St, L, 0, 0, Is, Map, Map1
rewrite_blocksBs0, An, St, Bs, Map1
394rewrite_insts[], _, _, _, _, _, [], Map, Map
395rewrite_instsI0|Is0, An, St, L, Old, New, Is, Map, Rest ←
rewrite_instI0, An, St, Out
emission_sitesL, Old, New, Out, Map, Map1, Next
appendOut, Is1, Is
Old1 is Old + 1
rewrite_instsIs0, An, St, L, Old1, Next, Is1, Map1, Rest
402rewrite_instlettmpD, p, itop, X, _, St, Out ←
St = st_, _, Re, _, _, It
get_assocD, It, I1−I2
!
Out = lettmpI1, l, ptoi, C, lettmpI2, l, sub, X, tmpI1, lettmpD, p, add, C, tmpI2
409rewrite_instI, An, _, I|Shadows ←
I = lettmpA, p, alloc_, _
An = an_, _, _, _, Shadows0
findalllettmpSh, p, alloc8, const8, gen_assocA−_, Shadows0, Sh, Shadows
Shadows ≢ []
!
415rewrite_instI, An, St, I, storep, Cap, tmpSh ←
I = storeW, V, tmpX
W ≢ p
!
St = st_, _, Re, _, _, _
an_stateAn, V, def_
V = tmpT
get_assocT, Re, Cap0
→
Cap = Cap0
else
Cap = const0
423rewrite_instI, An, St, I, lettmpC, p, loadp, tmpSh ←
I = lettmpT, _, load_, tmpX
St = st_, _, _, Lo, _, _
get_assocT, Lo, C
!
427rewrite_instI, _, _, I
429get_assoc_valuetmpT, Re, C ←
get_assocT, Re, C