Zero-runtime classes

Posted 2020-07-27

I'll write a bit about how it is easier to use D's classes without druntime than it ever has been before and recent dmd PRs are slated to make it easier and easier.

Core D Development Statistics

In the community

Community announcements

See more at the announce forum.

Zero-runtime classes

D classes are pretty easy to use without druntime, at least in part. Check this out:

1 /*
2 dmd test.d object.d -defaultlib=
3 */
4 
5 extern(C) int printf(const char*, ...);
6 
7 interface I {
8         int omg();
9 }
10 
11 class A : I {
12         int omg() { return 12; }
13 }
14 
15 class B : A {
16         override int omg() { return 34; }
17 }
18 
19 template New(T) {
20         pragma(mangle, "_D" ~ T.mangleof[1..$] ~ "6__initZ")
21         // llvm says it is supposed to be { [2 x i8*]*, i8* }, whatever that is
22         __gshared extern immutable ubyte[__traits(classInstanceSize, T)] initializer;
23 
24         T New(ref ubyte[__traits(classInstanceSize, T)] memory) {
25                 foreach(idx, ref b; memory) {
26                         b = initializer.ptr[idx];
27                 }
28                 //memory[] = initializer[];
29                 return cast(T) memory.ptr;
30         }
31 
32         // T New() { }
33 }
34 
35 extern(C) int main() {
36         ubyte[__traits(classInstanceSize, A)] buffer;
37         A a = New!B(buffer);
38         I i = a;
39         printf("hi %d\n", i.omg());
40         return 0;
41 }

Make a file object.d with just module object; in it too to get the zero runtime build together with the defaultarg= dmd switch. Note this only works on dmd, apparently ldc is stricter about type checks on the extern thing and I don't know exactly what it should be.

But, observe that it compiles and runs! It does virtual overriding just fine. Generates a ~20kb binary on Linux, just like C++ would.

Of course, dynamic casts won't work as they require the use of some runtime type info. You can kinda recreate that data from compile time type info inside a method, but it is awkward anyway. I'd suggest either simply using druntime or better yet, designing your interfaces so you don't need it!

BTW wow object.d upstream has a lot of imports now. It is spreading stuff out I guess. But the holy grail is that those functions are only brought in if specifically needed. Templates do that automatically, but the compiler forces some of this. Note there's change in the works: https://github.com/dlang/dmd/pull/11459

Anyway, note that dmd still outputs the typeinfo block to the object file, but you could strip that out with the linker if you want to slash every byte from the final file since it is unused. The initializer is used though - that's what that ugly extern var is about. But you could perhaps do without even that and just pull out the __vtbl symbol to make your own initializer and zero out the rest (but obviously that means any initialized members also won't work).

My point with all this is that D with a minimal runtime can do a surprising number of things as it is right now, and then you can opt-in for more pieces as you need them. This remains tedious today - it means copy/pasting code (or at least importing select modules from real druntime) - but for specialized design, you can absolutely do it, and a lot easier than it was when I first wrote my old minimal.zip many years ago.

My hope is to see this trend continue such that minimal runtime D is identical to real D and it only takes in what it needs by default.