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
dataX1, ...a data function (zreturn) returned these words, each an integer or ptrAddr, Cap
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.

133runM, 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.

143module_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.

177run_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.

186arg_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(_, _).

192class_argw, X ←
193class_argl, X ←
194class_args, X ←
must_beinteger, X
195class_argd, X ←
must_beinteger, X
196class_argp, X ←
X = ptr_, _
→
true
201int_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.

213call_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.

238invalidate_locals[], S, S
239invalidate_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.

248returned_stateplain, Regions, smemBytes, _, Next, Aux, Fuel, smemBytes, Regions, Next, Aux, Fuel
250returned_statefilc, _, S, S
252param_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).

257entry_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.

266args_snapshot[], Mem, ptr0, none, Mem
267args_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
272snapshot_values[], _, _, Mem, Mem
273snapshot_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.

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

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

288reifyTest, B ←
groundTest
→
callTest
→
B = 1
else
B = 0
else
B in 0 .. 1
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
299reify_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.

308block_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.

324phis_env[], _, _, _, Env, Env
325phis_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.

336result_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
341result_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.

350jump_outcomegotoT, Ctx, From, Env, S0, Out, S ←
edge_outcomeT, Ctx, From, Env, S0, Out, S
352jump_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
359jump_outcomeretV, Ctx, _, Env, S, Out, S ←
Ctx = ctx_, _, _, data, _, _
!
value_xp, V, Ctx, Env, P
returned_dataP, S, Out
363jump_outcomeretV, Ctx, _, Env, S, retX, S ←
Ctx = ctx_, _, _, K, _, _
value_xK, V, Ctx, Env, X
366jump_outcomeretsVs, Ctx, _, Env, S, retsXs, S ←
Ctx = ctx_, _, _, tupleKs, _, _
maplistclass_value_xCtx, Env, Ks, Vs, Xs
369jump_outcomeret, _, _, _, S, ret, S
370jump_outcomeunreachable, _, _, _, S, unreachable, S
returned_data+P, +S, -Out

Out is what ret(P) of a data function returns, as filc_prepare_to_return_with_data does: no words for a null P, else the words from P to its object's end, each loaded as a pointer (an integer word has no capability). It traps where libpizlo's checks fail: no object, a function, a freed object, or P not 8-aligned.

377returned_dataP, S, Out ←
P = ptrA, Cap
→
true
else
A = P
Cap = none
must_beinteger, A
A =:= 0
→
Out = data[]
Cap = objBase
or
Cap = roBase
→
data_wordsBase, A, Cap, S, Out
Cap = extN
→
formatatomMsg, 'cannot interpret an access to external ~w', N
thrownanofil_errorMsg
Cap ≡ none
→
Out = trapnull_capability
else
Out = trapout_of_bounds
389data_wordsBase, A, Cap, S, Out ←
S = smem_, Rs, _, _, _
object_regionBase, Rs, regionBase, End, _, Life
Life ≢ live
→
Out = trapout_of_bounds
A mod 8 ≠ 0
→
Out = trapmisaligned
A < Base
→
Out = trapout_of_bounds
else
N is max0, End−A//8
lengthXs, N
foldldata_wordA, Cap, S, Xs, 0, _
Out = dataXs
401data_wordA, Cap, S, X, I, I1 ←
Ai is A + 8 × I
pointer_load_resfilc, ptrAi, Cap, S, valX, _
I1 is I + 1
406edge_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.

419insts_result[], _, Env, S, contEnv, S
420insts_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.

436doomed_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
443doomed_result_, _, Env0, stopuncheckedW, S, stoptrapearlyWhy, S ←
¬memberchkW, alias, limit
get_assochiddendoomed, Env0, Why
!
446doomed_result_, _, _, Result, Result
rest_result+Result0, +Insts, +Ctx, -Result

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

450rest_resultcontEnv, S, Insts, Ctx, Result ←
insts_resultInsts, Ctx, Env, S, Result
452rest_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.

458inst_resultloc_, _, _, Env, S, contEnv, S ←
!
459inst_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
467inst_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
471inst_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
476inst_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
482inst_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
487inst_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
491inst_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
496inst_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
502inst_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.

512inst_resultatomic_storep, V, A, Ctx, Env, S, Result ←
!
inst_resultstorep, V, A, Ctx, Env, S, Result
514inst_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.

