List of Abstracts

Optimizing Functional Programs Using Flow Inference

Nonstrict higher order functional programming languages are notorious for their low run time efficiency. Optimizations based on flow analysis, which determines for each variable x in a program which expressions could have originated the value of x, can improve the situation by removing redundant eval and thunk operations, avoiding thunk updates, and allowing the use of unboxed representations of some data. We formulate flow analysis as an inference problem in a type system built using type inclusion constraints and an algorithm for solving these constraints is also given.

DVI, Postscript, compressed Postscript

Flow Inference, Code Generation, and Garbage Collection for Lazy Functional Languages

Lazy functional languages such as LML, Haskell, and Concurrent Clean typically feature first class functions, polymorphic type systems, and automatic garbage collection. These powerful mechanisms translate into short, readable, and reusable programs at a high level of abstraction. They do however lead to quite a lot of run-time bookkeeping and make the dynamic flow of control rather unpredictable. This overhead increases execution time by as much as a factor of twenty in some cases.

This thesis describes some techniques that can be used to reduce this overhead. Specifically, we eliminate thunk and eval operations, use unboxed data representations and try to avoid updating thunks. These optimizations depend on information gained by a program-wide dataflow analysis which is based on a polymorphic subtype inference system.

The techniques have been implemented in a compiler for a simple lazy functional language called Plain, and this implementation is described in some detail. The issue of memory management is also discussed and the compilers garbage collector is presented.

Finally, some experimental results are given.

Postscript, compressed Postscript

Polyvariance, Polymorphism, and Flow Analysis

Flow analysis is a potentially very useful analysis for higher order functional languages, but its practical application has been slow in coming, partially hindered by shortcomings of the current analysis techniques. Among these are the limited precision, long analysis times, incompatibility with separate compilation, inapplicability to untyped languages and sensitivity to program structure associated with various earlier formulations.

We address these shortcomings with an approach based on a novel type system integrating recursive types, subtyping with liberal union types, and system F style polymorphism, in a constraint based formulation. The type system is "soft" in the sense that it does not reject programs. It is also not decidable, but there is a broad spectrum of sound and terminating, though incomplete, algorithms whose precision can be characterized by restricting the substitutions used for instantiation of type schemes.

We give an inference algorithm which is based on constraint solving and which has the important property that the constraints generated from a function definition can be simplified before being put in a type scheme. Thus we believe that combinatorial explosion can be avoided in practice. Type schemes are also a suitable representation for communicationg the analysis result over module boundaries, making separate compilation feasible.

Postscript, compressed Postscript

Analysing, Transforming and Compiling Lazy Functional Programs

Lazy functional languages such as LML, Haskell and Concurrent Clean typically feature first class functions, polymorphic type systems, and automatic garbage collection. These powerful mechanisms translate into short, readable, and reusable programs at a high level of abstraction. They do however lead to quite a lot of run-time bookkeeping and make the dynamic flow of control rather unpredictable. This overhead increases execution time by as much as a factor of twenty in some cases.

This thesis describes some techniques that can be used to reduce this overhead. Specifically, we eliminate thunk and eval operations, use unboxed data representations and try to avoid updating thunks. These optimizations depend on information gained by a program-wide dataflow analysis which is based on a polymorphic subtype inference system.

The techniques have been implemented in a compiler for a simple lazy functional language called Plain, and this implementation is described in some detail. Some experimental results are also given.

A different method for flow analysis is also presented. It can be seen as a soft type system based on higher order polymorphism, and is closely related to the kind of polyvariance found in abstract interpretation based approaches. Starting from this technique for flow analysis, a combined analysis and transformation system is defined. It performs the transformations mentioned above, but without ever constructing the flow information for the entire program. These transformations also incorporate cloning, to improve the results on large programs.

Postscript, compressed Postscript

Representation Analysis for Coercion Placement

Representation analysis finds parts of a program where specialized unboxed data representations can be used instead of the less efficient uniform boxed representation. Boxing and unboxing coercions are used to convert between boxed and unboxed representations as needed.

