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: division_by_zero, division_overflow, 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

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.

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

141module_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
,
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.

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

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

186class_argw, X ←
187class_argl, X ←
188class_args, X ←
must_beinteger, X
189class_argd, X ←
must_beinteger, X
190class_argp, X ←
X = ptr_, _
→
true
195int_arg_in_rangeK, X ←
modulusK, Mod
Top #= Mod−1
X in 0 .. Top
call_outcome+Fns, +Globals, +Mode, +Fn, +Xs, +S0, -Out, -S

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.

204call_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

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.

227invalidate_locals[], S, S
228invalidate_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.

237returned_stateplain, Regions, smemBytes, _, Next, Aux, Fuel, smemBytes, Regions, Next, Aux, Fuel
239returned_statefilc, _, S, S
241param_arg_envparam_, tmpN, X, Env0, Env ←
put_assocN, Env0, X, Env
243entry_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.

252args_snapshot[], Mem, ptr0, none, Mem
253args_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
258snapshot_values[], _, _, Mem, Mem
259snapshot_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.

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

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

274reifyTest, B ←
groundTest
→
callTest
→
B = 1
else
B = 0
else
B in 0 .. 1
280reify_constraintX =:= Y, B ←
B #<==> X #= Y
281reify_constraintX ≠ Y, B ←
B #<==> X #≠ Y
282reify_constraintX < Y, B ←
B #<==> X #< Y
283reify_constraintX ≤ Y, B ←
B #<==> X #≤ Y
284reify_constraintX > Y, B ←
B #<==> X #> Y
285reify_constraintX ≥ Y, B ←
B #<==> X #≥ Y

Blocks

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

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.

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

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

318result_outcomecontEnv, S0, Jump, Ctx, Label, Out, S ←
jump_outcomeJump, Ctx, Label, Env, S0, Out, S
320result_outcomestopOut, S, _, _, _, Out, S
jump_outcome+Jump, +Ctx, +From, +Env, +S0, -Out, -S

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.

328jump_outcomegotoT, Ctx, From, Env, S0, Out, S ←
block_outcomeCtx, L, fromFrom, Env, S0, Out, S
331jump_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
block_outcomeCtx, L, fromFrom, Env, S0, Out, S
339jump_outcomeretV, Ctx, _, Env, S, retX, S ←
Ctx = ctx_, _, _, K, _, _
value_xK, V, Ctx, Env, X
342jump_outcomeretsVs, Ctx, _, Env, S, retsXs, S ←
Ctx = ctx_, _, _, tupleKs, _, _
maplistclass_value_xCtx, Env, Ks, Vs, Xs
345jump_outcomeret, _, _, _, S, ret, S
346jump_outcomeunreachable, _, _, _, S, unreachable, S

Instructions

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

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

352insts_result[], _, Env, S, contEnv, S
353insts_resultInst|Insts, Ctx, Env0, S0, Result ←
inst_resultInst, Ctx, Env0, S0, Result0
rest_resultResult0, Insts, Ctx, Result
rest_result+Result0, +Insts, +Ctx, -Result

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

359rest_resultcontEnv, S, Insts, Ctx, Result ←
insts_resultInsts, Ctx, Env, S, Result
361rest_resultstopOut, S, _, _, stopOut, S
inst_result+Inst, +Ctx, +Env0, +S0, -Result

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

365inst_resultloc_, _, _, Env, S, contEnv, S ←
!
366inst_resultlocal_allocName, Size, ctx_, _, _, _, filc, Locals, Env, sMem0, Fuel, contEnv, sMem, Fuel ←
get_assocName, Locals, Base
varBase
→
true
else
thrownanofil_error'duplicate or undeclared local allocation'
alloc_baselocal, Mem0, 16, Size, Base, Mem
371inst_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
375inst_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
380inst_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
386inst_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
391inst_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
395inst_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
400inst_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
406inst_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
414inst_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
422inst_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
430inst_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
else
external_call_outcomeMode, Globals, F, Res, Args, Xs, S0, Out, S
call_outcome_resultOut, Res, Env0, S, Result
438inst_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
443inst_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
450local_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 is handled above; an in-module data symbol is not a function. Other signatures and external calls are explicit gaps, not silently failed relations or guessed libc effects.

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

a store of Width reads its value at K.

width_value_class
475bw
476hw
477ww
478ll
479pp
480ss
481dd
function_check_res+P, +Ctx, -Res

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.