518inst_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
526inst_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.

529inst_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
535inst_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
543inst_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
551inst_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
562inst_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
567inst_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
574local_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.

583external_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)'
589external_call_outcome_, _, F, _, _, _, _, _, _ ←
formatatomMsg, 'cannot interpret a call to external ~w', F
thrownanofil_errorMsg
593free_outcomeok, ret
594free_outcometrapWhy, trapWhy
width_value_class+Width, -K

a store of Width reads its value at K.

width_value_class
597bw
598hw
599ww
600ll
601pp
602ss
603dd
604xx
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.

612function_check_resptrA, Cap, ctx_, Globals, _, _, _, _, Res ←
must_becallable, Cap
cap_function_resCap, A, Globals, Res
616cap_function_resfnF, A, Globals, Res ←
get_assocF, Globals, ptrFnAddr, _
A ≡ FnAddr
→
Res = ok
else
Res = trapnot_a_function
622cap_function_resextF, _, _, _ ←
formatatomMsg, 'cannot interpret a call to external ~w', F
thrownanofil_errorMsg
625cap_function_resnone, _, _, trapnot_a_function
626cap_function_resobj_, _, _, trapnot_a_function
627cap_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.

632checked_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
642checked_call_resulttrapWhy, _, _, _, _, _, _, S, stoptrapWhy, S
643checked_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.

648conv_call_resulttrapWhy, _, _, _, _, _, _, _, S0, stoptrapWhy, S0
649conv_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
652conv_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).

667indirect_call_convRet, Params, Res, Args, Xs, Ys, Conv ←
lengthParams, NParams
lengthArgs, NArgs
result_wordsRes, CallerWords
A data function's words are counted when it returns.
Ret ≡ data
→
CalleeWords = CallerWords
else
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.

691result_wordsvoid, []
692result_wordsresult_, K, K
693result_wordstupleTos, Ks ←
findallK, memberresult_, K, Tos, Ks
return_words
695void[]
696tupleKsKs
697ww
698ll
699pp
700ss
701dd
word_class?K, ?Class

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

word_class
705wint
706lint
707pp
708ss
709dd
generic_word+From, +To, +X, -Y

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

713generic_wordw, To, X, Y ←
int_word_asTo, X, Y
714generic_wordl, To, X, Y ←
int_word_asTo, X, Y
715generic_wordp, To, X, Y ←
716generic_words, To, X, Y ←
int_word_asTo, X, Y
717generic_wordd, To, X, Y ←
int_word_asTo, X, Y
719int_word_asw, X, X
720int_word_asl, X, X
721int_word_asp, X, ptrX, none
722int_word_ass, X, Y ←
723int_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.

728pointer_word_asw, X, A ←
729pointer_word_asl, X, A ←
730pointer_word_asp, X, X
731pointer_word_ass, X, Y ←
732pointer_word_asd, X, Y ←
734pointer_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.

739converted_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.

751outcome_wordsret, []
752outcome_wordsretX, X
753outcome_wordsretsXs, Xs ←
Xs = _, _|_
755class_value_xCtx, Env, K, V, X ←
value_xK, V, Ctx, Env, X
756arg_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.

760res_resultvalX, D, Env0, S, contEnv, S ←
put_assocD, Env0, X, Env
761res_resultubWhy, _, _, S, stopubWhy, S
762res_resulttrapWhy, _, _, S, stoptrapWhy, S
763res_resultuncheckedWhy, _, _, S, stopuncheckedWhy, S
store_res_result
765okEnvScontEnv, S
766ubWhy_SstopubWhy, S
767trapWhy_SstoptrapWhy, S
768uncheckedWhy_SstopuncheckedWhy, S
770call_outcome_resultretX, Res, Env0, S, contEnv, S ←
ret_envRes, X, Env0, Env
772call_outcome_resultretsXs, Res, Env0, S, contEnv, S ←
rets_envRes, Xs, Env0, Env
774call_outcome_resultret, void, Env, S, contEnv, S

A data function's words, read as the call site reads them through the generic entry; too few fail its size check.

