Fil-C on Nix

Filnix wiki

mbrock/filnix/nanofil

interp.pl

Relational semantics of the IR (ir.pl): the outcome of running a function of a module, as a relation that runs in every direction, so that arguments and outcome can both be partially unknown.

Entry points

PredicateDoes
run+Module, +Fn, ?Args, +Fuel, ?Outcomeprepares Module, then runs Fn
module_prepared+Module, −Preparedthe module's mode, initial memory, and indexes of its functions and blocks
run_prepared+Prepared, +Fn, ?Args, +Fuel, ?Outcomeruns Fn of a prepared module

Prepare once to run many times. Args are integers or CLP(Z) variables, one per parameter, in the range of the parameter's class (ptrAddr, Cap for a p parameter).

Outcomes

OutcomeMeans
retVreturned V (an integer or constrained variable)
retsX1, X2returned a tuple
retreturned from a void function
unreachableexecuted unreachable
ubReasonundefined behaviour: float_conversion (an invalid float-to-integer conversion), or in plain mode out_of_boundsA, Size, a memory access outside every live allocation
trapReasona Fil-C check failed (filc mode only): null_capability, out_of_bounds, misaligned, readonly, not_a_function, argument_size, return_size, invalid_free, double_free
uncheckedReasona raw access (checked stage, ir.pl) that its capability does not allow: a check is missing or too weak, which only a broken pass produces. It is neither ub nor a trap, so it refines nothing.
out_of_fuelexecuted Fuel blocks without finishing
unknownasmM, Xsreached the fixed-register asm M (cpuid, xgetbv, rdtsc, rdtscp) on the input values Xs, whose results only the machine knows (docs/asm-fixed.md): native tests cover what follows

Unknown values

Integer arithmetic and branches are CLP(Z) constraints, so the relation runs in every direction. Known values take a fast path (is/2 and if-then-else), which gives the same answers; only unknown values pay for constraints.

Memory addresses, allocation sizes and capabilities must be known when an access, alloc or indirect call uses them; otherwise the run raises an instantiation error, which is incomplete but never wrong.

Float values are concrete unsigned IEEE bits. Float arithmetic uses ieee.pl's integer-only SSE2 semantics, not CLP(Z) or host floats; unknown float inputs also raise an instantiation error.

Memory

Memory is memBytes, Regions, Next, Aux:

PartIs
Bytesan assoc from address to byte
RegionsregionBase, End, Kind, Life objects; Kind is stack, heap or global, Life is live or freed
Nextthe next allocation address; addresses are never reused
Auxin filc mode, the capabilities stored in memory

A byte nobody wrote is, in plain mode, a fresh variable in 0..255, so reading uninitialized memory gives an unknown value rather than a made-up one; in filc mode it is 0, since Fil-C zeroes allocations.

In plain mode a function's allocations die when it returns, and addresses are never reused, so use after return is an out-of-bounds access. In filc mode allocations outlive their function. External callvoid, globalfree, argp, P invalidates P's heap object: End becomes Base and Life becomes freed. A live zero-sized object also has End = Base, but may be freed once. Bytes and stored lowers stay intact; every alias consults the same region's current bounds. Module-defined functions take precedence over this external adapter.

A capability's Base is stable identity, never recovered from address bits. All exposed capabilities for an object have the same rights (objBase or roBase); snapshot initialization is private until its readonly capability is published. Memory-using injected pointer arguments must obey these invariants and name existing objects.

This is the deterministic explicit-free subset, not a GC simulator: no reclamation, concurrent or signal-handler frees, or rewriting dead lowers to the runtime's free singleton. Unknown external effects and addresses remain outside the oracle's domain. It does not justify check elimination or safepoint motion (docs/lifetime.md).

Run state

The run state is sMem, Fuel; each block costs one unit of Fuel. Inside a function the interpreter carries ctxFns, Globals, Blocks, Ret, Mode, Locals:

PartIs
Fnsthe prepared functions
Globalsthe address of each global
Blocksthe function's blocks by label
Retits return class
Modethe module mode
Localsthis invocation's local names and fresh object identities, bound at local_alloc

A block is entered fromPred, Pred the label of the block that jumped to it, or at entry.

Variable names

NameIs
Ka class
Van IR value: tmpN, constN or globalN
Xa run-time value: an integer, a constrained variable, or in filc mode ptrAddr, Cap
Envthe assoc from temporary to run-time value
Sa run state
Outan outcome
Resa partial result: valX, ok, ubWhy, trapWhy or uncheckedWhy

Env0/Env and S0/S are the state before and after.

usesclpz
usesassoc
useslists
useserror
usesir
usesieee
usescfgedge_label/2
usesprovenancecode_items/2
run+M, +Fn, ?Args, +Fuel, ?Outcomenondet

Outcome is the result of calling the function Fn of module M on Args and executing at most Fuel blocks. With unknown Args each solution constrains them to the inputs that take one path; see run_prepared/5.

132runM, Fn, Args, Fuel, Outcome ←
module_preparedM, Prepared
run_preparedPrepared, Fn, Args, Fuel, Outcome
module_prepared+M, -Prepareddet

Prepared is M ready to run: its mode, the address of each global, its initial memory, and its functions and their blocks indexed by name and label. The initial memory holds integers only, so runs can share it.

142module_preparedM0, prepFns, Globals, Mode, Mem0 ←
M0 = moduleAll
code_itemsAll, Items
M = moduleItems
initial_memoryMode, M, Globals, Mem0
findallName−fnRet, Params, Entry, BlockMap,
memberfuncName, _, Ret, Params, Blocks, Items
Blocks = blockEntry, _, _, _|_
findallLabel−Block,
memberBlock, Blocks
Block = blockLabel, _, _, _
,
LabelBlocks
list_to_assocLabelBlocks, BlockMap
,
NameFns0
An alias of a function is called as its target.
findallAlias−Fn,
memberaliasAlias, _, Target, Items
memberchkTarget−Fn, NameFns0
,
AliasFns
appendNameFns0, AliasFns, NameFns
list_to_assocNameFns, Fns
run_prepared+Prepared, +Fn, ?Args, +Fuel, ?Outcomenondet

Outcome is the result of calling Fn of the prepared module on Args within Fuel blocks. Args is constrained to the ranges of Fn's parameter classes. Where a branch or a division depends on an unknown value (an argument, or a byte nobody wrote), there is one solution per case, each constraining that value; comparisons are reified instead. When every such value is known the outcome is unique, though the run may leave a choice point, so callers use once/1. Fails when the module defines no Fn. Throws nanofil_error when the run needs what the interpreter does not model (a symbol the module does not define, a temporary read before any assignment), and an instantiation error when an address, allocation size or capability is unknown.

176run_preparedprepFns, Globals, Mode, Mem0, Fn, Args, Fuel, Outcome ←
get_assocFn, Fns, fn_, Params, _, Blocks
assoc_to_valuesBlocks, Bs
variadic_funcfunc_, _, _, _, Bs
→
lengthParams, N
lengthFixed, N
appendFixed, _, Args
else
Fixed = Args
maplistarg_in_range, Params, Fixed
call_outcomeFns, Globals, Mode, Fn, Args, sMem0, Fuel, Outcome, _
arg_in_range+Param, ?X

X is a value of Param's class.

185arg_in_rangeparamK, _, X ←
class_arg+K, ?X

