The cost of doing compile time introspection

Moritz Maxeiner via Digitalmars-d digitalmars-d at puremagic.com
Wed May 10 04:45:05 PDT 2017


So, after being asked to support dynamic loading in llvm-d[1] at 
DConf 2017 I essentially had the following two options:
- have two separate declarations of the bindings, one with the 
function signatures (linking) and one with function pointers 
(loading)
- Have one declaration and derive the other at compile time

Since this is D and I was reminded of Andrei's keynote I thought 
to myself "use the Compiler, Dummy" and went about 
introspecting[2] the function signatures[3]. The relevant code 
looks like this:

---
import functions = llvm.functions.link;

template isCFunction(alias scope_, string member)
{
     static if (isFunction!(__traits(getMember, scope_, member)) &&
                (functionLinkage!(__traits(getMember, scope_, 
member)) == "C" ||
                 functionLinkage!(__traits(getMember, scope_, 
member)) == "Windows")) {
         enum isCFunction = true;
     } else {
         enum isCFunction = false;
     }
}

template CFunctions(alias mod)
{
     alias isCFunction(string member) = .isCFunction!(mod, member);
     alias CFunctions = Filter!(isCFunction, __traits(allMembers, 
mod));
}

string declareStubs()
{
     import std.array : appender;
     auto code = appender!string;
     foreach (fn; CFunctions!functions) {
         code.put("typeof(functions."); code.put(fn);
         code.put(")* "); code.put(fn); code.put(";\n");
     }
     return code.data;
}

mixin (declareStubs);
---

Now, the above code essentially searches through all symbols in 
the llvm.functions.link module, filters out anything that's not 
an extern(System) function and then generates code declaring a 
function pointer for each of them, and then finally mixes that 
code in.

This increases compilation time on my Broadwell i5-5200U from 
faster than my terminal emulator can print to 4 seconds. Yikes. 
 From experience I knew that it wouldn't be cheap, but that's a 
hefty cost for a conceptually simple use case (from my PoV).

You might ask why I need to use CTFE here (as the interpreter is 
not cheap): It's because I want the mixed-in function pointers to 
be at module scope and I'm not aware of any other way to do that 
currently; if the new static foreach works the way I suspect, 
then that part could essentially be replaced (at module scope) 
with something like

---
static foreach (fn; CFunctions!functions) {
     typeof(functions.fn)* __traits(identifier, fn);
}
---

Looks much nicer, but will it be fast? I don't know, but I hope 
so, since it shouldn't need to spin up CTFE.

TLDR: Compile time introspection still increases compile time 
from below human response time (i.e. without encouraging context 
switching in your brain) to breaking your concentration even for 
simple use cases.

[1] https://github.com/Calrama/llvm-d
[2] 
https://github.com/Calrama/llvm-d/blob/master/source/llvm/functions/load.d
[3] 
https://github.com/Calrama/llvm-d/blob/master/source/llvm/functions/link.d


More information about the Digitalmars-d mailing list