777call_outcome_resultdataXs, Res, Env0, S, Result ←
lengthKs, N
lengthXs, NXs
NXs < N
→
Result = stoptrapreturn_size, S
else
lengthXs1, N
appendXs1, _, Xs
maplistgeneric_wordp, Ks, Xs1, Ys
Ys ≡ []
→
Out = ret
Ys = Y
→
Out = retY
else
Out = retsYs
call_outcome_resultOut, Res, Env0, S, Result
785call_outcome_resultunreachable, _, _, S, stopunreachable, S
786call_outcome_resultubWhy, _, _, S, stopubWhy, S
787call_outcome_resulttrapWhy, _, _, S, stoptrapWhy, S
788call_outcome_resultuncheckedWhy, _, _, S, stopuncheckedWhy, S
789call_outcome_resultout_of_fuel, _, _, S, stopout_of_fuel, S
790call_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.

795ret_envresultT, K, X, Env0, Env ←
to_word_envresultT, K, X, Env0, Env
796ret_envvoid, _, Env, Env
798rets_envtupleTos, Xs, Env0, Env ←
foldlto_word_env, Tos, Xs, Env0, Env
799rets_envvoid, _, Env, Env
801to_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.

807word_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).

810word_at_classl, X0, X ←
nonvarX0
X0 = limit_, _
→
X = X0
else
modulusl, Mod
int_isX, X0 mod Mod
814word_at_classp, X, X
815word_at_classa, X, X
816word_at_classf, X, X
817word_at_classs, X0, X ←
must_beinteger, X0
X is X0 mod 4294967296
818word_at_classd, X0, X ←
must_beinteger, X0
X is X0 mod 18446744073709551616
819word_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.

825value_xK, V, Ctx, Env, X ←
value_x_V, K, Ctx, Env, X
827value_x_tmpN, K, _, Env, X ←
get_assocN, Env, X0
→
true
else
formatatomMsg, 'read of tmp(~w) before any assignment', N
thrownanofil_errorMsg
833value_x_constN, K, _, _, X ←
modulusK, Mod
A is N mod Mod
837value_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
844wAA
845lAA
846pAptrA, none
847sAA
848dAA
849xAA
851undefined_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.

856flight_arg_xCtx, Env, addressP, X ←
!
value_xp, P, Ctx, Env, ptrA, _
859flight_arg_xCtx, Env, V, X ←
value_xl, V, Ctx, Env, X
861flight_capnone, _, _, none
862flight_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.

871op_rescmpC, CK, _, _, A, B, S, valR, S ←
memberchkCK, s, d, x
!
ieee_compareCK, C, A, B, R
873op_resOp, K, _, Xs, S, valR, S ←
memberchkK, s, d, x
memberchkOp, add, sub, mul, div, neg, copy
!
float_arithmeticOp, K, Xs, R
876op_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.

882kind_ressetjmp, _, _, _, _, _, _, _ ←
thrownanofil_error'interpreter does not support setjmp/nonlocal returns'
884kind_resload, loadT, K, Mode, Args, S0, Res, S ←
load_op_resT, K, Mode, Args, S0, Res, S
886kind_resatomic_load, atomic_loadp, K, Mode, Args, S0, Res, S ←
load_op_resp, K, Mode, Args, S0, Res, S
888kind_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).

899kind_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).

915kind_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).

923kind_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).

927kind_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
934kind_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).

940kind_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
950kind_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.

954kind_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).

969kind_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.

983kind_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).