X is an argument of class K. A p argument is ptr(Addr, Cap), or an integer address with no pointer term (main.pl's canonical arguments): a defaulty case that comes from outside this module, so the cut stays. An unknown p argument is ptr(_, _).

191class_argw, X ←
192class_argl, X ←
193class_args, X ←
must_beinteger, X
194class_argd, X ←
must_beinteger, X
195class_argp, X ←
X = ptr_, _
→
true
200int_arg_in_rangeK, X ←
modulusK, Mod
Top #= Mod−1
X in 0 .. Top
call_outcome+Fns, +Globals, +Mode, +Fn, +Xs, +S0, -Out, -Snondet

Calling Fn on Xs in state S0 ends with Out in state S. In plain mode a callee's allocations die when it returns; in filc mode every allocation is a heap object that lives until explicitly freed. A variadic callee whose argument snapshot the address space cannot hold traps out_of_memory at its entry, as Fil-C does.

212call_outcomeFns, Globals, Mode, Fn, Args, S0, Out, S ←
get_assocFn, Fns, fnRet, Params, Entry, Blocks
→
true
else
formatatomMsg, 'cannot interpret a call to external ~w', Fn
thrownanofil_errorMsg
empty_assocEnv0
lengthParams, NParams
lengthFixed, NParams
appendFixed, _, Args
foldlparam_arg_env, Params, Fixed, Env0, Env1
entry_snapshotBlocks, Args, Env1, Env, S0, SEntry
→
assoc_to_valuesBlocks, Bs
local_objectsfuncFn, local, Ret, Params, Bs, Objects
findallName−_, memberName−_, Objects, LocalPairs
list_to_assocLocalPairs, Locals
Ctx = ctxFns, Globals, Blocks, Ret, Mode, Locals
block_outcomeCtx, Entry, entry, Env, SEntry, Out, S1
assoc_to_valuesLocals, Bases
invalidate_localsBases, S1, S2
S0 = smem_, Regions, _, _, _
returned_stateMode, Regions, S2, S
else
Out = trapout_of_memory
S = S0

Keep identities, bytes and aux, but end just this invocation's stack lifetimes in the latest state. A trap may precede a declaration; its unbound Base is not authority to select and invalidate another object.

237invalidate_locals[], S, S
238invalidate_localsBase|Bases, smemBytes, Rs0, Next, Aux, Fuel, S ←
integerBase
→
selectregionBase, _, stack, live, Rs0, Rest
Rs = regionBase, Base, stack, freed|Rest
else
Rs = Rs0
invalidate_localsBases, smemBytes, Rs, Next, Aux, Fuel, S
returned_state+Mode, +Regions, +S1, -S

S is the caller's state after a callee that ended in S1, Regions the caller's live regions.

247returned_stateplain, Regions, smemBytes, _, Next, Aux, Fuel, smemBytes, Regions, Next, Aux, Fuel
249returned_statefilc, _, S, S
251param_arg_envparam_, tmpN, X, Env0, Env ←
put_assocN, Env0, X, Env
entry_snapshot+Blocks, +Args, +Env0, -Env, +S0, -Ssemidet

a variadic callee's entry takes the snapshot of its arguments. Fails when the address space cannot hold the snapshot (alloc_base/6).

256entry_snapshotBlocks, Args, Env0, Env, sMem0, Fuel, sMem, Fuel ←
assoc_to_valuesBlocks, Bs
variadic_funcfunc_, _, _, _, Bs
→
args_snapshotArgs, Mem0, Snapshot, Mem
put_assochiddenva_snapshot, Env0, Snapshot, Env
else
Env = Env0
Mem = Mem0

promote_cc_to_heap returns null for an empty argument packet. This is distinct from alloc(0), which creates an ordinary zero-sized object.

265args_snapshot[], Mem, ptr0, none, Mem
266args_snapshotX|Xs, Mem0, ptrBase, roBase, Mem ←
lengthX|Xs, N
Size is N × 8
alloc_basefilc, Mem0, 16, Size, Base, Mem1
snapshot_valuesX|Xs, Base, Base, Mem1, Mem
271snapshot_values[], _, _, Mem, Mem
272snapshot_valuesX|Xs, A, Base, Mem0, Mem ←
nonvarX
X = ptr_, _
→
W = p
else
W = l
Snapshot initialization precedes publishing its readonly capability.
store_resfilc, W, Mem0, ptrA, objBase, X, ok, Mem1
A1 is A + 8
snapshot_valuesXs, A1, Base, Mem1, Mem
int_is?R, +Expr

R is the value of the integer expression Expr. Concrete fast path: when every operand is known, plain arithmetic and if-then-else give the same answers as the constraints, without propagators or choice points. Unknown operands still get constraints. The ground/1 test reads the instantiation, but both branches state the same relation, so the program stays sound for partially known Expr.

284int_isR, Expr ←
groundExpr
→
R is Expr
else
R #= Expr
reify+Test, ?B

B is 1 if Test holds, else 0 (reified when unknown).

287reifyTest, B ←
groundTest
→
callTest
→
B = 1
else
B = 0
else
B in 0 .. 1
293reify_constraintX =:= Y, B ←
B #<==> X #= Y
294reify_constraintX ≠ Y, B ←
B #<==> X #≠ Y
295reify_constraintX < Y, B ←
B #<==> X #< Y
296reify_constraintX ≤ Y, B ←
B #<==> X #≤ Y
297reify_constraintX > Y, B ←
B #<==> X #> Y
298reify_constraintX ≥ Y, B ←
B #<==> X #≥ Y

Blocks

block_outcome+Ctx, +Label, +Pred, +Env0, +S0, -Out, -Snondet

Out and S are the outcome and final state of running the block Label, entered from Pred (from(L) or entry), and everything after it. Each block costs one unit of fuel.

307block_outcomeCtx, Label, Pred, Env0, sMem, Fuel0, Out, S ←
Fuel0 =:= 0
→
Out = out_of_fuel
S = sMem, Fuel0
else
Fuel1 is Fuel0−1
Ctx = ctx_, _, Blocks, _, _, _
get_assocLabel, Blocks, block_, Phis, Insts, Jump
phis_envPhis, Ctx, Pred, Env0, Env0, Env1
insts_resultInsts, Ctx, Env1, sMem, Fuel1, Result
result_outcomeResult, Jump, Ctx, Label, Out, S
phis_env+Phis, +Ctx, +Pred, +EntryEnv, +Env0, -Env

Env is Env0 with each phi of Phis bound to its value for the edge from Pred, read in EntryEnv. Phis read the environment as it was on entry, so they all bind at once. An entry block has no phis.

323phis_env[], _, _, _, Env, Env
324phis_envphitmpD, K, Pairs|Phis, Ctx, fromPred, EntryEnv, Env0, Env ←
memberchkPred−V, Pairs
value_xK, V, Ctx, EntryEnv, X
put_assocD, Env0, X, Env1
phis_envPhis, Ctx, fromPred, EntryEnv, Env1, Env
result_outcome+Result, +Jump, +Ctx, +Label, -Out, -S

the block's Jump continues the run unless an instruction ended it. A doomed run (a check_ahead's early failure, docs/schedule.md) must reach its trap within the block it is in or past plain edges: a return or a poll edge first means the early check was not anticipated.

335result_outcomecontEnv, S0, Jump, Ctx, Label, Out, S ←
get_assochiddendoomed, Env, Why
¬Jump = goto_
¬Jump = branch_, _, _
→
Out = uncheckedaheadWhy
S = S0
else
jump_outcomeJump, Ctx, Label, Env, S0, Out, S
340result_outcomestopOut, S, _, _, _, Out, S
jump_outcome+Jump, +Ctx, +From, +Env, +S0, -Out, -Snondet

Out is the outcome of Jump, ending block From, and everything after it. When the jnz condition is unknown, both edges are solutions, each constraining it; a known one takes the fast path. A pollcheck on the edge (poll(L)) changes nothing the interpreter models: explicit free calls run, but there are no asynchronous frees.

349jump_outcomegotoT, Ctx, From, Env, S0, Out, S ←
edge_outcomeT, Ctx, From, Env, S0, Out, S
351jump_outcomebranchV, T1, T2, Ctx, From, Env, S0, Out, S ←
value_xw, V, Ctx, Env, X
integerX
→
X ≠ 0
→
T = T1
else
T = T2
else
X #≠ 0
T = T1
or
X #= 0
T = T2
edge_outcomeT, Ctx, From, Env, S0, Out, S
358jump_outcomeretV, Ctx, _, Env, S, retX, S ←
Ctx = ctx_, _, _, K, _, _
value_xK, V, Ctx, Env, X
361jump_outcomeretsVs, Ctx, _, Env, S, retsXs, S ←
Ctx = ctx_, _, _, tupleKs, _, _
maplistclass_value_xCtx, Env, Ks, Vs, Xs
364jump_outcomeret, _, _, _, S, ret, S
365jump_outcomeunreachable, _, _, _, S, unreachable, S
367edge_outcomeT, Ctx, From, Env, S0, Out, S ←
T = poll_
get_assochiddendoomed, Env, Why
→
Out = uncheckedaheadWhy
S = S0
else
block_outcomeCtx, L, fromFrom, Env, S0, Out, S

Instructions

insts_result+Insts, +Ctx, +Env0, +S0, -Resultnondet

Result is cont(Env, S) when every instruction ran, or stop(Outcome, S) when one ended the run.

380insts_result[], _, Env, S, contEnv, S
381insts_resultInst|Insts, Ctx, Env0, S0, Result ←
get_assochiddendoomed, Env0, Why
→
Result = stopuncheckedaheadWhy, S0
else
inst_resultInst, Ctx, Env0, S0, Result0
doomed_resultInst, Ctx, Env0, Result0, Result1
rest_resultResult1, Insts, Ctx, Result
doomed_result+Inst, +Ctx, +Env0, +Result0, -Result

a check_ahead whose early bytes fail (docs/schedule.md) yields its pointer and dooms the run: from then on it must reach, through ahead_transparent/1 instructions only, an access whose reference check fails, which shows here as a renew's failed span or a raw access's unchecked bounds (a read of the widened proof itself). That is the program's trap, made early(Why); anything else first is unchecked(ahead(Why)). A renew of another object (alias) or a wrong limit stays a broken pass.

397doomed_resultlettmpD, a, check_ahead_, _, _, _, _, V, Ctx, Env0, stoptrapearlyWhy, S, contEnv, S ←
!
value_xp, V, Ctx, Env0, P
put_assocD, Env0, P, Env1
get_assochiddendoomed, Env1, _
→
Env = Env1
else
put_assochiddendoomed, Env1, Why, Env
404doomed_result_, _, Env0, stopuncheckedW, S, stoptrapearlyWhy, S ←
¬memberchkW, alias, limit
get_assochiddendoomed, Env0, Why
!
407doomed_result_, _, _, Result, Result
rest_result+Result0, +Insts, +Ctx, -Result

Result is the result of the instructions Insts after one whose result is Result0.

411rest_resultcontEnv, S, Insts, Ctx, Result ←
insts_resultInsts, Ctx, Env, S, Result
413rest_resultstopOut, S, _, _, stopOut, S
inst_result+Inst, +Ctx, +Env0, +S0, -Resultnondet

Result is cont(Env, S) after Inst, or stop(Outcome, S) when it ends the run.

419inst_resultloc_, _, _, Env, S, contEnv, S ←
!
420inst_resultlocal_allocName, Size, ctx_, _, _, _, filc, Locals, Env, sMem0, Fuel, Result ←
get_assocName, Locals, Base
varBase
→
true
else
thrownanofil_error'duplicate or undeclared local allocation'
alloc_baselocal, Mem0, 16, Size, Base, Mem
→
Result = contEnv, sMem, Fuel
else
Result = stoptrapout_of_memory, sMem0, Fuel
428inst_resultlocal_loadtmpD, K, T, Name, Off, Ctx, Env, S0, Result ←
local_pointerCtx, Name, Off, P
load_op_resT, K, filc, P, S0, Res, S
res_resultRes, D, Env, S, Result
432inst_resultlocal_storeW, V, Name, Off, Ctx, Env, sMem0, Fuel, Result ←
local_pointerCtx, Name, Off, P
value_xK, V, Ctx, Env, X
store_resfilc, W, Mem0, P, X, Res, Mem
store_res_resultRes, Env, sMem, Fuel, Result
437inst_resultlettmpD, a, check_localName, Off, Size, Rights, Align, [], Ctx, Env, sMem, Fuel, Result ←
!
local_pointerCtx, Name, Off, ptrA, Cap
access_resMem, A, Cap, Size, Rights, Align, Check
checked_resCheck, ptrA, Cap, Res
res_resultRes, D, Env, sMem, Fuel, Result
443inst_resultflighttmpD, Op, Args, Lower, Ctx, Env0, S, Result ←
maplistflight_arg_xCtx, Env0, Args, Xs
aluOp, l, Xs, valA
flight_capLower, Ctx, Env0, Cap
res_resultvalptrA, Cap, D, Env0, S, Result
448inst_resultlettmpD, p, va_args, constOff, _, Env0, S, Result ←
!
get_assochiddenva_snapshot, Env0, ptrA, Cap
B is A + Off mod 18446744073709551616
res_resultvalptrB, Cap, D, Env0, S, Result
452inst_resultlettmpD, f, check_function, V, Ctx, Env0, S, Result ←
!
value_xp, V, Ctx, Env0, P
function_check_resP, Ctx, Check
checked_resCheck, P, Res
res_resultRes, D, Env0, S, Result
457inst_resultlettmpD, K, Op, Args, Ctx, Env0, S0, Result ←
Ctx = ctx_, _, _, _, Mode, _
op_arg_classesOp, K, Mode, ArgClasses
maplistclass_value_xCtx, Env0, ArgClasses, Args, Xs
op_resOp, K, Mode, Xs, S0, Res, S
res_resultRes, D, Env0, S, Result
463inst_resultstoreWidth, V, A, Ctx, Env, sMem0, Fuel, Result ←
Ctx = ctx_, _, _, _, Mode, _
width_value_classWidth, ValueClass
value_xValueClass, V, Ctx, Env, X
addr_classMode, AddrClass
value_xAddrClass, A, Ctx, Env, Addr
store_resMode, Width, Mem0, Addr, X, Res, Mem
store_res_resultRes, Env, sMem, Fuel, Result

