ReferencesTyping Mutable References
- mutable pointer structures
- non-local control constructs (exceptions, continuations, etc.)
- process synchronization and communication
- etc.
Definitions
- use the mechanisms we already have for name binding (abstraction, let);
- introduce new, explicit operations for allocating, changing, and looking up the contents of references (pointers).
The basic operations on references are allocation,
dereferencing, and assignment.
- To allocate a reference, we use the ref operator, providing
an initial value for the new cell.
- To read the current value of this cell, we use the
dereferencing operator !.
- To change the value stored in a cell, we use the assignment
operator.
Types
T ::= Nat
| Unit
| T → T
| Ref T
Terms
t ::= ... Terms
| ref t allocation
| !t dereference
| t := t assignment
| l location
Typing (Preview)
Gamma |-- t1 ∈ T1 | (T_Ref) |
Gamma |-- ref t1 ∈ Ref T1 |
Gamma |-- t1 ∈ Ref T1 | (T_Deref) |
Gamma |-- !t1 ∈ T1 |
Gamma |-- t1 ∈ Ref T2 | |
Gamma |-- t2 ∈ T2 | (T_Assign) |
Gamma |-- t1 := t2 : Unit |
Values and Substitution
Inductive value : tm → Prop :=
| v_abs : ∀ x T2 t1,
value <{\x:T2, t1}>
| v_nat : ∀ n : nat ,
value <{ n }>
| v_unit :
value <{ unit }>
| v_loc : ∀ l,
value <{ loc l }>.
| v_abs : ∀ x T2 t1,
value <{\x:T2, t1}>
| v_nat : ∀ n : nat ,
value <{ n }>
| v_unit :
value <{ unit }>
| v_loc : ∀ l,
value <{ loc l }>.
Side Effects and Sequencing
r:=succ(!r); !ras an abbreviation for
(\x:Unit, !r) (r := succ(!r)).
References and Aliasing
let r = ref 5 in let s = r in s := 82; (!r)+1the cell referenced by r will contain the value 82, while the result of the whole expression will be 83. The references r and s are said to be aliases for the same cell.
r := 5; r := !sassigns 5 to r and then immediately overwrites it with s's current value; this has exactly the same effect as the single assignment
r := !sunless we happen to do it in a context where r and s are aliases for the same cell!
let r = ref 0 in let s = r in r := 5; r := !s
Shared State
let c = ref 0 in let incc = \_:Unit, (c := succ (!c); !c) in let decc = \_:Unit, (c := pred (!c); !c) in ...
Objects
newcounter = \_:Unit, let c = ref 0 in let incc = \_:Unit, (c := succ (!c); !c) in let decc = \_:Unit, (c := pred (!c); !c) in {i=incc, d=decc}
let c1 = newcounter unit in let c2 = newcounter unit in // Note that we've allocated two separate storage cells now! let r1 = c1.i unit in let r2 = c2.i unit in r2 // yields 1, not 2!
References to Compound Types
newarray = \_:Unit, ref (\n:Nat,0)
lookup = \a:NatArray, \n:Nat, (!a) n
update = \a:NatArray, \m:Nat, \v:Nat, let oldf = !a in a := (\n:Nat, if equal m n then v else oldf n);
Null References
- In C, a pointer variable can contain either a valid pointer
into the heap or the special value NULL
- A source of many errors and much tricky reasoning
- (any pointer may potentially be "not there")
- but occasionally useful
- Null pointers are easy to implement here using references
plus options (which can be built out of disjoint sum types)
Option T = Unit + T NullableRef T = Ref (Option T)
Garbage Collection
Locations
- Concretely: An array of 8-bit bytes, indexed by 32-bit integers.
- More abstractly: a list or array, of values
- Even more abstractly: a partial function from locations to values.
Stores
Definition store := list tm.
We use store_lookup n st to retrieve the value of the reference
cell at location n in the store st. Note that we must give a
default value to nth in case we try looking up an index which is
too large. (In fact, we will never actually do this, but proving
that we don't will require a bit of work.)
Definition store_lookup (n:nat) (st:store) :=
nth n st <{ unit }>.
nth n st <{ unit }>.
Fixpoint replace {A:Type} (n:nat) (x:A) (l:list A) : list A :=
match l with
| nil ⇒ nil
| h :: t ⇒
match n with
| O ⇒ x :: t
| S n' ⇒ h :: replace n' x t
end
end.
match l with
| nil ⇒ nil
| h :: t ⇒
match n with
| O ⇒ x :: t
| S n' ⇒ h :: replace n' x t
end
end.
Reduction
value v2 | (ST_AppAbs) |
(\x:T2,t1) v2 / st --> [x:=v2]t1 / st |
t1 / st --> t1' / st' | (ST_App1) |
t1 t2 / st --> t1' t2 / st' |
value v1 t2 / st --> t2' / st' | (ST_App2) |
v1 t2 / st --> v1 t2' / st' |
(ST_RefValue) | |
ref v / st --> loc |st| / st,v |
t1 / st --> t1' / st' | (ST_Ref) |
ref t1 / st --> ref t1' / st' |
l < |st| | (ST_DerefLoc) |
!(loc l) / st --> lookup l st / st |
t1 / st --> t1' / st' | (ST_Deref) |
!t1 / st --> !t1' / st' |
l < |st| | (ST_Assign) |
loc l := v / st --> unit / replace l v st |
t1 / st --> t1' / st' | (ST_Assign1) |
t1 := t2 / st --> t1' := t2 / st' |
t2 / st --> t2' / st' | (ST_Assign2) |
v1 := t2 / st --> v1 := t2' / st' |
Typing
Definition context := partial_map ty.
Store typings
Gamma |-- lookup l st : T1 | |
Gamma |-- loc l : Ref T1 |
We could try adding st to the judgment: Gamma; st |-- t : T, which gives us this rule:
Gamma; st |-- lookup l st : T1 | |
Gamma; st |-- loc l : Ref T1 |
[\x:Nat, (!(loc 1)) x, \x:Nat, (!(loc 0)) x]
Definition store_ty := list ty.
Here we represent locations as indices into a list of types so
the type at index i is the type of the values stored in cell i.
The store_Tlookup function retrieves the type at a particular
index.
Definition store_Tlookup (n:nat) (ST:store_ty) :=
nth n ST <{ Unit }>.
nth n ST <{ Unit }>.
l < |ST| | |
Gamma; ST |-- loc l : Ref (store_Tlookup l ST) |
The Typing Relation
l < |ST| | (T_Loc) |
Gamma; ST |-- loc l : Ref (store_Tlookup l ST) |
Gamma; ST |-- t1 : T1 | (T_Ref) |
Gamma; ST |-- ref t1 : Ref T1 |
Gamma; ST |-- t1 : Ref T1 | (T_Deref) |
Gamma; ST |-- !t1 : T1 |
Gamma; ST |-- t1 : Ref T2 | |
Gamma; ST |-- t2 : T2 | (T_Assign) |
Gamma; ST |-- t1 := t2 : Unit |
Properties
- Progress -- pretty much same as always
- Preservation -- needs to be stated more carefully!
Well-Typed Stores
Theorem preservation_wrong1 : ∀ ST T t st t' st',
empty ; ST |-- t \in T →
t / st --> t' / st' →
empty ; ST |-- t' \in T.
Abort.
empty ; ST |-- t \in T →
t / st --> t' / st' →
empty ; ST |-- t' \in T.
Abort.
Obviously wrong: no relation between assumed store typing
and provided store!
We need a way of saying "this store satisfies the assumptions of
that store typing"...
Definition store_well_typed (ST:store_ty) (st:store) :=
length ST = length st ∧
(∀ l, l < length st →
empty; ST |-- { store_lookup l st } \in {store_Tlookup l ST }).
length ST = length st ∧
(∀ l, l < length st →
empty; ST |-- { store_lookup l st } \in {store_Tlookup l ST }).
Informally, we will write ST |-- st for store_well_typed ST st.
We can now state something closer to the desired preservation
property:
Theorem preservation_wrong2 : ∀ ST T t st t' st',
empty ; ST |-- t \in T →
t / st --> t' / st' →
store_well_typed ST st →
empty ; ST |-- t' \in T.
Abort.
empty ; ST |-- t \in T →
t / st --> t' / st' →
store_well_typed ST st →
empty ; ST |-- t' \in T.
Abort.
This works... for all but one of the reduction rules!
Extending Store Typings
Inductive extends : store_ty → store_ty → Prop :=
| extends_nil : ∀ ST',
extends ST' nil
| extends_cons : ∀ x ST' ST,
extends ST' ST →
extends (x::ST') (x::ST).
| extends_nil : ∀ ST',
extends ST' nil
| extends_cons : ∀ x ST' ST,
extends ST' ST →
extends (x::ST') (x::ST).
Preservation, Finally
Definition preservation_theorem := ∀ ST t t' T st st',
empty ; ST |-- t \in T →
store_well_typed ST st →
t / st --> t' / st' →
∃ ST',
extends ST' ST ∧
empty ; ST' |-- t' \in T ∧
store_well_typed ST' st'.
empty ; ST |-- t \in T →
store_well_typed ST st →
t / st --> t' / st' →
∃ ST',
extends ST' ST ∧
empty ; ST' |-- t' \in T ∧
store_well_typed ST' st'.
Note that this gives us just what we need to "turn the
crank" when applying the theorem to multi-step reduction
sequences.
Assignment Preserves Store Typing
Lemma assign_pres_store_typing : ∀ ST st l t,
l < length st →
store_well_typed ST st →
empty ; ST |-- t \in {store_Tlookup l ST} →
store_well_typed ST (replace l t st).
l < length st →
store_well_typed ST st →
empty ; ST |-- t \in {store_Tlookup l ST} →
store_well_typed ST (replace l t st).
Proof with auto.
intros ST st l t Hlen HST Ht.
inversion HST; subst.
split. rewrite length_replace...
intros l' Hl'.
destruct (l' =? l) eqn: Heqll'.
- (* l' = l *)
apply eqb_eq in Heqll'; subst.
rewrite lookup_replace_eq...
- (* l' <> l *)
apply eqb_neq in Heqll'.
rewrite lookup_replace_neq...
rewrite length_replace in Hl'.
apply H0...
Qed.
intros ST st l t Hlen HST Ht.
inversion HST; subst.
split. rewrite length_replace...
intros l' Hl'.
destruct (l' =? l) eqn: Heqll'.
- (* l' = l *)
apply eqb_eq in Heqll'; subst.
rewrite lookup_replace_eq...
- (* l' <> l *)
apply eqb_neq in Heqll'.
rewrite lookup_replace_neq...
rewrite length_replace in Hl'.
apply H0...
Qed.
Weakening for Stores
Lemma store_weakening : ∀ Gamma ST ST' t T,
extends ST' ST →
Gamma ; ST |-- t \in T →
Gamma ; ST' |-- t \in T.
extends ST' ST →
Gamma ; ST |-- t \in T →
Gamma ; ST' |-- t \in T.
Proof with eauto.
intros. induction H0; eauto.
- (* T_Loc *)
rewrite <- (extends_lookup _ _ ST')...
apply T_Loc.
eapply length_extends...
Qed.
intros. induction H0; eauto.
- (* T_Loc *)
rewrite <- (extends_lookup _ _ ST')...
apply T_Loc.
eapply length_extends...
Qed.
Lemma store_well_typed_app : ∀ ST st t1 T1,
store_well_typed ST st →
empty ; ST |-- t1 \in T1 →
store_well_typed (ST ++ T1::nil) (st ++ t1::nil).
store_well_typed ST st →
empty ; ST |-- t1 \in T1 →
store_well_typed (ST ++ T1::nil) (st ++ t1::nil).
Proof with auto.
intros.
unfold store_well_typed in ×.
destruct H as [Hlen Hmatch].
rewrite app_length, add_comm. simpl.
rewrite app_length, add_comm. simpl.
split...
- (* types match. *)
intros l Hl.
unfold store_lookup, store_Tlookup.
apply le_lt_eq_dec in Hl; destruct Hl as [Hlt | Heq].
+ (* l < length st *)
apply <-Nat.succ_lt_mono in Hlt.
rewrite !app_nth1...
× apply store_weakening with ST. apply extends_app.
apply Hmatch...
× rewrite Hlen...
+ (* l = length st *)
injection Heq as Heq; subst.
rewrite app_nth2; try lia.
rewrite <- Hlen.
rewrite sub_diag. simpl.
apply store_weakening with ST...
{ apply extends_app. }
rewrite app_nth2; [|lia].
rewrite sub_diag. simpl. assumption.
Qed.
intros.
unfold store_well_typed in ×.
destruct H as [Hlen Hmatch].
rewrite app_length, add_comm. simpl.
rewrite app_length, add_comm. simpl.
split...
- (* types match. *)
intros l Hl.
unfold store_lookup, store_Tlookup.
apply le_lt_eq_dec in Hl; destruct Hl as [Hlt | Heq].
+ (* l < length st *)
apply <-Nat.succ_lt_mono in Hlt.
rewrite !app_nth1...
× apply store_weakening with ST. apply extends_app.
apply Hmatch...
× rewrite Hlen...
+ (* l = length st *)
injection Heq as Heq; subst.
rewrite app_nth2; try lia.
rewrite <- Hlen.
rewrite sub_diag. simpl.
apply store_weakening with ST...
{ apply extends_app. }
rewrite app_nth2; [|lia].
rewrite sub_diag. simpl. assumption.
Qed.
Preservation!
Progress
Theorem progress : ∀ ST t T st,
empty ; ST |-- t \in T →
store_well_typed ST st →
(value t ∨ ∃ t' st', t / st --> t' / st').
empty ; ST |-- t \in T →
store_well_typed ST st →
(value t ∨ ∃ t' st', t / st --> t' / st').
Proof with eauto.
intros ST t T st Ht HST. remember empty as Gamma.
induction Ht; subst; try solve_by_invert...
- (* T_App *)
right. destruct IHHt1 as [Ht1p | Ht1p]...
+ (* t1 is a value *)
inversion Ht1p; subst; try solve_by_invert.
destruct IHHt2 as [Ht2p | Ht2p]...
× (* t2 steps *)
destruct Ht2p as [t2' [st' Hstep]].
∃ <{ (\ x0 : T0, t0) t2' }>, st'...
+ (* t1 steps *)
destruct Ht1p as [t1' [st' Hstep]].
∃ <{ t1' t2 }>, st'...
- (* T_Succ *)
right. destruct IHHt as [Ht1p | Ht1p]...
+ (* t1 is a value *)
inversion Ht1p; subst; try solve [ inversion Ht ].
× (* t1 is a const *)
∃ <{ {S n} }>, st...
+ (* t1 steps *)
destruct Ht1p as [t1' [st' Hstep]].
∃ <{ succ t1' }>, st'...
- (* T_Pred *)
right. destruct IHHt as [Ht1p | Ht1p]...
+ (* t1 is a value *)
inversion Ht1p; subst; try solve [inversion Ht ].
× (* t1 is a const *)
∃ <{ {n - 1} }>, st...
+ (* t1 steps *)
destruct Ht1p as [t1' [st' Hstep]].
∃ <{ pred t1' }>, st'...
- (* T_Mult *)
right. destruct IHHt1 as [Ht1p | Ht1p]...
+ (* t1 is a value *)
inversion Ht1p; subst; try solve [inversion Ht1].
destruct IHHt2 as [Ht2p | Ht2p]...
× (* t2 is a value *)
inversion Ht2p; subst; try solve [inversion Ht2].
∃ <{ {n × n0} }>, st...
× (* t2 steps *)
destruct Ht2p as [t2' [st' Hstep]].
∃ <{ n × t2' }>, st'...
+ (* t1 steps *)
destruct Ht1p as [t1' [st' Hstep]].
∃ <{ t1' × t2 }>, st'...
- (* T_If0 *)
right. destruct IHHt1 as [Ht1p | Ht1p]...
+ (* t1 is a value *)
inversion Ht1p; subst; try solve [inversion Ht1].
destruct n.
× (* n = 0 *) ∃ t2, st...
× (* n = S n' *) ∃ t3, st...
+ (* t1 steps *)
destruct Ht1p as [t1' [st' Hstep]].
∃ <{ if0 t1' then t2 else t3 }>, st'...
- (* T_Ref *)
right. destruct IHHt as [Ht1p | Ht1p]...
+ (* t1 steps *)
destruct Ht1p as [t1' [st' Hstep]].
∃ <{ref t1'}>, st'...
- (* T_Deref *)
right. destruct IHHt as [Ht1p | Ht1p]...
+ (* t1 is a value *)
inversion Ht1p; subst; try solve_by_invert.
eexists. eexists. apply ST_DerefLoc...
inversion Ht; subst. inversion HST; subst.
rewrite <- H...
+ (* t1 steps *)
destruct Ht1p as [t1' [st' Hstep]].
∃ <{ ! t1' }>, st'...
- (* T_Assign *)
right. destruct IHHt1 as [Ht1p|Ht1p]...
+ (* t1 is a value *)
destruct IHHt2 as [Ht2p|Ht2p]...
× (* t2 is a value *)
inversion Ht1p; subst; try solve_by_invert.
eexists. eexists. apply ST_Assign...
inversion HST; subst. inversion Ht1; subst.
rewrite H in H4...
× (* t2 steps *)
destruct Ht2p as [t2' [st' Hstep]].
∃ <{ t1 := t2' }>, st'...
+ (* t1 steps *)
destruct Ht1p as [t1' [st' Hstep]].
∃ <{ t1' := t2 }>, st'...
Qed.
intros ST t T st Ht HST. remember empty as Gamma.
induction Ht; subst; try solve_by_invert...
- (* T_App *)
right. destruct IHHt1 as [Ht1p | Ht1p]...
+ (* t1 is a value *)
inversion Ht1p; subst; try solve_by_invert.
destruct IHHt2 as [Ht2p | Ht2p]...
× (* t2 steps *)
destruct Ht2p as [t2' [st' Hstep]].
∃ <{ (\ x0 : T0, t0) t2' }>, st'...
+ (* t1 steps *)
destruct Ht1p as [t1' [st' Hstep]].
∃ <{ t1' t2 }>, st'...
- (* T_Succ *)
right. destruct IHHt as [Ht1p | Ht1p]...
+ (* t1 is a value *)
inversion Ht1p; subst; try solve [ inversion Ht ].
× (* t1 is a const *)
∃ <{ {S n} }>, st...
+ (* t1 steps *)
destruct Ht1p as [t1' [st' Hstep]].
∃ <{ succ t1' }>, st'...
- (* T_Pred *)
right. destruct IHHt as [Ht1p | Ht1p]...
+ (* t1 is a value *)
inversion Ht1p; subst; try solve [inversion Ht ].
× (* t1 is a const *)
∃ <{ {n - 1} }>, st...
+ (* t1 steps *)
destruct Ht1p as [t1' [st' Hstep]].
∃ <{ pred t1' }>, st'...
- (* T_Mult *)
right. destruct IHHt1 as [Ht1p | Ht1p]...
+ (* t1 is a value *)
inversion Ht1p; subst; try solve [inversion Ht1].
destruct IHHt2 as [Ht2p | Ht2p]...
× (* t2 is a value *)
inversion Ht2p; subst; try solve [inversion Ht2].
∃ <{ {n × n0} }>, st...
× (* t2 steps *)
destruct Ht2p as [t2' [st' Hstep]].
∃ <{ n × t2' }>, st'...
+ (* t1 steps *)
destruct Ht1p as [t1' [st' Hstep]].
∃ <{ t1' × t2 }>, st'...
- (* T_If0 *)
right. destruct IHHt1 as [Ht1p | Ht1p]...
+ (* t1 is a value *)
inversion Ht1p; subst; try solve [inversion Ht1].
destruct n.
× (* n = 0 *) ∃ t2, st...
× (* n = S n' *) ∃ t3, st...
+ (* t1 steps *)
destruct Ht1p as [t1' [st' Hstep]].
∃ <{ if0 t1' then t2 else t3 }>, st'...
- (* T_Ref *)
right. destruct IHHt as [Ht1p | Ht1p]...
+ (* t1 steps *)
destruct Ht1p as [t1' [st' Hstep]].
∃ <{ref t1'}>, st'...
- (* T_Deref *)
right. destruct IHHt as [Ht1p | Ht1p]...
+ (* t1 is a value *)
inversion Ht1p; subst; try solve_by_invert.
eexists. eexists. apply ST_DerefLoc...
inversion Ht; subst. inversion HST; subst.
rewrite <- H...
+ (* t1 steps *)
destruct Ht1p as [t1' [st' Hstep]].
∃ <{ ! t1' }>, st'...
- (* T_Assign *)
right. destruct IHHt1 as [Ht1p|Ht1p]...
+ (* t1 is a value *)
destruct IHHt2 as [Ht2p|Ht2p]...
× (* t2 is a value *)
inversion Ht1p; subst; try solve_by_invert.
eexists. eexists. apply ST_Assign...
inversion HST; subst. inversion Ht1; subst.
rewrite H in H4...
× (* t2 steps *)
destruct Ht2p as [t2' [st' Hstep]].
∃ <{ t1 := t2' }>, st'...
+ (* t1 steps *)
destruct Ht1p as [t1' [st' Hstep]].
∃ <{ t1' := t2 }>, st'...
Qed.
References and Nontermination
(\r:Ref (Unit -> Unit),
r := (\x:Unit,(!r) unit); (!r) unit)
(ref (\x:Unit,unit))