Lecture notes 20210310 Denotational Semantics 1
Remark. Some material in this lecture is from << Software Foundation >>
volume 1.
Require Import PL.Imp.
Module squaring.
Import Assertion_S.
Import Assertion_S_Tac.
Import Assertion_S_Rules.
Import Concrete_Pretty_Printing.
Import Axiomatic_semantics.
Import Derived_Rules.
Local Instance X: var := new_var().
Local Instance RES: var := new_var().
Local Instance I: var := new_var().
Local Instance J: var := new_var().
Fact square2_correct: ∀m: Z,
{{ [[X]] = m }}
I ::= 0;;
RES ::= 0;;
While !(I == X) Do
J ::= I + I + 1;;
While !(J ≤ 0) Do
RES ::= RES + 1;;
J ::= J - 1
EndWhile;;
I ::= I + 1
EndWhile
{{ [[RES]] = m*m }} .
Proof.
intros.
apply hoare_asgn_seq.
assert_subst.
assert_simpl.
apply hoare_asgn_seq.
assert_subst.
assert_simpl.
apply hoare_consequence with
([[RES]] = [[I * I]] AND [[X]] = m AND 0 ≤ [[I]] )%assert
([[RES]] = [[I * I]] AND [[X]] = m AND 0 ≤ [[I]] AND NOT [[! (I == X)]])%assert.
1: { entailer. intros. nia. }
2: { entailer. intros. nia. }
apply hoare_while.
apply hoare_asgn_seq.
assert_subst.
assert_simpl.
eapply hoare_consequence_pre with
([[RES + J]] = [[(I+1)*(I+1)]] AND [[X]] = m AND 0 ≤ [[I]] AND 0 ≤ [[J]])%assert.
{ entailer. intros. nia. }
eapply hoare_seq.
{
apply hoare_while.
apply hoare_asgn_seq.
assert_subst.
assert_simpl.
apply hoare_asgn_conseq.
entailer.
intros.
destruct H as [k [? ?]].
destruct H as [k0 ?].
nia.
}
apply hoare_asgn_conseq.
apply EXISTS_left.
intros.
entailer.
intros.
nia.
Qed.
End squaring.
Expression Evaluation
a ::= Z
| var
| a + a
| a - a
| a * a
In Hoare logic, we talk about the property that an expressions a's value
should satisfy. In denotational semantics, we directly define expressions'
evaluation result.
| var
| a + a
| a - a
| a * a
Import Abstract_Pretty_Printing.
Module AEval_first_try.
Section AEval.
Variable st: state.
Fixpoint aeval (a : aexp) : Z :=
match a with
| ANum n ⇒ n
| AId X ⇒ st X
| APlus a1 a2 ⇒ (aeval a1) + (aeval a2)
| AMinus a1 a2 ⇒ (aeval a1) - (aeval a2)
| AMult a1 a2 ⇒ (aeval a1) * (aeval a2)
end.
End AEval.
Module AEval_first_try.
Section AEval.
Variable st: state.
Fixpoint aeval (a : aexp) : Z :=
match a with
| ANum n ⇒ n
| AId X ⇒ st X
| APlus a1 a2 ⇒ (aeval a1) + (aeval a2)
| AMinus a1 a2 ⇒ (aeval a1) - (aeval a2)
| AMult a1 a2 ⇒ (aeval a1) * (aeval a2)
end.
End AEval.
Here
- The keyword Fixpoint means that we defines a recursive (递归) function.
- The function aeval defines the evaluation result of integer expressions in our simple imperative programming language.
- The argument announcement st: state says that st is a program state. Here, a program state is defined as a function from program variables to integers.
- The argument announcement a: aexp says that a is an integer expression. We follow <<Software Foundation>>'s tradition to call integer expressions aexp.
- The match expression (Coq expression) defines how this expression (program expression) evaluation is defined for different kind of a.
- ANum n says if a is a constant expression n.
- AId X says if a is a singleton variable X.
- st X says: applying function st on X. Remember, st is a function from program variables to integers.
Example aeval_example1: ∀(st: state) (X Y: var),
st X = 1 ->
st Y = 2 ->
aeval st (X + Y) = 3.
Proof.
intros.
simpl.
st X = 1 ->
st Y = 2 ->
aeval st (X + Y) = 3.
Proof.
intros.
simpl.
Here, the tactic simpl simplifies aeval st (X + Y) using the aeval's
definition.
rewrite H, H0.
reflexivity.
Qed.
Example aeval_example2: ∀(st: state) (X Y: var),
st X = 1 ->
st Y = 2 ->
aeval st (X * Y + 1) = 3.
Example aeval_example3: ∀(st: state) (X Y Z: var),
st X = 1 ->
st Y = 2 ->
st Z = 4 ->
aeval st ((X - 1) * X * X * (X + Y - Z) * Y * Y * (Y + 1)) = 0.
reflexivity.
Qed.
Example aeval_example2: ∀(st: state) (X Y: var),
st X = 1 ->
st Y = 2 ->
aeval st (X * Y + 1) = 3.
Proof.
intros.
simpl.
rewrite H, H0.
reflexivity.
Qed.
intros.
simpl.
rewrite H, H0.
reflexivity.
Qed.
Example aeval_example3: ∀(st: state) (X Y Z: var),
st X = 1 ->
st Y = 2 ->
st Z = 4 ->
aeval st ((X - 1) * X * X * (X + Y - Z) * Y * Y * (Y + 1)) = 0.
Proof.
intros.
simpl.
rewrite H, H0, H1.
reflexivity.
Qed.
intros.
simpl.
rewrite H, H0, H1.
reflexivity.
Qed.
Expression Equivalence
Definition aexp_equiv (a1 a2: aexp): Prop :=
∀st, aeval st a1 = aeval st a2.
Declare Scope DSem.
Local Open Scope DSem.
Notation "a1 '≡≡a' a2" :=
(aexp_equiv (a1:aexp) (a2:aexp)) (at level 69, no associativity): DSem.
∀st, aeval st a1 = aeval st a2.
Declare Scope DSem.
Local Open Scope DSem.
Notation "a1 '≡≡a' a2" :=
(aexp_equiv (a1:aexp) (a2:aexp)) (at level 69, no associativity): DSem.
Definition is another keyword for writing definitions in Coq besides
Fixpoint. It can beused to define functions and/or values but recursions are
not allowed in such definitions.
Here, we define that two integer expressions are semantically equivalent
(语义等价) (or behaviorally equivalent, 行为等价) if they evaluate to the same
result in every state. Here are some examples.
Example aexp_equiv_sample: ∀(X: var),
X + X =a= X * 2.
Proof.
intros.
unfold aexp_equiv.
X + X =a= X * 2.
Proof.
intros.
unfold aexp_equiv.
The tactic unfold will unfold a definition.
intros.
simpl.
lia.
Qed.
Lemma zero_plus_equiv: ∀(a: aexp),
0 + a =a= a.
Lemma plus_zero_equiv: ∀(a: aexp),
a + 0 =a= a.
Lemma minus_zero_equiv: ∀(a: aexp),
a - 0 =a= a.
Lemma zero_mult_equiv: ∀(a: aexp),
0 * a =a= 0.
Lemma mult_zero_equiv: ∀(a: aexp),
a * 0 =a= 0.
Lemma const_plus_const: ∀n m: Z,
ANum n + ANum m =a= ANum (n + m).
Lemma const_minus_const: ∀n m: Z,
ANum n - ANum m =a= ANum (n - m).
Lemma const_mult_const: ∀n m: Z,
ANum n * ANum m =a= ANum (n * m).
End AEval_first_try.
simpl.
lia.
Qed.
Lemma zero_plus_equiv: ∀(a: aexp),
0 + a =a= a.
Proof.
intros.
unfold aexp_equiv.
intros.
simpl.
lia.
Qed.
intros.
unfold aexp_equiv.
intros.
simpl.
lia.
Qed.
Lemma plus_zero_equiv: ∀(a: aexp),
a + 0 =a= a.
Proof.
intros.
unfold aexp_equiv.
intros.
simpl.
lia.
Qed.
intros.
unfold aexp_equiv.
intros.
simpl.
lia.
Qed.
Lemma minus_zero_equiv: ∀(a: aexp),
a - 0 =a= a.
Proof.
intros.
unfold aexp_equiv.
intros.
simpl.
lia.
Qed.
intros.
unfold aexp_equiv.
intros.
simpl.
lia.
Qed.
Lemma zero_mult_equiv: ∀(a: aexp),
0 * a =a= 0.
Proof.
intros.
unfold aexp_equiv.
intros.
simpl.
lia.
Qed.
intros.
unfold aexp_equiv.
intros.
simpl.
lia.
Qed.
Lemma mult_zero_equiv: ∀(a: aexp),
a * 0 =a= 0.
Proof.
intros.
unfold aexp_equiv.
intros.
simpl.
lia.
Qed.
intros.
unfold aexp_equiv.
intros.
simpl.
lia.
Qed.
Lemma const_plus_const: ∀n m: Z,
ANum n + ANum m =a= ANum (n + m).
Proof.
intros.
unfold aexp_equiv.
intros.
simpl.
reflexivity.
Qed.
intros.
unfold aexp_equiv.
intros.
simpl.
reflexivity.
Qed.
Lemma const_minus_const: ∀n m: Z,
ANum n - ANum m =a= ANum (n - m).
Proof.
intros.
unfold aexp_equiv.
intros.
simpl.
reflexivity.
Qed.
intros.
unfold aexp_equiv.
intros.
simpl.
reflexivity.
Qed.
Lemma const_mult_const: ∀n m: Z,
ANum n * ANum m =a= ANum (n * m).
Proof.
intros.
unfold aexp_equiv.
intros.
simpl.
reflexivity.
Qed.
intros.
unfold aexp_equiv.
intros.
simpl.
reflexivity.
Qed.
End AEval_first_try.
Inductive types and recursive functions
Inductive tree: Type :=
| Leaf: tree
| Node (l: tree) (v: Z) (r: tree): tree.
| Leaf: tree
| Node (l: tree) (v: Z) (r: tree): tree.
This definition says: a tree is either an empty tree or a tree with left
subtree l, root node (whose value is v) and right subtree r. We can
define tree heights and tree sizes recursively.
Fixpoint tree_height (t: tree): Z :=
match t with
| Leaf ⇒ 0
| Node l v r ⇒ Z.max (tree_height l) (tree_height r) + 1
end.
Fixpoint tree_size (t: tree): Z :=
match t with
| Leaf ⇒ 0
| Node l v r ⇒ tree_size l + tree_size r + 1
end.
match t with
| Leaf ⇒ 0
| Node l v r ⇒ Z.max (tree_height l) (tree_height r) + 1
end.
Fixpoint tree_size (t: tree): Z :=
match t with
| Leaf ⇒ 0
| Node l v r ⇒ tree_size l + tree_size r + 1
end.
We can even define trees from trees. The following definition turns a tree
left-right reversed.
Fixpoint tree_reverse (t: tree): tree :=
match t with
| Leaf ⇒ Leaf
| Node l v r ⇒ Node (tree_reverse r) v (tree_reverse l)
end.
match t with
| Leaf ⇒ Leaf
| Node l v r ⇒ Node (tree_reverse r) v (tree_reverse l)
end.
For example, if t is the left tree, then tree_reverse t is the right
tree:
5 5
/ \ / \
3 9 9 3
/ \ / \
8 100 100 8
/ \ / \
3 9 9 3
/ \ / \
8 100 100 8
Example tree_reverse_example:
tree_reverse
(Node (Node Leaf 3 Leaf) 5 (Node (Node Leaf 8 Leaf) 9 (Node Leaf 100 Leaf)))
=
Node (Node (Node Leaf 100 Leaf) 9 (Node Leaf 8 Leaf)) 5 (Node Leaf 3 Leaf).
Proof. reflexivity. Qed.
tree_reverse
(Node (Node Leaf 3 Leaf) 5 (Node (Node Leaf 8 Leaf) 9 (Node Leaf 100 Leaf)))
=
Node (Node (Node Leaf 100 Leaf) 9 (Node Leaf 8 Leaf)) 5 (Node Leaf 3 Leaf).
Proof. reflexivity. Qed.
Structure Induction
Lemma size_nonneg: ∀t,
0 ≤ tree_size t.
Proof.
intros.
induction t.
+ (* base step *)
simpl.
lia.
+ (* induction step *)
simpl.
0 ≤ tree_size t.
Proof.
intros.
induction t.
+ (* base step *)
simpl.
lia.
+ (* induction step *)
simpl.
Here, we see two assumptions in the proof goal, IHt1 and IHt2. The
English abbreviation IH stands for induction hypothesis (归纳假设).
In this case, IHt1 and IHt2 are induction hypothese of left subtree
t1 and right subtree t2.
lia.
Qed.
Lemma height_le_size: ∀t,
tree_height t ≤ tree_size t.
Proof.
intros.
induction t; simpl.
Qed.
Lemma height_le_size: ∀t,
tree_height t ≤ tree_size t.
Proof.
intros.
induction t; simpl.
The tactic language in Coq provide semicolon ;. Specifically,
tac1 ; tac2 says do tac1, then do tac2 in every single proof goal
that tac1 generates. Semicolon is right associative.
+ lia.
+ pose proof size_nonneg t1.
pose proof size_nonneg t2.
lia.
+ pose proof size_nonneg t1.
pose proof size_nonneg t2.
lia.
Note that lia can also reason about Z.max and Z.min.
Qed.
We can also define properties recursively.
Fixpoint tree_ele_le (ub: Z) (t: tree): Prop :=
match t with
| Leaf ⇒ True
| Node l k r ⇒ tree_ele_le ub l ∧ k ≤ ub ∧ tree_ele_le ub r
end.
Fixpoint tree_ele_ge (lb: Z) (t: tree): Prop :=
match t with
| Leaf ⇒ True
| Node l k r ⇒ tree_ele_ge lb l ∧ k ≥ lb ∧ tree_ele_ge lb r
end.
Declare Scope tree_scope.
Local Open Scope tree_scope.
Notation "t <== n" := (tree_ele_le n t) (at level 49, no associativity): tree_scope.
Notation "t >== n" := (tree_ele_ge n t) (at level 49, no associativity): tree_scope.
match t with
| Leaf ⇒ True
| Node l k r ⇒ tree_ele_le ub l ∧ k ≤ ub ∧ tree_ele_le ub r
end.
Fixpoint tree_ele_ge (lb: Z) (t: tree): Prop :=
match t with
| Leaf ⇒ True
| Node l k r ⇒ tree_ele_ge lb l ∧ k ≥ lb ∧ tree_ele_ge lb r
end.
Declare Scope tree_scope.
Local Open Scope tree_scope.
Notation "t <== n" := (tree_ele_le n t) (at level 49, no associativity): tree_scope.
Notation "t >== n" := (tree_ele_ge n t) (at level 49, no associativity): tree_scope.
Here, tree_ele_le n t says every element in t is less than or equal to
n. Similarly, tree_ele_ge n t says every element in t is greater than
or equal to n. For Coq's notations, you do not need to fully understand
them.
Coq even allows us to write other recursive definitions based on <== and
>==.
Fixpoint low2high (t: tree): Prop :=
match t with
| Leaf ⇒ True
| Node l k r ⇒ low2high l ∧ l <== k ∧ r >== k ∧ low2high r
end.
Fixpoint high2low (t: tree): Prop :=
match t with
| Leaf ⇒ True
| Node l k r ⇒ high2low l ∧ l >== k ∧ r <== k ∧ high2low r
end.
match t with
| Leaf ⇒ True
| Node l k r ⇒ low2high l ∧ l <== k ∧ r >== k ∧ low2high r
end.
Fixpoint high2low (t: tree): Prop :=
match t with
| Leaf ⇒ True
| Node l k r ⇒ high2low l ∧ l >== k ∧ r <== k ∧ high2low r
end.
... and to prove interesting properties about them.
Lemma reverse_low2high: ∀t,
low2high t ->
high2low (tree_reverse t).
Proof.
intros.
induction t.
+ simpl.
exact I.
+ simpl in H.
low2high t ->
high2low (tree_reverse t).
Proof.
intros.
induction t.
+ simpl.
exact I.
+ simpl in H.
We can do simpl in assumptions as we do in conclusions.
simpl.
It seems that we need some properties about <== and >== for help
here. Let's prove them as separate lemmas.
Abort.
Lemma reverse_le: ∀n t,
t <== n ->
tree_reverse t <== n.
Proof.
intros.
induction t; simpl.
+ exact I.
+ simpl in H.
destruct H as [Ht1 [? Ht2]].
split; [| split].
Lemma reverse_le: ∀n t,
t <== n ->
tree_reverse t <== n.
Proof.
intros.
induction t; simpl.
+ exact I.
+ simpl in H.
destruct H as [Ht1 [? Ht2]].
split; [| split].
Here, tac; [ tac1 | tac2 | ... | tacn] means running tac first
which should generate n subgoals, and then executing tac1, tac2,
..., tacn on each of them respectively.
Moreover, sometimes there may be too many subgoals. You can write
tactics like tac; [ tac1 | tac2 | tac3 .. ] . It means running tac
first, and then executing tac1 and tac2 one the first and the second
subgoal, but doing tac3 on all rest subgoals.
(* WORKED IN CLASS *)
- apply IHt2.
exact Ht2.
- exact H.
- apply IHt1.
exact Ht1.
Qed.
Lemma reverse_ge: ∀n t,
t >== n ->
tree_reverse t >== n.
Proof.
intros.
induction t; simpl.
+ exact I.
+ simpl in H.
tauto.
- apply IHt2.
exact Ht2.
- exact H.
- apply IHt1.
exact Ht1.
Qed.
Lemma reverse_ge: ∀n t,
t >== n ->
tree_reverse t >== n.
Proof.
intros.
induction t; simpl.
+ exact I.
+ simpl in H.
tauto.
This tactic tauto is a solver for propositional connectives, i.e.
∧, ∨, ¬, ->, True and False.
Qed.
Lemma reverse_low2high: ∀t,
low2high t ->
high2low (tree_reverse t).
Proof.
(* WORKED IN CLASS *)
intros.
induction t; simpl.
+ exact I.
+ simpl in H.
pose proof reverse_le v t1.
pose proof reverse_ge v t2.
tauto.
Qed.
Lemma reverse_involutive: ∀t,
tree_reverse (tree_reverse t) = t.
Proof.
(* WORKED IN CLASS *)
intros.
induction t; simpl.
+ reflexivity.
+ rewrite IHt1, IHt2.
reflexivity.
Qed.
Lemma reverse_low2high: ∀t,
low2high t ->
high2low (tree_reverse t).
Proof.
(* WORKED IN CLASS *)
intros.
induction t; simpl.
+ exact I.
+ simpl in H.
pose proof reverse_le v t1.
pose proof reverse_ge v t2.
tauto.
Qed.
Lemma reverse_involutive: ∀t,
tree_reverse (tree_reverse t) = t.
Proof.
(* WORKED IN CLASS *)
intros.
induction t; simpl.
+ reflexivity.
+ rewrite IHt1, IHt2.
reflexivity.
Qed.
Lemma reverse_result_Leaf: ∀t,
tree_reverse t = Leaf ->
t = Leaf.
Proof.
intros.
tree_reverse t = Leaf ->
t = Leaf.
Proof.
intros.
The follow destruct command enables us to do case analysis. Is t an
empty tree?
destruct t.
If yes, the conclusion is already proved.
+ reflexivity.
If no, the assumption is impossible to be true.
+ simpl in H.
That is, if t is nonempty, then tree_reverse t is also nonempty.
A equation like H cannot hold since its left hand side and right hand
side are categorically different. The tactic discriminate can discover
such contradictions.
discriminate H.
Qed.
Qed.
The following property is comparatively nontrivial.
Lemma reverse_result_Node: ∀t t1 k t2,
tree_reverse t = Node t1 k t2 ->
t = Node (tree_reverse t2) k (tree_reverse t1).
Proof.
intros.
tree_reverse t = Node t1 k t2 ->
t = Node (tree_reverse t2) k (tree_reverse t1).
Proof.
intros.
In the following line, we explicitly give names to those case analysis
results. After case analysis, the empty-tree situation is obviously a
contradiction.
destruct t as [| t1' v t2']; simpl in H.
+ discriminate H.
+ discriminate H.
The nonempty situation is more interesting. The hypothesis H says:
- Node (tree_reverse t2') v (tree_reverse t1') = Node t1 k t2.
- tree_reverse t2' = t1
- tree_reverse t1' = t2
+ injection H as ? ? ?.
rewrite <- H, <- H0, <- H1.
rewrite <- H, <- H0, <- H1.
The symbol "!" here means to rewrite as many times as possible.
rewrite ! reverse_involutive.
reflexivity.
Qed.
reflexivity.
Qed.
Strengthing an Induction Hypothesis
Fixpoint same_structure (t1 t2: tree): Prop :=
match t1, t2 with
| Leaf, Leaf ⇒ True
| Leaf, Node _ _ _ ⇒ False
| Node _ _ _, Leaf ⇒ False
| Node l1 _ r1, Node l2 _ r2 ⇒ same_structure l1 l2 ∧ same_structure r1 r2
end.
match t1, t2 with
| Leaf, Leaf ⇒ True
| Leaf, Node _ _ _ ⇒ False
| Node _ _ _, Leaf ⇒ False
| Node l1 _ r1, Node l2 _ r2 ⇒ same_structure l1 l2 ∧ same_structure r1 r2
end.
In this definition, we see that we are allowed to do pattern match over two
items simultaneously.
Lemma same_structure_same_height: ∀t1 t2,
same_structure t1 t2 ->
tree_height t1 = tree_height t2.
Proof.
intros.
same_structure t1 t2 ->
tree_height t1 = tree_height t2.
Proof.
intros.
A natural idea here is to prove the theorem by structural induction over
t1. Then the induction hypothesis should tell us: t1's left/right
subtree has the same height with t2's left/right subtree.
induction t1.
For base step, we can do case analysis over t2; t2 must be empty
according to H.
+ destruct t2.
- reflexivity.
- simpl in H.
contradiction.
+
Abort.
- reflexivity.
- simpl in H.
contradiction.
+
Abort.
Oops! This is not what we expected! The induction hypothese should be about
t2's subtrees not t2 itself. But why this will happen in Coq? The
problem is that, at the point our proof script call induction, we have
already introduced t2 into the context — intuitively, we have told Coq,
"Let's consider some particular t2...". The tactic induction t1 says: we
are going to show the goal by induction on t1. That is, we are going to
prove, for all t1, that the proposition tree_height t1 = tree_height t2
is true on that particular t2. In such situations, we should strength our
induction hypothesis.
Lemma same_structure_same_height: ∀t1 t2,
same_structure t1 t2 ->
tree_height t1 = tree_height t2.
Proof.
intros.
same_structure t1 t2 ->
tree_height t1 = tree_height t2.
Proof.
intros.
Remember that revert is the reverse tactic of intros.
revert t2 H.
induction t1 as [| l1 IHl v1 r1 IHr]; intros.
induction t1 as [| l1 IHl v1 r1 IHr]; intros.
The base step should be similar.
+ destruct t2.
- reflexivity.
- simpl in H.
contradiction.
- reflexivity.
- simpl in H.
contradiction.
The induction hypothese are different now!
+ destruct t2 as [| l2 v2 r2]; simpl in H.
{ contradiction. }
destruct H.
{ contradiction. }
destruct H.
Now, we can get IH about t2's subtrees.
apply IHl in H.
apply IHr in H0.
simpl.
lia.
Qed.
apply IHr in H0.
simpl.
lia.
Qed.
Module AEval_lemmas.
Import AEval_first_try.
Local Open Scope DSem.
Now, we are already quite familiar with recursion and structural induction.
We will use them repeatedly when building the theory of denotational
semantics. Let's start with a very simple definition and a very simple
proof.
Fixpoint alength (a : aexp) : Z :=
match a with
| ANum n ⇒ 1
| AId X ⇒ 1
| APlus a1 a2 ⇒ (alength a1) + (alength a2) + 1
| AMinus a1 a2 ⇒ (alength a1) + (alength a2) + 1
| AMult a1 a2 ⇒ (alength a1) + (alength a2) + 1
end.
Lemma alength_pos: ∀a,
alength a > 0.
Proof.
intros.
induction a; simpl; lia.
Qed.
match a with
| ANum n ⇒ 1
| AId X ⇒ 1
| APlus a1 a2 ⇒ (alength a1) + (alength a2) + 1
| AMinus a1 a2 ⇒ (alength a1) + (alength a2) + 1
| AMult a1 a2 ⇒ (alength a1) + (alength a2) + 1
end.
Lemma alength_pos: ∀a,
alength a > 0.
Proof.
intros.
induction a; simpl; lia.
Qed.
The Constant-Folding Transformation
Fixpoint fold_constants_aexp (a : aexp) : aexp :=
match a with
| ANum n ⇒ ANum n
| AId x ⇒ AId x
| APlus a1 a2 ⇒
match fold_constants_aexp a1, fold_constants_aexp a2 with
| ANum n1, ANum n2 ⇒ ANum (n1 + n2)
| _, _ ⇒ APlus (fold_constants_aexp a1) (fold_constants_aexp a2)
end
| AMinus a1 a2 ⇒
match fold_constants_aexp a1, fold_constants_aexp a2 with
| ANum n1, ANum n2 ⇒ ANum (n1 - n2)
| _, _ ⇒ AMinus (fold_constants_aexp a1) (fold_constants_aexp a2)
end
| AMult a1 a2 ⇒
match fold_constants_aexp a1, fold_constants_aexp a2 with
| ANum n1, ANum n2 ⇒ ANum (n1 * n2)
| _, _ ⇒ AMult (fold_constants_aexp a1) (fold_constants_aexp a2)
end
end.
match a with
| ANum n ⇒ ANum n
| AId x ⇒ AId x
| APlus a1 a2 ⇒
match fold_constants_aexp a1, fold_constants_aexp a2 with
| ANum n1, ANum n2 ⇒ ANum (n1 + n2)
| _, _ ⇒ APlus (fold_constants_aexp a1) (fold_constants_aexp a2)
end
| AMinus a1 a2 ⇒
match fold_constants_aexp a1, fold_constants_aexp a2 with
| ANum n1, ANum n2 ⇒ ANum (n1 - n2)
| _, _ ⇒ AMinus (fold_constants_aexp a1) (fold_constants_aexp a2)
end
| AMult a1 a2 ⇒
match fold_constants_aexp a1, fold_constants_aexp a2 with
| ANum n1, ANum n2 ⇒ ANum (n1 * n2)
| _, _ ⇒ AMult (fold_constants_aexp a1) (fold_constants_aexp a2)
end
end.
Here, we see that the match expressions in Coq are very flexible. (1) We
can apply pattern matching on not only Coq variables but also any Coq expression
whose type is inductively defined. (2) We can apply pattern matching on two
expressions at the same time. (3) We can use underscore _ to cover default
cases.
Module fold_const_example.
Example ex1 : ∀(X: var),
fold_constants_aexp ((1 + 2) * X)
= (3 * X)%imp.
Proof. intros. reflexivity. Qed.
Example ex1 : ∀(X: var),
fold_constants_aexp ((1 + 2) * X)
= (3 * X)%imp.
Proof. intros. reflexivity. Qed.
Note that this version of constant folding doesn't eliminate trivial
additions, etc. — we are focusing attention on a single optimization for the
sake of simplicity. It is not hard to incorporate other ways of simplifying
expressions; the definitions and proofs just get longer.
Example fold_aexp_ex2 : ∀(X: var) (Y: var),
fold_constants_aexp (X - ((0 * 6) + Y))%imp = (X - (0 + Y))%imp.
Proof. intros. reflexivity. Qed.
End fold_const_example.
fold_constants_aexp (X - ((0 * 6) + Y))%imp = (X - (0 + Y))%imp.
Proof. intros. reflexivity. Qed.
End fold_const_example.
Theorem fold_constants_aexp_sound : ∀a,
fold_constants_aexp a =a= a.
fold_constants_aexp a =a= a.
Proof.
unfold aexp_equiv. intros.
induction a.
+ (* ANum case *)
simpl.
reflexivity.
+ (* AId case *)
simpl.
reflexivity.
+ (* APlus case *)
simpl.
destruct (fold_constants_aexp a1), (fold_constants_aexp a2);
rewrite <- IHa1, <- IHa2;
reflexivity.
+ (* AMinus case *)
simpl.
destruct (fold_constants_aexp a1), (fold_constants_aexp a2);
rewrite <- IHa1, <- IHa2;
reflexivity.
+ (* AMult case *)
simpl.
destruct (fold_constants_aexp a1), (fold_constants_aexp a2);
rewrite <- IHa1, <- IHa2;
reflexivity.
Qed.
unfold aexp_equiv. intros.
induction a.
+ (* ANum case *)
simpl.
reflexivity.
+ (* AId case *)
simpl.
reflexivity.
+ (* APlus case *)
simpl.
destruct (fold_constants_aexp a1), (fold_constants_aexp a2);
rewrite <- IHa1, <- IHa2;
reflexivity.
+ (* AMinus case *)
simpl.
destruct (fold_constants_aexp a1), (fold_constants_aexp a2);
rewrite <- IHa1, <- IHa2;
reflexivity.
+ (* AMult case *)
simpl.
destruct (fold_constants_aexp a1), (fold_constants_aexp a2);
rewrite <- IHa1, <- IHa2;
reflexivity.
Qed.
After proving this soundness property, we want to prove that this
optimization really improves something.
Arguments Z.add: simpl never.
Lemma fold_constants_aexp_improve : ∀a,
alength (fold_constants_aexp a) ≤ alength a.
Lemma fold_constants_aexp_improve : ∀a,
alength (fold_constants_aexp a) ≤ alength a.
Proof.
intros.
induction a.
+ simpl.
lia.
+ simpl.
lia.
+ simpl.
destruct (fold_constants_aexp a1), (fold_constants_aexp a2);
simpl in IHa1;
simpl in IHa2;
simpl;
lia.
+ simpl.
destruct (fold_constants_aexp a1), (fold_constants_aexp a2);
simpl in IHa1;
simpl in IHa2;
simpl;
lia.
+ simpl.
destruct (fold_constants_aexp a1), (fold_constants_aexp a2);
simpl in IHa1;
simpl in IHa2;
simpl;
lia.
Qed.
intros.
induction a.
+ simpl.
lia.
+ simpl.
lia.
+ simpl.
destruct (fold_constants_aexp a1), (fold_constants_aexp a2);
simpl in IHa1;
simpl in IHa2;
simpl;
lia.
+ simpl.
destruct (fold_constants_aexp a1), (fold_constants_aexp a2);
simpl in IHa1;
simpl in IHa2;
simpl;
lia.
+ simpl.
destruct (fold_constants_aexp a1), (fold_constants_aexp a2);
simpl in IHa1;
simpl in IHa2;
simpl;
lia.
Qed.
Definition max32: Z := 2^31 -1.
Definition min32: Z := - 2^31.
Fixpoint signed32_eval (st: state) (a: aexp): Prop :=
min32 ≤ aeval st a ≤ max32 ∧
match a with
| ANum n ⇒ True
| AId X ⇒ True
| APlus a1 a2 ⇒ signed32_eval st a1 ∧ signed32_eval st a2
| AMinus a1 a2 ⇒ signed32_eval st a1 ∧ signed32_eval st a2
| AMult a1 a2 ⇒ signed32_eval st a1 ∧ signed32_eval st a2
end.
In short, signed32_eval a st says that evaluating a on state st is
within the range of signed 32-bit integers (including all intermediate
results). Now, we will show that constant-folding does not only preserve
evaluation result but also keep evaluation process within 32-bit range.
Lemma fold_constants_aexp_signed32: ∀st a,
signed32_eval st a ->
signed32_eval st (fold_constants_aexp a).
End AEval_lemmas.
signed32_eval st a ->
signed32_eval st (fold_constants_aexp a).
Proof.
intros.
induction a; simpl in H; simpl.
+ exact H.
+ exact H.
+ destruct H as [? [? ?]].
assert (signed32_eval st (fold_constants_aexp a1 + fold_constants_aexp a2)).
{
simpl.
rewrite ! fold_constants_aexp_sound.
tauto.
}
destruct (fold_constants_aexp a1), (fold_constants_aexp a2);
[ simpl in *; lia | exact H2 ..].
+ destruct H as [? [? ?]].
assert (signed32_eval st (fold_constants_aexp a1 - fold_constants_aexp a2)).
{
simpl.
rewrite ! fold_constants_aexp_sound.
tauto.
}
destruct (fold_constants_aexp a1), (fold_constants_aexp a2);
[ simpl in *; lia | exact H2 ..].
+ destruct H as [? [? ?]].
assert (signed32_eval st (fold_constants_aexp a1 * fold_constants_aexp a2)).
{
simpl.
rewrite ! fold_constants_aexp_sound.
tauto.
}
destruct (fold_constants_aexp a1), (fold_constants_aexp a2);
[ simpl in *; lia | exact H2 ..].
Qed.
intros.
induction a; simpl in H; simpl.
+ exact H.
+ exact H.
+ destruct H as [? [? ?]].
assert (signed32_eval st (fold_constants_aexp a1 + fold_constants_aexp a2)).
{
simpl.
rewrite ! fold_constants_aexp_sound.
tauto.
}
destruct (fold_constants_aexp a1), (fold_constants_aexp a2);
[ simpl in *; lia | exact H2 ..].
+ destruct H as [? [? ?]].
assert (signed32_eval st (fold_constants_aexp a1 - fold_constants_aexp a2)).
{
simpl.
rewrite ! fold_constants_aexp_sound.
tauto.
}
destruct (fold_constants_aexp a1), (fold_constants_aexp a2);
[ simpl in *; lia | exact H2 ..].
+ destruct H as [? [? ?]].
assert (signed32_eval st (fold_constants_aexp a1 * fold_constants_aexp a2)).
{
simpl.
rewrite ! fold_constants_aexp_sound.
tauto.
}
destruct (fold_constants_aexp a1), (fold_constants_aexp a2);
[ simpl in *; lia | exact H2 ..].
Qed.
End AEval_lemmas.
Additional Reading
- Basics
- Induction
- Lists
- Tactics.
(* 2021-03-09 23:58 *)