One thread and no atomic boxes: an atomic pointer store is a pointer store, and a fence does nothing observable.

473inst_resultatomic_storep, V, A, Ctx, Env, S, Result ←
!
inst_resultstorep, V, A, Ctx, Env, S, Result
475inst_resultfence, _, Env, S, contEnv, S ←
!

A vector's full-width check (docs/vector-access.md): in a Fil-C module the access check of Size bytes, which writes nothing; in a plain one nothing, since C's semantics are the chunks'. touch uses a proof.

479inst_resultaccessK, A, Size, Align, Ctx, Env, sMem, Fuel, Result ←
!
Ctx = ctx_, _, _, _, Mode, _
Mode ≡ filc
→
value_xp, A, Ctx, Env, ptrAddr, Cap
access_resMem, Addr, Cap, Size, K, Align, Res
store_res_resultRes, Env, sMem, Fuel, Result
else
Result = contEnv, sMem, Fuel
487inst_resulttouch_, _, Env, S, contEnv, S ←
!

One thread in program order: lfence, sfence and pause change nothing. The others' results are the machine's, so the run stops there.

490inst_resultasm_fixedM, Ins, _, Ctx, Env, S, Result ←
!
memberchkM, lfence, sfence, pause
→
Result = contEnv, S
else
maplistasm_in_xCtx, Env, Ins, Xs
Result = stopunknownasmM, Xs, S
496inst_resultraw_storeWidth, Off, V, A, Ctx, Env, sMem0, Fuel, Result ←
width_value_classWidth, ValueClass
value_xValueClass, V, Ctx, Env, X
value_xa, A, Ctx, Env, ptrB, Cap
raw_addressB, Off, Addr
store_resfilc, Width, Mem0, ptrAddr, Cap, X, Res0, Mem
raw_resRes0, Res
store_res_resultRes, Env, sMem, Fuel, Result
504inst_resultraw_store_pointerOff, V, A, Effects, Ctx, Env, sMem0, Fuel, Result ←
value_xp, V, Ctx, Env, X
value_xa, A, Ctx, Env, ptrB, Cap
raw_addressB, Off, Addr
access_resMem0, Addr, Cap, 8, write, 8, Check
pointer_store_effects_resCheck, Effects, Addr, X, Mem0, Res, Mem
store_res_resultRes, Env, sMem, Fuel, Result
512inst_resultcallRes, globalF, Args, Ctx, Env0, S0, Result ←
Ctx = ctxFns, Globals, _, _, Mode, _
maplistarg_xCtx, Env0, Args, Xs
get_assocF, Fns, _
→
call_outcomeFns, Globals, Mode, F, Xs, S0, Out, S
get_assocF, Globals, _
→
Data the module defines: native code's function check traps.
Out = trapnot_a_function
S = S0
else
external_call_outcomeMode, Globals, F, Res, Args, Xs, S0, Out, S
call_outcome_resultOut, Res, Env0, S, Result
523inst_resultcallRes, tmpN, Args, Ctx, Env0, S0, Result ←
value_xp, tmpN, Ctx, Env0, ptrA, Cap
maplistarg_xCtx, Env0, Args, Xs
function_check_resptrA, Cap, Ctx, Check
checked_call_resultCheck, Cap, Res, Args, Xs, Ctx, Env0, S0, Result
528inst_resultraw_callRes, Callee, Args, Ctx, Env0, S0, Result ←
value_xf, Callee, Ctx, Env0, ptrA, Cap
maplistarg_xCtx, Env0, Args, Xs
function_check_resptrA, Cap, Ctx, Check0
raw_resCheck0, Check
checked_call_resultCheck, Cap, Res, Args, Xs, Ctx, Env0, S0, Result
535local_pointerctx_, _, _, _, filc, Locals, Name, Off, ptrA, objBase ←
get_assocName, Locals, Base
integerBase
→
true
else
thrownanofil_error'local access before allocation'
A is Base + Off

Only libc free's ordinary signature is modeled. Resolution comes first: an in-module function or data symbol is handled above. Other signatures and external calls are explicit gaps, not silently failed relations or guessed libc effects.

544external_call_outcomefilc, _, free, Res, Args, Xs, sMem0, Fuel, Out, sMem, Fuel ←
!
Res ≡ void
Args = argp, _
Xs = P
→
free_resP, Mem0, Freed, Mem
free_outcomeFreed, Out
else
thrownanofil_error'interpreter supports only void free(p)'
550external_call_outcome_, _, F, _, _, _, _, _, _ ←
formatatomMsg, 'cannot interpret a call to external ~w', F
thrownanofil_errorMsg
554free_outcomeok, ret
555free_outcometrapWhy, trapWhy
width_value_class+Width, -K

a store of Width reads its value at K.

width_value_class
558bw
559hw
560ww
561ll
562pp
563ss
564dd
565xx
function_check_res+P, +Ctx, -Resdet

P designates a function only when it carries that function's capability at its exact callable address. An unknown address is not proof; an external symbol's kind is unknown to the interpreter, which must not guess that it names a function.

573function_check_resptrA, Cap, ctx_, Globals, _, _, _, _, Res ←
must_becallable, Cap
cap_function_resCap, A, Globals, Res
577cap_function_resfnF, A, Globals, Res ←
get_assocF, Globals, ptrFnAddr, _
A ≡ FnAddr
→
Res = ok
else
Res = trapnot_a_function
583cap_function_resextF, _, _, _ ←
formatatomMsg, 'cannot interpret a call to external ~w', F
thrownanofil_errorMsg
586cap_function_resnone, _, _, trapnot_a_function
587cap_function_resobj_, _, _, trapnot_a_function
588cap_function_resro_, _, _, trapnot_a_function

A target check authorizes the call, not its signature. Only a failed target check becomes unchecked in raw_call; ABI and callee traps keep their meaning. The conversions below are shared with unchecked call.

593checked_call_resultok, fnF, Res, Args, Xs, Ctx, Env0, S0, Result ←
Ctx = ctxFns, Globals, _, _, Mode, _
get_assocF, Fns, fnRet, Params, _, Bs
indirect_call_convRet, Params, Res, Args, Xs, Fixed, Conv
assoc_to_valuesBs, Blocks
variadic_funcfunc_, _, _, _, Blocks
Conv ≠ trap_
→
lengthParams, NP
lengthPrefix, NP
appendPrefix, Extra, Xs
appendFixed, Extra, Ys
else
Ys = Fixed
conv_call_resultConv, Fns, Globals, Mode, F, Ys, Res, Env0, S0, Result
603checked_call_resulttrapWhy, _, _, _, _, _, _, S, stoptrapWhy, S
604checked_call_resultuncheckedWhy, _, _, _, _, _, _, S, stopuncheckedWhy, S
conv_call_result+Conv, +Fns, +Globals, +Mode, +F, +Ys, +Res, +Env0, +S0, -Result

the result of an indirect call to F on Ys, with the call's conversion Conv.

609conv_call_resulttrapWhy, _, _, _, _, _, _, _, S0, stoptrapWhy, S0
610conv_call_resultsame, Fns, Globals, Mode, F, Ys, Res, Env0, S0, Result ←
call_outcomeFns, Globals, Mode, F, Ys, S0, Out, S
call_outcome_resultOut, Res, Env0, S, Result
613conv_call_resultconvCallerWords, CalleeWords, Fns, Globals, Mode, F, Ys, Res, Env0, S0, Result ←
call_outcomeFns, Globals, Mode, F, Ys, S0, Out0, S
converted_outcomeCallerWords, CalleeWords, Out0, Out
call_outcome_resultOut, Res, Env0, S, Result

indirect_call_conv(+CalleeRet, +Params, +Res, +Args, +Xs, -Ys, -Conv): a call through a function pointer. When the classes the call site passes and expects give the callee's signature (w and l are the same word), it is a direct call. Otherwise it goes through the callee's generic entry: each argument and result word is 8 bytes with its lower beside it, so a p word read as an integer is its address and an integer read as p has no capability. Too few arguments, or fewer result words than the call site reads, fail the generic entry's size checks. Conv is same, conv(CallerWords, CalleeWords) or trap(Why).

628indirect_call_convRet, Params, Res, Args, Xs, Ys, Conv ←
lengthParams, NParams
lengthArgs, NArgs
result_wordsRes, CallerWords
return_wordsRet, CalleeWords
lengthCallerWords, NCallerWords
lengthCalleeWords, NCalleeWords
findallK, memberparamK, _, Params, ParamClasses
findallK, memberargK, _, Args, ArgClasses
NArgs < NParams
→
Conv = trapargument_size
NCallerWords > NCalleeWords
→
Conv = trapreturn_size
else
lengthXs1, NParams
appendXs1, _, Xs
lengthArgClasses1, NParams
appendArgClasses1, _, ArgClasses
maplistgeneric_word, ArgClasses1, ParamClasses, Xs1, Ys
maplistword_class, CallerWords, WordClasses
maplistword_class, CalleeWords, WordClasses
→
Conv = same
else
Conv = convCallerWords, CalleeWords
result_words+Res, -Ks

and return_words(+Ret, -Ks): the classes of the words a call site reads and a function returns.

651result_wordsvoid, []
652result_wordsresult_, K, K
653result_wordstupleTos, Ks ←
findallK, memberresult_, K, Tos, Ks
return_words
655void[]
656tupleKsKs
657ww
658ll
659pp
660ss
661dd
word_class?K, ?Class

Class is p for p, and int for w and l, which are the same word.

word_class
665wint
666lint
667pp
668ss
669dd
generic_word+From, +To, +X, -Y

Y is the word X written at class From and read at class To through the generic entry.

673generic_wordw, To, X, Y ←
int_word_asTo, X, Y
674generic_wordl, To, X, Y ←
int_word_asTo, X, Y
675generic_wordp, To, X, Y ←
676generic_words, To, X, Y ←
int_word_asTo, X, Y
677generic_wordd, To, X, Y ←
int_word_asTo, X, Y
679int_word_asw, X, X
680int_word_asl, X, X
681int_word_asp, X, ptrX, none
682int_word_ass, X, Y ←
683int_word_asd, X, Y ←

A p word read as an integer is its address. An integer held at p (see class_arg/2) is its own address: the defaulty case keeps the if-then-else.

688pointer_word_asw, X, A ←
689pointer_word_asl, X, A ←
690pointer_word_asp, X, X
691pointer_word_ass, X, Y ←
692pointer_word_asd, X, Y ←
694pointer_addressX, A ←
X = ptrA0, _
→
A = A0
else
A = X
converted_outcome+CallerWords, +CalleeWords, +Out0, -Out

Out is the callee's outcome Out0 as a call site that reads CallerWords reads it. An outcome that does not return passes unchanged.

