Program Logics for Sequential Higher-Order Control - Semantic Scholar

Report 0 Downloads 27 Views
Program Logics for Sequential Higher-Order Control? Martin Berger

Abstract. We introduce a Hoare logic for higher-order functional languages with control operators such as callcc. The key idea is to build the assertion language and proof rules on the basis of types that generalise the standard types for control operators (for ’jumping-to’) with dual types (for ’being-jumped-to’). This enables the assertion language to capture precisely the intensional and extensional effects of jumps by internalising rely/guarantee reasoning, leading to simple proof rules for call-by-value PCF with callcc and/or name-abstraction. All new operators come with powerful associated axioms. We show that the logic allows specification and reasoning about non-trivial examples of using callcc. The logic matches exactly with the operational semantics of the target language (observational completeness), is relatively complete in Cook’s sense and allows efficient generation of characteristic formulae.

1

Introduction

Non-trivial control manipulation is an essential part of computation and shows up in many variants such as jumps, exceptions and continuations. Increasingly, it is no longer the preserve of low-level languages. To realise the goal of pervasive proof-carrying code with proof-compilation, it is thus vital to have logical accounts of control that integrate well with operational semantics and program logics at other levels of abstraction. Unfortunately, satisfactory axiomatic semantics of control manipulation have been put forward only for simple jumps in first-order imperative languages [7]. A reason for this omission maybe that program logics are traditionally based on abstracting behaviour into input/output relations. This is a powerful abstraction for simple languages but does not cater well for jumping, a rather more intensional form of behaviour. This work proposes jumps xhei ˜ as an explicit logical operator which says that a program jumps to x carrying values e. ˜ This formula is dual to the evaluation formulae x • heiA ˜ which mean: jumps to x carrying values e˜ lead to a program state where A holds [12]. The special case where e˜ contains a return address r and A specifies jumps to r only, recovers input/output behaviour. This duality between jumping and beingjumped-to is made precise with types. We extend the standard functional types with a ˜ ? expressing jumping-to with values of types α. ˜ It corresponds to cont(α), ˜ the type (α) ˜ ! means being-jumped-to, with arguments type of continuations in [8]. The dual type (α) ˜ Types enable powerful combinations of intensional and extensional speciof types α. fications through our third proposal: a rely/guarantee formula {A}B which says that if the environment is as specified by B, then the program will act as constrained by B. This is a strict generalisation of A ⊃ B in that {A}B no longer requires that A and B have the same type, only that they are dual: e.g. xh2ui ∧ {∀vw.x • hvwiwhv + 1i}uh3i says that a the program jumps to x carrying 2 and u (an intensional specification at x). In addition, if the environment offers a server at x that computes the successor of its first ?

Omitted proofs and additional material can be found at [4].

2

Martin Berger

argument and returns the result at the second (an extensional specification at x), then the program will jump to u carrying 3. This mix of intensional and extensional specification facilitates. reasoning about complicated forms of control manipulation like non-linear continuation usage. Contributions. This work identifies the key ingredients for axiomatising sequential control: dual types for jumps and the targets of jumps, explicit logical representation of jumps, an internalised rely/guarantee construct as a first-class citizen in the logical language, axiomatisation of the new constructs and their interplay with other logical operators. We use these for the first axiomatic semantics of functional languages with callcc or λµ-like control features and prove relative and observational completeness, and show that characteristic formulae can be derived.

2

PCF With Jumps And Associated Logics

Now we define our main programming language. We extend call-by-value PCF with callcc and throw, and call the resulting language PCF+ . Later we briefly consider µPCF, a variant of PCF with λµ-like features. Types, terms and values are given by the grammar below. Sums and products for PCF+ are discussed in [4]. α ::= N | B | M ::= xα | c | | callcc V ::= xα | c |

Unit | α → β | X | µX.α | (α)? ˜ | if M then N else N 0 MN | λxα .M | rec f .λx.M | op(M) | throw M N λxα .M | rec f .λx.M

