Lecture notes 20210517 Pointer And Address

Require Import Coq.Relations.Relation_Definitions.
Require Export Coq.ZArith.ZArith.
Require Export Coq.Strings.String.
Require Export Coq.Logic.Classical.
Require Import PL.Imp PL.ImpExt2 PL.ImpExt3.

Open Scope Z.

A Language With Addresses And Pointers

In this lecture, we consider a typical programming language, extending the simple imperative language with addresses (地址) and pointers (指针). We still use their indices to represent variables and add two operators to the programming language: dereference and address-of.
Dereference is like the "*" operator in C, e.g. x = * y; . Address-of is like the "&" operator in C, e.g. y = & x.
Definition var: Type := nat.

Inductive aexp : Type :=
  | ANum (n : Z)
  | AId (X : var)
  | APlus (a1 a2 : aexp)
  | AMinus (a1 a2 : aexp)
  | AMult (a1 a2 : aexp)
  | ADeref (a1: aexp) (* <-- new *)
  | AAddr (a1: aexp). (* <-- new *)

Inductive bexp : Type :=
  | BTrue
  | BFalse
  | BEq (a1 a2 : aexp)
  | BLe (a1 a2 : aexp)
  | BNot (b : bexp)
  | BAnd (b1 b2 : bexp).

Inductive com : Type :=
  | CSkip
  | CAss (a1 a2 : aexp) (* <-- new *)
  | CSeq (c1 c2 : com)
  | CIf (b : bexp) (c1 c2 : com)
  | CWhile (b : bexp) (c : com).

Module Example1.
We suppose that X and Y are variable 0 and 1.
Definition X := 0%nat.
Definition Y := 1%nat.
Here is some sample expressions and command.
Example aexp_sample: aexp := ADeref (APlus (AAddr (AId X)) (ANum 1)).
  (* It is like the C expression:  * (& x + 1) . *)

Example bexp_sample: bexp := BEq (ADeref (AId X)) (ADeref (AId Y)).
  (* It is like the C expression:  * x == * y . *)

Example com_sample1: com :=
  CAss (ADeref (AId X)) (APlus (ADeref (AId X)) (ANum 1)).
  (* It is like the C program:  ( * x ) ++; . *)

Example com_sample2: com :=
  CWhile (BNot (BEq (AId X) (ANum 0)))
         (CSeq (CAss (AId Y) (APlus (AId Y) (ANum 1)))
               (CAss (AId X) (ADeref (AId X)))).
  (* It is like the C program:  while (x != 0) { y ++; x = * x; } . *)

End Example1.

Expressions' Denotations

In order to make the program state model simple, we assume the value of program variable #n is stored at address n + 1. A program state is a partial mapping from addresses to values. It is a partial function instead of a total functions since some addresses may be invalid.
Definition var2addr (X: var): Z := Z.of_nat X + 1.

Definition state: Type := Z -> option Z.
In order to define a denotational semantics, we define two functions for expression evaluation: aevalR and aevalL. They define expressions' rvalue (右值) and lvalue (左值). Rvalues are the values for computation and lvalues are the addresses for reading and writing. We introduce aevalR and aevalL by a mutually recursive definition.
Fixpoint aevalR (a: aexp): state -> option Z :=
  match a with
  | ANum nfun _Some n
  | AId Xfun stst (var2addr X)
  | APlus a1 a2OptF.add (aevalR a1) (aevalR a2)
  | AMinus a1 a2OptF.sub (aevalR a1) (aevalR a2)
  | AMult a1 a2OptF.mul (aevalR a1) (aevalR a2)
  | ADeref a1fun st
                   match aevalR a1 st with
                   | Some n1st n1
                   | NoneNone
                   end
  | AAddr a1aevalL a1
  end
with aevalL (a: aexp): state -> option Z :=
  match a with
  | ANum nfun _None
  | AId Xfun stSome (var2addr X)
  | APlus a1 a2fun _None
  | AMinus a1 a2fun _None
  | AMult a1 a2fun _None
  | ADeref a1aevalR a1
  | AAddr a1fun _None
  end.

Record bexp_denote: Type := {
  true_set: state -> Prop;
  false_set: state -> Prop;
  error_set: state -> Prop;
}.

Definition opt_test (R: Z -> Z -> Prop) (X Y: state -> option Z): bexp_denote :=
{|
  true_set := fun st
                   match X st, Y st with
                   | Some n1, Some n2R n1 n2
                   | _, _False
                   end;
  false_set := fun st
                   match X st, Y st with
                   | Some n1, Some n2 ⇒ ¬R n1 n2
                   | _, _False
                   end;
  error_set := fun st
                   match X st, Y st with
                   | Some n1, Some n2False
                   | _, _True
                   end;
|}.

