Ch 3.5 Describing the Meaning of Programs: Dynamic Semantics

Dynamic semantics is strictly concerned with the behavior of a running program. There is no single widely acceptable notation or formalism for dynamic semantics. There are three primary methods: Operation, Axiomatic, Denotational.

Axiomatic Semantics

Axiomatic Semantics, which is based on formal logic, was designed as a means of formal program verification. The "view of a system" is essentially a set of invariants (assertions) that hold or don't hold throughout program execution. The meaning of a program is ultimately whether the program is correct (all assertions hold) or not correct (at least assertion fails). The set of predefined assertions (invariants) may be deliberately selected to prove whatever is desired.

To prove the "correctness" of a programming language...

Axioms and inference rules must be defined for each statement type in the language

An axiom is a logical statement that is taken to be true.

An inference rule is a way of describing a transformation from one assertion to the next.

An assertion before a statement (a precondition) states the relationships and constraints among variables that are true at that point in execution

An assertion following a statement is a postcondition

A weakest precondition is the least restrictive precondition that will guarantee the postcondition

Axiomatic Semantics Form:

     {P} statement {Q}, 
where {P} is the pre-condition and {Q} is the post-condition

An example:

a = b + 1 {a > 1}

One possible precondition: {b > 10} Weakest precondition: {b > 0}

Start by building an axiomatic semantics for assignment statements ...

An axiom for assignment statements:

Let x = E denote an assignment statement

{P} x = E {Q}

The precondition is defined by the axiom:

P = Qx->E

Which means P is computed as Q with all instances of x in Q replaced by E. Example:

        x = b / 2  {x < 5} 

      b / 2 < 5
      b < 5 * 2
      P = {b < 10}   <=this is the weakest precondition
Thus, the axiom for (x = E) is:

{Qx->E} x = E {Q}

Next you need inference rules to prove the correctness of sequences of statements.

1. The Rule of Consequence (where '=>' is "implies" and 'S' is "statement")

      {P} S {Q}, P' => P, Q => Q'   <= antecedent
      --------------------------
            {P'} S {Q'}             <= consequence 
