Fil-C on Nix

Filnix wiki

mbrock/filnix/nanofil

pizlonate.pl

Pizlonation: from the unchecked IR to the checked IR (ir.pl), the form the Fil-C backend compiles, named after Fil-C's LLVM pass.

The checks Fil-C requires are implicit in the unchecked stage, where a load or store at a p address means "check, then access". This pass states them: each such access becomes a check, whose class a result is the proven address, and the raw access that reads it.

UncheckedChecked
lettmpD, K, loadT, AlettmpQ, a, check0, S, read, Al, A, then lettmpD, K, raw_loadT, 0, tmpQ
storeW, V, AlettmpQ, a, check0, S, write, Al, A, then raw_storeW, 0, V, tmpQ
pointer load/storethe same check, then raw_load_pointer or raw_store_pointer with its explicit pointer_memory_effects/2 sequence
callRes, tmpP, ArgslettmpQ, f, check_function, tmpP, then raw_callRes, tmpQ, Args
letD, p, add, P, X (also sub)flightD, add, addressP, X, lowerP
letD, p, copy, P (not a symbol)flightD, copy, addressP, lowerP
letD, p, itop, XflightD, copy, X, none

S and Al are the access's size and alignment (ir:access_size_align/3), and Q is a fresh name.

Every edge that does not go forward in block order, to a block at or before its source, gets a pollcheck, pollL, so every cycle polls. In a plain module, whose addresses are not Fil-C pointers, only the polls are added.

This is the naive pass: one check per access, right before it, and every backward edge polls. Merging checks needs a model of free in the interpreter first (docs/pizlonation.md).

The jmp_buf store of setjmp and the cursor of va_arg stay opaque: the backend checks them inside the instruction, as the runtime helpers they call do.

modulepizlonate
useslists
usesassoc
usesformat
usesir
usescfginst_def/2
pizlonate_module+M0, -Mdet

M is the unchecked module M0 with every check explicit and a poll on every edge to a block at or before its source in block order. In a plain module, whose addresses are not Fil-C pointers, only the polls are added.

51pizlonate_moduleM0, moduleItems ←
module_modeM0, Mode
legacy_contextM0, moduleItems0
code_itemsItems0, Code
maplistpizlonated_itemMode, Items0, Code, Groups
appendGroups, Items
56pizlonated_itemMode, All, funcName, Linkage, Ret, Params, Blocks0, Items ←
func_temp_namesParams, Blocks0, Names
findallL−I, nth0I, Blocks0, blockL, _, _, _, Pairs
list_to_assocPairs, Index
context_recordsAll, Name, Rs0
pizlonated_blocksBlocks0, pMode, Names, Index, 0, 0, Blocks, Map, Generated
remap_sitesRs0, Map, Rs1
checked_originsRs0, Rs1, Generated, Rs
with_contextfuncName, Linkage, Ret, Params, Blocks, Rs, Items
65pizlonated_item_, _, dataN, L, A, Is, dataN, L, A, Is
67checked_origins[], Rs, _, Rs ←
!
68checked_originsBefore, Rs0, Generated, Rs ←
site_indexBefore, Sites
origin_idsBefore, Ids0
findallQ, memberproofQ, _, Generated, Temps
retire_valuesRs0, Temps, Ids0, Ids1, Retired
findallL−true, memberpollL, Generated, Pairs
list_to_assocPairs, Polls
excludepolled_sitePolls, Retired, Kept
generated_recordsGenerated, Sites, Ids1, _, Extra
appendKept, Extra, Rs
76generated_records[], _, Ids, Ids, []
77generated_recordsproofQ, Site|Gs, Sites, Ids0, Ids, originId, none, generatedpizlonate, check, Os, valueQ, none, Id|Rs ←
site_originsSites, Site, Os
fresh_originpizlonateQ, Ids0, Ids1, Id
generated_recordsGs, Sites, Ids1, Ids, Rs
82generated_recordspollL|Gs, Sites, Ids0, Ids, originId, none, generatedpizlonate, poll, Os, sitejumpL, Id|Rs ←
site_originsSites, jumpL, Os
fresh_originpollL, Ids0, Ids1, Id
generated_recordsGs, Sites, Ids1, Ids, Rs
87polled_sitePolls, sitejumpL, _ ←
get_assocL, Polls, _
func_temp_names+Params, +Blocks, -Names

the assoc of every temp name the function defines, so a fresh name avoids them. A valid function reads only temps it defines.

92func_temp_namesParams, Blocks, Names ←
findallN−t,
memberparam_, tmpN, Params
or
memberblock_, Phis, Insts, _, Blocks
memberphitmpN, _, _, Phis
or
memberInst, Insts
inst_defInst, N
,
Pairs0
sortPairs0, Pairs
list_to_assocPairs, Names
pizlonated_blocks+Blocks0, +P, +I, +N, -Blocks

Blocks are Blocks0, the first at index I, checked; P is p(Mode, Names, Index), and N counts fresh names. Plain recursion: Trealla copies a closure's arguments at every call.

