Tuesday, April 14, 2009

Static Constructors in D.NET

The D programming language features static constructors that are similar to class constructors in C#: they are called automatically to initialize the static fields (shared data) of a class.

At the IL level, static constructors are implemented by the special .cctor methods. The experimental compiler for D that I am working on in my virtual garage groups together user-written static constructor code with static field initializers into .cctors (and I believe that the C# compiler does the same).

class Example {
static int solution = 42; // assignment is moved inside .cctor
static double pi;

static this() { // explicit static ctor ==> .cctor
pi = 3.14159;
}
}
The code above produces the same IL as:

class Example {
static int solution = 42;
static double pi = 3.14159;
}

Also, the compiler synthesizes one class per module to group all "free-standing"global variables (if any).

For example, the IL code that is generated out of this D source

static int x = 42;

void main() {
writefln(x);
}

is equivalent to the code generated for:

class ModuleData {
static int x = 42;
}
void main() {
writefln(ModuleData.x);
}

only that in the first case the ModuleData class is implicit and not accessible from D code. This strategy allows for the initializers of global variables to be moved inside the .cctor of the hidden module data class.

IL guarantees that the class constructors are invoked right before any static fields are first referenced. If the compiler flags the class with the beforefieldinit attribute, then the class constructors are called even earlier, i.e. right when the class is referenced, even if no members of the class are ever accessed (the C# compiler sets the beforefieldinit attribute on classes without explicit class constructors).

Serge Lidin explains all the mechanics in his great book Expert .NET 2.0 IL Assembler, and recommends avoiding beforefieldinit, on grounds that invoking a .cctor is a slow business. I am considering using it though, on the synthesized module data class.

In conjunction with a compiler-generated reference to the module data class, the beforefieldinit attribute will guarantee that the global variables are initialized on the main thread, and will avoid strange race conditions and bugs.

2 comments:

BCSd said...

Whatever you do make shure this works the same on both sides:

module A;

char[][char[]] map;
void main()
{
foreach(k,v;map)
writef("%s -> %s\n", k,v);
}

module B;
import A;
static this()
{
map["hello"] = "world";
}

Cristache said...

Hi BCSd, you are making a good point, which is that in addition to class static ctors and dtors, D also has module static tors. The mechanics involved in supporting them in the .NET backend is slightly different from what I described in this post, and perhaps deserves a full-size blog entry.

Your example does not compile under D 2.0, because const string literals are not implicitly-convertible to char[] -- you should use "string" instead.

My D on .NET project focuses entirely on the 2.0 version of the language.