rocq/theories/Buffer.v
A program with a heap object, proved never to panic: rocq/programs/buffer.c, which allocates a buffer of four longs (__builtin_alloca, which Fil-C places in the collected heap), fills it, then sums p[i % 4] for i from 0 while i < n, for every int n (BufferCode.v, checked current by rocq/programs.sh). BufferLogic.v proves the same theorem through the Iris program logic.
The proof is Cycle.v's: an assertion at each of the function's 32 points (buffer_assert), and one step from any configuration where it holds is safe and reaches a point where its assertion holds (buffer_safe). What is new is the object. The run chooses its base, so the assertions speak of any base b that the allocation could return (fresh): from the allocation on, the temporary v4 holds a pointer to b with b's own capability, and b's region is a live heap object of 32 bytes. The access checks then pass because each address is b plus 0 to 24.
A safepoint leaves the object as it is: the collector changes no region (Runtime.v's collector_live), and Fil-C's runtime frees nothing by itself. Only code a program brings in, a signal handler it installs or a thread it starts, could free the buffer under it, and buffer calls no external at all. So the theorem holds for every runtime with no premise.
Decisions
| Decision | Why |
|---|---|
| no premise about safepoints | the theorem once took safepoint_keeps_live, when Runtime.v's safepoint stood for signal handlers too; a handler or a thread is the program's code, and Fil-C's collector frees nothing, so the safepoint axiom is now the collector's alone (collector_live) |
| the assertions say nothing of the buffer's bytes | the program reads them only as longs it adds; any value is safe, so neither the fill nor a safepoint's bytes matter |
an allocation's base is any fresh one | Panics.v's never_panics_by_invariant gives every base the runtime may return; the proof uses only that it is positive and that the object ends at or below 2^64 |
Integer arithmetic on an unknown stays folded when a step is computed (Symbolic.v).
The program
The checked module, decoded from the fixture buffer_term (buffer_decodes).
The fixture decodes to buffer_module.
Explore proof 2 tactics
Initial goal
vm_compute
reflexivity
No remaining goals.
Qed
✓ Proof checked and closed by Rocq.
Source script
The function buffer, the module's only item.
The module's one global, the function buffer.
The module's initial memory, which holds no object.
Laying out the module succeeds, with buffer_globals and buffer_mem.
Explore proof 3 tactics
Initial goal
assert H : option_mapfst initial_memorybuffer_module = Somebuffer_globals by vm_compute ; reflexivity
unfold buffer_mem
destruct initial_memorybuffer_module as g m | ; inversion H ; reflexivity
No remaining goals.
Qed
✓ Proof checked and closed by Rocq.
Source script
The genv of the module with any free; the program calls none.
buffer is a function of the core subset in the module.
Explore proof 3 tactics
Initial goal
intros free
vm_compute
reflexivity
No remaining goals.
Qed
✓ Proof checked and closed by Rocq.
Source script
Any arguments whose first word is the integer n start buffer at its entry block with n as its parameter v0.
Explore proof 1 tactics
The points
Block l of buffer after its first j instructions.
Every point of buffer, numbered: the entry block start, before and after its allocation (0 to 2); the fill loop's head L6 (3 to 6) and body L7 (7 to 15); the sum loop's head L24 (16 to 19) and body L25 (20 to 29); and the exit L27 (30, 31).
The assertions
The buffer at the base b, in the environment e and the memory m: v4 holds a pointer to b with b's capability, b is positive, the object ends at or below 2^64, and its region is a live heap object of 32 bytes.
The temporary x holds a pointer into the buffer at b with 8 bytes after it.
What holds after the first j instructions of the fill loop's body, which stores i + 1 at pi: the index pr.1 below 4, then v14 and v15, the offset, the pointer v16, the next index v18 and its long v19, and the checked proof chk0.
What holds after the first j instructions of the sum loop's body, as Cycle.v's body_assert with the buffer for the array.
What holds at point k of buffer_points besides the buffer: at each loop's head and branch, its index and the comparison's answer; in each body, fill_assert or sum_assert; at the exit, the sum.
What holds at point k in the memory m: nothing before the allocation; from it on, the buffer at some base b and the point's own assertion.
A configuration of buffer where the assertions hold: at point k, with no caller, the input v0 an integer, and the assertion of k.
Each step
Unfold the assertion at a known point.
Unfold the assertion at a known point in the hypothesis H.
The point of the next configuration is point n of buffer_points or a later one: find it by computing each in turn.
The next configuration is in buffer_inv: at its point, with the facts shown.
The allocation's result is safe: its extent is nonnegative, and every fresh base gives a configuration where the assertions hold.
One point of buffer_safe: the point from Hk, an index into the points pts, the assertion Ha taken apart, the step taken by its rule (take_step), and its result shown safe.
One step from a configuration where the assertions hold is safe_step for them.
Proof 18 steps Qed
A call with an integer first argument starts where the assertions hold, with any fuel.
Explore proof 5 tactics
Initial goal
intros n fuel
exists O, EnterTAtom"start" None, TAtom"v0", WIntn
split ; reflexivity |
split ; vm_compute ; reflexivity |
split ; exists n ; split ; reflexivity | exact I | exact I
No remaining goals.
Qed
✓ Proof checked and closed by Rocq.
Over any runtime
What R does at a safepoint keeps the assertions: it changes only the memory, and no region (R.collector_live), so the buffer's region stays.
Explore proof 16 tactics
Initial goal
intros c s s′ m′ k & pt & e & Ec & Hk & Hin & Ha Hsp
exists k, pt, e
rewrite Ec at 1
unfold P.RM.with_mem ; cbn [ c_frame c_callers c_mem c_fuel ]
split ; reflexivity |
split ; exact Hk |
split ; exact Hin |
destruct k as | | k ; exact I | exact I |
destruct Ha as b & Hv & Hpos & Hle & Hr & Hp
exists b
split ; | exact Hp
split ; exact Hv |
split ; exact Hpos |
split ; exact Hle |
rewrite R.collector_live_ _ _ _ Hsp
exact Hr
No remaining goals.
Qed
✓ Proof checked and closed by Rocq.
Source script
Explore proof 12 tactics
Initial goal
apply P.never_panics_by_invariant_ _ _ buffer_globals buffer_mem buffer_fn buffer_inv
5 openfocused goal
focused goal
focused goal
focused goal
–exact buffer_initial
4 opendeferred goal
deferred goal
deferred goal
deferred goal
–apply buffer_find
3 opendeferred goal
deferred goal
deferred goal
–intros xs n & rest & →
3 opendeferred goal
deferred goal
eexists
4 opendeferred goal
deferred goal
split ; apply buffer_call_frame |
3 opendeferred goal
deferred goal
intros fuel
3 opendeferred goal
deferred goal
apply buffer_start
2 opendeferred goal
deferred goal
–intros c s Hc _
2 opendeferred goal
apply buffer_safe , Hc
deferred goal
–intros c s s′ m′ Hc _ _ Hsp
eapply buffer_safepoint ; eassumption
No remaining goals.
Qed
✓ Proof checked and closed by Rocq.
Source script
The theorem for the interpreter's runtime.
buffer never panics under the interpreter's runtime, an instance of buffer_never_panics.
Explore proof 1 tactics
Initial goal
apply InterpBuffer.buffer_never_panics
No remaining goals.
Qed
✓ Proof checked and closed by Rocq.