699converted_outcomeCallerWords, CalleeWords, Out0, Out ←
outcome_wordsOut0, Xs0
→
lengthCallerWords, N
lengthXs, N
appendXs, _, Xs0
lengthCalleeWords1, N
appendCalleeWords1, _, CalleeWords
maplistgeneric_word, CalleeWords1, CallerWords, Xs, Ys
else
Out = Out0
outcome_words?Out, ?Xs

the returning outcome Out returns the words Xs. Fails for an outcome that does not return.

711outcome_wordsret, []
712outcome_wordsretX, X
713outcome_wordsretsXs, Xs ←
Xs = _, _|_
715class_value_xCtx, Env, K, V, X ←
value_xK, V, Ctx, Env, X
716arg_xCtx, Env, argK, V, X ←
value_xK, V, Ctx, Env, X

res_result/5, store_res_result/4 and call_outcome_result/5 relate an operation's Res, or a callee's outcome, to the instruction's Result.

720res_resultvalX, D, Env0, S, contEnv, S ←
put_assocD, Env0, X, Env
721res_resultubWhy, _, _, S, stopubWhy, S
722res_resulttrapWhy, _, _, S, stoptrapWhy, S
723res_resultuncheckedWhy, _, _, S, stopuncheckedWhy, S
store_res_result
725okEnvScontEnv, S
726ubWhy_SstopubWhy, S
727trapWhy_SstoptrapWhy, S
728uncheckedWhy_SstopuncheckedWhy, S
730call_outcome_resultretX, Res, Env0, S, contEnv, S ←
ret_envRes, X, Env0, Env
732call_outcome_resultretsXs, Res, Env0, S, contEnv, S ←
rets_envRes, Xs, Env0, Env
734call_outcome_resultret, void, Env, S, contEnv, S
735call_outcome_resultunreachable, _, _, S, stopunreachable, S
736call_outcome_resultubWhy, _, _, S, stopubWhy, S
737call_outcome_resulttrapWhy, _, _, S, stoptrapWhy, S
738call_outcome_resultuncheckedWhy, _, _, S, stopuncheckedWhy, S
739call_outcome_resultout_of_fuel, _, _, S, stopout_of_fuel, S
740call_outcome_resultunknownWhy, _, _, S, stopunknownWhy, S
ret_env+Res, +X, +Env0, -Env

and rets_env(+Res, +Xs, +Env0, -Env): Env is Env0 with the returned word X, or words Xs, bound as the call site's Res reads them.

745ret_envresultT, K, X, Env0, Env ←
to_word_envresultT, K, X, Env0, Env
746ret_envvoid, _, Env, Env
748rets_envtupleTos, Xs, Env0, Env ←
foldlto_word_env, Tos, Xs, Env0, Env
749rets_envvoid, _, Env, Env
751to_word_envresulttmpD, K, X0, Env0, Env ←
put_assocD, Env0, X, Env
word_at_class+K, ?X0, ?X

X is the word X0 taken at class K, its low bits(K) bits; a p word is itself.

757word_at_classw, X0, X ←
modulusw, Mod
int_isX, X0 mod Mod

A limit's snapshot (docs/bounds.md) is no integer; only check_limit reads it (ir.pl's limit_reads/2).

760word_at_classl, X0, X ←
nonvarX0
X0 = limit_, _
→
X = X0
else
modulusl, Mod
int_isX, X0 mod Mod
764word_at_classp, X, X
765word_at_classa, X, X
766word_at_classf, X, X
767word_at_classs, X0, X ←
must_beinteger, X0
X is X0 mod 4294967296
768word_at_classd, X0, X ←
must_beinteger, X0
X is X0 mod 18446744073709551616
769word_at_classx, X0, X ←
must_beinteger, X0
X is X0 mod 1 << 80
value_x+K, +V, +Ctx, +Env, -X

X is the IR value V read at class K, which takes its low bits(K) bits. value_x_/5 indexes on the value. A symbol the module does not define is ptr(0, ext(Name)) in filc mode; in plain mode (which the validator rejects) the read fails.

775value_xK, V, Ctx, Env, X ←
value_x_V, K, Ctx, Env, X
777value_x_tmpN, K, _, Env, X ←
get_assocN, Env, X0
→
true
else
formatatomMsg, 'read of tmp(~w) before any assignment', N
thrownanofil_errorMsg
783value_x_constN, K, _, _, X ←
modulusK, Mod
A is N mod Mod
787value_x_globalN, _, ctx_, Globals, _, _, Mode, _, _, X ←
get_assocN, Globals, X0
→
X = X0
else
constant_at_class+K, +A, -X

X is the constant A at class K; at p it is a pointer with no capability.

constant_at_class
794wAA
795lAA
796pAptrA, none
797sAA
798dAA
799xAA
801undefined_symbolfilc, N, ptr0, extN

Flight's two components are read independently from the old environment. Projecting an address takes its low 64 bits; projecting a lower copies capability identity without inspecting it or its address.

806flight_arg_xCtx, Env, addressP, X ←
!
value_xp, P, Ctx, Env, ptrA, _
809flight_arg_xCtx, Env, V, X ←
value_xl, V, Ctx, Env, X
811flight_capnone, _, _, none
812flight_caplowerP, Ctx, Env, Cap ←
value_xp, P, Ctx, Env, ptr_, Cap

Operations

op_res+Op, +K, +Mode, +Args, +S0, -Res, -Snondet

Res is val(X), ub(Why) or trap(Why). Op is an IR term (ir.pl). Commit before integer dispatch: float bits must never get CLP(Z) arithmetic, even if a caller asks for a different result on backtracking.

821op_rescmpC, CK, _, _, A, B, S, valR, S ←
memberchkCK, s, d, x
!
ieee_compareCK, C, A, B, R
823op_resOp, K, _, Xs, S, valR, S ←
memberchkK, s, d, x
memberchkOp, add, sub, mul, div, neg, copy
!
float_arithmeticOp, K, Xs, R
826op_resOp, K, Mode, Xs, S0, Res, S ←
op_kindOp, Kind
kind_resKind, Op, K, Mode, Xs, S0, Res, S
kind_res+Kind, +Op, +K, +Mode, +Args, +S0, -Res, -S

as op_res/7, for Op of Kind. Loads and allocs use the memory; the rest is arithmetic.

832kind_ressetjmp, _, _, _, _, _, _, _ ←
thrownanofil_error'interpreter does not support setjmp/nonlocal returns'
834kind_resload, loadT, K, Mode, Args, S0, Res, S ←
load_op_resT, K, Mode, Args, S0, Res, S
836kind_resatomic_load, atomic_loadp, K, Mode, Args, S0, Res, S ←
load_op_resp, K, Mode, Args, S0, Res, S
838kind_rescheck, checkLo, Hi, Rights, Align, a, filc, ptrA, Cap, sMem, Fuel, Res, sMem, Fuel ←
raw_addressA, Lo, B
Size is Hi−Lo
access_resMem, B, Cap, Size, Rights, Align, Res0
checked_resRes0, ptrA, Cap, Res

A check_ahead (docs/schedule.md) is its check of [Lo, AHi), the access's own requirement, with that check's trap; when that passes and [Lo, Hi) does not, it traps early(Why): a trap the reference program reaches later, which only the oracle's earlier-trap rule accepts (main.pl's pizlonation_verdict/4).

849kind_rescheck_ahead, check_aheadLo, Hi, Rights, Align, AHi, a, filc, ptrA, Cap, sMem, Fuel, Res, sMem, Fuel ←
raw_addressA, Lo, B
Need is AHi−Lo
access_resMem, B, Cap, Need, Rights, Align, Res0
Res0 = trapWhy
→
Res = trapWhy
else
Size is Hi−Lo
access_resMem, B, Cap, Size, Rights, Align, Res1
Res1 = trapWhy
→
Res = trapearlyWhy
else
Res = valptrA, Cap

A renew runs the check's whole access test on its own pointer P (docs/m7.md, round 4), which has the capability of Q1's: P may lie at a distance from Q1's pointer (docs/schedule.md). Only a freed object or a write to a read-only one is the program's trap, as the reference check's; any other failure means the span the validator relied on was false: unchecked(span).

865kind_resrenew, renewLo, Hi, Rights, Align, a, filc, ptr_, Cap1, ptrA, Cap, sMem, Fuel, Res, sMem, Fuel ←
Cap1 ≡ Cap
!
raw_addressA, Lo, B
Size is Hi−Lo
access_resMem, B, Cap, Size, Rights, Align, Res0
renew_resRes0, ptrA, Cap, Mem, Res

P is not of the object Q1's check saw (pointer_distance/4 was wrong).

873kind_resrenew, renew_, _, _, _, a, filc, _, _, S, uncheckedalias, S

A renew_hull (docs/forward.md) is a renew whose two sources both have P's capability: the same access test on P, the same traps; any other failure is unchecked(span), another object's source unchecked(alias).

877kind_resrenew_hull, renew_hullLo, Hi, Rights, Align, a, filc, ptr_, CapL, ptr_, CapH, ptrA, Cap, sMem, Fuel, Res, sMem, Fuel ←
CapL ≡ Cap
CapH ≡ Cap
!
raw_addressA, Lo, B
Size is Hi−Lo
access_resMem, B, Cap, Size, Rights, Align, Res0
renew_resRes0, ptrA, Cap, Mem, Res
884kind_resrenew_hull, renew_hull_, _, _, _, a, filc, _, _, _, S, uncheckedalias, S

A half check (docs/half.md) is its check of P, which has Q1's capability. Natively it tests only Bound, upper or lower, and for lower the free flag, then the read-only flag for a write and the alignment, as the check does. Where that test and the check disagree, the bound the validator took as known was false: unchecked(half).

890kind_reshalf_check, half_checkBound, Lo, Hi, Rights, Align, a, filc, ptr_, Cap1, ptrA, Cap, sMem, Fuel, Res, sMem, Fuel ←
Cap1 ≡ Cap
!
raw_addressA, Lo, B
Size is Hi−Lo
access_resMem, B, Cap, Size, Rights, Align, Res0
half_passesBound, Mem, B, Size, Cap, Rights, Align, Passes
→
half_resRes0, Passes, ptrA, Cap, Res
else
Res = uncheckedhalf
900kind_reshalf_check, half_check_, _, _, _, _, a, filc, _, _, S, uncheckedalias, S

A span test (docs/m7.md, round 5) compares the object's current bounds with the span, addresses modulo 2^64 as native code forms them; it is 0 for any capability that is not an object's, and never traps.

904kind_resspan_test, span_testLo, Hi, w, filc, ptrA, Cap, sMem, Fuel, valG, sMem, Fuel ←
Mem = mem_, Rs, _, _
Cap = objBase
or
Cap = roBase
object_regionBase, Rs, regionBase, End, _, _
raw_addressA, Lo, L
raw_addressA, Hi, H
Base ≤ L
L < H
H ≤ End
→
G = 1
else
G = 0

renew_if is its check when G is 0. When G is 1 it runs the check's whole access test too: a freed object, a write to a read-only one and a misaligned address are the program's traps, which the native fast path tests itself; any other failure means the guard or the interval rules were wrong: unchecked(span).