Fixpoint beval (b: bexp): bexp_denote :=
  match b with
  | BTrue
      {| true_set := Sets.full;
         false_set := Sets.empty;
         error_set := Sets.empty;
      |}
  | BFalse
      {| true_set := Sets.empty;
         false_set := Sets.full;
         error_set := Sets.empty;
      |}
  | BEq a1 a2
      opt_test Z.eq (aevalR a1) (aevalR a2)
  | BLe a1 a2
      opt_test Z.le (aevalR a1) (aevalR a2)
  | BNot b
      {| true_set := false_set (beval b);
         false_set := true_set (beval b);
         error_set := error_set (beval b);
      |}
  | BAnd b1 b2
      {| true_set := Sets.intersect
                       (true_set (beval b1))
                       (true_set (beval b2));
         false_set := Sets.union
                        (false_set (beval b1))
                        (Sets.intersect
                           (true_set (beval b1))
                           (false_set (beval b2)));
         error_set := Sets.union
                        (error_set (beval b1))
                        (Sets.intersect
                           (true_set (beval b1))
                           (error_set (beval b2)));
      |}
  end.

Programs' Denotations

The interesting cases below are about assignment commands. Specifically, an assignment command may fail in 3 different way: l-value evaluation fails; rvalue evaluation fails; the address to write is not available.
Record com_denote: Type := {
  com_term: state -> state -> Prop;
  com_error: state -> Prop
}.

Definition skip_sem: com_denote :=
  {| com_term := BinRel.id;
     com_error := Sets.empty;
  |}.

Definition asgn_sem (DA1 DA2: state -> option Z): com_denote :=
  {| com_term :=
       fun st1 st2
         a v,
           DA1 st1 = Some a
           DA2 st1 = Some v
           st1 aNone
           st2 a = Some v
           a', aa' -> st1 a' = st2 a';
     com_error :=
       fun st
         DA1 st = NoneDA2 st = None
         (a, DA1 st = Some ast a = None)
  |}.

Definition seq_sem (DC1 DC2: com_denote): com_denote :=
  {| com_term :=
       BinRel.concat (com_term DC1) (com_term DC2);
     com_error :=
       Sets.union
         (com_error DC1)
         (BinRel.dia (com_term DC1) (com_error DC2))
  |}.

Definition if_sem (DB: bexp_denote) (DC1 DC2: com_denote): com_denote :=
  {| com_term :=
       BinRel.union
        (BinRel.concat (BinRel.test_rel (true_set DB)) (com_term DC1))
        (BinRel.concat (BinRel.test_rel (false_set DB)) (com_term DC2));
     com_error :=
       Sets.union
         (error_set DB)
         (Sets.union
            (BinRel.dia (BinRel.test_rel (true_set DB)) (com_error DC1))
            (BinRel.dia (BinRel.test_rel (false_set DB)) (com_error DC2)))
  |}.

