Footprints, room, and products of data structures sharing one memory #
A Memory.Framed D w is a refinement of D over Memory.spec w together with a footprint, the
finite set of allocated cells that holds the representation of each memory state, and a room,
the number of cells the structure has reserved for its future allocations. Three obligations make
them honest.
congr: the invariant, the abstraction, the footprint and the room read only the footprint's cells, and survive a growing frontier that leaves the reserved room fitting a word.modifies: a method run changes no allocated cell outside the footprint, the footprint grows only into cells that the run allocated, and the frontier advances by at most the room the run gives up.lt_frontier: the footprint is allocated.
A structure laid out in fixed blocks has a constant footprint and no room, and Framed.ofStatic
asks for less. A structure that allocates inside its operations, such as a growing array,
reserves room for all of its future growth. Its invariant may then ask that the frontier plus its
room fit a word, which is what Memory.valid needs of each allocation. That fact is not
preserved by other structures' allocations on its own, so the product keeps it for everybody.
Products. Two framed refinements implement the product of their specifications over one
shared memory (Framed.prod). The product's invariant asks that the footprints be disjoint and
that the frontier plus both rooms fit a word. This is the shared room budget: a method run of one
side writes only its own cells and fresh ones, and advances the frontier by at most the room it
gives up, so the other side keeps its cells and its room, and the budget still fits. The product
is framed again, with the union of the footprints and the sum of the rooms, so products iterate:
(M₁.prod M₂).prod M₃ implements three structures in one memory. A potential that reads only
the footprint (Framed.Local) is left unchanged by the other side's methods, so amortized
certificates add across the product (Framed.amortized_prod), and so do worst-case
Module.ChargeBounds, as the case of zero potentials.
Composition. A refinement of D over E composed with a framed refinement of E is framed
by the same footprint and room (Framed.comp), since a whole linked client keeps the frame of its
calls (Framed.modifies_demonic). So a structure built on others, such as a queue over two
stacks, joins a product like any other.
Allocation is a memory program like any other. A run that leaves every allocated cell
unchanged and leaves the room fitting, such as the allocation of a further structure, preserves a
framed refinement (Framed.of_fresh), and Framed.prod_of_fresh sets up a product from
structures allocated one after the other. Memory.Allocator packages this.
Main definitions #
Memory.Framed: a refinement over memory with a footprint and a room.Memory.Framed.ofStatic: the constructor for a constant footprint and no room.Memory.Framed.Local: a function of memory that reads only the footprint.Memory.Framed.prod: two framed refinements with disjoint footprints implement the product.Memory.Framed.comp: a refinement over a framed refinement is framed.
Main results #
Memory.frontier_le_of_clientRun,Memory.frontier_le_clientState: a run never lowers the frontier.Memory.mem_run_of_lt_frontier,Memory.mem_clientState_of_lt_frontier: a run changes an allocated cell only by writing it.Memory.mem_run_of_frontier_le,Memory.mem_clientState_of_frontier_le: an admissible run changes no cell at or above its final frontier.Memory.Framed.modifies_demonic: a linked client keeps the frame of its calls.Memory.Framed.frame: a method run of one structure leaves a structure with a disjoint footprint unchanged, and the shared room budget fitting.Memory.Framed.prod_methods: the product's methods are the two sides' methods.Memory.Framed.amortized_prod: potentials and charges add across the product.Memory.Framed.of_fresh,Memory.Framed.prod_of_fresh: allocation preserves structures and sets up products.
References #
The footprint and frame discipline is the locality principle of separation logic, here with
explicit finite footprints as in Cslib.WordRAM.HeapAssertion; see
J. C. Reynolds, Separation Logic: A Logic for Shared Mutable Data Structures.
Runs and allocated cells #
An admissible sequence of operations changes no cell at or above its final frontier: writes go below the frontier, and an allocation clears only the block that it hands out.
An admissible memory program changes no cell at or above its final frontier.
Framed refinements #
A refinement of D over memory whose representation lives in a footprint of allocated cells,
with room reserved for its growth. The invariant, the abstraction, the footprint and the room
read only the footprint's cells; a method run changes no allocated cell outside the footprint;
the footprint grows only into cells at or above the old frontier, which the run allocated; and
the frontier advances by at most the room the run gives up.
- methods : Methods D (Memory.spec w)
- inv : (Memory.spec w).State → Prop
- abs : (Memory.spec w).State → D.State
- valid {e : (Memory.spec w).State} (o : D.Op) : self.inv e → D.valid (self.abs e) o → (Memory.spec w).DemonicValid e (self.methods o)
- correct {e : (Memory.spec w).State} (o : D.Op) {a : D.Result o} {e' : (Memory.spec w).State} {tr : (Memory.spec w).Transcript} : self.inv e → D.valid (self.abs e) o → (Memory.spec w).ClientRun e (self.methods o) a e' tr → D.step (self.abs e) o (self.abs e') a ∧ self.inv e'
The cells holding the representation.
The cells reserved for the structure's future allocations.
The footprint is allocated.
- congr {s s' : (spec w).State} : self.inv s → (∀ a ∈ self.footprint s, s'.mem a = s.mem a) → s.frontier ≤ s'.frontier → WordRAM.Fits w (s'.frontier + self.room s) → self.inv s' ∧ self.abs s' = self.abs s ∧ self.footprint s' = self.footprint s ∧ self.room s' = self.room s
The invariant, the abstraction, the footprint and the room read only the footprint's cells, and survive a growing frontier that leaves the room fitting a word.
- modifies {s : (spec w).State} (o : D.Op) {a : D.Result o} {s' : (spec w).State} {tr : (spec w).Transcript} : self.inv s → D.valid (self.abs s) o → (spec w).ClientRun s (self.methods o) a s' tr → (∀ b < s.frontier, b ∉ self.footprint s → s'.mem b = s.mem b) ∧ (∀ b ∈ self.footprint s', b ∈ self.footprint s ∨ s.frontier ≤ b) ∧ s'.frontier + self.room s' ≤ s.frontier + self.room s
A method run changes no allocated cell outside the footprint, the footprint grows only into fresh cells, and the frontier advances by at most the room given up.
Instances For
The constructor for a constant footprint fp and no room: the invariant and abstraction read
only fp, and each method's final state agrees with its initial state on the allocated cells
outside fp and has the same frontier.
Instances For
A function of memory reads only the footprint: it is unchanged when the footprint's cells
are and the frontier grows, leaving the room fitting a word. The abstraction is one
(Local.abs), and so is the room (Local.room); so are potentials that a product must keep
apart.
Instances For
The abstraction reads only the footprint.
The room reads only the footprint.
A constant reads no cell.
A function of the abstract state is local.
A run that leaves every allocated cell unchanged and leaves the room fitting preserves a framed refinement. Allocating another structure is such a run.
The frame of a client. A run of a linked admissible client changes no cell outside the footprint that was allocated before it, grows the footprint only into fresh cells, and advances the frontier by at most the room it gives up, as each method run does.
Composition #
A refinement over a framed refinement is framed, by the same footprint and room: the upper invariant and abstraction read the lower state only through its abstraction, and each upper method is a linked client of the lower methods.
Instances For
The composite's methods link the upper methods through the lower ones.
The composite is the composition of the refinements.
The composite's invariant: the lower invariant, and the upper invariant of the lower abstraction.
The composite represents a memory state by the upper abstraction of the lower one.
The composite's footprint is the lower footprint.
The composite's room is the lower room.
A function local to the lower framed refinement is local to the composite.
Potentials stack across composition with a framed refinement, as for refinements
(Refinement.Amortized.comp).
Frames of disjoint structures #
A method run of M keeps every cell of a disjoint, allocated footprint.
A method run of M leaves room for N when both rooms fitted before.
The frame of a framed refinement. A method run of M leaves a framed refinement with a
disjoint footprint unchanged, and the footprints disjoint and the shared room budget fitting.
A method run of M leaves unchanged every function that reads only a disjoint footprint.
Products #
What keeps two framed refinements apart in one memory: disjoint footprints, and room for both.
Instances For
A method run of the left side keeps the right side and keeps the two apart.
A method run of the right side keeps the left side and keeps the two apart.
Products over one memory. Two framed refinements implement the product of their specifications over one memory, with the union of their footprints and the sum of their rooms, while they are apart: their footprints are disjoint and the frontier plus both rooms fits a word. Being apart is part of the product's invariant.
Instances For
Potentials add across a product over one memory. Certificates of the two sides against one lower charge, with potentials that read only their own footprints, give a certificate of the product with componentwise upper charges and the sum of the potentials.
Setting up a product from structures allocated one after the other: M₁ holds at s; a
run from s to s' leaves the cells below s.frontier unchanged; M₂ holds at s' on a
footprint disjoint from M₁'s, such as one at or above s.frontier; and the frontier plus both
rooms fits. Then the product holds at s', with M₁'s abstraction, footprint and room
unchanged.