Sunday, December 21, 2008

Hello .NET, D Here Calling

A piece of advice from someone who spent fifteen years writing software professionally: if some "experts" ever say "printf debugging" is a poor technique, tell them to get out of town.

Printf debugging is helpful in a many great deal of situations, for example when you are writing a compiler. The debugger cannot be trusted, because the work-in-progress compiler may not output complete debug information just yet. But you can trust what's printed white on black on the screen.

There is a chicken and egg problem with printf though: how does one compile the implementation of printf (or writefln, as it is the case with the D programming language) if the compiler itself is not there?

Luckily, in my implementation of D for .NET (a project that I plan to release under the BSD in 2009) I was able to completely circumvent the problem. See, .NET already has a rich set of libraries and services. As a matter of fact, the very purpose of D.NET is to enable D programmers to take advantage of this state-of-the-art computing platform.

The obvious choice is to use one of the System.Console.WriteLine overloads instead of writefln. In order to do that, the front-end needs to know about System.Console. The good news is that Walter Bright's implementation of the D compiler front-end (which I am using) already handles imports.

In order to get this code to work

import System;

void main() {
System.Console.WriteLine("hello D.NET");
}

I wrote a System.d file, containing the D version of the Console class declaration (not complete, but good enough to get me going):

public class Console
{
static public void WriteLine();
static public void WriteLine(string);
static public void WriteLine(string, ...);

static public void WriteLine(char);
static public void WriteLine(bool);
static public void WriteLine(int);
static public void WriteLine(uint);
static public void WriteLine(long);
static public void WriteLine(float);
static public void WriteLine(double);

static public void Write(char);
static public void Write(string);
static public void Write(string, ...);

static public void Write(bool);
static public void Write(int);
static public void Write(long);
static public void Write(float);
static public void Write(double);
}

In the future, I plan to write a program that produces this kind of declaration automatically from .NET assemblies, using reflection.

Okay, so at this point the front-end compiles the "hello D.NET" code happily, but ILASM cannot resolve the System.Console::WriteLine symbol. This is because in IL the class names need to be qualified by assembly name, so what ILASM expects is a statement like:

call void class [mscorlib]System.Console::WriteLine(string)

One of my design guide lines for this project is to modify the front-end as least as possible, if at all. So then how do I get the imports to be fully qualified by assembly names?

With a clever hack, of course. I added these lines at the top of System.d:

class mscorlib { }
class assembly : mscorlib { }

Then I tweaked my back-end to recognize the "class assembly" construct and prefix the imported module names with whatever the name of the base class of the assembly class is.

4 comments:

Anonymous said...

You should better use pragma for hacks.

Cristache said...

pragma hacks would require changing the front-end (which I am trying to avoid). Also, the hack described in my blog does not modify the language. Maybe this example helps driving my point better: if one day I decide to have the backend generate unsafe code, I will need to do something like:
#pragma unsafe
{
}
#pragma safe

since there is no other way with the existing features in the language.

Anonymous said...

Hey, if you need to get the declaration for a type like that, you can use the monop tool that Mono ships with.

Here's how you can use the monop2 tool that ships with Mono (I use SVN trunk, so I'm not sure what the exact invocation is for other versions, but it should be the same):

monop2 -r /opt/mono/lib/mono/gac/System/2.0.5.0__7cec85d7bea7798e/System.dll System.Uri

There's probably a better way to get the assembly path, but that's the best I could come up with. Hope you can get some use from it!

Cristache said...

Thanks Bojan, this looks promising.

Speaking of mono, I noticed that ILASM2 does not handle .typedef nor #define statements, do you know if they fixed this issue in the SVN trunk?