Lecture notes 20210512 Nondeterminism and Nontermination
Require Import PL.RTClosure PL.Imp PL.ImpExt PL.ImpExt2.
Before this lecture, we only discuss deterministic languages. The simple
imperative language is deterministic; the language with control flow is
deterministic; the language with function call is deterministic and the
language with integer division is also deterministic. However, realistic
programming languages like C are nondeterministic.
For example, the result of malloc is nondeterministic. When it succeeds,
it allocate a new piece of memory, arbitrarily. Also, when evaluating one
expression with two function calls involved, e.g.
the evaluation order is unspecified. A compiler can choose to evaluate
f(x) first; a compiler can choose to evaluate f(y) first; and a compiler
can even choose to pick a different order in different executions. We will
discuss programming languages with nondeterministic behavior today.
In fact, determinism can be interpret in two different ways.
f(x) + f(y)
Determinism
- A. A programming language is deterministic if every program only has one
possible outcome. An outcome can be (1) terminating on some program state
(2) not terminating and keep running forever or maybe (3) crushing, etc.
- B. A programming language is deterministic if every intermediate state of execution has at most one possible ``next step''.
Definition Deterministic_D: Prop :=
∀c st1 st2 st2',
ceval c st1 st2 ->
ceval c st1 st2' ->
Func.equiv st2 st2'.
Definition Deterministic_S: Prop :=
∀c1 st1 c2 st2 c2' st2',
cstep (c1, st1) (c2, st2) ->
cstep (c1, st1) (c2', st2') ->
c2 = c2' ∧ Func.equiv st2 st2'.
∀c st1 st2 st2',
ceval c st1 st2 ->
ceval c st1 st2' ->
Func.equiv st2 st2'.
Definition Deterministic_S: Prop :=
∀c1 st1 c2 st2 c2' st2',
cstep (c1, st1) (c2, st2) ->
cstep (c1, st1) (c2', st2') ->
c2 = c2' ∧ Func.equiv st2 st2'.
The simple imperative language and its extensions that we have discussed are
all deterministic under either interpretation.
Module ComCChoice.
Inductive com : Type :=
| CSkip
| CAss (X: var) (a : aexp)
| CSeq (c1 c2 : com)
| CIf (b : bexp) (c1 c2 : com)
| CWhile (b : bexp) (c : com)
| CChoice (c1 c2: com). (* <-- new *)
Notation "x '::=' a" :=
(CAss x a) (at level 80) : imp_scope.
Notation "'Skip'" :=
CSkip : imp_scope.
Notation "c1 ;; c2" :=
(CSeq c1 c2) (at level 80, right associativity) : imp_scope.
Notation "'While' b 'Do' c 'EndWhile'" :=
(CWhile b c) (at level 80, right associativity) : imp_scope.
Notation "'If' c1 'Then' c2 'Else' c3 'EndIf'" :=
(CIf c1 c2 c3) (at level 10, right associativity) : imp_scope.
Coercion AId: var >-> aexp.
Local Open Scope imp.
| CSkip
| CAss (X: var) (a : aexp)
| CSeq (c1 c2 : com)
| CIf (b : bexp) (c1 c2 : com)
| CWhile (b : bexp) (c : com)
| CChoice (c1 c2: com). (* <-- new *)
Notation "x '::=' a" :=
(CAss x a) (at level 80) : imp_scope.
Notation "'Skip'" :=
CSkip : imp_scope.
Notation "c1 ;; c2" :=
(CSeq c1 c2) (at level 80, right associativity) : imp_scope.
Notation "'While' b 'Do' c 'EndWhile'" :=
(CWhile b c) (at level 80, right associativity) : imp_scope.
Notation "'If' c1 'Then' c2 'Else' c3 'EndIf'" :=
(CIf c1 c2 c3) (at level 10, right associativity) : imp_scope.
Coercion AId: var >-> aexp.
Local Open Scope imp.
Here, we add one more constructor to the syntax trees of program commands.
To execute CChoice c1 c2 is to execute either c1 or c2. A compiler or
some external environment may decide which one to choose.
In this language, we can write a program with different outcomes.
Module sample_program.
Definition X: var := 1%nat.
Definition Y: var := 2%nat.
Definition prog_sample_1: com :=
CChoice (X ::= X + 1) (X ::= X - 1).
Definition prog_sample_2: com :=
CChoice (X ::= X + Y) (X ::= Y + X).
Definition prog_sample_3: com :=
CChoice (X ::= X + Y) (X ::= X - 1;; X ::= Y + X + 1).
Definition prog_sample_4: com :=
CChoice (X ::= 0) (While BTrue Do X ::= X - 1 EndWhile).
End sample_program.
Definition X: var := 1%nat.
Definition Y: var := 2%nat.
Definition prog_sample_1: com :=
CChoice (X ::= X + 1) (X ::= X - 1).
Definition prog_sample_2: com :=
CChoice (X ::= X + Y) (X ::= Y + X).
Definition prog_sample_3: com :=
CChoice (X ::= X + Y) (X ::= X - 1;; X ::= Y + X + 1).
Definition prog_sample_4: com :=
CChoice (X ::= 0) (While BTrue Do X ::= X - 1 EndWhile).
End sample_program.
Small step semantics
cstep (CChoice c1 c2, st) (c1, st),
cstep (CChoice c1 c2, st) (c2, st).
cstep (CChoice c1 c2, st) (c2, st).
Inductive cstep : (com * state) -> (com * state) -> Prop :=
| CS_AssStep : ∀st X a a',
astep st a a' ->
cstep (CAss X a, st) (CAss X a', st)
| CS_Ass : ∀st1 st2 X n,
st2 X = n ->
(∀Y, X ≠ Y -> st1 Y = st2 Y) ->
cstep (CAss X (ANum n), st1) (Skip, st2)
| CS_SeqStep : ∀st c1 c1' st' c2,
cstep (c1, st) (c1', st') ->
cstep (c1 ;; c2 , st) (c1' ;; c2, st')
| CS_Seq : ∀st c2,
cstep (Skip ;; c2, st) (c2, st)
| CS_IfStep : ∀st b b' c1 c2,
bstep st b b' ->
cstep
(If b Then c1 Else c2 EndIf, st)
(If b' Then c1 Else c2 EndIf, st)
| CS_IfTrue : ∀st c1 c2,
cstep (If BTrue Then c1 Else c2 EndIf, st) (c1, st)
| CS_IfFalse : ∀st c1 c2,
cstep (If BFalse Then c1 Else c2 EndIf, st) (c2, st)
| CS_While : ∀st b c,
cstep
(While b Do c EndWhile, st)
(If b Then (c;; While b Do c EndWhile) Else Skip EndIf, st)
| CS_Choice_Left : ∀st c1 c2, (* <-- new *)
cstep (CChoice c1 c2, st) (c1, st)
| CS_Choice_Right : ∀st c1 c2, (* <-- new *)
cstep (CChoice c1 c2, st) (c2, st).
| CS_AssStep : ∀st X a a',
astep st a a' ->
cstep (CAss X a, st) (CAss X a', st)
| CS_Ass : ∀st1 st2 X n,
st2 X = n ->
(∀Y, X ≠ Y -> st1 Y = st2 Y) ->
cstep (CAss X (ANum n), st1) (Skip, st2)
| CS_SeqStep : ∀st c1 c1' st' c2,
cstep (c1, st) (c1', st') ->
cstep (c1 ;; c2 , st) (c1' ;; c2, st')
| CS_Seq : ∀st c2,
cstep (Skip ;; c2, st) (c2, st)
| CS_IfStep : ∀st b b' c1 c2,
bstep st b b' ->
cstep
(If b Then c1 Else c2 EndIf, st)
(If b' Then c1 Else c2 EndIf, st)
| CS_IfTrue : ∀st c1 c2,
cstep (If BTrue Then c1 Else c2 EndIf, st) (c1, st)
| CS_IfFalse : ∀st c1 c2,
cstep (If BFalse Then c1 Else c2 EndIf, st) (c2, st)
| CS_While : ∀st b c,
cstep
(While b Do c EndWhile, st)
(If b Then (c;; While b Do c EndWhile) Else Skip EndIf, st)
| CS_Choice_Left : ∀st c1 c2, (* <-- new *)
cstep (CChoice c1 c2, st) (c1, st)
| CS_Choice_Right : ∀st c1 c2, (* <-- new *)
cstep (CChoice c1 c2, st) (c2, st).
Denotational semantics
Module TheFirstTry.
Fixpoint ceval (c: com): state -> state -> Prop :=
match c with
| CSkip ⇒ BinRel.id
| CAss X E ⇒
fun st1 st2 ⇒
st2 X = aeval E st1 ∧
∀Y, X ≠ Y -> st1 Y = st2 Y
| CSeq c1 c2 ⇒ BinRel.concat (ceval c1) (ceval c2)
| CIf b c1 c2 ⇒ if_sem b (ceval c1) (ceval c2)
| CWhile b c ⇒ loop_sem b (ceval c)
| CChoice c1 c2 ⇒ BinRel.union (ceval c1) (ceval c2)
end.
End TheFirstTry.
Fixpoint ceval (c: com): state -> state -> Prop :=
match c with
| CSkip ⇒ BinRel.id
| CAss X E ⇒
fun st1 st2 ⇒
st2 X = aeval E st1 ∧
∀Y, X ≠ Y -> st1 Y = st2 Y
| CSeq c1 c2 ⇒ BinRel.concat (ceval c1) (ceval c2)
| CIf b c1 c2 ⇒ if_sem b (ceval c1) (ceval c2)
| CWhile b c ⇒ loop_sem b (ceval c)
| CChoice c1 c2 ⇒ BinRel.union (ceval c1) (ceval c2)
end.
End TheFirstTry.
That is, we add one line to our original denotational semantics. We define
the denotation of CChoice c1 c2 as the union of c1 and c2's
denotaitons.
Thus, we turn to consider the following domain for describing a denotational
semantics.
Record com_denote: Type := {
com_term: state -> state -> Prop; (* <-- ``term'' for terminate *)
com_div: state -> Prop (* <-- ``div'' for diverge *)
}.
com_term: state -> state -> Prop; (* <-- ``term'' for terminate *)
com_div: state -> Prop (* <-- ``div'' for diverge *)
}.
That is, we have to explicitly describe all possible outcomes. This is not
necessary for a deterministic language. Using the simple imperative language
as an example, if executing c from st1 does not terminate at any state
st2, then it will not terminate. Also, if executing c from st1 may
terminate at some state st2, then non-termination is impossible. Thus,
describing all terminating cases is enough. Using our language with control
flow commands as another example, we only defines whether executing c from
st1 will terminate normally at some state st2, or terminate by break at
some state st2, or terminate by continue at some st2. If none of the
above happens, this execution will not terminate. If some situation above
happens, then non-termination is impossible.
Now, in our new language with non-determinism, there can be different kinds
outcomes for a fixed beginning state st1 and a program c.
Module TheSecondAttempt.
The semantics for skips, assignments, sequentail compositions and
if-branches is easy to define.
Definition skip_sem: com_denote :=
{| com_term := BinRel.id;
com_div := Sets.empty;
|}.
Definition asgn_sem (X: var) (a: aexp): com_denote :=
{| com_term :=
fun st1 st2 ⇒
st2 X = aeval a st1 ∧
∀Y, X ≠ Y -> st1 Y = st2 Y;
com_div := Sets.empty
|}.
Definition seq_sem (DC1 DC2: com_denote): com_denote :=
{| com_term :=
BinRel.concat (com_term DC1) (com_term DC2);
com_div :=
Sets.union
(com_div DC1)
(BinRel.dia (com_term DC1) (com_div DC2))
|}.
Definition if_sem (b: bexp) (DC1 DC2: com_denote): com_denote :=
{| com_term :=
BinRel.union
(BinRel.concat (BinRel.test_rel (beval b)) (com_term DC1))
(BinRel.concat (BinRel.test_rel (beval (! b))) (com_term DC2));
com_div :=
Sets.union
(BinRel.dia (BinRel.test_rel (beval b)) (com_div DC1))
(BinRel.dia (BinRel.test_rel (beval (!b))) (com_div DC2))
|}.
{| com_term := BinRel.id;
com_div := Sets.empty;
|}.
Definition asgn_sem (X: var) (a: aexp): com_denote :=
{| com_term :=
fun st1 st2 ⇒
st2 X = aeval a st1 ∧
∀Y, X ≠ Y -> st1 Y = st2 Y;
com_div := Sets.empty
|}.
Definition seq_sem (DC1 DC2: com_denote): com_denote :=
{| com_term :=
BinRel.concat (com_term DC1) (com_term DC2);
com_div :=
Sets.union
(com_div DC1)
(BinRel.dia (com_term DC1) (com_div DC2))
|}.
Definition if_sem (b: bexp) (DC1 DC2: com_denote): com_denote :=
{| com_term :=
BinRel.union
(BinRel.concat (BinRel.test_rel (beval b)) (com_term DC1))
(BinRel.concat (BinRel.test_rel (beval (! b))) (com_term DC2));
com_div :=
Sets.union
(BinRel.dia (BinRel.test_rel (beval b)) (com_div DC1))
(BinRel.dia (BinRel.test_rel (beval (!b))) (com_div DC2))
|}.
The behavior of CChoice is defined as by unions.
Definition choice_sem (DC1 DC2: com_denote): com_denote :=
{| com_term :=
BinRel.union (com_term DC1) (com_term DC2);
com_div :=
Sets.union (com_div DC1) (com_div DC2)
|}.
{| com_term :=
BinRel.union (com_term DC1) (com_term DC2);
com_div :=
Sets.union (com_div DC1) (com_div DC2)
|}.
Loops' semantics is tricky. We first define the cases of terminating
executions.
Fixpoint iter_loop_body (b: bexp)
(DC: com_denote)
(n: nat): state -> state -> Prop :=
match n with
| O ⇒ BinRel.test_rel (beval (! b))
| S n' ⇒
BinRel.concat
(BinRel.test_rel (beval b))
(BinRel.concat
(com_term DC)
(iter_loop_body b DC n'))
end.
(DC: com_denote)
(n: nat): state -> state -> Prop :=
match n with
| O ⇒ BinRel.test_rel (beval (! b))
| S n' ⇒
BinRel.concat
(BinRel.test_rel (beval b))
(BinRel.concat
(com_term DC)
(iter_loop_body b DC n'))
end.
We then start to consider nonterminating cases.
Fixpoint live_after_iter (b: bexp)
(DC: com_denote)
(n: nat): state -> Prop :=
match n with
| O ⇒ Sets.full
| S n' ⇒
BinRel.dia
(BinRel.test_rel (beval b))
(Sets.union
(com_div DC)
(BinRel.dia
(com_term DC)
(live_after_iter b DC n')))
end.
(DC: com_denote)
(n: nat): state -> Prop :=
match n with
| O ⇒ Sets.full
| S n' ⇒
BinRel.dia
(BinRel.test_rel (beval b))
(Sets.union
(com_div DC)
(BinRel.dia
(com_term DC)
(live_after_iter b DC n')))
end.
When discussing the execution of While b Do c EndWhile, we use
live_after_iter b (ceval c) n
to represent the set of beginning states that:
For example, consider the following program.
Then,
- may fall into a diverging loop body in the first n-th iterations, or
- may complete at least n iterations.
While (! (X == 0)) Do
If (X == 1)
Then (CChoice
(While BTrue Do Skip EndWhile)
(X ::= 0))
Else X ::= X - 2
EndWhile
If (X == 1)
Then (CChoice
(While BTrue Do Skip EndWhile)
(X ::= 0))
Else X ::= X - 2
EndWhile
- live_after_iter _ _ 0 contains all states;
- live_after_iter _ _ 1 contains those states st such that st (X) is
nonzero;
- live_after_iter _ _ 2 contains those states st such that st (X) is neither 0 nor 2.
Definition loop_sem (b: bexp) (DC: com_denote): com_denote :=
{| com_term :=
BinRel.omega_union (iter_loop_body b DC);
com_div :=
Sets.omega_intersection (live_after_iter b DC)
|}.
Fixpoint ceval (c: com): com_denote :=
match c with
| CSkip ⇒ skip_sem
| CAss X E ⇒ asgn_sem X E
| CSeq c1 c2 ⇒ seq_sem (ceval c1) (ceval c2)
| CIf b c1 c2 ⇒ if_sem b (ceval c1) (ceval c2)
| CWhile b c ⇒ loop_sem b (ceval c)
| CChoice c1 c2 ⇒ choice_sem (ceval c1) (ceval c2)
end.
End TheSecondAttempt.
{| com_term :=
BinRel.omega_union (iter_loop_body b DC);
com_div :=
Sets.omega_intersection (live_after_iter b DC)
|}.
Fixpoint ceval (c: com): com_denote :=
match c with
| CSkip ⇒ skip_sem
| CAss X E ⇒ asgn_sem X E
| CSeq c1 c2 ⇒ seq_sem (ceval c1) (ceval c2)
| CIf b c1 c2 ⇒ if_sem b (ceval c1) (ceval c2)
| CWhile b c ⇒ loop_sem b (ceval c)
| CChoice c1 c2 ⇒ choice_sem (ceval c1) (ceval c2)
end.
End TheSecondAttempt.
Is this definition correct? Yes. But the reason is nontrivial. We need to
explain that for any command c and program state st, the following two
statements are equivalent:
- executing c from st must terminate;
- there exists a natural number n such that executing c from st must terminate within n steps.
End ComCChoice.
Module ComCAssAny.
Now we consider another nondeterministic language.
Inductive com : Type :=
| CSkip
| CAss (X: var) (a : aexp)
| CSeq (c1 c2 : com)
| CIf (b : bexp) (c1 c2 : com)
| CWhile (b : bexp) (c : com)
| CAssAny (X: var). (* <-- new *)
| CSkip
| CAss (X: var) (a : aexp)
| CSeq (c1 c2 : com)
| CIf (b : bexp) (c1 c2 : com)
| CWhile (b : bexp) (c : com)
| CAssAny (X: var). (* <-- new *)
The new command CAssAny X will assign an arbitrary integer to the program
variable X. What is different between this language and our language with
CChoice? A CAssAny command has infinite possible outcomes. You may argue
that one of them is more realistic than the other. But we only focus on the
difference between their theories.
Consider the following program:
This program will always terminate, but there is no such bound n that we
can guarantee its termination in n steps. Moreover, we can make a slight
modification to it and turn it into one single loop.
If Y is nonzero in the beginning state, then this loop will always
terminate but the number of iterations is unbounded, which fails our
semantic definition for loops above.
We define its denotational semantics below.
X ::= AnyInteger;;
If (X ≤ 0) Then X ::= 0 - X Else Skip EndIf;;
While (! (X == 0)) Do
X ::= X - 1
EndWhile
If (X ≤ 0) Then X ::= 0 - X Else Skip EndIf;;
While (! (X == 0)) Do
X ::= X - 1
EndWhile
While (! (X == 0 && Y == 0)) Do
If (Y == 0)
Then X ::= X - 1
Else Y ::= 0;;
X ::= AnyInteger;;
If (X ≤ 0) Then X ::= 0 - X Else Skip EndIf
EndIf
EndWhile
If (Y == 0)
Then X ::= X - 1
Else Y ::= 0;;
X ::= AnyInteger;;
If (X ≤ 0) Then X ::= 0 - X Else Skip EndIf
EndIf
EndWhile
Better denotational semantics
Record com_denote: Type := {
com_term: state -> state -> Prop;
com_div: state -> Prop
}.
Definition skip_sem: com_denote :=
{| com_term := BinRel.id;
com_div := Sets.empty;
|}.
Definition asgn_sem (X: var) (a: aexp): com_denote :=
{| com_term :=
fun st1 st2 ⇒
st2 X = aeval a st1 ∧
∀Y, X ≠ Y -> st1 Y = st2 Y;
com_div := Sets.empty
|}.
Definition seq_sem (DC1 DC2: com_denote): com_denote :=
{| com_term :=
BinRel.concat (com_term DC1) (com_term DC2);
com_div :=
Sets.union
(com_div DC1)
(BinRel.dia (com_term DC1) (com_div DC2))
|}.
Definition if_sem (b: bexp) (DC1 DC2: com_denote): com_denote :=
{| com_term :=
BinRel.union
(BinRel.concat (BinRel.test_rel (beval b)) (com_term DC1))
(BinRel.concat (BinRel.test_rel (beval (! b))) (com_term DC2));
com_div :=
Sets.union
(BinRel.dia (BinRel.test_rel (beval b)) (com_div DC1))
(BinRel.dia (BinRel.test_rel (beval (!b))) (com_div DC2))
|}.
Definition asgn_any_sem (X: var): com_denote :=
{| com_term :=
fun st1 st2 ⇒
∀Y, X ≠ Y -> st1 Y = st2 Y;
com_div := Sets.empty
|}.
Fixpoint iter_loop_body (b: bexp)
(DC: com_denote)
(n: nat): state -> state -> Prop :=
match n with
| O ⇒ BinRel.test_rel (beval (! b))
| S n' ⇒
BinRel.concat
(BinRel.test_rel (beval b))
(BinRel.concat
(com_term DC)
(iter_loop_body b DC n'))
end.
com_term: state -> state -> Prop;
com_div: state -> Prop
}.
Definition skip_sem: com_denote :=
{| com_term := BinRel.id;
com_div := Sets.empty;
|}.
Definition asgn_sem (X: var) (a: aexp): com_denote :=
{| com_term :=
fun st1 st2 ⇒
st2 X = aeval a st1 ∧
∀Y, X ≠ Y -> st1 Y = st2 Y;
com_div := Sets.empty
|}.
Definition seq_sem (DC1 DC2: com_denote): com_denote :=
{| com_term :=
BinRel.concat (com_term DC1) (com_term DC2);
com_div :=
Sets.union
(com_div DC1)
(BinRel.dia (com_term DC1) (com_div DC2))
|}.
Definition if_sem (b: bexp) (DC1 DC2: com_denote): com_denote :=
{| com_term :=
BinRel.union
(BinRel.concat (BinRel.test_rel (beval b)) (com_term DC1))
(BinRel.concat (BinRel.test_rel (beval (! b))) (com_term DC2));
com_div :=
Sets.union
(BinRel.dia (BinRel.test_rel (beval b)) (com_div DC1))
(BinRel.dia (BinRel.test_rel (beval (!b))) (com_div DC2))
|}.
Definition asgn_any_sem (X: var): com_denote :=
{| com_term :=
fun st1 st2 ⇒
∀Y, X ≠ Y -> st1 Y = st2 Y;
com_div := Sets.empty
|}.
Fixpoint iter_loop_body (b: bexp)
(DC: com_denote)
(n: nat): state -> state -> Prop :=
match n with
| O ⇒ BinRel.test_rel (beval (! b))
| S n' ⇒
BinRel.concat
(BinRel.test_rel (beval b))
(BinRel.concat
(com_term DC)
(iter_loop_body b DC n'))
end.
The most important part is still nonterminating cases of loops.
Definition diverge (b: bexp) (DC: com_denote) (X: state -> Prop): Prop :=
∀st,
X st ->
BinRel.dia
(BinRel.test_rel (beval b))
(Sets.union
(com_div DC)
(BinRel.dia (com_term DC) X)) st.
∀st,
X st ->
BinRel.dia
(BinRel.test_rel (beval b))
(Sets.union
(com_div DC)
(BinRel.dia (com_term DC) X)) st.
When discussing the execution of While b Do c EndWhile, we use
diverge b (ceval c) X
to claim the following property and program state set X: for any state
st in X,
- loop condition b must be true on st, and
- there exists some execution of loop body c from st will directly diverge, or there exists some execution of loop body c from st will terminate in some state in X.
Definition loop_sem (b: bexp) (DC: com_denote): com_denote :=
{| com_term :=
BinRel.omega_union (iter_loop_body b DC);
com_div :=
Sets.infinite_union (diverge b DC)
|}.
{| com_term :=
BinRel.omega_union (iter_loop_body b DC);
com_div :=
Sets.infinite_union (diverge b DC)
|}.
Here, Sets.infinite_union (diverge b DC) is the union of all sets X
such that diverge b DC X holds.
Print Sets.infinite_union.
(* Sets.infinite_union =
fun (A : Type) (X : (A -> Prop) -> Prop) (a : A) =>
exists S : A -> Prop, X S /\ S a *)
Fixpoint ceval (c: com): com_denote :=
match c with
| CSkip ⇒ skip_sem
| CAss X E ⇒ asgn_sem X E
| CSeq c1 c2 ⇒ seq_sem (ceval c1) (ceval c2)
| CIf b c1 c2 ⇒ if_sem b (ceval c1) (ceval c2)
| CWhile b c ⇒ loop_sem b (ceval c)
| CAssAny X ⇒ asgn_any_sem X
end.
End ComCAssAny.
(* 2021-05-17 00:32 *)
(* Sets.infinite_union =
fun (A : Type) (X : (A -> Prop) -> Prop) (a : A) =>
exists S : A -> Prop, X S /\ S a *)
Fixpoint ceval (c: com): com_denote :=
match c with
| CSkip ⇒ skip_sem
| CAss X E ⇒ asgn_sem X E
| CSeq c1 c2 ⇒ seq_sem (ceval c1) (ceval c2)
| CIf b c1 c2 ⇒ if_sem b (ceval c1) (ceval c2)
| CWhile b c ⇒ loop_sem b (ceval c)
| CAssAny X ⇒ asgn_any_sem X
end.
End ComCAssAny.
(* 2021-05-17 00:32 *)