The way DMD generates TypeInfo causes excessive binary bloat so badly that D absolutely cannot be used for resource constrained systems until this problem is solved.
=Evidence=
Create two source files object.d and test.d in the same directory as show below.
// object.d
module object;
alias immutable(char)[] string;
class Object
{ }
class TypeInfo
{ }
class TypeInfo_Class : TypeInfo
{
ubyte[136] ignore;
}
extern(C) void _d_dso_registry(void* data)
{ }
// test.d
module test;
long sys_write(long arg1, in void* arg2, long arg3)
{
long result;
asm
{
mov RAX, 1;
mov RDI, arg1;
mov RSI, arg2;
mov RDX, arg3;
syscall;
}
return result;
}
void write(in string text)
{
sys_write(2, text.ptr, text.length);
}
void write(A...)(in A a)
{
foreach(t; a)
{
write(t);
}
}
final abstract class TestClass1 { }
final abstract class TestClass2 { }
final abstract class TestClass3 { }
final abstract class TestClass4 { }
final abstract class TestClass5 { }
final abstract class TestClass6 { }
final abstract class TestClass7 { }
final abstract class TestClass8 { }
final abstract class TestClass9 { }
extern(C) void main()
{
write("Hello\n");
}
Compile on 64-bit Linux with ( This method compiles without phobos or druntime to obtain the smallest possible binary):
dmd -m64 -defaultlib= -debuglib= -conf= -betterC -release object.d test.d -oftest
Analyze with
objdump -s -j .rodata test
Contents of section .rodata:
4006c0 01000200 00000000 00000000 00000000 ................
4006d0 f0064000 00000000 00000000 00000000 ..@.............
4006e0 6f626a65 63742e4f 626a6563 74000000 object.Object...
4006f0 68126000 00000000 00000000 00000000 h.`.............
400700 20074000 00000000 00000000 00000000 .@.............
400710 6f626a65 63742e54 79706549 6e666f00 object.TypeInfo.
400720 08136000 00000000 00000000 00000000 ..`.............
400730 d8074000 00000000 00000000 00000000 ..@.............
400740 00000000 00000000 00000000 00000000 ................
400750 00000000 00000000 00000000 00000000 ................
400760 00000000 00000000 00000000 00000000 ................
400770 00000000 00000000 00000000 00000000 ................
400780 00000000 00000000 00000000 00000000 ................
400790 00000000 00000000 00000000 00000000 ................
4007a0 00000000 00000000 00000000 00000000 ................
4007b0 00000000 00000000 00000000 00000000 ................
4007c0 00000000 00000000 54797065 496e666f ........TypeInfo
4007d0 5f436c61 73730000 a8136000 00000000 _Class....`.....
4007e0 00084000 00000000 00000000 00000000 ..@.............
4007f0 74657374 2e546573 74436c61 73733100 test.TestClass1.
400800 48146000 00000000 00000000 00000000 H.`.............
400810 30084000 00000000 00000000 00000000 0.@.............
400820 74657374 2e546573 74436c61 73733200 test.TestClass2.
400830 e8146000 00000000 00000000 00000000 ..`.............
400840 60084000 00000000 00000000 00000000 `.@.............
400850 74657374 2e546573 74436c61 73733300 test.TestClass3.
400860 88156000 00000000 00000000 00000000 ..`.............
400870 90084000 00000000 00000000 00000000 ..@.............
400880 74657374 2e546573 74436c61 73733400 test.TestClass4.
400890 28166000 00000000 00000000 00000000 (.`.............
4008a0 c0084000 00000000 00000000 00000000 ..@.............
4008b0 74657374 2e546573 74436c61 73733500 test.TestClass5.
4008c0 c8166000 00000000 00000000 00000000 ..`.............
4008d0 f0084000 00000000 00000000 00000000 ..@.............
4008e0 74657374 2e546573 74436c61 73733600 test.TestClass6.
4008f0 68176000 00000000 00000000 00000000 h.`.............
400900 20094000 00000000 00000000 00000000 .@.............
400910 74657374 2e546573 74436c61 73733700 test.TestClass7.
400920 08186000 00000000 00000000 00000000 ..`.............
400930 50094000 00000000 00000000 00000000 P.@.............
400940 74657374 2e546573 74436c61 73733800 test.TestClass8.
400950 a8186000 00000000 00000000 00000000 ..`.............
400960 80094000 00000000 00000000 00000000 ..@.............
400970 74657374 2e546573 74436c61 73733900 test.TestClass9.
400980 48196000 00000000 48656c6c 6f0a0000 H.`.....Hello...
400990 06000000 00000000 88094000 00000000 ..........@.....
One can see that although TypeInfo is used nowhere in the code, implicitly or explicitly, it is still included in the binary; the TypeInfo.name field being the greatest contributor to the problem.
I have an embedded system with a large number of peripherals all programmable using memory-mapped I/O resulting in 100s of register and more than 1000 bitfields (http://www.st.com/web/en/resource/technical/document/reference_manual/DM00031020.pdf). I have modeled these statically with D's fantastic modelling features to produce an object-oriented, easily-navigable hierarchy of the hardware memory map (example: https://github.com/JinShil/stm32f42_discovery_demo/blob/master/source/stm32f42/gpio.d). It is an excellent model that generates fast code and plays very well with tooling. The only problem is the TypeInfo bloat.
For even a miniscule proof of concept as linked above, the resulting binary is 400KB. If the executable is hacked to remove the TypeInfo data (sometimes works, sometimes doesn't), the resulting binary is only about 5KB; 80 times the size it should be. And it gets worse as more peripherals are added.
Even when compiling with GDC's -fdata-sections and -ffunction-sections and linking with --gc-sections the data cannot be removed due to the way the compiler emits TypeInfo to the binary.
It is intolerable because some binaries are so bloated they cannot be uploaded to the hardware's flash memory, and I therefore cannot continue work on this platform in D.
This issue was also filed under GDC's bug tracker (http://bugzilla.gdcproject.org/show_bug.cgi?id=184), but my investigation has led me to believe it to be intrinsically a DMD issue.
The TypeInfo implementation in DMD is code smell evident by the fact that the compiler checks the size of the TypeInfo declaration in the runtime against the hard-coded TypeInfo generation in DMD, resulting in hacks as demonstrated above in object.d (e.g. ubyte[size] ignore;). DMD is a compiler capable of parsing D code, so there is no reason for the compiler to hard-code the TypeInfo generation when it can read the definition from druntime, and use that.
There have been proposals to add a -nortti flag to the compiler to remove TypeInfo completely, but that would force a compromise on slicing, postblit and other features. Such compromises are most undesirable.
A potential solution is Issue 12270 - Move TypeInfo to the D Runtime. But the emitted object code must be generated in a way that allows unused data and functions to be removed by either --gc-sections or link-time optimization, or better yet, just not generated at all.
Comment #1 by dmitry.olsh — 2015-08-20T06:34:48Z
> I have modeled these statically with D's fantastic modelling features to produce an object-oriented, easily-navigable hierarchy of the hardware memory map (example: https://github.com/JinShil/stm32f42_discovery_demo/blob/master/source/stm32f42/gpio.d). It is an excellent model that generates fast code and plays very well with tooling. The only problem is the TypeInfo bloat.
I'd just go for structs + template mixins for inheritance. It's not like you do any of virtual calls/typeinfo/object factory/whatever.
mixin template Register(blah...){ }
struct Peripheral{
mixin Register!(x,y,z,...);
mixin Register!(q,w,e,...);
}
Makes any sesne?
Comment #2 by slavo5150 — 2015-08-20T06:46:09Z
(In reply to Dmitry Olshansky from comment #1)
> I'd just go for structs + template mixins for inheritance. It's not like you
> do any of virtual calls/typeinfo/object factory/whatever.
>
> mixin template Register(blah...){ }
>
> struct Peripheral{
>
> mixin Register!(x,y,z,...);
> mixin Register!(q,w,e,...);
>
> }
>
> Makes any sesne?
Yes, it makes sense, but it is not my preference, and is beside the point of this issue. Furthermore, with your suggestions, you will still have a new type for each peripheral, and therefore unwanted TypeInfo bloat for each and every one of those peripherals.
The compiler/linker should generate efficient code using my pattern, and I shouldn't have to compromise. Nor should I have to add stubs for runtime features that have no hope of every being used. Rust does a good job with this, and I don't see why I should have to lower my expectations for D.
Comment #3 by dfj1esp02 — 2015-08-20T08:31:52Z
I have an impression that LDC doesn't generate TypeInfo for structs for me (because when it does, linking fails), if I don't use array comparisons and disable opEquals. The Rust argument is valid though :)
Comment #4 by dmitry.olsh — 2015-08-20T08:36:37Z
(In reply to Sobirari Muhomori from comment #3)
> I have an impression that LDC doesn't generate TypeInfo for structs for me
> (because when it does, linking fails), if I don't use array comparisons and
> disable opEquals. The Rust argument is valid though :)
I also was under an impression that only classes do have obligatory type-info everything else is generated on demand.
(In reply to Mike from comment #6)
> (In reply to Sobirari Muhomori from comment #5)
> > And compile with -O2 or -Os
>
> Doesn't work. See
> http://forum.dlang.org/post/[email protected]
Yes, but try without classes.
Comment #8 by dfj1esp02 — 2015-08-20T09:19:18Z
I mean -O0 leaves all junk intact, so at least with LDC -Os is required for successful linking (or maybe it was required before I wrote needed druntime functions? will see). I didn't try to use classes yet (wonder what I would do when I need polymorphism).
> There have been proposals to add a -nortti flag to the compiler to remove TypeInfo completely, but that would force a compromise on slicing, postblit and other features. Such compromises are most undesirable.
There are very few places where dynamic type info is actually necessary, it's just that they were used by the old C style compiler runtime interface.
The remaining places are mostly GC, rt.lifetime, and currently the AA.
I don't think you can properly use D on a small SoC with TypeInfo or ModuleInfo.
Please try the -betterC switch, it's supposed to avoid all runtime dependencies.
Comment #11 by johannespfau — 2015-08-21T11:30:51Z
> There are very few places where dynamic type info is actually necessary, it's > just that they were used by the old C style compiler runtime interface.
Yes, we should probably revisit these cases at some point. I wanted to cleanup my noRTTI (fno-rtti option for gdc) branch for DMD but I thought we won't merge big non-DDMD changes for 2.069 so I postponed that.
I think the most important thing really requiring TypeInfo might be class down casts. I don't see how you can do that without some kind of RTTI. But we could just use some minimal RTTI for that (As long as we can put a unique pointer for every class in the vtable any kind of RTTI should work).
There's also some postblit related stuff where I think the compiler could statically generate the necessary code. It was probably easier to implement it in druntime using RTTI.
Comment #12 by dfj1esp02 — 2015-08-21T12:31:12Z
Downcasts can be done the COM way:
void* TypeId(T)=&TypeId;
final T toType(T)()
{
cast(T)toType(TypeId!T); //reinterpret_cast
}
override void* toType(void* typeId)
{
if(typeId==TypeId!Me)return this;
}
Comment #13 by slavo5150 — 2015-08-21T13:03:05Z
(In reply to Martin Nowak from comment #10)
> I don't think you can properly use D on a small SoC with TypeInfo or
> ModuleInfo.
Yes, it is seldom needed, but the current toolchain implementation is too tightly coupled to it, so it's not so easily removed. See http://forum.dlang.org/post/[email protected] for a relevant discussion.
> Please try the -betterC switch, it's supposed to avoid all runtime
> dependencies.
If you look at the example posted in the initial comment of this issue, you will see that the -betterC switch was used, yet the dead code remains.
Comment #14 by slavo5150 — 2015-11-11T23:14:56Z
I've tested this issue with DMD v2.069 and the results look good. I had to modify my test code. For some reason I now have to add additional stubs for Throwables, but that's a separate issue. Here's the code to get a build.
// object.d
module object;
alias immutable(char)[] string;
class Object
{ }
class TypeInfo
{
bool equals(in void* p1, in void* p2) const
{
return p1 == p2;
}
int compare(in void* p1, in void* p2) const
{
return _xopCmp(p1, p2);
}
}
class TypeInfo_Class : TypeInfo
{
ubyte[136] ignore;
}
alias TypeInfo_Class ClassInfo;
extern (C) Object _d_newclass(const ClassInfo ci)
{
return null;
}
extern(C) void _d_throwc(Object h)
{ }
class Throwable
{ }
class Error : Throwable
{
this(string x)
{ }
}
extern(C) void _d_dso_registry(void* data)
{ }
//test.d
module test;
long sys_write(long arg1, in void* arg2, long arg3)
{
long result;
asm
{
mov RAX, 1;
mov RDI, arg1;
mov RSI, arg2;
mov RDX, arg3;
syscall;
}
return result;
}
void write(in string text)
{
sys_write(2, text.ptr, text.length);
}
void write(A...)(in A a)
{
foreach(t; a)
{
write(t);
}
}
final abstract class TestClass1 { }
final abstract class TestClass2 { }
final abstract class TestClass3 { }
final abstract class TestClass4 { }
final abstract class TestClass5 { }
final abstract class TestClass6 { }
final abstract class TestClass7 { }
final abstract class TestClass8 { }
final abstract class TestClass9 { }
extern(C) void main()
{
write("Hello\n");
}
Compile on 64-bit Linux with ( This method compiles without phobos or druntime to obtain the smallest possible binary):
dmd -m64 -defaultlib= -debuglib= -conf= -betterC -release object.d test.d -oftest
Analyze with
objdump -s -j .rodata test
Contents of section .rodata:
400760 01000200 00000000 00000000 00000000 ................
400770 54797065 496e666f 2e657175 616c7320 TypeInfo.equals
400780 6973206e 6f742069 6d706c65 6d656e74 is not implement
400790 65640054 79706549 6e666f2e 636f6d70 ed.TypeInfo.comp
4007a0 61726520 6973206e 6f742069 6d706c65 are is not imple
4007b0 6d656e74 65640048 656c6c6f 0a000000 mented.Hello....
As you can see the situation is MUCH improved, but there are still these strange messages about TypeInfo.equals and TypeInfo.compare. I think I'll file a separate issue for that.
I'm hesitant to close this issue at the moment because the test case to illustrate the issue uses DMD's backed, which doesn't support my platform (ARM Cortex-M); I'm not sure if the change just swept the issue under the rug in DMD's backend or genuinely addressed the problem. I intend to test if the changes also improved things for LDC and GDC, and will update this issue with more information as I get results.
Comment #15 by slavo5150 — 2016-08-09T13:56:34Z
Just tested this with LDC 1.0.0 (frontend 2.070.2), and the dead code is still in the binary. The DMD generated binary looks good, but the LDC binary is still bloated with dead code. I'm assuming the change made by Walter in Comment 9 only affected the DMD backend.
It appears GDC is still on 2.067, so I haven't tested there, but given my results with LDC 1.0.0, I don't expect GDC to be any better.
Comment #16 by ibuclaw — 2016-08-13T09:17:09Z
(In reply to Mike from comment #15)
> Just tested this with LDC 1.0.0 (frontend 2.070.2), and the dead code is
> still in the binary. The DMD generated binary looks good, but the LDC
> binary is still bloated with dead code. I'm assuming the change made by
> Walter in Comment 9 only affected the DMD backend.
>
> It appears GDC is still on 2.067, so I haven't tested there, but given my
> results with LDC 1.0.0, I don't expect GDC to be any better.
TypeInfo should be better, however ClassInfo is the last on my list to fix due to its complexity.
On my side, all generated layouts for types in object.d will eventually all be moved over to a pick-n-choose way of writing out members. Meaning if you remove one member from TypeInfo - size checking has been thrown out the window - it won't generate any data for it.
Because of ClassInfo being prevalent almost everywhere though, you may not notice side effects just yet. And though it's yet to be tested whether there's any benefit, I'm optimistic. :-)
Comment #17 by slavo5150 — 2017-06-27T23:36:33Z
GDC appears to have solved this problem by wrapping `TypeInfo.name` in a static variable. See https://github.com/D-Programming-GDC/GDC/pull/505#event-1141470083
I tested it on ARM Cortex-M and it works as expected. Perhaps there's something there that the other compilers can leverage.