|
|
1.1 ! root 1: .EQ ! 2: delim $$ ! 3: define |-> ' down 3 size -3 | back 25 -> ' ! 4: .EN ! 5: .nr PS 11 ! 6: .nr VS 13 ! 7: .fp 5 CW ! 8: .ND August 5, 1987 ! 9: .TL ! 10: A Standard ML Compiler ! 11: .AU ! 12: Andrew W. Appel \fR*\fP ! 13: .FS ! 14: * Supported by NSF Grant DCR-8603453 and by ! 15: a Digital Equipment Corporation Faculty Incentive Grant. ! 16: .FE ! 17: .AI ! 18: Dept. of Computer Science ! 19: Princeton University ! 20: Princeton, NJ 08544 ! 21: .AU ! 22: David B. MacQueen \fR\(dg\fP ! 23: .FS ! 24: \(dg Part of this author's work was done while an SERC Senior Visiting ! 25: Fellow at the University of Edinburgh. ! 26: .FE ! 27: .AI ! 28: AT&T Bell Laboratories ! 29: Murray Hill, NJ 07974 ! 30: .AB ! 31: .LP ! 32: Standard ML is a major revision of earlier dialects of the functional ! 33: language ML. We describe the first compiler written for Standard ML ! 34: in Standard ML. The compiler incorporates a number of novel features ! 35: and techniques, and is probably the largest system written to date in ! 36: Standard ML. ! 37: .LP ! 38: Great attention was paid to modularity in the construction of the ! 39: compiler, leading to a successful large-scale test of the modular ! 40: capabilities of Standard ML. The front end ! 41: is useful for purposes other than compilation, and the back end is ! 42: easily retargetable (we have code generators for the VAX and MC68020). ! 43: The module facilities of Standard ML were taken into account early in ! 44: the design of the compiler, and they particularly influenced the ! 45: environment management component of the front end. For example, the ! 46: symbol table structure is designed for fast access to opened ! 47: structures. ! 48: .LP ! 49: The front end of the compiler is a single phase that integrates ! 50: parsing, environment management, and type checking. The middle end ! 51: uses a sophisticated decision tree scheme to produce efficient pattern ! 52: matching code for functions and case expressions. ! 53: The abstract syntax produced by the front end is translated into a simple ! 54: lambda-calculus-based intermediate representation that lends itself to ! 55: easy case analysis and optimization in the code generator. Special ! 56: care was taken in designing the runtime data structures for fast ! 57: allocation and garbage collection. ! 58: .LP ! 59: We describe the overall organization of the compiler and present some ! 60: of the data representations and algorithms used in its various phases. ! 61: We conclude with some lessons learned about the ML language itself and ! 62: about compilers for modern functional languages. ! 63: .AE ! 64: .pn 1 ! 65: .nr VS 17 ! 66: .np ! 67: .NH ! 68: Introduction ! 69: .LP ! 70: The ML language is a typed functional language roughly based on ! 71: Landin's ISWIM [1]. ! 72: It was originally designed in the ! 73: mid-1970s as the metalanguage of Edinburgh LCF, a machine-assisted ! 74: reasoning system, and its features were in part motivated by its ! 75: intended use to express proof tactics. However, these features made ! 76: it an attractive vehicle for general purpose symbolic programming, and ! 77: it wasn't long before free-standing implementations appeared, such as ! 78: Cardelli's compiler[2], ! 79: which was written in Pascal. In 1983 a ! 80: group of interested parties began work on an extensive revision of the ! 81: language design that lead to Standard ML [3,\|4,\|5,\|6]. ! 82: Standard ML ! 83: extended the earlier versions in certain ways and incorporated ideas ! 84: from Hope[7], ! 85: another language developed in Edinburgh in the late 1970s. ! 86: It also included a module facility that significantly extends the ! 87: basic polymorphic type system of earlier ML versions and supports ! 88: large-scale program development. ! 89: ! 90: Several implementations of Standard ML have been under development in ! 91: recent years. ! 92: Luca Cardelli's compiler was modified to be partially ! 93: compatible with Standard ML. Meanwhile, Cardelli's original compiler ! 94: had been reimplemented in its own variant of ML by Kevin Mitchell and ! 95: Alan Mycroft at Edinburgh, and this compiler was also modified with ! 96: the help of Robert Harper to make it conform fairly closely to the ! 97: Standard ML definition. A new compiler was developed in Cambridge by ! 98: David Matthews, using his Poly language [8,\|9] ! 99: as the implementation ! 100: language, and sharing the Poly back end. ! 101: At INRIA, a group headed by Gerard Huet and Guy Cousineau ! 102: have been implementing an ML variant called CAML[10] ! 103: that is intermediate ! 104: between the LCF version and Standard ML. ! 105: ! 106: Yet there was still justification for another Standard ML ! 107: compiler. One of the main motivations was to build a compiler that ! 108: would itself be implemented in Standard ML. Such a compiler would ! 109: have several advantages. It would be a good basis for building ! 110: meta-level tools (pretty-printers, program analyzers, debuggers, etc.) ! 111: that could share data types and code with the compiler itself and ! 112: might be used in ML application programs or in an ML programming environment. ! 113: It would also serve as a large-scale test of the Standard ML design, ! 114: particularly the module facility, which was the most novel and untested ! 115: part of the language. Finally, a new compiler provides an ! 116: opportunity to try out new implementation strategies (for code ! 117: generation, optimization, type checking, and modules, for instance). ! 118: A simple lambda-calculus intermediate representation ! 119: makes the compiler much cleaner, and could also ! 120: provide a base for ! 121: research in evaluation algorithms (call-by-value, normal-order, lazy, ! 122: combinator-based, parallel, and others) for lambda-calculus. ! 123: ! 124: As a call-by-value, higher-order functional language, many of the ! 125: conventional compilation techniques for functional languages apply to ! 126: Standard ML. The distinguishing features of ML are its polymorphic ! 127: type system, the notion of concrete data types for expressing disjoint ! 128: unions and recursion, the use of pattern matching based on the ! 129: constructor-functions associated with data types, the use of typed, ! 130: value-carrying exceptions, the fact that all updateable variables are ! 131: introduced explicitly by application of the ! 132: .B ref ! 133: constructor, and ! 134: modular programming based on signatures (interface specifications), ! 135: structures (implementations of interfaces) and functors (functions ! 136: from structures to structures). These features, singly or in combination, ! 137: produce novel challenges in compiler design. They also, it turns ! 138: out, can be effectively exploited in the writing of a compiler. ! 139: ! 140: The compiler was written using the Edinburgh Standard ML compiler and ! 141: then bootstrapped to compile itself. However, the compiler shares ! 142: no code with previous compilers; and the runtime system, ! 143: written in C with the help of Peter Weinberger, is also completely ! 144: new. ! 145: .NH ! 146: Architecture ! 147: .LP ! 148: The ML compiler described here consists of three phases. The front ! 149: end consists of a recursive descent parser into which environment ! 150: management (determining scopes, associating binding and applied ! 151: occurrences of variables) and type checking are integrated. The ! 152: output of the front end is a fully typed abstract syntax tree. ! 153: The front end makes no decisions about runtime ! 154: locations of variables. It simply assigns them unique identifiers, ! 155: and the back end makes representation decisions. ! 156: ! 157: The middle end performs a simple translation of this abstract tree ! 158: into an untyped lambda calculus representation, ! 159: and produces an optimized representation of pattern-matching as one-dimensional ! 160: switch expressions. ! 161: ! 162: The back end transforms the lambda ! 163: form into target machine code using a largely machine-independent code ! 164: generator based on an ``abstract machine'' interface. The expressions ! 165: given to the back end never contain free variables; this simplifies ! 166: the interface between the code generator and the runtime system. ! 167: .NH ! 168: The Front End ! 169: .LP ! 170: The front end of the compiler consists of a conventional lexical ! 171: analyzer, abstract syntax datatypes, environment management machinery, ! 172: a type checker, and a recursive descent parser that drives the lexical ! 173: analysis, environment management and type checking. ! 174: .NH 2 ! 175: Abstract syntax ! 176: .LP ! 177: The abstract syntax is implemented in two layers. There is a ! 178: collection of data types defining the ``bare'' abstract syntax ! 179: that forms a minimal kernel of the language. For instance, the ! 180: \fIexpression\fP data type is defined as follows: ! 181: .DS ! 182: .ft CW ! 183: .vs 13 ! 184: datatype exp = VARexp of var ref ! 185: | CONexp of datacon ! 186: | INTexp of int ! 187: | REALexp of real ! 188: | STRINGexp of string ! 189: | RECORDexp of (numberedLabel * exp) list ! 190: | SEQexp of exp list ! 191: | APPexp of exp * exp ! 192: | CONSTRAINTexp of exp * ty ! 193: | HANDLEexp of exp * handler ! 194: | RAISEXexp of exp ! 195: | LETexp of dec * exp ! 196: | CASEexp of exp * rule list ! 197: | FNexp of rule list ! 198: .vs 17 ! 199: .ft R ! 200: .DE ! 201: The full abstract syntax consists of these bare syntax types augmented ! 202: with a small number of derived forms, such as clausal function definitions. ! 203: These derived forms are implemented as functions that generate the ! 204: appropriate expansion into bare syntax forms. ! 205: .NH 2 ! 206: Environment management ! 207: .LP ! 208: We separate the idea of symbol-table manipulation ! 209: from the details of the kinds of bindings found in ML. ! 210: There is a generic environment mechanism that performs binding, ! 211: lookup, and scope management functions; it ! 212: is implemented as a functor that takes the ! 213: binding type as a parameter. ! 214: The binding type is a disjoint union of the specific categories of identifiers ! 215: (variables, constructors, type constructors, etc.): ! 216: .DS ! 217: .ft CW ! 218: .vs 13 ! 219: datatype binding ! 220: = VARbind of var \fI(* variables *)\fP ! 221: | CONbind of datacon \fI(* data constructors *)\fP ! 222: | EXNbind of datacon \fI(* exceptions *)\fP ! 223: | TYCbind of tycon ref \fI(* type constructors (patchable) *)\fP ! 224: | TYVbind of tyvar \fI(* type variables *)\fP ! 225: | SIGbind of signatureVar \fI(* signatures *)\fP ! 226: | STRbind of structureVar \fI(* structures *)\fP ! 227: | FCTbind of functorVar \fI(* functors *)\fP ! 228: | FIXbind of fixity \fI(* infix attributes of variables *)\fP ! 229: .vs 17 ! 230: .ft R ! 231: .DE ! 232: The generic environment uses hash tables, which we call binding tables ! 233: or simply tables, to map symbols to bindings. The environment ! 234: consists roughly of a stack of tables to hold the actual bindings and ! 235: a stack of ``remarks'' that records information about bindings and ! 236: scopes. Only the stack of tables is used when looking up symbols, ! 237: while the remarks are used when entering and leaving scopes and when ! 238: binding symbols. There are two types of tables in the stack of ! 239: tables: those representing the bindings in a previously defined ! 240: structure that has been ``opened'', and those representing bindings in ! 241: currently open scopes (i.e. top-level, let-bound, and function ! 242: parameter bindings). ! 243: ! 244: As indicated above, the binding type is a union of all the ! 245: various kinds of bindings a symbol may have. The tables therefore ! 246: hold bindings of all kinds, and the specialized access functions for ! 247: variables, constructors, etc. search only for bindings of the desired ! 248: kind. Thus it is possible for a symbol to simultaneously have several ! 249: bindings of different kinds, e.g. as a variable, a type constructor, ! 250: and an exception. However, variables and constructors share the same ! 251: name space, so it is not possible for a symbol to be simultaneously ! 252: bound as a variable and a constructor, despite the fact that these are ! 253: two disjoint kinds of bindings. The information associated with a ! 254: symbol, such as the type of a variable or the signature of a ! 255: structure, is contained in the projected value of its binding (e.g. the ! 256: var or structureVar). ! 257: .NH 2 ! 258: Parsing ! 259: .LP ! 260: The lexical analyzer is conventional, turning the input ! 261: character stream into a stream of tokens, recognizing keywords, ! 262: identifiers, and string and numeric literals. ! 263: The parser reads tokens from the lexical analyzer and produces an ! 264: abstract syntax tree containing type and binding information. ! 265: Parsing is done by recursive descent, with a precedence parser ! 266: for infix operators in expressions. Unlike previous ML compilers, but ! 267: like most compilers for Algol-like languages, management of the ! 268: compile-time environment is done at the same time as parsing: identifiers ! 269: are entered into the environment as they are defined, and looked up ! 270: in the environment as they are used. ! 271: ! 272: Two particular problems crop up in parsing ML: identifiers may be declared ! 273: infix, or have their precedence changed, in lexically-scoped declarations; ! 274: and constructors cannot be syntactically distinguished from identifiers. ! 275: To handle the infix-operator problem, the lexical analyzer makes no distinction ! 276: between ordinary identifiers and identifiers that are declared as infix operators. ! 277: Instead, the operator-precedence expression-parser looks up each identifier ! 278: in the symbol table to determine if it has a fixity binding, and if it does, ! 279: what its precedence is. Recognizing constructors can be done similarly ! 280: by looking up identifiers ! 281: in the environment. ! 282: This scheme works nicely in the presence of ! 283: modules and ! 284: .B open ! 285: declarations: if a module $A$ is opened in a scope (even a local scope), ! 286: and an infix identifier $i$ is used from $A$, the precedence parser will ! 287: automatically find the precedence declaration of $i$ in the environment. ! 288: ! 289: Forward reference to identifiers is legal in mutually recursive definitions. ! 290: For example, in the function declaration: ! 291: .DS ! 292: .ft CW ! 293: .vs 13 ! 294: fun f(a,b) = if a=0 then b else g(a-1) ! 295: and g(x) = f(x,x) ! 296: .vs 17 ! 297: .ft R ! 298: .DE ! 299: the first occurrence of the identifier \f5g\fP is in the body of \f5f\fP, ! 300: which is before the function \f5g\fP is declared. To handle this problem, ! 301: all free identifiers found in the body of a recursive function definition are ! 302: kept on a ``backpatch list;'' when the mutually recursive declarations ! 303: are completely parsed, any references to identifiers defined in those ! 304: declarations are patched. This extends straightforwardly to nested ! 305: mutually recursive definitions. Note that constructors are not treated ! 306: this way; constructors cannot be redefined by ! 307: .B fun ! 308: or ! 309: .B ! 310: val rec ! 311: .R ! 312: definitions, so they are not put on the backpatch list. This means, of course, ! 313: that all identifiers must be looked up just to determine if they are constructors. ! 314: .NH 2 ! 315: Type Checking ! 316: .LP ! 317: The conventional polymorphic type checking algorithm[11,\|12] ! 318: is used at present. ! 319: The basis of the algorithm ! 320: is the unification of type terms by destructively instantiating type ! 321: variables so that they become indirections to other types. The ! 322: administration of generic or bound type variables is done more ! 323: systematically than in the Cardelli and Edinburgh compilers, but ! 324: generic types are still copied for each applied occurrence of a ! 325: variable. A new type representation and type checking algorithm based ! 326: on structure sharing (in the Boyer-Moore sense [13]) ! 327: has been prototyped with the help of Nick Rothwell. In this new scheme, ! 328: a polymorphic type is represented as a scheme whose bound variables are ! 329: indices into an environment vector; different instantiations share the ! 330: scheme but have different environment vectors. ! 331: ! 332: Overloading is accommodated to a limited extent. Currently only ! 333: certain predefined primitives such as ``+'' are overloaded, and there ! 334: are no facilities provided by the language for defining new overloaded ! 335: identifiers. Overloading is implemented as in the Edinburgh compiler, ! 336: where a type scheme is associated with the overloaded identifier and ! 337: each overloading (variant) has a type that is an instance of that ! 338: scheme. The contextual type of an occurrence of the identifier is ! 339: matched against the scheme and the resulting instantiations of the ! 340: scheme type variables is used to choose the appropriate variant. ! 341: This is simpler though more restrictive than the technique used in Hope[7], ! 342: where variants could have arbitrary types, as long as they ! 343: were incomparable (i.e. one was not an instance of another). ! 344: .NH 2 ! 345: Reference and exception types ! 346: .LP ! 347: The treatment of references and exceptions with ``open'' types ! 348: is new, and is based on the fact that the contents of a reference ! 349: cell cannot be constrained to be polymorphic, and so must be considered ! 350: to be monomorphic. The following example illustrates the problem. ! 351: .DS ! 352: .ft CW ! 353: .vs 13 ! 354: let val s = ref (fn x => x) ! 355: in s := (fn x => x+1); (!s) true ! 356: end ! 357: .vs 17 ! 358: .ft R ! 359: .DE ! 360: If s were given the polymorphic type ! 361: $roman "ALL"^ alpha "." ( alpha -> alpha ) bold roman "ref"$, then this ! 362: expression would type check, permitting an obvious type error. To prevent ! 363: this, we insist that the type of an applied occurrence of the ref ! 364: constructor should always be given a ``ground'' type (one with no locally-bound ! 365: type variables). ! 366: ! 367: However, functions whose application can create ! 368: reference variables can still have polymorphic types of a restricted ! 369: kind. Consider the declaration ! 370: .DS ! 371: .ft CW ! 372: .vs 13 ! 373: val F = fn x => let val r = ref x ! 374: in !r ! 375: end ! 376: .vs 17 ! 377: .ft R ! 378: .DE ! 379: Here the function F can be given polymorphic type ! 380: $roman "ALL"^ alpha sup 1 roman "." ! 381: alpha sup 1 -> alpha sup 1$ where $alpha sup 1$ ! 382: is a special kind of type variable ! 383: called a \fIweak\fP type variable (the superscript ``1'' indicates that ! 384: there is one lambda abstraction suspending the creation of the ! 385: ref cell). When F is applied to an argument, a reference value ! 386: of type $alpha sup 1$ is created, and hence this weak type ! 387: variable must be instantiated to a ground type. This means that ! 388: an expression like (F nil) would not be properly typed. In contrast, ! 389: the type ! 390: $alpha sup 1 bold roman "ref"$ ! 391: assigned to r is permissible because $alpha sup 1$ is implicitly ! 392: bound in an outer scope and within the scope of its binding is treated as ! 393: a constant type. ! 394: ! 395: Exception declarations raise similar problems, which are handled ! 396: by an analogous use of weak type variables. ! 397: ! 398: The Cardelli and Edinburgh compilers used an earlier form of this ! 399: treatment proposed by Damas[14]. ! 400: This earlier version was looser in one respect (it allowed unbound ! 401: weak type variables in types) and more restrictive ! 402: in another (multiple levels of lambda abstraction were not ! 403: allowed), and it had some rather counterintuitive effects. ! 404: .NH 2 ! 405: Modules ! 406: .LP ! 407: The simplest kind of module in Standard ML is called a structure. It is ! 408: most simply defined as an encapsulated set of declarations, as in ! 409: .DS ! 410: .ft CW ! 411: .vs 13 ! 412: structure S = ! 413: struct ! 414: type t = int * int ! 415: val x = (13, 18) ! 416: end ! 417: .vs 17 ! 418: .ft R ! 419: .DE ! 420: The compile-time symbol table representation of such a structure is ! 421: a binding table containing a binding for each identifier ! 422: declared in the structure. There is also an abstract syntax tree for ! 423: the structure, from which code is generated. ! 424: ! 425: A signature is an interface specification for a structure. For ! 426: instance, the structure above will match the following signature: ! 427: .DS ! 428: .ft CW ! 429: .vs 13 ! 430: signature SS = ! 431: sig ! 432: type t ! 433: val x : t ! 434: end ! 435: .vs 17 ! 436: .ft R ! 437: .DE ! 438: Signatures are also represented by binding tables, but an abstract ! 439: syntax tree is not needed since there is no code generated for ! 440: signatures. ! 441: ! 442: A functor is a structure abstracted with respect to one or more ! 443: structure parameters, which are characterized by signatures. It ! 444: follows that the representation of a functor consists of a structure ! 445: representation (the body) and a parameter specification. As part of ! 446: the parameter specification one can impose sharing constraints among ! 447: the parameters; these constraints are represented by transforming the ! 448: parameter specification into a directed acyclic graph of parameter ! 449: structures. ! 450: ! 451: Functors introduce some additional forms of structures, namely formal ! 452: parameter structures and structures defined by functor applications. ! 453: For formal parameters, the parameter signature serves as a virtual ! 454: template defining the components of the structure. In the case of ! 455: functor applications, the functor body (which may itself be a functor ! 456: application) is closed with respect to the environment formed by ! 457: binding actual parameters to the formals. ! 458: ! 459: Functor application in principle involves a beta-reduction in which ! 460: the actual structure parameters are substituted for the formal ! 461: parameters in the functor body to create the result structure. This ! 462: beta-reduction acts at two levels\(emthe static level, involving the ! 463: type aspects of the structures involved, and the dynamic level, ! 464: involving the value and exception components. The dynamic aspect of ! 465: the reduction is realized by generating code that performs an ordinary ! 466: function application. The static aspect is performed at compile time, ! 467: producing a static representation of the result structure by ! 468: ``instantiating'' the body of the functor using the formal/actual ! 469: parameter binding environment. ! 470: ! 471: .NH 3 ! 472: Naive copying ! 473: .LP ! 474: This static instantiation of functor bodies can be implemented in ! 475: several ways. The simplest and most direct is to perform a ! 476: straightforward nondestructive substitution, copying the ! 477: representation of the functor body in the process. This is roughly ! 478: the approach followed by Harper and Matthews in their implementations, ! 479: but the space consumed has been found to be excessive. ! 480: .NH 3 ! 481: Functor application closures ! 482: .LP ! 483: A second ! 484: alternative initially employed in our compiler was to represent ! 485: structures formed by functor applications as closures, consisting of ! 486: the unmodified functor body structure together with the parameter ! 487: binding environment. This approach saves some copying, but it turns ! 488: out to be rather unwieldy because functor applications can appear ! 489: within the body of a functor definition, and consequently the actual ! 490: parameters may be expressions that themselves refer to other formal ! 491: parameter variables. For instance, consider ! 492: .DS ! 493: .ft CW ! 494: .vs 13 ! 495: functor F(X:sigX) = bodyF ! 496: ! 497: functor G(Y:sigY) = ! 498: struct ! 499: structure S = F(Y) ! 500: ... ! 501: end ! 502: ! 503: structure B = G(A) ! 504: .vs 17 ! 505: .ft R ! 506: .DE ! 507: In this example, the interpretation of \f5B.S\fP is given in terms of the ! 508: closure of \f5F(X)\fP, which is a pair $< bodyF , ~ roman "{" X |-> ! 509: Y roman "}" > $. Since the ! 510: binding of \f5X\fP involves \f5Y\fP, this must be interpreted in the additional ! 511: context of the binding environment ! 512: $ roman "{" Y |-> A roman "}" $ produced by the ! 513: application \f5G(A)\fP. In general, a functor application closure may need ! 514: several layers of such contexts to be properly defined. ! 515: ! 516: Another drawback of this approach ! 517: is that it still performs the ! 518: copying that is inherent in the naive implementation ! 519: of signature matching, where a declaration such as ! 520: .DS ! 521: .ft CW ! 522: .vs 13 ! 523: structure S': sig1 = S ! 524: .vs 17 ! 525: .ft R ! 526: .DE ! 527: may cause a partial copy of \f5S\fP to be constructed and bound to \f5S'\fP. ! 528: .NH 3 ! 529: Structure-sharing ! 530: .LP ! 531: The final approach is inspired, like Harper's prototype implementation, ! 532: by the semantic model developed by Harper, Milner, and Tofte[15]. ! 533: We return to the straightforward idea of ! 534: actually performing the static reductions to obtain instantiated ! 535: copies of the functor body, but a structure-sharing representation is ! 536: used to minimize the amount of copying. In this representation, which ! 537: is similar in principle to the structure-sharing representation of ! 538: polytypes alluded to above, the statically ``interesting'' components ! 539: of each structure (i.e. types and substructures) are represented by ! 540: indices into an environment vector that is associated with the binding ! 541: table in another, simpler form of closure. The copying of structures that is ! 542: entailed by functor application and signature matching can then be ! 543: reduced to copying the closure objects and their associated ! 544: environment vectors, leaving the binding tables themselves unaffected. ! 545: ! 546: Each type or structure component also has an identifying ``name'', ! 547: which is basically just a unique number or time stamp. These names ! 548: serve a couple of purposes: representing sharing constraints, and ! 549: identifying two sorts of ``bound'' components. To capture sharing ! 550: specifications in signatures, components that are required to ``share'' ! 551: (i.e. represent views of the same structure) ! 552: are given the same name. There are two forms of bound components: ! 553: those incorporated in functor parameter structures, and those ! 554: representing the components of a functor body that are created each ! 555: time the functor is applied. All other names are free, and represent ! 556: actual structures. The first sort of bound names are mapped to ! 557: components by the process of signature matching between formal and ! 558: actual functor parameters. The second sort are replaced by new unique ! 559: names during the static elaboration of functor applications. ! 560: ! 561: This third approach is currently under development and will eventually ! 562: replace the implementation based on functor application closures. ! 563: .NH ! 564: Translating abstract syntax into lambda calculus ! 565: .LP ! 566: The middle end of the compiler translates type-checked abstract ! 567: syntax trees into lambda-calculus trees. Because all of the environment ! 568: and scope manipulation has been done by the front end, and all of the ! 569: abstract-machine manipulation will be done by the back end, the ! 570: translator is simple, small, and fast. ! 571: ! 572: Though most optimization is purposely left for the back end, some ! 573: simplification ! 574: is done in the middle end. Formally recursive ! 575: .B fun ! 576: definitions are examined to see if they really contain references to ! 577: themselves; ! 578: if not they are replaced by (simpler) nonrecursive definitions. ! 579: The composition of structure-creation and structure-thinning is evaluated ! 580: at compile-time rather than run-time. ! 581: The ML equality predicate is a special ! 582: function that can be applied to any concrete data type (one built up from ! 583: primitive types and reference types using record and datatype constructors). ! 584: The algorithm for testing equality is a recursive tree traversal that varies ! 585: in its details for each type to which it is applied. ! 586: For each such occurrence, the translator builds an equality predicate ! 587: appropriate to the instance. ! 588: ! 589: Recent changes to Standard ML will require the use of an ``equality ! 590: interpreter'' which traverses arbitrary structures in the runtime system, ! 591: distinguishing certain kinds of cells (like ! 592: .B ref ! 593: cells) by special descriptor tags. The present implementation can still ! 594: be used as an optimized version in those cases where enough is known about ! 595: the type at compile time. ! 596: .NH 2 ! 597: Translation of pattern-matching ! 598: .LP ! 599: One important and nontrivial job of the middle end ! 600: is to select optimal comparison ! 601: sequences for the compilation of pattern-matching. A \fImatch\fP in ML ! 602: is a sequence of pattern-expression pairs, called \fIrules\fP. When ! 603: a match is applied to an argument, the argument is matched against the ! 604: patterns, and the first rule with a matching pattern ! 605: is selected and its expression ! 606: is evaluated. A pattern is either a constant, which must match the argument ! 607: exactly; a variable, which matches any argument (and is bound to it for ! 608: the purposes of evaluating the expression); a tuple of patterns, which matches ! 609: a corresponding tuple argument whose components match the components of the ! 610: pattern-tuple; or a constructor applied to a pattern, which matches ! 611: an argument built using that constructor if the rest of the pattern matches. ! 612: ! 613: As an example, consider the case statement: ! 614: .DS ! 615: .ft CW ! 616: .vs 13 ! 617: case a ! 618: of (false, nil) => nil ! 619: | (true, W) => W ! 620: | (false, cons(X, nil)) => cons(X, cons(X, nil)) ! 621: | (false, cons(Y, Z)) => Z ! 622: .vs 17 ! 623: .ft R ! 624: .DE ! 625: The argument (false, cons(4,nil)) matches the third pattern, while the ! 626: argument (true, cons(4,nil)) matches the second pattern. ! 627: ! 628: One could imagine a naive compilation of matches just by testing the ! 629: rules in turn as called for by the semantics. Our approach is to transform a ! 630: sequence of patterns into a decision tree[16]. ! 631: Each internal node of the decision tree corresponds to a matching test and ! 632: each branch is labeled with one of the possible results of the matching test ! 633: and with a list of the patterns that remain potential candidates in that ! 634: case. ! 635: It is then straightforward to translate the decision tree into code for ! 636: pattern matching. During the construction of the decision tree it is also ! 637: easy to determine whether the pattern set is ``exhaustive,'' meaning that ! 638: every possible argument value matches at least one pattern; and whether ! 639: there are any ``redundant'' patterns that only match arguments covered ! 640: by previous rules. Nonexhaustive and redundant patterns result in warning ! 641: messages by the compiler. ! 642: ! 643: Our goal in constructing the decision tree is simply to minimize the total ! 644: number of test-nodes. This minimizes the size of the generated code and ! 645: also generally reduces the number of tests performed on value terms. ! 646: However, finding the decision tree with the minimum number of nodes is ! 647: an NP-complete problem[16]; ! 648: so a set of efficient heuristics is used that in practice produces an optimal ! 649: decision tree in almost all cases. ! 650: ! 651: In the example above, testing the first component of the pair for truth ! 652: or falsity suffices to distinguish the second rule from the others; ! 653: then testing the second component to see whether it is \f5cons\fP or ! 654: \f5nil\fP distinguishes the first rule from the last two; one more test ! 655: suffices to separate the last two rules. Thus, in just two or three ! 656: tests, the appropriate rule can be selected; instead of two or three ! 657: tests \fIper rule\fP that the naive algorithm would use. ! 658: ! 659: The details of this algorithm were originally worked out by Marianne Baudinet ! 660: and have been implemented in our compiler by Trevor Jim. ! 661: .NH 2 ! 662: The Lambda Language ! 663: .LP ! 664: The front end of the compiler translates ML source into ! 665: lambda calculus; the back end translates lambda-calculus into machine ! 666: code for the VAX or MC68020. ! 667: A significant advantage of having a very simple lambda-calculus ! 668: as the intermediate representation is that many compiler ! 669: optimizations can be cleanly described. This is the approach ! 670: successfully taken in the Rabbit[17] ! 671: and Orbit[18] ! 672: compilers for Scheme. ! 673: ! 674: The ``lambda language'' is simply an ! 675: ML datatype, as follows: ! 676: .DS ! 677: .ft CW ! 678: .vs 13 ! 679: datatype lexp ! 680: = VAR of lvar ! 681: | FN of lvar * lexp ! 682: | FIX of lvar list * lexp list * lexp ! 683: | APP of lexp * lexp ! 684: | CON of con * lexp ! 685: | DECON of con * lexp ! 686: | SWITCH of lexp * (con*lexp) list * lexp Option ! 687: | RECORD of lexp list ! 688: | SELECT of int * lexp ! 689: | RAISE of lexp ! 690: | HANDLE of lexp * lexp ! 691: .vs 17 ! 692: .ft R ! 693: .DE ! 694: The elements of the lambda language are variables (VAR), ! 695: abstraction (FN), simultaneous recursive definition (FIX), ! 696: application (APP), constructors (CON, DECON, and SWITCH), ! 697: tuples (RECORD and SELECT), and exception handling (HANDLE and ! 698: RAISE). ! 699: Exception names, and integer, real, and string literals, ! 700: are represented as constructors. ! 701: ! 702: There are no built-in library functions, at least from the point of ! 703: view of the compiler; instead, the library module is lambda-bound ! 704: at top level to some variable $v$, and functions from the ! 705: library are just components of the structure $v$. Certain functions ! 706: that are compiled in-line are represented as fields of a special structure ! 707: bound to a distinguished variable $v sub 0$. ! 708: ! 709: Constructor-expressions are explicitly represented in the lambda ! 710: language using $roman "CON" ( c, e) $ representing the application ! 711: of the constructor $c$ to the expression $e$, and $roman "DECON" ! 712: (c,e)$ representing the removal of $c$ from the constructed ! 713: expression $e$. ! 714: The lambda language could be simplified by ! 715: representing constructor-expressions as pairs of (tag,value), ! 716: and removing the CON and DECON operators. The advantage of using ! 717: CON and DECON is that the lambda language can be a typed lambda ! 718: calculus; the disadvantage is that the code generator is somewhat ! 719: more complicated. Since it's not clear why the low-level ! 720: representation needs to be typable, we may change the representation ! 721: of constructors in the lambda language. ! 722: ! 723: A structure resembles a record of components; ! 724: in the lambda language, it is treated exactly as a RECORD. There ! 725: are no special forms in the lambda language for structures and functors. ! 726: Functors are treated as functions from structures (RECORDs) to structures. ! 727: Actually, a structure definition is not just a record; it is an expression ! 728: that \fIevaluates\fP to a record. ! 729: ! 730: Structure $A$ may refer to an element of structure $B$; in the lambda language ! 731: this will mean that $B$ is a free variable of the expression representing ! 732: $A$. To keep things simple, it is important to eliminate all free variables ! 733: from an expression before code is generated for it; so the variable $B$ will ! 734: be lambda-bound at top level in the definition of $A$, and the runtime system ! 735: will be instructed to apply the resulting pseudo-functor to the structure ! 736: $B$ to initialize it. ! 737: This is a form of automatic, ! 738: compiler-controlled link-loading. For a link-loading scheme ! 739: it is extremely simple; our runtime structure-manager and link-loader ! 740: is less than a page of ML code. ! 741: .NH ! 742: Code generation ! 743: .LP ! 744: The back end of the compiler is organized as the composition of ! 745: functors; these functors are most naturally described starting at the end and working ! 746: toward the front. The last phase is the back-patching of jumps and ! 747: other relative addresses in a machine-language program. Relative ! 748: jump instructions on many machines are of different sizes depending on the distance ! 749: jumped, and several iterations of estimating jump sizes may be ! 750: required before a fixed point is found[19]. ! 751: This is handled in a machine-independent way by the Backpatch ! 752: functor: ! 753: .DS ! 754: .ft CW ! 755: .vs 13 ! 756: signature RelativeAddresses = ! 757: sig type JumpKind ! 758: val sizeJump : JumpKind -> int ! 759: val emitJump : (int -> unit) -> JumpKind -> unit ! 760: end ! 761: .ft R ! 762: .vs 17 ! 763: .DE ! 764: ! 765: .DS ! 766: .ft CW ! 767: .vs 13 ! 768: signature BackPatch = ! 769: sig ! 770: type Label ! 771: val newlabel : unit -> Label ! 772: type JumpKind ! 773: val emitbyte : int -> unit ! 774: val align : unit -> unit ! 775: val define : Label -> unit ! 776: val jump : JumpKind*Label -> unit ! 777: . . . ! 778: end ! 779: ! 780: functor Backpatch( Rel : RelativeAddresses ) : BackPatch ! 781: = struct ! 782: . . . ! 783: end ! 784: .vs 17 ! 785: .ft R ! 786: .DE ! 787: For a given machine with a particular kind of relative address, one ! 788: builds a small structure explaining what size of relative address can ! 789: reach what distance, and how to emit code for that sort of relative ! 790: address. For example, on the Vax, a short conditional branch ! 791: takes 2 bytes, but a medium-size conditional ! 792: branch requires 6 bytes (a short conditional branch around a longer ! 793: unconditional jump), and a longer conditional jump takes 8 bytes. ! 794: ! 795: When the Backpatch functor is applied to a structure of type RelativeAddresses, ! 796: a new structure is created that understands how to ! 797: backpatch the code for a particular machine. A code generator for ! 798: that machine can make use of the \fIemit\fP and \fIlabel\fP primitives provided ! 799: by the specialized Backpatch structure. ! 800: ! 801: The signature VaxCode (not shown here) ! 802: provides an interface to a useful subset of the ! 803: Vax instruction set. The various instructions and addressing modes ! 804: are defined in this signature. There are two structures that ! 805: implement the VaxCode signature in different ways: one that ! 806: generates machine code when the instructions in the signature are ! 807: called for, and one that generates assembly code. ! 808: The VaxMcode structure matches the VaxCode signature, and ! 809: makes use of the Backpatch functor: ! 810: .DS ! 811: .ft CW ! 812: .vs 13 ! 813: structure VaxMcode : VaxCode = ! 814: struct ! 815: structure R : RelativeAddresses = ! 816: struct ! 817: . . . functions to compile relative jumps, etc. ! 818: end ! 819: structure B = Backpatch(R) ! 820: datatype AddressMode = . . . ! 821: fun movl (src : AddressMode, dst: AddressMode) = . . . ! 822: end ! 823: .vs 17 ! 824: .ft R ! 825: .DE ! 826: There is a peephole optimization module that operates on abstract Vax ! 827: instructions. It is implemented as a functor taking any VaxCode ! 828: structure into an ``optimized'' VaxCode structure: ! 829: .DS ! 830: .ft CW ! 831: .vs 13 ! 832: functor Peephole ( V : VaxCode ) : VaxCode = struct . . . end ! 833: structure OptVaxMcode = Peephole(VaxMcode) ! 834: .vs 17 ! 835: .ft R ! 836: .DE ! 837: If some other structure asks the module OptVaxMcode to produce ! 838: a particular instruction sequence, it may be that some optimized version of ! 839: the sequence will be generated instead. ! 840: ! 841: The code generator translates programs from ! 842: lambda language into machine code by ! 843: means of an abstract machine intermediate form. The abstract machine ! 844: is similar in spirit to Cardelli's[20]. ! 845: The abstract machine interface is written as a signature in ML: ! 846: .DS ! 847: .ft CW ! 848: .vs 13 ! 849: signature machine = ! 850: sig type Label ! 851: val select : int -> unit ! 852: val apply : unit -> unit ! 853: val tailrecur : int -> unit ! 854: val startrecord : int -> unit ! 855: val endrecord : unit -> unit ! 856: . . . ! 857: end; ! 858: .vs 17 ! 859: .ft R ! 860: .DE ! 861: Each of these functions, when called, generates machine code for the ! 862: corresponding operation. ! 863: ! 864: There is a functor Vax that transforms a VaxCode structure into ! 865: a Machine structure: ! 866: .DS ! 867: .ft CW ! 868: .vs 13 ! 869: functor Vax(C : VaxCode) : Machine = ! 870: struct ! 871: fun select j = C.movl(Displace(r0,4*j),Direct(r0)) ! 872: . ! 873: . ! 874: . ! 875: end ! 876: ! 877: structure VaxM = Vax(OptVaxMcode) ! 878: structure VaxA = Vax(OptVaxAcode) ! 879: .vs 17 ! 880: .ft R ! 881: .DE ! 882: This Vax functor can be applied to the structure OptVaxMcode, which will ! 883: result in an abstract machine that generates machine code for the ! 884: Vax; or to the structure OptVaxAcode, which generates assembly ! 885: language. We have a similar set of structures and functors that generate ! 886: code for the Motorola MC68020 architecture. ! 887: ! 888: Finally we can take this implementation of the abstract Machine, and ! 889: apply the Codegen functor to it: ! 890: .DS ! 891: .ft CW ! 892: .vs 13 ! 893: functor Codegen(M : Machine) = ! 894: struct ! 895: fun codegen(APP(a,b)) = (codegen a; codegen b; M.apply()) ! 896: | codegen(FN(v,b)) = . . . ! 897: \fI(The codegen function is not quite as simple as this, of course.)\fP ! 898: end ! 899: ! 900: structure VaxMCodegen = Codegen(VaxM) ! 901: structure M68MCodegen = Codegen(M68M) ! 902: .vs 17 ! 903: .ft R ! 904: .DE ! 905: Now the function VaxMCodegen.codegen, when given a lambda expression, ! 906: generates optimized, backpatched machine code for the Vax. ! 907: ! 908: This arrangement of functors is quite satisfactory. However, the ! 909: details of the Machine signature might ! 910: be changed to make ! 911: it less like a stack machine. This might improve the generated ! 912: code, especially for architectures that are not naturally ! 913: stack-oriented. We are considering a re-implementation of the ! 914: code generator using continuation-passing style[17] ! 915: and Orbit[18]. ! 916: .NH 2 ! 917: Pattern-matching in code generation ! 918: .LP ! 919: Many ! 920: modern code generators are written in the form of tree-pattern matchers[21,\|22]. ! 921: By writing the lambda-calculus ! 922: tree data structure as an ML datatype (with constructors), ! 923: the tree pattern matching can be directly specified as an ML function. ! 924: There is one pattern for each constructor, e.g. ! 925: .DS ! 926: .ft CW ! 927: .vs 13 ! 928: | APP(f,a) => (gen(f); gen(a); machine.apply()) ! 929: .vs 17 ! 930: .ft R ! 931: .DE ! 932: but there are also cases that match certain ``idioms;'' like ! 933: .B let ! 934: expressions, which are represented as the application of a lambda-function: ! 935: .DS ! 936: .ft CW ! 937: .vs 13 ! 938: | APP(FN(w,b), a) => \fIgenerate code for \fBlet \fIw\fB=\fIa\fB in \fIb\fP ! 939: .vs 17 ! 940: .ft R ! 941: .DE ! 942: There are about a dozen simple cases that handle small patterns with just one ! 943: constructor, and a dozen more complicated cases that recognize idioms. ! 944: ! 945: Some of these cases are applicable in only certain situations. For example, ! 946: the pattern \f5APP(VAR\ w,\ a)\fP can be compiled without a closure only if ! 947: \fIw\fP refers to a ``known'' function. One might like to write the ! 948: clause for this case as: ! 949: .DS ! 950: .ft CW ! 951: .vs 13 ! 952: | APP(VAR w, a) => if knownfunc(w) then applyknown(w,a) else ??? ! 953: .vs 17 ! 954: .ft R ! 955: .DE ! 956: The problem is that if $w$ is not a known function, then this pattern-match ! 957: is not useful, and the pattern \f5APP(f,a)\fP should be matched. By ! 958: the time the else clause is reached, it is impossible to transfer control ! 959: to the \f5APP(f,a)\fP clause. ! 960: ! 961: One solution to this problem is to attach boolean conditions to patterns, ! 962: as is done in Miranda[23]: ! 963: .DS ! 964: .ft CW ! 965: .vs 13 ! 966: | APP(VAR w, a) when knownfunc(w) => ! 967: .vs 17 ! 968: .ft R ! 969: .DE ! 970: Though would add some ``syntactic sludge'' to the language, it is worth ! 971: considering as a future extension. ! 972: .NH 2 ! 973: Flat versus linked environments ! 974: .LP ! 975: An ! 976: .I environment ! 977: is a mapping from some sort of ! 978: .I identifiers ! 979: to some sort of ! 980: .I bindings, ! 981: on which the fundamental operations are ! 982: .EQ ! 983: update ~~ ":" ~~ roman "Env" ~ times ~ roman "Ide" ~ times ~ roman "Bdg" ~~ ! 984: -> ~~roman "Env" ! 985: .EN ! 986: .EQ ! 987: overlay ~~ ":" ~~ roman "Env" ~ times ~ roman "Env" ~~ -> ~~ roman "Env" ! 988: .EN ! 989: .EQ ! 990: access ~~ ":" ~~ roman "Env" ~ times ~ roman "Ide" ~~ -> ~~roman "Bdg" ! 991: .EN ! 992: The update function adds an identifier and binding to an ! 993: environment; the access function looks up an identifier; and the ! 994: overlay function ! 995: adds all of the bindings of one environment to ! 996: another. The overlay function might be used to implement the ML ! 997: .B open ! 998: primitive, for example. ! 999: ! 1000: Environments occur both in the compiler, where the identifiers are ! 1001: those of the source text, and the bindings are types and other ! 1002: information; and at runtime, in the form of function closures. ! 1003: A function closure, in implementations of lambda calculus, is a pair ! 1004: consisting of a function-code and the values to be associated with ! 1005: all of its free variables. ! 1006: Though these two kinds of environments are used in different ways, ! 1007: they share many of the same problems of implementation. ! 1008: ! 1009: We know of no data structure that implements environments with both a very ! 1010: fast \fIaccess\fP function and a very fast \fIoverlay\fP function. ! 1011: For compile-time environments, ! 1012: a fast \fIaccess\fP can be achieved by using a hash table; but ! 1013: then to overlay one hash table onto another takes time proportional ! 1014: to the size of the smaller table. A fast \fIoverlay\fP can be ! 1015: achieved by representing environments as trees of \fIoverlay\fP ! 1016: operations, but then \fIaccess\fP requires a tree search. ! 1017: In the compiled code, closures can be represented as flat ! 1018: or linked structures. A flat closure[2] ! 1019: is just a vector with one slot ! 1020: for each free variable, containing the value to which it is bound. ! 1021: A linked closure[17] ! 1022: has the binding of one free variable at the ! 1023: innermost level, along with a pointer to the closure for the ! 1024: enclosing scope. ! 1025: Flat closures have a fast \fIaccess\fP, but to build the vector takes ! 1026: time proportional to the number of free variables in the function. ! 1027: Linked closures can be built quickly: since the enclosing linked ! 1028: closure has already been built, just one \fIcons\fP operation is required. ! 1029: However, linked closures have a slower \fIaccess\fP function. ! 1030: ! 1031: The question of whether to make \fIaccess\fP or \fIoverlay\fP fast ! 1032: is a difficult one, since examples can be found to support either ! 1033: decision. Consequently, we have adopted a compromise: a mixed ! 1034: representation will be used, so that the time required for a typical ! 1035: sequence of \fIaccess\fP and \fIoverlay\fP operations will probably be ! 1036: smaller than if either the flat (hash-table) or linked (tree) ! 1037: representation were used. ! 1038: ! 1039: In the front end, environments are represented as linked lists of ! 1040: hash tables. The operation of \fBopen\fPing a structure just adds ! 1041: the (already-built) hash-table for that structure onto the list, but ! 1042: a typical \fIupdate\fP operation just adds a binding to an existing ! 1043: hash table. ! 1044: In the generated code, closures are represented as trees\(em ! 1045: a compromise between flat and linked closures. For each free variable ! 1046: in a closure, the back end heuristically decides whether to add ! 1047: another slot to the closure for this variable, or to access this ! 1048: variable through the (already existing) linked closure. The former ! 1049: is more expensive to build, but cheaper to use. When we have more ! 1050: experience with this compiler, we hope to measure the relative ! 1051: efficiencies of flat and linked closures in practice. ! 1052: .NH 2 ! 1053: Data structures in the run-time system ! 1054: .LP ! 1055: The representations of ML structures in the compiled code are designed ! 1056: to be simple and general. We took care to avoid arbitrary restrictions ! 1057: on the size of the address space, or of individual objects. Except for ! 1058: .B ref ! 1059: cells, the graph of runtime objects is acyclic\(em ! 1060: even mutually recursive closures are represented without cycles. ! 1061: ! 1062: Because of ML's polymorphic type system, all values must be the same size. ! 1063: We take this size to be one 32-bit word (this discussion of runtime ! 1064: data structures is specific to implementations on 32-bit computers). ! 1065: Any value that is naturally represented in more than one word will ! 1066: be manipulated as a (one-word) pointer to a (multi-word) object; ! 1067: this is known as a ! 1068: .I boxed ! 1069: value. An ! 1070: .I unboxed ! 1071: value is one which fits in one word and is not a pointer. ! 1072: The garbage collector must be able to tell which words are boxed (pointers) ! 1073: and which words are not. We use the low-order bit of the word as the ! 1074: indicator. Since all of our pointers (on byte-addressable machines) ! 1075: point to word boundaries, the low-order bit of any pointer is zero. ! 1076: Unboxed values are represented with their low-order bits turned on. ! 1077: Unboxed values include constructor tags, ! 1078: one-character strings, and integers (the integer $k$ is ! 1079: represented as $2k+1$). ! 1080: ! 1081: All records in the runtime system contain an integral number of 32-bit words, ! 1082: and are prefaced by a descriptor describing their size and type. There are two ! 1083: types of records: those that contain pointers (and unboxed values), and ! 1084: those that contain no pointers. All ML tuples and closures are of the ! 1085: former variety (Figure 1); ! 1086: strings and machine-code fragments are of the latter. ! 1087: The fact that machine-code fragments contain no pointers is a consequence ! 1088: of the fact that the back-end code generator is never applied to any ! 1089: lambda-expression with free variables. ! 1090: .KF ! 1091: .PS ! 1092: boxht = .2; boxwid = .35; a = .4 ! 1093: [down; A: box; B: box; C: box; D: box ! 1094: "desc" at A.c ! 1095: spline -> from B.c right a then up a/2 then right a+a ! 1096: "78" at C.c ! 1097: spline -> from D.c right a then down a/4 then right a ! 1098: line <- from B.w left a ! 1099: "Figure 1. A triple of two" "pointers and an integer" \ ! 1100: at 2.2 between A.c and D.c ! 1101: ] ! 1102: move right 4*a ! 1103: [down; A: box; B: box; C: box; D: box; E: box ! 1104: "desc" at A.c ! 1105: line <- from B.w left a ! 1106: line <- from C.w left a ! 1107: line <- from D.w left a ! 1108: spline -> from B.c right a then up a/2 then right a+a ! 1109: spline -> from C.c right a then up a/4 then right a ! 1110: spline -> from D.c right a then down a/2 then right a+a ! 1111: "62" at E.c ! 1112: "Figure 2. Three mutually recursive" \ ! 1113: "functions with one free variable" \ ! 1114: at 2.2 between A.c and D.c ! 1115: ] ! 1116: .PE ! 1117: .KE ! 1118: ! 1119: There are several different representations used for constructors, ! 1120: just as in Cardelli's compiler[2]. ! 1121: Constructors that carry values are usually represented as pairs of ! 1122: (tag, value). Constant constructors (link \fBnil\fP) are represented ! 1123: as small unboxed integers. Value-carrying constructors can be ! 1124: represented completely transparently if they carry an always-boxed value, ! 1125: and there are no other value-carrying constructors in the datatype ! 1126: (like \fBcons\fP), or if there is only one constructor in the datatype. ! 1127: Finally, \fBref\fP cells look like one-word records. ! 1128: ! 1129: Earlier ML compilers[20] ! 1130: represented mutually recursive functions as a set of closures that point ! 1131: to each other in a cyclic data structure. We wanted to avoid gratuitous ! 1132: circularity, because many garbage collection algorithms are more efficient ! 1133: on acyclic data structures[24]. ! 1134: We represent mutually recursive functions with just one closure ! 1135: record that has ! 1136: several function-part entries (Figure 2). To represent function $i$, ! 1137: one just uses a pointer to the $i sup roman "th"$ field. If the ! 1138: $i sup roman "th"$ function wants to refer to the closure for the $j sup ! 1139: roman "th"$ function, it just adds $ j - i $ to the pointer value for ! 1140: its own closure. This creates no cycles of pointers; although there ! 1141: is an implicit cycle because each segment of machine code can refer ! 1142: to the machine's registers, which in turn point to the closure. ! 1143: ! 1144: Several machine-code fragments may be stored in the same record. ! 1145: Because the record may be moved by the garbage collector, references within ! 1146: the block must be by relative addresses. Many computers have PC-relative ! 1147: addressing modes to facilitate this; for other machines, a pointer to ! 1148: the currently-executing block would have to be kept in a register. ! 1149: A typical record contains all of the function and string-literal fragments ! 1150: for an entire ML structure (module). In order not to confuse the garbage ! 1151: collector, just before the start of each fragment within the record ! 1152: is a (relative) pointer back to the beginning of the record, where ! 1153: the record's descriptor may be found; and all fragments must begin ! 1154: on even byte addresses, so that pointers to fragments within the record ! 1155: won't look like unboxed values. ! 1156: .NH 2 ! 1157: Fast consing ! 1158: .LP ! 1159: The operation of allocating and initializing a record\(em ! 1160: known in Lisp ! 1161: as \fIcons\fP\(em ! 1162: is a fundamental operation in ML. The creation of a tuple value, ! 1163: the application of a constructor, and the creation of a function-closure ! 1164: all rely on this operation. Our implementation strives to make this operation ! 1165: as fast as possible. ! 1166: ! 1167: We use a copying (and compacting) garbage collection algorithm. One ! 1168: consequence of this is that the free space available for storage ! 1169: allocation is a contiguous region of memory. We reserve a register ! 1170: to point at the lowest address in the free region. To allocate an ! 1171: $n$-word initialized record, the $n sup roman "th"$ word is stored ! 1172: first, followed by the other words and the descriptor. Then the ! 1173: free-space register is incremented by $n$ words. ! 1174: ! 1175: At some point the free space is exhausted. We arrange to have an ! 1176: inaccessible page of the address space immediately beyond the free ! 1177: space region. When there is an attempt to allocate a record that ! 1178: crosses into this page, a page fault occurs; and since the page is ! 1179: marked as inaccessible, the operating system transfers control to the ! 1180: ML runtime system instead of handling the page fault itself. Because ! 1181: the last word of a record is stored first, the creation of ! 1182: any record that crosses ! 1183: the boundary into the inaccessible region will fault at the beginning ! 1184: (where it is easy to patch things up and recover) rather than halfway ! 1185: through its initialization. (A special case is required for those ! 1186: records larger than a page; but these are rare and identifiable at ! 1187: compile-time.) ! 1188: Thus, the creation of an $n$-word record takes just $n$ stores ! 1189: (plus one for the descriptor) and an ! 1190: add instruction; the $n$ stores are required in any case to do the ! 1191: initialization. This is an extremely low-overhead ! 1192: .I cons ! 1193: operation. ! 1194: ! 1195: A fast ! 1196: .I cons ! 1197: is not helpful if garbage collection is slow. When physical memory ! 1198: is large, then the cost of copying garbage collection, amortized over ! 1199: the ! 1200: .I cons ! 1201: operations, can come to less than one instruction per \fIcons\fP $~~$[25]. ! 1202: We are using a variant of generational garbage collection[24,\|26,\|27] ! 1203: that promises to be very fast even in moderate-size memories. ! 1204: ! 1205: In writing our compiler, we have aimed for clarity and ! 1206: straightforwardness; we have eschewed the coding tricks that enable ! 1207: programmers to avoid using dynamically allocated memory. As a ! 1208: result, our compiler probably \fIcons\fPes a lot more than other compilers; ! 1209: it is fortunate that we have made ! 1210: .I cons ! 1211: so fast. ! 1212: .NH 2 ! 1213: Exception Handling ! 1214: .LP ! 1215: Exception names in Standard ML behave a bit like constructors, except that ! 1216: they are generatively (dynamically) defined. The statements ! 1217: .DS ! 1218: .ft CW ! 1219: .vs 13 ! 1220: exception E : int and R : real ! 1221: \fIand\fP ! 1222: ... handle E with 0 => 10 ! 1223: | i => 2*i ! 1224: || R with 0.0 => 10 ! 1225: | r => 2*floor(r) ! 1226: .vs 17 ! 1227: .ft R ! 1228: .DE ! 1229: are, if one considers $E$ and $R$ to be constructors, ! 1230: a bit like the (hypothetical) statements ! 1231: .DS ! 1232: .ft CW ! 1233: .vs 13 ! 1234: datatype exception = . . . E of int | R of real . . . ! 1235: \fIand\fP ! 1236: ... handle E(0) => 10 ! 1237: | E(i) => 2*i ! 1238: | R(0.0) => 10 ! 1239: | R(r) => 2*floor(r) ! 1240: .vs 17 ! 1241: .ft R ! 1242: .DE ! 1243: Generative data constructors cannot be given small integer tags like ordinary ! 1244: constructors. The tag for the constructor \f5E\fP must be constructed ! 1245: at run time. Its representation will be that of a ! 1246: .B ref ! 1247: cell: \f5ref("E")\fP. Any match applied to the exception type can ! 1248: compare the address of the ref cell against the tag of the exception ! 1249: object; and if the exception is propagated out of the user program into ! 1250: the bootstrap system, then the name of the exception can be determined ! 1251: (for debugging) by dereferencing the tag. ! 1252: ! 1253: The ML statement \f5raise e with v\fP is compiled into the lambda language ! 1254: as \f5RAISE(CON(e,v))\fP; and the handle statement applies a match ! 1255: (with exceptions as constructors) to the exception raised. That is, ! 1256: \f5HANDLE(e,h)\fP executes expression \f5e\fP; if any exception is raised ! 1257: in \f5e\fP, then the handler \f5h\fP (which is a ! 1258: \fImatch\fP as above) is applied to that exception. ! 1259: If \f5e\fP has type $t$, then \f5h\fP must have type $"exception" ^ -> ^ t$. ! 1260: ! 1261: The implementation of \f5HANDLE(e,h)\fP in the code generator is ! 1262: straightforward. An exception handler is pushed on the stack; ! 1263: expression \f5e\fP is executed; if no exception is raised, the ! 1264: exception handler is popped from the stack. An exception handler ! 1265: has two components: the address of the machine-code for the match ! 1266: \f5h\fP, and the address (on the stack) of the enclosing ! 1267: exception handler. Thus, entering the scope of a ! 1268: .B handle ! 1269: takes about two instructions, as does leaving its scope. ! 1270: ! 1271: To raise an exception \f5E(v)\fP, first the constructor-expression ! 1272: \f5E(v)\fP is evaluated and put into a register; ! 1273: then the stack pointer is reset to the position ! 1274: of the current exception handler; the current handler is removed; and ! 1275: control passes to the text address found in the current handler. ! 1276: The match \f5h\fP found at this address is thereby applied to the ! 1277: exception \f5E(v)\fP previously evaluated. Raising an exception ! 1278: therefore takes just three or four instructions, not including the ! 1279: match that may have to be done upon arrival at the handler to determine ! 1280: which exception was raised. ! 1281: ! 1282: The constructor view of exception ! 1283: names is not just a convenience in implementation. ! 1284: After some discussion in the Standard ML community, exceptions-as-constructors ! 1285: have been incorporated into the language. ! 1286: We no longer need ! 1287: an awkward notation (using \f5|\fP and \f5||\fP) for exception matches; ! 1288: and such things as re-raising an arbitrary expression are now possible: ! 1289: .DS ! 1290: .ft CW ! 1291: .vs 13 ! 1292: handle e => (clean up; raise e) \fI(e is a variable of type ``exception'')\fP ! 1293: .vs 17 ! 1294: .ft R ! 1295: .DE ! 1296: .NH ! 1297: The runtime system ! 1298: .LP ! 1299: The ML standard library is implemented as a single module in the runtime ! 1300: system. The library functions are mostly written in ML, with some ! 1301: references to primitive functions written in assembly language. ! 1302: All references to library functions are treated as references ! 1303: to fields of this structure. Thus, a typical expression might have ! 1304: only one free variable\(em ! 1305: the library structure itself. In order to ensure that the code generator ! 1306: is given only closed expressions, this variable is lambda-bound; the ! 1307: machine code resulting from this closed expression can then be treated as ! 1308: a function whose argument will be the standard library. Access to user-defined ! 1309: structures is handled similarly. ! 1310: ! 1311: This general plan is used both for the ``bootstrap'' system (in which ! 1312: the machine code for each ! 1313: structure is written to an external file), and ! 1314: the ``interactive'' system ! 1315: (in which the code for each structure or expression is ! 1316: put in memory and executed). In the interactive system, each expression ! 1317: when compiled is represented as a function whose argument is the ! 1318: current top-level environment, and whose result is the new binding. ! 1319: Thus, the interface between the compiler and the runtime system is ! 1320: very narrow and clean. ! 1321: .NH ! 1322: Conclusion ! 1323: .LP ! 1324: Standard ML is a complicated language. We manage the complexity by using ! 1325: a sequence of well-defined intermediate representations: tokens, abstract ! 1326: syntax, lambda-calculus, abstract machine, symbolic machine instructions. ! 1327: The datatypes and signatures of ML allow these interfaces to be cleanly ! 1328: specified, which is a great help. ! 1329: ! 1330: Lexical analysis\(em ! 1331: the translation from source programs to tokens\(em ! 1332: we make as simple as possible (760 lines). All complications of recognizing ! 1333: constructors and infix operators are handled in the parser, which ! 1334: has better access to the environment mechanisms. ! 1335: ! 1336: The translation from tokens to ! 1337: fully typed abstract syntax is the most complicated phase of our ! 1338: compiler (4500 lines). ! 1339: Because compile-time environments have some effect on parsing ! 1340: (constructors and infix operators), we merged the compilation ! 1341: of syntax, scopes, and types. ! 1342: This is where most of the complexity of the module features ! 1343: appear. But since structures and functors impinge on scopes and types ! 1344: in fundamental ways, we decided to build them into this phase from the ! 1345: beginning. ! 1346: ! 1347: Since the abstract syntax is so well-digested and complete, the next phase\(em ! 1348: translation into lambda-calculus\(em ! 1349: is relatively simple (1480 lines). ! 1350: The major task of this phase is the compilation of patterns into ! 1351: decision trees. ! 1352: ! 1353: A simple lambda-calculus representation of programs allows a simple ! 1354: and consistent code generator that can recognize idioms in lambda calculus ! 1355: and optimize them (750 lines). ! 1356: The lambda-calculus can also be useful for other kinds ! 1357: of analysis, like in-line function expansion. ! 1358: The back end needs to know very little about front-end data structures; ! 1359: thus, we found it simplest to have an environment manager for the back ! 1360: end that is completely separate from the front-end environment manager. ! 1361: Back-end environments map applied occurrences of variables ! 1362: in the lambda-calculus to their bindings, just as the front-end environment ! 1363: does that job for abstract syntax trees. ! 1364: ! 1365: The last two phases\(em ! 1366: translation from abstract machine instructions into ! 1367: symbolic (Vax or MC68020) instructions, and the translation of those ! 1368: into backpatched byte sequences\(em ! 1369: are the only machine-dependent phases. ! 1370: These phases have a largely straightforward structure (950 lines per machine). ! 1371: In the current ! 1372: implementation, our code generators do not make adequate use of registers, ! 1373: and we may redesign the abstract machine interface. ! 1374: ! 1375: Finally, the runtime system is kept very simple (450 lines of C, ! 1376: including the garbage collector; 300 lines ! 1377: of assembly language). ! 1378: As a statically-typed ! 1379: language, ML by its nature is oriented to compile-time analysis rather ! 1380: than complicated runtime systems; and we push this as far as possible. ! 1381: Our standard library is mostly implemented in ML ! 1382: (750 lines), and those parts ! 1383: implemented in assembly language are arranged to match a Standard ML ! 1384: signature. We pay particular attention to fast allocation of dynamic ! 1385: storage, since the features of ML (constructors, function closures) necessitate ! 1386: an efficient memory allocator and garbage collector. ! 1387: ! 1388: Our compiler is concise, efficient, and (we think) well-designed. ! 1389: It should prove to be a useful tool for the functional programming community. ! 1390: .sp 2 ! 1391: .nr PS 10 ! 1392: .nr VS 12 ! 1393: .LP ! 1394: .]< ! 1395: .ds [F 1 ! 1396: .]- ! 1397: .ds [A P. J. Landin ! 1398: .ds [U landin ! 1399: .ds [K ISWIM ! 1400: .ds [T The next 700 programming languages ! 1401: .ds [J Comm. ACM ! 1402: .ds [V 9 ! 1403: .ds [N 3 ! 1404: .ds [P 157-166 ! 1405: .nr [P 1 ! 1406: .ds [D 1966 ! 1407: .nr [T 0 ! 1408: .nr [A 0 ! 1409: .nr [O 0 ! 1410: .][ 1 journal-article ! 1411: .ds [F 2 ! 1412: .]- ! 1413: .ds [A Luca Cardelli ! 1414: .ds [U cardelli ! 1415: .ds [T Compiling a functional language ! 1416: .ds [I ACM ! 1417: .ds [J 1984 Symp. on LISP and Functional Programming ! 1418: .ds [P 208-217 ! 1419: .nr [P 1 ! 1420: .ds [D 1984 ! 1421: .nr [T 0 ! 1422: .nr [A 0 ! 1423: .nr [O 0 ! 1424: .][ 1 journal-article ! 1425: .ds [F 3 ! 1426: .]- ! 1427: .ds [K milner84 ! 1428: .ds [K SML ! 1429: .ds [U milner ! 1430: .ds [A Robin Milner ! 1431: .ds [T A proposal for Standard ML ! 1432: .ds [I ACM ! 1433: .ds [J ACM Symposium on LISP and Functional Programming ! 1434: .ds [P 184-197 ! 1435: .nr [P 1 ! 1436: .ds [D 1984 ! 1437: .nr [T 0 ! 1438: .nr [A 0 ! 1439: .nr [O 0 ! 1440: .][ 1 journal-article ! 1441: .ds [F 4 ! 1442: .]- ! 1443: .ds [K milner85 ! 1444: .ds [K SML ! 1445: .ds [u milner ! 1446: .ds [A Robin Milner ! 1447: .ds [T The Standard ML Core Language ! 1448: .ds [J Polymorphism ! 1449: .ds [V 2 ! 1450: .ds [N 2 ! 1451: .ds [D October 1985 ! 1452: .nr [T 0 ! 1453: .nr [A 0 ! 1454: .nr [O 0 ! 1455: .][ 1 journal-article ! 1456: .ds [F 5 ! 1457: .]- ! 1458: .ds [A David MacQueen ! 1459: .ds [T Modules for Standard ML ! 1460: .ds [I ACM ! 1461: .ds [J Proc. 1984 ACM Conf. on LISP and Functional Programming ! 1462: .ds [C Austin, Texas ! 1463: .ds [P 198-207 ! 1464: .nr [P 1 ! 1465: .ds [D 1984 ! 1466: .nr [T 0 ! 1467: .nr [A 0 ! 1468: .nr [O 0 ! 1469: .][ 1 journal-article ! 1470: .ds [F 6 ! 1471: .]- ! 1472: .ds [A David MacQueen ! 1473: .ds [T Modules for Standard ML ! 1474: .ds [J Polymorphism ! 1475: .ds [V 2 ! 1476: .ds [N 2 ! 1477: .ds [D October 1985 ! 1478: .nr [T 0 ! 1479: .nr [A 0 ! 1480: .nr [O 0 ! 1481: .][ 1 journal-article ! 1482: .ds [F 7 ! 1483: .]- ! 1484: .ds [A R. Burstall ! 1485: .as [A ", D. MacQueen ! 1486: .as [A ", and D. Sannella ! 1487: .ds [T Hope: an Experimental Applicative Language ! 1488: .ds [D 1980 ! 1489: .ds [P 136-143 ! 1490: .nr [P 1 ! 1491: .ds [K burstall80 ! 1492: .ds [C Stanford ! 1493: .ds [J Proceedings of the 1980 LISP Conference ! 1494: .nr [T 0 ! 1495: .nr [A 0 ! 1496: .nr [O 0 ! 1497: .][ 1 journal-article ! 1498: .ds [F 8 ! 1499: .]- ! 1500: .ds [A David C. J. Matthews ! 1501: .ds [T The Poly manual ! 1502: .ds [J SIGPLAN Notices ! 1503: .ds [D September 1985 ! 1504: .nr [T 0 ! 1505: .nr [A 0 ! 1506: .nr [O 0 ! 1507: .][ 1 journal-article ! 1508: .ds [F 9 ! 1509: .]- ! 1510: .ds [A David C. J. Matthews ! 1511: .ds [T An implementation of Standard ML in Poly ! 1512: .ds [D May 1986 ! 1513: .nr [T 0 ! 1514: .nr [A 0 ! 1515: .nr [O 0 ! 1516: .][ 0 other ! 1517: .ds [F 10 ! 1518: .]- ! 1519: .ds [A G. Cousineau ! 1520: .as [A ", P. L. Curien ! 1521: .as [A ", and M. Mauny ! 1522: .ds [T The Categorical Abstract Machine ! 1523: .ds [P 50-64 ! 1524: .nr [P 1 ! 1525: .ds [I Springer-Verlag ! 1526: .ds [B Functional Programming Languages and Computer Architecture, LNCS Vol 201 ! 1527: .ds [E J. P. Jouannaud ! 1528: .ds [D 1985 ! 1529: .nr [T 0 ! 1530: .nr [A 0 ! 1531: .nr [O 0 ! 1532: .][ 3 article-in-book ! 1533: .ds [F 11 ! 1534: .]- ! 1535: .ds [K milner78 ! 1536: .ds [A Robin Milner ! 1537: .ds [U milner ! 1538: .ds [T A Theory of Type Polymorphism in Programming ! 1539: .ds [J J. CSS ! 1540: .ds [V 17 ! 1541: .ds [P 348-375 ! 1542: .nr [P 1 ! 1543: .ds [D 1978 ! 1544: .nr [T 0 ! 1545: .nr [A 0 ! 1546: .nr [O 0 ! 1547: .][ 1 journal-article ! 1548: .ds [F 12 ! 1549: .]- ! 1550: .ds [A Luca Cardelli ! 1551: .ds [U cardelli ! 1552: .ds [T Basic polymorphic typechecking ! 1553: .ds [J Polymorphism ! 1554: .ds [V 2 ! 1555: .ds [N 1 ! 1556: .ds [D January 1985 ! 1557: .nr [T 0 ! 1558: .nr [A 0 ! 1559: .nr [O 0 ! 1560: .][ 1 journal-article ! 1561: .ds [F 13 ! 1562: .]- ! 1563: .ds [A R. S. Boyer ! 1564: .as [A " and J Moore ! 1565: .ds [U boyer ! 1566: .ds [T The sharing of structure in theorem-proving programs ! 1567: .ds [B Machine Intelligence 7 ! 1568: .ds [E B. Meltzer ! 1569: .ds [E D. Michie ! 1570: .ds [I Edinburgh University Press ! 1571: .ds [D 1972 ! 1572: .nr [T 0 ! 1573: .nr [A 0 ! 1574: .nr [O 0 ! 1575: .][ 3 article-in-book ! 1576: .ds [F 14 ! 1577: .]- ! 1578: .ds [A Luis Damas ! 1579: .ds [T Type Assignment in Programming Languages ! 1580: .ds [R PhD Thesis ! 1581: .ds [I Department of Computer Science, University of Edinburgh ! 1582: .ds [D 1985 ! 1583: .nr [T 0 ! 1584: .nr [A 0 ! 1585: .nr [O 0 ! 1586: .][ 4 tech-report ! 1587: .ds [F 15 ! 1588: .]- ! 1589: .ds [A Robert Harper ! 1590: .as [A ", Robin Milner ! 1591: .as [A ", and Mads Tofte ! 1592: .ds [T A type discipline for program modules ! 1593: .ds [U harper ! 1594: .ds [I Univ. of Edinburgh ! 1595: .ds [R ECS-LFCS-87-28 ! 1596: .ds [D 1987 ! 1597: .nr [T 0 ! 1598: .nr [A 0 ! 1599: .nr [O 0 ! 1600: .][ 4 tech-report ! 1601: .ds [F 16 ! 1602: .]- ! 1603: .ds [A Marianne Baudinet ! 1604: .as [A " and David MacQueen ! 1605: .ds [T Tree Pattern Matching for ML ! 1606: .ds [D 1986 ! 1607: .nr [T 0 ! 1608: .nr [A 0 ! 1609: .nr [O 0 ! 1610: .][ 0 other ! 1611: .ds [F 17 ! 1612: .]- ! 1613: .ds [K steele78 ! 1614: .ds [A Guy L. Steele ! 1615: .ds [T Rabbit: a compiler for Scheme ! 1616: .ds [U steele ! 1617: .ds [I MIT ! 1618: .ds [R AI-TR-474 ! 1619: .ds [D 1978 ! 1620: .nr [T 0 ! 1621: .nr [A 0 ! 1622: .nr [O 0 ! 1623: .][ 4 tech-report ! 1624: .ds [F 18 ! 1625: .]- ! 1626: .ds [A D. Kranz ! 1627: .as [A ", R. Kelsey ! 1628: .as [A ", J. Rees ! 1629: .as [A ", P. Hudak ! 1630: .as [A ", J. Philbin ! 1631: .as [A ", and N. Adams ! 1632: .ds [T ORBIT: An optimizing compiler for Scheme ! 1633: .ds [P 219-233 ! 1634: .nr [P 1 ! 1635: .ds [D July 1986 ! 1636: .ds [J Proc. Sigplan '86 Symp. on Compiler Construction ! 1637: .ds [V 21 (Sigplan Notices) ! 1638: .ds [N 7 ! 1639: .nr [T 0 ! 1640: .nr [A 0 ! 1641: .nr [O 0 ! 1642: .][ 1 journal-article ! 1643: .ds [F 19 ! 1644: .]- ! 1645: .ds [A W. Wulf ! 1646: .as [A ", R. K. Johnsson ! 1647: .as [A ", C. B. Weinstock ! 1648: .as [A ", C. B. Hobbs ! 1649: .as [A ", and C. M. Geschke ! 1650: .ds [T Design of an Optimizing Compiler ! 1651: .ds [I Elsevier North-Holland ! 1652: .ds [C New York ! 1653: .ds [D 1975 ! 1654: .ds [k wulf75 ! 1655: .nr [T 0 ! 1656: .nr [A 0 ! 1657: .nr [O 0 ! 1658: .][ 2 book ! 1659: .ds [F 20 ! 1660: .]- ! 1661: .ds [A Luca Cardelli ! 1662: .ds [U cardelli ! 1663: .ds [T The functional abstract machine ! 1664: .ds [J Polymorphism ! 1665: .ds [V 1 ! 1666: .ds [N 1 ! 1667: .ds [D January 1983 ! 1668: .nr [T 0 ! 1669: .nr [A 0 ! 1670: .nr [O 0 ! 1671: .][ 1 journal-article ! 1672: .ds [F 21 ! 1673: .]- ! 1674: .ds [A R. G. G. Cattell ! 1675: .ds [T Formalization and automatic derivation of code generators ! 1676: .ds [I Carnegie-Mellon University ! 1677: .ds [C Pittsburgh, PA ! 1678: .ds [D April 1978 ! 1679: .ds [K cattell78 ! 1680: .ds [R Ph.D. Thesis ! 1681: .nr [T 0 ! 1682: .nr [A 0 ! 1683: .nr [O 0 ! 1684: .][ 4 tech-report ! 1685: .ds [F 22 ! 1686: .]- ! 1687: .ds [K agt86 ! 1688: .ds [K twig ! 1689: .ds [A A. V. Aho ! 1690: .as [A ", M. Ganapathi ! 1691: .as [A ", and S. W. K. Tjiang ! 1692: .ds [U aho ! 1693: .ds [T Code generation using tree matching and dynamic programming ! 1694: .ds [D 1986 ! 1695: .nr [T 0 ! 1696: .nr [A 0 ! 1697: .nr [O 0 ! 1698: .][ 0 other ! 1699: .ds [F 23 ! 1700: .]- ! 1701: .ds [A D. A. Turner ! 1702: .ds [T Miranda: a non-strict functional language with polymorphic types ! 1703: .ds [I Springer-Verlag ! 1704: .ds [P 1-16 ! 1705: .nr [P 1 ! 1706: .ds [B Functional Programming Languages and Computer Architecture, LNCS Vol 201 ! 1707: .ds [E J. P. Jouannaud ! 1708: .ds [D 1985 ! 1709: .nr [T 0 ! 1710: .nr [A 0 ! 1711: .nr [O 0 ! 1712: .][ 3 article-in-book ! 1713: .ds [F 24 ! 1714: .]- ! 1715: .ds [K lieberman83 ! 1716: .ds [U lieberman ! 1717: .ds [A Henry Lieberman ! 1718: .as [A " and Carl Hewitt ! 1719: .ds [T A real-time garbage collector based on the lifetimes of objects ! 1720: .ds [J Communications of the ACM ! 1721: .ds [I ACM ! 1722: .ds [V 23 ! 1723: .ds [N 6 ! 1724: .ds [P 419-429 ! 1725: .nr [P 1 ! 1726: .ds [D 1983 ! 1727: .nr [T 0 ! 1728: .nr [A 0 ! 1729: .nr [O 0 ! 1730: .][ 1 journal-article ! 1731: .ds [F 25 ! 1732: .]- ! 1733: .ds [U appel ! 1734: .ds [A A. W. Appel ! 1735: .ds [T Garbage collection can be faster than stack allocation ! 1736: .ds [J Information Processing Letters ! 1737: .ds [V (to appear) ! 1738: .ds [D 1987 ! 1739: .nr [T 0 ! 1740: .nr [A 0 ! 1741: .nr [O 0 ! 1742: .][ 1 journal-article ! 1743: .ds [F 26 ! 1744: .]- ! 1745: .ds [U moon ! 1746: .ds [K moon84 ! 1747: .ds [A David A. Moon ! 1748: .ds [T Garbage collection in a large LISP system ! 1749: .ds [D 1984 ! 1750: .ds [P 235-246 ! 1751: .nr [P 1 ! 1752: .ds [I ACM ! 1753: .ds [J ACM Symposium on LISP and Functional Programming ! 1754: .nr [T 0 ! 1755: .nr [A 0 ! 1756: .nr [O 0 ! 1757: .][ 1 journal-article ! 1758: .ds [F 27 ! 1759: .]- ! 1760: .ds [A David Ungar ! 1761: .ds [T Generation scavenging: a non-disruptive high performance storage ! 1762: .as [T " reclamation algorithm ! 1763: .ds [I ACM ! 1764: .ds [J SIGPLAN Notices (Proc. ACM SIGSOFT/SIGPLAN Software Eng. Symp. on Practical Software ! 1765: .as [J " Development Environments) ! 1766: .ds [V 19 ! 1767: .ds [N 5 ! 1768: .ds [D 1984 ! 1769: .ds [P 157-167 ! 1770: .nr [P 1 ! 1771: .nr [T 0 ! 1772: .nr [A 0 ! 1773: .nr [O 0 ! 1774: .][ 1 journal-article ! 1775: .]>
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.