rocq/theories/Libio.v
A capability-backed memory view of the pinned byte stdout (milestone 3c).
Offsets are measured with the Fil-C headers, not the host's FILE. This is a view of the buffer/lock roots, not a complete predicate for native FILE validity or a proof of libio's execution. In particular, vtable dispatch, TLS initialization, protocol scheduling and native prefix coverage remain trusted realization obligations.
The selected malloc forwards to zgc_alloc: runtime refusal does not return NULL to _IO_file_doallocate. The generic libio NULL fallback and the pin's terminal resource ending are distinct.
Source-selected capacity and buffer paths
The byte capacity of stdout's allocated buffer, given whether stat succeeded and the block size it reported. As filedoalloc.c, for the selected stdout descriptor 1 and the pinned x86-64 _STATBUF_ST_BLKSIZE configuration: use a positive st_blksize smaller than BUFSIZ (8192) only after a successful stat; otherwise retain BUFSIZ. This is NOT the capacity function for arbitrary descriptors.
The capacity is always positive and at most 8192 bytes, so the buffer request is never too big.
Explore proof 8 tactics
Initial goal
intros
unfold file_capacity
destruct stat_ok && 0 <? block_size && block_size <? 8192 eqn: E ; | lia
apply andb_true_iff in E as E Hhi
apply andb_true_iff in E as _ Hlo
apply Z.ltb_lt in Hhi
apply Z.ltb_lt in Hlo
lia
No remaining goals.
Qed
✓ Proof checked and closed by Rocq.
Source script
Where stdout's buffer lives: no buffer yet, the one-byte buffer embedded in the FILE (_shortbuf), or a heap buffer of size bytes at base.
| | | NoBuffer | ||
| | | ShortBuffer | ||
| | | HeapBuffer | base size : Z |
Why libio picks a buffer: one already exists, the stream is unbuffered, malloc returned base, or malloc returned NULL. These causes describe libio's control flow, not R.alloc's result. MallocNull is useful for generic libio, but is excluded at this pin. Unbuffered is for a byte stream: wide orientation takes another path.
| | | Existing | ||
| | | Unbuffered | ||
| | | MallocSuccess | base : Z | |
| | | MallocNull |
Generic libio's buffer choice, from the old storage for a cause to the new: an existing buffer is kept; with no buffer, an unbuffered stream or a NULL malloc falls back to the embedded byte, and a successful malloc gives a heap buffer of the given capacity.
The pin's buffer choice: a buffer_choice whose cause is not a NULL malloc, since this glibc's malloc ends the run on refusal instead.
Measured pointer slots and writable roots
The pointers, with their capabilities, through which stdout's state is reached: the FILE, its lock, and errno.
The writable spans of the FILE, lock and errno. stdfiles.c allocates _IO_FILE_plus: FILE (216) plus its vtable pointer (8). NPTL's lock is { int lock; int cnt; void *owner; }. errno is a separate four-byte TLS cell. These spans are authority supplied by the caller of the trusted boundary, never inferred just from the addresses stored in FILE.
A pointer to the FILE's embedded one-byte buffer (_shortbuf, at offset 131), with the FILE's own capability.
The span of the buffer the storage names: none, the embedded byte, or the heap buffer with an Obj capability for its base.
All of stdout's writable roots: the fixed_roots, plus a heap buffer's span. The embedded byte needs no root of its own: it is inside the FILE's.
In memory m, the FILE's _IO_buf_base and _IO_buf_end slots (offsets 56 and 64) hold, as a pointer_slot with address bytes and aux, the start and end of the storage's buffer, or null when there is none. The end pointer keeps the buffer's capability.
Memory m holds stdout's selected state for roots r and storage:
- every root of
memory_rootsis awritable_span; - the whole FILE and its three pointer slots (56, 64 and the lock slot 136) are readable, 8-aligned; the lock and errno writable;
- the lock slot points at the lock, and
buffer_slotsholds; - a heap buffer is nonempty and a live heap object.
This view supplies actual address bytes and aux, not a detached logical list of roots. Heap kind, liveness and buffer bounds are checked independently of the two pointer slots. It intentionally does not describe ownership, flags, orientation, pending bytes or lock state. These are selected roots, NOT a complete puts footprint: storing THREAD_SELF in lock.owner needs another capability source.
The stdio_world this view gives: a usable stdout whose roots are memory_roots.
Where stdout_memory holds, its world is stdio_ready, as puts_pre requires.
Explore proof 2 tactics
Initial goal
intros m r storage H _
split ; reflexivity | exact H
No remaining goals.
Qed
✓ Proof checked and closed by Rocq.
Source script
Where the fixed roots are writable, the embedded buffer's capability is available from them and its byte is under their write authority: it neither allocates nor expands root authority. Its one byte is already inside the FILE root, with the SAME capability.
Explore proof 16 tactics
Initial goal
intros m r Hroots
split
2 openfocused goal
–right
2 opendeferred goal
left
2 opendeferred goal
exists mkSpanfile_ptrr 224
2 opendeferred goal
simpl
2 opendeferred goal
auto
deferred goal
–intros a Ha
left
exists mkSpanfile_ptrr 224
inversion Hroots as | s rest Hw Hrest ; subst
split ; simpl ; auto |
split ; exact Hw |
unfold span_contains , short_ptr in *
simpl in *
lia
No remaining goals.
Qed
✓ Proof checked and closed by Rocq.
Installing a buffer from the null-buffer state
This is _IO_setb's selected no-old-buffer case. The two pointer stores change bytes AND aux; the four-byte flags store changes only _IO_USER_BUF. No release is hidden in this construction. Selection of this path still needs NoBuffer and backend/mode premises.
Explore proof 11 tactics
Initial goal
intros m r storage s Hs Hp Hend
unfold buffer_slots , install_buffer
rewrite Hs
split
2 openfocused goal
–apply pointer_slot_bytes_disjoint ; lia | right ; simpl ; lia |
2 opendeferred goal
eapply pointer_slot_pointer_disjoint with p := mkPtrptr_addrspan_ptrs + span_sizes ptr_capspan_ptrs ; left ; lia |
2 opendeferred goal
apply pointer_slot_written
2 opendeferred goal
exact Hp
deferred goal
–apply pointer_slot_bytes_disjoint ; lia | right ; simpl ; lia |
apply pointer_slot_written_ _ mkPtrptr_addrspan_ptrs + span_sizes ptr_capspan_ptrs
exact Hend
No remaining goals.
Qed
✓ Proof checked and closed by Rocq.
Source script
Explore proof 5 tactics
Initial goal
intros m r storage s Hs
unfold install_buffer
rewrite Hs
rewrite stored_bytes_value by lia
reflexivity
No remaining goals.
Qed
✓ Proof checked and closed by Rocq.
Source script
Explore proof 6 tactics
Initial goal
intros m r born a Hroots Ha
left
exists mkSpanfile_ptrr 224
inversion Hroots as | s rest Hw Hrest ; subst
split ; simpl ; auto |
split ; auto
No remaining goals.
Qed
✓ Proof checked and closed by Rocq.
Fresh allocation leaves the null slots and every caller root valid. It does not install its address into FILE.
Explore proof 14 tactics
Initial goal
intros m r q base Hroots & Hfile & Hfields & Hlock & Herr & Hslot & Hnull & _ Hnew
split
2 openfocused goal
–eapply Forall_impl ; | exact Hroots
2 opendeferred goal
intros span Hspan
2 opendeferred goal
eapply allocated_old_span ; eauto
deferred goal
–split ; eapply allocated_old_access ; eauto |
split
2 openfocused goal
+eapply Forall_impl ; | exact Hfields
2 opendeferred goal
intros offset Hfield
2 opendeferred goal
eapply allocated_old_access ; eauto
deferred goal
+split ; eapply allocated_old_access ; eauto |
split ; eapply allocated_old_access ; eauto |
split ; exact Hslot |
auto
No remaining goals.
Qed
✓ Proof checked and closed by Rocq.
Source script
Installation preserves the checked fixed fields and lock pointer; target root bounds and heap lifetime are independent obligations. In particular, matching slot bytes alone cannot prove this view.
Proof 26 steps Qed
Allocation evidence over any runtime
Buffer allocation over the caller's exact machine instance.
The one allocation request stdout's buffer setup makes: 16-aligned, of file_capacity bytes.
When the runtime, ready in state s and memory m, grants the buffer request at base: base was no object of m, the runtime stays ready, the allocation is an effects path with born list base, and the new object's Obj capability and its capacity bytes are the call's authority. No theorem turns a logical HeapBuffer into an allocated object. This theorem starts with the runtime's actual successful answer. Installing its pointer slots and native flags is still a separate local-effects obligation.
Proof 37 steps Qed
If the runtime refuses the buffer request, the call may end as OutOfMemory (resource_failure).
Explore proof 7 tactics
Initial goal
intros stat_ok block_size s H
pose proof file_capacity_boundsstat_ok block_size
eapply E.failure_oom with align := 16 size := file_capacitystat_ok block_size
3 openfocused goal
focused goal
–split ; reflexivity
2 opendeferred goal
deferred goal
–unfold max_allocation_size
2 opendeferred goal
lia
deferred goal
–exact H
No remaining goals.
Qed
✓ Proof checked and closed by Rocq.
Source script
The buffer request never fails as TooBig: its capacity is at most 8192 bytes.
Explore proof 5 tactics
Initial goal
intros stat_ok block_size s H
inversion H as | align size _ → Hbig
pose proof file_capacity_boundsstat_ok block_size
unfold max_allocation_size in Hbig
lia
No remaining goals.
Qed
✓ Proof checked and closed by Rocq.
Source script
Explore proof 30 tactics
Initial goal
intros s m r requests Hroots _
set a := ptr_addrfile_ptrr
set p := short_ptrr
set m1 := store_auxstore_bytesm a + 56 8 ptr_addrp a + 56 ptr_capp
set m2 := store_auxstore_bytesm1 a + 64 8 ptr_addrp + 1 a + 64 ptr_capp
set m3 := store_bytesm2 a 4 Z.lorload_bytesm a 4 1
change E.effectsfixed_rootsr fixed_rootsr [ ] requests s m [ ] s m3 [ ]
eapply E.effects_local with m1 := m1
2 openfocused goal
–apply local_store_pointer with p := p
3 openfocused goal
deferred goal
+intros b Hb
3 opendeferred goal
deferred goal
apply file_write_authority ; exact Hroots | simpl in * ; lia
2 opendeferred goal
deferred goal
+right
2 opendeferred goal
left
2 opendeferred goal
exists mkSpanfile_ptrr 224
2 opendeferred goal
simpl
2 opendeferred goal
auto
deferred goal
–eapply E.effects_local with m1 := m2
2 openfocused goal
+apply local_store_pointer with p := mkPtrptr_addrp + 1 ptr_capp
3 openfocused goal
deferred goal
∗intros b Hb
3 opendeferred goal
deferred goal
apply file_write_authority ; exact Hroots | simpl in * ; lia
2 opendeferred goal
deferred goal
∗right
2 opendeferred goal
left
2 opendeferred goal
exists mkSpanfile_ptrr 224
2 opendeferred goal
simpl
2 opendeferred goal
auto
deferred goal
+eapply E.effects_local with m1 := m3
2 openfocused goal
∗apply local_store_bytes
2 opendeferred goal
intros b Hb
2 opendeferred goal
apply file_write_authority ; exact Hroots | simpl in * ; lia
deferred goal
∗constructor
No remaining goals.
Qed
✓ Proof checked and closed by Rocq.
Source script
Explore proof 29 tactics
Initial goal
intros s m r requests born base size Hroots _ Hborn
set a := ptr_addrfile_ptrr
set m1 := store_auxstore_bytesm a + 56 8 base a + 56 Objbase
set m2 := store_auxstore_bytesm1 a + 64 8 base + size a + 64 Objbase
set m3 := store_bytesm2 a 4 Z.landload_bytesm a 4 −2
change E.effectsfixed_rootsr fixed_rootsr [ ] requests s m born s m3 born
eapply E.effects_local with m1 := m1
2 openfocused goal
–apply local_store_pointer with p := mkPtrbase Objbase
3 openfocused goal
deferred goal
+intros b Hb
3 opendeferred goal
deferred goal
apply file_write_authority ; exact Hroots | simpl in * ; lia
2 opendeferred goal
deferred goal
+right
2 opendeferred goal
right
2 opendeferred goal
right
2 opendeferred goal
exists base
2 opendeferred goal
auto
deferred goal
–eapply E.effects_local with m1 := m2
2 openfocused goal
+apply local_store_pointer with p := mkPtrbase + size Objbase
3 openfocused goal
deferred goal
∗intros b Hb
3 opendeferred goal
deferred goal
apply file_write_authority ; exact Hroots | simpl in * ; lia
2 opendeferred goal
deferred goal
∗right
2 opendeferred goal
right
2 opendeferred goal
right
2 opendeferred goal
exists base
2 opendeferred goal
auto
deferred goal
+eapply E.effects_local with m1 := m3
2 openfocused goal
∗apply local_store_bytes
2 opendeferred goal
intros b Hb
2 opendeferred goal
apply file_write_authority ; exact Hroots | simpl in * ; lia
deferred goal
∗constructor
No remaining goals.
Qed
✓ Proof checked and closed by Rocq.
Source script
Proof 31 steps Qed
Proof 92 steps Qed
Standalone wrapper for existing buffer clients.
Public branch and representation boundaries
Block sizes from 1 to 8191 after a successful stat are the capacity; 8192 and above, zero, negative, or a failed stat give 8192.
Explore proof 2 tactics
Initial goal
vm_compute
repeat split ; reflexivity
No remaining goals.
Qed
✓ Proof checked and closed by Rocq.
Source script
Capacity 8191 is not rounded, though heap_req reserves 8192.
Explore proof 2 tactics
Initial goal
vm_compute
auto
No remaining goals.
Qed
✓ Proof checked and closed by Rocq.
Source script
Generic libio falls back to the embedded byte when malloc returns NULL.
Explore proof 1 tactics
Initial goal
constructor
No remaining goals.
Qed
✓ Proof checked and closed by Rocq.
Source script
At the pin, that NULL branch is never taken.
Explore proof 3 tactics
Initial goal
intros size storage _ H
apply H
reflexivity
No remaining goals.
Qed
✓ Proof checked and closed by Rocq.
Source script
An unbuffered stdout uses the embedded byte without allocating.
Explore proof 1 tactics
Initial goal
split ; constructor | discriminate
No remaining goals.
Qed
✓ Proof checked and closed by Rocq.
Source script
An existing buffer has priority: no malloc replaces it.
Explore proof 2 tactics
Initial goal
intros size old new H Hchoice _
inversion Hchoice ; subst ; contradiction
No remaining goals.
Qed
✓ Proof checked and closed by Rocq.
Source script
The embedded buffer's pointer has the FILE's capability.
Explore proof 1 tactics
Initial goal
reflexivity
No remaining goals.
Qed
✓ Proof checked and closed by Rocq.
Source script
A buffer slot with the right address bytes but no capability in its aux does not satisfy buffer_slots.
Explore proof 4 tactics
Initial goal
intros m r base size Ha _ _ Hcap _
simpl in Hcap
rewrite Ha in Hcap
discriminate
No remaining goals.
Qed
✓ Proof checked and closed by Rocq.
Source script
Nor does one with the right capability but other address bytes.
Explore proof 3 tactics
Initial goal
intros m r base size Ha _ Haddr _ _
apply Ha
exact Haddr
No remaining goals.
Qed
✓ Proof checked and closed by Rocq.
Source script
Example roots: the FILE at 2048, its lock at 4096, errno at 8192. With the memories below, an inhabited memory view, not a fixture claiming to be native FILE.
A memory with null buffer slots and a capability-bearing lock slot pointing at 4096.
unbuffered_example satisfies stdout_memory with no buffer.
Proof 15 steps Qed
Storing in the two buffer slots BOTH the address bytes (the embedded byte's start 2179 and end 2180) and the FILE's capability in their aux satisfies stdout_memory with the embedded buffer.
Proof 15 steps Qed
An asymmetric capacity exercises installed slots, rounded extent, the nontrivial flag mask, and unchanged lock/errno contents.
Explore proof 26 tactics
Initial goal
split
2 openfocused goal
–constructor
3 openfocused goal
deferred goal
+split ; simpl ; lia |
3 opendeferred goal
deferred goal
exists 2048, mkRegion2048 2272 GlobalData Live
3 opendeferred goal
deferred goal
repeat split ; reflexivity
2 opendeferred goal
deferred goal
+constructor
3 openfocused goal
deferred goal
∗split ; simpl ; lia |
3 opendeferred goal
deferred goal
exists 4096, mkRegion4096 4112 GlobalData Live
3 opendeferred goal
deferred goal
repeat split ; reflexivity
2 opendeferred goal
deferred goal
∗constructor
3 openfocused goal
deferred goal
--split ; simpl ; lia |
3 opendeferred goal
deferred goal
exists 8192, mkRegion8192 8196 Heap Live
3 opendeferred goal
deferred goal
repeat split ; reflexivity
2 opendeferred goal
deferred goal
--constructor ; | constructor
2 opendeferred goal
split ; simpl ; lia |
2 opendeferred goal
exists 16384, mkRegion16384 24576 Heap Live
2 opendeferred goal
repeat split ; reflexivity
deferred goal
–split ; reflexivity |
split ; repeat constructor ; reflexivity |
split ; reflexivity |
split ; reflexivity |
split ; vm_compute ; repeat constructor ; discriminate |
split ; vm_compute ; repeat constructor ; discriminate |
split ; lia |
exists mkRegion16384 24576 Heap Live
repeat split ; reflexivity
No remaining goals.
Qed
✓ Proof checked and closed by Rocq.
Source script
Explore proof 2 tactics
Initial goal
vm_compute
repeat constructor ; discriminate
No remaining goals.
Qed
✓ Proof checked and closed by Rocq.
Source script
Explore proof 10 tactics
Initial goal
split ; | reflexivity
eapply installed_memory
7 openfocused goal
focused goal
focused goal
shelved goal
–exact no_buffer_memory_inhabited
6 opendeferred goal
deferred goal
deferred goal
shelved goal
–reflexivity
4 opendeferred goal
deferred goal
deferred goal
–exact proj1no_buffer_memory_inhabited
3 opendeferred goal
deferred goal
–change 0 ≤ 2179 < 2 ^ 64
3 opendeferred goal
deferred goal
lia
2 opendeferred goal
deferred goal
–change 0 ≤ 2180 < 2 ^ 64
2 opendeferred goal
lia
deferred goal
–exact I
No remaining goals.
Qed
✓ Proof checked and closed by Rocq.
Source script
Explore proof 4 tactics
Initial goal
intros _ & _ & _ & _ & _ & _ & _ & _ & reg & Hreg & _ & Hlife
change SomemkRegion16384 16384 Heap Freed = Somereg in Hreg
injection Hreg as ←
discriminate Hlife
No remaining goals.
Qed
✓ Proof checked and closed by Rocq.
Explore proof 7 tactics
Initial goal
intros Hroots _
rewrite Forall_forall in Hroots
specialize HrootsmkSpanmkPtr16384 Obj16384 8191 ltac:simpl ; auto
destruct Hroots as _ & base & reg & Hcap & _ & _ & Haccess
change Obj16384 = Objbase in Hcap
injection Hcap as ←
discriminate Haccess
No remaining goals.
Qed
✓ Proof checked and closed by Rocq.