108pizlonated_blocks[], _, _, _, [], [], []
109pizlonated_blocksblockL, Phis, Insts0, Jump0|Bs0, P, I, N0, blockL, Phis, Insts, Jump|Bs, Map, Generated ←
P = pMode, Names, Index
checked_instsInsts0, Mode, Names, N0, N, Insts, L, 0, 0, Map0, G0
polled_jumpJump0, I, Index, Jump
I1 is I + 1
pizlonated_blocksBs0, P, I1, N, Bs, Map1, G1
Jump0 ≡ Jump
→
G2 = G1
else
G2 = pollL|G1
appendMap0, Map1, Map
appendG0, G2, Generated
checked_insts+Insts0, +Mode, +Names, +N0, -N, -Insts
120checked_insts[], _, _, N, N, [], _, _, _, [], []
121checked_instsInst0|Insts0, Mode, Names, N0, N, Insts, L, Old, New, Map, Generated ←
Mode ≡ filc
checked_accessInst0, Q, Out, []
→
fresh_nameNames, N0, N1, Q
Generated = proofQ, instL, Old|Gs
Mode ≡ filc
flight_instInst0, Inst
→
Out = Inst
N1 = N0
Generated = Gs
else
Out = Inst0
N1 = N0
Generated = Gs
appendOut, Insts1, Insts
emission_sitesL, Old, New, Out, Map, Maps, Next
Old1 is Old + 1
checked_instsInsts0, Mode, Names, N1, N, Insts1, L, Old1, Next, Maps, Gs

Pure pointer operations choose the address calculation and lower independently. No new temporary or safepoint separates the components. A symbol copy still calls its getter; it is not a pure projection.

flight_inst
135letD, p, add, P, XflightD, add, addressP, X, lowerP
136letD, p, sub, P, XflightD, sub, addressP, X, lowerP
137letD, p, copy, tmpNflightD, copy, addresstmpN, lowertmpN
138letD, p, copy, constNflightD, copy, addressconstN, lowerconstN
139letD, p, itop, XflightD, copy, X, none
checked_access+Access, ?Q, -Insts, ?Insts1

Insts less Insts1 are Access's check, defining tmp(Q), and its raw access.

143checked_accessletD, p, loadp, A, Q, lettmpQ, a, check0, Size, read, Align, A, letD, p, raw_load_pointer0, Effects, tmpQ|Is, Is ←
!
access_size_alignp, Size, Align
148checked_accessstorep, V, A, Q, lettmpQ, a, check0, Size, write, Align, A, raw_store_pointer0, V, tmpQ, Effects|Is, Is ←
!
access_size_alignp, Size, Align
153checked_accessletD, K, loadT, A, Q, lettmpQ, a, check0, Size, read, Align, A, letD, K, raw_loadT, 0, tmpQ|Is, Is ←
access_size_alignT, Size, Align
!
157checked_accessstoreW, V, A, Q, lettmpQ, a, check0, Size, write, Align, A, raw_storeW, 0, V, tmpQ|Is, Is ←
access_size_alignW, Size, Align
!
161checked_accesscallRes, tmpP, Args, Q, lettmpQ, f, check_function, tmpP, raw_callRes, tmpQ, Args|Is, Is
164checked_accesslocal_loadD, K, T, Name, Off, Q, lettmpQ, a, check_localName, Off, Size, read, Align, [], Access|Is, Is ←
access_size_alignT, Size, Align
T ≡ p
→
Access = letD, p, raw_load_pointer0, Es, tmpQ
else
Access = letD, K, raw_loadT, 0, tmpQ
171checked_accesslocal_storeW, V, Name, Off, Q, lettmpQ, a, check_localName, Off, Size, write, Align, [], Access|Is, Is ←
access_size_alignW, Size, Align
W ≡ p
→
pointer_memory_effectslocal_store, Es
Access = raw_store_pointer0, V, tmpQ, Es
else
Access = raw_storeW, 0, V, tmpQ
fresh_name+Names, +N0, -N, -Q

Q is chkI for the least I >= N0 not among Names, and N is I + 1.

181fresh_nameNames, N0, N, Q ←
formatatomQ0, "chk~d", N0
N1 is N0 + 1
get_assocQ0, Names, _
→
fresh_nameNames, N1, N, Q
else
Q = Q0
N = N1
polled_jump+Jump0, +From, +Index, -Jump

Jump is Jump0, ending the block at index From, with poll(L) for each target L at or before it.

191polled_jumpgotoL, From, Index, gotoT ←
polled_targetL, From, Index, T
193polled_jumpbranchV, L1, L2, From, Index, branchV, T1, T2 ←
polled_targetL1, From, Index, T1
polled_targetL2, From, Index, T2
196polled_jumpretV, _, _, retV
197polled_jumpretsVs, _, _, retsVs
198polled_jumpret, _, _, ret
199polled_jumpunreachable, _, _, unreachable

Checked IR (a poll target, and no load or store) passes unchanged.

202polled_targetpollL, _, _, pollL ←
!
203polled_targetL, From, Index, T ←
get_assocL, Index, To
To ≤ From
→
T = pollL
else
T = L