CS 334
|
Rules
Assignment axiom:
{P [expression / id]} id := expression {P}
While rule:
If {P & B} stats {P}, then {P} while B do stats {P & not B}
Composition:
If {P} S1 {Q}, {R} S2 {T}, and Q => R, then {P} S1; S2 {T}
Conditional:
If {P & B} S1 {Q}, {P & not B} S2 {Q}, then {P} if B then S1 else S2 {Q}
Consequence:
If P => Q, R => T, and {Q} S {R}, then {P} S {T}
Prove program correct if show
{Precondition} Prog {PostCondition}
Usually easiest to work backwards from Postcondition to Precondition.
Ex:
{Precondition: exponent0 >= 0} base <- base0 exponent <- exponent0 ans <- 1 while exponent > 0 do {assert: ans * (base ** exponent) = base0 ** exponent0} { & exponent >= 0} if odd(exponent) then ans <- ans*base exponent <- exponent - 1 else base <- base * base exponent <- exponent div 2 end if end while {Postcondition: exponent = 0} { & ans = base0 ** exponent0}
Let us show that:
P = ans * (base ** exponent) = (base0 ** exponent0) & exponent >= 0is an invariant assertion of the while loop.
The proof rule for a while loop is:
If {P & B} S {P} then {P} While B do S {P & not-B}We need to show P above is invariant (i.e., verify that {P & B} S {P}).
Thus we must show:
{P & exponent > 0} if odd(exponent) then ans <- ans*base exponent <- exponent - 1 else base <- base * base exponent <- exponent div 2 end if {P}However, the if..then..else.. rule is:
if {P & B} S1 {Q} and {P & not-B} S2 {Q} then {P} if B then S1 else S2 {Q}.Thus it will be sufficient if we can show
(1) {P & exponent > 0 & odd(exponent)} ans<- ans*base; exponent <- exponent - 1 {P}and
(2) {P & exponent > 0 & not-odd(exponent)} base <- base * base; exponent <- exponent div 2 {P}But these are now relatively straight-forward to show. We do (1) in detail and leave (2) as an exercise.
Recall the assignment axiom is {P[exp/X]} X := exp {P}.
If we push P "back" through the two assignment statements in (1), we get:
{P[exponent - 1/exponent][ans*base/ans]} ans<- ans*base; exponent <- exponent - 1 {P}But if we make these substitutions in P we get the precondition is:
ans*base* (base ** (exponent - 1)) = base0 ** exponent0 & exponent - 1 >= 0which can be rewritten using rules of exponents as:
ans*(base ** exponent) = base0 ** exponent0 & exponent >= 1Thus, by the assignment axiom (applied twice) we get
(3) {ans*(base**exponent) = base0**exponent0 & exponent >= 1} base <- base * base; exponent <- exponent div 2 {P}
Because we have the rule:
If {R} S {Q} and R' => R then {R'} S {Q}To prove (1), all we have to do is show that
(3) P & exponent > 0 & odd(exponent) => ans*(base ** exponent) = base0 ** exponent0 & exponent >= 1where P is
ans*(base**exponent) = (base0**exponent0) & exponent >= 0.Since ans * (base ** exponent) = (base0 ** exponent0) appears in both the hypothesis and the conclusion, there is no problem with that. The only difficult is to prove that exponent >= 1.
However exponent > 0 & odd(exponent) => exponent >= 1.
Thus (3) is true and hence (1) is true.
A similar proof shows that (2) is true, and hence that P truly is an invariant of the while loop!
Axiomatic semantics due to Floyd & Hoare, Dijkstra also major contributor. Used to define semantics of Pascal [Hoare & Wirth, 1973]
Too high level to be of much use to compiler writers.
Perfect for proving programs correct.
E.g. (4 + 2), (12 - 6), and (2 * 3) all denote the same number.
Developed by Scott and Strachey, late 60's early 70's
Program is defined as a mathematical function from states to states. Use these functions to derive properties of programs (e.g. correctness, soundness of typing system, etc.)
Start with functions defined by simple statements and expressions, combine these to get meaning of more complex statements and programs.
Tiny
Syntactic Domains:
I in Ide E in NumExp B in BoolExp C in Command
Formal grammar
E ::= 0 | 1 | read | I | E1 + E2 B ::= true | false | E1 = E2 | not B | fn x => E | E1 (E2) C ::= I := E | output E | if B then C1 else C2 | while B do C | C1; C2
Semantic Domains:
State = Memory x Input x Output Memory = Ide -> [Value + {unbound}] Input = Value* Output = Value* Value = Nat + Bool + (Nat -> Value)where Nat = {0, 1, 2, ...} is as defined above, and Bool = {true, false}
We assume that the following built-in functions are defined on the above domains:
and : Bool x Bool -> Bool, if...then...else... : Bool x Y x Y -> Y + {error} for any semantic domain Y, = : Value x Value -> Bool, hd : Value* -> Value, tl : Value* -> Value*,where each of these has the obvious meaning.
In the denotational semantics given below, we use s or (m, i, o) for a typical element of State, m for Memory, i for Input, o for Output, and v for Value.
Denotational Definitions:
We wish to define:
Note that in the first instance the valuation of an expression may
Therefore we will let E have the following functionality:
E : NumExp -> [State -> [[Value x State] + {error}]]where we write
[[E]]s = (v,s') where v is E's value in s & s' is the state after evaluation of E. or = error, if an error occurs
B and C are defined similarly. The three functions are defined below.
Define E : NumExp -> [State -> [[Value x State] + {error}]] by:
E [[0]]s = (0,s) E [[1]]s = (1,s) E [[read]](m, i, o) = if (empty i) then error, else (hd i, (m, tl i, o)) E [[I]](m, i, o) = if m i = unbound then error, else (m I, (m, i, o)) E [[E1 + E2]]s = if (E [[E1]]s = (v1,s1) & E [[E2]]s1 = (v2,s2)) then (v1 + v2, s2) else error E [[fn x => E]]s = fun n in Nat. E [[E]](s[n/x]) E [[E1 (E2)]]s = E [[E1]]s (E [[E2]]s)Note difference in meaning of function here from that of operational semantics!
Define B: BoolExp -> [State -> [[Value x State] + {error}]] by:
B [[true]]s = (true,s) B [[false]]s = (false,s) B [[not B]]s = if B [[B]]s = (v,s') then (not v, s'), else error B [[E1 = E2]]s = if (E [[E1]]s = (v1,s1) & E [[E2]]s1 = (v2,s2)) then (v1 = v2, s2) else error
Define C : Command -> [State -> [State + {error}]] by:
C [[I := E]]s = if E [[E]]s = (v,(m,i,o)) then (m[v/I],i,o) else errorwhere m' = m[v/I] is identical to m except the value of I is v.
C [[output E]]s = if E [[E]]s = (v,(m,i,o)) then (m,i,v.o) else errorwhere v.o is the result of attaching v to the front of o.
C [[if E then C1 else C2]]s = if B [[B]]s = (v,s') then if v then C [[C1]]s' else C [[C2]]s' else error C [[while E do C]]s = if B [[B]]s = (v,s') then if v then if C [[C]]s' = s'' then C [[while E do C]]s'' else error else s' else error C [[C1; C2]]s = if C [[C1]]s = error then error else C [[C2]] ( C [[C1]]s)End Tiny
Notice that definition of while is a recursive definition.
Thus, if B [[B]] s = True and s' = C [[S]] s, then C[[while B do S]]s = C [[while B do S]]s'
Solution involves computing what is known as least fixed points.
Denotational semantics gained considerable popularity over last 15 years.
Many people have considered denotational semantics as most appropriate for studying meaning of language independent of its implementation.
Good for looking at trade-offs between alternate forms of similar constructs.
Which is best?
No good answer, since have different uses.
More recently, more focus on high-level operational semantics over denotational semantics. Seems better for describing imperative languages.
Axiomatic semantics generally better for proofs of correctness, but still developing techniques for handling higher-order functions and object-oriented systems.
Complementary definitions. Can be compared for consistency.
Programming language definitions usually still given in English. Formal definitions often too hard to understand or require too much sophistication. Gaining much more acceptance. Now relatively standard intro graduate course in CS curricula.
Some success at using formal semantics (either denotational or operational) to automate compiler construction (similar to use of formal grammars in automatic parser generation).
Semantic definitions have also proved to be useful in understanding new programming constructs.
Semantics and type theory now being used to prove security properties of code.
SISD - traditional von Neumann - one processor, one piece of data at a time,
SIMD - currently used supercomputers (typically synchronous)
pipeline (interleave operations) for speed-up or
array-processor - do same thing to all elts of array,
MIMD - most interesting to computer science (asynchronous).
Difficulties - synchronization - wait for one to finish for another to take output and start.
Generalization of sequential algorithm not necessarily fastest on parallel machine
Contention over resources - reader - writer problem.
Each processor has its own memory but share a common communications channel.
In one of three states: executing, blocked, or waiting.
Problems:
With shared memory, communication typically via shared memory.
Problem: Mutual exclusion (reader-writer contention)
Distributed memory, communication via sending messages:
Problem: How to asynchronously send and receive messages?
Need mechanisms in OS to
Three major mechanisms:
Focus here on producer/consumer or bounded buffer problem.
Two processes cooperating, one by adding items to a buffer, the other
removing items. Ensure not remove when nothing there and not overflow
buffer as well.
Text also focuses on parallel matrix multiplication (read on own).
Text also discusses some ways of handling with simple extensions of existing languages:
Coroutines also worth noting (part of Modula-2). Iterators were a special case.
Idea is co-equal routines which pass control back and forth.
E.g., our Modula-2 has library supporting routines:
NewCoroutine(P: PROC; workspaceSize:CARDINAL; VAR q: COROUTINE);
Starts up new coroutine, q, by executing procedure P.
Transfer(VAR from, to: COROUTINE)
Transfers control from one coroutine to another.
Can have multiple coroutines executing same procedure or can all be distinct.
Usually run on single processor.
Can think of as supporting multi-tasking. Good for writing operating systems.
See Modula-2 code in text for bounded buffer problem with coroutines.
Three operations:
InitSem(S: Semaphore; value: integer); Wait(S: Semaphore); Signal(S: Semaphore);
InitSem starts up semaphore with an initial (non-negative) value.
Wait(S): If S > 0 then S := S - 1 else suspend self
Signal(S): if processes are waiting, then wake up a process, else S := S + 1;
Think of Wait(S) as claiming a resource so that no one else can get it, while Signal(S) releases the resource.
In order to solve mutual exclusion problem, must ensure that Wait and Signal execute atomically (i.e., cannot be interrupted and no one else can execute at same time).
If start w/S = 1 then protect a critical region by:
Wait(S); -- grab token {Critical region} Signal(S); -- release tokenCan also start with other values of S, e.g., if start w/S = 0 and call Wait(S) then suspend execution until another process executes Signal(S).
Solution to bounded buffer:
Suppose also have procedures:
CreateProcess(p:PROC; workspacesize: CARDINAL); Creates nameless process StartProcesses; -- starts all processes which have been created. Terminate; -- stop execution of processWhen all processes are terminated control returns to unit calling StartProcesses.
Main program: CreateProcess(Producer,WorkSize); - - create at least one producer CreateProcess(Consumer,WorkSize); -- create at least one consumer BufferStart := 1; BufferEnd := 0 InitSem(NonEmpty, 0) -- semaphore w/initial value of 0 to indicate empty InitSem(NonFull, MaxBuffSize) -- semaphore w/initial value of size of buffer InitSem(MutEx,1) -- semaphore used for mutual exclusion StartProcesses end; Procedure Producer; begin loop read(ch) Wait(NonFull); Wait(MutEx); BufferEnd := BufferEnd MOD MaxBuffSize + 1; Buffer[BufferEnd] := ch; Signal(MutEx); Signal(NonEmpty); end loop; end; Procedure Consumer; begin loop Wait(NonEmpty); Wait(MutEx); ch := Buffer[BufferStart]; BufferStart := BufferStart MOD MaxBuffSize + 1; Signal(MutEx); Signal(NonFull); Write(ch) end loop end;
Why is there a MutEx semaphore?
Technically it is not necessary here since Producer only changes BufferEnd, while Consumer only changes BufferStart, but if they both changed a count of the number of items in the buffer would be important to keep them from executing at the same time!
What would go wrong if you changed the order of the two Wait's at the beginning of either Producer or Consumer?
Biggest problem with semaphores is that they are too low level and unstructured. Accidentally reversing order or Wait'ing twice could be disastrous.