Here (α)? is the type of continuations carrying α, and x, f range over variables. The notions of free variables (fv(M)) and bound variables (bv(M)) of M are defined as usual. Typing judgements are standard and of the form Γ ` M : α, where Γ is a finite, partial map from variables to the types given by the grammar above. From now on we assume all occurring programs to be well-typed. Informal Explanation of Our Approach. To understand callcc and throw, consider a typical program: def

M = callcc λk.(3 + throw k 5).

(1)

Here callcc captures the current continuation, the code that is passed the result of evaluating the argument to callcc ; throw takes two arguments, jumping to the first of those, carrying the second as value. Such programs evaluate in configurations (M, σ) where σ maps names k of continuation type to continuations, i.e. terms like M([·]7) with a hole. As an example, we have the following reduction for M + 1 with σ0 = σ · k 7→ [·] + 1: (M + 1; σ)



((3 + throw k 5) + 1; σ0 )



(5 + 1; σ0 )



(6; σ0 )

The example illustrates that throwing a value is like value-passing jumping in that it abandons its context (here 3 + [·]) and transfers control elsewhere. Dually, callcc

Program Logics for Sequential Higher-Order Control

3

dynamically generates a name to jump to. The question is: how to reason about this behaviour? In [12] assertions named the value returned by a program upon termination and used that name to specify that value, leading to judgements like {T} 5 :u {u = 5}. Operationally, a value like 5 can be seen as a value-passing jump carrying 5 to some default port left implicit in the language, but made explicit in implementations, usually as a return address on the stack. This return address can be left implicit in the absence of advanced control constructs because there are no alternatives for returning: every function, if it returns at all, does so at the default port. Advanced control constructs break this simplicity: for example throw k 5 will jump to k and not to the default port. Our logic reacts to this loss of predictability by explicitly specifying jumps with jumping formulae xhei. ˜ We also name default ports in judgements. Then we can specify: {T} 5 :u {uh7i}

{T} throw k 5 :u {kh5i}.

In these assertions u no longer names the value returned upon termination at the default port, but is the default port instead. Hence we can specify not only values returning at the default port but also those that do not. The assertion above about throw k 5 can be used to infer {T} 3 + throw k 5 :u {kh5i}.

(2)

Using the rules in [12] for functional abstraction, easily adapted, we infer from (2): {T} λk.3 + throw k 5 :m {m(b)∀ku.b • hkuikh5i}.

(3)

˜ is short for ∃y.(xh ˜ yi∧A). ˜ Hence (3) says: the program returns a value named Here x(y)A b at the default port m. This value, every time it is jumped to with one argument named k, has default port u, and jumps to k carrying 5. The question is now: how to go from (3) to a specification of M, i.e. how to reason about callcc N? Let’s consider our options: (1) N does not terminate. In this case callcc N must also diverge. (2) N jumps to some place other than its default port. Then callcc is ignored and callcc N behaves like N. The interesting case is (3) where N jumps to the default port carrying a function λk.N 0 with {A} N :n {n(a)∀km.a • hkmiB}. By typing, variables k and m have the following functions: m is the default port for evaluation of N 0 , and k is another port that N 0 may jump to using throw. If we execute callcc N with default port u then u names the current continuation of callcc N. This means u must somehow be substituted for k in B. But N in callcc N may alternatively choose not to jump to the current continuation. It may instead just return at its default port m: an example of ignoring the passed continuation would be callcc λk.1. Then the default port for N, named m in B, should be the same as that of callcc N. Hence k and m must be replaced by the same name. The following rule does this. {A} N :m {{m(a)ahuui}B} {A} callcc N :u {B}

(4)

This rule uses a rely/guarantee formula {C}E which is easy to understand: if C holds of the environment, then the current program together with the environment guarantee E. Hence (4) says the following:

4

Martin Berger

Assume A holds. If running N with default port m in an environment where each jump mhai leads to a jump ahuui allows us to infer B, then running callcc N under A with default port u also guarantees B. Clearly, this deals with the case where N diverges by assuming A to be F. Otherwise, we assume A to be T for simplicity. If N evaluates to throw k 2, say, then callcc N leads to callcc throw k 2 which in turn reduces to throw k 2. This is exactly matched by (4): in the present case B would typically be kh2i, and since default ports are always freshly chosen, catching jumps to m in the environment by m(a)ahuui does not affect jumps to k at all. Hence kh2i implies {m(a)ahuui}kh2i, which means that (4) gives the desired conclusion. Finally, if N converges to an abstraction (λk.N 0 ), we can infer m(a)a(kn)B. Now the jump at m is caught by m(a)ahuui, and the ensuing jump ahuui is in turn going to the target a(kn)B, leading to B[u/kn]. This way the abstracted k and the default port for the evaluation of N[u/k] both become u. Hence jumps to the abstracted k and normal termination both go to u. This behaviour corresponds exactly to that of callcc N. To see the rule in action, let us return to M from (1) above, and reason as follows. 1

{T} λk.3 + throw k 5 :m {m(a)a(km)kh5i}

2

{T} λk.3 + throw k 5 :m {{m(a)ahuui}u(5)}

3

{T} M :m {uh5i}

X C HANGE ,

1

Rule (4) above, 2

Assuming that inference (2) is valid, rule (4) gives the expected result. The key idea behind inference (2) is simple: If a program makes a jump x (for simplicity we omit arguments), and if the environments is such that jumps to x trigger a jump to y, then (with these assumptions on the environment), we can conclude to y. Using the rely/guarantee formula, we can express this formally: ⊃

x

{x.y}y.

(5)

This cannot be expressed as x ⊃ (x.y) ⊃ y because the name x cannot be used as input and output in a single formula. Hence using rely/guarantee formulae is vital for this style for reasoning. The implication (5) generalises to value-passing jumps, free or bound. x(y)A ˜ ⊃ {x(y){A}B}∃ ˜ y.B ˜

xhei ˜ ⊃ {x(v)A}A[ ˜ e/ ˜ v]. ˜

(6)

Applying this to the postcondition of Line 1 in the inference above we get m(a) a(km)kh5i | {z }



{m(a){A}uh5i}uh5i.

(7)

A

Now we apply the same reasoning to ahuui. ahuui



{a(km)kh5i}uh5i

(8)

Program Logics for Sequential Higher-Order Control

5

Rely/guarantee formulae admit a form of consequence rule: if A ⊃ A0 and B0 ⊃ B hold, then {A0 }B0 ⊃ {A}B. Hence (7, 8) imply ⊃

m(a)A

{m(a){A}uh5i}uh5i



{m(a).ahuui}uh5i.

Next we use the consequence rule [C ONS] to reason as follows. 1

{T} λk.3 + throw k 5 :m {m(a)a(km)uh5i}

2

m(a)a(km)kh5i

3

{T} λk.3 + throw k 5 :m {{m(a).ahuui}uh5i}

4

{T} M :m {uh5i}



{m(a).ahuui}uh5i

X C HANGE

C ONS, 1, 2

Rule (4) above, 3

We are now ready to introduce the logic formally. Types for the Logic. We present our logic in several steps. First we define our types which decompose the well-known types of λ-calculi, following [13]. Next we define expressions and formulae of the logic. Then we present rules for PCF+ . Finally we give an axiomatisation for the logic. This logic will be used to axiomatise the semantics of PCF+ and extensions of PCF with the jumping constructs of the λµ-calculus [18]. Types of the logic are given by the following grammar. ˜ ? | (α) ˜ ! α ::= N | B | Unit | X | µX.α | (α) Note that we remove function spaces form the PCF+ types and add a being-jumpedto type. A typing environment (Γ, ∆, ...) is a finite map from variables to closed types, i.e. not containing free type variables. We write dom(Γ) for Γ’s domain of definition. To mediate between the types of the logic and those for PCF, we translate as follows. def

α• = (α◦ )?

◦ def ◦ ˜ ? = (α˜ )! (α)

def

def

(α → β)◦ = (α◦ β• )!

N◦ = N

Other basetypes are like N. This is extended to typing environments Γ = x˜ : α˜ in a def pointwise manner: Γ◦ = x˜ : α˜◦ . def

def

˜ p ) = p and md(α) = The mode of α, denoted md(α), is given inductively: md((α) def

def

def

˜ ? = (α) ˜ ! , (α) ˜ ! = (α) ˜ ? and α = α in val otherwise. The dual α of α is given by: (α) all other non-type-variable cases. Now is the least partial, binary, associative and commutative operation on types such that: def

α α = α (md(α) =!)

def

α α = α (for all other types α)

We write α  β to indicate that α β is defined. We extend  to typing environments as follows. Γ  ∆ iff for all x ∈ dom(Γ) ∩ def

dom(∆) : Γ(x)  ∆(x). Γ ∆ is defined iff Γ  ∆. In this case, with S = dom(Γ) ∩ def

dom(∆): Γ ∆ = {(x, Γ(x) ∆(x) | x ∈ S)} ∪ Γ|dom(Γ)\S ∪ ∆|dom(∆)\S

6

Martin Berger

Expressions, Formulae, Assertions. The expressions and formulae for our logic are generated by the following grammars, with x ranging over variables. e A

::= ::=

xα | c | op(e) ˜ 0 e = e | A ∧ B | ¬A | ∀xα .A | x • heiA ˜ | xhei ˜ | {A}Γ B

Here c ranges over constants like 0, 1, 2, ... and op over functions like addition. Standard operators such as implication and existential quantification are defined as usual and are used without further comment. Typing judgements for expressions ∆ ` e : α and formulae ∆ ` A are defined below for the new constructs. In defining typing judgements, we make use of an auxiliary relation Γ `inp A which states that (1) Γ ` A and (2) that A is input-moded. That means A does not contain jumps xhei ˜ that are not guarded by an evaluation formula. ˜ ! ∆ ` e˜ : α˜ ∆ ` x : (α) ∆ ` x • heiA ˜