Example:
       {P}  x > 5       {Q} x > 5 
       {P'} x > 10      {Q} x > 5  (still true)

       {P} x > 5       {Q}  x > 5 
       {P} x > 5       {Q'} x > 0  (still true)
 
This shows that a precondition can always be strengthened and a postconditon can always be weakened!

2. An Inference Rule for Sequences:

       {P1} S1 {P2}
       {P2} S2 {P3}

       {P1} S1 {P2}, {P2} S2 {P3}
       --------------------------
           {P1} S1; S2 {P3}         <= relies on the Rule of Consequence
3. An inference rule for logical pretest loops (while loops)
           {P} while B do S end {Q}


                (I and B) S {I}
         ------------------------------------
          {I} while B do S end {I and (not B)}

   where I is the loop Invariant and must meet the following conditions:
   
   P => I               - the loop invariant must be true initially
  {I} B {I}             - evaluation of Boolean must not change validity of I
  {I and B} S {I}       - I is not changed by executing statements in the loop
  (I and (not B)) => Q  - if I is true and B is false, Q is implied
The loop terminates when (not B) is true

The loop invariant I is a weakened version of the loop postcondition, and also a precondition.

I must be weak enough to be satisfied prior to the beginning of the loop, but when combined with the loop exit condition, it must be strong enough to force the truth of the postcondition

Another example...

The rules and axioms above can be used to define a rule for a repeat command.

      { P } repeat C until B { Q and B }
"repeat C until B" is operationally equivalent to:
      C; while (not B) do C end while
Making use of this fact, the proof rule for a repeat until is a combination of the rules for a sequence and a while loop:
       { P } C { Q }, { Q and (not B) } C { Q }
      -----------------------------------------
        { P } repeat C until B { Q and B }
The loop invariant is Q, which is the postcondition of C.

In similar fashion, an axiomatic semantics can be defined for all constructs in the language!

Program Proof Process

The postcondition for the entire program is the desired result

Work back through the program to the first statement. If the precondition on the first statement is the same as the program specification, the program is correct.

Evaluation of Axiomatic Semantics

A good tool for correctness proofs and for reasoning about compact programs

Developing axioms or inference rules for all statements in a language is difficult

A 2006 article in Scientific American discusses Alloy, a tool based on axiomatic semantics, that can verify the safety of a cancer therapy machine.

For language users or compiler writers - usefulness in describing the meaning of an entire programming language is limited

Operational Semantics

An operational semantics is a formal (mathematical) or an informal model of the behavior of a programming language at execution.

The goal of an operational semantics is to precisely define the *behavior* of a language; i.e., how the interpreter should work.

An operational semantics of a programming language can be used during the design process of the language or during the process of writing a compiler or interpreter.

The first formal operational semantics was Lambda Calculus (reduction example)

Operational semantics can be implemented as an interpreter on an actual or on a simulated machine.

The change in the state of the machine (memory, registers, etc.) defines the meaning of the statement

A hardware pure interpreter would be too expensive

A software pure interpreter also has problems: -detailed characteristics of the particular computer would make actions difficult to understand -Such a semantic definition would be machine-dependent

A better alternative: A complete computer simulation

The process:

Build a translator (translates source code to the machine code of an idealized computer)

Build a simulator for the idealized computer

To use operational semantics for a high-level language, use a virtual machine

Evaluation of operational semantics:

Good if used informally (language manuals, etc.)

Extremely complex if used formally (e.g., VDL), it was used for describing semantics of PL/I (which is nearly dead).

Example:

An informal example using virtual machine instructions to describe the operational semantics (pp. 149):

  C for Statement                    Virtual machine instructions  
                                              
  for ( expr1; expr2; expr3) {                expr1;
      ....                              loop: if expr2 == 0 goto out
  }                                            ...
                                              expr3;
                                              goto loop
                                        out:  ...    

  * assume (expr2 == 0) means expr2 is false 


  Fortran Do Statement                  Virtual machine instructions  
             
  do i = start, end, step                         i = start 
      ...                                   loop: if  i > end goto out  
  end do                                          i = i + step 
                                                  goto loop
                                            out:

Denotational Semantics

Concerned with semantics, assumes syntax and static semantics has been verified elsewhere

Based on recursive function theory

The most abstract semantics description method

Originally developed by Scott and Strachey (1970)

The process of building a denotational specification for a language

Define a mathematical object for each language entity

Define a function that maps instances of the language entities onto instances of the corresponding mathematical objects.

The meaning of language constructs are defined by only the values of the program's variables.

Denotational Semantics vs. Operational Semantics

In operational semantics, the state changes are defined by coded algorithms.

In denotational semantics, the state changes are defined by rigorous mathematical functions.

The components of a denotational specification:

  (1) Syntactic categories and abstract production rules (enough to identify
      syntactic constructs -- not to parse them)
  (2) Semantic domains -- sets of mathematical objects
      A -> B is the set of functions with domain A and codomain B
  (3) Semantic functions to map objects of syntactic world to objects in 
      semantic world; e.g. <bin_num> -> N is a function that maps
      the syntactic category <bin_num> to the set of nonnegative decimal 
      integers.  
Note: The subscripts in the examples of denotational semantics are used to identify a particular mapping function; e.g., Mdec refers to the mapping function for decimal numbers; e.g. Mdec( <dec_num> -> N ), which maps from the abstract domain of <dec_num> to the real domain of integers.

Denotational Semantics: Program State

The state of a program s is the values of all its current variables. s is a set of ordered pairs, where i is the name of a variable and v is its current value:

s = {<i1, v1>, <i2, v2> . . . , <in, vn>}

Let VARMAP be a function that, when given a variable name and a state, returns the current value of the variable

VARMAP(ij, s) = vj

Example 1: Decimal Numbers


  The syntax:
  <dec_num> ->   0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

                 | <dec_num> (0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9)


  Denotational mappings, from <dec_num> -> N, for above rules:
 
  Mdec('0') = 0,  Mdec('1') = 1, . . . , Mdec('9') = 9
  Mdec(<dec_num> '0') = 10 * Mdec (<dec_num>)
  Mdec(<dec_num> '1) = 10 * Mdec (<dec_num>) + 1
  . . .
  Mdec (<dec_num> '9') = 10 * Mdec (<dec_num>) + 9

Example 2: Expressions

Syntactic component:

  <expr> -> <dec_num> | <var> <binary_expr>
  <binary_expr> -> <left_expr> <op> <right_expr> 
  <left_expr> -> <dec_num> | <var> 
  <right_expr> -> <dec_num> | <var> 
  <op> ->  + | * 
Expressions are decimal numbers, variables, or binary expressions having one arithmetic operator and two operands, each of which can be an expression; e.g. 786, NUM, A + 3, 768 * A + B

Map expressions onto N ∪ {error}, were N is the set of integers and {error} is an error value (in this case undefined)

Δ= denotes the definition of a mathematical function . (dot) refers to the child nodes of a node.

The mapping function for expression E and state s:

  Me(<expr>, s) &Delta=
      case <expr> of
        <dec_num> => Mdec(<dec_num>, s)
        <var> => 
             if VARMAP(<var>, s) == undef
                  then error
                  else VARMAP(<var>, s)
       <binary_expr> => 
            if (Me(<binary_expr>.<left_expr>, s) == undef
                  OR Me(<binary_expr>.<right_expr>, s) == undef)
                 then error
            else
            if (<binary_expr>.<operator> == + then
                 Me(<binary_expr>.<left_expr>, s) + 
                 Me(<binary_expr>.<right_expr>, s)
            else Me(<binary_expr>.<left_expr>, s) * 
                 Me(<binary_expr>.<right_expr>, s)
Example 3: Assignment Statements

An expression (as defined above) is denoted by E The state is denoted by s := denotes the assignment operator Maps a state to a state

  Ma(x := E, s) Δ=
      if Me(E, s) == error
         then error
      else s' = {<i'1,v'1>,<i'2,v'2>,...,<i'n,v'n>}, where 
             for j = 1, 2, ..., n, v'j = VARMAP(ij, s) if ij != x;
              Me(E, s) if ij == x
notes: ij and x refer to variable names, not values s' is the state that is mapped from state s after the assignment operation x = E.

Example 4: Logical Pretest Loops (while loops)

  Assume:
  Msl(statement lists -> states)
  Mb(Boolean expressions -> Boolean values)

   Ml(state sets -> state sets)

   Ml(while B do L, s) Δ= 
      if Mb(B, s) == undef
          then error
          else if Mb(B, s) == false
              then s
              else if Msl(L, s) == error
                    then error
                    else Ml(while B do L, Msl(L, s))
Loop Meaning

The meaning of the loop is the value of the program variables after the statements in the loop have been executed the prescribed number of times, assuming there have been no errors.

In essence, the loop has been converted from iteration to recursion, where the recursive control is mathematically defined by other recursive state mapping functions

Recursion, when compared to iteration, is easier to describe with mathematical rigor

Evaluation of Denotational Semantics

* Can be used to prove the correctness of programs

* Provides a rigorous way to think about programs

* Can be an aid to language design

* Has been used in compiler generation systems

* Because of its complexity, they are of little use to language users top