Software constructs that facilitate partial evaluation of source code6199201Abstract A partial evaluator, or pre-compiler, for a computer program enables a user to provide, at suitable places within a program, language constructs which cause certain expressions within the program to be evaluated at runtime or at partial evaluation time. These language constructs can be used to shorten runtime, such as by avoiding unnecessary duplication of code at runtime. Claims What is claimed is: Description FIELD OF THE INVENTION
(define send
(lambda (message-name obj . args)
(let* ((message (find-message obj message-name))
(offset (message-offset message)))
(check-send-args message args) ;check number and type of args
(apply (aref (obj-methods obj) offset) obj args))))
The function of this code is to look up a message descriptor from the message name, find out which position in the method vector holds the method body, check that the arguments therein are of the correct types, grab the method code, and then call the method code. While this code is very simple, and corresponds neatly to the individual steps required to carry out the procedure "send," this code is extremely impractical. The code is basically interpreter code that happens to use compiler-like data structures, in that all of the functions described therein are intended to be executed at runtime. Thus, the code as written above involves several runtime table look-ups, which will take orders of magnitude longer than an equivalent set of code which performs look-ups at partial evaluation time. In particular, with regard to the above code, the following operations could be performed at partial evaluation time: finding the message descriptor, finding its offset in the method table and checking the types of the arguments. All that must be delayed until runtime is the last line of the procedure, the basic object dispatch. It would preferable to modify the code, such as with the software constructs of the present invention, so that the operations commanded by the code could be performed at partial evaluation time where ever possible. Here is the same code as above, incorporating the partial evaluation instructions of the present invention:
(define send
(instantiate per-reference
(lambda (message-name obj . args)
(compute statistically
(let* ((message (find-message obj message-name))
(offset (message-offset message)))
(check-send-args message args) ;check number and type of args
(compute aggressively
(apply (aref (obj-methods obj) offset) obj args)))))))
This is exactly the same code as above, except for the addition of commands corresponding to the software constructs of the present invention. INSTANTIATE PER REFERENCE says this definition of "send" should be grouped once per reference to the defined operation. In other words, assuming a first order language, this definition will be grouped once per call site in the program submitted by the programmer. The effect is thus similar to that of the "inline" programs of several programming languages. The procedure will be compiled once for each group, using whatever information is available at partial evaluation time about the calls in that group. COMPUTE STATICALLY instructs the partial evaluator that the code following it is expected to be executed at partial evaluation time, once per group. A partial evaluation time error will result if that is not possible. Once again, the programmer must be careful to associate this instruction only with an expression he knows will be executable at partial evaluation time; i.e., the programmer must know in advance that enough information will be available at partial evaluation time in order to execute the code to some extent. In the present case, because of the previous INSTANTIATE PER REFERENCE, the actual argument (that is, the fixed variable name) of the argument "message-name" will be known by the time the COMPUTE STATICALLY is reached. Because the variable message-name will be filled in, the three lines of code following COMPUTE STATICALLY in the above example will be executable at partial evaluation time. If for some reason message-name was not known, the compiler would respond with an error message to the programmer. COMPUTE AGGRESSIVELY, in this context, is basically an override of the previous COMPUTE STATICALLY command, saying that the expressions following it are not required to be executed at partial evaluation time. In the present example, this command is essentially reserved for the last line of the code, which as mentioned above is the only line of the code which absolutely must be executed at runtime. The overall effect of using the basic instructions of the present invention is that a programmer is allowed to use the essentially intuitive programming style in the first example of code, but can then make the compiling and execution of the intuitive code more efficient by superimposing the software constructs thereon, thus controlling whether various portions of the code are executed at runtime or partial evaluation time. Basically, if possible, it is almost always more desirable to have code executed at partial evaluation time, if only to avoid the situation in which information that could be exploited at partial evaluation time has to be repeatedly "rediscovered" at runtime. In addition to the basic instructions used by a programmer with the partial evaluator of the present invention, there exists, according to a preferred embodiment of the present invention, constructs which allow interaction between what is known statically and the way a computation is carried out. These additional instructions allow true programmability in terms of static information, and allow the way a result is computed to be adjusted to reflect when certain information is known. In order to provide a fully general way of giving control over the way a result is computed, the instructions allow choice among different computations, depending on how much is known statically. More specifically, according to the present embodiment, there are provided three such conditionalizing instructions: KNOWN-VALUE, STATIC OVERRIDE, and KNOWN-CASE. All three of these "static knowledge" instructions can be used to cause the partial evaluator to return a first sub-expression, if a predetermined quantity of information associated with the instruction is known at partial evaluation time, and causing the partial evaluator to return a second sub-expression, if the predetermined quantity of information associated with the expression is not known at partial evaluation time. The instruction KNOWN-VALUE? is the basic form for determining whether a value associated with an expression is known statically. Although the details of the KNOWN-VALUE instruction will be discussed in detail below, in brief, and with particular reference to the claims, the essential function of the KNOWN-VALUE instruction associated with a particular expression is to cause the partial evaluator to return a "true" flag if the expression can be reduced to a constant at partial evaluation time, and to return a false flag if the expression cannot be reduced to a constant at partial evaluation time. The true or false flag replaces the expression in the partially-evaluated code. KNOWN-VALUE, expressed in context as (known-value? <expr>), returns true if the value of the expression associated therewith is known statically. For example (known-value? 1) (known-value? (+1 2)) (let ((x (+1 2))) (known-value? x)) will all produce #t (true) as residual code. While (known-value? y) (known-value? (+1 y)) will all produce #f (false) as residual code, assuming that the value of y is not known statically. The known-value? construct, itself, is always executed at compile time, since it is querying what is known at compile time. Thus (known-value? (known-value? <expr>)) will produce #t as residual code, no matter what <expr> is. A major use of the known-value? construct is to control inlining and specialization. For example,
(letrec ((f1 (lambda (n acc)
(if (= n 0)
acc
(f (- n 1)(* n acc))))
(f (instantiate per-reference (lambda (n acc)
(if (known-value? n)
((instantiate per-reference f1) n acc)
(f1 n acc))))))
(+ (f y 1)
(f 3 y)))
will produce
(letrec ((f1 (lambda (n acc)
(if (= n 0)
acc
(f1 (- n 1) (* n acc))))))
(+ (f1 y 1)
(* 3(* 2(* 1 y)))))
as residual code, assuming that the value of y is unknown. The known-value? construct ensures that unfolding only happens when the value of n is known. There is an important pattern here, of an instanitiate-per-reference function that will be executed essentially statically to check to see how much is known and then So far, there has be no precise definition of a value being known statically. The exact behavior of the known-value? construct depends, of course, on what it means for a value to be known statically. For simple constants, like numbers, it is clear what is intended, but it is less clear for structured values or for values that are functions. The precise definition is: An expression has a statically known value if the partial evaluator determines, at compile time, a value of the language which can be substituted for the expression without changing the result of the program (except via changing the result of constructions like known-value?). Furthermore, a variable that has been indicated as statically known by known-value? must be consistently treated as statically known. If it refers to an immutable value, then all references to the variables must be treated as static; while if it refers to a mutable value, then all temporally subsequent references must be treated as static. To provide examples of this definition in action, if an expression reduces to a number, it is a known value. A pair that does not support side-effects is a known value if each component is a known value. For example, if the expression make-immutable-pair produces immutable pairs, then (known-value? (make-immutable-pair 1 2)) must produce #t as its residual code. It does not matter if it is not known where the pair was constructed. For example, the known-value? in
(let ((x (make-immutable-pair 1 2))
(z (make-immutable-pair 1 2))
(let ((p (if y x z)))
...
(known-value? p)
...))
is allowed to return true. The answer is not required to return true in this case, since the partial evaluator is not required to realize that the result of the dynamic conditional could be expressed as a static value. But since all references to p could be replaced by a reference to a statically computed (make-im-mutable-pair 1 2), the partial evaluator is allowed to indicate that it made that inference, as long as it is consistent about whether or not p is static. If part of a pair is not used, the definition means that the partial evaluator is allowed to report that the pair as known statically even if that part of the pair is not known. For example, the known-value? in (let ((x (make-immutable-pair 1 y)) . . . (known-value? x) . . . is allowed to return true if the second element of x is never accessed, even if the value of y is not known, because that would mean that all references to x could be replaced by references to a statically computed (make-immutable-pair 1 423), for example. Again, the partial evaluator is not required to make this inference, but it has the option. Another instruction that may be associated with an expression, KNOWN-CASE, causes the partial evaluator to determine if, at partial evaluation time, the value of a test expression resulted from a kind of computation in a predetermined pattern. If so, the partial evaluator will return the value of the true case expression, and, if not, return the value of the false case expression. A KNOWN-CASE instruction is expressed in the form (known-case <expr> (<fun> <var1> . . . <varn>) <true-case-expr> <false-case-expr>). It tests, at partial evaluation time, whether the partial evaluator has simplified the value of <expr> to a call to the function <fun> with the specified number of arguments. If so, then the value of the known-case is the value <true-case-expr>, which is evaluated in a context where <var1> through <varn> are bound to the arguments to <fun>. Otherwise, the value of the known-case is the value of <false-case-expr>. While the KNOWN VALUE and KNOWN CASE constructs are used to find out how much is known statically, STATIC OVERRIDE, expressed in the form of (static-override (<var> <expr>) <body>) and (accept <expr>), is used to inform the system about static information that it might not otherwise know about. For example:
(if (eqv? (car x) 3)
(static-override (car (lambda (pair)
(if (eqv? pair x)
(accept 3))))
((instantiate-per-reference foo) x)))
What is going on here is that the value of the car of x is 3 in the true branch of the if (assuming no side-effects). We would like foo's uses of the car applied to x to be replaced by 3, which is what the static-override accomplishes. The static-override construct takes a variable, an expression, and a body, and runs the body in a modified environment, returning its result. This construct does not bind the variable; it must already be bound. Rather, it gives an alternative (but presumably equivalent) value for it that is appropriate when control is inside the body of the construct. As illustrated in the example, the alternative definition is, in general, conditional. This is achieved by requiring one or more accept constructs to appear in the definition of the alternative value. The override only takes place at an applicable location if the expression for the alternative value can statically reduce to an accept construct, in which case the expression in the accept construct indicates the value to be used. If the expression for the alternative value cannot statically reduce to an accept construct, then the static override has no effect. It is always legal for the partial evaluator to inline the alternative value for a static-override. It is as if the alternative value was wrapped in an instantiate-per-reference construct. Like the standard fluid-let construct of Scheme, the static-override construct respects lexical scope in that it will only affect references to the same lexical variable as the one that it overrides. If foo, for example defined its own local variable named car, references to that variable would not be affected. The partial evaluator is only required to consider the alternative value for those references that it can statically determine as being inside the dynamic extent of the static-override construct. In the above example, all references to car in foo will be considered, since foo is inlined so that its references are statically visible as being inside the dynamic extent of the body. Even closures constructed inside foo are affected, since the alternative version of car is closed over. But if foo calls some other function, which is not inlined or executed statically, then references to car inside that function are not affected (unless the partial evaluator determines that the function is only called from dynamically inside foo). The amount of staticness the partial evaluator is able to infer can affect the applicability of a static-override. To prevent the result of the program from being affected by the amount of staticness inferred, the alternative value given by the construct should be equivalent to the value it overrides, as far as it affects the result that will be computed by the program. Presumably, the alternative value will be superior in some other way, such as performance or staticness. Here is an example that emphasizes the dynamic properties of static-override.
(static override (baz (lambda (z) (accept (bar z))))
(static-override (bar (lambda (x) (accept (foo x))))
(baz y)))
will produce
(foo y)
as residual code. The call to baz gets replaced by a call to bar, which is now visibly inside the dynamic scope of the inner static-override, which means that it gets replaced by a call to foo. In the following discussion, the functionality of the above unique instructions which can be associated by a programmer with expressions, according to the present invention will be described from the perspective of the partial evaluator itself, that is, the below discussion will focus on how the partial evaluator according to the present invention reacts to expressions being associated with particular expressions in a program being evaluated, compiled, and run. As part of this description, there is first provided an overview of the preferred overall structure of "closures" which are used by the partial evaluator; followed by a recursion, which is in effect a flowchart describing the reaction of the partial evaluator to different expressions which are associated with instructions, such as described above. Finally, as an appendix, the recursion is rendered in LISP, which thus forms an embodiment of a partial evaluator which carries out the method of the present invention. OVERVIEW Here is described the basic structure of a partial evaluator embodying the invention for a purely functional subset of Scheme. It takes an expression and a context and returns an equivalent expression, where all static computations have been performed. Actual code for the partial evaluator is also appended, and includes some details left out of this less formal description. It should be consulted in case of any question. The partial evaluator is structured as a purely functional recursive interpreter, which, in addition to the expression, takes an additional context argument, consisting of three components: The lexical environment maps each bound variable to a closure. The override environment maps identifiers to sequences of alternate implementations. The static indicator specifies whether all computation must be done at partial evaluation time. When initially partial evaluating a top level expression, the lexical environment typically contains entries only for pre-defined primitives, the override environment is empty, and the static indicator is false. The partial evaluator actually returns closures, not just expressions. Closures serve several purposes. Some closures fulfill the traditional role of recording a lexical environment. These closures contain lambda expressions that have not been partial evaluated yet, and they record the entire evaluation context. More commonly, closures contain expressions that have been partially evaluated and contain no more references to lexical variables, but may contain references to other closures. This available closure list records all other closures referred to by the expression of the closure that are not available in the context where the closure is currently being processed. This reference list is thus built up as a closure is passed up through lexical contexts. A closure also provides several ways of describing its value. To handle the known-case form, the partial evaluator may have to take apart the computation that gives rise to a value. But in other cases, it will just have to insert a reference to the value. So a closure records both a name for its value, the unique identifier, and an expression for how the value is computed. While both are always available, a closure distinguishes between whether it stands for the actual computation of its value or just a reference to that value. This is recorded in the local expression flag, which is true if the closure stands for the actual computation. Finally, a closure also records other information about its context and history: whether its computation must be done at partial evaluation time, the may not residualize flag, and whether its expression has been authorized to be copied, the per reference flag. To fully partial evaluate an expression, the partial evaluator is invoked in a context consisting of the initial lexical environment and an empty override environment. Then the resulting closure is converted to an expression (see note 3). (The conversion to an expression may invoke more partial evaluation.) OUTLINE OF PARTIAL EVALUATION RECURSION To partial evaluate an expression in a context, dispatch on what type of expression it is: A constant: return a closure containing the constant as its expression, the local expression flag set, and a new unique identifier. A primitive operation: partial evaluate each of the primitive's operands in the context. Return a closure consisting of: If the operands all resulted in closures consisting of constants, the result of the operation on those constants. Otherwise, if the primitive is "equal?" and the operands both evaluated to identical closures, then return #t. Otherwise, return a primitive operation, consisting of the primitive operating on the partially evaluated operands. A lambda expression: Package the expression in a closure, along with the current context. An application: partial evaluate the function and each of the arguments in the context. If the function partial evaluated to a closure with a unique identifier, go through each alternative recorded in the override environment for that unique identifier, and apply the alternative lambda expression to the partially evaluated arguments (see note 4). For the first one that results in a closure over an "accept" expression, if any, return the same closure, but over the body of the accept expression. If no override succeeded, but the function partial evaluated to a closure over a lambda expression, and the closure has a "local expression" or "per-reference" flag, then apply the partially evaluated function to the partially evaluated arguments (see note 2). Otherwise, with shared values consisting of the partially evaluated arguments (see note 1), return a closure over an expression, consisting of the partially evaluated function applied to the partially evaluated arguments. A variable: lookup the variable in the lexical environment. If it is bound, return the closure from the environment. Otherwise return a closure consisting of just the variable. An instantiate-per-reference: partial evaluate the body of the per-reference in the context, and return the resulting closure, but with the "per-reference" added. A compute-statically: partial evaluate the body of the compute-statically in the context with the static indicator set to true, and return the result. A compute-aggressively: partial evaluate the body of the compute-aggressively in the context with the static indicator set to false, and return the result. A known-value?: partial evaluate the body of the compute-statically in the context with the static indicator set to false. If it results a constant, or a closure over a lambda, return a closure consisting of the constant #t. Otherwise return a closure consisting of the constant #f. A known-case: Lookup the name of the function of the case expression in the lexical environment, and note the unique identifier. Partial evaluate the test value with the static indicator set to false. If the result's expression is an application of the unique identifier to the correct number of arguments, return the result of partial evaluating the true case expression, with shared values consisting of the argument closures from the application (see note 1) and with available closures of the result closure (see note 3) in the context with the lexical environment extended with each of the case expression's variables bound to the corresponding argument closure. Otherwise, return the result of partial evaluating the false case expression. A static-override: lookup the variable in the lexical environment. If there is no binding, fail with an error that unbound variables cannot be overridden. Otherwise, partial evaluate the value in the context to get a result closure. Extend the override environment with a binding of the unique identifier from the variable lookup to the result closure, with the "per-reference" flag set. Partial evaluate the body, with available closures consisting of the result closure, (see note 2) in that environment, and return the result. An accept: Partial evaluate the body in the context, return the same closure, but with the expression wrapped in an accept expression. SUBROUTINES Note 1: To do a computation with shared values: Clear the local expression flags and required contexts of the closures and call the computation with the resulting closures. Make the original closures available in the resulting closure. Note 2: to make closures available to a closure: Add all of the closures and any of their required closures that have the local expression flag set to the required closures of the original closure, removing any required closures of the added closures. Note 3: to convert a closure to an expression: Compute an initial expression: If the closure does not have the "local expression" flag and is not per reference use its unique identifier. Otherwise, if its expression is not a lambda expression, use that. Otherwise, take the context out of the closure, set the static indicator to false and extend the lexical environment with bindings for all the arguments of the lambda expression to closures with undefined expressions and new unique identifiers. Partial evaluate the body of the lambda in the new context, and make a lambda expression with the result as its body. Once the initial expression has been computed, convert any of its subcdosures to expressions. Then, for each of the available closures of the original closure whose unique identifiers occur free in the resulting expression, wrap the expression in a let expression binding the identifier to available closure, itself converted to an expression. Note 4: To apply a closure over a lambda to closures of arguments: Partial evaluate the body of the lambda with shared values (see note 1) consisting of the arguments and the available closures of the lambda closure available (see note 2) and in the context with the lexical environment augmented with each of the formal argument variables of the lambda bound to the corresponding argument closure. Below is reproduced, as an appendix, a usable LISP version of the above recursion. While the invention has been described with reference to the structure disclosed, it is not confined to the details set forth, but is intended to cover such modifications or changes as may come within the scope of the following claims.
APPENDIX
;;; Here is an example that uses most of the constructs. It defines
plus
;;; to do addition and be inlined. It then defines an override for plus
;;; that checks to see if the first argument is known to be 0, in which
;;; case, it reduces to just the second argument.
(quote
(fully-pe `(let ((plus (instantiate-per-reference (lambda (x y) (+ x
y)))))
(let ((f (lambda ( )
(plus
(plus 0 z)
(plus 1 z)))))
(static-override (plus (lambda (x y)
(compute-statically
(if (known-value? x)
(if (= x 0)
(accept y)
nil)
nil))))
((instantiate-per-reference f))))))
)
;;; It returns
;;; (LET ((#:G1759 1))
;;; (LET ((#:G1779 (+ #:G1759 Z)))
;;; (+ Z #:G1779)))
;;; which is a name converted version of (+ z (+ 1 z)), where the one
plus
;;; has been simplified, and the rest inlined.
;;; Here is an example that also uses known-case. It defines a
constructor,
;;; pair, and two deconstructors, left and right. It defines overrides
for
;;; the deconstructors that do the deconstruction at pe time if their
;;; argument is known to be a call to the constructor. Then it defines
a
;;; function, f, that takes a pair and adds its pieces. It then defines
an
;;; override for f. The override first defines a two argument function
;;; that does the same thing as f, but takes two arguments, rather than
the
;;; pair. The override then says to use the two argument version in
place
;;; of any call to f where the arguments are known to be a pair.
Finally,
;;; f is called on a pair.
(quote
(fully-pe `(let ((pair (lambda (x y) (cons x y)))
(left (lambda (x) (car x)))
(right (lambda (x) (cdr x))))
(static-override
(left (lambda (x) (known-case x (pair u v)
(accept u)
nil)))
(static-override
(right (lambda (x) (known-case x (pair u v)
(accept v)
nil)))
(let ((f (lambda (x) (+ (left x) (right x)))))
(static-override
(f (let ((two-arg-f (lambda (u v)
((instantiate-per-reference f)
(pair u v)))))
(lambda (x)
(known-case x (pair u v)
(accept (two-arg-f u v))
nil))))
(f (pair 1 2))))))))
)
;;; It returns
;;; (LET ((#:G5499 1))
;;; (LET ((#:G5500 2))
;;; (LET ((#:G5481 (LAMBDA (#:G5546 #:G5547) (+ #:G5546
#:G5547))))
;;; (#:G5481 #:G5499 #:G5500))))
;;; which is a name converted version of
;;; (let ((two-arg-f (lambda (u v) (+ u v))))
;;; (two-arg-f 1 2))
;;; where the call to f has been replaced by a call to two-arg-f, and
the
;;; definition of two-arg-f has its deconstructors eliminated.
;;; --------------------- Language Definition --------------------------
---
(defconstant *keywords* ` (lambda
if
instantiate-per-reference
compute-statically
compute-aggressively
known-value?
known-case
static-override
accept))
;;; Constants
(defun constant-expression? (expression)
(or (numberp expression)
(eq expression *true*)
(eq expression *false*)))
(defconstant *true* t)
(defconstant *false* nil)
;;; Primops are the primitive data operations of the language.
;;; (<primop-operator> . <primop-arguments>)
(defun primop-expression? (expression)
(and (listp expression)
(member (first expression) *primops*)))
(defun make-primop (primop arguments)
(cons primop arguments))
(defun primop-operator (expression) (first expression))
(defun primop-arguments (expression) (rest expression))
(defconstant *primops* ` (+
-
*
/
>
<
=
cons
car
cdr
equal))
(defun apply-primitive (operator arguments)
(apply (ecase operator
(+ #`+)
(- #`-)
(* ##`*)
(/ #`/)
(> #`>)
(< #`<)
(= #`=)
(cons #`cons)
(car #`car)
(cdr #`cdr)
(equal #`equal))
arguments))
;;; Lambdas define functions.
;;; (lambda <lambda-formals> <lambda-body>)
(defun lambda-expression? (expression)
(and (listp expression)
(= (length expression) 3)
(eq (car expression) `lambda)
(listp (second expression))
(every #`symbolp (second expression))))
(defun make-lambda (formals body)
(list `lambda formals body))
(defun lambda-formals (expression) (second expression))
(defun lambda-body (expression) (third expression))
;;; Applications
;;; (<application-function> . <application-arguments>)
(defun application-expression? (expression)
(and (listp expression)
(not (primop-expression? expression))
(not (member (first expression) *keywords*))))
(defun make-application (function arguments)
(cons function arguments))
(defun application-function (expression) (first expression))
(defun application-arguments (expression) (rest expression))
;;; Variables
(defun variable-expression? (expression) (symbolp expression))
;;; Ifs define conditionals
;;; (if <if-test> <if-true-case> <if-false-case>)
(defun if-expression? (expression)
(and (listp expression)
(= (length expression) 4)
(eq (first expression) `if)))
(defun make-if (test true false)
(list `if test true false))
(defun if-test (expression) (second expression))
(defun if-true-case (expression) (third expression))
(defun if-false-case (expression) (fourth expression))
;;; Instantiate-per-reference says that its body may be copied.
;;; (instantiate-per-reference <instantiate-per-reference-body>)
(defun instantiate-per-reference-expression? (expression)
(and (listp expression)
(= (length expression) 2)
(eq (first expression) `instantiate-per-reference)))
(defun make-instantiate-per-reference (body)
(list `instantiate-per-reference body))
(defun instantiate-per-reference-body (expression) (second expression))
;;; Compute-statically says that computations in its body must be done
at
;;; pe time.
;;; (compute-statically> <compute-statically-body)
(defun compute-statically-expression? (expression)
(and (listp expression)
(= (length expression) 2)
(eq (first expression) `compute-statically)))
(defun make-compute-statically (body)
(list `compute-statically body))
(defun compute-statically-body (expression) (second expression))
;;; Compute-aggressively says that computations in its body may be done
at run
;;; time.
;;; (compute-aggressively <compute-aggressively-body>)
(defun compute-aggressively-expression? (expression)
(and (listp expression)
(= (length expression) 2)
(eq (first expression) `compute-agressively)))
(defun make-compute-agressively (body)
(list `compute-agressively body))
(defun compute-agressively-body (expression) (second expression))
;;; Known-value? is true if its body returns a constant or lambda at pe
time.
;;; (known-value? <known-value-body>)
(defun known-value-expression? (expression)
(and (listp expression)
(=(length expression) 2)
(eq (first expression) `known-value?)))
(defun make-known-value (body)
(list `known-value? body))
(defun known-value-body (expression) (second expression))
;;; Known-case takes the true case if the test value is known at pe time
to be
;;; of the form of the function applied to some arguments. Otherwise it
takes
;;; the false case. If the true case is taken, it may reference
arguments of
;;; the aplication via the formals.
;;; (known-case
;;; <known-case-test-value (<known-case-function> . <known-case-
formals>)
;;; <known-case-true-case>
;;; <known-case-false-case>)
(defun known-case-expression? (expression)
(and (listp expression)
(=(length expression) 5)
(eq (first expression) `known-case)
(listp (third expression))
(every #`symbolp (rest (third expression)))))
(defun make-known-case (test function formals true false)
(list `known-case test (cons function formals) true false))
(defun known-case-test-value (expression)
(second expression))
(defun known-case-function (expression)
(first (third expression)))
(defun known-case-formals (expression)
(rest (third expression)))
(defun known-case-true-case (expression)
(fourth expression))
(defun known-case-false-case (expression)
(fifth expression))
;;; Static-override says that applications in the body known at pe time
to be.
;;; applications of the value of variable may be replaced by
applications of
;;; the override value, provided they are known at pe time to result in
an
;;; accept form.
;; (static-override (<static-override-variable> <static-override-
value>)
;;; static-override-body)
|
Same subclass Same class Consider this |
||||||||||