919kind_resrenew_if, renew_ifLo, Hi, Rights, Align, a, filc, ptrA, Cap, G, sMem, Fuel, Res, sMem, Fuel ←
raw_addressA, Lo, B
Size is Hi−Lo
access_resMem, B, Cap, Size, Rights, Align, Res0
G =:= 0
→
checked_resRes0, ptrA, Cap, Res
Res0 = trapmisaligned
→
Res = trapmisaligned
else
renew_resRes0, ptrA, Cap, Mem, Res

A limit (docs/bounds.md) is an opaque snapshot, which only check_limit reads (ir.pl's limit_reads/2): the capability and the access size it was taken for. Natively it holds the object's upper bound then, which changes only when the object is freed, and check_limit tests the free flag too; here a free empties the bounds the check reads directly.

933kind_reslimit, limitSize, l, filc, ptr_, Cap, S, vallimitCap, Size, S

check_limit is its check when its limit was taken of P's capability for its size. A limit of another capability or size means the validator's lower chain or the pass was wrong: unchecked(limit).

937kind_rescheck_limit, check_limitLo, Hi, Rights, Align, a, filc, ptrA, Cap, U, sMem, Fuel, Res, sMem, Fuel ←
Size is Hi−Lo
U ≡ limitCap, Size
→
raw_addressA, Lo, B
access_resMem, B, Cap, Size, Rights, Align, Res0
checked_resRes0, ptrA, Cap, Res
else
Res = uncheckedlimit
946kind_resld, raw_loadT, Off, K, filc, ptrA, Cap, S0, Res, S ←
raw_addressA, Off, B
load_op_resT, K, filc, ptrB, Cap, S0, Res0, S
raw_resRes0, Res
950kind_resld_pointer, raw_load_pointerOff, Effects, p, filc, ptrA, Cap, sMem0, Fuel, Res, sMem, Fuel ←
raw_addressA, Off, B
access_resMem0, B, Cap, 8, read, 8, Check
pointer_load_effects_resCheck, Effects, B, Mem0, Res, Mem
955kind_rescas, casT, K, filc, E, N, ptrA, Cap, S0, Res, S ←
cas_resT, K, E, N, A, Cap, S0, Res, S
957kind_resraw_cas, raw_casT, Off, K, filc, ptrA, Cap, E, N, S0, Res, S ←
raw_addressA, Off, B
cas_resT, K, E, N, B, Cap, S0, Res0, S
raw_resRes0, Res
961kind_resalloc, allocAlign, _, Mode, Size, sMem0, Fuel, Res, sMem, Fuel ←
alloc_resMode, Mem0, Align, Size, Res, Mem
964kind_resva_arg, _, K, filc, P, S0, Res, S ←
pointer_load_resfilc, P, S0, Cursor, S1
va_cursor_resCursor, K, P, S1, Res, S
967kind_resadditive, Op, K, _, Xs, S, Res, S ←
additive_resK, Op, Xs, Res
968kind_resalu, Op, K, _, Xs, S, Res, S ←
aluOp, K, Xs, Res
969kind_resshift, Op, K, _, Xs, S, Res, S ←
aluOp, K, Xs, Res
970kind_resneg, Op, K, _, Xs, S, Res, S ←
aluOp, K, Xs, Res
971kind_rescopy, Op, K, _, Xs, S, Res, S ←
aluOp, K, Xs, Res
972kind_resptoi, Op, K, _, Xs, S, Res, S ←
aluOp, K, Xs, Res
973kind_resitop, Op, K, _, Xs, S, Res, S ←
aluOp, K, Xs, Res
974kind_rescmp, Op, K, _, Xs, S, Res, S ←
aluOp, K, Xs, Res
975kind_resext, Op, K, _, Xs, S, Res, S ←
aluOp, K, Xs, Res
976kind_resfloat_convert, exts, K, _, X, S, valR, S ←
ieee_converts, K, X, R
978kind_resfloat_convert, extd, x, _, X, S, valR, S ←
ieee_convertd, x, X, R
980kind_resfloat_convert, truncd, s, _, X, S, valR, S ←
ieee_convertd, s, X, R
982kind_resfloat_convert, truncx, K, _, X, S, valR, S ←
ieee_convertx, K, X, R
984kind_resfloat_to_int, Op, K, _, X, S, Res, S ←
float_integerOp, K, X, Res
986kind_resint_to_float, Op, K, _, X, S, valR, S ←
must_beinteger, X
988kind_rescast, _, _, _, X, S, valX, S ←
must_beinteger, X
990float_arithmeticcopy, _, X, X ←
must_beinteger, X
991float_arithmeticneg, K, X, R ←
ieee_negK, X, R
992float_arithmeticOp, K, X, Y, R ←
ieee_binaryK, Op, X, Y, R
994integer_sourceswtof, X, N ←
signedw, X, N
995integer_sourceuwtof, X, X
996integer_sourcesltof, X, N ←
signedl, X, N
997integer_sourceultof, X, X
999float_integerOp, K, X, Res ←
float_sourceOp, From, Sign
Sign ≡ signed
→
Min is −M//2
Max is M//2−1
else
Min = 0
Max is M−1
ieee_to_intFrom, X, N
N ≥ Min
N ≤ Max
→
R is N mod M
Res = valR
else
Res = ubfloat_conversion
float_source
1008stosissigned
1008stouisunsigned
1009dtosidsigned
1009dtouidunsigned
1010xtosixsigned
1010xtouixunsigned

A va_arg of class K takes Step bytes at an Align-aligned cursor, as filc_get_next_ptr_bytes_for_va_arg does: a long double's cell is 16 bytes at 16, of which it reads its 10.

1015va_cellx, 16, 16 ←
!
1016va_cellK, Size, 8 ←
1018va_cursor_restrapWhy, _, _, S, trapWhy, S
1019va_cursor_resvalptrA, Cap, K, P, sMem0, Fuel, Res, S ←
va_cellK, Step, Align
Aligned is A + Align−1//Align × Align
access_resMem0, Aligned, Cap, Step, read, 8, Check
va_checked_resCheck, K, P, Aligned, Size−Step, sMem0, Fuel, Res, S
1025va_checked_restrapWhy, _, _, _, _, S, trapWhy, S
1026va_checked_resok, K, P, A, Size−Step, sMem0, Fuel, Res, sMem, Fuel ←
Next is A + Step
store_resfilc, l, Mem0, P, Next, Store, Mem1
Store = trapWhy
→
Res = trapWhy
Mem = Mem1
K ≡ p
→
pointer_access_loadok, Mem1, A, Res, Mem
else
load_resfilc, Mem1, A, Size, Res, Mem
checked_res+Res0, +P, -Resdet

A check whose access_res/7 is Res0 yields P, proven, or traps.

1036checked_resok, P, valP
1037checked_restrapWhy, _, trapWhy
1039asm_in_xCtx, Env, _−V, X ←
value_xw, V, Ctx, Env, X
renew_res+Res0, +P, +Mem, -Resdet

A renew (docs/m7.md, round 4) of P whose access_res/7 is Res0 yields P, proven, or traps as the check it replaced would: a read-only trap stands, and a freed object traps out of bounds. Any other failure means the earlier check's bounds did not cover this access, which a valid renew rules out, so it is unchecked(span), a broken pass.

1048renew_resok, P, _, valP
1049renew_restrapreadonly, _, _, trapreadonly
1050renew_restrapWhy, ptr_, Cap, mem_, Rs, _, _, Res ←
Why ≢ readonly
Cap = objBase
or
Cap = roBase
object_regionBase, Rs, region_, _, _, freed
→
Res = trapout_of_bounds
else
Res = uncheckedspan
half_passes+Bound, +Mem, +B, +Size, +Cap, +Rights, +Align, -Passes

Passes is true when a half check's native test passes at address B (filc_amd64's half_bounds//5): upper, B + Size =< upper; lower, B taken signed at or above lower and the object not freed; then not read-only for a write, and aligned. Fails for a capability that is not an object's, whose header no half check may read.

1064half_passesBound, mem_, Rs, _, _, B, Size, Cap, Rights, Align, Passes ←
Cap = objBase
Ro = false
or
Cap = roBase
Ro = true
!
object_regionBase, Rs, regionBase, End, _, Life
half_boundBound, B, Size, Base, End, Life
¬
Rights ≡ write
Ro ≡ true
B mod Align =:= 0
→
Passes = true
else
Passes = false
1074half_boundupper, B, Size, _, End, _ ←
B + Size ≤ End
1075half_boundlower, B, _, Base, _, Life ←
Life ≢ freed
B ≥ 1 << 63
→
S is B−1 << 64
else
S = B
S ≥ Base
half_res
1080oktruePvalP
1081trapWhyfalse_trapWhy
1082okfalse_uncheckedhalf
1083trap_true_uncheckedhalf
raw_res+Res0, -Resdet

A raw access is the access its check covers, made again without trapping: what would have trapped is unchecked.

raw_res
1089valXvalX
1090okok
1091ubWhyubWhy
1092trapWhyuncheckedWhy
raw_address+A, +Off, -B

B is the address A plus the constant Off.

1095raw_addressA, Off, B ←
B is A + Off mod 18446744073709551616
1097alloc_pointerplain, Base, Base
1098alloc_pointerfilc, Base, ptrBase, objBase
load_op_res+T, +K, +Mode, +Args, +S0, -Res, -S

Res is the result of a load of type T at class K. A load reads Size bytes and extends them as T names; load(l) takes them as they are.

1103load_op_resp, _, Mode, Args, S0, Res, S ←
pointer_load_resMode, Args, S0, Res, S
1105load_op_resl, _, Mode, Args, S0, Res, S ←
int_load_resMode, 8, Args, S0, Res, S
1107load_op_ress, _, Mode, Args, S0, Res, S ←
int_load_resMode, 4, Args, S0, Res, S
1109load_op_resd, _, Mode, Args, S0, Res, S ←
int_load_resMode, 8, Args, S0, Res, S
1111load_op_resx, _, Mode, Args, S0, Res, S ←
int_load_resMode, 10, Args, S0, Res, S
1113load_op_ressw, K, Mode, Args, S0, Res, S ←
ext_load_ressw, 4, K, Mode, Args, S0, Res, S
1115load_op_resuw, K, Mode, Args, S0, Res, S ←
ext_load_resuw, 4, K, Mode, Args, S0, Res, S
1117load_op_ressh, K, Mode, Args, S0, Res, S ←
ext_load_ressh, 2, K, Mode, Args, S0, Res, S
1119load_op_resuh, K, Mode, Args, S0, Res, S ←
ext_load_resuh, 2, K, Mode, Args, S0, Res, S
1121load_op_ressb, K, Mode, Args, S0, Res, S ←
ext_load_ressb, 1, K, Mode, Args, S0, Res, S
1123load_op_resub, K, Mode, Args, S0, Res, S ←
ext_load_resub, 1, K, Mode, Args, S0, Res, S

load(p) exists in filc mode only. It reads the word and the capability the last store(p, ...) left there, none if there was none.

1128pointer_load_resfilc, ptrA, Cap, sMem0, Fuel, Res, sMem, Fuel ←
access_resMem0, A, Cap, 8, read, 8, Res0
pointer_access_loadRes0, Mem0, A, Res, Mem
1132pointer_access_loadok, Mem0, A, valptrU, C, Mem ←
load_resfilc, Mem0, A, 8, valU, Mem
Mem = mem_, _, _, Aux
get_assocA, Aux, C
→
true
else
C = none
1136pointer_access_loadtrapWhy, Mem, _, trapWhy, Mem

Execute the supplied pointer-access protocol. Its local state keeps the aux word separate from the resolved lower and primary address; only the completed load publishes a pointer. Aux contains abstract capability identities, not native boxes, so resolution is identity.

1142pointer_load_effects_resok, Effects, A, Mem0, valP, Mem ←
foldlpointer_load_effectA, Effects, loadednone, ptr0, none, Mem0, loaded_, P, Mem
1145pointer_load_effects_restrapWhy, _, _, Mem, uncheckedWhy, Mem
1147pointer_load_effectA, aux_read, loaded_, ptrU, _, Mem, loadedC, ptrU, C, Mem ←
Mem = mem_, _, _, Aux
get_assocA, Aux, C
→
true
else
C = none
1151pointer_load_effect_, lower_resolve, loadedC, ptrU, _, Mem, loadedC, ptrU, C, Mem

Fil-C's helper reads the address and the lower (or the box's pair) as one unit; with one thread that is aux_read then address_read.

