DeepAI
Log In Sign Up

Clocked Definitions in HOL

03/09/2018
by   Ramana Kumar, et al.
0

Many potentially non-terminating functions cannot be directly defined in a logic of total functions, such as HOL. A well-known solution to this is to define non-terminating functions using a clock that forces termination at a certain depth of evaluation. Such clocked definitions are often frowned upon and avoided, since the clock is perceived as extra clutter. In this short paper, we explain that there are different ways to add a clock, some less intrusive than others. Our contribution is a technique by which termination proofs are kept simple even when minimising the use of the clock mechanism. Our examples are definitions of semantic interpreters for programming languages, so called functional big-step semantics.

READ FULL TEXT VIEW PDF

page 1

page 2

page 3

page 4

10/10/2018

DefunT: A Tool for Automating Termination Proofs by Using the Community Books (Extended Abstract)

We present a tool that automates termination proofs for recursive defini...
09/22/2020

Research Summary on Implementing Functional Patterns by Synthesizing Inverse Functions

In this research summary we present our recent work on implementing func...
10/10/2022

Classifying topoi in synthetic guarded domain theory

Several different topoi have played an important role in the development...
04/24/2018

Guarded Computational Type Theory

Nakano's later modality can be used to specify and define recursive func...
05/03/2017

A Versatile, Sound Tool for Simplifying Definitions

We present a tool, simplify-defun, that transforms the definition of a g...
06/13/2018

TTT2 with Termination Templates for Teaching

On the one hand, checking specific termination proofs by hand, say using...
02/06/2018

Büchi-Kamp Theorems for 1-clock ATA

This paper investigates Kamp-like and Büchi-like theorems for 1-clock Al...

1 Introduction

Some functions are naturally non-terminating, which makes them non-trivial to model in a logic of total functions. A prototypical example is a definitional interpreter [4], which defines the semantics of a programming language and diverges when the program being interpreted should diverge. A nice way to make a total model of such a function is to add a clock: an extra parameter whose ticks act as fuel for recursive calls. The clock makes the function terminating, since any application will eventually run out of fuel, without sacrificing reasoning about divergence, since divergence is equivalent to timing out for every initial clock. The clock idea is well-known to users of ACL2 [1], and we have recently advocated for its use in higher-order logic (HOL) for definitions of programming-language semantics [3].

There is trade off to be made whenever a clocked function is defined: how intricate should the clock mechanism be? A more nuanced clock might lead to a better semantics but require a more subtle termination proof. There are two dimensions to consider:

  • On which recursive calls is the clock decremented?

  • Does the clock measure the depth or the length of execution? Equivalently: is the clock environment-like (not returned) or state-like (threaded through)?

At the simple end of the first dimension is a clock that consumes fuel on every recursive call. While conceptually neat, and good for proving termination, this kind of clock mechanism can easily become a burden in later proofs. By contrast, if the clock is decremented only on problematic recursive calls, there is less clock-related overhead in proofs.

Along the second dimension, an environment-like clock supports a straightforward termination proof, whereas a state-like clock can make termination rather tricky. The difference is akin to reasoning about accumulator-passing versus directly recursive functions. However, unlike accumulator-passing style, measuring length versus depth of execution with the clock is a real semantic difference, and certain applications may call for one or the other.

In this paper, we illustrate the four options entailed by the two dimensions above and explain how clocked functions in each style can be defined. Our technical contribution is a simple technique by which the tricky termination proofs arising from a state-like clock are made simple.

