-
-
Notifications
You must be signed in to change notification settings - Fork 606
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix 23103 - static initialization of associative arrays is not implemented #15468
Conversation
Thanks for your pull request and interest in making D better, @dkorpel! We are looking forward to reviewing it, and you should be hearing from a maintainer soon.
Please see CONTRIBUTING.md for more information. If you have addressed all reviews or aren't sure how to proceed, don't hesitate to ping us with a simple comment. Bugzilla references
Testing this PR locallyIf you don't have a local development environment setup, you can use Digger to test this PR: dub run digger -- build "master + dmd#15468" |
Oh nice! I will take a look at how this is being done, probably the aaAsHash function can be streamlined -- the newaa thing doesn't have to actually work as a normal type, it just has to initialize the hash table. One of the bigger things -- the And it doesn't look like you are calling the asAA function (which does this). |
I didn't know |
@dkorpel yes, this is nice and works. I got confused thinking that the AA requires extra representation for to_dt. |
Looking for usages of entryTI:
Seems we could remove the second usage, so it just becomes a parameter to the first usage. In answer to the question, to test, you could build an AA statically with values that have destructors, then add an element, remove it, and see if the GC calls the destructor later (it obviously won't call dtors on statically constructed elements) |
newaa -> druntime is the right move. Will take a look and hack on this on Monday when I'm back from holiday. If the basic idea is sound it shouldn't be super difficult to get the nested tables and so on working. It's also a good idea to migrate away from a blob of hooks to libraries that happen to be in druntime - currently the amount of crap we import on every compilation is just huge, compartmentalization is a good antidote. |
I would say, the newaa part that initializes the AA should be moved to druntime. We don't need lookups, etc. We aren't going to use this thing other than to do static initializations. Note that before this is included, we need a unittest (one which I intended to write) that validates the layout is identical for both real AA and the |
I don't see why not use it everywhere ideally? The existing implementation is a mess, quite unmalleable etc so why not use some more modern practices. It's not uncommon for a syntax such as this to desugar to a library type - we don't have to expose that to the user directly (although maybe we should). In short it would pay off some technical debt for once. |
That and having two implementations does beg the question of how to truly test they are the same. |
d871c0a
to
a107f99
Compare
5c2a43d
to
22aa673
Compare
compiler/src/dmd/semantic2.d
Outdated
id = new DotIdExp(exp.loc, id, Id.object); | ||
id = new DotIdExp(exp.loc, id, Id._aaAsStruct); | ||
auto arguments = new Expressions(); | ||
arguments.push(exp.syntaxCopy()); |
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 second syntax copy does not look necessary, exp
is a fresh copy of initExp
and it gets reassigned anyway.
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.
Good catch. The code was hastily copy pasted from other lowerings, so I've also given better variable names now.
compiler/src/dmd/semantic2.d
Outdated
@@ -275,6 +275,7 @@ private extern(C++) final class Semantic2Visitor : Visitor | |||
// https://issues.dlang.org/show_bug.cgi?id=20417 | |||
// Don't run CTFE for the temporary variables inside typeof or __traits(compiles) | |||
vd._init = vd._init.initializerSemantic(sc, vd.type, sc.intypeof == 1 || sc.flags & SCOPE.compile ? INITnointerpret : INITinterpret); | |||
tryLowerStaticAA(vd, sc); |
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.
So it turns out classes get initializerSemantic run twice, so this does the lowering a second time, which is a bit wasteful. Perhaps the VarDeclaration
/ Initializer
should get a semanticDone
flag.
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.
VarDeclaration has a semanticRun field. Doesn't that help?
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.
Yes, I'm now checking for semanticDone >= PASS.semantic2done
to skip this
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.
I really think we need a unittest to ensure binary compatibility. I'll see if I can come up with one.
I think we need a unittest inside // ensure the struct layout is the same
unittest
{
import newaa = core.internal.newaa;
// ensure the implementation structs are the same size
static assert(newaa.Impl.sizeof == Impl.sizeof);
// ensure all the offsets are the same
static foreach(i; Impl.tupleof.length)
{
// for bucket array and Flags, we "compatible" types, not exactly the same types.
static if(__traits(identifier, Impl.tupleof[i]) == "buckets")
// not sure if this is right, basically I want to compare the Bucket structs.
static assert(is(typeof(typeof(Impl.tupleof[i]).tupleof) == typeof(typeof(newaa.Impl.tupleof[i]).tupleof)));
else static if(__traits(identifier, Impl.tupleof[i]) == "flags")
static assert(Impl.tupleof[i].sizeof == newaa.Impl.tupleof[i].sizeof)
else
static assert(is(typeof(Impl.tupleof[i]) == typeof(newaa.Impl.tupleof[i])));
static assert(Impl.tupleof[i].offsetof == newaa.Impl.tupleof[i].offsetof);
}
} I think this sanity check is enough for now. Probably it would be good to make some random AAs both at compile time and runtime, and ensure they have the same Bucket ordering, but maybe not necessary for this change. As somewhat of a compromise, we could put some (or all) of the aaA You may have to make the |
At some point in the future, we may have a fully-library-based API for AAs. You might be able to use a custom druntime at that point to add in your own special allocators. You could technically do that today if you were ok dealing with As it stands, I don't see any allocation schemes besides the GC fitting the AA api anyway. |
This looks great! I don't want to push the button myself since I don't really understand how the compiler code works. |
compiler/src/dmd/expression.d
Outdated
@@ -3250,6 +3250,7 @@ extern (C++) final class AssocArrayLiteralExp : Expression | |||
|
|||
Expressions* keys; | |||
Expressions* values; | |||
Expression lowering; /// Lower to core.internal.newaa for static initializaton |
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.
Comment before decl please.
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.
DDoc knows the comment belongs to lowering
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 reads nicer preceding the declaration
compiler/src/dmd/semantic2.d
Outdated
* vd = Variable to lower | ||
* sc = Scope | ||
*/ | ||
void tryLowerStaticAA(VarDeclaration vd, Scope* sc) |
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 is called tryLower, how do I know (documentation, type signature) if it failed or not?
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.
The caller doesn't need to know whether it succeeded
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.
If so why does the name contain try?
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 the VarDeclaration
may not contain an AA
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.
I kind of agree with max. try
has a specific meaning in D.
What about lowerAnyStaticAA
or something like this?
Do I understand correctly that the visitor will recurse through the AST looking for static AAs?
compiler/src/dmd/todt.d
Outdated
@@ -488,8 +488,13 @@ extern (C++) void Expression_toDt(Expression e, ref DtBuilder dtb) | |||
*/ | |||
void visitAssocArrayLiteral(AssocArrayLiteralExp e) | |||
{ | |||
e.error("static initializations of associative arrays is not allowed."); | |||
errorSupplemental(e.loc, "associative arrays must be initialized at runtime: https://dlang.org/spec/hash-map.html#runtime_initialization"); | |||
if (e.lowering) |
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.
Minor not: this branch being the other way around reads a bit nicer
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.
*nit
the associative array type in druntime, just enough to create an AA from an | ||
existing range of key/value pairs. | ||
|
||
Copyright: Copyright Digital Mars 2000 - 2015, Steven Schveighoffer 2022. |
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.
Digital Mars copyright should be dlang foundation I think, and the years potentially wrong?
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 is copied verbatim from here: https://github.com/dlang/dmd/blob/98833b0b82b5f51c769e460c6956d6477480d6c6/druntime/src/rt/aaA.d
druntime/src/object.d
Outdated
@@ -104,6 +104,14 @@ else version (AArch64) | |||
else version = WithArgTypes; | |||
} | |||
|
|||
// Lower Associative Array to a newaa struct for static initialization. |
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.
Doc comment please.
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 isn't really meant to be a public function.
In fact, I'm actually thinking, we should rename this with a _d_
in front to let people know this is a compiler hook and not for general use.
// ensure compatible types and offsets | ||
static foreach (i; 0 .. Impl.tupleof.length) | ||
{ | ||
// for bucket array and Flags, we "compatible" types, not exactly the same types. |
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 counts as a compatible 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.
i.e. the same layout, different types. The Bucket struct should be identical in both implementations.
Note that this test isn't adequate to ensure that, as you could e.g. reorder the fields and this would still pass. But this is better than nothing.
Should probably go under a preview flag to start with but could be enabled pretty swiftly. I am worried about having two AA implementations though |
This is a straight-up bug fix. I don't think it needs a preview flag. The spec specifically identifies that it's an intended feature, just not implemented.
This is the same implementation. I can think of some possible updates once this is merged, i.e. we can probably factor out some of the common elements, that should help make sure this stays in sync. The big sticking point is that the |
We want less preview flags, not more |
@schveiguy
@RazvanN7 This is the idea I discussed this morning:
lowering
field toAssocArrayLiteralExp
[3:4]
asobject.aaAsHash([3:4])
and runctfeInterpret
on it when it's a static var initializertodt
, callExpression_toDt
on thelowering
, which is binary compatibleIt's WIP because it only handles the easy case so far:
While an AA could be only part of an initializer expression:
Which could be implemented as well with a visitor, but I want a review on the approach first.
Update: visitor now implemented