∆`A

∆ ` x : (α)? ∆ ` e˜ : α˜ ∆ ` xhei ˜

Γ∆ `inp A Γ(∆ ∆) ` B Γ∆ ` {A}∆ B

The evaluation formula x • heiA ˜ says the following: any jump to x carrying arguments ˜ simply specifies e˜ will lead to a terminating computation that satisfies A. Dually, xhei a jump to x carrying e. ˜ We write xh(y)eiA for ∃y.(xhyei ∧ A). Finally a rely/guarantee formula {A}∆ B says that if the environment is typable under Γ∆ and does satisfy A, then the current program (assumed typable as Γ∆) together with the environment terminate and guarantee B (i.e. rely/guarantee formulae are tailored for total correctness here). A typical example of use is x(yz)zhy + 1i ∧ a



{∃g.(x • h7zizhgi ∧ even(g)) ⊃ a • hib}b

This assertion says that the current program can jump at b, provided it can jump to a and has a successor function located at x. In addition, the environment is required to have a server at a which jumps to b when invoked. Finally, the server at x must reply with an even number when invoked with 7. Judgements, also called assertions, are of the form {A} M :m {B}. Typing for judgements is given below: Γ-m ∆◦ `inp A ∆-m ` M : α Γ(∆◦ ∆◦ ) · m : α• ` B Γ; ∆; m : α ` {A} M :m {B} From now on we assume all occurring expressions, formulae and assertions to be welltyped, unless stated otherwise. Example 1. 1. The assertion {x = 7} x :m {m(a)¬even(a)} simply states that x returns def

an odd number at its default port where even(e) = ∃n.e = 2 · n. 2. With {T} M :u {u(x)x(yz)zhy + 1i} we specify a program that jumps to u, carrying as payload a pointer to a successor function. It is easy to establish that (λy.y + 1) is such a program. In contrast, there is no PCF-program M such that {T} M :u {u(x)x(ym)zhy + 1i} because PCF+ does not offer facilities, such as continuations or exceptions, for expressing converging functions that do not return normally.

Program Logics for Sequential Higher-Order Control

7

3. Let A be the assertion f (xu)uhx · xi. It says that jumps to f with arguments x, a natural number, and u of type (N)? will yield a jump, carrying a value x · x. In other words, it specifies a squaring function. Then the formula {A}m(a)a(xu)uhx − 2i says that if the environments guarantees that f is a squaring function, then the program jumps to m carrying a pointer to a function that subtracts 2 from its argument. Our logic will be able to derive that the assertions {A} M :m {m(a)a(xu)uhx − 2i}

{T} M :m {{A}m(a)a(xu)uhx − 2i}

are equivalent. This shows how assume formulae internalise preconditions. 4. It is natural to mix default returns with explicit jumping. The next example shows how our uniform use of jumps, together with explicit default ports makes this very easy. {T} if x = 0 then 1 else throw k 2 :u {(x = 0 ⊃ uh1i) ∧ (x 6= 0 ⊃ kh2i)}

− VAR {A[x/u]} x :u {u(a)A}

{T} λx.M :u {u(a){fwga }A} R EC {T} rec g.λx.M :u {u(a)A}

{C-x ∧ A} M :m {B} A BS {C} λx.M :u {u(a)∀xm.{A}a • hxmiB}

− C CC {T} callcc :u {u(a)a(xm)xhmmi}

{A} M :m {{{A0 }m(a)B}C} {A0 } N :n {{n(b)ahbi}B} T HROW {A} throw M N :u {C} {A} M :m {{{A0 }m(a)B}C} {A0 } N :n {{n(b)ahbui}B} A PP {A} MN :u {C}

{A} M :m {{B}C} X C HANGE {A ∧ B} M :m {C}

Fig. 1. Selected compositional rules for total correctness.

Rules. We continue with the rules for PCF+ . Rules for program logics are divided into language rules, partly given in Figure 1, and structural rules, which are in essence those of [12], suitable adapted to using default ports. Our rules are typed, but for readability the types are omitted as much as possible. This is admissible because we can always deduce the unique minimal typing that types each given assertion. From now on we assume that all rules are well-typed. We explain the rules in some detail. [VAR] is a straightforward generalisation of the corresponding rule in [12]: it says that the program x returns at the default port carrying something named a that is correctly described by A, provided A[x/a] is assumed. The recursion rule [R EC] is likewise an immediate adaption of the corresponding rule in [12], but forwarding all jumps to the recursion variable g directly to the recursive function at a. We have to use forwarding rather than straight substitution because jumps ˜ ? ˜ ! , y : (α) to f have a type dual to that of the jump target at a. The forwarder fwxy ` x : (α)

