|Guy L. Steele Jr.||11||LAMBDA: The Ultimate Declarative|
Who Pops the Return Address?
Earlier we showed a translation of
BAR into "machine language", and noted that there was no code which explicitly popped a return address; the buck was always passed to another function (
H). This may seem surprising at first, but it is in fact a necessary consequence of our view of function calls as "
GOTOs with a message". We will show by induction that only primitive functions not expressible in our language (SCHEME) perform
POPJ; indeed, only this nature of the primitives determines the fact that our language is functionally oriented!
What is the last thing performed by a function? Consider the definition of one:
(DEFINE FUN (LAMBDA (X1 X2 ... XN) <body>))
<body> must be a form in our language. There are several cases:
- Constant, variable, or closure. In this case we actually compiled a
POPJin the case of
FACTabove, but we could view constants, variables, and closures (in general, things which "evaluate trivially"in the sense described in [Steele 76]) as functions of zero arguments if we wished, and so
GOTOa place which would get the value of the constant, variable, or closure into
RESULT. This place would inherit the return address, and so our function need not pop it. Alternatively, we may view constants, etc. as primitives, the same way we regard integer addition as a primitive (note that
CTA2above required a
POPJ, since we had "open-coded" the addition primitive).
(IF <pred> <exp1> <exp2>). In this case the last thing our function does is the last thing
<exp2>does, and so we appeal to this analysis inductively.
(LABELS <defns> <exp>). In this case the last thing our function does is the last thing
<exp>does. This may involve invoking a function defined in the
LABELS, but we can consider them to be separate functions for our purposes here.
- A function call. In this case the function called will inherit the return address.
Since these are all the cases, we must conclude that our function never pops its return address! But it must get popped at some point so that the final value may be returned.
Or must it? If we examine the four cases again and analyze the recursive argument, it becomes clear that the last thing a function that we define in SCHEME eventually does is invoke another function. The functions we define therefore cannot cause a return address to be popped. It is, rather, the primitive, built-in operators of the language which pop return addresses. These primitives cannot be directly expressed in the language itself (or, more accurately, there is some bases set of them which cannot be expressed). It is the constants (which we may temporarily regard as zero-argument functions), the arithmetic operators, and so forth which pop the return address. (One might note that in the compilation of
CURRIED-TRIPLE-ADD above, a
POPJ appeared only at the point the primitive "
+" function was open-coded as