487function_check_resptrA, Cap, ctx_, Globals, _, _, _, _, Res ←
must_becallable, Cap
cap_function_resCap, A, Globals, Res
491cap_function_resfnF, A, Globals, Res ←
get_assocF, Globals, ptrFnAddr, _
A ≡ FnAddr
→
Res = ok
else
Res = trapnot_a_function
497cap_function_resextF, _, _, _ ←
formatatomMsg, 'cannot interpret a call to external ~w', F
thrownanofil_errorMsg
500cap_function_resnone, _, _, trapnot_a_function
501cap_function_resobj_, _, _, trapnot_a_function
502cap_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.

507checked_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
517checked_call_resulttrapWhy, _, _, _, _, _, _, S, stoptrapWhy, S
518checked_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.

523conv_call_resulttrapWhy, _, _, _, _, _, _, _, S0, stoptrapWhy, S0
524conv_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
527conv_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).

542indirect_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.

565result_wordsvoid, []
566result_wordsresult_, K, K
567result_wordstupleTos, Ks ←
findallK, memberresult_, K, Tos, Ks
return_words
569void[]
570tupleKsKs
571ww
572ll
573pp
574ss
575dd
word_class?K, ?Class

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

word_class
579wint
580lint
581pp
582ss
583dd
generic_word+From, +To, +X, -Y

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

587generic_wordw, To, X, Y ←
int_word_asTo, X, Y
588generic_wordl, To, X, Y ←
int_word_asTo, X, Y
589generic_wordp, To, X, Y ←
590generic_words, To, X, Y ←
int_word_asTo, X, Y
591generic_wordd, To, X, Y ←
int_word_asTo, X, Y
593int_word_asw, X, X
594int_word_asl, X, X
595int_word_asp, X, ptrX, none
596int_word_ass, X, Y ←
597int_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.

602pointer_word_asw, X, A ←
603pointer_word_asl, X, A ←
604pointer_word_asp, X, X
605pointer_word_ass, X, Y ←
606pointer_word_asd, X, Y ←
608pointer_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.

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

625outcome_wordsret, []
626outcome_wordsretX, X
627outcome_wordsretsXs, Xs ←
Xs = _, _|_
629class_value_xCtx, Env, K, V, X ←
value_xK, V, Ctx, Env, X
630arg_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.

634res_resultvalX, D, Env0, S, contEnv, S ←
put_assocD, Env0, X, Env
635res_resultubWhy, _, _, S, stopubWhy, S
636res_resulttrapWhy, _, _, S, stoptrapWhy, S
637res_resultuncheckedWhy, _, _, S, stopuncheckedWhy, S
store_res_result
639okEnvScontEnv, S
640ubWhy_SstopubWhy, S
641trapWhy_SstoptrapWhy, S
642uncheckedWhy_SstopuncheckedWhy, S
644call_outcome_resultretX, Res, Env0, S, contEnv, S ←
ret_envRes, X, Env0, Env
646call_outcome_resultretsXs, Res, Env0, S, contEnv, S ←
rets_envRes, Xs, Env0, Env
648call_outcome_resultret, void, Env, S, contEnv, S
649call_outcome_resultunreachable, _, _, S, stopunreachable, S
650call_outcome_resultubWhy, _, _, S, stopubWhy, S
651call_outcome_resulttrapWhy, _, _, S, stoptrapWhy, S
652call_outcome_resultuncheckedWhy, _, _, S, stopuncheckedWhy, S
653call_outcome_resultout_of_fuel, _, _, S, stopout_of_fuel, 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.

658ret_envresultT, K, X, Env0, Env ←
to_word_envresultT, K, X, Env0, Env
659ret_envvoid, _, Env, Env
661rets_envtupleTos, Xs, Env0, Env ←
foldlto_word_env, Tos, Xs, Env0, Env
662rets_envvoid, _, Env, Env
664to_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.

670word_at_classw, X0, X ←
modulusw, Mod
int_isX, X0 mod Mod
671word_at_classl, X0, X ←
modulusl, Mod
int_isX, X0 mod Mod
672word_at_classp, X, X
673word_at_classa, X, X
674word_at_classf, X, X
675word_at_classs, X0, X ←
must_beinteger, X0
X is X0 mod 4294967296
676word_at_classd, X0, X ←
must_beinteger, X0
X is X0 mod 18446744073709551616
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.

