CONS Should Not CONS Its Arguments, Or, a Lazy Alloc Is a Smart Alloc Henry G
Total Page:16
File Type:pdf, Size:1020Kb
CONS Should not CONS its Arguments, or, a Lazy Alloc is a Smart Alloc Henry G. Baker Nimble Computer Corporation, 16231 Meadow Ridge Way, Encino, CA 91436, (818) 501-4956 (818) 986-1360 (FAX) November, 1988; revised April and December, 1990, November, 1991. This work was supported in part by the U.S. Department of Energy Contract No. DE-AC03-88ER80663 Abstract semantics, and could profitably utilize stack allocation. One would Lazy allocation is a model for allocating objects on the execution therefore like to provide stack allocation for these objects, while stack of a high-level language which does not create dangling reserving the more expensive heap allocation for those objects that references. Our model provides safe transportation into the heap for require it. In other words, one would like an implementation which objects that may survive the deallocation of the surrounding stack retains the efficiency of stack allocation for most objects, and frame. Space for objects that do not survive the deallocation of the therefore does not saddle every program with the cost for the surrounding stack frame is reclaimed without additional effort when increased flexibility--whether the program uses that flexibility or the stack is popped. Lazy allocation thus performs a first-level not. garbage collection, and if the language supports garbage collection The problem in providing such an implementation is in determining of the heap, then our model can reduce the amortized cost of which objects obey LIFO allocation semantics and which do not. allocation in such a heap by filtering out the short-lived objects that Much research has been done on determining these objects at can be more efficiently managed in LIFO order. A run-time compile time using various static methods which are specific to the mechanism called result expectation further filters out unneeded particular type of object whose allocation is being optimized. results from functions called only for their effects. In a shared- Unfortunately, these techniques are limited, complex and expensive. memory multi-processor environment, this filtering reduces We present a technique which acts more like a cache or a virtual contention for the allocation and management of global memory. memory, in the sense that no attempt is made to predict usage at Our model performs simple local operations, and is therefore suitable compile time, but the usage is determined at run time. In other for an interpreter or a hardware implementation. Its overheads for words, the system learns about the usage of objects "on-the-fly". functional data are associated only with assignments, making lazy The major contribution of this paper is the recognition that a wide allocation attractive for mostly functional programming styles. variety of stack-allocation optimizations are all instances of the Many existing stack allocation optimizations can be seen as same underlying mechanism--lazy allocation. Using this insight, instances of this generic model, in which some portion of these we can simplify hardware architecture and language implementations local operations have been optimized away through static analysis by factoring the problem into two abstraction layerswlanguage techniques. implementation using generic storage allocation, and the Important applications of our model include the efficient allocation implementation of generic storage allocation using lazy allocation. of temporary data structures that are passed as arguments to Safety is also enhanced by the elimination of "dangling references" anonymous procedures which may or may not use these data due to objects escaping a stack-allocated scope. While lazy structures in a stack-like fashion. The most important of these allocation already provides for stack-allocation of arguments and temporaries, a programmer can extract even better performance by objects are functional arguments (funargs), which require some run- time allocation to preserve the local environment. Since a funarg is utilizing "continuation-passing style" to stack-allocate function sometimes returned as a first-class value, its lifetime can survive the results. stack frame in which it was created. Arguments which are evaluated 2. Stack Allocation is an Implementation Issue, not in a lazy fashion (Scheme delays or "suspensions") are similarly a Language Issue handled. Variable-length argument "lists" themselves can be The stack allocation of variables and contexts in higher-level allocated in this fashion, allowing these objects to become "first- compiled languages such as C, Pascal and Ada has been a fact of life class". Finally, lazy allocation correctly handles the allocation of a for so many years since its introduction in Algol-60 that most Scheme control stack, allowing Scheme continuations to become programmers today assume that stack allocation is a language, rather first-class values. than an implementation, issue. Stack allocation cannot be a 1. Introduction. language issue, however, since the most direct mapping of the nested Stack allocation of objects in higher level programming languages lexical variable scopes in these languages is a tree, not a stack. Rather, stack allocation was a conscious decision on the part of is desired because it is elegant, efficient, and can handle the great majority of short-lived object allocations. Traditional higher-level these language designers to provide the implementation efficiency of a stack as a storage allocation mechanism even though this choice languages such as Algol, Pascal and Aria have preferred to perform all automatic storage management by using a stack, while non-stack substantially compromised language elegance. We have also since learned that stack allocation of variables and contexts severely allocation remains the responsibility of the programmer. However, limits the expressiveness of a programming language. Indeed, many the limitations of stack allocation are semantically confining, of the advances in programming languages after Algol-60 can be because a strict last-in, first-out, (LIFO) allocation/deallocation ordering does not allow for important classes of program behavior characterized as attempts to ameliorate the restrictions of stack allocation. For example, most of the functionality of Simula-67 such as that of returning a functional argument as a result. Therefore, the programmer is forced to use complex and error-prone techniques could have been achieved in Algol-60 through the dropping of the stack allocation requirement along with the syntactic restrictions on to simulate this behavior himself, even though the abstract programming language may be capable of expressing the behavior procedure arguments and returned values. more elegantly and directly using, for example, functional arguments The stack allocation of Algol-60 provided a major advance over as results. Modem higher level languages such as Lisp, Smalltalk, Fortran's static allocation. Functions could be arbitrarily nested, and Mesa, Modula-3, ML, and Eiffel, seek to escape these LIFO recursion became possible. So long as the basic values being restrictions to gain in expressive power while retaining elegance and manipulated were numbers and individual characters, stack allocation simplicity in the language. Insofar as they succeed, they can greatly proved remarkably expressive. With the advent of dynamic strings improve engineering productivity and software quality. of characters and larger dynamic objects such as arrays, stack allocation started to break down. PL/I used pointers and explicit A cost must be paid for this flexibility through the increased use of allocation/deallocation to avoid the limits of stack allocation. heap allocation for objects in the language. Yet the vast majority of Substantial arguments in the 1970's raged about the allocation of objects obey a straight-forward last-in, first-out (LIFO) allocation 24 ACM SIGPLAN Notices, Volume 27, No. 3, March 1992 function-calling and variable-allocation contexts--"retention" address by following a forwarding pointer.1 The mechanisms for (non-stack allocation) versus "deletion" (normal stack allocation) dealing with objects which can be unexpectedly moved are well [Berry71] [Fischer72]. Non-stack-allocation has become steadily known in the context of "on-the-fly", or "incremental" compacting more important as the sophistication and complexity of programs garbage collectors [Baker78][Lieberman83]. have increased. For example, in modern "object-oriented" Once we have installed the machinery necessary to deal with objects programming style, most objects are heap-allocated rather than which can suddenly move, we can contemplate the possibility of stack-allocated. allocating everything initially on the stack. If we implement an Unfortunately, heap-allocation is substantially less efficient than "escape alarm system" to detect escaping references, we can use these stack-allocation. As a result, programmers constantly seek to utilize alarms to trap to an eviction routine which will transport evicted stack allocation whenever possible. This change requires much objects into the heap. We call the policy of stack allocation work, because the source code changes required to change from one followed by eviction traps "lazy allocation", because the system is sort of allocation to another are substantial, even when the logic of too lazy to perform the work involved in heap allocation until this the program has not changed. Most importantly, the programmer is work is forced by the object's usage.