It was less than a year ago that I wrote about linker hacking the runtime out of D code so that it could work as “better C” code, but things have already changed a lot since then. A few days ago Walter Bright announced a new, improved -betterC switch, which can now do a lot of the stuff that needed ugly hacking before.

What is -betterC, and Why do I Need it?

The short answer is that most D programmers don’t need it. The longer answer is that it does two things: first, it restricts the language to a lower-level subset (that’s still higher-level than C), and, second, it changes the implementation of compiled code a little so that it only depends on the C runtime, and not the D runtime.

If you just want control over things like GC and runtime features for performance reasons, you can already get it without -betterC. You can read more about that in this GC series on the official blog, and in my previous post about the D runtime itself.

What -betterC does provide is an intermediate language that integrates very well with both C code and D code. Walter envisions this as a way for D to penetrate more into parts of the software world that are still dominated by C. For example, practically all languages today still run on top of a layer of operating system libraries that are written in C (and C++ in the Windows world). D’s runtime itself depends on this layer, so D can’t ever replace C unless runtimeless programming is possible.

The -betterC switch is a little controversial, and I agree that things could be better in the future. But -betterC is here today, and ultimately we’re only going to figure out how to use D as a better C by trying it out. That’s why I originally published that post about runtimeless D, even though it was a horrible hack.

What’s New?

There are two main ways betterC programming has improved since I last wrote about it. One pain point was the over-dependence on runtime reflection in the language implementation, even for things like integer array comparisons that could be implemented with just memcmp(). A lot of work has been done since then to replace reflection with templates, which is good news even for programmers who aren’t doing low-level stuff. Lucia Cojocaru presented some of this work at DConf 2017.

The other area of improvement is in the -betterC switch itself. Back then there were only two places in the DMD compiler where -betterC had any effect at all, so most runtime dependencies were left in. Simply defining a struct, for example, would still cause the D compiler to insert TypeInfo instances for runtime type information, which depend on base class implementations that are defined in the D runtime library. assert statements would still be implemented using a D runtime implementation, not the C runtime implementation. These are the two most obvious problems that have been fixed.

-betterC Take Two

In that old post, I took some D code, compiled it, hacked out the runtime, and then linked it directly to some C code without the D runtime. Let’s see how things work now. Here’s the D code again:

module count;

@nogc:
nothrow:

import core.atomic : atomicOp, atomicLoad;

extern(C)
{
        int count()
        {
                scope(exit) counter.addOne();
                return counter.getValue();
        }
}

private:

shared struct AtomicCounter(T)
{
        void addOne() pure
        {
                atomicOp!"+="(_v, 1);
        }

        int getValue() const pure
        {
                return atomicLoad(_v);
        }

        private:
        T _v;
}

unittest
{
        shared test_counter = AtomicCounter!int(42);
        assert (test_counter.getValue() == 42);
        test_counter.addOne();
        assert (test_counter.getValue() == 43);
}

shared counter = AtomicCounter!int(1);

And here’s the C code:

#include <stdio.h>

int count();  // From the D code

int main()
{
    int j;
    for (j = 0; j < 10; j++)
    {
        printf("%d\n", count());
    }
    return 0;
}

Update: this didn’t quite work on an older version of DMD, but it does since 2.079. Leaving this here for the sake of history.

Here’s what happens now (on a GNU/Linux system):


$ dmd --version
DMD64 D Compiler v2.076.0-b2-dirty
Copyright (c) 1999-2017 by Digital Mars written by Walter Bright
$ ls
count.d  program.c
$ dmd -c -betterC count.d 
$ gcc count.o program.c -o program
count.o:(.data.DW.ref.__dmd_personality_v0+0x0): undefined reference to `__dmd_personality_v0'
collect2: error: ld returned 1 exit status

Damn. So close. The D compiler has left in some exception handling data structures, even though -betterC isn’t supposed to support exceptions. You’ll see I’m using the new DMD beta, and there’s already an open bug report and pull request for this kind of problem, so I expect it’ll be fixed soon. I’ll update this post when it is.(Done.)

Here’s a quick workaround for now (read the original linker hacking article for an explanation):


$ objcopy -R .data.DW.ref.__dmd_personality_v0 -R .eh_frame count.o
$ gcc count.o program.c -o program
$ ./program 
1
2
3
4
5
6
7
8
9
10

It might not look like much, but it’s a huge improvement. Thanks to all the developers who helped make it happen.