682value_xK, V, Ctx, Env, X ←
value_x_V, K, Ctx, Env, X
684value_x_tmpN, K, _, Env, X ←
get_assocN, Env, X0
→
true
else
formatatomMsg, 'read of tmp(~w) before any assignment', N
thrownanofil_errorMsg
690value_x_constN, K, _, _, X ←
modulusK, Mod
A is N mod Mod
694value_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
701wAA
702lAA
703pAptrA, none
704sAA
705dAA
707undefined_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.

712flight_arg_xCtx, Env, addressP, X ←
!
value_xp, P, Ctx, Env, ptrA, _
715flight_arg_xCtx, Env, V, X ←
value_xl, V, Ctx, Env, X
717flight_capnone, _, _, none
718flight_caplowerP, Ctx, Env, Cap ←
value_xp, P, Ctx, Env, ptr_, Cap

Operations

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

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.

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

737kind_ressetjmp, _, _, _, _, _, _, _ ←
thrownanofil_error'interpreter does not support setjmp/nonlocal returns'
739kind_resload, loadT, K, Mode, Args, S0, Res, S ←
load_op_resT, K, Mode, Args, S0, Res, S
741kind_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
747kind_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
751kind_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
756kind_resalloc, allocAlign, _, Mode, Size, sMem0, Fuel, valP, sMem, Fuel ←
alloc_baseMode, Mem0, Align, Size, Base, Mem
alloc_pointerMode, Base, P
760kind_resva_arg, _, K, filc, P, S0, Res, S ←
pointer_load_resfilc, P, S0, Cursor, S1
va_cursor_resCursor, K, P, S1, Res, S
763kind_resadditive, Op, K, _, Xs, S, Res, S ←
additive_resK, Op, Xs, Res
764kind_resalu, Op, K, _, Xs, S, Res, S ←
aluOp, K, Xs, Res
765kind_resshift, Op, K, _, Xs, S, Res, S ←
aluOp, K, Xs, Res
766kind_resneg, Op, K, _, Xs, S, Res, S ←
aluOp, K, Xs, Res
767kind_rescopy, Op, K, _, Xs, S, Res, S ←
aluOp, K, Xs, Res
768kind_resptoi, Op, K, _, Xs, S, Res, S ←
aluOp, K, Xs, Res
769kind_resitop, Op, K, _, Xs, S, Res, S ←
aluOp, K, Xs, Res
770kind_rescmp, Op, K, _, Xs, S, Res, S ←
aluOp, K, Xs, Res
771kind_resext, Op, K, _, Xs, S, Res, S ←
aluOp, K, Xs, Res
772kind_resfloat_convert, exts, d, _, X, S, valR, S ←
ieee_converts, d, X, R
774kind_resfloat_convert, truncd, s, _, X, S, valR, S ←
ieee_convertd, s, X, R
776kind_resfloat_to_int, Op, K, _, X, S, Res, S ←
float_integerOp, K, X, Res
778kind_resint_to_float, Op, K, _, X, S, valR, S ←
must_beinteger, X
780kind_rescast, _, _, _, X, S, valX, S ←
must_beinteger, X
782float_arithmeticcopy, _, X, X ←
must_beinteger, X
783float_arithmeticneg, K, X, R ←
ieee_negK, X, R
784float_arithmeticOp, K, X, Y, R ←
ieee_binaryK, Op, X, Y, R
786integer_sourceswtof, X, N ←
signedw, X, N
787integer_sourceuwtof, X, X
788integer_sourcesltof, X, N ←
signedl, X, N
789integer_sourceultof, X, X
791float_integerOp, K, X, Res ←
memberchkOp, stosi, stoui
→
From = s
else
From = d
memberchkOp, stosi, dtosi
→
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
800va_cursor_restrapWhy, _, _, S, trapWhy, S
801va_cursor_resvalptrA, Cap, K, P, sMem0, Fuel, Res, S ←
Aligned is A + 7//8 × 8
access_resMem0, Aligned, Cap, Size, read, 8, Check
va_checked_resCheck, K, P, Aligned, Size, sMem0, Fuel, Res, S
806va_checked_restrapWhy, _, _, _, _, S, trapWhy, S
807va_checked_resok, K, P, A, Size, sMem0, Fuel, Res, sMem, Fuel ←
Next is A + Size
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, -Res

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

816checked_resok, P, valP
817checked_restrapWhy, _, trapWhy
raw_res+Res0, -Res

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

raw_res
821valXvalX
822okok
823ubWhyubWhy
824trapWhyuncheckedWhy
raw_address+A, +Off, -B

B is the address A plus the constant Off.