8

Martin Berger def

is given by fwxy = x(v)yh ˜ vi. ˜ Conventional recursion rules based on natural induction are derivable from [R EC]. [A BS] guarantees a jump at the default port carrying a function which behaves like the specification of its body. Note the use of a rely/guarantee formula to adjoin the premise of the precondition to the evaluation formula in the conclusion’s postcondition. This shows how rely/guarantee formulae break the rigidity of the pre-/postcondition distinction since the function of the precondition can now be simulated precisely in the postcondition using rely/guarantee formulae. At the heart of the new rules is [A PP] which specifies applications. It has three heavy task: to negotiate normal function application, to deal with termination, which is trickier than in purely functional languages, and to allow multiple returns of a function application which can be the effect of non-linear continuation usage. Nested rely/guarantee formulae are required in this case. The formula {A0 }m(a)B describes a server that can be jumped to at m, and upon invocation terminates and acts like B, all assuming that the environment is as described by A0 , assuming that A0 does not hypothesise on m, i.e. m is not a free name in the type of A0 . Then {{A0 }m(a)B}C says that if the environment provides a server at m which guarantees B upon invocation, then the program ensures termination and C, all provided that the environment’s environment (i.e. the program) ensures A0 . Clearly, if M jumps guaranteeing C, but not to the default port, then the nested rely/guarantee formula has no effect and C implies {{A0 }m(a)B}C. In this case N is ignored, this corresponds exactly to the operational semantics, e.g. (throw k 3)M → throw k 3. The special case of A0 being F is important. It means N may not terminate. Then {A0 }m(a)B is trivially true, hence by typing, any server may be at m, including that which diverges upon every invocation. However, {T}C guarantees termination in our total correctness setting. This is only possible if M does no initial jump to m, i.e. if it does not return at the default port, as already described above. If M does jump to m, then A must already be F. Hence non-termination of the argument N is catered for. Finally we consider purely functional and terminating application by example: assume M is λx.x and N is 3. Then {T} 3 :n {nh3i}, hence {T} 3 :n {{n(b)ahbui}ah3ui} using the axioms formalised below. Likewise we can derive (using the Consequence rule) {T} λx.x :m {m(a)a(3u)uh3i}. Using formal reasoning we infer {{T}m • haiah3ui}uh3i. Then [A PP] yields {T} (λx.x)3 :u {uh3i} as required. [C CC] states that callcc is a constant value which always returns at the default port, carrying a function value which takes two arguments x, m and upon invocation jumps to x carrying m twice. In conjunction with [A PP] we can infer Rule (4) easily. [T HROW] is quite similar to [A PP] because it has to deal with the same divergence behaviour. Let’s consider throw k 1. We can derive {T} k :m {mhki} and {T} 1 :n {nh1i}. By formal reasoning we see that nh1i ⊃ {n(b)ahbi}ah1i. Likewise mhki ⊃ {m(a)ah1i}kh1i ⊃ {{T}m(a)ah1i}kh1i. Hence overall we obtain {T} throw k 1 :u {kh1i} as required. Finally, [X C HANGE] ensures the correspondence between preconditions and the rely-part of a rely/guarantee formula: it does not matter if B is assumed in the former or the latter. Note that judgements allow hypothesising on m in the postcondition of the premise, but not in the precondition of the conclusion. This is merely a simplification that can easily be dropped, if a more symmetric system is desired.