987kind_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
996kind_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
1000kind_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
1005kind_rescas, casT, K, filc, E, N, ptrA, Cap, S0, Res, S ←
cas_resT, K, E, N, A, Cap, S0, Res, S
1007kind_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
1011kind_resalloc, allocAlign, _, Mode, Size, sMem0, Fuel, Res, sMem, Fuel ←
alloc_resMode, Mem0, Align, Size, Res, Mem
1014kind_resva_arg, _, K, filc, P, S0, Res, S ←
pointer_load_resfilc, P, S0, Cursor, S1
va_cursor_resCursor, K, P, S1, Res, S
1017kind_resadditive, Op, K, _, Xs, S, Res, S ←
additive_resK, Op, Xs, Res
1018kind_resalu, Op, K, _, Xs, S, Res, S ←
aluOp, K, Xs, Res
1019kind_resshift, Op, K, _, Xs, S, Res, S ←
aluOp, K, Xs, Res
1020kind_resneg, Op, K, _, Xs, S, Res, S ←
aluOp, K, Xs, Res
1021kind_rescopy, Op, K, _, Xs, S, Res, S ←
aluOp, K, Xs, Res
1022kind_resptoi, Op, K, _, Xs, S, Res, S ←
aluOp, K, Xs, Res
1023kind_resitop, Op, K, _, Xs, S, Res, S ←
aluOp, K, Xs, Res
1024kind_rescmp, Op, K, _, Xs, S, Res, S ←
aluOp, K, Xs, Res
1025kind_resext, Op, K, _, Xs, S, Res, S ←
aluOp, K, Xs, Res
1026kind_resfloat_convert, exts, K, _, X, S, valR, S ←
ieee_converts, K, X, R
1028kind_resfloat_convert, extd, x, _, X, S, valR, S ←
ieee_convertd, x, X, R
1030kind_resfloat_convert, truncd, s, _, X, S, valR, S ←
ieee_convertd, s, X, R
1032kind_resfloat_convert, truncx, K, _, X, S, valR, S ←
ieee_convertx, K, X, R
1034kind_resfloat_convert, sqrt, K, _, X, S, valR, S ←
ieee_sqrtK, X, R
1036kind_resfloat_to_int, Op, K, _, X, S, Res, S ←
float_integerOp, K, X, Res
1038kind_resint_to_float, Op, K, _, X, S, valR, S ←
must_beinteger, X
1040kind_rescast, _, _, _, X, S, valX, S ←
must_beinteger, X
1042float_arithmeticcopy, _, X, X ←
must_beinteger, X
1043float_arithmeticneg, K, X, R ←
ieee_negK, X, R
1044float_arithmeticOp, K, X, Y, R ←
ieee_binaryK, Op, X, Y, R
1046integer_sourceswtof, X, N ←
signedw, X, N
1047integer_sourceuwtof, X, X
1048integer_sourcesltof, X, N ←
signedl, X, N
1049integer_sourceultof, X, X
1051float_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
1060stosissigned
1060stouisunsigned
1061dtosidsigned
1061dtouidunsigned
1062xtosixsigned
1062xtouixunsigned

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.

1067va_cellx, 16, 16 ←
!
1068va_cellK, Size, 8 ←
1070va_cursor_restrapWhy, _, _, S, trapWhy, S
1071va_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
1077va_checked_restrapWhy, _, _, _, _, S, trapWhy, S
1078va_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.

1088checked_resok, P, valP
1089checked_restrapWhy, _, trapWhy
1091asm_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.

1100renew_resok, P, _, valP
1101renew_restrapreadonly, _, _, trapreadonly
1102renew_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.

1116half_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
1126half_boundupper, B, Size, _, End, _ ←
B + Size ≤ End
1127half_boundlower, B, _, Base, _, Life ←
Life ≢ freed
B ≥ 1 << 63
→
S is B−1 << 64
else
S = B
S ≥ Base
half_res
1132oktruePvalP
1133trapWhyfalse_trapWhy
1134okfalse_uncheckedhalf
1135trap_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
1141valXvalX
1142okok
1143ubWhyubWhy
1144trapWhyuncheckedWhy
raw_address+A, +Off, -B

B is the address A plus the constant Off.

1147raw_addressA, Off, B ←
B is A + Off mod 18446744073709551616
1149alloc_pointerplain, Base, Base
1150alloc_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.

1155load_op_resp, _, Mode, Args, S0, Res, S ←
pointer_load_resMode, Args, S0, Res, S
1157load_op_resl, _, Mode, Args, S0, Res, S ←
int_load_resMode, 8, Args, S0, Res, S
1159load_op_ress, _, Mode, Args, S0, Res, S ←
int_load_resMode, 4, Args, S0, Res, S
1161load_op_resd, _, Mode, Args, S0, Res, S ←
int_load_resMode, 8, Args, S0, Res, S
1163load_op_resx, _, Mode, Args, S0, Res, S ←
int_load_resMode, 10, Args, S0, Res, S
1165load_op_ressw, K, Mode, Args, S0, Res, S ←
ext_load_ressw, 4, K, Mode, Args, S0, Res, S
1167load_op_resuw, K, Mode, Args, S0, Res, S ←
ext_load_resuw, 4, K, Mode, Args, S0, Res, S
1169load_op_ressh, K, Mode, Args, S0, Res, S ←
ext_load_ressh, 2, K, Mode, Args, S0, Res, S
1171load_op_resuh, K, Mode, Args, S0, Res, S ←
ext_load_resuh, 2, K, Mode, Args, S0, Res, S
1173load_op_ressb, K, Mode, Args, S0, Res, S ←
ext_load_ressb, 1, K, Mode, Args, S0, Res, S
1175load_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.