827raw_addressA, Off, B ←
B is A + Off mod 18446744073709551616
829alloc_pointerplain, Base, Base
830alloc_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.

835load_op_resp, _, Mode, Args, S0, Res, S ←
pointer_load_resMode, Args, S0, Res, S
837load_op_resl, _, Mode, Args, S0, Res, S ←
int_load_resMode, 8, Args, S0, Res, S
839load_op_ress, _, Mode, Args, S0, Res, S ←
int_load_resMode, 4, Args, S0, Res, S
841load_op_resd, _, Mode, Args, S0, Res, S ←
int_load_resMode, 8, Args, S0, Res, S
843load_op_ressw, K, Mode, Args, S0, Res, S ←
ext_load_ressw, 4, K, Mode, Args, S0, Res, S
845load_op_resuw, K, Mode, Args, S0, Res, S ←
ext_load_resuw, 4, K, Mode, Args, S0, Res, S
847load_op_ressh, K, Mode, Args, S0, Res, S ←
ext_load_ressh, 2, K, Mode, Args, S0, Res, S
849load_op_resuh, K, Mode, Args, S0, Res, S ←
ext_load_resuh, 2, K, Mode, Args, S0, Res, S
851load_op_ressb, K, Mode, Args, S0, Res, S ←
ext_load_ressb, 1, K, Mode, Args, S0, Res, S
853load_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.

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

872pointer_load_effects_resok, Effects, A, Mem0, valP, Mem ←
foldlpointer_load_effectA, Effects, loadednone, ptr0, none, Mem0, loaded_, P, Mem
875pointer_load_effects_restrapWhy, _, _, Mem, uncheckedWhy, Mem
877pointer_load_effectA, aux_read, loaded_, ptrU, _, Mem, loadedC, ptrU, C, Mem ←
Mem = mem_, _, _, Aux
get_assocA, Aux, C
→
true
else
C = none
881pointer_load_effect_, lower_resolve, loadedC, ptrU, _, Mem, loadedC, ptrU, C, Mem
883pointer_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.

892pointer_store_effects_resok, Effects, A, P, Mem0, ok, Mem ←
foldlpointer_store_effectA, P, Effects, Mem0, Mem
894pointer_store_effects_restrapWhy, _, _, _, Mem, uncheckedWhy, Mem
896pointer_store_effect_, _, aux_ensure, Mem, Mem
897pointer_store_effect_, _, lower_barrier, Mem, Mem
898pointer_store_effect_, _, rooted_reload, Mem, Mem
899pointer_store_effectA, ptr_, C, lower_write, memBytes, Rs, Next, Aux0, memBytes, Rs, Next, Aux ←
stored_auxcapC, A, Aux0, Aux
902pointer_store_effectA, ptrU, _, address_write, memBytes0, Rs, Next, Aux, memBytes, Rs, Next, Aux ←
le_bytes8, U, Written
written_bytesWritten, A, Bytes0, Bytes
907ext_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.

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

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

930address_accessplain, A, _, _, A, ok
931address_accessfilc, ptrA, Cap, Mem, Size, A, Res ←
access_resMem, A, Cap, Size, read, 1, Res
934int_access_loadok, Mode, Mem0, A, Size, Res, Mem ←
load_resMode, Mem0, A, Size, Res, Mem
936int_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.

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

948pointer_offsetadd, A, X, M, R ←
int_isR, A + X mod M
949pointer_offsetsub, A, X, M, R ←
int_isR, A−X mod M
alu+Op, +K, +Xs, -Res

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.

954aluptoi, _, ptrA, _, valA
955aluitop, _, X, valptrX, none
957aluadd, K, X, Y, valR ←
int_isR, X + Y mod M
958alusub, K, X, Y, valR ←
int_isR, X−Y mod M
959alumul, K, X, Y, valR ←
int_isR, X × Y mod M
960aluneg, K, X, valR ←
int_isR, −X mod M
961alucopy, _, X, valX
962aluand, _, X, Y, valR ←
int_isR, X /\ Y
963aluor, _, X, Y, valR ←
int_isR, X \/ Y
964aluxor, _, X, Y, valR ←
int_isR, X xor Y
965aluudiv, _, X, Y, Res ←
unsigned_divisionX, Y, X//Y, Res
966aluurem, _, X, Y, Res ←
unsigned_divisionX, Y, X mod Y, Res
967aludiv, K, X, Y, Res ←
signed_divisionK, X, Y, //, Res
968alurem, K, X, Y, Res ←
signed_divisionK, X, Y, rem, Res
969alushl, K, X, Y, valR ←
int_isR, X × P mod M
973alushr, K, X, Y, valR ←
int_isR, X//P
976alusar, K, X, Y, valR ←
signedK, X, SX
int_isR, SX div P mod M
981aluextT, K, X, valR ←
extensionT, X, S
int_isR, S mod M
985alucmpCond, CK, _, X, Y, valB ←
cond_bitCond, CK, X, Y, B
unsigned_division+X, +Y, +Expr, -Res

