Lecture notes 20210428 Function Call

Require Import PL.Imp.
Require Import Coq.ZArith.ZArith.
Require Import Coq.Lists.List.
Import ListNotations.
Local Open Scope Z.

The language


Definition glob_var := nat.
Definition local_var := nat.
Definition func := nat.
We distinguish local variables and global variables.
Inductive aexp : Type :=
  | ANum (n : Z)
  | AIdGlob (X : glob_var) (* <-- new *)
  | AIdLocal (X : local_var) (* <-- new *)
  | APlus (a1 a2 : aexp)
  | AMinus (a1 a2 : aexp)
  | AMult (a1 a2 : aexp).

Inductive bexp : Type :=
  | BTrue
  | BFalse
  | BEq (a1 a2 : aexp)
  | BLe (a1 a2 : aexp)
  | BNot (b : bexp)
  | BAnd (b1 b2 : bexp).
We also have assignment for local variables and for global variables. For function calls, we consider a very simple setting: no function arguments and no return value.
Inductive com : Type :=
  | CSkip
  | CAssGlob (X: glob_var) (a : aexp) (* <-- new *)
  | CAssLocal (X: local_var) (a : aexp) (* <-- new *)
  | CCall (F: func) (* <-- new *)
  | CSeq (c1 c2 : com)
  | CIf (b : bexp) (c1 c2 : com)
  | CWhile (b : bexp) (c : com).

Definition prog: Type := list (func * com).
Program states are local variables' values and global variables' values.
Definition local_state: Type := local_var -> Z.
Definition glob_state: Type := glob_var -> Z.

Record state: Type := {
  glob_of_state: glob_state;
  local_of_state: local_state;
}.

Denotational semantics


Module Denotation_With_Call.
Although denotational semantics of expressions are similar to our original setting, we still need to redefine them since the syntax is changed.
Definition query_var_local (X: local_var) : state -> Z :=
  fun st ⇒ (local_of_state st) X.

Definition query_var_glob (X: glob_var) : state -> Z :=
  fun st ⇒ (glob_of_state st) X.

Fixpoint aeval (a : aexp) : state -> Z :=
  match a with
  | ANum nconstant_func n
  | AIdLocal Xquery_var_local X
  | AIdGlob Xquery_var_glob X
  | APlus a1 a2 ⇒ (aeval a1 + aeval a2)%Func
  | AMinus a1 a2 ⇒ (aeval a1 - aeval a2)%Func
  | AMult a1 a2 ⇒ (aeval a1 * aeval a2)%Func
  end.

Fixpoint beval (b : bexp) : state -> Prop :=
  match b with
  | BTrueSets.full
  | BFalseSets.empty
  | BEq a1 a2Func.test_eq (aeval a1) (aeval a2)
  | BLe a1 a2Func.test_le (aeval a1) (aeval a2)
  | BNot b1Sets.complement (beval b1)
  | BAnd b1 b2Sets.intersect (beval b1 ) (beval b2)
  end.
Most definitions in com's denotations are not interesting.
Definition if_sem
  (b: bexp)
  (then_branch else_branch: state -> state -> Prop)
  : state -> state -> Prop
:=
  BinRel.union
    (BinRel.concat (BinRel.test_rel (beval b)) then_branch)
    (BinRel.concat (BinRel.test_rel (beval (BNot b))) else_branch).