1180pointer_load_resfilc, ptrA, Cap, sMem0, Fuel, Res, sMem, Fuel ←
access_resMem0, A, Cap, 8, read, 8, Res0
pointer_access_loadRes0, Mem0, A, Res, Mem
1184pointer_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
1188pointer_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.

1194pointer_load_effects_resok, Effects, A, Mem0, valP, Mem ←
foldlpointer_load_effectA, Effects, loadednone, ptr0, none, Mem0, loaded_, P, Mem
1197pointer_load_effects_restrapWhy, _, _, Mem, uncheckedWhy, Mem
1199pointer_load_effectA, aux_read, loaded_, ptrU, _, Mem, loadedC, ptrU, C, Mem ←
Mem = mem_, _, _, Aux
get_assocA, Aux, C
→
true
else
C = none
1203pointer_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.

1207pointer_load_effectA, atomic_read, L0, L ←
pointer_load_effectA, aux_read, L0, L1
pointer_load_effectA, address_read, L1, L
1210pointer_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.

1219pointer_store_effects_resok, Effects, A, P, Mem0, ok, Mem ←
foldlpointer_store_effectA, P, Effects, Mem0, Mem
1221pointer_store_effects_restrapWhy, _, _, _, Mem, uncheckedWhy, Mem
1223pointer_store_effect_, _, aux_ensure, Mem, Mem
1224pointer_store_effect_, _, lower_barrier, Mem, Mem
1225pointer_store_effect_, _, rooted_reload, Mem, Mem
1226pointer_store_effectA, P, atomic_write, Mem0, Mem ←
pointer_store_effectA, P, lower_write, Mem0, Mem1
pointer_store_effectA, P, address_write, Mem1, Mem
1229pointer_store_effectA, ptr_, C, lower_write, memBytes, Rs, Next, Aux0, memBytes, Rs, Next, Aux ←
stored_auxcapC, A, Aux0, Aux
1232pointer_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.

1242cas_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
1247cas_checkedtrapWhy, _, _, _, _, _, _, Mem, trapWhy, Mem
1248cas_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
1255cas_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
1268ext_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.

1274extended_resvalU, T, K, valR ←
extensionT, U, S
int_isR, S mod M
1278extended_resubWhy, _, _, ubWhy
1279extended_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.

1283int_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.

1291address_accessplain, A, _, _, A, ok
1292address_accessfilc, ptrA, Cap, Mem, Size, A, Res ←
access_resMem, A, Cap, Size, read, 1, Res
1295int_access_loadok, Mode, Mem0, A, Size, Res, Mem ←
load_resMode, Mem0, A, Size, Res, Mem
1297int_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.

1301additive_resp, Op, ptrA, C, X, valptrR, C ←
pointer_offsetOp, A, X, M, R
1304additive_resw, Op, Xs, Res ←
aluOp, w, Xs, Res
1305additive_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.

1309pointer_offsetadd, A, X, M, R ←
int_isR, A + X mod M
1310pointer_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.

1317aluptoi, _, ptrA, _, valA
1318aluitop, _, X, valptrX, none
1320aluadd, K, X, Y, valR ←
int_isR, X + Y mod M
1321alusub, K, X, Y, valR ←
int_isR, X−Y mod M
1322alumul, K, X, Y, valR ←
int_isR, X × Y mod M
1323aluneg, K, X, valR ←
int_isR, −X mod M
1324alucopy, _, X, valX
1325aluand, _, X, Y, valR ←
int_isR, X /\ Y
1326aluor, _, X, Y, valR ←
int_isR, X \/ Y
1327aluxor, _, X, Y, valR ←
int_isR, X xor Y
1328aluudiv, _, X, Y, Res ←
unsigned_divisionX, Y, X//Y, Res
1329aluurem, _, X, Y, Res ←
unsigned_divisionX, Y, X mod Y, Res
1330aludiv, K, X, Y, Res ←
signed_divisionK, X, Y, //, Res
1331alurem, K, X, Y, Res ←
signed_divisionK, X, Y, rem, Res
1332alushl, K, X, Y, valR ←
int_isR, X × P mod M
1336alushr, K, X, Y, valR ←
int_isR, X//P
1339alusar, K, X, Y, valR ←
signedK, X, SX
int_isR, SX div P mod M
1344aluextT, K, X, valR ←
extensionT, X, S
int_isR, S mod M
1348alucmpCond, 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.