Res is val of Expr, or ub when the divisor Y is zero. An unknown Y gives both cases as solutions; a known one takes the fast path.

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

x86 idiv traps on both a zero divisor and the one quotient that overflows, so both are undefined here. Known operands take the fast path; otherwise the three cases are disjoint constraints.

1002signed_divisionK, X, Y, Fn, Res ←
signedK, X, SX
signedK, Y, SY
Min is −H
integerSX
integerSY
→
SY =:= 0
→
Res = ubdivision_by_zero
SY =:= -1
SX =:= Min
→
Res = ubdivision_overflow
else
divisionFn, SX, SY, Q
R is Q mod M
Res = valR
else
SY #= 0
Res = ubdivision_by_zero
or
SY #= -1
SX #= Min
Res = ubdivision_overflow
or
SY #≠ 0
#\SY #= -1 #/\ SX #= Min
divisionFn, SX, SY, Q
R #= Q mod M
Res = valR
1024division//, X, Y, Q ←
int_isQ, X//Y
1025divisionrem, X, Y, Q ←
int_isQ, X rem Y

The shift count is taken modulo the class width.

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

1035cond_biteq, _, X, Y, B ←
reifyX =:= Y, B
1036cond_bitne, _, X, Y, B ←
reifyX ≠ Y, B
1037cond_bitult, _, X, Y, B ←
reifyX < Y, B
1038cond_bitule, _, X, Y, B ←
reifyX ≤ Y, B
1039cond_bitugt, _, X, Y, B ←
reifyX > Y, B
1040cond_bituge, _, X, Y, B ←
reifyX ≥ Y, B
1041cond_bitslt, K, X, Y, B ←
signed_pairK, X, Y, SX, SY
reifySX < SY, B
1044cond_bitsle, K, X, Y, B ←
signed_pairK, X, Y, SX, SY
reifySX ≤ SY, B
1047cond_bitsgt, K, X, Y, B ←
signed_pairK, X, Y, SX, SY
reifySX > SY, B
1050cond_bitsge, K, X, Y, B ←
signed_pairK, X, Y, SX, SY
reifySX ≥ SY, B
1054signed_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.

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

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

1081extensionsb, X, S ←
sign_extension256, 128, X, S
1082extensionub, X, S ←
int_isS, X mod 256
1083extensionsh, X, S ←
sign_extension65536, 32768, X, S
1084extensionuh, X, S ←
int_isS, X mod 65536
1085extensionsw, X, S ←
sign_extension4294967296, 2147483648, X, S
1086extensionuw, 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.

1103free_resA, Mem0, Res, Mem ←
integerA
!
cap_free_resnone, A, Mem0, Res, Mem
1106free_resptrA, Cap, Mem0, Res, Mem ←
must_beinteger, A
must_becallable, Cap
cap_free_resCap, A, Mem0, Res, Mem
1110cap_free_resextN, _, _, _, _ ←
formatatomMsg, 'cannot interpret free of external ~w', N
thrownanofil_errorMsg
1113cap_free_resnone, A, Mem, Res, Mem ←
A =:= 0
→
Res = ok
else
Res = trapinvalid_free
1115cap_free_resfn_, A, Mem, Res, Mem ←
A =:= 0
→
Res = ok
else
Res = trapinvalid_free
1117cap_free_resobjBase, A, Mem0, Res, Mem ←
object_free_resBase, A, Mem0, Res, Mem
1119cap_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.

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

1140object_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, -Mem

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

1152load_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, -Mem

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

1166store_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
1177store_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
1186bVV1none
1187hVV1none
1188wVV1none
1189lVV1none
1190sVV1none
1191dVV1none
1192pptrV, CV8capC
access_store+Res, +Mem0, +A, +Size, +V, +Stored, -Mem

Mem is Mem0 after a checked store with result Res.

