-
-
Notifications
You must be signed in to change notification settings - Fork 667
[BLOCKED][WIP] Don't instantiate unittest templates in libraries #8124
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
Conversation
|
Thanks for your pull request and interest in making D better, @marler8997! 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 referencesYour PR doesn't reference any Bugzilla issue. If your PR contains non-trivial changes, please reference a Bugzilla issue or create a manual changelog. Testing this PR locallyIf you don't have a local development environment setup, you can use Digger to test this PR: dub fetch digger
dub run digger -- build "master + dmd#8124" |
0fd60c2 to
6609f0c
Compare
src/dmd/dmodule.d
Outdated
| * ownership of template instantiation. | ||
| */ | ||
| Module importedFrom; | ||
| bool isCompiledImport; |
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 PR modifies the code to determine if an import should be compiled earlier than before (right after parsing the module declaration instead of after parsing the whole file). This way, we know if we are going to compile unittests before parsing the file. To that end, I found out that the parser depends on importedFrom being set correctly meaning we can no longer "hijack" this field by resetting it when an imported module is compiled. The simple solution is to add a new field that indicates when an imported module should be compiled, and to leave the importedFrom field intact so it can be used during the parse stage.
6609f0c to
66adf93
Compare
|
This is really cool. Thanks you so much for pushing this!! |
|
Yeah still WIP (making sure the concept works). Thanks for the suggested test...I'll see if I can get some tests going. |
c4cd29e to
076401e
Compare
87cb5d9 to
07d9d60
Compare
|
Very cool! A few things:
On the other hand, |
Did more research, indeed the unittest version is more blunt, it determines if the scope should be included based on the global flag only. I can think of a way to make it work more like I had expected, but now I'm not sure if that's the correct course. There are cases when The one case where I'm concerned is when |
|
We could make |
|
If I understand correctly, the An alternative approach that, I think, would not affect |
|
The slowdown is in analyzing the unittests, not "combining them/generating code" as you describe. And I don't think it "combines" them into one function. I think it leaves them as separate functions and just calls them in succession. |
Yes. Not sure what I was thinking.
That might be the case. |
|
I didn't look too closely at how this was implemented, but the fact that it is implemented gets a big applause from me. :-) We badly, badly need this. Thanks for taking the time to do it rather than merely talk about it, like the rest of us. :-D |
07d9d60 to
65fe80f
Compare
Or maybe we reverse the logic, have So...code that is used outside the module meant for unittesting would go in one type type of Given this description, maybe another good name would be version(unittest_library)
{
// gets compiled if `-unittest` was given on the command line
// provides code to support unittests inside and outside this module
}
version(unittest)
{
// only gets compiled if this module is being unittested
// provides code to support unittests in this module only
} |
|
I don't know exactly what we want to do with Now, we can choose to do something like print a deprecation message if you ever import anything that's in a Regardless, if we want to make it so that |
|
Yeah, this PR takes the conservative approach, leaving in Without this special version, each unittest block would have to define all their imports/data structures/functions that aren't included in the non-unittest code, none of it could be shared between unittests in the same module. version(unittest)
{
// gets compiled if -unittest is given on the
// command line
}
version(unittesting_this_module)
{
// gets compiled if -unittest is given on the
// command line AND unittests are being
// compiled for this module
} |
If you have an assertion fails in a |
| This field holds the root module that caused this module | ||
| to be loaded. If this module is a root module, then it will | ||
| be set to `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.
Unless it's no longer true, I'd keep the part of the comment that says "This is used to determine the ownership of templaet instantiation".
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 information was just moved to the isRoot function.
| @@ -0,0 +1,6 @@ | |||
| module printVersionUnittest; | |||
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 the next module is printingUnittest, this one should be pragmaMsgVersionUnittest, no?
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.
meh, not a big deal, kind of a long name
test/runnable/selectiveUnittests.sh
Outdated
|
|
||
| unittest_output="unittesting printingUnittest" | ||
|
|
||
| $DMD -m${MODEL} -I=${EXTRA_FILES} -unittest ${EXTRA_FILES}/printingUnittest.d -run ${EXTRA_FILES}/selectiveUnittests.d | grep -q "${unittest_output}" |
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 error code for this isn't 0, but it won't cause the whole script to fail.
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.
ah...I see you're not familiar with the latest work on DMD's Bash tests...see test/tools/sh_do_test.sh :)
|
Are there bug reports for these mangling issues? |
|
I saw a bunch of bugs listed in |
|
So, I have figured out (somewhat) the issues here, and we have this peculiar notion that when An example: importme.d: module importme;
struct S(T)
{
T t;
void method() {}
}
void baz(S!int) {}
void foo()() {
S!int x;
x.method();
}importer.d: import importme;
void main() { foo(); }Compile with But when I execute Indeed, compiling separately, both .o modules contain a definition of Doing some research, I found that this "moving" actually ALWAYS used to happen without -unittest or -debug (even before this if statement was introduced). Here is the original pull that removed the "moving" situation for all code but certain cases (including unittests inexplicably): #2550 And here is the one that adds moving for So IMO, I think we need to do 2 things:
These 2 fixes will help fix the extra imports due to template unittests in some cases, but not all. In cases where the instantiation is happening inside a root module, you will still see unittests be compiled (and even included). Then I think we can move forward with a PR that kills semantic/emitting of unittests for imported modules. Preferably, this would allow I don't think we should disable |
|
Yeah, I think the mangling issue has to do with the lack of "moving" to root modules. Note that the debug build works! This is because -debug is passed, and you haven't turned off the moving for -debug. What it looks like is happening is the "moving" is re-compiling the code as if it were in the root module, and this masks any incorrect symbols in the library object. So @marler8997 is correct that this is "uncovering" dip1000 issues, albeit probably only when doing separate compilation. It's kind of alarming that we have bugs in separate compilation, which is how we compile unittests for phobos, but we ship the code compiled all-at-once! And all of it is masked over by moving all template instantiations local 😝 I wonder how much the autotester will speed up when it's properly ignoring imported templates it shouldn't be compiling. |
For the record, this issue of separate mangling for dip1000 is "well-known" (at least among Phobos developers) and definitely a blocker for it (sadly not the only one), because it means that we once we ship Phobos compiled with -dip1000 everyone has to use -dip1000 too (and of course in the other way around: as long as we don't ship a -dip1000 version of Phobos, it's really difficult to us Phobos with -dip1000). |
I'm sure the path would be to make dip 1000 enabled by default before shipping phobos with dip1000 enabled. |
|
Am I understanding that we are running the auto tester with dip1000, but shipping without it? Is this really a good thing? I mean, dip1000 isn't being shipped, isn't properly working, yet it's impeding other work (such as this PR). Worse yet, if we have issues that are somehow masked by dip1000, they will get through the tester. I understand and agree that the dip1000 issues are important, but at the cost of preventing other things from being worked on, I'd prefer it be tested in the background instead of being a blocker. You could run only the master branch with -dip1000 and still see when things went bad. Most of the time, dip1000 issues aren't going to regress anyway. |
Partially. Only on some machines.
Yes (see answers below).
That's why it's only run on some CIs.
If that's really blocking this PR, you could remove the
No warnings don't work. Everyone ignores them.
Not sure how this be different from the status quo? (
No. People love to break -dip1000, because no one (except Walter maybe), really understands it. |
This isn't encouraging when it's a blocker in some cases. How can there be regressions if it wasn't being tested before that PR?
I mean not run it on any PRs, just the master build. And I don't mean the real master, I mean like a separate build, similar to how @yebblies had his own build branch until we flipped the DDMD switch. This is similar to the broken cycle situation we had. Until the cycle tester was fixed, any PR could mysteriously break the build for weird reasons, and then the PR is rejected for reasons having nothing to do with the PR changes. Having the status quo be a broken build is not conducive to productivity.
Which ones? The only ones that matter here are the autotester (where it seems every platform but Windows is using -dip1000). It'd be fine if there was a different CI that tested dip1000 issues and we could override it individually, but the time to build Phobos with separate compilation probably rules that out. |
-> dlang/phobos#6443 |
|
I'll check to see if that phobos PR fixes the issues. I also pulled out some of the changes in this PR and moved them into here #8151 ...waiting for that one to get merged. |
changelog/selectiveUnittests.dd
Outdated
| @@ -0,0 +1,3 @@ | |||
| Only compile unittests in compiled modules | |||
|
|
|||
| The compiler was modified to only compile unittests in modules that are being compiled. The most common impact is that unittests from druntime/phobos will not be included when compiling other programs that use these libraries. | |||
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.
Would be nice to expand this with an explanation of which situations are adversely affected by this change, and what are the steps for fixing said situations.
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.
Actually...I should update this. Turns out the unittests already weren't being compiled in (thanks @schveiguy for looking into this). What's actually happening is that version(unittest) blocks are importing and instantiating templates that aren't instantiated without -unittest. Then these instantiations are being moved to root modules. With this new information, as far as I know this change shouldn't have any "functional" impact on users other than a performance increase when compiling with -unittest.
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 this just fixes sync between whether unittest blocks and version(unittest) blocks are being compiled, right?
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.
Actually no. This is preventing template instances inside libraries from being moved to root modules when compiling with -unittest. This is what's causing the compilation slowdown because the template instances that are being moved cause ALOT of phobos modules to be loaded and analyzed that wouldn't be otherwise.
version(unittest) blocks still get analyzed, however, @schveiguy is working on a phobos PR that should prevent those blocks from causing too much slowdown. This PR along with the phobos PR should be everything we need to make -unittest compile just as fast as without -unittest (when the app has no unittesting itself).
NOTE: this PR is also adding a small optimization where it won't even create a parser AST for the code inside unittest blocks. But the unittests don't get included even if they are parsed.
|
I slightly modified dlang/phobos#6443 (see my comment in there Seb) and was able to successfully run the phobos unittests. Good stuff. |
c14f9dc to
69fb77b
Compare
|
Any progress with phobos? Could you create a WIP PR with what you have now so we can monitor progress? |
I want to split my single giant commit into smaller PRs for easier review. Will do soon. |
As far as "inexplicably" goes: The reason This assumption is what allows template instances to be elided if they are already known to be used in an imported module. For example, if Phobos already uses Obviously, this can only work if those templates are actually emitted into the library/other object files linked in. However, typical libraries (including Phobos) instantiate more/other templates in unit test builds than; hence This is obviously problematic for unit testing user code that links against Phobos, since we don't ship a Phobos version with Hence, as a concession to practicality a hack was introduced, whereby template instances are never assumed to be present in imported modules for This is a huge hack, and the reliance of user code on these Frankensteinian builds to work has created all sorts of trouble for LDC in the past when it comes to cross-module inlining and things like that. It might seem that changing However, note that this would also make any |
|
@klickverbot I think I understand what you are saying. An imported module may instantiate more templates when compiled with -unittest. The mechanism that says "I don't need to instantiate this template locally because it's already done inside the import" will NOT move the instantiation local (given this PR is accepted). So if you haven't actually compiled the imported module with -unittest, then there is no instantiation anywhere, and you get linker errors. BUT, with this PR, the only place this should happen is in I'm very concerned about eliminating |
|
As the Phobos PR has been merged this is starting to pass on some machines, but interestingly only for the 32-bit ones. |
|
The demangled symbol looks like this: pure nothrow @nogc @safe void std.conv.toChars!(10, char, 1, uint).toChars(uint).Result.popFront()Something to look into... |
|
A shame we can't get this to work. Likely the symbol failure is because DMD gets confused about which symbols are already generated. The fact that it's a uint, and a 64-bit system has size_t being ulong, makes me think that in the 32-bit case it doesn't fail because the uint and size_t versions are the same, and size_t is used somewhere else that makes it link. |
Uh oh!
There was an error while loading. Please reload this page.