Program Logics for Sequential Higher-Order Control

9

Axioms. It is vital to have a powerful axiomatisation of a logic’s consequence relation. Without that, reasoning would need to “go through the model”, thus negating the abstraction gains offered by program logics. Below we exhibit some key axioms – [4] offers a more complete list that is sufficient for all examples considered so far. x(y)A ˜ xhei ˜ x(y){x( ˜ y)zh ˜ y˜ai} ˜ xA ∆ x • hyi({A} ˜ B) x(y){A ˜ -y˜ }∆ B {A}∆ B {A0 }∆ B0 x(v)y(˜ ˜ z)A

⊃ ≡ ⊃ ⊃ ≡ ⊃ ⊃ ≡

x ∃y.B {x(y){A}B} ˜ ˜ {T}xhei ˜ {x(y)zh ˜ y˜ai} ˜ x zh(y) ˜ aiA ˜ ∆ {A} x • hyiB ˜ {A}∆ x(y)B ˜ {A ∧C}∆ (B ∧C) {A}∆ B (when A ⊃ A0 and B0 ⊃ B) x(v)y(˜ ˜ z)(A ∧ x(v)y(˜ ˜ z)A)

[X C HANGE] [¬X C HANGE] [M ULTI] [A-I N] [A-O UT] [I NVAR] [C ONS] [S TABLE]

All axioms can be recast using bound output instead of free, or vice versa, and likewise for input. The [X C HANGE] axiom has already been discussed above. Its counterpart [¬X C HANGE] is applicable when we have a jump and the environment does not provide a compensating input, where this absence of compensation is determined by typing. It is sufficient to have T in the rely-formula because it can aways be strengthened by [C ONS] below. [M ULTI] is required for non-linear continuation usage: startdef

ing from A = m(a)a(xn)m(a)B we may want to compensate the jump to m in the environment by a server. Using [X C HANGE] and some other axioms, we can infer A ⊃ m(a){m(a)C}a(xn)C, but now we are stuck since [X C HANGE] can no longer be used to convert the leftmost jump to m because of typing. [M ULTI] comes to help and in effect allows to fuse two identical rely/guarantee formulae into one. The next two axioms [I NVAR , C ONS] internalise the invariance and consequence rules of Hoare logic. Finally, [S TABLE] formalises the statelessness of functions. We conclude this section by considering a variant of PCF+ with somewhat different control manipulating constructors. An Alternative Form of Jumping. A Curry-Howard correspondence for classical logic with novel jumping operators is proposed in [18]. Here throw M N is replaced by [x]M which is a jump to x carrying M. As a binding operator for jumps µx.M replaces callcc. In contradistinction from PCF+ , variables used for jumping cannot be λ-abstracted. The resulting calculus is called µPCF and can express PCF+ and vice versa. If we want to specify µPCF programs, we remove [C CC , T HROW] from the rules for PCF+ and add essentially only the rules given next. {A} M :m {B} {A} [x]M :u {B[x/m]}

3

{A} M :m {B} {A} µx.M :u {B[u/x]}

{A} M :m {B} {A[z/xy]} M[z/xy] :m {B[z/xy]}

Reasoning About Non-Linear Continuation Usage

One peculiarity of continuations that sets them apart form other control constructs like goto is that the can be used non-linearly, i.e. more than once. A well-known example [9] of such behaviour is def

argfc = callcc λk.(throw k λx.(throw k λy.x)).

10

Martin Berger

This function distinguishes λ-terms that would be equated in the absence of continuations. [19] shows that (λx.(x1); (x2)) argfc = 1 and (λxy.(x1); (y2)) argfc argfc = 2. Our logic can easily specify and reason about such advanced continuation usage and we now show that: {T} (λx.(x1); (x2)) argfc :u {uh1i}

{T} (λxy.(x1); (y2)) argfc argfc :u {uh2i}.

To infer these specifications, we introduce some convenient derivable inference rules, starting with the following. {T} V :m {m(a)A} S IMPLE T HROW {T} throw k V :u {k(a)A} We can derive the following assertion mechanically from program syntax. {T} argfc :u {u(a)a(x·)u(b)b(·r)rhxi} We begin by showing that {T} (λx.(x1); (x2)) argfc :u {uh1i}. We do this using [A PP] for this purpose together with the next two judgements, assumdef

ing B = ah(b)uib(x·)ah(b)uib(·r)rhxi. 1

{T} λx.((x1); (x2)) :m {{{T}m(a)B}uh1i}

2

{T} argfc :n {{n(b)ahbui}B}

3

{T} (λx.(x1); (x2)) argfc :u {uh1i}.

A PP, 1, 2

Hence all that remains to be done is to establish Lines 1 and 2. We begin with the latter. def

=

4

B0

5

{T} argfc :n {B0 }

6

B0

7

{T} argfc :n {{n(b)ahbui}B}



n(b)b(x·)n(b)b(·r)rhxi

{n(b)ahbui}B

see below C ONS, 5, 6

The inference of line 5 is completely mechanical from the syntax of argfc, while the implication in Line 6 is conceptually the most difficult step of the entire proof and explained in detail below. But before that we tackle Line 1 using the following derivable rules where m(·)B means m(x)B for some fresh x not occurring in B. − A PP S IMPLE {T} x e :m {xhemi}

{A} M :m {{{A0 }m(·)B}C} {A0 } N :n {B} S EQ {A} M; N :u {C}

