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.
| Unchecked | Checked |
|---|---|
lettmpD, K, loadT, A | lettmpQ, a, check0, S, read, Al, A, then lettmpD, K, raw_loadT, 0, tmpQ |
storeW, V, A | lettmpQ, a, check0, S, write, Al, A, then raw_storeW, 0, V, tmpQ |
| pointer load/store | the same check, then raw_load_pointer or raw_store_pointer with its explicit pointer_memory_effects/2 sequence |
callRes, tmpP, Args | lettmpQ, 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, X | flightD, 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.
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.
the assoc of every temp name the function defines, so a fresh name avoids them. A valid function reads only temps it defines.
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.
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.
| 135 | letD, p, add, P, X | flightD, add, addressP, X, lowerP |
| 136 | letD, p, sub, P, X | flightD, sub, addressP, X, lowerP |
| 137 | letD, p, copy, tmpN | flightD, copy, addresstmpN, lowertmpN |
| 138 | letD, p, copy, constN | flightD, copy, addressconstN, lowerconstN |
| 139 | letD, p, itop, X | flightD, copy, X, none |
Insts less Insts1 are Access's check, defining tmp(Q), and its raw access.
Q is chkI for the least I >= N0 not among Names, and N is I + 1.
Jump is Jump0, ending the block at index From, with poll(L) for each target L at or before it.