Relational Separating Conjunction #
A separating conjunction on relational assertions RPre = Heap → Heap → Prop, and
the frame rule it supports.
Main definitions #
HeapDisj,heapUnion(⊎),HeapSplit— the partial commutative monoid of heapssepPre(∗) — separating conjunction of relational preconditionssepPost(∗ᵖ) — separating conjunction of a relational postcondition with a frameemp— the assertion holding only of the pair of empty heapsFrameable c D— the separation-logic locality ofcon heaps satisfyingDr_frame_sep— the frame rulenot_frameable_get— reading is not local at a heap that does not own the location
Design #
sepPre P Q splits both heaps of the pair, and splits them independently:
(P ∗ Q) h₁ h₂ asserts that h₁ = a₁ ⊎ b₁ and h₂ = a₂ ⊎ b₂ for some splits with
P a₁ a₂ and Q b₁ b₂. The two splits are unrelated to each other; in particular the
domains a₁ and a₂ need not coincide.
The alternative — a lockstep split, additionally demanding a₁.data.keys = a₂.data.keys
— is a strictly stronger assertion. As a precondition that is harmless, but the frame
rule needs the same connective in the postcondition, where the extra conjunct becomes an
obligation: the two runs must end with equal domains on the non-frame part. rHoare
couples the two runs through a relation on (value, heap) pairs and delivers exactly
that relation, so a lockstep postcondition would have to be established from Ψ, which
in general does not mention domains. Independent splits keep the frame rule free of that
obligation, and the frame parts b₁, b₂ are still related by the frame assertion, so
no information about the frame is lost.
The locality side condition #
rHoare is a global triple: it quantifies over whole heaps, and SPComp has no fault
state, so a computation cannot be prevented from reading or writing outside a region by
typing alone. The frame rule therefore cannot be side-condition-free; the side condition
is Frameable c D, the semantic locality of separation logic:
Frameable.frame— runningconh ⊎ hfis running it onhand re-attachinghf;Frameable.confined—callocates no location outside the domain it was given.
Both are relative to a domain predicate D, because SPComp.get l and SPComp.set l v
are local only on heaps that already contain l — that hypothesis is exactly what the
points-to assertion l ↦ _ supplies in a small-footprint semantics. The frame rule
requires D to follow from the precondition.
Unlike DependsOn, Frameable constrains the computation, not the assertion, so the
frame assertion is arbitrary: eqPre and agreeOff, which have no finite footprint and
so are outside the reach of r_frame_local, are framable here.
Relation to the location-based frame rules of CatCryptCore.Relational.Frame #
r_frame_sep does not subsume r_frame_local, r_frame_of_preservesNothing or
r_frame_agreeOff, and none of them subsumes it. Two independent obstructions separate
∗ from the location-based family.
The hypothesis on the computations. All three location-based rules ask only for
PreservesOutside, a condition on writes. Frameable is a condition on writes and on
reads: the frame property fails as soon as a computation can observe a location outside
the heap it was given. not_frameable_get is the witness — SPComp.get l satisfies
PreservesOutside (SPComp.get l) ∅, the strongest hypothesis any of the three rules can
demand, and is not Frameable (SPComp.get l) (fun _ => True). So the three rules apply to
computations for which r_frame_sep has no side condition to offer, at any choice of
frame assertion.
The reading of the assertions. (P ∗ R) h₁ h₂ says P and R hold of parts of
h₁ and h₂; (P ⋀ R) h₁ h₂ says both hold of the wholes. Neither implies the other,
and a whole-heap precondition determines no split, so a location-based conclusion cannot
be re-derived by instantiating ∗ even where Frameable does hold.
In the other direction, r_frame_set_sync_eqPre frames whole-heap equality of the
untouched region across computations that write. r_frame_local cannot state that frame
(no finite footprint), r_frame_of_preservesNothing forbids the writes, and
r_frame_agreeOff frames only agreeOff L, not an arbitrary assertion on the untouched
region. agreeOff_of_sepPost_eqPre is the one-way reading of a ∗ᵖ conclusion back as a
whole-heap agreeOff assertion.
The four rules therefore cover four points:
r_frame_local— finite-footprint frame, writes confined away from it, reads free.r_frame_of_preservesNothing— arbitrary frame, no writes, reads free.r_frame_agreeOff— co-finite frame of the fixed shapeagreeOff L, writes insideL, reads free.r_frame_sep— arbitrary frame, writes free, reads confined to the owned region.
References #
- Bluebell (POPL 2025) —
P * Qseparating conjunction for independence - O'Hearn, Reynolds, Yang — Separation logic; locality = safety monotonicity + the frame property
Heap splitting #
Two heaps have no location in common.
Equations
- CatCrypt.Relational.HeapDisj h₁ h₂ = h₁.data.Disjoint h₂.data
Instances For
Union of two heaps. Left-biased on the overlap, so it is the disjoint union
exactly when the arguments are HeapDisj.
Equations
- One or more equations did not get rendered due to their size.
Instances For
HeapSplit h h₁ h₂ : h is the disjoint union of h₁ and h₂.
Equations
- CatCrypt.Relational.HeapSplit h h₁ h₂ = (CatCrypt.Relational.HeapDisj h₁ h₂ ∧ h = CatCrypt.Relational.heapUnion h₁ h₂)
Instances For
Algebra of disjointness and union #
Algebra of splits #
Splitting is symmetric.
The empty heap is a unit for splitting.
Separating conjunction #
Separating conjunction of relational preconditions: each of the two heaps splits,
and P relates the two left parts while Q relates the two right parts. The splits
on the two sides are independent.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Separating conjunction of relational preconditions: each of the two heaps splits,
and P relates the two left parts while Q relates the two right parts. The splits
on the two sides are independent.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Separating conjunction of a relational postcondition with a frame assertion: the postcondition constrains the parts the computations ran on, the frame the parts they did not.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Separating conjunction of a relational postcondition with a frame assertion: the postcondition constrains the parts the computations ran on, the frame the parts they did not.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The assertion of the pair of empty heaps: the unit of ∗.
Equations
- CatCrypt.Relational.emp h₁ h₂ = (h₁ = CatCrypt.Core.Heap.empty ∧ h₂ = CatCrypt.Core.Heap.empty)
Instances For
Algebra of ∗ #
Locality of computations #
Separation-logic locality of c, relative to the heaps satisfying D.
frame is the frame property: extending the heap by a disjoint hf extends every
outcome by the same hf, with the same probabilities. confined says c allocates
no location it was not given, which is what keeps the extension disjoint.
- frame (h hf : Core.Heap) : D h → HeapDisj h hf → c (heapUnion h hf) = (c h).bind fun (p : α × Core.Heap) => Prob.SDistr.pure (p.1, heapUnion p.2 hf)
Running on an extended heap is running on the small heap and re-attaching.
- confined (h : Core.Heap) (a : α) (h' : Core.Heap) : D h → (c h) (some (a, h')) ≠ 0 → ∀ id ∈ h'.data, id ∈ h.data
Outcomes stay inside the domain of the initial heap.
Instances For
Confinement turns disjointness of the initial heap from a frame into disjointness of every outcome from that frame.
Support extraction #
A nonzero outcome of a bind comes from a nonzero outcome of each stage.
Frameable instances #
Every heap-independent computation is local, with no constraint on the heap.
The heaps that own location l: the domain predicate of get l and set l v.
Equations
- CatCrypt.Relational.Owns l h = (l.id ∈ h.data)
Instances For
Reading a location is local on the heaps that own it. Ownership is needed: on a
heap without l the read would see the frame's value instead of default.
Reading a location is not local at an arbitrary heap. SPComp.get l writes nothing,
so PreservesOutside (SPComp.get l) ∅ holds, yet the frame property fails on a heap that
does not own l together with a frame that does: the read sees the frame's value. The
witness needs a location type with a non-default element.
Writing a location is local on the heaps that own it. Ownership is needed for confinement: writing to an unowned location allocates it, and could collide with the frame.
Locality composes through bind, provided the first stage lands in the domain predicate of the second.
The frame rule #
The frame rule for the relational separating conjunction.
The frame assertion R is arbitrary — no footprint condition. What is required is
that the two computations are local (Frameable) on the heaps their precondition
describes, which is the semantic content of separation logic's "the triple only talks
about the heap it owns".
Framing assertions with no finite footprint #
Whole-heap equality is framable. r_frame_local cannot frame it: DependsOn
requires a LocSet, which is finite, and eqPre constrains every location.
Agreement outside a finite set is framable. Its footprint is co-finite, so it too
is out of reach of r_frame_local.
A worked instance #
Writing the same value to the same owned location on both sides keeps the two heaps
equal. The precondition carries the ownership of l that frameable_set needs.
The synchronised write, framed by an arbitrary assertion on the rest of the heap.
The synchronised write framed by whole-heap equality of the untouched region: the
frame assertion here has no finite footprint, so r_frame_local does not apply to it.
Framing whole-heap equality yields agreement outside the region the computations ran on: the untouched parts are literally the same heap, so the final heaps can differ only inside the two owned domains, which are finite.