In addition we make use of the derivable axiom given next. It coalesces several applications of [X C HANGE] into one. xhyzi ˜



{z(v)A}xh ˜ y(z ˜ 0 )iz0 (v)A ˜

[OX C HANGE]

Program Logics for Sequential Higher-Order Control

11

Now we begin the derivation. 8

{T} x 1 :b {xh1bi}

A PP S IMPLE

9

xh1bi



OX C HANGE

10

{b(·)xh2ui} x 1 :b {xh1(c)ic(·)xh2ui}

11

{T} x 2 :u {xh2ui}

A PP S IMPLE

12

{T} (x1); (x2) :u {xh1(c)ic(·)xh2ui}

S EQ, 10, 11

13

C

14

{T} λx.((x1); (x2)) :m {m(a)C}

15

m(a)C

16

B

17

m(a)C

18

{T} λx.((x1); (x2)) :m {{m(a)B}uh1i}

def

=

{b(·)xh2ui}xh1(c)ic(·)xh2ui

C ONS, 8, 9

a(bu)bh1(c)ic(·)xh2ui





A BS, 12

{m(a){C}uh1i}uh1i

X C HANGE

{C}uh1i ⊃

see below

{m(a)B}uh1i

C ONS, 15, 16 C ONS, 14, 17

Now we derive (16). We note that by [S TABLE] we can derive ⊃

C

a(bu)(C ∧ bh1(c)ic(·)xh2ui)



a(bu)bh1(c)iC

and likewise for B. Hence in fact: C B

⊃ ⊃

a(bu)bh1(c)ia(bu)bh1(c)ic(·)bh2ui ah(b)uib(x·)ah·uiah(b)uib(·c)chxib(·u)uhxi.

Through multiple applications of the following derivable rule we justify the derivation below. A ⊃ {B}C Z IG Z AG xhe( ˜ y)iB ˜ ⊃ {x(v˜y)A}∃ ˜ y.C[ ˜ e/ ˜ v] ˜ This rule is a key tool for dealing with chained applications of [X C HANGE]. ⊃

{T}uh1i

19

uh1i

20

bh2ui

21

ch1ib(·a)ah1i

22

bh1(c)ic(·)bh2ui

23

B0

24

C0

25

B0

26

bh1(c)iC0

27

B



def

=

¬ X C HANGE

{b(·a)ah1i}uh1i ⊃

{c(·)bh2ui}uh1i ⊃

20

{b(·c)ch1ib(·a)ah1i}uh1i

21

ah(b)uib(·c)ch1ib(·a)ah1i

def

=

a(bu)bh1(c)ic(·)bh2ui



{C0 }uh1i



19



22

{b(x·)B0 }uh1i

25

{C}uh1i

26

Thus (16) must hold. We are now ready to tackle (6). We need one more axiom which is not derivable from the others but part of the full axiomatisation: x(y){A}B ˜



{A}x(y)B ˜

[A-I N]

12

Martin Berger def

The reverse is incorrect in a total correctness setting. To conclude, let fw∗ = n(b)ahbui. Then: ⊃

{b(·r)rhxi}ah(b)uib(·r)rhxi

28

ahbui

29

n(b)b(·r)rhxi

30

b(x·)n(b)b(·r)rhxi

31

b(x·){fw∗ }ah(b)uib(·r)rhxi

32

b(x·)n(b)b(·r)rhxi

33

B0

34

n(b){fw∗ }b(x·)ah(b)uib(·r)rhxi

35

B0







OX C HANGE

{fw∗ }ah(b)uib(·r)rhxi ⊃



Z IG Z AG, 28

b(x·){fw∗ }ah(b)uib(·r)rhxi ⊃

{fw∗ }b(x·)ah(b)uib(·r)rhxi

C ONS, 29 A-I N, 30

{fw∗ }b(x·)ah(b)uib(·r)rhxi

n(b){fw∗ }b(x·)ah(b)uib(·r)rhxi ⊃

30, 31 C ONS, 32

n(b){fw∗ }b(x·)ah(b)uib(·r)rhxi

B

M ULTI 33, 34

Using similar reasoning, we also derive {T} (λxy.(x1); (y2)) argfc argfc :u {uh2i}.

4

Models and Completeness

This section gives the semantics of our logic and states soundness and completeness results. We use a typed π-calculus to construct our semantics. This choice simplifies models and reasoning because models need to be built only once and then cater for a wide variety of source languages, including all used here. Moreover, π-calculus semantics for sequential languages with non-trivial control is substantially simpler and less ad-hoc than the original operational semantics, especially for λµ-style jumping [13]. In addition, (typed) labelled transitions are a powerful reasoning tool for π-calculus processes that currently has no match in the world of sequential control. A further reason is that the types used in model construction are exactly those used for typing the logic. Thus types serve as glue, unifying the logical and operational view of sequential computation with jumps. Processes. Expressions and Processes are given by the grammar below where x ranges over names which must be disjoint from variables, cf. [13] for details. e ::= x | c | op(e) ˜

˜ | !x(v).P ˜ | (νx)P | P|Q P ::= 0 | xhei

