Require Import Coq.Program.Equality.
Require Import FSets.
Require Import Imp.
Require Import Sequences.
Require Import Semantics.
In this chapter: liveness analysis and its use for an optimization
called dead code elimination.
1. Sets of variables
The static analysis we need -- liveness analysis -- operates over
sets of variables. Coq's standard library provides a rich, efficient
implementation of finite sets. Before being able to use it, however,
we must provide it with a Coq module equipping the type of identifiers
with a total, decidable ordering. The implementation of this module follows.
Module Id_Ordered <:
OrderedType with Definition t :=
id.
Definition t :=
id.
Definition eq (
x y:
t) :=
x =
y.
Definition lt (
x y:
t) :=
match x,
y with Id nx,
Id ny =>
nx <
ny end.
Lemma eq_refl :
forall x :
t,
eq x x.
Proof.
intro. reflexivity. Qed.
Lemma eq_sym :
forall x y :
t,
eq x y ->
eq y x.
Proof.
unfold eq; intros; auto. Qed.
Lemma eq_trans :
forall x y z :
t,
eq x y ->
eq y z ->
eq x z.
Proof.
unfold eq; intros; congruence. Qed.
Lemma lt_trans :
forall x y z :
t,
lt x y ->
lt y z ->
lt x z.
Proof.
unfold lt; intros. destruct x; destruct y; destruct z. omega. Qed.
Lemma lt_not_eq :
forall x y :
t,
lt x y -> ~
eq x y.
Proof.
unfold lt, eq; intros. destruct x; destruct y.
assert (n <> n0) by omega. congruence.
Qed.
Definition compare:
forall (
x y:
t),
Compare lt eq x y.
Proof.
intros.
case x;
intro nx.
case y;
intro ny.
remember (
beq_nat nx ny).
destruct b.
apply EQ.
red.
f_equal.
apply beq_nat_true.
auto.
assert (
nx <>
ny).
apply beq_nat_false.
auto.
remember (
ble_nat nx ny).
destruct b.
assert (
nx <=
ny).
apply ble_nat_true;
auto.
apply LT.
red.
omega.
assert (~(
nx <=
ny)).
apply ble_nat_false;
auto.
apply GT.
red.
omega.
Defined.
Definition eq_dec:
forall (
x y:
t), {
x=
y} + {
x<>
y}.
Proof.
intros;
destruct x;
destruct y.
remember (
beq_nat n n0);
destruct b.
left.
f_equal.
apply beq_nat_true.
auto.
right.
assert (
n <>
n0).
apply beq_nat_false;
auto.
congruence.
Defined.
End Id_Ordered.
We then instantiate the finite sets modules from Coq's standard library
with this ordered type of integers.
Module VS :=
FSetAVL.Make(
Id_Ordered).
Module VSP :=
FSetProperties.Properties(
VS).
Module VSdecide :=
FSetDecide.Decide(
VS).
Import VSdecide.
2. Liveness analysis
Free variables
Computation of the set of variables appearing in an expression or command.
Fixpoint fv_aexp (
a:
aexp) :
VS.t :=
match a with
|
ANum n =>
VS.empty
|
AId v =>
VS.singleton v
|
APlus a1 a2 =>
VS.union (
fv_aexp a1) (
fv_aexp a2)
|
AMinus a1 a2 =>
VS.union (
fv_aexp a1) (
fv_aexp a2)
|
AMult a1 a2 =>
VS.union (
fv_aexp a1) (
fv_aexp a2)
end.
Fixpoint fv_bexp (
b:
bexp) :
VS.t :=
match b with
|
BTrue =>
VS.empty
|
BFalse =>
VS.empty
|
BEq a1 a2 =>
VS.union (
fv_aexp a1) (
fv_aexp a2)
|
BLe a1 a2 =>
VS.union (
fv_aexp a1) (
fv_aexp a2)
|
BNot b1 =>
fv_bexp b1
|
BAnd b1 b2 =>
VS.union (
fv_bexp b1) (
fv_bexp b2)
end.
Fixpoint fv_com (
c:
com) :
VS.t :=
match c with
|
SKIP =>
VS.empty
|
x ::=
a =>
fv_aexp a
| (
c1;
c2) =>
VS.union (
fv_com c1) (
fv_com c2)
|
IFB b THEN c1 ELSE c2 FI =>
VS.union (
fv_bexp b) (
VS.union (
fv_com c1) (
fv_com c2))
|
WHILE b DO c END =>
VS.union (
fv_bexp b) (
fv_com c)
end.
Computing post-fixpoints.
Section FIXPOINT.
Variable F:
VS.t ->
VS.t.
Variable default:
VS.t.
Our goal is to find a set of variables x such that F x is a subset of x
(a post-fixpoint of F). We use a naive algorithm: iterate F at most n times,
stopping as soon as a post-fixpoint is encountered. If not,
we return a safe default result.
Fixpoint iter (
n:
nat) (
x:
VS.t) :
VS.t :=
match n with
|
O =>
default
|
S n' =>
let x' :=
F x in
if VS.subset x'
x then x else iter n'
x'
end.
Definition niter := 10%
nat.
Definition fixpoint :
VS.t :=
iter niter VS.empty.
Lemma fixpoint_charact:
VS.Subset (
F fixpoint)
fixpoint \/
fixpoint =
default.
Proof.
Hypothesis F_stable:
forall x,
VS.Subset x default ->
VS.Subset (
F x)
default.
Lemma fixpoint_upper_bound:
VS.Subset fixpoint default.
Proof.
End FIXPOINT.
Liveness analysis.
L is the set of variables live "after" command c.
The result of live c L is the set of variables live "before" c.
Fixpoint live (
c:
com) (
L:
VS.t) :
VS.t :=
match c with
|
SKIP =>
L
|
x ::=
a =>
if VS.mem x L
then VS.union (
VS.remove x L) (
fv_aexp a)
else L
| (
c1;
c2) =>
live c1 (
live c2 L)
|
IFB b THEN c1 ELSE c2 FI =>
VS.union (
fv_bexp b) (
VS.union (
live c1 L) (
live c2 L))
|
WHILE b DO c END =>
let L' :=
VS.union (
fv_bexp b)
L in
let default :=
VS.union (
fv_com (
CWhile b c))
L in
fixpoint (
fun x =>
VS.union L' (
live c x))
default
end.
Lemma live_upper_bound:
forall c L,
VS.Subset (
live c L) (
VS.union (
fv_com c)
L).
Proof.
induction c;
intros;
simpl.
fsetdec.
case_eq (
VS.mem i L);
intros.
fsetdec.
fsetdec.
generalize (
IHc1 (
live c2 L)).
generalize (
IHc2 L).
fsetdec.
generalize (
IHc1 L).
generalize (
IHc2 L).
fsetdec.
apply fixpoint_upper_bound.
intro x.
generalize (
IHc x).
fsetdec.
Qed.
Lemma live_while_charact:
forall b c L,
let L' :=
live (
WHILE b DO c END)
L in
VS.Subset (
fv_bexp b)
L' /\
VS.Subset L L' /\
VS.Subset (
live c L')
L'.
Proof.
3. Dead code elimination
Code transformation
The code transformation turns assignments x ::= a to dead variables
into SKIP statements.
Fixpoint dce (
c:
com) (
L:
VS.t):
com :=
match c with
|
SKIP =>
SKIP
|
x ::=
a =>
if VS.mem x L then x ::=
a else SKIP
| (
c1;
c2) => (
dce c1 (
live c2 L);
dce c2 L)
|
IFB b THEN c1 ELSE c2 FI =>
IFB b THEN dce c1 L ELSE dce c2 L FI
|
WHILE b DO c END =>
WHILE b DO dce c (
live (
WHILE b DO c END)
L)
END
end.
Example of optimization.
Notation va := (
Id 0).
Notation vb := (
Id 1).
Notation vq := (
Id 2).
Notation vr := (
Id 3).
Definition prog :=
(
vr ::=
AId va;
vq ::=
ANum 0;
WHILE BLe (
AId vb) (
AId vr)
DO
vr ::=
AMinus (
AId vr) (
AId vb);
vq ::=
APlus (
AId vq) (
ANum 1)
END ).
Eval vm_compute in (
dce prog (
VS.singleton vr)).
Result is:
r := a; ===> r := a;
q := 0; skip;
while b <= r do while b <= r do
r := r - b; r := r - b;
q := q + 1; skip;
done done
Eval vm_compute in (
dce prog (
VS.singleton vq)).
Program is unchanged.
Semantic correctness
Two states agree on a set L of live variables if they assign
the same values to each live variable.
Definition agree (
L:
VS.t) (
s1 s2:
state) :
Prop :=
forall x,
VS.In x L ->
s1 x =
s2 x.
Monotonicity property.
Lemma agree_mon:
forall L L'
s1 s2,
agree L'
s1 s2 ->
VS.Subset L L' ->
agree L s1 s2.
Proof.
unfold VS.Subset, agree; intros. auto.
Qed.
Agreement on the free variables of an expression implies that this
expression evaluates identically in both states.
Lemma aeval_agree:
forall L s1 s2,
agree L s1 s2 ->
forall a,
VS.Subset (
fv_aexp a)
L ->
aeval s1 a =
aeval s2 a.
Proof.
induction a; simpl; intros.
auto.
apply H. generalize H0; fsetdec.
f_equal. apply IHa1. generalize H0; fsetdec. apply IHa2. generalize H0; fsetdec.
f_equal. apply IHa1. generalize H0; fsetdec. apply IHa2. generalize H0; fsetdec.
f_equal. apply IHa1. generalize H0; fsetdec. apply IHa2. generalize H0; fsetdec.
Qed.
Lemma beval_agree:
forall L s1 s2,
agree L s1 s2 ->
forall b,
VS.Subset (
fv_bexp b)
L ->
beval s1 b =
beval s2 b.
Proof.
induction b;
simpl;
intros.
auto.
auto.
repeat rewrite (
aeval_agree L s1 s2);
auto;
generalize H0;
fsetdec.
repeat rewrite (
aeval_agree L s1 s2);
auto;
generalize H0;
fsetdec.
f_equal;
apply IHb;
auto.
f_equal.
apply IHb1.
generalize H0;
fsetdec.
apply IHb2.
generalize H0;
fsetdec.
Qed.
Agreement is preserved by simultaneous assignment to a live variable.
Lemma agree_update_live:
forall s1 s2 L x v,
agree (
VS.remove x L)
s1 s2 ->
agree L (
update s1 x v) (
update s2 x v).
Proof.
intros;
red;
intros.
unfold update.
remember (
beq_id x x0).
destruct b.
auto.
apply H.
apply VS.remove_2.
apply beq_id_false_not_eq.
auto.
auto.
Qed.
Agreement is also preserved by unilateral assignment to a dead variable.
Lemma agree_update_dead:
forall s1 s2 L x v,
agree L s1 s2 -> ~
VS.In x L ->
agree L (
update s1 x v)
s2.
Proof.
intros; red; intros. unfold update. remember (beq_id x x0). destruct b.
assert (x = x0). apply beq_id_eq; auto. subst x0. contradiction.
apply H; auto.
Qed.
We now show that dead code elimination preserves semantics for terminating
source program. We use big-step semantics to show the following diagram:
agree (live c L) st st1
c / st ----------------------------- dce C L / st1
| |
| |
| |
v v
st' -------------------------------------- st1'
agree L st' st1'
The proof is a simple induction on the big-step evaluation derivation on the left.
Theorem dce_correct_terminating:
forall st c st',
c /
st ||
st' ->
forall L st1,
agree (
live c L)
st st1 ->
exists st1',
dce c L /
st1 ||
st1' /\
agree L st'
st1'.
Proof.
induction 1;
intros;
simpl.
Case "
skip".
exists st1;
split.
constructor.
auto.
Case ":=".
rename l into x.
simpl in H0.
remember (
VS.mem x L)
as is_live.
destruct is_live.
SCase "
x is live after".
assert (
aeval st1 a1 =
n).
rewrite <-
H.
symmetry.
eapply aeval_agree.
eauto.
fsetdec.
exists (
update st1 x n);
split.
apply E_Ass.
auto.
apply agree_update_live.
eapply agree_mon.
eauto.
fsetdec.
SCase "
x is dead after".
exists st1;
split.
apply E_Skip.
apply agree_update_dead.
auto.
red;
intros.
assert (
VS.mem x L =
true).
apply VS.mem_1;
auto.
congruence.
Case "
seq".
simpl in H1.
destruct (
IHceval1 _ _ H1)
as [
st1' [
E1 A1]].
destruct (
IHceval2 _ _ A1)
as [
st2' [
E2 A2]].
exists st2';
split.
apply E_Seq with st1';
auto.
auto.
Case "
if true".
simpl in H1.
assert (
beval st1 b1 =
true).
rewrite <-
H.
symmetry.
eapply beval_agree;
eauto.
fsetdec.
destruct (
IHceval L st1)
as [
st1' [
E A]].
eapply agree_mon;
eauto.
fsetdec.
exists st1';
split.
apply E_IfTrue;
auto.
auto.
Case "
if false".
simpl in H1.
assert (
beval st1 b1 =
false).
rewrite <-
H.
symmetry.
eapply beval_agree;
eauto.
fsetdec.
destruct (
IHceval L st1)
as [
st1' [
E A]].
eapply agree_mon;
eauto.
fsetdec.
exists st1';
split.
apply E_IfFalse;
auto.
auto.
Case "
while end".
destruct (
live_while_charact b1 c1 L)
as [
P [
Q R]].
assert (
beval st1 b1 =
false).
rewrite <-
H.
symmetry.
eapply beval_agree;
eauto.
exists st1;
split.
apply E_WhileEnd.
auto.
eapply agree_mon;
eauto.
Case "
while loop".
destruct (
live_while_charact b1 c1 L)
as [
P [
Q R]].
assert (
beval st1 b1 =
true).
rewrite <-
H.
symmetry.
eapply beval_agree;
eauto.
destruct (
IHceval1 (
live (
CWhile b1 c1)
L)
st1)
as [
st2 [
E1 A1]].
eapply agree_mon;
eauto.
destruct (
IHceval2 L st2)
as [
st3 [
E2 A2]].
auto.
exists st3;
split.
apply E_WhileLoop with st2;
auto.
auto.
Qed.
Exercise (4 stars)
To prove semantic preservation both for terminating and diverging programs.
complete the following simulation proof, which uses the small-step semantics
of Imp, without continuations.
Fixpoint measure (
c:
com) :
nat :=
match c with
| (
x ::=
a) => 1
| (
c1;
c2) =>
measure c1
|
_ => 0
end.
Theorem dce_simulation:
forall c st c'
st',
c /
st -->
c' /
st' ->
forall L st1,
agree (
live c L)
st st1 ->
(
exists st1',
dce c L /
st1 -->
dce c'
L /
st1' /\
agree (
live c'
L)
st'
st1')
\/
(
measure c' <
measure c /\
dce c L =
dce c'
L /\
agree (
live c'
L)
st'
st1).
Proof.
intros until st'. intro STEP. dependent induction STEP; simpl; intros.
Admitted.