Cool D project: Remove cycle detection code from runtime

Steven Schveighoffer via Digitalmars-d digitalmars-d at puremagic.com
Thu Jun 30 08:30:15 PDT 2016


For those who wish to have a deeper understanding of D's runtime and/or 
binary files, there is a project I think would be a nice fun challenge.

Currently, when D builds, it uses the linker to assemble a table of 
ModuleInfo structures, each of which define the pieces of the module 
that the runtime needs to be aware of. One of these things is the static 
constructors and destructors, both shared (run at startup) and unshared 
(run at thread start). Note that this is a simplification, I'm not 
actually sure how the compiler and linker assemble this stuff.

Because we don't have control over the linker, and indeed the compiler 
cannot tell which modules will be included in the final binary, we must 
run an interesting, yet annoying, check every time you run a D 
executable -- topological sorting of the module constructors.

The reason is simple, let's say you have 2 modules:

chicken.d:
import egg;

static int x;

static this()
{
    x = egg.x + 1;
}

egg.d:
import chicken;

static int x;
static this()
{
    x = chicken.x + 1;
}

Which x came first? The answer is easy -- it depends on the order the 
modules are linked (obviously)!

To avoid such a disastrous situation, the runtime can fetch dependency 
data out of the executable (stored by the compiler/linker), and do a 
proper determination of the ordering for the static constructors. If, 
for instance, chicken stopped importing egg, or vice versa, then the 
static constructors would have a well-defined order. If instead there is 
a cyclic dependency, the runtime will halt execution and print an error 
informing you of the cycle.

The solution for detecting cycles is quite interesting (and recently I 
discovered that the cycle detection algorithm was broken, and I've 
re-implemented the cycle detection, this time with a test case [1]), but 
one very annoying piece of this is that the cycle detection has to run 
every time the executable is run, or every time a shared object is 
loaded. This is a sheer waste of computing power -- we are sorting the 
same static data every time we load because we lack the hooks to the 
linker to make it happen at compile time. There's not much we can do to 
improve the linker, but we CAN monkey around with the executable file :)

I propose that we (well, mostly someone other than me) write a tool that 
reads the generated linked code, extracts the relevant module 
information, sorts the data per the same algorithm as defined in 
druntime, then writes the ordering into a reserved index space (for both 
shared and TLS construction), or returns an error if there is a cycle. 
Then the runtime can be modified to detect if this tool has been run and 
avoid doing the cycle detection code. The tool will become part of the 
build normally done by your makefile. Druntime will use a flag written 
by the tool to determine that a pre-sort has been done.

This requires knowledge of executable/object format, knowledge of the 
layout of the module info, knowledge of DMD (you have to properly output 
the space for the reserved spaces), knowledge of druntime's runtime 
intialization, and knowledge of graph algorithms (specifically 
topological sorting and cycle detection).

Any hackers out there want to try this? You get to close an old bug if 
so: https://issues.dlang.org/show_bug.cgi?id=2457

-Steve

[1] https://github.com/dlang/druntime/pull/1602


More information about the Digitalmars-d mailing list