We assume given an evaluation relation ⇓ on expressions, e.g. 3 + 8 ⇓ 11. The structural congruence ≡ is standard. Reductions →, closed under parallel composition, restriction and application of ≡ are given by: xhei|!x( ˜ v).P ˜ → P[y/ ˜ v]|!x( ˜ v).P, ˜ in both cases assuming that e˜ ⇓ y. ˜ Types and typing environments for processes are those for the logic. Modes, ranged over by φ, φ0 , ..., are O and I. Typing judgements are of the form `φ P . A. Here A maps free names in P to types. The typing rules are those of [13], but affine [11]. A process `φ P . A is semi-closed is x ∈ fn(P) implies md(A(x)) =?. We write → → for the transitive closure of ≡ ∪ &. P is a value if P → → Q implies P ≡ Q, in this case we write P ⇓ Q. We write ∼ = for the canonical typed congruence. We can use this calculus to give fully abstract encodings of PCF+ and µPCF [13, 16]. Translation is straightforward and we show some key cases. def

[[λx.M]]u = u(a)!a(xm).[[M]]m def

def

[[callcc ]]u = u(a)!a(xm).xhmmi

[[throw M N]]u = (νm)([[M]]m |!m(a).(νn)([[N]]n |!n(b).ahbi))

Program Logics for Sequential Higher-Order Control

13

Note that the translation of callcc is almost identical with the postcondition of [C CC]. The Model and Satisfaction Relations. Models of type Γ are of the form (ξ, P) where P is a semi-closed process and ξ maps the domain of Γ as follows: if Γ(x) = N, then ξ(x) ∈ N, and likewise for other basetypes. In all other cases, ξ(x) is a name, where we ˜ ! and x 6= y. P is typable as `φ P . A where require ξ(x) 6= ξ(y) whenever Γ(x) = (α) ˜ ! A(x) = α means that Γ(y) = α for some y with ξ(y) = x. Furthermore, if Γ(x) = (α) and ξ(x) = y then P must be of the form P|!y(v).Q. ˜ If φ = O, (ξ, P) is an output-model, else it is an input-model. The satisfaction relation |= {A} M :m {B} is holds if for all input-models (ξ, P): (ξ, P) |= A

implies ([[M]]m |P) ⇓ Q and (ξ · m : {m}, Q) |= B.

Note that the model required to satisfy B is an output-model. On formulae, the satisfaction relation is standard except in the following three cases, simplified to streamline the presentation. – (ξ, P) |= xhyi if (ξ, P) is an output model such that: P ∼ = Q|ahbi, ξ(x) = a, ξ(y) = b. – M = (ξ, P) |= x • hyiA if M is an input-model, P ∼ = Q|!a(v).R with ξ(x) = a and P|ahξ(y)i ⇓ P0 , and (ξ, P0 ) |= A. – (ξ, P) |= {A}B if for all semi-closed Q of appropriate type and all ξ0 that agree with ξ on shared variables, such that (ξ0 , Q) |= A we have: P|Q ⇓ R and (ξ ∪ ξ0 , R) |= B. The construction shows that rely/guarantee corresponds to parallel composition [15]. By straightforward induction we prove the following [4]. Theorem 2. All axioms and rules are sound. Descriptive Completeness. It desirable for a language that its axiomatic and operational descriptions coincide, i.e. that two programs are contextually indistinguishable exactly when they satisfy the same pairs of formulae. This property is called observational completeness. We prove observational completeness as a consequence of descriptive completeness (DC), an even stronger property that constructs a characteristic formula pair for ever program by induction on program syntax. DC also implies relative completeness in the sense of Cook. Definition 3. A pair (A, B) in a total characteristic assertion pair (TCAP) of M at m if the following condition hold, assuming well-typedness, assuming that (ξ, P) is an input-model. – (Soundness) |= {A} M :m {B}. – (Minimal Termination Condition, MTC) [[M]]m ξ ⇓ iff (ξ, P) |= A. – (Closure) If |= {A0 } N :m {B} and A0 ⊃ A and ξ |= A0 . Then [[M]]m ξ ⇓ M ≤ N. Here ≤ is the standard contextual preorder on PCF+ terms, and [[M]]m ξ means turning [[M]]m into a semi-closed process with the non-?-moded values in ξ. To construct TCAPs inductively we first convert a program M into sequential let-form (e.g. all applications are of the form let y = f x in L and all throwing happens in the

14

Martin Berger

form throw k V , etc.) which is straightforward. Then we apply the inference system `tcap , the key new rules of which are in Figure 2. [C CC] is unchanged from Figure 1 as it is a value. [T HROW] simply substitutes k, the continuation to be used next, for V ’s default port. We know that V converges at its default port because it is a value. Finally [A PP] follows that treatment of application in [10] by essentially requiring in the precondition the effect to be achieved by application. This is possible because the function f and its argument x are both variables about which we cannot infer anything non-trivial, unless it is assumed. The free variable i formalises a case-distinction between convergence of the application at the default port, and jumping. In the latter case, the argument can be ignored, but we must ensure that the jump does not go to the default port u of the conclusion. In addition, the target of this jump is recorded in the free variable h, and the values carried are in h. The remaining rules are adaptable from [10]. `tcap {C} L :u {C0 } i, g, h fresh {∀m. f • hxmi(i = 0 ⊃ m(b)(C ∧ a = b)) ∧ (i 6= 0 ⊃ (ghhi ∧ g 6= m)))} let a = f x in L :u {(i = 0 ∧C0 ) ∨ (i 6= 0 ∧ ghhi ∧ g 6= u)} tcap

`

− C CC {T} callcc :u {u(a)a(xm)x(mm)}

A PP

`tcap {T} V :m {m(a)B} T HROW ` {T} throw k V :u {k(a)B} tcap

