Tuesday, April 28, 2009

Argument against _argptr

Variadic functions work slightly different in my D.NET implementation than under the native D compiler.

For functions with variable numbers of arguments, the native compiler synthesizes two parameters: _arguments and _argptr; _arguments is an array of TypeInfo objects, and _argptr is a pointer to the beginning of the variable arguments on the stack. The user is supposed to query the type information in _arguments, and do the proper pointer arithmetic to navigate the arguments. You can see some examples at http://www.digitalmars.com/d/2.0/function.html:

void printargs(int x, ...)
{
writefln("%d arguments", _arguments.length);
for (int i = 0; i < _arguments.length; i++)
{ _arguments[i].print();

if (_arguments[i] == typeid(int))
{
int j = *cast(int *)_argptr;
_argptr += int.sizeof;
writefln("\t%d", j);
}
else if (_arguments[i] == typeid(long))
{
long j = *cast(long *)_argptr;
_argptr += long.sizeof;
writefln("\t%d", j);
}
// ...


The pointer arithmetic is not verifiable in managed code. A separate array of type descriptors is not necessary in .net, because the type meta-data can be passed in with the arguments.

In D.NET, the variable arguments are passed as an array of objects. For example, for a D function with the prototype
void fun(...)
the compiler outputs:

.method public void '_D23funFYv' (object[] _arguments)

I handled variadic support slightly differently from the native compiler: I dropped _argptr and provided a new helper function, _argtype, that can be used as demonstrated in this example:

void fun(...)
{
foreach(arg; _arguments)
{
if (_argtype(arg) == typeid(int))
{
int i = arg;
Console.WriteLine("int={0}".sys, i);
}
else if (_argtype(arg) == typeid(string))
{
string s = arg;
Console.WriteLine(s.sys);
}
}
}

If the type of the arguments is known, there is no need to check for the typeid:

void fun(...)
{
foreach(arg; _arguments)
{
int i = arg;
Console.WriteLine(i);
}
}

If an incorrect type is passed in, it is still okay, because the error is detected at runtime.

fun("one", "two", "three"); // int i = arg will throw


The downside of this approach is that it is not compatible with the native code. This does not affect template variadic functions, which should be perfectly portable.

No comments: