Refers:
Part1 - The weekend ‘Scheme’ compiler in C#
The compiler as of now does not do functions with variable arguments. In scheme functions like + can take any number of arguments.
Function Definition and Variables
You right a function – more correctly a lambda in scheme like this (lambda <arguments> <code>) and you bind it to a variable like so
(define foo (lambda <args> <code>))
Effectively foo is a variable that can hold any type – at this point of time it is bound to the lambda. The way I represent this in the compiler is by creating foo to be of type object and then assigning to it a delegate instance which will invoke the function generated corresponding to the lambda.
The equivalent of
object lambda1(<args>)
{
}
delegate object call_lambda1(<args>);
object foo = new call_delegate1(lambda1);
This means that every function call via foo, basically causes a type cast from object to the corresponding delegate type and then an invocation via indirection. This is certainly slower than direct function calls, but this is atleast a close enough mapping to the semantics of scheme.
Function Dispatch
Looking at the above its easy to see that I will need delegates types of various arities and function dispatch will happen accordingly. So when compiling
(foo 1 2)
I know that foo is some value that is of delegate type that will take two arguments. I don’t really have to know the function that its bound, I just need a guarantee of the arity of the function and then I can invoke it.
The above call will get compiled to the following in IL
ldsfld object Program::foo
castclass [SchemeLibs]SchemeLibs.call2
ldc.i4 0x1
box [mscorlib]System.Int32
ldc.i4 0x2
box [mscorlib]System.Int32
callvirt instance object [SchemeLibs]SchemeLibs.call2::Invoke(object, object)
- where call2 is a predefined delegate that takes two parameters.
Problems with variable argument dispatch
The problem with functions with variable arguments is this. When the compiler sees (foo 1 2)
what can it conclude about the arity of the function? With variable arguments, it can only say that foo is a delegate that points to function that requires not more that 2 mandatory arguments.
But that’s not too good in the IL world because the opcodes that you need to call functions with variable arguments are different from the opcodes that take a fixed count of arguments.
In the clr world there are two approaches to passing variable arguments to functions that I am aware of.
params array
Firstly what C# officially does which is
void foo(params object[] args)
{
foreach(object arg in args)
{
}
}
foo(1, 2, 3, 4);
If you look at this in IL the compiler basically creates an array in the caller, fills it with the arguments and then calls the function with an array as the parameter.
arglist
The second approach - unofficially what C# does is the real way of supporting variable arguments at the CIL level using the IL instruction arglist. Here is an example from Vijay Mukhi’s book on IL –
.method public hidebysig static vararg void abc(int32 i) il managed
{
.locals (value class [mscorlib]System.ArgIterator V_0)
ldloca.s V_0
//create the arglist object
arglist
call instance void [mscorlib]System.ArgIterator::.ctor
(value class [mscorlib]System.RuntimeArgumentHandle)
br.s IL_001d
//get one argument at a time
IL_000b: ldloca.s V_0
call instance typedref [mscorlib]System.ArgIterator::GetNextArg()
refanyval [mscorlib]System.Int32
//do something with the value here
ldind.i4
call void [mscorlib]System.Console::WriteLine(int32)
//get the next ones index
IL_001d: ldloca.s V_0
call instance int32 [mscorlib]System.ArgIterator::GetRemainingCount()
ldc.i4.0
bgt.s IL_000b
ret
}
This does have an equivalent in C# via an undocumented (yes!! <insert swear word here>) keyword called __arglist. For more on this take a look at Vijay Mukhi’s discussion on Arrays and Undocumented C# Types and Keywords by Peter Bromberg (a C# MVP).
If you look at both of these, you see that the code generator of the compiler has to be able to look at the call (foo 1 2) and determine what instruction to generate. There is really no way I can determine the runtime type of foo. (Yes there are optimization possible in certain limited cases, but in a general sense no).
This has me thinking that the only way to solve the problem might be to always treat all function calls as calls with variable arguments. Yes, I know that’s bad. So now function dispatch will have to go through the overhead of
- type casting a an object to delegate type
- create a data structure for the variable arguments (for both the params and arglist case)
- call indirectly via a delegate
I am tempted to go with params instead of arglist for implementing variable arguments for a bunch of reasons. Both create some sort of GC managed list structure for extra parameters. Its just that params has a more logical mapping in the C# world – so it would be easier to implement some library functions in C# that take only one argument of the form object[].
If you have a better idea about how to do function dispatch that accommodates for variable arguments, let me know.