Temporary Read-Only Permissions for Separation Logic
Making Separation Logic’s Small Axioms
Smaller
Arthur Charguéraud François Pottier
Gallium seminar Paris, April 10, 2017
Separation Logic: to own, or not to own
Separation Logic (Reynolds, 2002) is aboutdisjointnessof heap fragments.
I what “we” own, versus what “others” own.
Therefore, it is aboutunique ownership. A dichotomy arises:
I Of every memory cell,eitherwe have ownership,orwe don’t.
I If we do, then we canread and writethis cell.
I If we don’t, then we canneither write nor even readthis cell.
From memory cells (and arrays), this dichotomy extends to data structures:
To a (user-defined) data structure,
either we haveno access at all, or we havefull read-write access.
Separation Logic’s read and write axioms
The reasoning rule for writing a cell requires (and returns) a full permission:
set
{l,→v0}(setl v){λy.l,→v} So does the reasoning rule for reading a cell:
traditional read axiom
{l,→v}(getl){λy.[y=v]?l,→v}
They are known as“small axioms”, because they require minimum permission.
But are they as small as they could be?
Isn’t it excessive for reading to require a full permission?
Are there adverse consequences of working with such coarse permissions?
Separation Logic’s read and write axioms
The reasoning rule for writing a cell requires (and returns) a full permission:
set
{l,→v0}(setl v){λy.l,→v} So does the reasoning rule for reading a cell:
traditional read axiom
{l,→v}(getl){λy.[y=v]?l,→v}
They are known as“small axioms”, because they require minimum permission.
terminology:
“permission” = “assertion”
But are they as small as they could be?
Isn’t it excessive for reading to require a full permission?
Are there adverse consequences of working with such coarse permissions?
Separation Logic’s read and write axioms
The reasoning rule for writing a cell requires (and returns) a full permission:
set
{l,→v0}(setl v){λy.l,→v} So does the reasoning rule for reading a cell:
traditional read axiom
{l,→v}(getl){λy.[y=v]?l,→v}
They are known as“small axioms”, because they require minimum permission.
But are they as small as they could be?
Isn’t it excessive for reading to require a full permission?
Are there adverse consequences of working with such coarse permissions?
The problem
Suppose we are implementing an abstract data type of (mutable) sequences.
Here is a typical specification of sequence concatenation:
{s1{SeqL1 ?s2{SeqL2} (append s1s2)
{λs3. s3{Seq(L1++L2) ?s1{SeqL1 ?s2{SeqL2}
Although correct, this style of specification can be criticized on several grounds:
I It is a bitnoisy.
I It requires the permissionss1{SeqL1 ?s2{SeqL2to be threaded throughout the proofofappend.
I It actuallydoes not guarantee thats1ands2are unmodified.— (next slide)
I It requiress1ands2to bedistinctdata structures.— (slide after next)
The problem
Suppose we are implementing an abstract data type of (mutable) sequences.
Here is a typical specification of sequence concatenation:
{s1{SeqL1 ?s2{SeqL2} (append s1s2)
{λs3. s3{Seq(L1++L2) ?s1{SeqL1 ?s2{SeqL2}
Although correct, this style of specification can be criticized on several grounds:
I It is a bitnoisy.
I It requires the permissionss1{SeqL1 ?s2{SeqL2to be threaded throughout the proofofappend.
I It actuallydoes not guarantee thats1ands2are unmodified.— (next slide)
I It requiress1ands2to bedistinctdata structures.— (slide after next)
The problem, facet 3: temporary modifications are not forbidden
Repeating “s{SeqL” in the pre- and postcondition can be deceiving.
This doesnotforbid changes to theconcretedata structure in memory.
Here is a function that really just reads the data structure:
{s{SeqL}(length s){λy.s{SeqL ?[y=|L|]}
And a function that actually modifies the data structure:
{s{SeqL ?[|L| ≤n]}(resize s n){λ().s{SeqL}
The problem, facet 3: temporary modifications are not forbidden
Repeating “s{SeqL” in the pre- and postcondition can be deceiving.
This doesnotforbid changes to theconcretedata structure in memory.
Here is a function that really just reads the data structure:
{s{SeqL}(length s){λy.s{SeqL ?[y=|L|]} And a function that actually modifies the data structure:
{s{SeqL ?[|L| ≤n]}(resize s n){λ().s{SeqL}
The problem, facet 4: sharing is not permitted
The specification ofappendrequiress1ands2to bedistinctdata structures:
{s1{SeqL1 ?s2{SeqL2} (append s1s2)
{λs3. s3{Seq(L1++L2) ?s1{SeqL1 ?s2{SeqL2} Indeed,s{SeqL ?s{SeqLis equivalent tofalse.
As a result, to allow sharing, we must establishanother specification: {s{SeqL}
(append s s)
{λs3. s3{Seq(L++L) ?s{SeqL}
Duplicate workfor us.Increased complicationand/or duplicate work for the user.
The problem, facet 4: sharing is not permitted
The specification ofappendrequiress1ands2to bedistinctdata structures:
{s1{SeqL1 ?s2{SeqL2} (append s1s2)
{λs3. s3{Seq(L1++L2) ?s1{SeqL1 ?s2{SeqL2} Indeed,s{SeqL ?s{SeqLis equivalent tofalse.
As a result, to allow sharing, we must establishanother specification:
{s{SeqL} (append s s)
{λs3. s3{Seq(L++L) ?s{SeqL}
Duplicate workfor us.Increased complicationand/or duplicate work for the user.
Fractional permissions to the rescue...?
Could sequence concatenation be specified as follows in Concurrent SL?
∀π1, π2.{π1·(s1{SeqL1) ? π2·(s2{SeqL2)} (append s1s2)
{λs3. s3{Seq(L1++L2) ? π1·(s1{SeqL1)? π2·(s2{SeqL2)} Yes, if the logic allowsscaling,π·H. This requires existential quantification to be restricted so as to be precise (Boyland, 2010).
Without scaling, one must defines{SeqπL.
This addresses problem facets 3 and 4,
I but is stillnoisy,
I and still requires carefulsplitting, threading, and joiningof permissions.
“Hiding the fractions” (Heule et al, 2013) is cool but requires yet more machinery.
Fractional permissions to the rescue...?
Could sequence concatenation be specified as follows in Concurrent SL?
∀π1, π2.{π1·(s1{SeqL1) ? π2·(s2{SeqL2)} (append s1s2)
{λs3. s3{Seq(L1++L2) ? π1·(s1{SeqL1)? π2·(s2{SeqL2)} Yes, if the logic allowsscaling,π·H. This requires existential quantification to be restricted so as to be precise (Boyland, 2010).
Without scaling, one must defines{SeqπL. This addresses problem facets 3 and 4,
I but is stillnoisy,
I and still requires carefulsplitting, threading, and joiningof permissions.
“Hiding the fractions” (Heule et al, 2013) is cool but requires yet more machinery.
In this paper
We propose a solution that is:
I not as powerfulas fractional permissions (or other share algebras),
I but significantlysimpler.
Our contributions:
I introducing aread-only modality, RO.
RO(H)representstemporaryread-only access to the memory governed byH.
I findingsimple and sound reasoning rulesfor RO.
I proposinga modelthat justifies these rules.
Some Intuition
Reasoning Rules
Model
Conclusion
Our solution
We would like the specification ofappendto look like this:
{RO(s1{SeqL1) ?RO(s2{SeqL2)} (append s1s2)
{λs3. s3{Seq(L1++L2)}
Compared with the earlier specification based on unique read-write permissions,
I this specification ismore concise,
I imposesfewer proof obligations,
I makes it clear thatthe data structures cannot be modifiedbyappend,
I anddoes not requires1ands2to be distinct.— (next slide)
I Furthermore, this specimpliesthe earlier spec.— (slide after next)
Our solution
We would like the specification ofappendto look like this:
{RO(s1{SeqL1) ?RO(s2{SeqL2)} (append s1s2)
{λs3. s3{Seq(L1++L2)}
Compared with the earlier specification based on unique read-write permissions,
I this specification ismore concise,
I imposesfewer proof obligations,
I makes it clear thatthe data structures cannot be modifiedbyappend,
I anddoes not requires1ands2to be distinct.— (next slide)
I Furthermore, this specimpliesthe earlier spec.— (slide after next)
Our solution, facet 4: sharing is permitted
The top Hoare triple is the new spec ofappend, wheres1ands2are instantiated withs.
{ RO(s{SeqL) ?RO(s{SeqL) } (append s s)
{ λs3. s3{Seq(L++L) } { RO(s{SeqL) } (append s s)
{ λs3. s3{Seq(L++L) }
consequence
The bottom triple states that, with read-only access tos,append s sispermitted.
Our solution, facet 4: sharing is permitted
The top Hoare triple is the new spec ofappend, wheres1ands2are instantiated withs.
{ RO(s{SeqL) ?RO(s{SeqL) } (append s s)
{ λs3. s3{Seq(L++L) } { RO(s{SeqL) } (append s s)
{ λs3. s3{Seq(L++L) }
consequence
The bottom triple states that, with read-only access tos,append s sispermitted.
read-only permissions are
duplicable
Our solution, facet 5: the earlier specification can be derived
The Hoare triple at the top is the new spec ofappend.
{ RO(s1{SeqL1) ?RO(s2{SeqL2) } (append s1s2)
{ λs3. s3{Seq(L1++L2) }
{ RO(s1{SeqL1 ?s2{SeqL2) } (append s1s2)
{ λs3. s3{Seq(L1++L2) } { s1{SeqL1 ?s2{SeqL2 }
(append s1s2)
{ λs3. s3{Seq(L1++L2) ?s1{SeqL1?s2{SeqL2 }
read-only frame consequence
The triple at the bottom is the earlier spec ofappend.
Our solution, facet 5: the earlier specification can be derived
The Hoare triple at the top is the new spec ofappend.
{ RO(s1{SeqL1) ?RO(s2{SeqL2) } (append s1s2)
{ λs3. s3{Seq(L1++L2) }
{ RO(s1{SeqL1 ?s2{SeqL2) } (append s1s2)
{ λs3. s3{Seq(L1++L2) } { s1{SeqL1 ?s2{SeqL2 }
(append s1s2)
{ λs3. s3{Seq(L1++L2) ?s1{SeqL1?s2{SeqL2 }
read-only frame consequence
The triple at the bottom is the earlier spec ofappend.
permission becomes read-only
Our solution, facet 5: the earlier specification can be derived
The Hoare triple at the top is the new spec ofappend.
{ RO(s1{SeqL1) ?RO(s2{SeqL2) } (append s1s2)
{ λs3. s3{Seq(L1++L2) }
{ RO(s1{SeqL1 ?s2{SeqL2) } (append s1s2)
{ λs3. s3{Seq(L1++L2) } { s1{SeqL1 ?s2{SeqL2 }
(append s1s2)
{ λs3. s3{Seq(L1++L2) ?s1{SeqL1?s2{SeqL2 }
read-only frame consequence
The triple at the bottom is the earlier spec ofappend.
read-only permission is weakened
Our solution, facet 5: the earlier specification can be derived
The Hoare triple at the top is the new spec ofappend.
{ RO(s1{SeqL1) ?RO(s2{SeqL2) } (append s1s2)
{ λs3. s3{Seq(L1++L2) }
{ RO(s1{SeqL1 ?s2{SeqL2) } (append s1s2)
{ λs3. s3{Seq(L1++L2) } { s1{SeqL1 ?s2{SeqL2 }
(append s1s2)
{ λs3. s3{Seq(L1++L2) ?s1{SeqL1?s2{SeqL2 }
read-only frame consequence
The triple at the bottom is the earlier spec ofappend.
appendis called
Our solution, facet 5: the earlier specification can be derived
The Hoare triple at the top is the new spec ofappend.
{ RO(s1{SeqL1) ?RO(s2{SeqL2) } (append s1s2)
{ λs3. s3{Seq(L1++L2) }
{ RO(s1{SeqL1 ?s2{SeqL2) } (append s1s2)
{ λs3. s3{Seq(L1++L2) } { s1{SeqL1 ?s2{SeqL2 }
(append s1s2)
{ λs3. s3{Seq(L1++L2) ?s1{SeqL1?s2{SeqL2 }
read-only frame consequence
The triple at the bottom is the earlier spec ofappend.
read-write permission
magically re-appears
Some Intuition
Reasoning Rules
Model
Conclusion
Permissions
The syntax of permissions is as follows:
H := [P]|l,→v|H1?H2|H1∨∨H2| ∃∃x.H|RO(H) EverypermissionHhas a read-only form RO(H).
Properties of RO
Read-only access to a data structure entails read-only access to its parts:
RO(H1?H2) . RO(H1)?RO(H2) (the reverse is false) Read-only permissions are duplicable (therefore, no need to count them!):
RO(H) = RO(H)?RO(H) Read-only permissions are generally well-behaved:
RO([P]) = [P]
RO(H1∨∨H2) = RO(H1)∨∨RO(H2) RO(∃∃x.H) = ∃∃x.RO(H) RO(RO(H)) = RO(H)
RO(H) . RO(H0) ifH.H0
A new read axiom
The traditional read axiom:
traditional read axiom
{l,→v}(getl){λy.[y=v]?l,→v} is replaced with a “smaller” axiom:
new read axiom
{RO(l,→v)}(getl){λy.[y=v]} The traditional axiom can be derived from the new axiom.
A new frame rule
The traditional frame rule is subsumed by a new “read-only frame rule”:
frame rule
{H}t{Q} normalH0 {H?H0}t{Q?H0}
read-only frame rule
{H?RO(H0)}t{Q} normalH0 {H?H0}t{Q?H0}
This says: upon entry into a block,H0istemporarily replacedwith RO(H0), and upon exit,magically re-appears.
The side condition normalH0means roughly thatH0has no RO components.
This means thatread-only permissions cannot be framed out.
If they could, the read-only frame rule would clearly be unsound. (Exercise!)
How read-only permissions are used
No reasoning rule involves a triple whose postcondition contains RO.
Read-only permissions always appear inpreconditions, never in postconditions.
They are alwayspassed down, never returned.
In fact, in the model, we will see that, in a triple{H}t{Q}:
I the postcondition applies only tosome read-write fragmentof the final heap;
I the read-only part of the heapmust be preservedanyway, so there is no need for the postcondition to describe it.
Some Intuition
Reasoning Rules
Model
Conclusion
Memories & heaps – a simple model of access rights
Amemoryis a finite map of locations to values.
Aheaphis a pair of twodisjointmemoriesh.f andh.r.
I h.f represents the locations to which we havefull access;
I h.r represents the locations to which we haveread-only access.
Anassertion, orpermission, is a predicate over heaps (or: a set of heaps).
Heap composition & separating conjunction
The combination of two heaps:
h1+h2= (h1.f]h2.f,h1.r∪h2.r)
read-write read-only
is defined only if:
I the read-write componentsh1.f andh2.f aredisjoint;
I the read-only componentsh1.r andh2.ragreewhere they overlap;
I the read-write componenth1.f isdisjointwith the read-only componenth2.r, and vice-versa.
With this in mind,separating conjunctionis interpreted as usual: H1?H2=
λh. ∃h1h2. (h1+h2is defined) ∧ h=h1+h2 ∧ H1h1 ∧ H2h2
Heap composition & separating conjunction
The combination of two heaps:
h1+h2= (h1.f]h2.f,h1.r∪h2.r)
read-write read-only
is defined only if:
I the read-write componentsh1.f andh2.f aredisjoint;
I the read-only componentsh1.r andh2.ragreewhere they overlap;
I the read-write componenth1.f isdisjointwith the read-only componenth2.r, and vice-versa.
With this in mind,separating conjunctionis interpreted as usual: H1?H2=
λh. ∃h1h2. (h1+h2is defined) ∧ h=h1+h2 ∧ H1h1 ∧ H2h2
Heap composition & separating conjunction
The combination of two heaps:
h1+h2= (h1.f]h2.f,h1.r∪h2.r)
read-write read-only
is defined only if:
I the read-write componentsh1.f andh2.f aredisjoint;
I the read-only componentsh1.r andh2.ragreewhere they overlap;
I the read-write componenth1.f isdisjointwith the read-only componenth2.r, and vice-versa.
With this in mind,separating conjunctionis interpreted as usual: H1?H2=
λh. ∃h1h2. (h1+h2is defined) ∧ h=h1+h2 ∧ H1h1 ∧ H2h2
Heap composition & separating conjunction
The combination of two heaps:
h1+h2= (h1.f]h2.f,h1.r∪h2.r)
read-write read-only
is defined only if:
I the read-write componentsh1.f andh2.f aredisjoint;
I the read-only componentsh1.r andh2.ragreewhere they overlap;
I the read-write componenth1.f isdisjointwith the read-only componenth2.r, and vice-versa.
With this in mind,separating conjunctionis interpreted as usual: H1?H2=
λh. ∃h1h2. (h1+h2is defined) ∧ h=h1+h2 ∧ H1h1 ∧ H2h2
Heap composition & separating conjunction
The combination of two heaps:
h1+h2= (h1.f]h2.f,h1.r∪h2.r)
read-write read-only
is defined only if:
I the read-write componentsh1.f andh2.f aredisjoint;
I the read-only componentsh1.r andh2.ragreewhere they overlap;
I the read-write componenth1.f isdisjointwith the read-only componenth2.r, and vice-versa.
With this in mind,separating conjunctionis interpreted as usual:
H1?H2=
λh. ∃h1h2. (h1+h2is defined) ∧ h=h1+h2 ∧ H1h1 ∧ H2h2
The read-only modality
h h’
RO(H)is interpreted as follows:
RO(H) =
λh. (h.f=∅) ∧ ∃h0.(h.r=h0.f]h0.r)∧H h0
This means:
I we have write access tonothing.
I if we had write accessto certain locations for which we have read access, thenHwould hold.
The read-only modality
h h’
RO(H)is interpreted as follows:
RO(H) =
λh. (h.f=∅) ∧ ∃h0.(h.r=h0.f]h0.r)∧H h0 This means:
I we have write access tonothing.
I if we had write accessto certain locations for which we have read access, thenHwould hold.
The read-only modality
h h’
RO(H)is interpreted as follows:
RO(H) =
λh. (h.f=∅) ∧ ∃h0.(h.r=h0.f]h0.r)∧H h0 This means:
I we have write access tonothing.
I if we had write accessto certain locations for which we have read access, thenHwould hold.
The rest of the connectives
[P] = λh. (h.f=∅)∧ (h.r=∅)∧ P l,→v = λh. (h.f= (l7→v))∧(h.r=∅) H1∨∨H2 = λh. H1h ∨H2h
∃∃x.H = λh. ∃x.H h normal(H) = ∀h.H h ⇒ h.r=∅
Interpretation of triples
The meaning of the Hoare triple{H}t{Q}is as follows:
∀h1h2.
( h1+h2is defined H h1
)
⇒ ∃vh01.
h01+h2is defined t/bh1+h2c ⇓v/bh01+h2c h01.r=h1.r
on-some-rw-frag(Q v)h01
What’s nonstandard?
I The read-only part of the heap must bepreserved.
I The postcondition describes onlya read-write fragment of the final heap. on-some-rw-frag(H) =
λh.∃h1h2.(h1+h2is defined) ∧h=h1+h2 ∧h1.r=∅∧ H h1
Interpretation of triples
The meaning of the Hoare triple{H}t{Q}is as follows:
∀h1h2.
( h1+h2is defined H h1
)
⇒ ∃vh01.
h01+h2is defined t/bh1+h2c ⇓v/bh01+h2c h01.r=h1.r
on-some-rw-frag(Q v)h01
What’s nonstandard?
I The read-only part of the heap must bepreserved.
I The postcondition describes onlya read-write fragment of the final heap. on-some-rw-frag(H) =
λh.∃h1h2.(h1+h2is defined) ∧h=h1+h2 ∧h1.r=∅∧ H h1
Interpretation of triples
The meaning of the Hoare triple{H}t{Q}is as follows:
∀h1h2.
( h1+h2is defined H h1
)
⇒ ∃vh01.
h01+h2is defined t/bh1+h2c ⇓v/bh01+h2c h01.r=h1.r
on-some-rw-frag(Q v)h01
What’s nonstandard?
I The read-only part of the heap must bepreserved.
I The postcondition describes onlya read-write fragment of the final heap.
on-some-rw-frag(H) =
λh.∃h1h2.(h1+h2is defined) ∧h=h1+h2 ∧h1.r=∅∧ H h1
Soundness
Theorem
With respect to this interpretation of triples, every reasoning rule is sound.
Proof.
“Straightforward”. Machine-checked.
Some Intuition
Reasoning Rules
Model
Conclusion
We propose:
I a simple extensionof Separation Logic with a read-only modality;
I a simple modelthat explains why this is sound.
We believe that temporary read-only permissions sometimes help state moreconcise,accurate,usefulspecifications, and lead to simpler proofs.
Possible future work: an implementation in CFML (Charguéraud).
Amnesia (1/2)
Supposepopulationhas this “RO” specification:
{RO(h{HashTableM)}(population h){λy.[y=cardM]}
Suppose a hash table is a mutable record whosedatafield points to an array:
h{HashTableM:=
∃∃a.∃∃L.(h{{data=a;. . .}?a{ArrayL? . . .) Suppose there is an operationfooon hash tables:
letfoo h=
letd=h.datain – read the address of the array letp=population hin – callpopulation
. . .
If “RO” is sugar for repeatingh{HashTableMin the pre and post, then the proof offooruns into a problem...
Amnesia (2/2)
Reasoning aboutfoomight go like this:
1 letfoo h=
2 {h{HashTableM} –foo’s precondition
3 {h{{data=a;. . .}?a{ArrayL? . . .} – by unfolding
4 letd=h.datain
5 {h{{data=a;. . .}?a{ArrayL? . . . ?[d=a]} – by reading
6 {h{HashTableM?[d=a]} – by folding
7 letp=population hin – wehaveto fold
8 {h{HashTableM?[d=a]?[p= #M]}
9 . . .
At line 8, the equationd=ais useless.
We haveforgottenwhatdrepresents, andlost the benefitof the read at line 4.
If “RO” is sugar, the specification ofpopulationisweakerthan it seems.
If “RO” is native, there is a way around this problem. (Details omitted.)