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.