Capability to get children compile-time info. #775
Conversation
foreach(i; __traits(allMembers, T)) | ||
static if (i == "_RTInfo") | ||
return true; | ||
return false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you do that without the looping? I'm a bit worried about the compile time impact because _RTInfo is instantiated for every type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, i could not get it right without the loop. The main reason for the loop is not to be tricked with opDispatch. I think that such things (_RTInfo) should be explicitly defined and not with opDispatch, otherwise strange things could implicitly happen. This is arguable though.
As to performance, i didn't notice any difference on my core i7 2.9Ghz/16GB ram macbook, but of course i didn't make any scientific research on that, sorry.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about __traits hasMember? Agreed upon the opDispatch thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
__traits hasMember is fooled miserably by opDispatch =(
Even worse, if using __traits hasMember as a precondition before the loop, it will instantiate this template. This is a tricky use-case, i stumbled upon in my objective-c bridge. I can provide more info on that, if needed.
I like the idea very much. A small newsgroup discussion would be nice. |
@@ -577,6 +577,15 @@ void __ctfeWriteln(T...)(auto ref T values) { __ctfeWrite(values, "\n"); } | |||
|
|||
template RTInfo(T) | |||
{ | |||
static if ({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please also update object_.d.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. However, i don't quite understand why it is needed in object_.d.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because object.di is just the header file to object_.d, but as we're currently not able to generate the header automatically we need to maintain both. Another reason is that unit tests only run on object_.d.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In such case, why does object_.d declare RTInfo(T).RTInfo as
enum RTInfo = null;
while object.di declares it as
enum RTInfo = cast(void*)0x12345678;
?
Well, i've started a humble thread here, though it has not drawn too much attention yet. |
Static this registering is nice already, but I wonder if it's also possible to inject this into TypeInfo somehow. |
I don't like this solution, see newsgroup discussion. |
Can you work with @jacob-carlborg to use a UDA attribute on the type instead of a nested template? Then this might also work for enums. |
Ok, i've played a bit with UDA, and here is what i've got now. struct MyCustomInfo(T) // or alias T
{
static this()
{
writeln("Registering runtime info for ", T.stringof);
}
}
@rtInfo!MyCustomInfo class RootOfSomeRuntimeRegisteringHierarchy
{
}
class A : RootOfSomeRuntimeRegisteringHierarchy
{
} Will output:
What do you think? |
I was thinking something like this: @rtInfo template MyCustomInfo (T)
{
// inspect T
} I want to be able to inspect third party types I don't have any control over. |
As I've said already, we cannot inject code into foreign modules, because it conflicts with separate compilation. What you can do is import foreign modules and inspect the hell out of them. |
template RTInfo(T) | ||
{ | ||
template _instanciateRTInfoFromAttributes(Attrs...) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's "instantiate" :).
Actually, I'm currently trying to develop something that might suite all of us. What if we mix the two approaches of Jacob and mine in the following way:
This could possibly work, and cover much more use-cases than just children CT-info, and still remain consistent. There's still a small question regarding cyclic imports, but i guess, ill have an answer soon. |
{ | ||
static if (Attrs.length == 0) | ||
alias _instanciateRTInfoFromAttributes = _Tuple!(); | ||
else static if (is(Attrs[0] == rtInfo!(U), alias U)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should work with multiple rtInfos as in @rtInfo!(Reg1, Reg2)
.
OK, I'm curious. |
Ok, i've got some results, but a bit shy to show them, and that's probably not the right way to go. Here's what i've done:
The whole thing works, with a few buts.
The way to go, IMHO:
In other words, do everything i've done experimenting, but with compiler's help. This way a module can control it's "children" just like a superclass can control it's subclasses in my PR here. There will be no link-time ambiguity. If a module wants to generate something for the modules it imports, it already may do so with existing language features. I still have nothing to say about rtInfo pointer, maybe that's because i still can't fully understand the reason to have any "default" runtime at all, except for cpp-ish dynamic_casts. So what do you think guys? |
Yeah, this is roughly what we talked about in dlang/dmd#2271. I'm still a bit worried about the performance implications of such a feature. alias getRTInfoTemplates(alias mod) = TypeTuple!(
filterRTInfoTemplates!(__traits(allMembers, mod)),
staticMap!(getRTInfoTemplates, importedModules!mod))
); The compiler will cache templates with identical arguments and can find them in O(1) (hashtable lookup). Now you'd only need to instatiate all of the relevant templates in rtInfo. alias instantiateTemplates(T) = TypeTuple!();
alias instantiateTemplates(T, Tmpls...) = TypeTuple!(staticMap!(Tmpls[0], T), instantiateTemplates!(T, Tmpls[1 .. $]));
alias rtInfo(T) = instantiateTemplates!(T, getRTInfoTemplates!(moduleOf!T)); |
Why this limitation? It also makes it more complicated, at least do document and explain. Why not just instantiate all Basically the only case your approach doesn't work for is when passing at least two files two the compiler and they don't import each other. Another idea is to skip [ |
Well that depends on how you put it. If we're talking about a language that is compiled and linked, your way would not work consistently if we're compiling/linking partially. Second there's dependencies. Now we know that every module B that imports module A depends on module A, meaning that changing A would require rebuilding B, otherwise linking with "outdated" B object leads to undefined result. Also there is semantic reason. Instantiating customs on everything leads to totally uncontrolled environment. All of your subsystems automatically become dependent on everything you link with, and you'll have a hard time tracking who does custom RTInfo over your own code. Even with my way_to_go the responsibility of writing an @rtInfo marked template is enormous. This feature is extremely powerful, and should be used with great care. I clearly imagine that this feature will be banned in commercial projects on D, just like excessive use of preprocessor or Alexandrescu-style templates in cpp. |
I'm not sure that's the case. As long as the module containing the But I guess explicitly importing the module might be safer and not that big of limitation that I first thought.
We already have that with the current So if I understand you correctly: Instantiate all What should we do about the current BTW, we do need a way to instantiate a template with all types (not just user defined types) seen by the compiler to be able to build a precise garbage collector. This is the reason |
No we don't, since RTInfo cannot be custom now.
I would say this way: Instantiate all @rtInfo templates that are imported, one way or another, with all types defined in currently-compiled module.
I'm currently trying to use it as an entry point for @rtInfo customs instantiations, but i'm not really successful in it. I followed @MartinNowak's advise to cache per-modules infos and it worked, and now we need just 50 seconds to build phobos, and 8GB ram would be enough, but i need somehow to crank it down to normal values, and also i could not solve the final binary size issue. So returning to RTInfo, i think it should be either as it is used now - the root for all custom rtinfos, or it may be removed completely, if i do the job with compiler's help, or somewhere in the middle.
And such possibility will be available, if GC's module that defines it's @rtInfo will be included somewhere in the core. |
So that means if I only compile module A which imports module B, only the types in A will be instantiate, not the ones in B?
That doesn't sound possible with what you just said above. Or I'm misunderstanding. |
Please note that RTInfo isn't generated reliably at the moment. I've been trying to get fixes merged for ages: dlang/dmd#2480 |
That's right. Types in B will be instantiated when compiling B. Isn't it the way current RTInfo works?
If you'll have and @rtInfo imported somewhere on early stages (core?), it will be instantiated pretty much on everything you compile. |
Almost, but not completely. I tried to summarize it here: https://github.com/D-Programming-Language/dmd/pull/2480/files#diff-3344a3f04733c74c9601b00e0b399ca0R10 |
@rainers, so do you think it's worth waiting for your PR to be merged, before continuing on custom rtInfos? Do you know, when it will be merged btw? =) Or do you think we should merge my current PR meanwhile? |
Just in case anyone interested in my progress on custom @rtInfos, here's the code with all the problems described above: |
Hmm, I think I need to actually check how RTinfo works.
There might be some confusion here. When I say "compiled", I'm referring to the file you actually pass to the compiler on the command line, not a file which is imported. |
As long as you don't hit problems with missing RTInfo, it will probably not affect your implemetation.
Probably not as long as Walter or Kenji hit the same problems ;-( (Kenji did a bit in the same direction fixing opCmp/opEqual members in the type info) I stopped pleading for review some time ago.
I'm a little uneasy about it. Some questions:
|
I'm not sure how to test that properly. Building phobos on my macbook 2.9GHz, 16GB ram:
With my changes:
Sorry, didn't understand that. I'm not looking up for members anywhere. Could you clarify your question pls?
Of course i will, but as far as i understand, the tests on this feature should be added to DMD, not runtime, right? |
Tests for instantiating rtInfo belong to dmd, tests for rtInfo itself belong to druntime. |
Hm, can't find a good spot to insert the unittest. When i add a test to object_.d, it would not link, saying the following:
static this is what's causing link error. Here's the test i'm trying to add: version(unittest)
{
static string[] registeredClasses;
struct CustomRTInfo(T)
{
static assert(is(T == A) || is(T == B));
static this()
{
registeredClasses ~= T.stringof;
}
}
@rtInfo!CustomRTInfo class A { }
class B : A { }
}
unittest
{
// @rtInfo test
assert(registeredClasses.length == 2);
assert(registeredClasses[0] == "A" || registeredClasses[1] == "A");
assert(registeredClasses[0] == "B" || registeredClasses[1] == "B");
} |
@yglukhov What's the status of this? |
ping @yglukhov |
Ok, I've added a test to |
@@ -600,8 +600,53 @@ bool _xopCmp(in void* ptr, in void* ptr); | |||
void __ctfeWrite(T...)(auto ref T) {} | |||
void __ctfeWriteln(T...)(auto ref T values) { __ctfeWrite(values, "\n"); } | |||
|
|||
private alias _Tuple(Args...) = Args; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use core.internal.traits.TypeTuple
instead.
Updated, rebased. |
Will close this for now as |
The point of the PR is to inspect subclasses in compile-time. Please, see the first comment in this PR. |
It's a huge machinery for this purpose and will probably slow down any compilation. |
Even better, you can use a base class constructor with a TemplateThisParameter to achieve what you want. http://dpaste.dzfl.pl/b66b4c18d582 |
@MartinNowak the downside to that is that it only works for one level of inheritance. So this only prints |
This commit adds a feature of compile-time introspection of aggregate types' "children".
E.g.
Will output:
This will need some documentation if going to be merged. I'll write it as soon as all other questions are settled.