One can read this paper as a tutorial on how to neatly define the clocked functions used in our previous paper on functional big-step semantics [3]. The technique in Section 3.2 has been used to clean up the definitions of the semantics for the CakeML compiler’s 13 intermediate languages (https://cakeml.org).

Running example.

We will use Nipkow and Klein’s IMP language from Concrete Semantics [2] as a running example. The IMP language is a simple While-language with the following abstract syntax.

Arithmetic and Boolean expressions are given semantics (aval and bval respectively) as would be expected, as functions.

In what follows we define four clocked functional semantics for this language. Section 2 gives definitions with environment-like clocks, and shows the pros and cons, within this style, of using the clock on every recursive call. Section 3 describes, and contrasts, state-like clocks, and illustrates the tricky termination problems they produce and how to solve them.

2 Environment-like clocks

Our first evaluation function for commands has a simple clock mechanism: we decrement the clock on every recursive call, and do not return it. The clock, , is a natural number, and the result of evaluation is either None representing timeout or with the final state.

This definition may look neat at first, but observe that most clauses require a non-zero input clock (), which means that case analysis on the clock is required in all proofs. Nevertheless, the termination proof is trivial, since the clock itself is a well-founded measure. (The termination proof is automatic in the HOL theorem prover.)

The function above can easily be modified to not check the clock for non-recursive cases, e.g. Skip, but the result is just a less well presented function.

2.1 Problem with decrement-everywhere style

The problem with functions that decrement the clock on every recursive call is that nearly every proof needs to assume a lower limit on the clock or use induction. As a simple example, consider proving that is the same as . The proof of this simple property is made very cumbersome by the eager use of the clock in ev.

To prove a theorem relating the evaluation of the two commands, we require some very specific assumptions about the clock:

These assumptions are a nuisance to deal with in any proof where we want to use the theorem above.

2.2 Minimal use of the clock

It is preferable to decrement the clock only on problematic recursive calls, rather than on every call as above. In our example, the only problematic call is the recursion in the While case. In all other cases, the expression that is being evaluated shrinks. Therefore, we can define the clocked function as follows with a clock-check-and-decrement only in the While case.

With such a definition of the semantics, it is easy to prove that always evaluates the same as . The equality can be stated without assumptions and used directly as a rewrite rule.

Termination proof.

The equations defining evmin terminate because the lexicographic combination of measuring the clock and the size of the evaluated expression is well-founded. For each recursive call, the clock either stays the same or shrinks; if it stays the same, then the size of the expression shrinks.

3 State-like clocks

The previous section defined functions where the clock limits the depth of evaluation. In some circumstances, e.g. when defining interpreters, it makes sense to limit the length of evaluation instead. This length is related to the length of an equivalent trace in a small-step semantics. The difference can be seen most clearly in constructs, like , where order might matter: we can either give clock ticks to each sub-expression independently, or we can use the same ticks for both and, say, only evaluate after ’s ticks have been subtracted.

In the following definition, the clock is passed around as state, limiting length rather than depth. Each Some-result contains a store-and-clock pair .

3.1 Challenging termination proof

Proving termination for functions that treat the clock as state is not as straightforward as previously. The reason for termination is the same as for evmin above, i.e. the clock decreases when the size of the expression does not. However, the termination proof is more difficult because the clock comes from recursive calls rather than from input arguments.

In the IMP language, this problem shows up in the termination goal for : here one needs to show that evaluation of in the state and clock produced by is smaller than the original input, i.e.  and . This goal is problematic because we have yet to define cval. Though technically possible with modern definition packages, it is cumbersome to prove lemmas about cval before its termination proof is complete.

A common trick to avoid such difficult termination proofs is to define a function with redundant safety checks that make the termination proof simple. Once the function is defined, we can manually prove definition-like equations without the added safety checks. The safety checks also need to be removed from the induction theorems produced for these definitions.

The obvious way to instrument a definition with checks that make the termination proof simple is to add a redundant safety check on the arguments to any problematic recursive call. For example, we could rephrase the problematic Seq case as follows with a redundant check of . We know that this check should always be false, but it is difficult to establish that property in the middle of the termination proof.

Once the function, in this case cval, is defined, one can manually prove the desired defining equation for the Seq case of cval:

Although, this technique of adding redundant safety checks to the incoming clock argument works, our experience with the CakeML compiler suggests that the removal of redundant safety checks on inputs tends to be ad hoc and tedious.

3.2 Simple definition technique: fix-clock wrapper

In the course of defining many clocked functions for CakeML, we realised how the safety checks can be expressed in a way that makes them very easy to remove.

The trick is to perform the safety checks on the return from recursive calls (i.e. the production of potentially bad values) instead of at the sites of consumption of potentially bad values. For the running example, we define a new function, fixclock, and wrap it around the producer of potentially bad values. The fixclock wrapper adjusts the clock back to its original value if the clock somehow increased during the recursive call:

The formulation above makes the safety checks (fixclock) very easy to remove. Once the cval function is defined, we prove that the clock never increases

and use this property to prove the following rewrite rule, which we apply to both the defining theorem for cval and its associated induction theorem.

3.3 Decrement everywhere and state-like clock

The final combination of the two options from the introduction is to pick decrement-everywhere and state-like clock. We omit such a definition since it is an obvious rephrasing of the decrement-everywhere function ev from Section 2.

Is this combination useful? We envision that such formulations can be convenient stepping stones when proving equivalence between functional big-step semantics and small-step semantics, since a length-limiting clock decremented on each call can be made to match the length of the small-step trace. However, we did not use this combination when proving such an equivalence [3].

4 Summary and related work

This short paper presents different styles of clocked definitions, and provides a simple definition technique for functions that treat the clock as a state component which gets threaded through evaluation.

Clocked functions are used in all major theorem provers, but are often seen as unwanted and the clocks are considered a burden. This paper’s purpose is to continue our recent work on programming language semantics [3] and to show that it is easy to define HOL functions with the clock as a state component.

ACL2 is a prover where clocked functions are encouraged and well supported. The ACL2 code base is full of examples of clocked functions, e.g. Centaur Inc’s industrial Verilog preprocessor111https://github.com/acl2/acl2/tree/master/books/centaur/vl/loader/preprocessor, retrieved 2016-03-07. ACL2 even supports tricks that allow execution of clocked functions as if they didn’t have a clock222http://www.cs.utexas.edu/users/moore/acl2/vstte-2012/acl2-dkms/problem5/breadth-first.lisp, retrieved 2016-03-07.

Acknowledgements.

We thank Jared Davis for a long list of pointers on how clocked functions are used in ACL2. NICTA is funded by the Australian Government through the Department of Communications and the Australian Research Council through the ICT Centre of Excellence Program.

References

  • [1] Kaufmann, M., Moore, J.S.: An ACL2 tutorial. In: Mohamed, O.A., Muñoz, C.A., Tahar, S. (eds.) Theorem Proving in Higher Order Logics (TPHOLs). LNCS, Springer (2008)
  • [2] Nipkow, T., Klein, G.: Concrete Semantics - With Isabelle/HOL. Springer (2014)
  • [3] Owens, S., Myreen, M.O., Kumar, R., Tan, Y.K.: Functional big-step semantics. In: Thiemann, P. (ed.) European Symposium on Programming (ESOP). LNCS, Springer (2016)
  • [4] Reynolds, J.C.: Definitional interpreters for higher-order programming languages. Higher-Order and Symbolic Computation 11(4), 363–397 (1998), http://dx.doi.org/10.1023/A:1010027404223