rocq/theories/Semantics.v
The small-step semantics of the core subset of the checked IR, transcribed from the interpreter's run/5 (interp.pl).
The interpreter is a big-step relation over blocks: block_outcome/7 runs a block and everything after it, and a call runs its callee to an outcome inside the instruction. The model takes the same steps one at a time over an explicit configuration (formal.md's decision: small-step, not interaction trees), so that Campaign 3's invariants can be stated between any two steps.
The configuration
| Part | Is | interp.pl |
|---|---|---|
c_frame | the running invocation | the ctx(...) term, Env and the block being run |
c_callers | the suspended callers, each with how it reads the result | Prolog's own stack |
c_mem | memory | s(Mem, Fuel)'s Mem, less Next |
c_fuel | blocks left to run | s(Mem, Fuel)'s Fuel |
Outcomes
| Gallina | interp.pl |
|---|---|
Retx | ret(V) |
Retsxs | rets([X1, X2]) |
RetVoid | ret |
Unreachable | unreachable |
UBwhy | ub(Reason): here only float_conversion, an invalid float-to-integer conversion |
Trapwhy | trap(Reason) |
Uncheckedwhy | unchecked(Reason) |
OutOfFuel | out_of_fuel |
| | | Ret | x : word | |
| | | Rets | xs : listword | |
| | | RetVoid | ||
| | | Unreachable | ||
| | | UB | why : term | |
| | | Trap | why : reason | |
| | | Unchecked | why : reason | |
| | | OutOfFuel |
The prepared module
A function the module names: one with a body in the core subset, or one outside it, kept only by its name n.
The global environment: the module ready to run (module_prepared/2's Fns and Globals, each global's name with its pointer), and the runtime's free that a call of libc's free runs: Memory.v's free_res in the interpreter, R.free of a Runtime R (Run.v's Driver, Machine.v).
| ge_module | : module | |
| ge_globals | ||
| ge_free |
The function or outside function of the module items named n, not looking through aliases.
The target of the alias named n among the module items, if there is one.
The function a call of the name n runs, as get_assoc(F, Fns, Fn) finds it in the prepared module's Fns: a function by its own name, or an alias of one by the alias's name.
Values
As value_x/5: the IR value v read at class k in the environment e. A constant at p is a pointer with no capability; a symbol the module does not define is ptr(0, ext(Name)), a pointer with an external capability.
f applied to every element of xs, or None if it fails on any.
f applied to the elements of xs and ys pairwise, or None if it fails on any pair or the lists differ in length.
As flight_arg_x/4: an argument of a flight's address computation. An address projection takes the low 64 bits of a pointer's address; any other argument is read at class l.
As flight_cap/4: the capability a flight gives its pointer. A lower source copies its pointer's capability, and so the object's identity; none gives no capability.
Frames and configurations
Where a frame is in its function:
Snapshotxs l: a variadic callee about to take the snapshot of its arguments xs, an allocation, before entering its entry block l;Enterl from: about to run block l, entered from the block from (None for the entry); entering costs one unit of fuel, and with none left the run isOutOfFuel, asblock_outcome/7says;Execl insts j: inside block l, with the instructions insts and then the jump j left to run.
| | | Snapshot | ||
| | | Enter | ||
| | | Exec |
One invocation of a function: the function, its environment of temporaries, each named local with its object's base once allocated, the reason it is doomed if an early check_ahead failed, and where it is.
How a suspended caller reads its callee's outcome: the call's Res, the temporaries it binds, and for a call through the generic entry the caller's and callee's word classes (indirect_call_conv/7's conv(CallerWords, CalleeWords)).
| pd_res | ||
| pd_conv |
A configuration: the running frame, the suspended callers innermost first, each with how it reads the result, the memory, and the fuel left (the table above).
The frame fr moved to the point pt, all else kept.
The new frame of a function f called with the words xs, as call_outcome/8 starts it: parameters bound to the first arguments as they are, each local declared and not yet allocated, at the entry block; a variadic callee first takes the snapshot of all its arguments (entry_snapshot/6). None if there are fewer arguments than parameters or f has no blocks.
The entry argument snapshot
The name of the environment's hidden entry that holds the pointer to a variadic callee's argument snapshot, which va_args reads.
As snapshot_values/5: the memory m with the argument words xs stored from address a on, 8 bytes apart, each as p if it is a pointer (its address in the bytes, its capability in the aux), else as l. The stores are into a fresh object of 8 bytes per argument, so their checks pass.
The words snapshot_values/5 can store: integers and pointers.
The block labelled l among bs.
As phis_env/6: the environment after a block's phis, entered from the block from. Each phi reads its value for the edge from that predecessor in the environment e0 as it was on entry, so they all bind at once. An entry block (from None) has no phis.
As invalidate_locals/3: an invocation's named locals end with it. Their identities, bytes and aux stay; their bounds become empty, so any later access through them traps.
Calls
As function_check_res/3: the pointer p designates a function only with that function's capability at its exact callable address; otherwise the check traps. A pointer with an external capability has no answer here.
As result_words/2: the classes of the words a call site reads.
The kinds of word a call passes or returns: integer, pointer, single float and double float.
| | | IntWord | ||
| | | PtrWord | ||
| | | SingleWord | ||
| | | DoubleWord |
As word_class/2: the kind of word a class k is. Classes w and l are the same word; p, s and d are each their own.
Whether two word kinds are the same.
The -> condition of indirect_call_conv/7: both lists of words have word classes, and the same ones.
As generic_word/4: the word x written at class from and read at class to through the generic entry. An integer read as a pointer has no capability; a pointer read as an integer or a float is its address.
What converting a call through a function pointer gives: a trap, or the converted arguments ys and, if the result words need converting too, the caller's and callee's word classes.
| | | ConvTrap | why : reason | |
| | | Converted |
As indirect_call_conv/7: a call through a pointer to f, reading its result as r, with arguments args whose values are xs. It traps if the caller passes fewer arguments than f has parameters or reads more result words than f returns; otherwise each argument is read as f's parameter through the generic entry (generic_word).
As outcome_words/2: the words a returning outcome returns; None for one that does not return.
The returning outcome that returns the words ys.
As converted_outcome/4: the callee's outcome o as a call site that reads the word classes caller reads it, each word converted from the callee's class by generic_word. An outcome that does not return passes unchanged.
As call_outcome_result/5 for a returning outcome o: the caller's environment e with the returned words bound as its Res r reads them.
Instructions
What one instruction does: continue with an environment, locals and memory; stop with an outcome; call a function with the words xs, the caller to read the result as pd says; allocate for the request q, going on as k says of the new base; or call a function outside the core subset.
As res_result/5: an operation's result r bound to the temporary d, or its trap or unchecked reason as the outcome.
As store_res_result/4: a store's result r, going on with nothing bound, or its trap or unchecked reason as the outcome.
As width_value_class/2 (filc_amd64.pl): the class at which a raw store of width w reads the value it stores.
The name of libc's free, the one external call the model runs.
A call of a name f the module does not define as a function: inst_result/5's call of global(F). Data the module defines is not a function, and the call traps, as native code's function check does. Otherwise it is external_call_outcome/9: only libc's void freep is modeled, through the runtime's free, the genv's ge_free; any other external call is stuck until Campaign 4 gives it a contract.
As inst_result/5 for the core subset: what the instruction i does in the frame fr with memory m. None where the interpreter throws or fails, such as a temporary read before it is assigned or an access through an external capability.
As doomed_result/5: an instruction's result out, adjusted for doom, with the frame's doom after it. A check_ahead whose early bytes fail yields its pointer and dooms the run (docs/schedule.md). From then on it must reach, through ahead-transparent instructions only, an access whose reference check fails: an unchecked raw access there is the program's trap, made early. A renew of another object (alias) or a wrong limit stays a broken pass.
Steps
What one step gives:
Nextc, the next configuration;Doneo, the run's outcome;Freshq k, an allocation of the request q, whose next configuration iskbasefor any basefreshin the memory (step_relmakes that choice nondeterministic);Stuckwhy, where the interpreter throws or fails (a call to an external symbol, an access through an external capability, a temporary read before it is assigned, an ill-classed word); the step relation has no step there.
The step result r, or stuck with the label why where there is none.
The configuration after the frame fr, in memory m, moved on.
The end of the running invocation of the configuration c with the returning outcome o (call_outcome/8 after block_outcome/7, then the caller's call_outcome_result/5): its locals end, and the caller, if any, reads the result and goes on; with no caller, o is the run's outcome.
As edge_outcome/7: taking the edge from the block from to the target t. A poll edge reached while doomed means the early check was not anticipated, an unchecked outcome.
As result_outcome/6 and jump_outcome/7: the step of the jump j that ends block l, a goto, a branch on a word, or a return. A doomed frame may not return: its early check was not anticipated.
The step after an instruction's result out in block l, with the instructions rest and the jump j left and the doom doomed after it: on in the block, the outcome, into a callee (the caller suspended on top of c's callers), or an allocation.
entry_snapshot/6 and args_snapshot/4: no arguments is a null pointer; otherwise a fresh 16-aligned object holds them, and its pointer is read-only.
block_outcome/7: fuel first, then the block's phis.
insts_result/5: a doomed run stops at the first instruction that is not ahead_transparent.
The step relation
The configuration c' may follow c. An allocation's base is any that is fresh in c's memory; the interpreter's choice is one.
The run in the configuration c ends now with the outcome o.
The configuration c' follows c after any number of steps of step_rel.