1354unsigned_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.

1368signed_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
1386division//, X, Y, Q ←
int_isQ, X//Y
1387divisionrem, X, Y, Q ←
int_isQ, X rem Y

The shift count is taken modulo the class width.

1390shift_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.

1397cond_biteq, _, X, Y, B ←
reifyX =:= Y, B
1398cond_bitne, _, X, Y, B ←
reifyX ≠ Y, B
1399cond_bitult, _, X, Y, B ←
reifyX < Y, B
1400cond_bitule, _, X, Y, B ←
reifyX ≤ Y, B
1401cond_bitugt, _, X, Y, B ←
reifyX > Y, B
1402cond_bituge, _, X, Y, B ←
reifyX ≥ Y, B
1403cond_bitslt, K, X, Y, B ←
signed_pairK, X, Y, SX, SY
reifySX < SY, B
1406cond_bitsle, K, X, Y, B ←
signed_pairK, X, Y, SX, SY
reifySX ≤ SY, B
1409cond_bitsgt, K, X, Y, B ←
signed_pairK, X, Y, SX, SY
reifySX > SY, B
1412cond_bitsge, K, X, Y, B ←
signed_pairK, X, Y, SX, SY
reifySX ≥ SY, B
1416signed_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.

1421signedK, 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.

1430sign_extensionM, H, U, S ←
integerU
!
L is U mod M
L ≥ H
→
S is L−M
else
S = L
1435sign_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.

1443extensionsb, X, S ←
sign_extension256, 128, X, S
1444extensionub, X, S ←
int_isS, X mod 256
1445extensionsh, X, S ←
sign_extension65536, 32768, X, S
1446extensionuh, X, S ←
int_isS, X mod 65536
1447extensionsw, X, S ←
sign_extension4294967296, 2147483648, X, S
1448extensionuw, 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.

1465free_resA, Mem0, Res, Mem ←
integerA
!
cap_free_resnone, A, Mem0, Res, Mem
1468free_resptrA, Cap, Mem0, Res, Mem ←
must_beinteger, A
must_becallable, Cap
cap_free_resCap, A, Mem0, Res, Mem
1472cap_free_resextN, _, _, _, _ ←
formatatomMsg, 'cannot interpret free of external ~w', N
thrownanofil_errorMsg
1475cap_free_resnone, A, Mem, Res, Mem ←
A =:= 0
→
Res = ok
else
Res = trapinvalid_free
1477cap_free_resfn_, A, Mem, Res, Mem ←
A =:= 0
→
Res = ok
else
Res = trapinvalid_free
1479cap_free_resobjBase, A, Mem0, Res, Mem ←
object_free_resBase, A, Mem0, Res, Mem
1481cap_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.

1487object_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.

1502object_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).

1516load_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.

1531store_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
1542store_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
1551bVV1none
1552hVV1none
1553wVV1none
1554lVV1none
1555sVV1none
1556dVV1none
1557xVV8none
1558pptrV, CV8capC
access_store+Res, +Mem0, +A, +Size, +V, +Stored, -Memdet

Mem is Mem0 after a checked store with result Res.

1563access_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
1568access_storetrap_, Mem, _, _, _, _, Mem
1570stored_auxnone, _, Aux, Aux
1571stored_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.

1584access_resmem_, Rs, _, _, A, Cap, Size, Kind, Align, Res ←
must_beinteger, A
must_becallable, Cap
cap_access_resCap, Rs, A, Size, Kind, Align, Res
1589cap_access_resnone, _, _, _, _, _, trapnull_capability
1590cap_access_resfn_, _, _, _, _, _, trapout_of_bounds
1591cap_access_resextN, _, _, _, _, _, _ ←
formatatomMsg, 'cannot interpret an access to external ~w', N
thrownanofil_errorMsg
1594cap_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
1600cap_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
1607readonly_access_resread, A, Align, Res ←
aligned_resA, Align, Res
1608readonly_access_reswrite, _, _, trapreadonly
1610aligned_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.

1616in_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.

