languages such as FORTRAN with little global optimization by the compiler, often no registers need be saved across a procedure call! Thus this convention can only lead to unnecessary extra running time, which gets charged to the poor procedure call. (This convention does have the virtue of helping to isolate the effects of buggy compiler output; but this feature is not without cost.)
The great speed of compiled MacLISP procedure calls is primarily due to its taking the inverse approach: the caller is responsible for saving any registers that it will need after calling another procedure. It might be thought that this would lead to a much greater code size than the other convention, but this is offset by three effects. One is that, as noted above, few registers actually need to be saved in practice. Another is that the compiler can know which registers are not destroyed by built-in functions and avoid saving such registers unnecessarily. (This can be compared with knowing which registers are used by the out-of-line "intrinsic" functions in a FORTRAN implementation; or, for that matter, knowing that certain instructions clobber certain registers, such as DIVIDE
producing both a quotient and a remainder.) The third is that the architecture of the PDP-10, while not essentially stack-oriented, does not make references to stack values unduly difficult; thus it is often just as easy to keep a variable on the stack rather than in a register in the first place.
At the source-language level, there are other factors which contribute to the procedure call's poor reputation. Nearly all algebraic computer languages draw a syntactic distinction between operators and user functions, if not also a semantic distinction. Often built-in functions are also distinguished in some silly way from user functions, even though they are used in syntactically similar ways. As an example, you cannot pass "+
" as an external function argument in FORTRAN, even though it is mathematically a perfectly good function of two arguments; similarly you cannot pass a statement function, even though there is no syntactic difficulty as there is for "+
". [ANS76,8.7/15.4.3] You can pass an intrinsic function as an argument, unless it is one of the MIN
/MAX
series. [ANS76,8.8/15.3.2] PL/I built-in functions can return array or structure values, but not user-defined functions. [IBM70b,162] Even as enlightened a language as APL does not permit, in current implementations, a user function to be used within the reduction or inner/outer product constructions. Such decisions are occasionally questioned, but most people accept them on the grounds of "efficiency". This is absurd. There is no reason one cannot accept the general case, and handle important special cases specially. For example, if a user should try to pass a statement function or intrinsic function as an argument in FORTRAN, the compiler could jolly well provide a reference to an EXTERNAL version of that routine, while continuing to use the internal version (if it is in fact compiled as a distinct version) where applicable.
Even if we accept such arbitrary semantic distinctions in our languages, there remain the syntactic differences. Most languages require user functions to be referenced in a rather awkward manner, and subroutines (value-less procedures) in even more awkward ways. FORTRAN requires subroutines to be invoked using the keyword "CALL
". COBOL is even worse: it uses the longer keyword "PERFORM
" for internal subroutines, and two keywords "CALL
... USING
" for external subroutines. [IBM70a] Moreover, for many years it took three statements to call an external procedure:
ENTER LINKAGE.
CALL FOO USING ARG1 ARG2 ARG3.
ENTER COBOL.