This paper presents a global approach to representation analysis based on program-wide data and control flow information. Coercions can be placed around any variable occurrence, not only where values are produced and consumed.

The analysis first constructs a graph representing all legal coercion placements, then selects one of them using a coercion placement strategy. Several such strategies are discussed and preliminary measurements are presented.

When combined with function cloning, the analysis is powerful enough to eliminate almost all coercions from several nonstrict programs, including a simple polymorphic type checker.

The analysis allows the use of unboxed data in data structures and as arguments in indirect function calls, yet it uses only simple coercions (box or unbox integers and other scalar data). No coercions which traverse recursive data structures are needed.

Our method is applicable to a wide variety of functional languages; we formulate it over an untyped call-by-value language called Fleet, which contains explicit graph reduction constructs. Fleet can be used as an intermediate language for strict as well as nonstrict and typed as well as untyped languages.

DVI, Postscript, compressed Postscript

The Costs and Benefits of Cloning in a Lazy Functional Language

Cloning is a transformation where several copies are made of some functions in a program in order to improve the effectiveness of optimizations of these functions. Cloning may thus lead to a reduction in the number of executed instructions, but it does in general also lead to an increase in the size of the executable program. Depending on the characteristics of the memory hierarchy of the target machine, this may increase the instruction cache miss rate enough that the cloned program executes slower than the original.

In this paper we try to quantify both the benefits and costs of cloning in the context of an optimizing compiler for a simple lazy functional language. We will also evaluate a few techniques for reducing the number of instruction cache misses.

DVI, Postscript, compressed Postscript

Cheap Eagerness: Speculative Evaluation in a Lazy Functional Language

Cheap eagerness is an optimization where cheap and safe expressions are evaluated before it is known that their values are needed. Many compilers for lazy functional languages implement this optimization, but they are limited by a lack of information about the global flow of control and about which variables are already evaluated. Without this information, even a variable reference is a potentially unsafe expression!

In this paper we show that significant speedups are achievable by cheap eagerness. Our cheapness analysis uses the results of a program-wide data and control flow analysis to find out which variables may be unevaluated and which variables may be bound to functions which are dangerous to call.

DVI, compressed Postscript

Dynamic Cheap Eagerness

Dynamic cheap eagerness extends cheap eagerness by allowing the decision of whether to build a thunk or speculatively evaluate its body to be deferred until run time. We have implemented this optimisation in a compiler for a simple functional language and measured its effect on a few benchmarks. It turns out that a large part of the overhead of graph reduction can be eliminated, but that run-times and instruction counts are not affected in the same degree.

DVI, compressed Postscript

A Static Semantics for Haskell

This paper gives a static semantics for Haskell 98, a non-strict purely functional programming language. The semantics formally specifies nearly all the details of the Haskell 98 type system, including the resolution of overloading, kind inference (including defaulting) and polymorphic recursion, the only major omission being a proper treatment of ambiguous overloading and its resolution.

Overloading is translated into explicit dictionary passing, as in all current implementations of Haskell. The target language of this translation is a variant of the Girard-Reynolds polymorphic lambda calculus featuring higher order polymorphism and explicit type abstraction and application in the term language. Translated programs can thus still be type checked, although the implicit version of this system is impredicative.

A surprising result of this formalization effort is that the monomorphism restriction, when rendered in a system of inference rules, compromises the principal type property.

DVI, compressed Postscript

Haskell and Principal Types

This paper points out two problems which prevent Haskell from having principal types. For each problem, we discuss a program which exhibits it. The first problem has to do with type signatures and class constraints containing both generic and nongeneric type variables. The second problem is caused by the monomorphism restriction. In both cases there is an interaction between generalization and class constraints where substituting a nonvariable type for a constrained type variable makes the constraint tautological and opens up for more aggressive generalization.

We also discuss how these problems can be solved by introducing quantified class constraints and strengthening the monomorphism restriction from prohibiting only the generalization of constrained type variables to prohibiting any generalization at all. We also give an inference algorithm producing principal types for the new system.

Postscript, PDF