parrotcode: Untitled | |
Contents | Language Implementations | .Net |
This document discusses .NET method calling and how this is translated to work on Parrot.
The method to call is given as a parameter to the call instructions (apart from calli, which calls through a method reference; here the signature is provided with the instruction). The method to call is specified by a 4-byte integer that points into the meta-data table. Note that if a virtual call is taking place then a method of the same name and signature may be called in place of the specific method the token refers to, depending what is in the v-table.
.NET does support having methods of the same name with different signatures, however the VM does not "support" this at call-time. It is up to the compiler to resolve which method to call and put the correct meta-data reference with the call.
Parrot also provides a multi-method dispatch mechanism. This is used to call methods with the same name but varying signatures. As Parrot supports dynamic languages, the method that will be called can not be determined at compile time and may change throughout the lifetime of the program as new methods appear and inheritance hierachies change. A cache is used to aid performance. In fact, in run cores that are capable of it the instruction stream is modified at runtime to just have a call to the method that the dispatch algorithm found, so the cost of the dynamic dispatch is amortised. This technique is known as a Polymorhpic Inline Cache (PIC).
Assuming that $P1 and $I2 contain the parameters to be passed and $I0 is to hold the return value, then a call to the method "factorial" in the class "Test" in the namespace "Testing" will translate to Parrot instructions as shown below.
$P1000000 = find_global "Testing.Test", "factorial"
$I0 = $P1000000($P1, $I2)
$I0 = $P1."foo"($I2)
A number of options exist to solve this. Name mangling the subs and then using the signature to generate the mangled name when translating the call would work. This avoids Parrot's MMD completely, meaning it is cheaper at runtime and that the intended method is always called. However, this really hurts interoperability with other languages.
Another option is to wrap up anything other than a native integer or double into a PMC type at call time - essentially boxing it - and then unboxing it inside the call. This allows the MMD system in Parrot to be used, avoids name mangling the methods so other languages can still see and call them as desired but makes calling more costly.
Since the goal of the project is interoperability rather than performance, the second option makes more sense. It is implemented by declaring a number of classes that derive either from Parrot's built in Integer or Float PMCs and named "@@DOTNET_MMDBOX_I1" for the single byte signed integer, etc. When translating a calling related instruction, code must be emitted to place any types that must be boxed for MMD purposes into the appropriate box type. All methods are annotated with ":multi(...)" directives using name of the boxed types where appropriate. Note that there is no need for explicit unboxing on the callee side; if an integer register is declared but a PMC is passed, then the get_integer v-table method will be called on that PMC automatically.
|