1155pointer_load_effectA, atomic_read, L0, L ←
pointer_load_effectA, aux_read, L0, L1
pointer_load_effectA, address_read, L1, L
1158pointer_load_effectA, address_read, loadedC, ptr_, Lower, Mem0, loadedC, ptrU, Lower, Mem ←
load_resfilc, Mem0, A, 8, valU, Mem

The interpreter has no GC, aux allocation or asynchronous frees. Ensure/barrier/reload are therefore abstract identities, not a claim that native helpers cannot exit or fail. The observable writes still execute in list order: lower first, then primary bytes, without another bounds check after ensure-aux. Regions and Next are carried opaquely.

1167pointer_store_effects_resok, Effects, A, P, Mem0, ok, Mem ←
foldlpointer_store_effectA, P, Effects, Mem0, Mem
1169pointer_store_effects_restrapWhy, _, _, _, Mem, uncheckedWhy, Mem
1171pointer_store_effect_, _, aux_ensure, Mem, Mem
1172pointer_store_effect_, _, lower_barrier, Mem, Mem
1173pointer_store_effect_, _, rooted_reload, Mem, Mem
1174pointer_store_effectA, P, atomic_write, Mem0, Mem ←
pointer_store_effectA, P, lower_write, Mem0, Mem1
pointer_store_effectA, P, address_write, Mem1, Mem
1177pointer_store_effectA, ptr_, C, lower_write, memBytes, Rs, Next, Aux0, memBytes, Rs, Next, Aux ←
stored_auxcapC, A, Aux0, Aux
1180pointer_store_effectA, ptrU, _, address_write, memBytes0, Rs, Next, Aux, memBytes, Rs, Next, Aux ←
le_bytes8, U, Written
written_bytesWritten, A, Bytes0, Bytes
cas_res+T, +K, +E, +N, +A, +Cap, +S0, -Res, -S

the cas(T) of Expected E and New N at A (ir.pl): a write check of its width, at its width's alignment; then old, the T at A, and N written there when old's bytes are E's low bytes (for p, when the addresses are equal, whatever the capabilities). Res is old, as load(T) would give it.

1190cas_resT, K, E, N, A, Cap, sMem0, Fuel, Res, sMem, Fuel ←
access_size_aligncasT, Size, Align
access_resMem0, A, Cap, Size, write, Align, Check
cas_checkedCheck, T, K, E, N, A, Size, Mem0, Res, Mem
1195cas_checkedtrapWhy, _, _, _, _, _, _, Mem, trapWhy, Mem
1196cas_checkedok, p, _, ptrEU, _, N, A, _, Mem0, valptrU, C, Mem ←
pointer_access_loadok, Mem0, A, valptrU, C, Mem1
U =:= EU
→
pointer_store_effectA, N, lower_write, Mem1, Mem2
pointer_store_effectA, N, address_write, Mem2, Mem
else
Mem = Mem1
1203cas_checkedok, T, K, E, N, A, Size, Mem0, Res, Mem ←
T ≢ p
load_resfilc, Mem0, A, Size, valU, Mem1
M is 1 << 8 × Size
U =:= E mod M
→
le_bytesSize, N, Written
Mem1 = memBytes0, Rs, Next, Aux
written_bytesWritten, A, Bytes0, Bytes
Mem = memBytes, Rs, Next, Aux
else
Mem = Mem1
T ≡ l
→
Res = valU
else
extended_resvalU, T, K, Res
1216ext_load_resT, Size, K, Mode, Args, S0, Res, S ←
int_load_resMode, Size, Args, S0, Res0, S
extended_resRes0, T, K, Res
extended_res+Res0, +T, +K, -Res

Res is the loaded Res0 extended as T names and taken at class K.

1222extended_resvalU, T, K, valR ←
extensionT, U, S
int_isR, S mod M
1226extended_resubWhy, _, _, ubWhy
1227extended_restrapWhy, _, _, trapWhy
int_load_res+Mode, +Size, +Args, +S0, -Res, -S

Res is val(U), U the Size bytes at the address in Args, or why there is none.

1231int_load_resMode, Size, A0, sMem0, Fuel, Res, sMem, Fuel ←
address_accessMode, A0, Mem0, Size, A, Res0
int_access_loadRes0, Mode, Mem0, A, Size, Res, Mem
address_access+Mode, +A0, +Mem, +Size, -A, -Res

A is the address of the access value A0 and Res is ok when a Size-byte read there may go ahead. Fil-C checks the capability; plain mode checks the region when it reads.

1239address_accessplain, A, _, _, A, ok
1240address_accessfilc, ptrA, Cap, Mem, Size, A, Res ←
access_resMem, A, Cap, Size, read, 1, Res
1243int_access_loadok, Mode, Mem0, A, Size, Res, Mem ←
load_resMode, Mem0, A, Size, Res, Mem
1245int_access_loadtrapWhy, _, Mem, _, _, trapWhy, Mem
additive_res+K, +Op, +Xs, -Res

Res is the result of add or sub (Op) of class K on Xs. Pointer arithmetic keeps the capability.

1249additive_resp, Op, ptrA, C, X, valptrR, C ←
pointer_offsetOp, A, X, M, R
1252additive_resw, Op, Xs, Res ←
aluOp, w, Xs, Res
1253additive_resl, Op, Xs, Res ←
aluOp, l, Xs, Res
pointer_offset+Op, +A, +X, +M, -R

R is the address A plus (add) or minus (sub) X, modulo M.

1257pointer_offsetadd, A, X, M, R ←
int_isR, A + X mod M
1258pointer_offsetsub, A, X, M, R ←
int_isR, A−X mod M
alu+Op, +K, +Xs, -Resnondet

Res is the result of the arithmetic Op of class K on Xs; add and sub are integer here (additive_res/4). ptoi drops the capability; itop has none.

1265aluptoi, _, ptrA, _, valA
1266aluitop, _, X, valptrX, none
1268aluadd, K, X, Y, valR ←
int_isR, X + Y mod M
1269alusub, K, X, Y, valR ←
int_isR, X−Y mod M
1270alumul, K, X, Y, valR ←
int_isR, X × Y mod M
1271aluneg, K, X, valR ←
int_isR, −X mod M
1272alucopy, _, X, valX
1273aluand, _, X, Y, valR ←
int_isR, X /\ Y
1274aluor, _, X, Y, valR ←
int_isR, X \/ Y
1275aluxor, _, X, Y, valR ←
int_isR, X xor Y
1276aluudiv, _, X, Y, Res ←
unsigned_divisionX, Y, X//Y, Res
1277aluurem, _, X, Y, Res ←
unsigned_divisionX, Y, X mod Y, Res
1278aludiv, K, X, Y, Res ←
signed_divisionK, X, Y, //, Res
1279alurem, K, X, Y, Res ←
signed_divisionK, X, Y, rem, Res
1280alushl, K, X, Y, valR ←
int_isR, X × P mod M
1284alushr, K, X, Y, valR ←
int_isR, X//P
1287alusar, K, X, Y, valR ←
signedK, X, SX
int_isR, SX div P mod M
1292aluextT, K, X, valR ←
extensionT, X, S
int_isR, S mod M
1296alucmpCond, CK, _, X, Y, valB ←
cond_bitCond, CK, X, Y, B
unsigned_division+X, +Y, +Expr, -Res

Res is val of Expr, or val(0) when the divisor Y is zero, as Fil-C defines it since deluge (KillUB divides 0 by 1; docs/ub.md). An unknown Y gives both cases as solutions; a known one takes the fast path.

1302unsigned_division_, Y, Expr, Res ←
integerY
→
Y =:= 0
→
Res = val0
else
int_isR, Expr
Res = valR
else
Y #= 0
Res = val0
or
Y #≠ 0
int_isR, Expr
Res = valR

Signed division is total too, as Fil-C defines it since deluge: a zero divisor gives 0 (quotient and remainder), and MIN / -1 wraps to MIN with remainder 0, which the general rule gives, since -MIN wraps to MIN at the class width. Known operands take the fast path; otherwise the two cases are disjoint constraints.

1316signed_divisionK, X, Y, Fn, Res ←
signedK, X, SX
signedK, Y, SY
integerSX
integerSY
→
SY =:= 0
→
Res = val0
else
divisionFn, SX, SY, Q
R is Q mod M
Res = valR
else
SY #= 0
Res = val0
or
SY #≠ 0
divisionFn, SX, SY, Q
R #= Q mod M
Res = valR
1334division//, X, Y, Q ←
int_isQ, X//Y
1335divisionrem, X, Y, Q ←
int_isQ, X rem Y

The shift count is taken modulo the class width.

1338shift_factorK, Y, P ←
class_bitsK, Bits
int_isC, Y mod Bits
int_isP, 2^C
cond_bit+Cond, +K, ?X, ?Y, ?B

B is 1 when X Cond Y holds at class K, else 0.