1626bytes_at[], _, _, Bytes, Bytes
1627bytes_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
1634unwritten_bytefilc, _, 0, Bytes, Bytes
1635unwritten_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, ...

1641written_bytes[], _, Bytes, Bytes
1642written_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.

1649le_value[], 0
1650le_valueX|Xs, U ←
le_valueXs, U1
int_isU, X + 256 × U1
1652le_bytesN, X, Bs ←
lengthBs, N
low_bytes?Bs, ?X

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

1657low_bytes[], _
1658low_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.

1669alloc_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.

1680max_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.

1689alloc_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.

1705address_spaceplain, _
1706address_spacefilc, Next ←
Next ≤ 281474976710656
1707address_spacelocal, Next ←
Next ≤ 281474976710656
1709alloc_extentplain, Size, Size, stack
1710alloc_extentfilc, Size, Extent, heap ←
Extent is Size + 15//16 × 16
1711alloc_extentlocal, Size, Size, stack
1713allocation_strideplain, _, Extent, Extent
1714allocation_stridefilc, _, Extent, Extent
1715allocation_stridelocal, Size, _, Stride ←
Stride is Size + 15//16 × 16
1717alloc_alignplain, Align, Align
1718alloc_alignfilc, Align0, Align ←
Align is maxAlign0, 16
1719alloc_alignlocal, _, 16
1721stack_base4294967296
1722data_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.

1730initial_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
1738initial_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.

1752plain_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
1759plain_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.

1763plain_contentsGlobals, dataName, _, _, Items, Bytes0, Bytes ←
get_assocName, Globals, A
maplistitem_bytesGlobals, Items, ItemBytes
appendItemBytes, DataBytes
written_bytesDataBytes, A, Bytes0, Bytes
1768plain_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).

1774filc_layoutdata_, externweak, _, [], State, State ←
!
1775filc_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
1784filc_layoutalias_, _, _, State, State
1785filc_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
1791exportBaseobjBase
1792localBaseobjBase
1793weakBaseobjBase

no other definition here

1794ro_BaseroBase
1795tls_BaseobjBase

one thread: one instance

1796init_, _BaseroBase

a table entry, never run here

1798filc_contents_, data_, externweak, _, [], State, State ←
!
1799filc_contentsGlobals, dataName, _, _, Items, Bytes0−Aux0, Bytes−Aux ←
get_assocName, Globals, ptrBase, _
foldlfilc_item_contentsGlobals, Items, Base−Bytes0−Aux0, _−Bytes−Aux
1802filc_contents_, func_, _, _, _, _, State, State
1803filc_contents_, alias_, _, _, State, State
alias_global+Item, +Globals0, -Globals

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

1807alias_globalaliasName, _, Target, Globals0, Globals ←
!
get_assocTarget, Globals0, P
put_assocName, Globals0, P, Globals
1809alias_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.

1815filc_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
1828filc_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).

1837item_sizezeroN, Size0, Size ←
Size is Size0 + N
1838item_sizeitemb, strCs, Size0, Size ←
!
lengthCs, N
Size is Size0 + N
1841item_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.

1848item_bytes_, zeroN, Bs ←
lengthBs, N
maplist=0, Bs
1849item_bytes_, itemb, strCs, Bs ←
!
maplistchar_code, Cs, Bs
1850item_bytes_, itemW, constN, Bs ←
Bits is Size × 8
U is N mod 1 << Bits
le_bytesSize, U, Bs
1855item_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.

1869outcome_textretV, T ←
1870outcome_textrets_, 'ret tuple'
1871outcome_textret, ret
1872outcome_textdataXs, T ←
lengthXs, N
formatatomT, 'ret data ~w words', N
1873outcome_textunreachable, unreachable
1874outcome_textubWhy, T ←
formatatomT, 'ub ~w', Why
1875outcome_texttrapWhy, T ←
formatatomT, 'trap ~w', Why
1876outcome_textuncheckedWhy, T ←
formatatomT, 'unchecked ~w', Why
1877outcome_textout_of_fuel, out_of_fuel
1878outcome_textunknownasmM, Xs, T ←
formatatomT, 'unknown asm ~w ~w', M, Xs
1880ret_textV, T ←
integerV
→
formatatomT, 'ret ~w', V
varV
→
T = 'ret unknown'
V = ptr_, _
→
T = 'ret pointer'
else
T = 'ret unknown'