Fixpoint iter_loop_body (DB: bexp_denote)
                        (DC: com_denote)
                        (n: nat): com_denote :=
  match n with
  | O
      {| com_term :=
           BinRel.test_rel (false_set DB);
         com_error :=
           Sets.union
             (error_set DB)
             (BinRel.dia
                (BinRel.test_rel (true_set DB))
                (com_error DC))
      |}
  | S n'
      {| com_term :=
           BinRel.concat
             (BinRel.test_rel (true_set DB))
             (BinRel.concat
                (com_term DC)
                (com_term (iter_loop_body DB DC n')));
         com_error :=
           BinRel.dia
             (BinRel.test_rel (true_set DB))
             (BinRel.dia
                (com_term DC)
                (com_error (iter_loop_body DB DC n')));
      |}
  end.

Definition loop_sem (DB: bexp_denote) (DC: com_denote): com_denote :=
  {| com_term :=
       BinRel.omega_union
         (fun ncom_term (iter_loop_body DB DC n));
     com_error :=
       Sets.omega_union
         (fun ncom_error (iter_loop_body DB DC n))
  |}.

Fixpoint ceval (c: com): com_denote :=
  match c with
  | CSkipskip_sem
  | CAss E1 E2asgn_sem (aevalL E1) (aevalR E2)
  | CSeq c1 c2seq_sem (ceval c1) (ceval c2)
  | CIf b c1 c2if_sem (beval b) (ceval c1) (ceval c2)
  | CWhile b cloop_sem (beval b) (ceval c)
  end.

Small Step Semantics for Expression Evaluation


Inductive aexp_halt: aexp -> Prop :=
  | AH_num : n, aexp_halt (ANum n).

Inductive astepR : state -> aexp -> aexp -> Prop :=
  | ASR_Id : st X n,
      st (var2addr X) = Some n ->
      astepR st
        (AId X) (ANum n)

  | ASR_Plus1 : st a1 a1' a2,
      astepR st
        a1 a1' ->
      astepR st
        (APlus a1 a2) (APlus a1' a2)
  | ASR_Plus2 : st a1 a2 a2',
      aexp_halt a1 ->
      astepR st
        a2 a2' ->
      astepR st
        (APlus a1 a2) (APlus a1 a2')
  | ASR_Plus : st n1 n2,
      astepR st
        (APlus (ANum n1) (ANum n2)) (ANum (n1 + n2))

  | ASR_Minus1 : st a1 a1' a2,
      astepR st
        a1 a1' ->
      astepR st
        (AMinus a1 a2) (AMinus a1' a2)
  | ASR_Minus2 : st a1 a2 a2',
      aexp_halt a1 ->
      astepR st
        a2 a2' ->
      astepR st
        (AMinus a1 a2) (AMinus a1 a2')
  | ASR_Minus : st n1 n2,
      astepR st
        (AMinus (ANum n1) (ANum n2)) (ANum (n1 - n2))

  | ASR_Mult1 : st a1 a1' a2,
      astepR st
        a1 a1' ->
      astepR st
        (AMult a1 a2) (AMult a1' a2)
  | ASR_Mult2 : st a1 a2 a2',
      aexp_halt a1 ->
      astepR st
        a2 a2' ->
      astepR st
        (AMult a1 a2) (AMult a1 a2')
  | ASR_Mult : st n1 n2,
      astepR st
        (AMult (ANum n1) (ANum n2)) (ANum (n1 * n2))

  | ASR_DerefStep : st a1 a1',
      astepR st
        a1 a1' ->
      astepR st
        (ADeref a1) (ADeref a1')
  | ASR_Deref : st n n',
      st n = Some n' ->
      astepR st
        (ADeref (ANum n)) (ANum n')

  | ASR_AddrStep : st a1 a1',
      astepL st
        a1 a1' ->
      astepR st
        (AAddr a1) (AAddr a1')
  | ASR_Addr : st n,
      astepR st
        (AAddr (ADeref (ANum n))) (ANum n)
with astepL : state -> aexp -> aexp -> Prop :=
  | ASL_Id: st X,
      astepL st
        (AId X) (ADeref (ANum (var2addr X)))

  | ASL_DerefStep: st a1 a1',
      astepR st
        a1 a1' ->
      astepL st
        (ADeref a1) (ADeref a1')
.

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

  | BS_Eq1 : st a1 a1' a2,
      astepR st
        a1 a1' ->
      bstep st
        (BEq a1 a2) (BEq a1' a2)
  | BS_Eq2 : st a1 a2 a2',
      aexp_halt a1 ->
      astepR 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,
      astepR st
        a1 a1' ->
      bstep st
        (BLe a1 a2) (BLe a1' a2)
  | BS_Le2 : st a1 a2 a2',
      aexp_halt a1 ->
      astepR 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.
Here, we assume BAnd expressions use short circuit evaluation.

Small Step Semantics for Program Execution


Inductive cstep : (com * state) -> (com * state) -> Prop :=
  | CS_AssStep1 st E1 E1' E2 (* <-- new *)
      (H1: astepL st E1 E1'):
      cstep (CAss E1 E2, st) (CAss E1' E2, st)
  | CS_AssStep2 st n E2 E2' (* <-- new *)
      (H1: astepR st E2 E2'):
      cstep (CAss (ADeref (ANum n)) E2, st) (CAss (ADeref (ANum n)) E2', st)
  | CS_Ass st1 st2 n1 n2 (* <-- new *)
      (H1: st1 n1None)
      (H2: st2 n1 = Some n2)
      (H3: n, n1n -> st1 n = st2 n):
      cstep (CAss (ADeref (ANum n1)) (ANum n2), st1) (CSkip, st2)
  | CS_SeqStep st c1 c1' st' c2
      (H1: cstep (c1, st) (c1', st')):
      cstep (CSeq c1 c2 , st) (CSeq c1' c2, st')
  | CS_Seq st c2:
      cstep (CSeq CSkip c2, st) (c2, st)
  | CS_IfStep st b b' c1 c2
      (H1: bstep st b b'):
      cstep (CIf b c1 c2, st) (CIf b' c1 c2, st)
  | CS_IfTrue st c1 c2:
      cstep (CIf BTrue c1 c2, st) (c1, st)
  | CS_IfFalse st c1 c2:
      cstep (CIf BFalse c1 c2, st) (c2, st)
  | CS_While st b c:
      cstep
        (CWhile b c, st)
        (CIf b (CSeq c (CWhile b c)) CSkip, st).

Discussion: Type Safety

Is this programming language a safe one? Definitely no. There are several kinds of errors that may happen.
The first is illegal assignments. In the C language, x + 1 = 0 is an illegal assignment. In our language, the corresponding syntax tree is
  • CAss (APlus (AId X) (ANum 1)) (ANum 0).
It causes error because its left hand side is not an lvalue. We do not want to call it a run-time-error because it should be detected at compile time.
The second is illegal dereferece. When we want to read or write on address n but this address is not accessible, it is a run time error. It is hard to completely get rid of this kind of errors at compile time.
(* 2021-05-17 20:13 *)