1345cond_biteq, _, X, Y, B ←
reifyX =:= Y, B
1346cond_bitne, _, X, Y, B ←
reifyX ≠ Y, B
1347cond_bitult, _, X, Y, B ←
reifyX < Y, B
1348cond_bitule, _, X, Y, B ←
reifyX ≤ Y, B
1349cond_bitugt, _, X, Y, B ←
reifyX > Y, B
1350cond_bituge, _, X, Y, B ←
reifyX ≥ Y, B
1351cond_bitslt, K, X, Y, B ←
signed_pairK, X, Y, SX, SY
reifySX < SY, B
1354cond_bitsle, K, X, Y, B ←
signed_pairK, X, Y, SX, SY
reifySX ≤ SY, B
1357cond_bitsgt, K, X, Y, B ←
signed_pairK, X, Y, SX, SY
reifySX > SY, B
1360cond_bitsge, K, X, Y, B ←
signed_pairK, X, Y, SX, SY
reifySX ≥ SY, B
1364signed_pairK, X, Y, SX, SY ←
signedK, X, SX
signedK, Y, SY
signed+K, ?U, ?S

S is the two's-complement reading of U at class K.

1369signedK, U, S ←
sign_extension+M, +H, ?U, ?S

S is the two's-complement reading of the low bits of U, for a field of modulus M with sign threshold H. The first clause is the fast path for a known U; the cut commits to it, and the second clause states the same relation with constraints.

1378sign_extensionM, H, U, S ←
integerU
!
L is U mod M
L ≥ H
→
S is L−M
else
S = L
1383sign_extensionM, H, U, S ←
L #= U mod M
Neg in 0 .. 1
Neg #<==> L #≥ H
S #= L−Neg × M
extension+T, ?X, ?S

S is the low bits of X that T names, sign- or zero-extended.

1391extensionsb, X, S ←
sign_extension256, 128, X, S
1392extensionub, X, S ←
int_isS, X mod 256
1393extensionsh, X, S ←
sign_extension65536, 32768, X, S
1394extensionuh, X, S ←
int_isS, X mod 65536
1395extensionsw, X, S ←
sign_extension4294967296, 2147483648, X, S
1396extensionuw, X, S ←
int_isS, X mod 4294967296

Memory: mem(Bytes, Regions, Next, Aux)

Each region(Base, End, Kind, Life) is shared by every capability with that Base. Only free changes a Fil-C object's bounds, monotonically to empty. Freed regions remain so zero-sized live objects and double frees are distinguishable. No reclamation or address reuse is modeled.

Aux is used in filc mode only: it maps an 8-aligned address to the capability a store(p, ...) left there. An integer store leaves it alone, as Fil-C's aux array does.

free_res+P, +Mem0, -Res, -Mem

the pinned runtime's free_with_checks, object_for_deallocate and free_impl, for objects this interpreter owns. ext(_) has a placeholder address, not a known zero; it stays unsupported. Public run/5 also accepts integer p arguments: these have no capability.

1413free_resA, Mem0, Res, Mem ←
integerA
!
cap_free_resnone, A, Mem0, Res, Mem
1416free_resptrA, Cap, Mem0, Res, Mem ←
must_beinteger, A
must_becallable, Cap
cap_free_resCap, A, Mem0, Res, Mem
1420cap_free_resextN, _, _, _, _ ←
formatatomMsg, 'cannot interpret free of external ~w', N
thrownanofil_errorMsg
1423cap_free_resnone, A, Mem, Res, Mem ←
A =:= 0
→
Res = ok
else
Res = trapinvalid_free
1425cap_free_resfn_, A, Mem, Res, Mem ←
A =:= 0
→
Res = ok
else
Res = trapinvalid_free
1427cap_free_resobjBase, A, Mem0, Res, Mem ←
object_free_resBase, A, Mem0, Res, Mem
1429cap_free_resroBase, A, Mem0, Res, Mem ←
object_free_resBase, A, Mem0, Res, Mem

Null is a no-op even with an object's lower. Otherwise kind/address validation precedes the FREE test, as object_for_deallocate precedes free_impl. READONLY alone does not prevent deallocation of a heap object.

1435object_free_resBase, A, memBytes, Rs0, Next, Aux, Res, memBytes, Rs, Next, Aux ←
A =:= 0
→
Res = ok
Rs = Rs0
else
object_regionBase, Rs0, regionBase, End, Kind, Life
Kind ≢ heap
→
Res = trapinvalid_free
Rs = Rs0
A ≠ Base
→
Res = trapinvalid_free
Rs = Rs0
Life ≡ freed
→
Res = trapdouble_free
Rs = Rs0
else
selectregionBase, End, Kind, Life, Rs0, Rest
Rs = regionBase, Base, heap, freed|Rest
Res = ok
object_region+Base, +Rs, -Region

capabilities refer to registered identities. An injected unknown base is outside the interpreter domain, not authority for the live object that happens to contain its address.

1450object_regionBase, Rs, Region ←
must_beinteger, Base
memberchkregionBase, End, Kind, Life, Rs
→
Region = regionBase, End, Kind, Life
else
thrownanofil_error'capability names an unregistered object'
load_res+Mode, +Mem0, +A, +Size, -Res, -Memdet

Res is val(U), U the little-endian value of the Size bytes at A, or ub(...). In plain mode an access outside every live region is undefined. In filc mode the access was checked against its capability already. Mem is Mem0 with any unwritten byte read now made a fresh variable (plain mode).

1464load_resMode, memBytes0, Rs, Next, Aux, A, Size, Res, memBytes, Rs, Next, Aux ←
must_beinteger, A
Mode ≡ filc
or
in_live_regionRs, A, Size
→
lengthRead, Size
bytes_atRead, Mode, A, Bytes0, Bytes
le_valueRead, U
Res = valU
else
Bytes = Bytes0
Res = ubout_of_boundsA, Size
store_res+Mode, +Width, +Mem0, +Addr, +X, -Res, -Memdet

Mem is Mem0 with X written at Addr as Width, when Res is ok.

1479store_resplain, Width, memBytes0, Rs, Next, Aux, A, X, Res, memBytes, Rs, Next, Aux ←
width_bytesWidth, Size
must_beinteger, A
in_live_regionRs, A, Size
→
le_bytesSize, X, Written
written_bytesWritten, A, Bytes0, Bytes
Res = ok
else
Bytes = Bytes0
Res = ubout_of_boundsA, Size
1490store_resfilc, Width, Mem0, ptrA, Cap, X, Res, Mem ←
width_bytesWidth, Size
width_wordWidth, X, V, Align, Stored
access_resMem0, A, Cap, Size, write, Align, Res
access_storeRes, Mem0, A, Size, V, Stored, Mem
width_word+Width, ?X, ?V, -Align, -Stored

a Fil-C store of Width writes the integer V of the value X, needs alignment Align, and Stored is cap(C) for the capability C of a store(p, ...), or none.

width_word
1499bVV1none
1500hVV1none
1501wVV1none
1502lVV1none
1503sVV1none
1504dVV1none
1505xVV8none
1506pptrV, CV8capC
access_store+Res, +Mem0, +A, +Size, +V, +Stored, -Memdet

Mem is Mem0 after a checked store with result Res.

1511access_storeok, memBytes0, Rs, Next, Aux0, A, Size, V, Stored, memBytes, Rs, Next, Aux ←
le_bytesSize, V, Written
written_bytesWritten, A, Bytes0, Bytes
stored_auxStored, A, Aux0, Aux
1516access_storetrap_, Mem, _, _, _, _, Mem
1518stored_auxnone, _, Aux, Aux
1519stored_auxcapC, A, Aux0, Aux ←
put_assocA, Aux0, C, Aux
access_res+Mem, +A, +Cap, +Size, +Kind, +Align, -Resdet

Fil-C's access check, Res ok or trap(Why). Cap is none, obj(Base) for a writable object, ro(Base) for read-only data, fn(F) for a function object (zero size, so every access fails the bounds check) or ext(Name) for a symbol defined outside the module, which the interpreter cannot follow. Cap must be known: an unknown capability (an unknown p argument) raises an instantiation error, as an unknown address does. An unregistered Base is outside the interpreter's domain. The trap reasons and their order are those of the backend's access check, so a check and the raw access it covers agree.

1532access_resmem_, Rs, _, _, A, Cap, Size, Kind, Align, Res ←
must_beinteger, A
must_becallable, Cap
cap_access_resCap, Rs, A, Size, Kind, Align, Res
1537cap_access_resnone, _, _, _, _, _, trapnull_capability
1538cap_access_resfn_, _, _, _, _, _, trapout_of_bounds
1539cap_access_resextN, _, _, _, _, _, _ ←
formatatomMsg, 'cannot interpret an access to external ~w', N
thrownanofil_errorMsg
1542cap_access_resobjBase, Rs, A, Size, _, Align, Res ←
object_regionBase, Rs, regionBase, End, _, _
A ≥ Base
A + Size ≤ End
→
aligned_resA, Align, Res
else
Res = trapout_of_bounds
1548cap_access_resroBase, Rs, A, Size, Kind, Align, Res ←
object_regionBase, Rs, regionBase, End, _, _
A ≥ Base
A + Size ≤ End
→
readonly_access_resKind, A, Align, Res
else
Res = trapout_of_bounds
1555readonly_access_resread, A, Align, Res ←
aligned_resA, Align, Res
1556readonly_access_reswrite, _, _, trapreadonly
1558aligned_resA, Align, Res ←
A mod Align =:= 0
→
Res = ok
else
Res = trapmisaligned
in_live_region+Rs, +A, +Size

the access [A, A+Size) lies inside one live region. A and the regions are integers here, so the if-then-else in the callers is sound. The cut commits to the first such region.

1564in_live_regionRs, A, Size ←
memberregionBase, End, _, live, Rs
A ≥ Base
A + Size ≤ End
!
bytes_at?Xs, +Mode, +A, +Bytes0, -Bytes

Xs are the bytes at A, A+1, ... in Bytes0; Bytes is Bytes0 with each unwritten byte among them bound. An unwritten byte is unknown in plain mode and zero in filc mode, since Fil-C allocations are zeroed.

1574bytes_at[], _, _, Bytes, Bytes
1575bytes_atX|Xs, Mode, A, Bytes0, Bytes ←
get_assocA, Bytes0, X
→
Bytes1 = Bytes0
else
unwritten_byteMode, A, X, Bytes0, Bytes1
A1 is A + 1
bytes_atXs, Mode, A1, Bytes1, Bytes
1582unwritten_bytefilc, _, 0, Bytes, Bytes
1583unwritten_byteplain, A, X, Bytes0, Bytes ←
X in 0 .. 255
put_assocA, Bytes0, X, Bytes
written_bytes+Xs, +A, +Bytes0, -Bytes

Bytes is Bytes0 with the bytes Xs at A, A+1, ...

1589written_bytes[], _, Bytes, Bytes
1590written_bytesX|Xs, A, Bytes0, Bytes ←
put_assocA, Bytes0, X, Bytes1
A1 is A + 1
written_bytesXs, A1, Bytes1, Bytes
le_value+Bytes, ?U

