parrotcode: Parrot Calling Conventions | |
Contents | Documentation |
docs/pdds/pdd03_calling_conventions.pod - Parrot Calling Conventions
This PDD describes Parrot's inter-routine calling conventions.
Please note that the following conventions are only necessary when exposing subs and methods via the generic parrot routine exposure mechanism. What does this mean?
It means the caller needs to follow these conventions only when calling into subs and methods that it has looked up symbolically via parrot's default lookup system. If a language has a lighter-weight calling mechanism that's safe to use in some circumstances, it's perfectly fine to use that.
This means that if you write a C compiler that targets Parrot, for example, you don't need to use parrot's caller-save, full-on calling conventions when one C function calls another if the compiler knows at compile (or possibly link) time what parameters are being passed into the function.
This can potentially save a significant amount of time when dealing with languages that are fully, or nearly fully, bound at compile time, and especially when dealing with languages, such as Forth, that would otherwise spend an inordinate amount of time calling small functions.
If a function isn't exposed at all, it doesn't need to have any way to call into it with the standard calling conventions. It's also perfectly acceptable for there to be two ways to call into a function--one with a language's private calling method, and another that follows the standard conventions.
When this document refers to an "array PMC", used for passing overflow parameters in and out, this means a PMC that can be accessed by integer index and which can return its length. It doesn't need to be of any particular class, it just needs to act like an array. It specifically does not need to be able to extend itself, and may be considered (and actually be) a constant array.
Since Parrot's calling conventions are continuation-based, there is arguably very little difference between a call and a return. Because of this, the registers are set the same regardless of whether code is invoking a subroutine or a return continuation.
Please note that, in the interest of speed, there is a count-free version of sub calling. This mechanism should only be used when calling a static subroutine (never a method) with a fixed-arg-count signature that is known at compile time and guaranteed not to change. It should not be used when writing code for/in dynamic languages.
Parrot uses a fully continuation passing style for control flow. As such there is no 'return' environment as such. The object being invoked is responsible for any saving of registers -- parrot itself makes no guarantees here.
The return continuation PMC type, used to create return continuations used for call/return style programming, guarantees that registers 16-31 for all types (P, N, S, and I) will be set such that the contents of those registers are identical to the content of the registers when the return continuation was created. Note that if a return continuation object is created explicitly, rather than by an invocation op, the preserved registers will be in the state they were at the time the continuation was made, rather than the state they were at the time the continuation was passed into an invocation.
The following registers are used in calling all subs and methods. The registers must be set by the caller, either explicitly or implicitly. (Implicit setting of these registers may be done by ops which document they do so. For example the callcc op notes that it creates and provides a return continuation) Some, but not all, of the registers are passed in to the callee. Parrot does, however, consume some of these values.
Note that registers 16-31 of each of the four types are, for security reasons, never passed into the invoked subroutine, method, or continuation. They are guaranteed to be garbage.
The registers as set up by the caller:
The following registers, with the exception of P registers, are used only when calling a subroutine for which there is a compile-time prototype. The first 11 PMC parameters may be passed in registers P5 through P15.
Overflow parameters go in the array PMC passed in P3. Overflow entries are in there in order, so element 0 is the first overflow parameter, element 1 the second, and so on.
The PMC for a hash, array, or list is passed if one of the entries in the parameter list is a hash, array, or list. The aggregate is not flattened. (Though when accessing the parameters it may be)
Parameters are passed in S, I, and N registers only if the sub's prototype specifically indicates it takes parameters of that type. I0 must be set to 1 if that is the case. If I0 is 0, then the S, I, and N registers can be assumed to be garbage.
Note that it doesn't matter what the order of the integer, string, numeric, and PMC parameters are in the parameter list up until overflow occurs. Once overflow occurs the parameters must be taken in the exact order that they appear in the signature.
The registers of the above set as seen by the callee are:
Note particularly that P0-P2 are not passed. They are consumed by Parrot and placed into the interpreter structure, and may be fetched with the interpinfo op if necessary. Fetching the return continuation may be expensive, and should only be done if truly necessary.
The callee must assume that any unused registers are garbage. This includes registers which could hold a parameter but don't due to lack of parameters. (For example, if only one PMC parameter is passed then PMC registers 6-15 are garbage)
A sub or method can be called in two ways--either prototyped or non-prototyped.
A prototyped call means that the caller has an idea of what parameters the sub takes, and has placed its parameters in the appropriate S, I, N, and P registers. I0 will be true in this case.
An unprototyped call means the caller doesn't know what the sub takes, so has stuffed all its parameters into PMCs, and put those PMCs first in the PMC registers with any overflow in the overflow array. I0 will be false in this case.
The sub being called is responsible for runtime checking the parameter types to see if there is a parameter mismatch problem, if it cares. (Often it doesn't) This isn't a replacement for that sort of parameter type checking. What it is, instead, is a means of allowing shortcutting parameter placement checking for the called sub.
For example, assume we have a subroutine with a signature that looks like:
sub foo(int a, int b, string c, PMC d, float e);
If we were calling without prototyping, all five parameters would be passed in as PMCs, in registers P5 through P9. If, on the other hand, we were calling with prototyping, a would be in I5, b in I6, c in S5, d in P5, and e in N5.
None.
1.4
Maintainer: Dan Sugalski
Class: Internals
PDD Number: 03
Version: 1.4
Status: Developing
Last Modified: 17 November 2003
PDD Format: 1
Language: English
|