Fig. 2. Key novel TCAPs derivation rules for cccPCFv .

Theorem 4. (Descriptive Completeness for Total Correctness) 1. Let Γ ` M : α. Then `tcap {A} M :m {B} implies that (A, B) is a TCAP for M at u. 2. (Observational Completeness) M ∼ = N iff for all formulae A, B we have: |= {A} M :u {B} iff {A} N :u {B}. 3. (Relative Completeness) |= {A} M :m {B} implies ` {A} M :m {B}, provided B is a total correctness assertion (cf. [10] for a definition).

5

Conclusion

We have investigated axiomatic semantics for stateless sequential control constructs. Most programming languages with interesting control constructs also feature state. We believe that adding state to PCF+ or µPCF can be done with the help of content quantification [12]. We hope to report results in this direction soon. Exceptions are a constrained form of jumping that is used to escape a context without a possibility of returning, a feature very useful for error handling. This omission is due to a typing difficulty: exceptions are caught dynamically, which does not sit comfortably with the typing system. We believe that a slight generalisation of the logic presented here can easily account for exceptions. Related Work. The present work adds a new member to a family of logics for MLlike languages [5, 12, 14, 22], and integrates in a strong sense: e.g. all rules and axioms from [12] are, adapting the syntax, also valid for PCF+ and µPCF. Moreover, the

Program Logics for Sequential Higher-Order Control

15

CPS-transforms of the latter two languages into PCF are logically fully abstract. This coherence paves the way for a comprehensive proof-compilation infrastructure for MLlike languages. Rely/Guarantee based reasoning was introduced in [15]. Research on axiomatic accounts of control manipulation started with [7]. Recently, this research tradition was revived by a sequence of works for simple, imperative first-order low-level languages [1–3, 6, 17, 20, 21], see [4] for a more comprehensive discussion. In most cases goto-like jumps is the only non-trivial control construct used. None of this work deals with the advanced control constructs investigated here. Completeness is not considered.

References 1. D. Aspinall, L. Beringer, M. Hofmann, H.-W. Loidl, and A. Momigliano. A program logic for resource verification. In Proc. Theorem Proving in Higher-Order Logics (TPHOL), 2004. 2. F. Bannwart and P. M¨uller. A program logic for bytecode. ENTCS, 141(1):255–273, 2005. 3. N. Benton. A Typed, Compositional Logic for a Stack-Based Abstract Machine. In Proc. APLAS, 2005. 4. M. Berger. Program Logics for Sequential Higher-Order Control (1): Stateless Case. Draft, available at http://doc.ic.ac.uk/∼mberger/jumps, Oct. 2007. 5. M. Berger, K. Honda, and N. Yoshida. A logical analysis of aliasing for higher-order imperative functions. In Proc. ICFP, pages 280–293, 2005. Full version to appear in JFP. 6. L. Beringer and M. Hofmann. A bytecode logic for JML and types. In Proc. APLAS, pages 389–405, 2006. 7. M. Clint and C. A. R. Hoare. Program Proving: Jumps and Functions. Acta Informatica, 1:214–224, 1972. 8. B. F. Duba, R. Harper, and D. MacQueen. Typing First-Class Continuations in ML. In Proc. POPL, pages 163–173, 1991. 9. R. Harper and M. Lillibridge. Operational Interpretations of an Extension of Fω with Control Operators. Journal of Functional Programming, 6(3):393–417, 1996. 10. K. Honda, M. Berger, and N. Yoshida. Descriptive and Relative Completeness of Logics for Higher-Order Functions. In Proc. ICALP, pages 360–371, 2006. 11. K. Honda and N. Yoshida. A uniform type structure for secure information flow. In POPL’02, pages 81–92. ACM Press, 2002. Full version to appear in ACM TOPLAS. 12. K. Honda and N. Yoshida. A compositional logic for polymorphic higher-order functions. In Proc. PPDP’04, pages 191–202. ACM Press, 2004. 13. K. Honda, N. Yoshida, and M. Berger. Control in the π-calculus. In Proc. CW’04. ACM Press, 2004. 14. K. Honda, N. Yoshida, and M. Berger. An observationally complete program logic for imperative higher-order functions. In LICS’05, pages 270–279, 2005. 15. C. B. Jones. Specification and Design of (Parallel) Programs. In IFIP Congress, pages 321–332, 1983. 16. J. Laird. A Semantic Analysis of Control. PhD thesis, Univ. of Edinburgh, 1998. 17. Z. Ni and Z. Shao. Certified Assembly Programming with Embedded Code Pointers. In Proc. POPL, 2006. 18. C.-H. L. Ong and C. A. Stewart. A Curry-Howard foundation for functional computation with control. In Proc. POPL, pages 215–227, 1997. 19. J. G. Riecke and H. Thielecke. Typed exceptions and continuations cannot macro-express each other. In Proc. ICALP, volume 1644 of LNCS, pages 635–644, 1999. 20. A. Saabas and T. Uustalu. A Compositional Natural Semantics and Hoare Logic for LowLevel Languages. In Proc Workshop Structural Operational Semantics (SOS), 2006.

16

Martin Berger

21. G. Tan and A. W. Appel. A Compositional Logic for Control Flow. In Proc. Int. Conf. Verification, Model Checking and Abstract Interpretation (VMCAI), 2006. 22. N. Yoshida, K. Honda, and M. Berger. Logical reasoning for higher-order functions with local state. In Proc. Fossacs, LNCS, pages 361–377, 2007.