and le_bytes(+N, ?X, -Bytes): U is the little-endian value of Bytes; Bytes are the N low bytes of X.

1597le_value[], 0
1598le_valueX|Xs, U ←
le_valueXs, U1
int_isU, X + 256 × U1
1600le_bytesN, X, Bs ←
lengthBs, N
low_bytes?Bs, ?X

Bs are the length(Bs) low bytes of X.

1605low_bytes[], _
1606low_bytesB|Bs, X ←
int_isB, X mod 256
int_isX1, X//256
low_bytesBs, X1
alloc_res+Mode, +Mem0, +Align, +Size, -Res, -Mem

the heap allocation allocAlign of Size bytes. Fil-C refuses an object of more than max_allocation_size/1 bytes before it allocates anything (filc_prepare_allocate_object/1: "attempt to allocate object that is too big"), and one its address space cannot hold ("out of memory"); both are safety errors.

1617alloc_resMode, Mem0, Align, Size, Res, Mem ←
must_beinteger, Size
Mode ≡ filc
Size > Max
→
Res = traptoo_big
Mem = Mem0
alloc_baseMode, Mem0, Align, Size, Base, Mem1
→
alloc_pointerMode, Base, P
Res = valP
Mem = Mem1
else
Res = trapout_of_memory
Mem = Mem0
max_allocation_size-Max

FILC_MAX_ALLOCATION_SIZE, PAS_MAX_ADDRESS, 2^48 - 1.

1628max_allocation_size281474976710655
alloc_base+Mode, +Mem0, +Align, +Size, -Base, -Memsemidet

Base is a new region of Size bytes in Mem. Allocations are aligned (Fil-C's to at least 16, as filc_allocate_with_alignment aligns them), with Fil-C bounds rounded to 16 as prepare_allocate_object does (zero stays zero). A 16-byte gap separates distinct, never-reused identities. Fails when Next would pass the end of the address space (address_space/2): the runtime is out of memory.

1637alloc_baseMode, memBytes, Rs, Next0, Aux, Align0, Size, Base, memBytes, regionBase, End, Kind, live|Rs, Next, Aux ←
must_beinteger, Size
alloc_alignMode, Align0, Align
alloc_extentMode, Size, Extent, Kind
Base is Next0 + Align−1//Align × Align
End is Base + Extent
allocation_strideMode, Size, Extent, Stride
Next is Base + Stride + 16
address_spaceMode, Next
address_space+Mode, +Next

Next is within the address space. Fil-C's objects lie below 2^48, libpas's address space (PAS_ADDRESS_BITS), so an address never wraps inside one, and two addresses of one object are less than 2^63 apart (the Rocq model's objects_fit). Plain mode sets no limit.

1653address_spaceplain, _
1654address_spacefilc, Next ←
Next ≤ 281474976710656
1655address_spacelocal, Next ←
Next ≤ 281474976710656
1657alloc_extentplain, Size, Size, stack
1658alloc_extentfilc, Size, Extent, heap ←
Extent is Size + 15//16 × 16
1659alloc_extentlocal, Size, Size, stack
1661allocation_strideplain, _, Extent, Extent
1662allocation_stridefilc, _, Extent, Extent
1663allocation_stridelocal, Size, _, Stride ←
Stride is Size + 15//16 × 16
1665alloc_alignplain, Align, Align
1666alloc_alignfilc, Align0, Align ←
Align is maxAlign0, 16
1667alloc_alignlocal, _, 16
1669stack_base4294967296
1670data_base65536

initial_memory(+Mode, +M, -Globals, -Mem): Mem holds every data definition, each in its own region, with its bytes. Globals maps a name to its address, or in filc mode to a pointer value: data objects are aligned to 16 or their stricter Align with their size rounded up to 8, as Fil-C's are, and each function gets an address and a fn capability.

1678initial_memoryplain, moduleItems, Globals, memBytes, Rs, Stack, Aux ←
empty_assocAux
data_baseDataBase
empty_assocGlobals0
foldlplain_layout, Items, DataBase−Globals0−[], _−Globals−Rs
empty_assocBytes0
foldlplain_contentsGlobals, Items, Bytes0, Bytes
1686initial_memoryfilc, moduleItems, Globals, memBytes, Rs, Stack, Aux ←
data_baseDataBase
empty_assocGlobals0
foldlfilc_layout, Items, DataBase−Globals0−[], _−Globals1−Rs
foldlalias_global, Items, Globals1, Globals
empty_assocBytes0
empty_assocAux0
foldlfilc_contentsGlobals, Items, Bytes0−Aux0, Bytes−Aux
plain_layout+Item, +A0−Globals0−Rs0, −A−Globals−Rs

Globals and Rs add to Globals0 and Rs0 the address and region of Item placed at the next address from A0 that its alignment allows; a function takes no space.

1700plain_layoutdataName, _, Align, DataItems, A0−Globals0−Rs0, A−Globals−regionBase, End, global, live|Rs0 ←
Base is A0 + Align−1//Align × Align
foldlitem_size, DataItems, 0, Size
End is Base + Size
put_assocName, Globals0, Base, Globals
A is End + 16
1707plain_layoutfunc_, _, _, _, _, State, State
','plain_contents+Globals, +Item, +Bytes0, −Bytes, filc_contents/4

Bytes (and in filc mode Aux) add the bytes (and capabilities) of Item.

1711plain_contentsGlobals, dataName, _, _, Items, Bytes0, Bytes ←
get_assocName, Globals, A
maplistitem_bytesGlobals, Items, ItemBytes
appendItemBytes, DataBytes
written_bytesDataBytes, A, Bytes0, Bytes
1716plain_contents_, func_, _, _, _, _, Bytes, Bytes
filc_layout+Item, +A0−Globals0−Rs0, −A−Globals−Rs

place Item at the next address from A0 aligned to 16 or, if stricter, its Align. A weak declaration (extern(weak)) takes no space: its symbol stays undefined, a null pointer (undefined_symbol/3).

1722filc_layoutdata_, externweak, _, [], State, State ←
!
1723filc_layoutdataName, Linkage, Align0, Items, A0−Globals0−Rs0, A−Globals−regionBase, End, global, live|Rs0 ←
Align is maxAlign0, 16
Base is A0 + Align−1//Align × Align
foldlitem_size, Items, 0, Size
End is Base + Size + 7//8 × 8
data_capLinkage, Base, Cap
put_assocName, Globals0, ptrBase, Cap, Globals
A is End + 16
1732filc_layoutalias_, _, _, State, State
1733filc_layoutfuncName, _, _, _, _, A0−Globals0−Rs, A−Globals−Rs ←
Base is A0 + 15//16 × 16
put_assocName, Globals0, ptrBase, fnName, Globals
A is Base + 16
data_cap+Linkage, +Base, -Cap

the capability of data at Base.

data_cap
1739exportBaseobjBase
1740localBaseobjBase
1741weakBaseobjBase

no other definition here

1742ro_BaseroBase
1743tls_BaseobjBase

one thread: one instance

1744init_, _BaseroBase

a table entry, never run here

1746filc_contents_, data_, externweak, _, [], State, State ←
!
1747filc_contentsGlobals, dataName, _, _, Items, Bytes0−Aux0, Bytes−Aux ←
get_assocName, Globals, ptrBase, _
foldlfilc_item_contentsGlobals, Items, Base−Bytes0−Aux0, _−Bytes−Aux
1750filc_contents_, func_, _, _, _, _, State, State
1751filc_contents_, alias_, _, _, State, State
alias_global+Item, +Globals0, -Globals

an alias's address is its target's, the same pointer.

1755alias_globalaliasName, _, Target, Globals0, Globals ←
!
get_assocTarget, Globals0, P
put_assocName, Globals0, P, Globals
1757alias_global_, Globals, Globals
filc_item_contents+Globals, +Item, +At0−Bytes0−Aux0, −At−Bytes−Aux

Item written at At0; At is the address after it. Data items are IR terms (ir.pl); the cut commits to the pointer item before the clause for every other item.

1763filc_item_contentsGlobals, itemp, symS, Off, At−Bytes0−Aux0, At1−Bytes−Aux ←
!
At mod 8 =:= 0
→
true
else
thrownanofil_error'pointer data item is not 8-aligned'
get_assocS, Globals, ptrSymAddr, Cap
→
true
else
SymAddr = 0
Cap = extS
U is SymAddr + Off mod 18446744073709551616
le_bytes8, U, Written
written_bytesWritten, At, Bytes0, Bytes
put_assocAt, Aux0, Cap, Aux
At1 is At + 8
1776filc_item_contentsGlobals, Item, At−Bytes0−Aux, At1−Bytes−Aux ←
item_bytesGlobals, Item, Written
written_bytesWritten, At, Bytes0, Bytes
lengthWritten, N
At1 is At + N
item_size+Item, +Size0, -Size

Size is Size0 plus Item's size. The cut commits to a string before the clause for any other item of width b (a constant).

1785item_sizezeroN, Size0, Size ←
Size is Size0 + N
1786item_sizeitemb, strCs, Size0, Size ←
!
lengthCs, N
Size is Size0 + N
1789item_sizeitemW, _, Size0, Size ←
Size is Size0 + N
item_bytes+Globals, +Item, -Bs

Bs are the bytes of the data Item, one per character of a string. The cut commits to a string, as in item_size/3.

1796item_bytes_, zeroN, Bs ←
lengthBs, N
maplist=0, Bs
1797item_bytes_, itemb, strCs, Bs ←
!
maplistchar_code, Cs, Bs
1798item_bytes_, itemW, constN, Bs ←
Bits is Size × 8
U is N mod 1 << Bits
le_bytesSize, U, Bs
1803item_bytesGlobals, iteml, symS, Off, Bs ←
get_assocS, Globals, A
U is A + Off
le_bytes8, U, Bs

Printing

outcome_text+Outcome, -Textdet

Text is the atom that prints Outcome: its integer return value, or just the kind of a pointer, unknown or tuple result. A returned value is an integer, an unknown (a variable, possibly constrained) or ptr(Addr, Cap): a defaulty term, so ret_text/2 tests it.

1817outcome_textretV, T ←
1818outcome_textrets_, 'ret tuple'
1819outcome_textret, ret
1820outcome_textunreachable, unreachable
1821outcome_textubWhy, T ←
formatatomT, 'ub ~w', Why
1822outcome_texttrapWhy, T ←
formatatomT, 'trap ~w', Why
1823outcome_textuncheckedWhy, T ←
formatatomT, 'unchecked ~w', Why
1824outcome_textout_of_fuel, out_of_fuel
1825outcome_textunknownasmM, Xs, T ←
formatatomT, 'unknown asm ~w ~w', M, Xs
1827ret_textV, T ←
integerV
→
formatatomT, 'ret ~w', V
varV
→
T = 'ret unknown'
V = ptr_, _
→
T = 'ret pointer'
else
T = 'ret unknown'