1196access_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
1201access_storetrap_, Mem, _, _, _, _, Mem
1203stored_auxnone, _, Aux, Aux
1204stored_auxcapC, A, Aux0, Aux ←
put_assocA, Aux0, C, Aux

access_res(+Mem, +A, +Cap, +Size, +Kind, +Align, -Res): 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.

1214access_resmem_, Rs, _, _, A, Cap, Size, Kind, Align, Res ←
must_beinteger, A
must_becallable, Cap
cap_access_resCap, Rs, A, Size, Kind, Align, Res
1219cap_access_resnone, _, _, _, _, _, trapnull_capability
1220cap_access_resfn_, _, _, _, _, _, trapout_of_bounds
1221cap_access_resextN, _, _, _, _, _, _ ←
formatatomMsg, 'cannot interpret an access to external ~w', N
thrownanofil_errorMsg
1224cap_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
1230cap_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
1237readonly_access_resread, A, Align, Res ←
aligned_resA, Align, Res
1238readonly_access_reswrite, _, _, trapreadonly
1240aligned_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.

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

1256bytes_at[], _, _, Bytes, Bytes
1257bytes_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
1264unwritten_bytefilc, _, 0, Bytes, Bytes
1265unwritten_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, ...

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

1279le_value[], 0
1280le_valueX|Xs, U ←
le_valueXs, U1
int_isU, X + 256 × U1
1282le_bytesN, X, Bs ←
lengthBs, N
low_bytes?Bs, ?X

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

1287low_bytes[], _
1288low_bytesB|Bs, X ←
int_isB, X mod 256
int_isX1, X//256
low_bytesBs, X1
alloc_base+Mode, +Mem0, +Align, +Size, -Base, -Mem

Base is a new region of Size bytes in Mem. Allocations are aligned (Fil-C's to 16), with Fil-C bounds rounded to 16 as prepare_allocate_object does (zero stays zero). A 16-byte gap separates distinct, never-reused identities.

1297alloc_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
1307alloc_extentplain, Size, Size, stack
1308alloc_extentfilc, Size, Extent, heap ←
Extent is Size + 15//16 × 16
1309alloc_extentlocal, Size, Size, stack
1311allocation_strideplain, _, Extent, Extent
1312allocation_stridefilc, _, Extent, Extent
1313allocation_stridelocal, Size, _, Stride ←
Stride is Size + 15//16 × 16
alloc_align
1315plainAlignAlign
1316filc_16
1317local_16
1319stack_base4294967296
1320data_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 16-aligned with their size rounded up to 8, as Fil-C's are, and each function gets an address and a fn capability.

1327initial_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
1335initial_memoryfilc, moduleItems, Globals, memBytes, Rs, Stack, Aux ←
data_baseDataBase
empty_assocGlobals0
foldlfilc_layout, Items, DataBase−Globals0−[], _−Globals−Rs
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.

1348plain_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
1355plain_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.

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

place Item at the next 16-aligned address from A0.

1368filc_layoutdataName, Linkage, _, Items, A0−Globals0−Rs0, A−Globals−regionBase, End, global, live|Rs0 ←
Base is A0 + 15//16 × 16
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
1376filc_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
1382exportBaseobjBase
1383localBaseobjBase
1384ro_BaseroBase
1386filc_contentsGlobals, dataName, _, _, Items, Bytes0−Aux0, Bytes−Aux ←
get_assocName, Globals, ptrBase, _
foldlfilc_item_contentsGlobals, Items, Base−Bytes0−Aux0, _−Bytes−Aux
1389filc_contents_, func_, _, _, _, _, State, State
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.

1395filc_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
1408filc_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).

1417item_sizezeroN, Size0, Size ←
Size is Size0 + N
1418item_sizeitemb, strCs, Size0, Size ←
!
lengthCs, N
Size is Size0 + N
1421item_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.

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

1449outcome_textretV, T ←
1450outcome_textrets_, 'ret tuple'
1451outcome_textret, ret
1452outcome_textunreachable, unreachable
1453outcome_textubWhy, T ←
formatatomT, 'ub ~w', Why
1454outcome_texttrapWhy, T ←
formatatomT, 'trap ~w', Why
1455outcome_textuncheckedWhy, T ←
formatatomT, 'unchecked ~w', Why
1456outcome_textout_of_fuel, out_of_fuel
1458ret_textV, T ←
integerV
→
formatatomT, 'ret ~w', V
varV
→
T = 'ret unknown'
V = ptr_, _
→
T = 'ret pointer'
else
T = 'ret unknown'