Fil-C on Nix

Filnix wiki

mbrock/filnix/nanofil

ir.pl

The nanofil IR: modules of functions and data as ground Prolog terms, the contract between the frontend, the middle end, the interpreter and the backends. This module states its grammar and checks that a module is well formed.

A function is a list of blocks; a block is phis, then instructions, then one jump. Temps are ground names; logic variables never appear in IR terms.

funcName, Linkage, Ret, Params, Blocks
entry block: first, no phis
→
phisphitmpD, K, Pairs
instslet, store, call, loc
jumpgoto, branch, ret ...

Classes

Every value has a class, one letter:

ClassValue
w32-bit integer (a word)
l64-bit integer (a long)
s32-bit float (single)
d64-bit float (double)
xx87 80-bit extended float (C's long double; filc mode only, never a parameter, result, call argument or phi: docs/float.md)
ppointer: an address with its Fil-C capability (filc mode only)
aproven address: a pointer or named-local access that a check has cleared (checked stage only)
fproven function: a p that a function check has cleared (checked stage only)

A store's width may also be b (8 bits) or h (16 bits). A load and ext name the narrow types sb ub sh uh sw uw: signed or unsigned byte, halfword and word.

Items

TermMeans
moduleItemsItems are func/5, data/4, alias/3 and optional context/2 terms
funcName, Linkage, Ret, Params, Blocksa function, fields below
dataName, Linkage, Align, Itemsa data object, fields under Data
aliasName, Linkage, Targeta second symbol Name, of linkage export or weak, for the function or data object Target the module defines (filc mode only; GNU's alias attribute)
contextName, Recordsexplanatory binding, typed-origin, value, alias and site records; see provenance.pl
Field of funcIs
Namean atom, the symbol
Linkageexport, local, or weak (filc mode only): exported, and a strong definition elsewhere replaces it
Retw, l, p, s, d, void, or tupleK1, K2
Paramsa list of paramK, tmpN, K one of w l p s d
Blocksa list of blockLabel, Phis, Insts, Jump, the entry block first

A tupleK1, K2 return is two words, each Ki l, p, s or d: a struct of 9 to 16 bytes, its eightbytes as SysV classes them (filc mode only).

Phis and instructions

TermMeans
phitmpD, K, Pred−ValueD is Value when control came from block Pred
lettmpD, K, Op, ArgsD, of class K, is Op of Args (Ops below)
storeWidth, Value, Addrstore Value at Addr; Width one of b h w l p s d
atomic_storep, Value, Addr(filc mode) store the pointer Value at Addr single-copy atomically, sequentially consistent (Fil-C's filc_store_ptr_atomic)
fencea sequentially consistent fence (mfence)
accessK, Addr, Size, Aligntrap as a K (read or write) access of Size (8, 16, 32 or 64) bytes at Addr with alignment Align (1, 2, 4 or 8) would, and access nothing: a vector's full-width check before its chunks (docs/vector-access.md)
touchtmpQchecked stage: use the proof Q of the check just before it, and do nothing else (pizlonated access)
asm_fixedM, Ins, Outsthe fixed-register instruction M (asm_fixed_regs/3) on w inputs R-V in the registers M reads, defining w temporaries R−tmpD from registers it writes (docs/asm-fixed.md)
local_allocName, Sizecreate a zeroed, exact-sized invocation-local object, without an address value
local_loadtmpD, K, T, Name, Offchecked load semantics at the fixed byte offset Off in Name
local_storeWidth, Value, Name, Offchecked store semantics at the fixed byte offset Off in Name
callRes, Callee, argK, Valuecall Callee: globalF, or (filc mode) a p temp
locFile, Linedoes nothing: the instructions after it, up to the next loc, come from line Line of File
raw_storeWidth, Off, Value, Addrchecked stage: a non-pointer store at Addr+Off that does not check (Stages, below)
raw_store_pointerOff, Value, Addr, Effectschecked stage: a pointer store whose ordered aux/barrier effects are explicit
raw_callRes, Callee, Argschecked stage: call the proven function Callee (class f); argument, result and exception handling are as for call
flighttmpD, Op, Args, Lowerchecked stage: construct a p from a 64-bit address calculation and an independently selected capability

A call's Res is one of resulttmpD, K, void, or tupleresulttmpD1, K1, resulttmpD2, K2.

Named locals are Fil-C-only. Their ground names have a separate namespace from temporaries. Each is declared once in the entry block, before its uses, and lives until this invocation exits. Functions with setjmp cannot contain them. Off is a signed 64-bit integer; an access outside [0, Size) traps, including every access to a zero-sized object. Pointer contents have ordinary capabilities, but a local's own native address is never a p value or heap lower.

flight computes its address with copy, add or sub, using unsigned 64-bit arithmetic. Each argument is an l value or addressP, the address bits of a p value. Lower is none or lowerP, exactly P's capability identity, not an integer bound. The donors may differ: constructing a pair proves nothing about access. No getter, check, memory access or safepoint occurs inside flight; both components become one rooted p result. Projections occur only here and cannot read globals or proof classes.

Jumps

JumpMeans
gotoLgo to L
branchV, L1, L2go to L1 if V is nonzero, else to L2
retVreturn V
retsV1, V2return a tuple
retreturn from a void function
unreachablestop the program (a trap instruction)

A jump's target may be pollL: a pollcheck on the edge, after the phis' copies, then L. A pollcheck is a safepoint: the thread may run signal handlers and answer the collector.

Ops

The Op of a lettmpD, K, Op, Args; op_kind/2 and valid_op/1 state these sets once, op_result_ok/3 and op_arg_classes/4 the classes.

OpResult KArgsMeans
add subany[K, K]; at p, a p and an l offset
mul divw l s d[K, K]
udiv rem urem and or xorw l[K, K]
sar shr shlw l[K, w]the count is read at w
negw l s d[K]
copyany[K]
cmpCond, K1w l; w when K1 is a float[K1, K1]Cond for K1 w or l: eq ne sle slt sge sgt ule ult uge ugt; for s or d: eq ne le lt ge gt o uo
extTl for sw uw; w or l for sh uh sb ub[w]extend the low bytes
extsd[s]single to double
extsx[s]single to extended
extdx[d]double to extended
truncds[d]double to single
truncxs d[x]extended to single or double
stosi stoui / dtosi dtoui / xtosi xtouiw l[s] / [d] / [x]float to signed or unsigned integer
swtof uwtof / sltof ultofs d[w] / [l]signed or unsigned integer to float
castw s l d[s] [w] [d] [l]bitwise: w and s, l and d
loadTas T: l, p, s, d; w or l for a narrow T[Addr]T one of l sw uw sh uh sb ub p s d
allocAlignthe address class[l], the sizeAlign a power of two from 4 to 4096
ptoil[p]filc only
itopp[l]filc only
va_argspconstNfilc only: a cursor into the entry argument snapshot; N, non-negative, is the byte count of the fixed prefix
va_argany[p]filc only: a load through a va_list address, advancing its cursor (reads and writes memory)
setjmpKindw[p, w]: Buf, SaveMaskfilc only, below
checkLo, Hi, Rights, Aligna[p]checked stage, below
check_aheadLo, Hi, Rights, Align, AHia[p]checked stage: checkLo, Hi, Rights, Align of which only [Lo, AHi) is its own access's requirement; the rest is anticipated (docs/schedule.md)
renewLo, Hi, Rights, Aligna[a, p]: Q1, Pchecked stage: renew an earlier heap proof's temporal facts (docs/m7.md, round 4)
renew_hullLo, Hi, Rights, Aligna[a, a, p]: QL, QH, Pchecked stage: a renew whose bounds the hull of two earlier heap proofs of P's object covers (docs/forward.md, multi-source renewal)
half_checkBound, Lo, Hi, Rights, Aligna[a, p]: Q1, Pchecked stage: checkLo, Hi, Rights, Align of P, which tests only the bound Bound, upper or lower; Q1's earlier check of the same object proves the other (docs/half.md)
span_testLo, Hiw[p]checked stage: 1 if the pointer's object contains [P+Lo, P+Hi), else 0; never traps (docs/m7.md, round 5)
renew_ifLo, Hi, Rights, Aligna[p, w]: P, Gchecked stage: checkLo, Hi, Rights, Align of P, whose bounds part a passing span test G proves (docs/m7.md, round 5)
limitSizel[p]checked stage: an opaque snapshot of the pointer's bounds for Size-byte accesses; never traps (docs/bounds.md)
check_limitLo, Hi, Rights, Aligna[p, l]: P, Uchecked stage: checkLo, Hi, Rights, Align of P, its bounds tested against the limit U of P's lower (docs/bounds.md)
check_localName, Off, Bytes, Rights, Aligna[]checked stage: prove exact bounds and alignment for the named object, preserving its identity and offset
atomic_loadpp[p](filc mode) a single-copy atomic, sequentially consistent pointer load (Fil-C's filc_load_ptr_atomic)
casTas loadT[K, K, p]: Expected, New, Addr(filc mode) atomic compare-exchange, below; T one of l sw uw sh uh sb ub p
raw_casT, Offas casT[a, K, K]checked stage: casT at A+Off, right after the check that covers it
raw_loadT, Offas loadT, except p[a]checked stage: a non-pointer load at A+Off that does not check
raw_load_pointerOff, Effectsp[a]checked stage: read aux, resolve the lower, then read the primary address
check_functionf[p]checked stage: trap unless the pointer designates a function object at its exact callable address

casT reads [Expected, New, Addr] and is one sequentially consistent atomic step: it reads the T at Addr, old; if old equals Expected, it writes New there; the result is old, extended as loadT extends it. Equality is bitwise on the low bytes of T, and for p it compares addresses only: capabilities are ignored, so a New may replace an old that has the expected address but another capability, and the result is old's pair, as Fil-C's filc_strong_cas_ptr does. An integer cas needs write rights and its address aligned to its width, even when it fails (x86 lock cmpxchg writes; a misaligned one is a split lock, and Fil-C traps); a pointer cas, the 8-byte slot's. The checked form raw_casT, Off is right after its own check, never a reused proof. A pointer cas is heap only, with the effect list [atomic_cas]: Fil-C's helper checks again, ensures aux (it may exit), barriers New, and boxes the slot; New and Addr stay rooted through it. Every cas ends every live proof. Read-modify-writes other than compare-exchange are loops of cas (c_lower.pl); there is no other atomic read-modify-write term.

setjmpKind: Kind is setjmp, '_setjmp' or sigsetjmp, and SaveMask must be const0 except for sigsetjmp. It returns twice natively; the snapshot and restore contract is in docs/setjmp.md.

checkLo, Hi, Rights, Align reads [p P]. It traps unless P's capability permits Rights (read or write) on [P+Lo, P+Hi) and P+Lo is Align-aligned (Align one of 1, 2, 4, 8, 16); otherwise its result is P, proven accessible.

check_aheadLo, Hi, Rights, Align, AHi reads [p P], with Lo < AHi < Hi, and is exactly checkLo, Hi, Rights, Align of P: its access must lie in [Lo, AHi), and the bytes [AHi, Hi) are checked early. It is valid only where every path from its access reaches an access through P's base that needs those bytes, and reaches it first: before a call or other effect (ahead_transparent/1), a poll edge, an edge back or a return; its own access must be ahead_transparent/1 too (ahead_justified/3, docs/schedule.md). So it traps where the plain check would not only on executions that trap later anyway.

renewLo, Hi, Rights, Align reads [a Q1, p P]: Q1 is the proof of an earlier heap check of P1 that dominates it, whose bounds and alignment still hold since an object's bounds change only when it is freed, and P the pointer of the check it replaces, at a constant distance D from P1 through one base (pointer_distance/4; 0 is same_pointer/3), with its capability. Q1's check covers [Lo+D, Hi+D). It traps as checkLo, Hi, Rights, Align of P would if the object has been freed since, or if Rights is write and the object is read-only; otherwise its result is P, proven accessible. Q1 and P1 have one definition each, Q1's check covers [Lo, Hi) at Align, and the function has no setjmp (stage_func/6).

renew_hullLo, Hi, Rights, Align reads [a QL, a QH, p P] and is a renew of P whose bounds and alignment two earlier heap proofs give together: QL's check proves P's object from at or below P+Lo, QH's to at or above P+Hi, each of a pointer at an exact distance from P through one base (hull_renewable/8, docs/forward.md). It traps as the renew would.

half_checkBound, Lo, Hi, Rights, Align reads [a Q1, p P] and is exactly checkLo, Hi, Rights, Align of P: it traps when the check would, with its reason, and is followed by the access it covers. Q1 is a single-definition heap proof of P1 (a renew's sources), P is at a distance D from P1 through one base (pointer_distance/4), taken signed modulo 2^64, and so has Q1's capability. Q1's check passed for [Lo1, Hi1) of P1. Bound upper needs Lo1 =< Lo + D: P's capability is not null and P+Lo is not below lower, so only the upper end is tested. Bound lower needs Hi + D =< Hi1: P+Hi is not above the upper that check saw, which changes only when the object is freed, so only the lower end and the free flag are tested. D + Lo - Lo1 and D + Hi - Hi1 lie within +-2^62 (near_offset/1) and the function has no setjmp (half_known/6, docs/half.md).

span_testLo, Hi reads [p P0], with integers Lo < Hi. It is 1 when P0's capability is an object's and lower =< P0+Lo < P0+Hi =< upper, addresses modulo 2^64 compared unsigned, and 0 otherwise (a null, function or external capability, a freed object). It never traps and reaches no safepoint.

renew_ifLo, Hi, Rights, Align reads [p P, w G] and is exactly checkLo, Hi, Rights, Align of P: it traps when the check would, with its reason, and is followed by the access it covers. G has one definition, a span test of P0; P is P0 plus an offset through single-definition add, sub and copy flights with P0's lower; the offset's interval (guard_chain/3) shifted by [Lo, Hi) lies in the span test's [Lo, Hi); every name on the way has one definition and the function has no setjmp. When G is 1 only the free flag, the read-only flag for a write, and alignment are tested natively.

limitSize reads [p P0], with 0 < Size < 2^31. Its value is a snapshot of P0's capability and of its object's bounds, in an encoding the backend chooses; it never traps, reaches no safepoint, and is read only as a check_limit's second argument (limit_reads/2). check_limit0, Size, Rights, Align reads [p P, l U] and is exactly check0, Size, Rights, Align of P, followed by the access it covers. U has one definition, a limitSize of P0 with one definition, P has P0's lower through single-definition flights (lower_chain/3), with any address, and the function has no setjmp. Natively the bounds are tested against the snapshot and the free flag (and for a write the read-only flag) at the access: an object's bounds change only when it is freed (docs/m7.md, round 4's lifetime lemma), which sets the free flag.

check_local proves [Off, Off+Bytes) within the named object's exact size and 16-aligned base. Its adjacent raw access has offset zero. Pointer load/store effects are respectively [aux_read, address_read] and [lower_write, address_write]: explicit stack aux has neither a heap header nor a lower box. Validation selects this protocol by proof provenance, not list shape.

Values

ValueMeans
tmpNamea temporary, defined once (SSA)
constIntegera number; at s or d an unsigned IEEE bit pattern (docs/float.md)
globalNamethe address of a function or data object, by its symbol

Data

In dataName, Linkage, Align, Items, Align is a power of two from 1 to 4096, the payload's alignment, and Linkage is export, local, roexport or rolocal, ro for read-only data (in .rodata), or tlsexport or tlslocal (filc mode only) for C's thread-local objects: each thread has its own instance, created and initialized with Items when that thread first takes the symbol's address. The interpreter runs one thread, so there it is an ordinary object. Linkage initctor, Priority or initdtor, Priority (filc mode only) is an entry of the constructor (destructor) table, which the program runs before main (at exit) through the Fil-C runtime: Items is the one itemp, symF, 0 of a function F defined in the module, and Priority an integer from 0 to 65535, lower first, or none, after those with one. It is not an object: nothing may take its address, neither globalName nor another item's symName, _, and the interpreter does not run it. Linkage weak or roweak (filc mode only) is export that a strong definition elsewhere replaces. Linkage externweak (filc mode only), with Items [], declares that Name, which the module does not define, is weak: if no object of the program defines it, globalName is a null pointer. It is not an object. Beside a definition of Name, globalName is the definition, which may not have local linkage: the declaration would make its symbols weak and global, and another object's Name could replace them. Align is 1, 2, 4, 8 or 16. Items is a list of:

ItemMeans
itemW, constNN at width W
itemb, strCharsthe bytes themselves, one character per byte (codes 0..255); a backend escapes them when it prints assembly
iteml, symName, Offthe address of Name plus Off (plain mode)
itemp, symName, Offa pointer with the capability of Name (filc mode only)
zeroNN zero bytes

Each item is one of these, with integer N and Off, an atom Name and W a width of width_bytes/2; a pointer item itemp, sym_, _ starts at a multiple of 8 bytes into the object. No two items of a module, functions, data other than an externweak declaration, and aliases, define the same name (unique_names/1).

Modes

A module is in one of two modes, told apart by what it uses (module_mode/2):

ModeMeans
plainclasses w, l, s and d only. Addresses are l values, and the plain backend (amd64.pl) compiles it to ordinary x86-64.
filcanything of class p, a tuple return, read-only data, a pointer-only op, or a call to a function the module does not define. Addresses are p values (flight pointers with a capability), alloc yields p, loads and stores take a p address, p arithmetic is add or sub of an l offset, and ptoi and itop convert.

In filc mode calls may name functions outside the module, or go through a p value. A call may name data the module defines; it traps (not_a_function), as the function check of native code does. It may not name an alias of data, nor, like a copy, a constructor or destructor table entry. A symbol globalS is read only by lettmpD, p, copy, globalS, which yields a pointer to the data or function object; S may be defined outside the module. See docs/m3.md and docs/m4.md.

Stages

A module is in one of two stages, told apart by its vocabulary:

StageMeans
uncheckedas the frontend and the middle end write it. In filc mode, loadT and store at a p address mean "Fil-C's access check, then the access".
checkedafter pizlonate.pl, what the Fil-C backend compiles: every check is explicit, and every cycle of the CFG has a poll edge.

In the checked stage a check yields a temporary of class a, a proven address, which only raw accesses read: raw_loadT, Off and raw_storeW, Off, V, A. The check that covers an access is its address operand's definition. A raw access its check does not cover is a bug of the pass, which the interpreter reports as the outcome uncheckedWhy.

Pure pointer copies, add/sub and integer-to-pointer conversion use flight in checked IR; ordinary p copy remains only for getters. The middle end runs before pizlonation. Its entry point leaves a directly supplied checked module unchanged, even with -O.

A check_function similarly yields class f, consumed only as the callee of the immediately following raw_call. It proves the target's identity, not a signature match: raw_call still selects fast or generic argument/result handling. A bad raw target reports uncheckednot_a_function; traps from that handling or from the callee remain traps. Direct calls keep call and their ABI thunks.

Pointer accesses carry the complete ordered list described by pointer_memory_effects/2. The list is executed, not an annotation. Each access is one instruction for liveness and layout, not an atomic transaction: aux_ensure may exit/re-enter or fail without returning. rooted_reload reloads the original p pair, not the ephemeral proof. The stored value also remains rooted through the whole instruction.

Neither proof class crosses an intervening safepoint (a call, a pollcheck, alloc, setjmp, va_arg or getter), since a free may make it stale. Each check is followed immediately by its first use. The pointer store's internal ensure-aux/reload sequence is the existing Fil-C exception, not permission to move another effect between the pair. The atomic store's helper call (atomic_write), which may exit in its ensure-aux, is the same exception; both atomic effect lists are heap-only and end every live proof.

A heap check's proof may also be read by later scalar raw accesses that it covers, while its evidence lasts (docs/m7.md): on every path from the check only evidence_preserving/1 instructions and unpolled edges come between, and neither the proof nor the checked pointer is redefined (a phi destination counts). Local and function proofs have one use.

Variable names

In the predicates below: M a module, Fn a function name, K a class, V an IR value, L a label.

useslists
usesassoc
usesordsets
usesdcgs
usescfg
usesprovenancecode_items/2, valid_contexts/1
class?K, ?Kind, ?Bitsnondet

K is a value class of Kind (int, pointer or float), Bits wide. Every other fact about classes below follows from this table.

class
424wint32
425lint64
426ppointer64
427sfloat32
428dfloat64
429xfloat80
431any_classK ←
classK, _, _
432int_classK ←
classK, int, _
433float_classK ←
classK, float, _
434numeric_classK ←
435numeric_classK ←
class_bits+K, -Bitssemidet

Bits is the width in bits of the numeric class K (w, l, s or d).

441class_bitsK, Bits ←
modulus+K, -Modsemidet

Mod is 2^bits(K): values of class K are the integers 0..Mod-1. A p value's address has the modulus of l.

450modulusK, Mod ←
classK, _, Bits
Mod is 1 << Bits
half_modulus+K, -Halfsemidet

Half is half the modulus of the integer class K: the values Half..Mod-1 are negative when read as signed.

459half_modulusK, Half ←
classK, int, Bits
Half is 1 << Bits−1
width_bytes+Width, -Sizesemidet

A store of Width (b, h, or a class) writes Size bytes.

width_bytes
467b1
468h2

class/3's widths, Bits // 8, as facts so that a call is deterministic.

470w4
471l8
472p8
473s4
474d8
475x10
addr_class+Mode, ?Ksemidet

K is the class of an address in a module of Mode (plain or filc).

481addr_classplain, l
482addr_classfilc, p
op_arity+Op, -Nsemidet

Op takes N arguments; it fails for anything that is not an Op of the grammar.

489op_arityOp, N ←
op_kindOp, Kind
kind_arityKind, N
kind_arity
493additive2
494alu2
495shift2
496cmp2
497neg1
498copy1
499ptoi1
500itop1
501va_args1
502va_arg1
503setjmp2
504ext1
505load1
506atomic_load1
507cas3
508raw_cas3
509alloc1
510float_convert1
511float_to_int1
512int_to_float1
513cast1
514check1
515check_ahead1
516renew2
517renew_hull3
518half_check2
519span_test1
520renew_if2
521limit1
522check_limit2
523check_local0
524check_function1
525ld1
526ld_pointer1
valid_op+Opsemidet

Op is an instruction of the IR: one of op_kind/2's, with operands from the sets below where it has them.

533valid_opcmpCond, K ←
!
comparisonCond, K
534valid_oploadT ←
535valid_opallocAlign ←
536valid_opcheckLo, Hi, Rights, Align ←
!
integerLo
integerHi
Lo < Hi
memberchkRights, read, write
memberchkAlign, 1, 2, 4, 8, 16
540valid_opcheck_aheadLo, Hi, Rights, Align, AHi ←
!
valid_opcheckLo, Hi, Rights, Align
integerAHi
Lo < AHi
AHi < Hi
543valid_oprenewLo, Hi, Rights, Align ←
!
valid_opcheckLo, Hi, Rights, Align
545valid_oprenew_hullLo, Hi, Rights, Align ←
!
valid_opcheckLo, Hi, Rights, Align
547valid_ophalf_checkBound, Lo, Hi, Rights, Align ←
!
memberchkBound, upper, lower
valid_opcheckLo, Hi, Rights, Align
550valid_opspan_testLo, Hi ←
!
integerLo
integerHi
Lo < Hi
Lo ≥ -9223372036854775808
Hi ≤ 9223372036854775807
553valid_oprenew_ifLo, Hi, Rights, Align ←
!
valid_opcheckLo, Hi, Rights, Align
555valid_oplimitSize ←
!
integerSize
Size > 0
Size ≤ 2147483647
557valid_opcheck_limitLo, Hi, Rights, Align ←
!
valid_opcheckLo, Hi, Rights, Align
559valid_opcheck_localName, Off, Bytes, Rights, Align ←
!
groundName
integerBytes
Bytes > 0
Bytes ≤ 2147483647
memberchkRights, read, write
memberchkAlign, 1, 2, 4, 8, 16
562valid_opraw_loadT, Off ←
!
T ≢ p
integerOff
563valid_opcasT ←
564valid_opraw_casT, Off ←
!
integerOff
565valid_opraw_load_pointerOff, Effects ←
!
integerOff
or
pointer_memory_effectslocal_load, Effects
or
pointer_memory_effectsatomic_load, Effects
569valid_opOp ←
op_kindOp, _
pointer_memory_effects?Access, ?Effectsnondet

Effects is the closed, ordered protocol of a pointer access of kind Access: load, store, local_load, local_store, atomic_load, atomic_store or cas. For an ordinary heap load, aux_read reads the encoded lower (or none) and lower_resolve unboxes it without taking the box's address. A store ensures aux (may exit), barriers the value's lower (may call, cannot exit), reloads its destination p pair from rooted storage, then writes the ordinary lower before the primary address. No effect list may omit or reorder these obligations; the backend executes the list, it is not an annotation.

pointer_memory_effects
582loadaux_read, lower_resolve, address_read
583storeaux_ensure, lower_barrier, rooted_reload, lower_write, address_write

Proven nonescaping memory has an explicit stack aux, never a heap header or atomic box. These protocols are selected by check_local's provenance, not merely by a shorter effect list supplied by the caller.

588local_loadaux_read, address_read
589local_storelower_write, address_write

An atomic access delegates its whole protocol to Fil-C's helper, after the inline check: atomic_read is filc_load_ptr_atomic (the address, then the lower or the atomic box's pair, fenced); atomic_write is filc_store_ptr_atomic (its own check, ensure-aux, which may exit, the store barrier, a box allocated or reused, the pair stored atomically, then the address). Heap storage only: stack aux has no boxes.

596atomic_loadatomic_read
597atomic_storeatomic_write

atomic_cas is filc_strong_cas_ptr_with_manual_tracking (its own check, ensure-aux, which may exit, the barrier of New, a box, then the address), named for raw_cas(p, _), which has no list of its own.

601casatomic_cas
access_size_align?X, ?Size, ?Alignnondet

A load of type X or a store of width X accesses Size bytes, and Fil-C requires its address to be Align-aligned: 8 for a pointer, whose capability lives in the aux word of its 8-byte slot, else 1.

609access_size_alignp, 8, 8
610access_size_aligncasT, Size, Size ←
611access_size_alignx, 10, 8
612access_size_alignX, Size, 1 ←
X ≢ p
X ≢ x
or
narrow_typeX, Size, _
cmp_condeqneslesltsgesgtuleultugeugt
621comparisonCond, K ←
622comparisonCond, K ←
float_condeqneleltgegtouo
narrow_type?T, ?Bytes, ?Signednessnondet

The extension (and load) type T takes the low Bytes bytes of an integer and extends them as Signedness says.

narrow_type
630sw4signed
630uw4unsigned
631sh2signed
631uh2unsigned
632sb1signed
632ub1unsigned
635load_typel
635load_typep
636load_typed
637load_types
638load_typex
639load_typeT ←
cas_type?T

a cas may be of type T, cas_bytes(+T, -Bytes) wide.

642cas_typel
642cas_typep
643cas_typeT ←
644cas_bytesl, 8
644cas_bytesp, 8
645cas_bytesT, B ←
alloc_align?Align

, data_align(?Align): an alloc's alignment is a power of two from 4 to 4096, a data item's from 1 to 4096. Above 16, Fil-C allocates with filc_allocate_with_alignment and pads a data object before its header (docs/abi-findings.md).

651alloc_alignAlign ←
Align ≥ 4
652data_alignAlign ←
integerAlign
!
Align ≥ 1
Align ≤ 4096
Align /\ Align−1 =:= 0
654data_alignAlign ←
between0, 12, E
Align is 1 << E
op_kind?Op, ?Kindnondet

Kind is the category of Op that its consumers dispatch on, so that each can case-split on Kind in clause heads. Deterministic in mode (+, -): one fact per op, indexed on the first argument.

additive  add sub: integer, or (filc) a p plus or minus an l offset
alu       mul div udiv rem urem and or xor: binary, integer only
shift     sar shr shl: the count is read at w
neg copy ptoi itop  each its own kind; copy may be of class p
cmp ext load alloc  compounds
check ld   the checked stage: a check and a raw load
669op_kindadd, additive
670op_kindsub, additive
671op_kindmul, alu
672op_kinddiv, alu
673op_kindudiv, alu
674op_kindrem, alu
675op_kindurem, alu
676op_kindand, alu
677op_kindor, alu
678op_kindxor, alu
679op_kindsar, shift
680op_kindshr, shift
681op_kindshl, shift
682op_kindneg, neg
683op_kindcopy, copy
684op_kindptoi, ptoi
685op_kinditop, itop
686op_kindva_args, va_args
687op_kindva_arg, va_arg
688op_kindsetjmpKind, setjmp ←
memberKind, setjmp, '_setjmp', sigsetjmp
689op_kindexts, float_convert
690op_kindtruncd, float_convert
691op_kindextd, float_convert
692op_kindtruncx, float_convert
693op_kindxtosi, float_to_int
694op_kindxtoui, float_to_int
695op_kindstosi, float_to_int
696op_kindstoui, float_to_int
697op_kinddtosi, float_to_int
698op_kinddtoui, float_to_int
699op_kindswtof, int_to_float
700op_kinduwtof, int_to_float
701op_kindsltof, int_to_float
702op_kindultof, int_to_float
703op_kindcast, cast
704op_kindcmp_, _, cmp
705op_kindextT, ext ←
706op_kindload_, load
707op_kindatomic_loadp, atomic_load
708op_kindcas_, cas
709op_kindraw_cas_, _, raw_cas
710op_kindalloc_, alloc
711op_kindcheck_, _, _, _, check
712op_kindcheck_ahead_, _, _, _, _, check_ahead
713op_kindrenew_, _, _, _, renew
714op_kindrenew_hull_, _, _, _, renew_hull
715op_kindhalf_check_, _, _, _, _, half_check
716op_kindspan_test_, _, span_test
717op_kindrenew_if_, _, _, _, renew_if
718op_kindlimit_, limit
719op_kindcheck_limit_, _, _, _, check_limit
720op_kindcheck_local_, _, _, _, _, check_local
721op_kindcheck_function, check_function
722op_kindraw_load_, _, ld
723op_kindraw_load_pointer_, _, ld_pointer
func_named+M, +Name, -Funcsemidet

Func is the func/5 term that defines Name in the module M, or in the symbols(Index) that module_symbols/2 made of it.

730func_namedmoduleItems, Name, Func ←
Func = funcName, _, _, _, _
memberchkFunc, Items
733func_namedsymbolsIndex, Name, Func ←
get_assocfuncName, Index, fFunc, _
data_named+M, +Namesemidet

The module M, or the one module_symbols/2 indexed, has data named Name.

741data_namedmoduleItems, Name ←
memberdataName, Linkage, _, _, Items
Linkage ≢ externweak
!
743data_namedsymbolsIndex, Name ←
get_assocdataName, Index, _

variadic_named(+M, +Name) is semidet.

The function named Name in M, or in the module module_symbols/2
indexed, is variadic (variadic_func/1), found without rescanning its
blocks at every call to it.
weak_defined+M, +Namesemidet

M, or the module module_symbols/2 indexed, defines Name with linkage weak (or roweak), which another object's definition may replace.

757weak_definedSymbols, Name ←
func_namedSymbols, Name, func_, weak, _, _, _
→
true
else
data_itemSymbols, Name, data_, Linkage, _, _
memberchkLinkage, weak, roweak
762data_itemmoduleItems, Name, D ←
D = dataName, Linkage, _, _
memberD, Items
Linkage ≢ externweak
!
764data_itemsymbolsIndex, Name, D ←
get_assocdataName, Index, D
weak_declared+M, +Namesemidet

M, or the module module_symbols/2 indexed, declares Name weak (linkage externweak) without defining it.

772weak_declaredmoduleItems, Name ←
memberchkdataName, externweak, _, [], Items
774weak_declaredsymbolsIndex, Name ←
get_assocweakName, Index, _
777variadic_namedmoduleItems, Name ←
func_namedmoduleItems, Name, F
779variadic_namedsymbolsIndex, Name ←
get_assocfuncName, Index, f_, variadic
module_symbols+M, -Symbolsdet

Symbols indexes the functions and data of module M by name, for func_named/3 and data_named/2: a large module (SQLite) calls thousands of functions, and a scan of its items per call would be quadratic. As memberchk/2 does, the first item of a name wins; in a valid module each name has one item (unique_names/1).

790module_symbolsmoduleItems, symbolsIndex ←
findallKey−Value,
memberItem, Items
symbol_entryItem, Key, Value
,
Pairs0
keysortPairs0, Pairs1stable: the first of a name stays first
first_of_keysPairs1, Pairs
ord_list_to_assocPairs, Index
796symbol_entryF, funcName, fF, V ←
F = funcName, _, _, _, _
→
V = variadic
else
V = fixed
799symbol_entryA, aliasName, A ←
A = aliasName, _, _
801symbol_entryD, Key, D ←
D = dataName, Linkage, _, _
Linkage ≡ externweak
→
Key = weakName
else
Key = dataName
805first_of_keys[], []
806first_of_keysK−V|Ps0, K−V|Ps ←
drop_keyK, Ps0, Ps1
807drop_keyK, K1−_|Ps0, Ps ←
K1 ≡ K
!
drop_keyK, Ps0, Ps
808drop_key_, Ps, Ps
variadic_func+Funcsemidet

Func takes the entry argument snapshot (va_args) of a variadic function. The snapshot's metadata is retained even if its cursor is unused.

815variadic_funcfunc_, _, _, _, Blocks ←
memberblock_, _, Insts, _, Blocks
memberlet_, p, va_args, _, Insts
!
setjmp_func+Funcsemidet

Func calls setjmp somewhere, reachable or not. Nonlocal returns restore compiler slots, not mutable local memory, so transformations must keep this boundary even in unreachable blocks: the passes leave such a function alone and the backend gives it the reference emission (docs/setjmp.md).

826setjmp_funcfunc_, _, _, _, Blocks ←
memberblock_, _, Insts, _, Blocks
memberlet_, _, setjmp_, _, Insts
!

Module mode

module_mode+M, -Modedet

Mode is filc when anything in M has class p, uses a pointer-only op, or calls a function M does not define (which only the Fil-C ABI can reach), and plain otherwise.

The mode is inferred rather than stated: M uses a feature only filc mode has, or it does not. Every rule above is such a feature, the external call as much as class p. Stating the mode would mean a module(Mode, Items) term or a Mode argument for valid_module/1, run/5, promote and the backends, and a mode clause in each IR file, for no case the inference gets wrong: a C module is filc unless it uses none of these features (no memory, no pointers, no external calls), and then validation, promotion and the interpreter treat it alike in both modes; nanofil cc picks the Fil-C backend itself.

848module_modemoduleItems, Mode ←
memberItem, Items
→
Mode = filc
memberfunc_, _, _, _, Blocks, Items
memberblock_, _, Insts, _, Blocks
membercall_, globalCallee, _, Insts
¬memberchkfuncCallee, _, _, _, _, Items
→
Mode = filc
else
Mode = plain
pointer_item+Item

Item has class p somewhere, or is read-only, weak or thread-local data or a weak function, which only filc mode models. The cuts commit to the first witness.

862pointer_itemfunc_, Linkage, Ret, Params, Blocks ←
Linkage ≡ weak
or
Ret ≡ p
or
Ret = tuple_
or
memberparamp, _, Params
or
memberblock_, Phis, Insts, _, Blocks
memberphi_, p, _, Phis
or
memberInst, Insts
!
873pointer_itemalias_, _, _
874pointer_itemdata_, Linkage, _, Items ←
Linkage = ro_
or
Linkage = tls_
or
Linkage ≡ weak
or
Linkage = extern_
or
memberchkitemp, _, Items
!
pointer_inst+Inst

Inst produces, stores, passes or calls through a p value, or converts one (ptoi). Several clauses may hold for one Inst.

885pointer_instlet_, p, _, _
886pointer_instlet_, x, _, _
887pointer_inststorex, _, _
888pointer_instlet_, _, ptoi, _
889pointer_instlet_, _, setjmp_, _
890pointer_inststorep, _, _
891pointer_instatomic_store_, _, _
892pointer_instcallresult_, p, _, _
893pointer_instcalltuple_, _, _
894pointer_instcall_, _, Args ←
memberchkargp, _, Args
895pointer_instcall_, tmp_, _
896pointer_instlet_, K, _, _ ←
897pointer_instraw_store_, _, _, _
898pointer_instraw_store_pointer_, _, _, _
899pointer_instlet_, _, raw_load_pointer_, _, _
900pointer_instraw_call_, _, _
901pointer_instflight_, _, _, _
902pointer_instlocal_alloc_, _
903pointer_instlocal_load_, _, _, _, _
904pointer_instlocal_store_, _, _, _

Validation

valid_module+Msemidet

M is a well-formed module. When it is not, throws nanofil_error(Message) describing the first problem found, so callers get a diagnostic rather than a silent failure. A few terms outside the grammar (an item, instruction or jump of no known shape) make it fail instead.

Functions are checked against a context

ctx(Symbols, Fn, Ret, LabelSet, TempClasses, PredMap, Mode)

where Symbols is the module's module_symbols/2, and whose three assocs map each label to t, each temporary to its class, and each label to its sorted predecessors.

921valid_moduleM ←
M = moduleAll
code_itemsAll, Items
module_stageM, Stage
forall/2: no choicepoint per item keeps its garbage alive.
module_symbolsM, Symbols
forallmemberItem, Items, valid_itemSymbols, Mode, Stage, Item
unique_names+Items

no two items define the same name. Functions, data and aliases share one namespace; an extern(weak) declaration defines nothing. The interpreter's layout, the symbol table and the assembler would otherwise each pick a different item of the name.

936unique_namesItems ←
findallN,
memberI, Items
,
Ns
msortNs, Sorted
append_, N, N1|_, Sorted
N ≡ N1
→
fn_errorN, '~w is defined more than once', N
else
true
944defined_namefuncN, _, _, _, _, N
945defined_namedataN, L, _, _, N ←
L ≢ externweak
946defined_namealiasN, _, _, N
module_stage+M, -Stagedet

Stage is checked when M uses any word of the checked stage (a check, renew, span test, raw access, flight or a poll edge), else unchecked. Like the mode, the stage is read off the vocabulary, never stated.

953module_stagemoduleItems, Stage ←
memberfunc_, _, _, _, Blocks, Items
memberblock_, _, Insts, Jump, Blocks
memberInst, Insts
or
→
Stage = checked
else
Stage = unchecked
checked_instlet_, _, check_, _, _, _, _let_, _, check_ahead_, _, _, _, _, _let_, _, renew_, _, _, _, _let_, _, renew_hull_, _, _, _, _let_, _, half_check_, _, _, _, _, _let_, _, span_test_, _, _let_, _, renew_if_, _, _, _, _let_, _, limit_, _let_, _, check_limit_, _, _, _, _let_, _, check_local_, _, _, _, _, _let_, _, check_function, _let_, _, raw_load_, _, _let_, _, raw_load_pointer_, _, _let_, _, raw_cas_, _, _raw_store_, _, _, _raw_store_pointer_, _, _, _raw_call_, _, _flight_, _, _, _touch_
checked_jumpgotopoll_branch_, poll_, _branch_, _, poll_
valid_item+M, +Mode, +Stage, +Itemsemidet

Item, an item of M, is well formed.

990valid_itemSymbols, Mode, Stage, funcName, Linkage, Ret, Params, Blocks ←
Blocks ≡ []
→
fn_errorName, 'function has no blocks', []
else
true
memberchkLinkage, export, local
→
true
Linkage ≡ weak
→
Mode ≡ filc
→
true
else
fn_errorName, 'weak linkage needs a Fil-C module', []
else
fn_errorName, 'invalid function linkage ~w', Linkage
or
Ret ≡ void
or
Ret = tuple_
→
true
else
fn_errorName, 'invalid return class ~w', Ret
Ret ≡ x
or
memberparamx, _, Params
or
memberblock_, Phis, Is, _, Blocks
memberphi_, x, _, Phis
or
membercallCR, _, CA, Is
CR = result_, x
or
memberchkargx, _, CA
→
fn_errorName, 'class x is a parameter, result, call argument or phi', []
else
true
Ret = tupleKs
¬
Ks = _, _
forallmemberK, Ks, memberchkK, l, p, s, d
→
fn_errorName, 'a tuple return is two words of class l, p, s or d, not ~w', Ks
else
true
findallLabel, memberblockLabel, _, _, _, Blocks, Labels
msortLabels, Sorted
sortLabels, Sorted
→
true
else
fn_errorName, 'duplicate block label', []
temp_defsParams, Blocks, Defs0
sortDefs0, Defs
Labels, definitions and predecessors are indexed once, so checking
a function stays near linear in its size.
findallLabel−t, memberLabel, Labels, LabelPairs
list_to_assocLabelPairs, LabelSet
list_to_assocDefs, TempClasses
predecessorsBlocks, PredMap
Ctx = ctxSymbols, Name, Ret, LabelSet, TempClasses, PredMap, Mode
forall/2: validation binds nothing, and a choicepoint per block
would keep a large function's (sqlite3VdbeExec's) garbage alive.
forallmemberBlock, Blocks, valid_blockCtx, Block
Blocks = blockEntry, _, _, _|_
memberblock_, _, _, Jump, Blocks
jump_targetJump, Entry
→
fn_errorName, 'jump to the entry block ~w', Entry
else
true
proof_class_defsName, Params, Blocks
definitions_dominateName, Params, Blocks
valid_localsName, Params, Blocks
stage_funcStage, Symbols, Name, Mode, Params, Blocks
1041valid_itemSymbols, Mode, _, dataName, Linkage, Align, Items ←
integerAlign
→
true
else
fn_errorName, 'unsupported data alignment ~w', Align
memberchkLinkage, export, local, roexport, rolocal
→
true
memberchkLinkage, weak, roweak
→
Mode ≡ filc
→
true
else
fn_errorName, 'weak linkage needs a Fil-C module', []
Linkage ≡ externweak
→
Mode ≡ filc
Items ≡ []
→
true
else
fn_errorName, 'a weak declaration has no items, in a Fil-C module', []
local_globalSymbols, Name
→
fn_errorName, 'a weak declaration of ~w, which the module defines with local linkage', Name
else
true
memberchkLinkage, tlsexport, tlslocal
→
Mode ≡ filc
→
true
else
fn_errorName, 'thread-local data needs a Fil-C module', []
Linkage = initKind, Prio
memberchkKind, ctor, dtor
Prio ≡ none
→
true
else
integerPrio
between0, 65535, Prio
→
Mode ≡ filc
Items = itemp, symF, 0
→
true
else
fn_errorName, 'a ~w entry is one function in a Fil-C module', Kind
func_namedSymbols, F, _
→
true
else
fn_errorName, 'a ~w entry names ~w, which is no function of the module', Kind, F
else
fn_errorName, 'invalid data linkage ~w', Linkage
forall
memberitemK, V, Items
,
V = constN
integerN
modulusK, Mod
N ≥ 0
N < Mod
→
true
else
fn_errorName, 'invalid float data ~w', V
Mode ≡ plain
memberchkitemp, _, Items
→
fn_errorName, 'pointer data items need a Fil-C module', []
Mode ≡ filc
memberchkiteml, symSym, _, Items
→
fn_errorName, 'a pointer in a Fil-C module is item(p, sym(~w, _))', Sym
else
true
forallmemberitem_, strCs, Items,
maplistbyte_char, Cs
→
true
else
fn_errorName, 'str/1 holds characters of codes 0..255', []
forallmemberItem, Items,
→
true
else
fn_errorName, 'invalid data item ~w', Item
forallmemberitem_, symS, _, Items,
data_itemSymbols, S, data_, init_, _, _, _
→
fn_errorName, 'sym(~w, _) names a constructor or destructor table entry', S
else
true
pointer_items_alignedName, Items, 0

An alias names a function or a data object the module defines, its target, and no other item defines its name. Its kind is the target's: a call names it only if the target is a function.

1097valid_itemSymbols, Mode, _, aliasName, Linkage, Target ←
Mode ≡ filc
→
true
else
fn_errorName, 'an alias needs a Fil-C module', []
memberchkLinkage, export, weak
→
true
else
fn_errorName, 'invalid alias linkage ~w', Linkage
func_namedSymbols, Name, _
or
data_namedSymbols, Name
→
fn_errorName, 'an alias and a definition share the name ~w', Name
else
true
func_namedSymbols, Target, _
→
true
data_itemSymbols, Target, data_, TL, _, _
¬memberchkTL, export, local, weak, ro_
→
fn_errorName, 'an alias of ~w data is not supported', TL
data_namedSymbols, Target
→
Symbols = symbolsIndex
gen_assocfuncF, Index, ffunc_, _, _, _, Bs, _
F ≢ Name
memberblock_, _, Is, _, Bs
memberchkcall_, globalName, _, Is
→
fn_errorName, 'a call names ~w, an alias of the data ~w', Name, Target
else
true
else
fn_errorName, 'alias target ~w is not defined in the module', Target
data_item_shape+Item

Item is one of the header's data items.

1122data_item_shapeitemW, constN ←
atomW
integerN
1123data_item_shapeitemb, strCs ←
is_listCs
1124data_item_shapeitemW, symS, Off ←
memberchkW, l, p
atomS
integerOff
1125data_item_shapezeroN ←
integerN
N ≥ 0
pointer_items_aligned+Name, +Items, +Off

each pointer item of Items, from Off bytes into the object, starts at a multiple of 8, as the layout of its capability requires.

1130pointer_items_aligned_, [], _
1131pointer_items_alignedName, Item|Items, Off0 ←
Item = itemp, sym_, _
Off0 mod 8 ≠ 0
→
fn_errorName, 'pointer data item at offset ~w is not 8-aligned', Off0
else
true
data_item_sizeItem, Size
Off is Off0 + Size
pointer_items_alignedName, Items, Off
1140data_item_sizezeroN, N
1141data_item_sizeitemb, strCs, N ←
!
lengthCs, N
1142data_item_sizeitemW, _, N ←
byte_char+C

C is a character that stands for one byte.

1145byte_charC ←
atomC
atom_lengthC, 1
char_codeC, N
N ≤ 255
local_objects+Func, -Objectsdet

Objects are Name-Size for Func's local_alloc declarations, in instruction order. Their names are a namespace of their own, never ordinary values.

1152local_objectsfunc_, _, _, _, Blocks, Objects ←
findallName−Size,
memberblock_, _, Is, _, Blocks
memberlocal_allocName, Size, Is
,
Objects
1157local_offsetOff ←
integerOff
Off ≥ -9223372036854775808
Off < 9223372036854775808
1159valid_localsFn, Params, Blocks ←
F = funcFn, local, void, Params, Blocks
local_objectsF, Objects
findallN, memberN−_, Objects, Names
sortNames, Unique
lengthNames, Count
lengthUnique, Count
→
true
else
fn_errorFn, 'duplicate local allocation', []
Objects ≢ []
→
fn_errorFn, 'local storage in a returns-twice function', []
else
true
Blocks = blockEntry, _, _, _|_
findallN−I,
memberblockEntry, _, Is, _, Blocks
nth0I, Is, local_allocN, _
,
Declared
lengthDeclared, Count
→
true
else
fn_errorFn, 'local allocation outside the entry block', []
forall
memberblockL, _, Is, _, Blocks
nth0I, Is, Ins
,
memberchkN−At, Declared
L ≢ Entry
or
At < I
→
true
else
fn_errorFn, 'local ~w is used before its declaration', N
local_reference
1177local_load_, _, _, N, _N
1178local_store_, _, N, _N
1179let_, _, check_localN, _, _, _, _, _N
temp_defs+Params, +Blocks, -Defsdet

Defs are Name-Class for every definition of a temporary: parameters, then each block's phis and instructions, duplicates kept.

1185temp_defsParams, Blocks, Defs ←
findallN−K, memberparamK, tmpN, Params, ParamDefs
One pass over each block's instructions, not one per form.
findallN−K,
memberblock_, Phis, Insts, _, Blocks
memberphitmpN, K, _, Phis
or
memberI, Insts
,
BlockDefs
appendParamDefs, BlockDefs, Defs
1196inst_temp_deflettmpN, K, _, _, N, K
1197inst_temp_deflocal_loadtmpN, K, _, _, _, N, K
1198inst_temp_defflighttmpN, _, _, _, N, p
1199inst_temp_defasm_fixed_, _, Outs, N, w ←
member_−tmpN, Outs
1200inst_temp_defI, N, K ←
Res = resulttmpN, K
or
Res = tupleTos
memberresulttmpN, K, Tos

Both call forms have the same result and argument vocabulary; only the callee's proof obligation differs.

1206call_result_argscallRes, _, Args, Res, Args
1207call_result_argsraw_callRes, _, Args, Res, Args

Defs is sorted, so two classes for one temporary are adjacent.

1210check_temp_classesFn, Defs ←
forallmember_−K, Defs,
→
true
else
fn_errorFn, 'invalid class ~w', K
append_, N−K1, N−K2|_, Defs
K1 ≢ K2
→
fn_errorFn, 'tmp(~w) has classes ~w and ~w', N, K1, K2
else
true
predecessors+Blocks, -PredMap

each label's sorted predecessors.

1219predecessorsBlocks, PredMap ←
findallTarget−Pred,
memberblockPred, _, _, Jump, Blocks
jump_targetJump, Target
,
Edges0
sortEdges0, Edges
empty_assocPredMap0
foldladd_predecessor, Edges, PredMap0, PredMap
1229add_predecessorTarget−Pred, PredMap0, PredMap ←
get_assocTarget, PredMap0, Preds0
→
true
else
Preds0 = []
appendPreds0, Pred, Preds
put_assocTarget, PredMap0, Preds, PredMap
valid_block+Ctx, +Blocksemidet

Block's phis cover exactly its predecessors, and its instructions and jump are well formed.

1238valid_blockCtx, blockLabel, Phis, Insts, Jump ←
Ctx = ctx_, Fn, _, _, _, PredMap, _
get_assocLabel, PredMap, Preds
→
true
else
Preds = []
maplistvalid_phiCtx, Label, Preds, Phis
maplistvalid_instCtx, Insts
valid_jumpCtx, Jump
Phis ≢ []
Preds ≡ []
→
fn_errorFn, 'phi in block ~w, which has no predecessors', Label
else
true
jump_target+Jump, -Lnondet

Jump can branch to the label L, with or without a pollcheck.

1252jump_targetgotoT, L ←
1253jump_targetbranch_, T, _, L ←
1254jump_targetbranch_, _, T, L ←
temp_class?K

a temporary may have a value class or a proof class. Proofs live in no memory and are not values of C.

1258temp_classK ←
1259temp_classK ←
1261proof_classa
1262proof_classf
proof_class_defs+Fn, +Params, +Blocksdet

Only a check defines a proof temporary, and nothing passes one as a call argument. Phis, parameters and call results would carry a proof across an edge or a safepoint. (A check's own class is op_result_ok/3's.)

1270proof_class_defsFn, Params, Blocks ←
memberparamK, _, Params
or
memberblock_, Phis, Insts, _, Blocks
memberphi_, K, _, Phis
or
memberI, Insts
call_result_argsI, Res, Args
Res = result_, K
or
Res = tupleTos
memberresult_, K, Tos
or
memberargK, _, Args
→
fn_errorFn, 'class ~w is only a check\'s result', K
else
true
definitions_dominate+Fn, +Params, +Blocksdet

In every reachable block, each use of a temporary is dominated by one of its definitions: a parameter, an earlier instruction or phi of the same block, or one in a block that strictly dominates it. A phi reads its value at the end of the predecessor, if that is reachable. A temporary may be defined more than once (tests/fixtures/memory.ir); one dominating definition is enough. Unreachable blocks are not checked.

1294definitions_dominateFn, Params, Blocks ←
func_cfgBlocks, Cfg
cfg_idomsCfg, Idom
dom_intervalsCfg, Idom, Intervals
findallN−param, memberparam_, tmpN, Params, ParamSites
block_sitesBlocks, Pairs0, []
appendParamSites, Pairs0, Pairs
keysortPairs, Sorted
group_sitesSorted, Grouped
list_to_assocGrouped, Sites
findallL−End,
memberblockL, _, Insts, _, Blocks
lengthInsts, End
,
Ends0
list_to_assocEnds0, Ends
dominated_blocksBlocks, domFn, Sites, Intervals, Ends
block_sites+Blocks//

N-at(L, I) for each definition of tmp(N), at index I of block L, a phi's at -1.

1311block_sites[] ⟶
[]
1312block_sitesblockL, Phis, Insts, _|Bs ⟶
phi_sitesPhis, L
inst_sitesInsts, L, 0
1317phi_sites[], _ ⟶
[]
1318phi_sitesphitmpN, _, _|Phis, L ⟶
N−atL, -1
phi_sitesPhis, L
1320inst_sites[], _, _ ⟶
[]
1321inst_sitesInst|Insts, L, I ⟶
findallN−atL, I, inst_defInst, N, Defs
I1 is I + 1
seqDefs
inst_sitesInsts, L, I1
group_sites+Pairs, -Groups

Pairs N-Site, sorted by N, grouped as N-Sites.

1329group_sites[], []
1330group_sitesN−S|Ps, N−S|Ss|Gs ←
same_keyN, Ps, Ss, Rest
group_sitesRest, Gs
1334same_keyN, N1−S|Ps, S|Ss, Rest ←
N1 ≡ N
!
same_keyN, Ps, Ss, Rest
1335same_key_, Ps, [], Ps
dominated_blocks+Blocks, +D

every use in the reachable blocks of Blocks (those with a dominator-tree interval) is dominated: an instruction's at its index, the jump's at the end of the block, and a phi's operand at the end of its predecessor, if that is reachable. D is dom(Fn, Sites, Intervals, Ends), Ends the number of instructions of each block. Plain recursion, not maplist/3 over closures, which Trealla copies with D at every call.

1344dominated_blocks[], _
1345dominated_blocksblockL, Phis, Insts, Jump|Bs, D ←
D = dom_, _, Intervals, _
get_assocL, Intervals, _
→
dominated_instsInsts, 0, End, L, D
jump_usesJump, Us
dominated_usesUs, L, End, D
else
true
1356dominated_insts[], I, I, _, _
1357dominated_instsInst|Insts, I, End, L, D ←
inst_usesInst, Us
dominated_usesUs, L, I, D
I1 is I + 1
dominated_instsInsts, I1, End, L, D
1363dominated_uses[], _, _, _
1364dominated_usesN|Ns, L, I, D ←
dominated_useD, L, I, N
dominated_usesNs, L, I, D
1368dominated_phis[], _
1369dominated_phisphi_, _, PVs|Phis, D ←
1373dominated_phi_operands[], _
1374dominated_phi_operandsP−V|PVs, D ←
D = dom_, _, Intervals, Ends
V = tmpN
get_assocP, Intervals, _
→
get_assocP, Ends, End
dominated_useD, P, End, N
else
true
dominated_use+D, +L, +I, +N

some definition of tmp(N) dominates position I of block L.

1385dominated_usedomFn, Sites, Intervals, _, L, I, N ←
get_assocN, Sites, Defs
memberDef, Defs
site_dominatesDef, L, I, Intervals
→
true
else
fn_errorFn, 'tmp(~w) is used in block ~w, which no definition dominates', N, L
site_dominates+Def, +L, +I, +Intervals

the definition Def comes before position I of block L on every path from the entry.

1396site_dominatesparam, _, _, _
1397site_dominatesatL, J, L, I, _ ←
J < I
1398site_dominatesatD, _, L, _, Intervals ←
D ≢ L
dominates_by_intervalIntervals, D, L
stage_func+Stage, +Symbols, +Fn, +Mode, +Params, +Blocksdet

Fn's blocks are well formed for the module's Stage. A checked function accesses memory only by raw accesses, each covered by a check whose evidence still holds, and polls on every cycle.

1407stage_funcunchecked, _, _, _, _, _
1408stage_funcchecked, Symbols, Fn, Mode, Params, Blocks ←
stage_sourcesSymbols, Params, Blocks, Sources
incoming_edgesBlocks, Incoming
empty_assocExits0
foldlchecked_blockFn−Sources, Mode, Incoming, Blocks, Exits0, _
limit_readsFn, Blocks
→
true
else
fn_errorFn, 'a cycle without a poll edge in checked IR', []

Evidence across edges (docs/m7.md): one pass in block order. A block's entry evidence is the intersection, over every incoming edge (both arms of a branch counted separately), of what the edge carries: the source's exit evidence on a plain edge from a block already processed, none on a poll edge or from a block not yet processed. A block with no incoming edge starts with none. Phi destinations then end the evidence that names them.

1425checked_blockFn−Sources, Mode, Incoming, blockL, Phis, Insts, _, Exits0, Exits ←
get_assocL, Incoming, Edges
→
true
else
Edges = []
maplistedge_evidenceExits0, Edges, Sets
evidence_meetSets, Live0
foldlphi_ends_evidence, Phis, Live0, Live1
checked_instsInsts, Fn−Sources, Mode, Ev, evLive, _, _
put_assocL, Exits0, Live, Exits
incoming_edges+Blocks, -Incoming

each label's incoming edges as From-plain or From-poll, one per jump target, duplicates kept.

1436incoming_edgesBlocks, Incoming ←
findallL−From−Kind,
memberblockFrom, _, _, Jump, Blocks
jump_edgeJump, T
edge_kindT, L, Kind
,
Pairs
empty_assocI0
foldladd_edge, Pairs, I0, Incoming
jump_edge
1444gotoTT
1445branch_, T, _T
1446branch_, _, TT
1448edge_kindpollL, L, poll ←
!
1449edge_kindL, L, plain
1451add_edgeL−E, I0, I ←
get_assocL, I0, Es
→
true
else
Es = []
put_assocL, I0, E|Es, I
1455edge_evidence_, _−poll, []
1456edge_evidenceExits, From−plain, Live ←
get_assocFrom, Exits, Live
→
true
else
Live = []
evidence_meet+Sets, -Live

the facts in every one of Sets; none for no sets.

1461evidence_meet[], []
1462evidence_meetS|Ss, Live ←
maplistsort, Ss, Os
includein_everyOs, S, Live
1465in_everyOs, F ←
forallmemberO, Os, ord_memberchkF, O

Within a block evidence is ev(Facts, Names, ByQ): Facts the live/6 facts, newest first; Names holds every name a fact mentions (perhaps more); ByQ each fact by its proof. A definition only scans Facts when Names has its name, which strict SSA makes rare, so a long block's validation is not quadratic in its checks.

1472evidence_indexFacts, evFacts, Names, ByQ ←
empty_assocE
foldlfact_names, Facts, E, Names
reverseFacts, Oldest
foldlfact_by_proof, Oldest, E, ByQ
1478fact_namesliveQ, P, _, _, _, _, N0, N ←
put_assocQ, N0, true, N1
P = tmpX
→
put_assocX, N1, true, N
else
N = N1
1482fact_by_proofF, B0, B ←
F = liveQ, _, _, _, _, _
put_assocQ, B0, F, B
1484evidence_noneev[], E, E ←
empty_assocE
1486evidence_addF, evFs, Ns0, B0, evF|Fs, Ns, B ←
fact_namesF, Ns0, Ns
1489evidence_killD, Ev0, Ev ←
Ev0 = evFs0, Ns, _
get_assocD, Ns, _
→
excludeevidence_namesD, Fs0, Fs
else
Ev = Ev0
1496phi_ends_evidencephitmpD, _, _, Live0, Live ←
excludeevidence_namesD, Live0, Live
checked_insts+Insts, +Fn, +Mode, +Live, -LiveOutdet

Each check in Insts is followed by an access it covers, its first use, so no safepoint can come between them; no other instruction accesses memory unchecked. Live are the heap proofs liveQ, P, Lo, Hi, Rights, Align whose evidence still holds (docs/m7.md): a later scalar raw access may read one that covers it. An instruction that is not evidence_preserving/1 ends them all, and redefining Q or P ends that one; LiveOut are those at the block's end. Local and function proofs are used once, right away.

1509checked_insts[], _, _, Live, Live
1510checked_instsInst|Insts, Fn−Sources, Mode, Live0, LiveOut ←
checked_inst_restInst, Insts, Fn−Sources, Mode, Live0, Rest, Live
checked_instsRest, Fn−Sources, Mode, Live, LiveOut

stage_sources(+Symbols, +Params, +Blocks, -src(Renews, Once, Ahead, Getters)): Renews maps each proof Q1 that a renew may read to check(P1, Lo1, Hi1, Align1): Q1 has one definition, a heap check or renew_if of tmp(P1), and P1 one too, counting parameters, phis and every instruction form. Once maps each name with one definition to it (param for a parameter), for renew_if's guard (round 5) and check_limit's limit. Both are empty when the function has none of these ops, or has a setjmp, whose second return the CFG does not show. With strict SSA (definitions_dominate/3), Q1's check dominates the renew, so P1 and Q1 there hold the values the check saw (docs/m7.md, round 4); a renew_if proves the same bounds as the check it replaced. Ahead holds each check_ahead's proof that ahead_justified/3 accepts, empty with a setjmp too. Getters holds the repeated getter copies (repeated_getters/4), empty with a setjmp.

1527stage_sourcesSymbols, Params, Blocks, srcRenews, Once, Ahead, Getters ←
empty_assocE
memberblock_, _, Is, _, Blocks
memberlet_, _, setjmp_, _, Is
→
Renews = E
Once = E
Ahead = E
Getters = E
¬
memberblock_, _, Is, _, Blocks
memberlet_, _, Op, _, Is
→
Renews = E
Ahead = E
Hull coverage (docs/forward.md) reads Once for any function.
single_definitionsParams, Blocks, Once
repeated_gettersSymbols, Blocks, Once, Getters
else
single_definitionsParams, Blocks, Once
repeated_gettersSymbols, Blocks, Once, Getters
ahead_justifiedBlocks, Once, Ahead
findallQ1−checkP1, Lo1, Hi1, Al1,
memberblock_, _, Is, _, Blocks
memberlettmpQ1, a, Op, Args, Is
renew_source_opOp, Args, tmpP1, Lo1, Hi1, Al1
get_assocQ1, Once, _
get_assocP1, Once, _
,
Pairs
list_to_assocPairs, Renews

repeated_getters(+Symbols, +Blocks, +Once, -Getters) is det.

Getters maps D to G for each single-definition getter copy of
global(G), G defined in this module with local linkage, that another
copy of G dominates (below).

repeated_getters(+Symbols, +Blocks, +Once, -Getters): Getters maps D to G for each single-definition lettmpD, p, copy, globalG that another copy of global(G) dominates: an earlier one in its block, or one in a block that strictly dominates it (dom_intervals/3). Unreachable blocks have none. G must be defined in this module with local linkage (local_global/2), so its getter is the one nanofil emits and no other module can interpose it. Such a copy is a repeated call of that getter (docs/forward.md, Getters): a call has returned before it in this activation, so it takes the fast path (or, inside an initialization on this thread, the recursive path), and reaches no safepoint, frees nothing and stores nothing.

1566repeated_gettersSymbols, Blocks, Once, Getters ←
empty_assocE
findallG−L−I−D,
memberblockL, _, Is, _, Blocks
nth0I, Is, lettmpD, p, copy, globalG
local_globalSymbols, G
,
Sites0
Sites0 ≡ []
→
Getters = E
else
func_cfgBlocks, Cfg
cfg_idomsCfg, Idom
dom_intervalsCfg, Idom, Intervals
findallD−G,
memberG−L−I−D, Sites0
get_assocD, Once, _
get_assocL, Intervals, _
once
memberG1−L1−I1−_, Sites0
G1 ≡ G
L1 ≡ L
→
I1 < I
else
dominates_by_intervalIntervals, L1, L
,
Pairs0
sortPairs0, Pairs
list_to_assocPairs, Getters
local_global+Symbols, +G

G is a function or data item this module defines with local linkage (not exported, weak or extern): its getter is a local symbol only this module calls.

1592local_globalSymbols, G ←
func_namedSymbols, G, func_, local, _, _, _
→
true
else
Symbols = symbolsIndex
get_assocdataG, Index, data_, L, _, _
memberchkL, local, rolocal, tlslocal
1598staged_opOp ←
1599staged_opcheck_ahead_, _, _, _, _
renewing_oprenew_, _, _, _renew_hull_, _, _, _renew_if_, _, _, _check_limit_, _, _, _half_check_, _, _, _, _
renew_source_op
1607checkLo, Hi, _, AlPPLoHiAl
1608check_aheadLo, Hi, _, Al, _PPLoHiAl
1609renew_ifLo, Hi, _, AlP, _PLoHiAl
1610check_limitLo, Hi, _, AlP, _PLoHiAl

A half check is its check (docs/half.md).

1612half_check_, Lo, Hi, _, Al_, PPLoHiAl

Scheduled checks (docs/schedule.md). ahead_justified(+Blocks, +Once, -Ahead): Ahead holds Q for each single-definition lettmpQ, a, check_aheadLo, Hi, _, _, _, P such that, right after its access, P's base B (a single-definition name on guard_chain/3 at an exact offset K) has need(B) >= K + Hi, with K and K + Lo inside the signed range (near_offset/1). The check's start B + K + Lo then lies at most 2^63 below the needed bytes' end B + H, so the two do not wrap past each other modulo 2^64: where the check fails at B + K + Hi, the later access ends beyond it (tests/unit.pl, schedule_validation; docs/rocq.md, "A need past 2^64").

need(B) = H at a point means: every path from it reaches a raw heap access through a pointer at an exact offset from B (well inside the signed range, near_offset/1) whose bytes reach B + H or beyond, and reaches it first: before any instruction that is not ahead_transparent/1, any poll edge, edge to the same or an earlier block, or the function's end (each of which ends the need), and before B is redefined. Computed backward in one pass over the blocks in reverse order:

  • a block's exit facts are the meet over its jump's edges, both arms of a branch counted: a plain edge to a later block carries that block's entry facts, any other edge none; a base present on every edge keeps the least H, and no edge means no facts;
  • an instruction that is not ahead_transparent/1 ends every fact; one that defines N ends N's fact; a raw access (not a cas) at a single-definition proof of P, at offset Off, of Size bytes, raises need(B) to at least K + Off + Size for each exact base B-i(K, K) of P;
  • phi destinations end their facts at the block's entry.

Any edge into a later block was walked before, so one pass suffices. In the reference program every such access has its own check, which fails if those bytes are outside the object: so where this check fails early, the reference traps later on every path (unless something else traps first).

1649ahead_justifiedBlocks, Once, Ahead ←
empty_assocE
¬
memberblock_, _, Is, _, Blocks
memberlet_, _, check_ahead_, _, _, _, _, _, Is
→
Ahead = E
else
proof_pointersBlocks, Once, Ptrs
findallL−N, nth0N, Blocks, blockL, _, _, _, OrderPairs
list_to_assocOrderPairs, Order
reverseBlocks, Rev
foldlahead_blockOrder, Once, Ptrs, Rev, E−[], _−Js
findallQ−true, memberQ, Js, Pairs0
sortPairs0, Pairs
list_to_assocPairs, Ahead
proof_pointers+Blocks, +Once, -Ptrs

Q-P for each single-definition heap proof Q and the pointer P it proves.

1665proof_pointersBlocks, Once, Ptrs ←
findallQ−P,
memberblock_, _, Is, _, Blocks
memberlettmpQ, a, Op, Args, Is
proof_pointerOp, Args, P
get_assocQ, Once, _
,
Pairs0
sortPairs0, Pairs
list_to_assocPairs, Ptrs
proof_pointer
1673check_, _, _, _PP
1674check_ahead_, _, _, _, _PP
1675renew_, _, _, __, PP
1676renew_hull_, _, _, __, _, PP
1677half_check_, _, _, _, __, PP
1678renew_if_, _, _, _P, _P
1679check_limit_, _, _, _P, _P
1681ahead_blockOrder, Once, Ptrs, blockL, Phis, Is, J, In0−Js0, In−Js ←
get_assocL, Order, Pos
findallF,
ahead_edgeT, Pos, Order, In0, F
,
Fs
ahead_meetFs, Out
reverseIs, RIs
empty_assocE
ahead_instsRIs, Once, Ptrs, Out, E, Entry0, Js0, Js
foldlphi_ends_need, Phis, Entry0, Entry
put_assocL, In0, Entry, In
1691ahead_edgepoll_, _, _, _, F ←
!
empty_assocF
1692ahead_edgeT, Pos, Order, In0, F ←
get_assocT, Order, TPos
TPos > Pos
get_assocT, In0, F0
→
F = F0
else
empty_assocF
ahead_meet+Facts, -Meet

the bases in every one of Facts, each with its least need; none for no facts.

1699ahead_meet[], E ←
empty_assocE
1700ahead_meetF|Fs, Meet ←
assoc_to_listF, Ps
findallB−H,
memberB−H0, Ps
foldlleast_needB, Fs, H0, H
,
Ms
list_to_assocMs, Meet
1705least_needB, F, H0, H ←
get_assocB, F, H1
H is minH0, H1
ahead_insts+RevInsts, +Once, +Ptrs, +After, +AfterNext, -Before, +Js0, -Js

After are the facts after the first of RevInsts (the block's last instruction), AfterNext those after the instruction that follows it.

1710ahead_insts[], _, _, F, _, F, Js, Js
1711ahead_instsI|Is, Once, Ptrs, F0, Next, F, Js0, Js ←
I = lettmpQ, a, check_aheadLo, Hi, _, _, _, P
get_assocQ, Once, _
guard_chainOnce, P, Chain
membertmpN−iK, K, Chain
get_assocN, Once, _
KLo is K + Lo
get_assocN, Next, H
H ≥ K + Hi
→
Js1 = Q|Js0
else
Js1 = Js0
ahead_stepI, Once, Ptrs, F0, F1
ahead_instsIs, Once, Ptrs, F1, F0, F, Js1, Js
1724ahead_stepI, Once, Ptrs, F0, F ←
→
del_assocD, F0, _, F1
→
true
else
F1 = F0
raw_accessI, Q, X, Off, _
X ≠ cas_
get_assocQ, Ptrs, P
→
guard_chainOnce, P, Chain
foldlraise_needOnce, Off, Size, Chain, F1, F
else
F = F1
else
empty_assocF
1737raise_needOnce, Off, Size, B−iK, K1, F0, F ←
K ≡ K1
B = tmpN
get_assocN, Once, _
H is K + Off + Size
→
true
get_assocN, F0, H0
H0 ≥ H
→
F = F0
else
put_assocN, F0, H, F
else
F = F0
near_offset+K

guard_chain/3's offsets are exact only modulo 2^64 (class l wraps), so a need is counted only for offsets well inside the signed range, where the integer is the true distance: an access at B + 2^64 - 8 is at B - 8, and must not count as reaching past B.

1749near_offsetK ←
K ≥ −1 << 62
K ≤ 1 << 62
half_known+Bound, +Lo, +Hi, +D0, +Lo1, +Hi1semidet

A check of [Lo, Hi) at P, where P is P1 + D0 modulo 2^64 with P1's capability, may test only Bound when a passed check of [Lo1, Hi1) at P1 proves the other end (docs/half.md). D is D0 taken signed modulo 2^64. upper: P+Lo is E = D + Lo - Lo1 >= 0 bytes above P1+Lo1, which was at or above lower, and with E =< 2^62 that sum cannot wrap, as P1+Lo1 is a user address. lower: P+Hi is F = D + Hi - Hi1 =< 0 bytes from P1+Hi1, at or below that check's upper; with F >= -2^62, P+Lo taken signed is the true address, which the signed lower test reads (and Hi - Lo =< 2^62 keeps P+Lo above -2^63).

1762half_knownBound, Lo, Hi, D0, Lo1, Hi1 ←
D is D0 + 1 << 63 mod 1 << 64−1 << 63
E is D + Lo−Lo1
F is D + Hi−Hi1
Hi−Lo ≤ 1 << 62
half_sideBound, E, F
1768half_sideupper, E, _ ←
E ≥ 0
1769half_sidelower, _, F ←
F ≤ 0
1771phi_ends_needphitmpD, _, _, F0, F ←
del_assocD, F0, _, F
→
true
else
F = F0
ahead_transparent+Instsemidet

Inst may come between a check_ahead and the access whose need it checks early (docs/schedule.md): it is evidence_preserving/1, so it reaches no safepoint, calls nothing and cannot free, and it is not a division, whose fault would end the program otherwise than a Fil-C trap. Calls, allocation, getters, pointer stores, atomics, setjmp and va_arg are not, nor are poll edges, which may run signal handlers.

1782ahead_transparentI ←
¬
I = let_, _, Op, _
memberchkOp, div, udiv, rem, urem

hull_renewable(+Sources, +Once, +P, +Lo, +Hi, +Align, +QL, +QH): a renew_hull of [Lo, Hi) at P may read QL and QH (docs/forward.md, multi-source renewal). Each is a single-definition heap proof in the renew-source table (a check that passed, at a point that dominates this one by strict SSA, in a function without setjmp) of a pointer at an exact distance from P through one base, so of P's object: QL's of [LoL, HiL) at PL, P = PL + DL, and QH's of [LoH, HiH) at PH, P = PH + DH, all modulo 2^64. With E = Lo + DL - LoL and F = HiH - Hi - DH both in [0, 2^62] and Hi - Lo =< 2^62:

  • P+Lo is E bytes above PL+LoL, which was at or above lower, and as PL+LoL is a user address (below 2^56) the sum does not wrap;
  • P+Hi is that plus Hi - Lo, and also F bytes below PH+HiH, which was at or below upper; both readings are below 2^63 + 2^62 + 2^56 and above -2^62 and equal modulo 2^64, so they are the same integer.

So [P+Lo, P+Hi) lies in the object as both checks saw it. Its bounds change only when it is freed, which the renew's flag test catches; read-only is the object's, which the flag test reads too. Alignment comes from either source, as renew's: P+Lo is (Lo + D - Lo1) bytes from a check's Al1-aligned start.

1805hull_renewableSources, Once, P, Lo, Hi, Align, tmpNL, tmpNH ←
get_assocNL, Sources, checkPL, LoL, _, AlL
get_assocNH, Sources, checkPH, LoH, HiH, AlH
pointer_distanceOnce, P, tmpPL, DL
pointer_distanceOnce, P, tmpPH, DH
E is Lo + DL−LoL
F is HiH−Hi−DH
S is Hi−Lo
Limit is 1 << 62
E ≥ 0
E ≤ Limit
F ≥ 0
F ≤ Limit
S ≤ Limit
AlL mod Align =:= 0
E mod Align =:= 0
→
true
else
AlH mod Align =:= 0
Lo + DH−LoH mod Align =:= 0
pointer_distance+Once, +P, +P1, -Dsemidet

P's address is P1's plus D modulo 2^64, with P1's capability, at any point both dominate: both reach one single-definition base B through guard_chain/3 at exact offsets K and K1, and D = K - K1 (the argument of same_pointer/3, which is the case D = 0 modulo 2^64).

1824pointer_distanceOnce, P, P1, D ←
guard_chainOnce, P, Chain
guard_chainOnce, P1, Chain1
memberB−iK, K, Chain
B = tmpN
get_assocN, Once, _
memberB1−iK1, K1, Chain1
B1 ≡ B
!
D is K−K1
single_definitions+Params, +Blocks, -Oncedet

Once maps each temporary with exactly one definition (temp_defs/3's forms) to it: param, its phi, or its instruction.

1834single_definitionsParams, Blocks, Once ←
findallN−param, memberparam_, tmpN, Params, D0
findallN−Def,
memberblock_, Phis, Insts, _, Blocks
memberDef, Phis
Def = phitmpN, _, _
or
memberDef, Insts
inst_temp_defDef, N, _
,
D1
appendD0, D1, D
keysortD, Sorted
once_pairsSorted, Pairs
list_to_assocPairs, Once
1845once_pairs[], []
1846once_pairsN−_, M−_|Ps, Once ←
N ≡ M
!
drop_pairsN, Ps, Rest
once_pairsRest, Once
1847once_pairsP|Ps, P|Once ←
once_pairsPs, Once
1849drop_pairsN, M−_|Ps, Rest ←
N ≡ M
!
drop_pairsN, Ps, Rest
1850drop_pairs_, Ps, Ps
guard_chain+Once, +P, -Chaindet

Range guards (docs/m7.md, round 5, slice A). Chain is P-i(0, 0) followed by B-i(Lo, Hi) for each base B of P: P has B's capability and an address congruent modulo 2^64 to B's plus some X with Lo =< X =< Hi, through single-definition flights add, sub and copy whose lower donor is their address operand. Once maps each single-definition name to its definition (single_definitions/3).

1860guard_chainOnce, P, Chain ←
guard_chainOnce, P, 0, 0, 0, Chain
1862guard_chainOnce, P, Lo, Hi, Depth, P−iLo, Hi|Chain ←
Depth < 64
P = tmpX
get_assocX, Once, flight_, Op, Args, lowerB
flight_termOp, Args, B, O, Sign
offset_intervalOnce, O, l, 0, OLo, OHi
→
Sign =:= 1
→
Lo1 is Lo + OLo
Hi1 is Hi + OHi
else
Lo1 is Lo−OHi
Hi1 is Hi−OLo
Depth1 is Depth + 1
guard_chainOnce, B, Lo1, Hi1, Depth1, Chain
else
Chain = []
1875flight_termadd, addressB0, O, B, O, 1 ←
B0 ≡ B
1876flight_termsub, addressB0, O, B, O, -1 ←
B0 ≡ B
1877flight_termcopy, addressB0, B, const0, 1 ←
B0 ≡ B

offset_interval(+Once, +V, +K, +Depth, -Lo, -Hi): every value V of class K can hold, taken signed in K's width, lies in [Lo, Hi]; for l, the value is congruent modulo 2^64 to one in [Lo, Hi], which is all an address needs. A name needs one definition (Once) of the forms below, each sound for any operand values within their intervals; a w result must lie in [-2^31, 2^31), so no w arithmetic wraps.

1885offset_intervalOnce, V, K, D, Lo, Hi ←
empty_assocM0
offset_intervalOnce, V, K, D, Lo, Hi, M0, _
offset_interval+Once, +V, +K, +Depth, -Lo, -Hi, +Memo0, -Memo

as above, with each name's interval (or none) memoised, so an offset whose terms share names (x = x + x, repeated) is walked once per name.

1891offset_interval_, constC, K, _, V, V, M, M ←
!
integerC
1892offset_intervalOnce, tmpN, K, Depth, Lo, Hi, M0, M ←
get_assocN−K, M0, R
→
M = M0
else
Depth < 32
get_assocN, Once, lettmpN, K, Op, Args
Depth1 is Depth + 1
op_intervalOp, Args, K, Once, Depth1, Lo0, Hi0, M0, M1
interval_fitsK, Lo0, Hi0
→
R = iLo0, Hi0
else
R = none
M1 = M0
put_assocN−K, M1, R, M
R = iLo, Hi
1906interval_fitsw, Lo, Hi ←
Lo ≥ -2147483648
Hi ≤ 2147483647
1907interval_fitsl, _, _
1909signed_valueK, C, V ←
class_bitsK, Bits
U is C mod 1 << Bits
U ≥ 1 << Bits−1
→
V is U−1 << Bits
else
V = U
narrow_range
1914ub0255
1915uh065535
1916sb-128127
1917sh-3276832767
1919op_intervalextT, _, _, _, _, Lo, Hi, M, M ←
narrow_rangeT, Lo, Hi
!
1920op_intervalraw_loadT, _, _, _, _, _, Lo, Hi, M, M ←
narrow_rangeT, Lo, Hi
!
1921op_intervalextsw, A, _, Once, D, Lo, Hi, M0, M ←
!
offset_intervalOnce, A, w, D, Lo, Hi, M0, M
1923op_intervalextuw, A, _, Once, D, Lo, Hi, M0, M ←
!
offset_intervalOnce, A, w, D, Lo, Hi, M0, M
Lo ≥ 0
1925op_intervalcopy, A, K, Once, D, Lo, Hi, M0, M ←
!
offset_intervalOnce, A, K, D, Lo, Hi, M0, M
1927op_intervaland, Args, K, _, _, 0, Mask, M, M ←
Args = _, constC
or
Args = constC, _
signed_valueK, C, Mask
Mask ≥ 0
!
1930op_intervalurem, _, constC, K, _, _, 0, Hi, M, M ←
!
class_bitsK, Bits
U is C mod 1 << Bits
U > 0
U ≤ 1 << Bits−1
Hi is U−1
1933op_intervalshl, A, constS, K, Once, D, Lo, Hi, M0, M ←
!
class_bitsK, Bits
integerS
S ≥ 0
S < Bits
offset_intervalOnce, A, K, D, Lo0, Hi0, M0, M
Lo is Lo0 × 1 << S
Hi is Hi0 × 1 << S
1937op_intervalmul, Args, K, Once, D, Lo, Hi, M0, M ←
Args = A, constC
or
Args = constC, A
A ≠ const_
!
offset_intervalOnce, A, K, D, Lo0, Hi0, M0, M
V ≥ 0
→
Lo is Lo0 × V
Hi is Hi0 × V
else
Lo is Hi0 × V
Hi is Lo0 × V
1942op_intervaladd, A, B, K, Once, D, Lo, Hi, M0, M ←
!
offset_intervalOnce, A, K, D, Lo1, Hi1, M0, M1
offset_intervalOnce, B, K, D, Lo2, Hi2, M1, M
Lo is Lo1 + Lo2
Hi is Hi1 + Hi2
1946op_intervalsub, A, constC, K, Once, D, Lo, Hi, M0, M ←
!
offset_intervalOnce, A, K, D, Lo0, Hi0, M0, M
Lo is Lo0−V
Hi is Hi0−V
same_pointer+Once, +P, +P1semidet

At any point both P and P1 dominate, they hold the same address and capability: both reach one single-definition base B through guard_chain/3 at point offsets equal modulo 2^64 (P == P1 included). After B's last execution before that point, each definition on either chain ran again, since each dominates it and B dominates each, so both were computed from B's current value (docs/m7.md, round 4 addendum). Once maps each single-definition name to its definition.

1960same_pointerOnce, P, P1 ←
guard_chainOnce, P, Chain
guard_chainOnce, P1, Chain1
memberB−iK, K, Chain
B = tmpN
get_assocN, Once, _
memberB1−iK1, K1, Chain1
B1 ≡ B
K−K1 mod 1 << 64 =:= 0
!
guard_covers+Once, +P, +G, +Lo, +Hi

G is a single-definition span test of a single-definition P0 whose span contains P's [Lo, Hi) for every offset P can have from P0.

1969guard_coversOnce, P, tmpGN, Lo, Hi ←
get_assocGN, Once, lettmpGN, w, span_testGLo, GHi, P0
P0 = tmpN0
get_assocN0, Once, _
guard_chainOnce, P, Chain
memberB−iOLo, OHi, Chain
B ≡ P0
!
GLo ≤ OLo + Lo
OHi + Hi ≤ GHi
limit_covers+Once, +P, +U, +Lo, +Hi

U is a single-definition limit(Size) of a single-definition P0 whose lower P has (lower_chain/3), for exactly P's accesses [0, Size) (docs/bounds.md, hoisted checks).

1979limit_coversOnce, P, tmpUN, 0, Size ←
get_assocUN, Once, lettmpUN, l, limitSize, P0
P0 = tmpN0
get_assocN0, Once, _
lower_chainOnce, P, P0
lower_chain+Once, +P, +Bsemidet

P has B's lower: P is B, or a single-definition flight whose lower donor has B's lower, whatever its address. At any point that P's definitions dominate, P's lower is B's current one, by round 4's dominance argument (docs/m7.md), since every name on the way has one definition. Once maps each single-definition name to its definition.

1991lower_chainOnce, P, B ←
lower_chainOnce, P, B, 0
1993lower_chain_, P, B, _ ←
P ≡ B
!
1994lower_chainOnce, tmpX, B, Depth ←
Depth < 64
get_assocX, Once, flight_, _, _, lowerD
Depth1 is Depth + 1
lower_chainOnce, D, B, Depth1
limit_reads+Fn, +Blocksdet

A limit's value is an encoding only check_limit reads (docs/bounds.md): no phi, jump or other instruction reads a name a limit defines, and a check_limit reads one only as its second argument. Throws otherwise.

2005limit_readsFn, Blocks ←
findallN−true,
memberblock_, _, Is, _, Blocks
memberlettmpN, _, limit_, _, Is
,
Ps0
Ps0 ≡ []
→
true
else
sortPs0, Ps
list_to_assocPs, Limits
forallmemberblockL, Phis, Is, J, Blocks,
forallmemberI, Is, inst_limit_readsFn, L, Limits, I
jump_usesJ, JUs
no_limit_readFn, L, Limits, JUs
forall
memberphi_, _, PVs, Phis
member_−tmpN, PVs
,
no_limit_readFn, L, Limits, N
2017inst_limit_readsFn, L, Limits, let_, _, check_limit_, _, _, _, P, U ←
!
P = tmpPN
→
no_limit_readFn, L, Limits, PN
else
true
U = tmpN
get_assocN, Limits, _
→
true
else
fn_errorFn, 'a check_limit in block ~w reads ~w, which no limit defines', L, U
2022inst_limit_readsFn, L, Limits, I ←
no_limit_readFn, L, Limits, Us
2025no_limit_readFn, L, Limits, Us ←
memberN, Us
get_assocN, Limits, _
→
fn_errorFn, 'tmp(~w), a limit, is read in block ~w by other than a check_limit', N, L
else
true
2031checked_inst_restlettmpQ, a, checkLo, Hi, Rights, Align, P, Insts, Fn−_, _, Live0, Rest, Live ←
!
Insts = Access|Rest
proof_useAccess, Q, Lo, Hi, Rights, Align
→
evidence_afterlettmpQ, a, checkLo, Hi, Rights, Align, P, Live0, Live1
evidence_addliveQ, P, Lo, Hi, Rights, Align, Live1, Live2
evidence_afterAccess, Live2, Live
else
fn_errorFn, 'check tmp(~w) is not followed by an access it covers', Q
2040checked_inst_restlettmpQ, a, check_aheadLo, Hi, Rights, Align, AHi, P, Insts, Fn−src_, _, Ahead, _, _, Live0, Rest, Live ←
!
get_assocQ, Ahead, _
→
true
else
fn_errorFn, 'check_ahead tmp(~w): its early bytes are not needed on every path ahead, or a setjmp', Q
Insts = Access|Rest
proof_useAccess, Q, Lo, AHi, Rights, Align
→
evidence_afterlettmpQ, a, check_aheadLo, Hi, Rights, Align, AHi, P, Live0, Live1
evidence_addliveQ, P, Lo, Hi, Rights, Align, Live1, Live2
evidence_afterAccess, Live2, Live
else
fn_errorFn, 'check_ahead tmp(~w) is not followed by a transparent access in its own range', Q
2053checked_inst_restlettmpQ, a, check_local_, _, Bytes, Rights, Align, [], Insts, Fn−_, _, Live0, Rest, Live ←
!
Insts = Access|Rest
raw_accessAccess, Q, _, 0, _
covered_accessAccess, Q, 0, Bytes, Rights, Align
→
evidence_killQ, Live0, Live1
evidence_afterAccess, Live1, Live
else
fn_errorFn, 'local check tmp(~w) is not followed by an access it covers', Q
2063checked_inst_restlettmpQ, f, check_function, _, Insts, Fn−_, _, _, Rest, Live ←
!
Insts = raw_call_, tmpQ, _|Rest
→
true
else
fn_errorFn, 'function check tmp(~w) is not followed by its raw call', Q
2068checked_inst_restlettmpQ, a, renewLo, Hi, Rights, Align, Q1, P, Insts, Fn−srcSources, Once, _, _, _, Live0, Rest, Live ←
!
Q1 = tmpN1
get_assocN1, Sources, checkP1, Lo1, Hi1, Al1
→
true
else
fn_errorFn, 'renew tmp(~w) of ~w: no single-definition heap check that covers it, or a setjmp', Q, Q1
pointer_distanceOnce, P, tmpP1, D
→
true
else
fn_errorFn, 'renew tmp(~w): ~w is not ~w\'s checked pointer tmp(~w)', Q, P, Q1, P1
LoD is Lo + D
HiD is Hi + D
Lo1 ≤ LoD
HiD ≤ Hi1
Al1 mod Align =:= 0
LoD−Lo1 mod Align =:= 0
→
true
else
fn_errorFn, 'renew tmp(~w) of ~w: no single-definition heap check that covers it, or a setjmp', Q, Q1
Insts = Access|Rest
proof_useAccess, Q, Lo, Hi, Rights, Align
→
evidence_afterlettmpQ, a, renewLo, Hi, Rights, Align, Q1, P, Live0, Live1
evidence_addliveQ, P, Lo, Hi, Rights, Align, Live1, Live2
evidence_afterAccess, Live2, Live
else
fn_errorFn, 'renew tmp(~w) is not followed by an access it covers', Q
2088checked_inst_restlettmpQ, a, renew_hullLo, Hi, Rights, Align, QL, QH, P, Insts, Fn−srcSources, Once, _, _, _, Live0, Rest, Live ←
!
hull_renewableSources, Once, P, Lo, Hi, Align, QL, QH
→
true
else
fn_errorFn, 'renew_hull tmp(~w) of ~w and ~w: no two single-definition heap checks of its object whose hull covers it, or a setjmp', Q, QL, QH
Insts = Access|Rest
proof_useAccess, Q, Lo, Hi, Rights, Align
→
evidence_afterlettmpQ, a, renew_hullLo, Hi, Rights, Align, QL, QH, P, Live0, Live1
evidence_addliveQ, P, Lo, Hi, Rights, Align, Live1, Live2
evidence_afterAccess, Live2, Live
else
fn_errorFn, 'renew_hull tmp(~w) is not followed by an access it covers', Q
2100checked_inst_restlettmpQ, a, half_checkBound, Lo, Hi, Rights, Align, Q1, P, Insts, Fn−srcSources, Once, _, _, _, Live0, Rest, Live ←
!
Q1 = tmpN1
get_assocN1, Sources, checkP1, Lo1, Hi1, _
pointer_distanceOnce, P, tmpP1, D0
half_knownBound, Lo, Hi, D0, Lo1, Hi1
→
true
else
fn_errorFn, 'half_check tmp(~w) of ~w: no single-definition heap check of its object that proves its ~w bound, or a setjmp', Q, Q1, Bound
Insts = Access|Rest
proof_useAccess, Q, Lo, Hi, Rights, Align
→
evidence_afterlettmpQ, a, half_checkBound, Lo, Hi, Rights, Align, Q1, P, Live0, Live1
evidence_addliveQ, P, Lo, Hi, Rights, Align, Live1, Live2
evidence_afterAccess, Live2, Live
else
fn_errorFn, 'half_check tmp(~w) is not followed by an access it covers', Q
2115checked_inst_restlettmpQ, a, renew_ifLo, Hi, Rights, Align, P, G, Insts, Fn−src_, Once, _, _, _, Live0, Rest, Live ←
!
guard_coversOnce, P, G, Lo, Hi
→
true
else
fn_errorFn, 'renew_if tmp(~w): no single-definition span test that covers it, or a setjmp', Q
Insts = Access|Rest
proof_useAccess, Q, Lo, Hi, Rights, Align
→
evidence_afterlettmpQ, a, renew_ifLo, Hi, Rights, Align, P, G, Live0, Live1
evidence_addliveQ, P, Lo, Hi, Rights, Align, Live1, Live2
evidence_afterAccess, Live2, Live
else
fn_errorFn, 'renew_if tmp(~w) is not followed by an access it covers', Q
2127checked_inst_restlettmpQ, a, check_limitLo, Hi, Rights, Align, P, U, Insts, Fn−src_, Once, _, _, _, Live0, Rest, Live ←
!
limit_coversOnce, P, U, Lo, Hi
→
true
else
fn_errorFn, 'check_limit tmp(~w): no single-definition limit of its pointer\'s lower for its size, or a setjmp', Q
Insts = Access|Rest
proof_useAccess, Q, Lo, Hi, Rights, Align
→
evidence_afterlettmpQ, a, check_limitLo, Hi, Rights, Align, P, U, Live0, Live1
evidence_addliveQ, P, Lo, Hi, Rights, Align, Live1, Live2
evidence_afterAccess, Live2, Live
else
fn_errorFn, 'check_limit tmp(~w) is not followed by an access it covers', Q
2139checked_inst_restInst, Insts, Fn−Sources, Mode, Live0, Insts, Live ←
Inst = lettmpD, p, copy, globalG
Sources = src_, _, _, Getters
get_assocD, Getters, G1
G1 ≡ G
→
evidence_killD, Live0, Live
else
evidence_afterInst, Live0, Live
raw_accessInst, Q, X, _, _
→
Live0 = ev_, _, ByQ
get_assocQ, ByQ, liveQ, P, Lo, Hi, Rights, Align
covered_accessInst, Q, Lo, Hi, Rights, Align
→
true
else
Sources = src_, Once, _, _
hull_coveredInst, Q, P, Lo, Align, Live0, Once
→
true
else
fn_errorFn, 'a raw access at tmp(~w) that no live check covers', Q
Inst = raw_call_, _, _
→
fn_errorFn, 'a raw call whose target is not checked just before it', []
Inst = touchT
→
fn_errorFn, 'touch(~w) is not right after the heap check, renew or renew_if that defines it', T
Mode ≡ filc
→
fn_errorFn, '~w in checked IR, where every access is checked', What
→
fn_errorFn, 'implicit pointer operation in checked IR; use flight', []
else
true
reusing_access+Inst, +X

Inst, a raw access of type or width X, may read a live proof that is not its own check's: a scalar load or store, or a pointer load with the heap load effects, which read aux and box words only (docs/forward.md). A pointer store's ensure-aux may exit, and a cas is atomic; both keep their own check.

2172reusing_access_, X ←
X ≢ p
X ≠ cas_
!
2173reusing_accesslet_, p, raw_load_pointer_, Es, _, p ←

hull_covered(+Access, +Q, +P, +Lo, +Align, +Live, +Once): Access at proof Q of P, whose live fact's lower end is Lo at alignment Align, lies within the hull of the live facts of P's object (docs/forward.md). Each live fact live(Q1, P1, Lo1, Hi1, R1, _) with P1 at an exact distance D from P through one single-definition base (pointer_distance/4) shows that P+[Lo1+D, Hi1+D) is inside the object; an object is one interval, so every byte between the least such lower end and the greatest upper end is too. All the facts are live, so the object has not been freed since any of their checks; a write needs one of them with write rights (read-only is the object's). Alignment is Q's own, as covered_access/6 has it. Every offset and span end counts only well inside the signed range (near_offset/1): then each passed fact lies in an object far smaller than 2^62, and the integer hull is the true address interval.

2189hull_coveredAccess, Q, P, Lo, Align, evFacts, _, _, Once ←
raw_accessAccess, Q, X, Off, Need
access_size_alignX, Size, AccessAlign
!
Align mod AccessAlign =:= 0
Off−Lo mod AccessAlign =:= 0
End is Off + Size
findallL1−H1−R1,
memberlive_, P1, Lo1, Hi1, R1, _, Facts
pointer_distanceOnce, P1, P, D
L1 is Lo1 + D
H1 is Hi1 + D
,
Spans
once
memberL1−_−_, Spans
L1 ≤ Off
once
member_−H1−_, Spans
H1 ≥ End
Need ≡ read
→
true
else
oncemember_−_−write, Spans
evidence_after+Inst, +Live0, -Live

Live are the proofs of Live0 whose evidence still holds after Inst (checked_insts/5).

2205evidence_afterInst, Live0, Live ←
→
inst_defInst, D
→
evidence_killD, Live0, Live
else
Live = Live0
2214evidence_namesD, liveQ, P, _, _, _, _ ←
Q ≡ D
or
P ≡ tmpD
evidence_preserving+Instsemidet

Inst reaches no safepoint and cannot free an object or change its bounds or rights: pure arithmetic, comparison and conversion, pure pointer construction, a heap check, a scalar raw access (object headers lie outside every object's bounds) and a raw pointer load (aux and box reads, no helper). Calls, alloc, getters, setjmp, va_arg, pointer stores and the rest are not.

2224evidence_preservingloc_, _
2225evidence_preservingtouch_
2226evidence_preservingflight_, _, _, _
2227evidence_preservinglet_, a, check_, _, _, _, _
2228evidence_preservinglet_, a, check_ahead_, _, _, _, _, _
2229evidence_preservinglet_, a, renew_, _, _, _, _
2230evidence_preservinglet_, a, renew_hull_, _, _, _, _
2231evidence_preservinglet_, a, half_check_, _, _, _, _, _
2232evidence_preservinglet_, w, span_test_, _, _
2233evidence_preservinglet_, a, renew_if_, _, _, _, _
2234evidence_preservinglet_, l, limit_, _
2235evidence_preservinglet_, a, check_limit_, _, _, _, _
2236evidence_preservinglet_, _, raw_loadT, _, _ ←
T ≢ p
2237evidence_preservinglet_, p, raw_load_pointer_, Es, _ ←
2239evidence_preservingraw_storeW, _, _, _ ←
W ≢ p
2240evidence_preservinglet_, _, Op, _ ←
memberchkOp, add, sub, mul, div, udiv, rem, urem, and, or, xor, sar, shr, shl, neg, ptoi, cast, truncd, stosi, stoui, dtosi, dtoui, swtof, uwtof, sltof, ultof, extd, truncx, xtosi, xtoui
2245evidence_preservinglet_, _, cmp_, _, _
2246evidence_preservinglet_, _, ext_, _
2247evidence_preservinglet_, _, copy, Args ←
¬memberchkglobal_, Args
implicit_flight+Instsemidet

Inst is a pure pointer operation (add, sub, itop, or a copy of a temporary or constant at class p) whose address and lower sources must be made explicit as flight before validation or emission of checked IR. A copy of a symbol is not one: it calls the getter, an effect.

2256implicit_flightlet_, p, Op, _ ←
memberchkOp, add, sub, itop
2257implicit_flightlet_, p, copy, tmp_
2258implicit_flightlet_, p, copy, const_
raw_access?Inst, ?Q, ?X, ?Off, ?Rights

Inst is a raw load of type X or store of width X at tmp(Q)+Off, needing Rights.

raw_access
2262let_, _, raw_loadT, Off, tmpQQTOffread
2263raw_storeW, Off, _, tmpQQWOffwrite
2264let_, _, raw_load_pointerOff, _, tmpQQpOffread
2265raw_store_pointerOff, _, tmpQ, _QpOffwrite
2266let_, _, raw_casT, Off, tmpQ|_QcasTOffwrite
unchecked_access
2268let_, _, loadT, _loadT
2269storeW, _, _storeW
2270let_, _, atomic_loadT, _atomic_loadT
2271atomic_storeW, _, _atomic_storeW
2272let_, _, casT, _casT
2273local_load_, _, T, _, _local_loadT
2274local_storeW, _, _, _local_storeW
2275call_, tmp_, _'indirect call'
2276access_, _, _, _access
2278access_storage_effectslet_, _, raw_load_, _, _, _
2279access_storage_effectsraw_store_, _, _, _, _
2280access_storage_effectslet_, _, raw_cas_, _, _, heap
2281access_storage_effectslet_, p, raw_load_pointer_, Es, _, Storage ←
2283access_storage_effectsraw_store_pointer_, _, _, Es, Storage ←
storage_effect_kindStorage, store, Kind
storage_effect_kind
2286heaploadload
2287heapstorestore
2288heaploadatomic_load
2289heapstoreatomic_store
2290localloadlocal_load
2291localstorelocal_store
proof_use+Access, +Q, +Lo, +Hi, +Rights, +Alignsemidet

Access, right after the heap check, renew or renew_if that defines Q over [Lo, Hi) with Rights and Align, uses its proof: a heap raw access it covers, or a touch of Q, which accesses nothing (docs/vector-access.md).

2299proof_usetouchT, Q, _, _, _, _ ←
!
T ≡ tmpQ
2300proof_useAccess, Q, Lo, Hi, Rights, Align ←
covered_accessAccess, Q, Lo, Hi, Rights, Align
covered_access+Access, +Q, +Lo, +Hi, +Rights, +Alignsemidet

Access is a raw access at tmp(Q) within [Q+Lo, Q+Hi), as aligned as it must be, and Rights allow it (write allows reading too).

2308covered_accessAccess, Q, Lo, Hi, Rights, Align ←
raw_accessAccess, Q, X, Off, Need
access_size_alignX, Size, AccessAlign
!
Lo ≤ Off
Off + Size ≤ Hi
Align mod AccessAlign =:= 0
Off−Lo mod AccessAlign =:= 0
rights_allowRights, Need
2316rights_allowwrite, _
2317rights_allowread, read
polls_every_cycle+Blockssemidet

Every cycle of the control-flow graph of Blocks, unreachable blocks included, has a poll edge: without them the graph is acyclic. A call does not count, since the callee need not poll. Depth-first search, with each label gray while on the path and done after.

2325polls_every_cycleBlocks ←
findallL−Ss,
memberblockL, _, _, Jump, Blocks
findallS, unpolled_edgeJump, S, Ss
,
Pairs
list_to_assocPairs, Graph
empty_assocColor0
findallL, memberL−_, Pairs, Labels
acyclic_from_allLabels, Graph, Color0, _
2335unpolled_edgegotoT, T ←
T ≠ poll_
2336unpolled_edgebranch_, T, _, T ←
T ≠ poll_
2337unpolled_edgebranch_, _, T, T ←
T ≠ poll_
acyclic_from_all+Ls, +Graph, +Color0, -Color

no cycle of Graph is reachable from Ls; a gray successor closes one. Plain recursion, since Trealla copies a closure's arguments, Graph included, at every call.

2342acyclic_from_all[], _, Color, Color
2343acyclic_from_allL|Ls, Graph, Color0, Color ←
get_assocL, Color0, C
→
C ≡ done
Color1 = Color0
else
put_assocL, Color0, gray, ColorG
get_assocL, Graph, Ss
acyclic_from_allSs, Graph, ColorG, ColorS
put_assocL, ColorS, done, Color1
acyclic_from_allLs, Graph, Color1, Color
2353valid_phiCtx, Label, Preds, phi_, K, Pairs ←
Ctx = ctx_, Fn, _, _, _, _, _
split_pairsPairs, PhiPreds0, Values
sortPhiPreds0, PhiPreds
PhiPreds ≡ Preds
→
true
else
fn_errorFn, 'phi in block ~w covers ~w but predecessors are ~w', Label, PhiPreds, Preds
maplistvalid_useCtx, K, Values

pairs_keys_values/3, without depending on library(pairs).

2364split_pairs[], [], []
2365split_pairsK−V|Ps, K|Ks, V|Vs ←
split_pairsPs, Ks, Vs
valid_inst+Ctx, +Instsemidet

Inst is well formed and reads each operand at a class its definition allows. In filc mode a symbol is an operand only of copy, which yields its pointer; the cut commits to that case before the general clause for ins/4.

2373valid_instctx_, Fn, _, _, _, _, _, locFile, Line ←
!
atomFile
integerLine
→
true
else
fn_errorFn, 'bad source location loc(~q, ~q)', File, Line
2377valid_instctx_, Fn, _, _, _, _, filc, local_allocName, Size ←
groundName
integerSize
Size ≥ 0
Size ≤ 2147483647
→
true
else
fn_errorFn, 'invalid local allocation ~w, ~w', Name, Size
2380valid_instctx_, Fn, _, _, _, _, filc, local_loadtmp_, K, T, _, Off ←
else
fn_errorFn, 'invalid local load ~w, ~w, ~w', K, T, Off
2383valid_instCtx, local_storeW, V, _, Off ←
Ctx = ctx_, Fn, _, _, _, _, filc
else
fn_errorFn, 'invalid local store ~w, ~w', W, Off
valid_useCtx, K, V
2388valid_instCtx, flighttmp_, Op, Args, Lower ←
Ctx = ctx_, Fn, _, _, _, _, filc
memberchkOp, copy, add, sub
lengthArgs, N
→
true
else
fn_errorFn, 'bad flight calculation ~w with ~w', Op, Args
maplistvalid_flight_argCtx, Args
2396valid_instctxM, Fn, _, _, _, _, filc, let_, p, copy, globalS ←
2398valid_instCtx, let_, K, Op, Args ←
Ctx = ctx_, Fn, _, _, _, _, Mode
→
true
else
fn_errorFn, 'unknown instruction ~w', Op
lengthArgs, N
→
true
else
fn_errorFn, 'bad instruction ~w with ~w', Op, Args
op_result_okMode, Op, K
→
true
else
fn_errorFn, 'instruction ~w cannot have class ~w', Op, K
Op ≡ va_args
¬
Args = constOff
integerOff
Off ≥ 0
→
fn_errorFn, 'va_args needs a non-negative constant', []
else
true
Op = setjmpKind
Kind ≢ sigsetjmp
Args ≠ _, const0
→
fn_errorFn, 'setjmp and _setjmp need a zero SaveMask', []
else
true
op_arg_classesOp, K, Mode, ArgClasses
maplistvalid_useCtx, ArgClasses, Args
2420valid_instCtx, atomic_storeW, Value, Addr ←
Ctx = ctx_, Fn, _, _, _, _, Mode
W ≡ p
Mode ≡ filc
→
true
else
fn_errorFn, 'atomic_store(~w, ...) needs a pointer in a Fil-C module', W
valid_useCtx, p, Value
valid_useCtx, p, Addr
2427valid_inst_, fence
2428valid_instCtx, accessK, Addr, Size, Align ←
Ctx = ctx_, Fn, _, _, _, _, Mode
memberchkK, read, write
integerSize
memberchkSize, 8, 16, 32, 64
integerAlign
memberchkAlign, 1, 2, 4, 8
→
addr_classMode, AddrClass
valid_useCtx, AddrClass, Addr
else
fn_errorFn, 'bad access ~w', accessK, Addr, Size, Align
2435valid_instCtx, touchQ ←
Ctx = ctx_, Fn, _, _, _, _, _
nonvarQ
Q = tmp_
→
valid_useCtx, a, Q
else
fn_errorFn, 'bad touch ~w', touchQ

Inputs are exactly class w, not an l read as w (docs/asm-fixed.md).

2441valid_instCtx, asm_fixedM, Ins, Outs ←
Ctx = ctx_, Fn, _, _, TempClasses, _, _
atomM
asm_fixed_regsM, Reads, Writes
is_listIns
split_pairsIns, Reads, Vs
is_listOuts
split_pairsOuts, Rs, Ds
ordered_subsetRs, Writes
forallmemberD, Ds,
nonvarD
D = tmpN
atomN
sortDs, Sorted
lengthSorted, L
lengthDs, L
→
maplistvalid_useCtx, w, Vs
membertmpN, Vs
get_assocN, TempClasses, K
K ≢ w
→
fn_errorFn, 'fixed-register asm input tmp(~w) has class ~w, not w', N, K
else
true
else
fn_errorFn, 'bad fixed-register asm ~w', asm_fixedM, Ins, Outs
2456valid_instCtx, storeWidth, Value, Addr ←
Ctx = ctx_, Fn, _, _, _, _, Mode
Width ≡ p
Mode ≡ plain
→
fn_errorFn, 'store(p, ...) needs a Fil-C module', []
else
true
store_value_classWidth, ValueClass
valid_useCtx, ValueClass, Value
addr_classMode, AddrClass
valid_useCtx, AddrClass, Addr
2466valid_instCtx, raw_storeWidth, Off, Value, Addr ←
Ctx = ctx_, Fn, _, _, _, _, _
Width ≢ p
store_value_classWidth, ValueClass
integerOff
→
true
else
fn_errorFn, 'bad raw store raw_store(~w, ~w, ...)', Width, Off
valid_useCtx, ValueClass, Value
valid_useCtx, a, Addr
2473valid_instCtx, raw_store_pointerOff, Value, Addr, Effects ←
Ctx = ctx_, Fn, _, _, _, _, filc
integerOff
or
pointer_memory_effectslocal_store, Effects
or
pointer_memory_effectsatomic_store, Effects
→
true
else
fn_errorFn, 'bad pointer store offset/effects ~w, ~w', Off, Effects
valid_useCtx, p, Value
valid_useCtx, a, Addr
2483valid_instCtx, callRes, globalCallee, Args ←
Ctx = ctxM, Fn, _, _, _, _, Mode
not_init_entryM, Fn, Callee
findallK, memberargK, _, Args, ArgClasses
func_namedM, Callee, CalleeFunc
CalleeFunc = func_, _, CalleeRet, Params, _
→
findallK, memberparamK, _, Params, ParamClasses
ParamClasses ≡ ArgClasses
or
variadic_funcCalleeFunc
appendParamClasses, _, ArgClasses
→
true
else
fn_errorFn, 'call to global(~w) passes ~w, expects ~w', Callee, ArgClasses, ParamClasses
call_result_okFn, Callee, Res, CalleeRet
Mode ≡ filc
→
true
else
fn_errorFn, 'call to undefined function global(~w)', Callee
forallmemberargK, V, Args, valid_useCtx, K, V
2501valid_instCtx, call_, Callee, Args ←
Ctx = ctx_, Fn, _, _, _, _, Mode
Mode ≡ filc
Callee = tmp_
→
valid_useCtx, p, Callee
else
fn_errorFn, 'bad callee ~w', Callee
forallmemberargK, V, Args, valid_useCtx, K, V
2507valid_instCtx, raw_call_, Callee, Args ←
valid_useCtx, f, Callee
forallmemberargK, V, Args, valid_useCtx, K, V
not_init_entry+M, +Fn, +S

global(S) does not name a constructor or destructor table entry, which is not an object (the header's Data): nothing may take its address or call it.

2514not_init_entryM, Fn, S ←
data_itemM, S, data_, init_, _, _, _
→
fn_errorFn, 'global(~w) names a constructor or destructor table entry', S
else
true
store_value_class?Width, ?K

a store of Width reads its value at K.

2521store_value_classb, w
2522store_value_classh, w
2523store_value_classK, K ←
call_result_ok+Fn, +Callee, +Res, +CalleeRet

the call site reads the result the way the callee returns it.

2527call_result_ok_, _, void, _
2528call_result_okFn, Callee, tupleTos, Ret ←
findallK, memberresult_, K, Tos, Ks
Ret ≡ tupleKs
→
true
else
fn_errorFn, 'reads the ~w result of global(~w) as tuple(~w)', Ret, Callee, Ks
2534call_result_okFn, Callee, result_, K, Ret ←
to_result_okRet, K, Fn, Callee
to_result_ok+Ret, +K, +Fn, +Callee

a call site may read at class K the result of Callee, which returns Ret.

2539to_result_okvoid, _, Fn, Callee ←
fn_errorFn, 'uses the result of void global(~w)', Callee
2541to_result_oktuple_, K, Fn, Callee ←
fn_errorFn, 'reads the tuple result of global(~w) as ~w', Callee, K
2543to_result_okRet, K, Fn, Callee ←
class_result_okRet, K, Fn, Callee
class_result_ok+Ret, +K, +Fn, +Callee

as to_result_ok/4, for a result of class Ret. A long may be read as a word, nothing else changes class.

2550class_result_okRet, K, Fn, Callee ←
Ret ≢ K
→
fn_errorFn, 'reads the ~w result of global(~w) as ~w', Ret, Callee, K
K ≡ l
Ret ≡ w
→
fn_errorFn, 'reads the w result of global(~w) as l', Callee
K ≡ p
Ret ≢ p
or
K ≢ p
Ret ≡ p
→
fn_errorFn, 'reads the ~w result of global(~w) as ~w', Ret, Callee, K
else
true
valid_jump+Ctx, +Jump

Jump names existing labels and returns what the function's return class requires.

2562valid_jumpCtx, gotoT ←
2563valid_jumpCtx, branchV, T1, T2 ←
2567valid_jumpCtx, retV ←
Ctx = ctx_, _, Ret, _, _, _, _
ret_value_okRet, Ctx, V
2570valid_jumpCtx, retsVs ←
Ctx = ctx_, Fn, Ret, _, _, _, _
Ret = tupleKs
lengthKs, N
lengthVs, N
→
maplistvalid_useCtx, Ks, Vs
else
fn_errorFn, 'ret of a tuple in a ~w function', Ret
2576valid_jumpCtx, ret ←
Ctx = ctx_, Fn, Ret, _, _, _, _
2579valid_jump_, unreachable
ret_value_ok+Ret, +Ctx, +V

a function that returns Ret may return the one value V.

2583ret_value_okvoid, ctx_, Fn, _, _, _, _, _, _ ←
fn_errorFn, 'ret with a value in a void function', []
2585ret_value_oktuple_, ctx_, Fn, _, _, _, _, _, _ ←
fn_errorFn, 'ret of one value in a tuple function', []
2587ret_value_okRet, Ctx, V ←
valid_useCtx, Ret, V
ret_void_ok+Ret, +Fn

a function that returns Ret may return nothing.

2593ret_void_okvoid, _
2594ret_void_oktuple_, Fn ←
fn_errorFn, 'ret without a value', []
2595ret_void_okRet, Fn ←
fn_errorFn, 'ret without a value', []
target_exists+Ctx, +T

the jump target T, a label or poll(Label), names a block.

2601target_existsCtx, T ←
2605label_existsctx_, Fn, _, LabelSet, _, _, _, L ←
get_assocL, LabelSet, _
→
true
else
fn_errorFn, 'undefined block label ~w', L

Projections are local to flight, not new ordinary operands. A lower is copied only from a p value; no integer can manufacture one.

2612valid_flight_argCtx, addressP ←
!
valid_useCtx, p, P
2613valid_flight_argCtx, V ←
valid_useCtx, l, V
2615valid_flight_lowernone, _
2616valid_flight_lowerlowerP, Ctx ←
valid_useCtx, p, P
valid_use+Ctx, +K, +V

V may be read at class K. Arguments are in the order maplist needs; valid_use_/3 indexes on the value.

2620valid_useCtx, K, V ←
Ctx = ctx_, Fn, _, _, _, _, _
→
true
else
fn_errorFn, 'invalid use class ~w', K
valid_use_V, Ctx, K

A p temporary is used only at p and vice versa; ptoi and itop are the conversions. An integer constant at p is a pointer with no capability. Only a check makes a proof (class a or f): no constant is one.

2628valid_use_tmpN, ctx_, Fn, _, _, TempClasses, _, _, K ←
get_assocN, TempClasses, K0
→
→
fn_errorFn, 'tmp(~w) has class ~w but is used as ~w', N, K0, K
→
fn_errorFn, 'tmp(~w) has class ~w but is used as ~w', N, K0, K
K ≡ l
K0 ≡ w
→
fn_errorFn, 'tmp(~w) has class w but is used as l', N
K0 ≡ p
K ≢ p
→
fn_errorFn, 'tmp(~w) has class p but is used as ~w (use ptoi)', N, K
K ≡ p
K0 ≢ p
→
fn_errorFn, 'tmp(~w) has class ~w but is used as p (use itop)', N, K0
else
true
else
fn_errorFn, 'undefined temporary tmp(~w)', N
2646valid_use_constN, ctx_, Fn, _, _, _, _, _, K ←
integerN
N < 0
or
N ≥ M
→
fn_errorFn, 'float bits out of range: ~w', N
→
fn_errorFn, 'const(~w) used as a proof (class ~w)', N, K
else
true
2653valid_use_globalG, Ctx, K ←
Ctx = ctx_, _, _, _, _, _, Mode
symbol_use_okMode, G, Ctx, K
symbol_use_ok+Mode, +G, +Ctx, +K

the symbol global(G) may be read at class K in a module of Mode: in plain mode as an address of data, in filc mode never (copy, which yields its pointer, is checked apart).

2660symbol_use_okfilc, G, ctx_, Fn, _, _, _, _, _, _ ←
fn_errorFn, 'in a Fil-C module only copy at p reads global(~w)', G
2662symbol_use_okplain, G, ctxM, Fn, _, _, _, _, _, K ←
addr_classplain, AddrClass
K ≡ AddrClass
→
true
else
fn_errorFn, 'global(~w) used as ~w', G, K
→
true
else
fn_errorFn, 'global(~w) is not a data definition', G

Operand and result classes

op_result_ok+Mode, ?Op, ?Knondet

Op may produce a result of class K in a module of Mode. With Op partial (cmp(_, _), ext(_), load(_)) it enumerates the operands.

2677op_result_okMode, Op, K ←
op_kindOp, Kind
kind_result_okKind, Op, Mode, K
kind_result_ok+Kind, +Op, +Mode, +K

as op_result_ok/3, for Op of Kind.

2683kind_result_okadditive, _, _, K ←
2684kind_result_okalu, Op, _, K ←
or
memberchkOp, mul, div
2686kind_result_okshift, _, _, K ←
2687kind_result_okneg, _, _, K ←
2688kind_result_okcopy, _, _, K ←
2689kind_result_okptoi, _, filc, l
2690kind_result_okitop, _, filc, p
2691kind_result_okva_args, _, filc, p
2692kind_result_okva_arg, _, filc, K ←
2693kind_result_oksetjmp, _, filc, w
2694kind_result_okcmp, cmpC, CK, _, K ←
→
K = w
2696kind_result_okfloat_convert, exts, _, K ←
memberchkK, d, x
2697kind_result_okfloat_convert, extd, _, x
2698kind_result_okfloat_convert, truncd, _, s
2699kind_result_okfloat_convert, truncx, _, K ←
memberchkK, s, d
2700kind_result_okfloat_to_int, _, _, K ←
2701kind_result_okint_to_float, _, _, K ←
2702kind_result_okcast, _, _, K ←
2703kind_result_okext, extT, _, K ←
2704kind_result_okload, loadT, Mode, K ←
load_result_okT, Mode, K
2705kind_result_okatomic_load, _, filc, p
2706kind_result_okalloc, _, Mode, K ←
addr_classMode, K
2707kind_result_okcheck, _, filc, a
2708kind_result_okcheck_ahead, _, filc, a
2709kind_result_okrenew, _, filc, a
2710kind_result_okrenew_hull, _, filc, a
2711kind_result_okhalf_check, _, filc, a
2712kind_result_okspan_test, _, filc, w
2713kind_result_okrenew_if, _, filc, a
2714kind_result_oklimit, _, filc, l
2715kind_result_okcheck_limit, _, filc, a
2716kind_result_okcheck_local, _, filc, a
2717kind_result_okcheck_function, _, filc, f
2718kind_result_okld, raw_loadT, _, filc, K ←
load_result_okT, filc, K
2719kind_result_okld_pointer, _, filc, p
2720kind_result_okcas, casT, filc, K ←
load_result_okT, filc, K
2721kind_result_okraw_cas, raw_casT, _, filc, K ←
load_result_okT, filc, K
ext_result_ok+T, +K

an extension of type T may have class K; one from a word is a long.

2725ext_result_oksw, l
2726ext_result_okuw, l
2727ext_result_oksh, K ←
2728ext_result_okuh, K ←
2729ext_result_oksb, K ←
2730ext_result_okub, K ←
load_result_ok+T, +Mode, +K

a load of type T may have class K in a module of Mode.

2734load_result_okl, _, l
2735load_result_okp, filc, p
2736load_result_oks, _, s
2737load_result_okd, _, d
2738load_result_okx, filc, x
2739load_result_oksw, _, K ←
2740load_result_okuw, _, K ←
2741load_result_oksh, _, K ←
2742load_result_okuh, _, K ←
2743load_result_oksb, _, K ←
2744load_result_okub, _, K ←
op_arg_classes+Op, +K, +Mode, ?Classessemidet

Classes is the list of classes at which the arguments of Op are read when its result has class K in a module of Mode. Fails for an Op outside the grammar.

2752op_arg_classesOp, K, Mode, Classes ←
op_kindOp, Kind
kind_arg_classesKind, Op, K, Mode, Classes
kind_arg_classes+Kind, +Op, +K, +Mode, ?Classes

as op_arg_classes/4, for Op of Kind.

2758kind_arg_classesadditive, _, K, _, Classes ←
2760kind_arg_classesalu, _, K, _, K, K
2761kind_arg_classesshift, _, K, _, K, w
2762kind_arg_classesneg, _, K, _, K
2763kind_arg_classescopy, _, K, _, K
2764kind_arg_classesptoi, _, _, _, p
2765kind_arg_classesitop, _, _, _, l
2766kind_arg_classesva_args, _, p, filc, l
2767kind_arg_classesva_arg, _, _, filc, p
2768kind_arg_classessetjmp, _, w, filc, p, w
2769kind_arg_classescmp, cmp_, CK, _, _, CK, CK
2770kind_arg_classesfloat_convert, exts, _, _, s
2771kind_arg_classesfloat_convert, truncd, _, _, d
2772kind_arg_classesfloat_convert, extd, _, _, d
2773kind_arg_classesfloat_convert, truncx, _, _, x
2774kind_arg_classesfloat_to_int, Op, _, _, K ←
2775kind_arg_classesint_to_float, Op, _, _, K ←
memberchkOp, swtof, uwtof
→
K = w
else
K = l
2777kind_arg_classescast, _, K, _, From ←
2778kind_arg_classesext, _, _, _, w
2779kind_arg_classesload, _, _, Mode, A ←
addr_classMode, A
2780kind_arg_classesatomic_load, _, p, filc, p
2781kind_arg_classesalloc, _, _, _, l
2782kind_arg_classescheck, _, _, filc, p
2783kind_arg_classescheck_ahead, _, _, filc, p
2784kind_arg_classesrenew, _, _, filc, a, p
2785kind_arg_classesrenew_hull, _, _, filc, a, a, p
2786kind_arg_classeshalf_check, _, _, filc, a, p
2787kind_arg_classesspan_test, _, _, filc, p
2788kind_arg_classesrenew_if, _, _, filc, p, w
2789kind_arg_classeslimit, _, _, filc, p
2790kind_arg_classescheck_limit, _, _, filc, p, l
2791kind_arg_classescheck_local, _, _, filc, []
2792kind_arg_classescheck_function, _, _, filc, p
2793kind_arg_classesld, _, _, filc, a
2794kind_arg_classesld_pointer, _, _, filc, a
2795kind_arg_classescas, _, K, filc, K, K, p
2796kind_arg_classesraw_cas, _, K, filc, a, K, K
additive_arg_classes+K, ?Classes

add and sub of class K read Classes; at p they offset a pointer by an l.

additive_arg_classes
2800ww, w
2801ll, l
2802pp, l
2803ss, s
2804dd, d
2805xx, x
float_int_source?Op, ?K

the float-to-integer Op reads class K.

float_int_source
2808stosis
2808stouis
2809dtosid
2809dtouid
2810xtosix
2810xtouix
cast_classes
2812ws
2813sw
2814ld
2815dl
fn_error+Fn, +Fmt, +Args

throw nanofil_error with the message Fmt/Args, attributed to the function Fn.

2819fn_errorFn, Fmt, Args ←
formatatomMsg, Fmt, Args
formatatomFull, 'in ~w: ~w', Fn, Msg
thrownanofil_errorFull
without_locs+Module0, -Moduledet

Erase explanatory metadata at execution/code-generation boundaries. Never use erasure to relax checked-IR validation's adjacency rule.

2828without_locsmoduleAll, moduleItems ←
code_itemsAll, Items0
maplistitem_without_locs, Items0, Items
2832item_without_locsfuncN, L, R, Ps, Bs0, funcN, L, R, Ps, Bs ←
!
maplistblock_without_locs, Bs0, Bs
2834item_without_locsItem, Item
2836block_without_locsblockL, Phis, Insts0, J, blockL, Phis, Insts, J ←
excludeis_loc, Insts0, Insts
2839is_locloc_, _
asm_fixed_regs?M, ?Reads, ?Writesnondet

The fixed-register instructions asm_fixed may name (docs/asm-fixed.md), with the registers each reads and writes (a, b, c, d: rax to rdx, of which only the low 32 bits are read; every write is 32 bits and zero-extends). mfence is the fence instruction.

asm_fixed_regs
2847cpuida, ca, b, c, d
2848xgetbvca, d
2849rdtsc[]a, d
2850rdtscp[]a, c, d
2851lfence[][]
2852sfence[][]
2853pause[][]
ordered_subset+Sub, +Set

Sub lists distinct elements of Set, in Set's order.

2857ordered_subset[], _
2858ordered_subsetX|Xs, Y|Ys ←
X ≡ Y
→
else