Fixpoint iter_loop_body (b: bexp)
                        (loop_body: state -> state -> Prop)
                        (n: nat): state -> state -> Prop :=
  match n with
  | O
         BinRel.test_rel (beval (BNot b))
  | S n'
         BinRel.concat
           (BinRel.test_rel (beval b))
           (BinRel.concat
              loop_body
              (iter_loop_body b loop_body n'))
  end.

Definition loop_sem (b: bexp) (loop_body: state -> state -> Prop):
  state -> state -> Prop :=
  BinRel.omega_union (iter_loop_body b loop_body).
The only interesting case is function call. But obviously, function calls' behaviour depends on callee function (被调用函数) behavior, i.e. the denotation of CCall f should be computed based on f's function body's denotation.
Section CEVAL.
Here, we assume that callee is all callee functions' denotations.
Variable callee: func -> glob_state -> glob_state -> Prop.

Fixpoint ceval (c: com): state -> state -> Prop :=
  match c with
  | CSkipBinRel.id
  | CAssLocal X E
      fun st1 st2
        glob_of_state st2 = glob_of_state st1
        local_of_state st2 X = aeval E st1
        Y, XY -> local_of_state st1 Y = local_of_state st2 Y
  | CAssGlob X E
      fun st1 st2
        local_of_state st2 = local_of_state st1
        glob_of_state st2 X = aeval E st1
        Y, XY -> glob_of_state st1 Y = glob_of_state st2 Y
  | CCall F
      fun st1 st2
        local_of_state st1 = local_of_state st2
        callee F (glob_of_state st1) (glob_of_state st2)
  | CSeq c1 c2BinRel.concat (ceval c1) (ceval c2)
  | CIf b c1 c2if_sem b (ceval c1) (ceval c2)
  | CWhile b cloop_sem b (ceval c)
  end.

End CEVAL.
What is the type of ceval? It is NOT simply a function from com to a binary relation. The variable callee is also one of its arguments.
Check ceval.
You may already find that callee functions' behavior also depends on their own inside calls. Thus, we define iter_func_body to be program behavior with a limit depth of call.
Fixpoint iter_func_body (n: nat) (p: prog):
  func -> glob_state -> glob_state -> Prop :=
  match n with
  | Ofun _ _ _False
  | S n'fun f gst1 gst2
              c st1 st2,
                List.In (f, c) p
                (X, local_of_state st1 X = 0%Z) ∧
                gst1 = glob_of_state st1
                gst2 = glob_of_state st2
                ceval (iter_func_body n' p) c st1 st2
  end.

Definition feval (p: prog): func -> glob_state -> glob_state -> Prop :=
  fun f gst1 gst2
    n, iter_func_body n p f gst1 gst2.

End Denotation_With_Call.

Small step semantics


Module Small_Step_Semantics_Calling_Stack.
Now we define a calling-stack-based small step semantics. Here, every element in a calling stack describes local stack.
Inductive aexp_halt: aexp -> Prop :=
  | AH_num : n, aexp_halt (ANum n).

Inductive astep : state -> aexp -> aexp -> Prop :=
  | AS_IdLocal : st X,
      astep st
        (AIdLocal X) (ANum (local_of_state st X))
  | AS_IdGlobal : st X,
      astep st
        (AIdGlob X) (ANum (glob_of_state st X))

  | AS_Plus1 : st a1 a1' a2,
      astep st
        a1 a1' ->
      astep st
        (APlus a1 a2) (APlus a1' a2)
  | AS_Plus2 : st a1 a2 a2',
      aexp_halt a1 ->
      astep st
        a2 a2' ->
      astep st
        (APlus a1 a2) (APlus a1 a2')
  | AS_Plus : st n1 n2,
      astep st
        (APlus (ANum n1) (ANum n2)) (ANum (n1 + n2))

  | AS_Minus1 : st a1 a1' a2,
      astep st
        a1 a1' ->
      astep st
        (AMinus a1 a2) (AMinus a1' a2)
  | AS_Minus2 : st a1 a2 a2',
      aexp_halt a1 ->
      astep st
        a2 a2' ->
      astep st
        (AMinus a1 a2) (AMinus a1 a2')
  | AS_Minus : st n1 n2,
      astep st
        (AMinus (ANum n1) (ANum n2)) (ANum (n1 - n2))

  | AS_Mult1 : st a1 a1' a2,
      astep st
        a1 a1' ->
      astep st
        (AMult a1 a2) (AMult a1' a2)
  | AS_Mult2 : st a1 a2 a2',
      aexp_halt a1 ->
      astep st
        a2 a2' ->
      astep st
        (AMult a1 a2) (AMult a1 a2')
  | AS_Mult : st n1 n2,
      astep st
        (AMult (ANum n1) (ANum n2)) (ANum (n1 * n2)).

Inductive bexp_halt: bexp -> Prop :=
  | BH_True : bexp_halt BTrue
  | BH_False : bexp_halt BFalse.

Inductive bstep : state -> bexp -> bexp -> Prop :=

  | BS_Eq1 : st a1 a1' a2,
      astep st
        a1 a1' ->
      bstep st
        (BEq a1 a2) (BEq a1' a2)
  | BS_Eq2 : st a1 a2 a2',
      aexp_halt a1 ->
      astep st
        a2 a2' ->
      bstep st
        (BEq a1 a2) (BEq a1 a2')
  | BS_Eq_True : st n1 n2,
      n1 = n2 ->
      bstep st
        (BEq (ANum n1) (ANum n2)) BTrue
  | BS_Eq_False : st n1 n2,
      n1n2 ->
      bstep st
        (BEq (ANum n1) (ANum n2)) BFalse


  | BS_Le1 : st a1 a1' a2,
      astep st
        a1 a1' ->
      bstep st
        (BLe a1 a2) (BLe a1' a2)
  | BS_Le2 : st a1 a2 a2',
      aexp_halt a1 ->
      astep st
        a2 a2' ->
      bstep st
        (BLe a1 a2) (BLe a1 a2')
  | BS_Le_True : st n1 n2,
      n1n2 ->
      bstep st
        (BLe (ANum n1) (ANum n2)) BTrue
  | BS_Le_False : st n1 n2,
      n1 > n2 ->
      bstep st
        (BLe (ANum n1) (ANum n2)) BFalse

  | BS_NotStep : st b1 b1',
      bstep st
        b1 b1' ->
      bstep st
        (BNot b1) (BNot b1')
  | BS_NotTrue : st,
      bstep st
        (BNot BTrue) BFalse
  | BS_NotFalse : st,
      bstep st
        (BNot BFalse) BTrue

  | BS_AndStep : st b1 b1' b2,
      bstep st
        b1 b1' ->
      bstep st
       (BAnd b1 b2) (BAnd b1' b2)
  | BS_AndTrue : st b,
      bstep st
       (BAnd BTrue b) b
  | BS_AndFalse : st b,
      bstep st
       (BAnd BFalse b) BFalse.

Definition cstack: Type := list (com * local_state).

Inductive start_with_call: com -> func -> com -> Prop :=
| SWC_Call: f, start_with_call (CCall f) f CSkip
| SWC_Seq: c1 f c12 c2,
             start_with_call c1 f c12 ->
             start_with_call (CSeq c1 c2) f (CSeq c12 c2).

Section CSTEP.

Variable p: prog.

Inductive cstep : (com * cstack * state) -> (com * cstack * state) -> Prop :=
  | CS_AssLocalStep : st s X a a',
      astep st a a' ->
      cstep
        (CAssLocal X a, s, st)
        (CAssLocal X a', s, st)
  | CS_AssLocal : st1 st2 s X n,
      glob_of_state st2 = glob_of_state st1 ->
      local_of_state st2 X = n ->
      (Y, XY -> local_of_state st1 Y = local_of_state st2 Y) ->
      cstep
        (CAssLocal X (ANum n), s, st1)
        (CSkip, s, st2)
  | CS_AssGlobStep : st s X a a',
      astep st a a' ->
      cstep
        (CAssGlob X a, s, st)
        (CAssGlob X a', s, st)
  | CS_AssGlob : st1 st2 s X n,
      local_of_state st2 = local_of_state st1 ->
      glob_of_state st2 X = n ->
      (Y, XY -> glob_of_state st1 Y = glob_of_state st2 Y) ->
      cstep
        (CAssGlob X (ANum n), s, st1)
        (CSkip, s, st2)
  | CS_SeqStep : st s c1 c1' st' c2,
      cstep
        (c1, s, st)
        (c1', s, st') ->
      cstep
        (CSeq c1 c2, s, st)
        (CSeq c1' c2, s, st')
  | CS_Seq : st s c2,
      cstep
        (CSeq CSkip c2, s, st)
        (c2, s, st)
  | CS_IfStep : st s b b' c1 c2,
      bstep st b b' ->
      cstep
        (CIf b c1 c2, s, st)
        (CIf b' c1 c2, s, st)
  | CS_IfTrue : st s c1 c2,
      cstep
        (CIf BTrue c1 c2, s, st)
        (c1, s, st)
  | CS_IfFalse : st s c1 c2,
      cstep
        (CIf BFalse c1 c2, s, st)
        (c2, s, st)
  | CS_While : st s b c1,
      cstep
        (CWhile b c1, s, st)
        (CIf b (CSeq c1 (CWhile b c1)) CSkip, s, st)
  | CS_Call : st1 st2 s c1 f body c2, (* <--- new *)
      start_with_call c1 f c2 ->
      glob_of_state st2 = glob_of_state st1 ->
      (X, local_of_state st2 X = 0%Z) ->
      List.In (f, body) p ->
      cstep
        (c1, s, st1)
        (body, (c2, local_of_state st1) :: s, st2)
  | CS_Skip : st1 st2 c l s , (* <--- new *)
      glob_of_state st2 = glob_of_state st1 ->
      local_of_state st2 = l ->
      cstep
        (CSkip, (c, l) :: s, st1)
        (c, s, st2).

End CSTEP.

End Small_Step_Semantics_Calling_Stack.

Hoare logic

We will use Gamma to represent a set of triples of: (1) precondition (2) function name (3) postcondition. A Hoare logic for procedure calls usually contains two parts:
  • proving commands correct based on assumption of callees;
  • establishing these assumption based on themselves.
Here are proof rules:
(1) Sequence rule:
         Gamma  ⊢  {P }}  c1  {Q }}  ->
         Gamma  ⊢  {Q }}  c2  {R }}  ->
         Gamma  ⊢  {P }}  c1 ;; c2  {R }} .
    
(2) If rule:
         Gamma  ⊢  {P AND [[b]}}  c1  {Q }}  ->
         Gamma  ⊢  {P AND NOT [[b]}}  c2  {Q }}  ->
         Gamma  ⊢  {P }}  If b Then c1 Else c2 EndIf  {Q }} .
    
(3) While rule:
         Gamma  ⊢  {P AND [[b]}}  c1  {P }}  ->
         Gamma  ⊢  {P }}  While b Do c1 EndWhile  {P AND NOT [[b]}} .
    
(4) Call rule:
         (PfQIn Gamma ->
         Local (L) ->
         Gamma  ⊢  {P AND L }}  Call f  {Q AND L }} .
    
We omit proof rules for skips and assignments. In the end, one needs to guarantee that for any (P, f, Q) in Gamma, we can derive
        Gamma  ⊢  {P }}  c  {Q }
where c is f's function body.
(* 2021-05-07 20:38 *)