-
-
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 Issue 23974 - A ModuleInfo in a separate Windows DLL should not b… #15298
base: master
Are you sure you want to change the base?
Conversation
Thanks for your pull request, @WalterBright! 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.
|
buildkite is failing all over the place with:
which should not be triggered by this Windows change. |
I agree with Rainer, no point in tackling this 'problem' (& this is working just fine with LDC). |
There is one key reason that this is a good idea, minus how it determines out of binary: PAYG + -better = no more ModuleInfo stubs. So it does help me personally, even if it replaces something that will one day be automatable. So I'm all for it, except for the fact that I believe that the mechanism to determine if it's out of binary is going to lead to bad times for people. |
I think this would currently totally break separately compiled DLLs - if each module is compiled separately and at least some of those contain |
Agreed. I tried to explain it in the ticket but didn't get anywhere. I recommended https://issues.dlang.org/show_bug.cgi?id=23850 instead which would be tooling friendly. |
Why would someone create a module that has:
that is not intended to be an imported module? If the I've been thinking about this for a while, and am not seeing the confusing case, unless the user is himself confused about what an import is and what an export is. |
They won't unless they are doing bindings and trying to do naughty things, things we do in druntime & phobos. Keep in mind the override switches like visibility add export to everything, so it won't matter if it isn't actually annotated by the user. But they do, do this: module a;
export void athing() {
} module b;
import a;
export void bthing() {
athing;
}
Does this work today? Yes. With -betterC I have this working right now with dmd and dub and so do plenty of others that I have helped. Works with ldc without -betterC of course. Oh and related linker warnings that we NEED to prevent (which this thought process eliminates the prevention of, down the road): https://learn.microsoft.com/en-us/cpp/error-messages/tool-errors/linker-tools-warning-lnk4286?view=msvc-170 |
That example wouldn't break with this PR. Both a and b export their definitions, and mydll exports those definitions. |
When you compile Note that the sources for |
I don't know what dllimport mode is. Is that part of Rainer's PR? |
No. I was hoping you were asking about the switch here, but this would explain a lot (but yes Rainer's PR adds the switch). If my explanation here isn't enough, either me or Martin need to have a chat with you about this because we need to be able to communicate this even to people who don't know what a linker is. Let's start with the basics, a D symbol can map directly to multiple linker symbols. On most platforms, this will be one-to-one, rather than one to two (like on Windows). A D symbol can be in one of three modes, internal (you're most familiar with this, it's a straightforward block of bytes with optional export out of object file, or extern declaration to another object file, to a name). DllExport which constructs (with the help of the linker) a secondary exported symbol, which is a pointer to an internal symbol. Finally DllImport, which is the inverse of DllExport, it dereferences the pointer to get access to an internal pointer and uses that instead at runtime. You should only use DllImport when accessing a symbol outside of the binary (executable or shared library, not static library or object file). Doing this when it is not outside of the binary will elicit linker warnings like LNK4286. If you do not use DllImport and try to use internal mode instead, the linker will error with symbol not found. The ability to put a symbol into DllImport, DllExport or internal modes already exists in the compiler. DllImport/DllExport is incomplete due to not mutating memory at runtime to dereference the pointers which is needed to make pointer arrays ext. work. Which is the problem with ModuleInfo dependency array. What we need is a way to tune it per D symbol to pick which one is needed (hence my proposal for adding a version parameter to export). Because you can have both D functions/globals declared in one module and still have bindings to DllImport or internal symbols outside of the object file. Off the top of my head there are four ways to put a D symbol into DllImport mode: (EDIT: I forgot no body)
There are only two ways to put a D symbol into DllExport mode:
Internal is the default. The above lists aren't exhaustive, I'm sure there are more rules and exceptions to them, but at the very least it's what I've personally encountered or had to deal with when debugging. Dub passes to ldc |
Here's how it works today:
It's not ambiguous. |
I've just remembered a very obscure detail of how import libraries work. They have a generated function in them that dereferences a DllImport symbol to allow calling functions using the internal name instead of the DllImport name. I've confirmed it by rereading: http://blog.omega-prime.co.uk/2011/07/04/everything-you-never-wanted-to-know-about-dlls/ After much checking of dmd-fe, you're right in that there is no special behavior of LDC does not modify anything related to So it looks like both me and @kinke are wrong about this PR breaking things. It's quite the opposite. Unless you use the .di generator with explicitly It would certainly be hackable to make work but would require adding code to every module that is importable from a DLL, but that is a significantly worse solution than ModuleInfo stubs that can be automated. If only there was a way to turn it on at the build manager level automatically... |
AFAICT, it's still easy to break: // a.d:
export extern int someSymbol; // dllimport from some C DLL, fwd-declared in this module
shared static this() {}
// b.d:
import a;
void foo() {}
shared static this() {} And then linking both to a binary. The single dllimport declaration in module While cutting off the dependency paths at actual binary boundaries might be safe and perhaps desirable (I guess under the assumption that the other DLL's modules will have been initialized already when loading that DLL earlier), it's not really required, and would just try to tackle one tiny symptom of the general missing relocation for dllimported data symbols. And that general problem is, AFAIU, tackled in Rainer's PR. |
Yeah absolutely. I kinda ignored it due to thinking The main thing this PR solves is when ModuleInfo is missing from DLL. But shouldn't setting the right flag to nullify the reference work instead? |
Yes, which is why best practice would be to have |
Rainer's PR works on a per-symbol basis, this PR is making assumptions based upon a single symbol which could very well be a binding to a system API's or some other external binary. Mixed binary and user code, will cause this to do the wrong thing. |
@rikkimax @kinke @rainers I am of the (probably minority) opinion that DLLs in Windows are not meant to coexist with things like template functions, meaning one does not know what functions will be actually generated when writing the code. DLLs are clearly set up to support a fixed, predetermined set of export/import points. The "export it all, whether it is intended to be exported or not" has an uncomfortable feel to it. For COM files, the set of exported names is very small, interfaces are discovered using QueryInterface. That said, I understand that many people want it to work like shared libraries in Linux. They don't want to sit down and define what the entry points should be. This PR is designed to support the "I want to think about what the entry points are and have a fixed set of them". @rainers is designed to support the "export it all" paradigm. But we can support both. This PR doesn't interfere with @rainers proposal. |
I understand that. You'll find exactly the same thing when doing Windows DLLs in C or C++. It's kinda what people are accustomed to with Windows DLL programming, which has little in common with Linux shared libraries. People from that background will find this straightforward, and the "export it all" uncomfortable. I am in that category. After all, I implemented dllimport and dllexport for Windows C and C++, and have done COM programming with it. Also, it's how DLLs work for Windows ImportC. |
PS for a template heavy library like Phobos, I doubt trying to make it a Windows DLL makes much sense. |
I take it you haven't looked at the code that I've linked to you in the past. I go out of my way to make sure that symbols go into the right binary by de-templating functions. So much so that I'm kinda desperate (long term) to see sum types with implicit construction for function calls. What I'm saying here is very much coming from a place of experience with using D and explicitly annotating export everywhere (which druntime and Phobos do not do). These string types under imports, were the result of the .di generator failing for me. I've tried it. It did not work for me. This does. Yesterday I spent the whole day debugging dmd built binaries trying to figure out why it was causing my code to segfault (with -betterc DLL + full D executable). Do you want to know what was wrong? Even if exported __initZ symbols ARE NULL. I still need to come up with a test case for this to report it, because Rainer's PR should not fix this. |
I've already fixed a couple problems with this, I want to fix where it's failing for your code. |
The .di generator is not a priority when we have bugs that prevent using dmd for code that 100% should work. Once I'm happy with -betterC support, then I'll look into the .di generator, we just aren't there yet. Stuff that I actually need:
Things I want:
Note: that these two things I want are actually linked in that both of them can be solved with the external At this point in time, I cannot see how anyone before me has got shared libraries for non-OMF working with dmd and have it look like regular D code. Let alone the 100k+ LOC that I've got. There are just so many pitfalls that it really takes some serious work to deal with. |
You mentioned it was causing problems for DLL generation, and fixing DLL generation problems is a priority. Also, .di problems tend to be easier to fix. I like easy to fix problems, because then I can make a lot of progress.
I can't either, but as noted earlier, Windows DLLs are simply not like shared libraries at all. They are essentially separate executables with a set of hooks.
I see your point, but the idea with encapsulating functionality in a DLL is one shouldn't have access to private symbols in the DLL. Access should be through a class reference to some public function in the class. A DLL should have a relatively small number of exported symbols, and I would be so bold as to suggest that variables shouldn't be exported at all from a properly designed DLL interface. Of all the DLLs from Microsoft that I have worked with, I cannot recall a single instance of exporting a variable.
That should be covered by .di generation. External binaries should supply .di files to interface with them. I understand that this requires more thinking, design, and implementation work on the part of the user. But it will result in a much more robust and clean DLL. For your other points, I appreciate hearing your needs and want to fix them, but the discussion about them belongs elsewhere. I recommend bugzilla, perhaps bringing them up in the n.g. as well. Having all the issues for betterC tagged with the betterC keyword helps a lot. If any of your concerns are not already in bugzilla, please add them! |
Indeed, they are unlike shared libraries, they can't have an entry point like they can with ELF. |
I can see that you are trying to be highly principled about limiting problems with shared library support. I am happy to see that. However, in practice, we as a community have a fairly large number of people who don't know what a linker is and as this is a subject matter that is highly linker-related it is very easy to cause people problems. Here is a recap of my latest debug I did for someone which was within the last week:
There are workarounds for the second problem using DllMain. However, this shouldn't be required. I even reported this issue as an example of poor leadership in my gripes list as std.stdio has some serious limitations and this has been known for a long time. The user decided after I did all that debug work to just use Nim instead because they had that already working... without any workarounds. Any issues that me or Martin raise regarding usage, are very much based on the fact that we are the ones debugging people's builds. I am sure I won't be the only one who would be quite happy to offer you a bottle of something if you were to take on this particular responsibility moving forward ;) |
I appreciate that you are doing the ground level work to help people be successful with DLLs.
DllMain runs the static construction of the D libraries
I can't find it: https://issues.dlang.org/buglist.cgi?quicksearch=dll%20stdio&list_id=245227 |
That is the current druntime solution for this purpose yes. But it is not the only way to do it. There are two different mechanisms that could replace it. https://issues.dlang.org/show_bug.cgi?id=23756
You shouldn't. It's not a bug of std.stdio, but a bug of phobos in terms of scoping. https://dlang.org/phobos/std_stdio.html#.File.write std.stdio should not be used for interacting with the console for multiple reasons. |
?? Could you explain this in a bugzilla issue, please? |
There's a PR for that. |
https://issues.dlang.org/show_bug.cgi?id=24001 There are a lot of tickets referenced there. But just to be clear, std.stdio is doing the right thing, we are simply misusing it. |
Thanks for putting that bugzilla together. |
If you want regular D code (not C with D syntax) to be usable across DLL boundaries I consider a shared runtime a prerequisite. druntime is a must (you have to share the GC, threads, etc.), not having phobos as a DLL would be pretty disappointing and can cause issues by multiple instances of similar resources (e.g. file handles in std.stdio). Templates can be a problem, but the compiler will not expect more symbols in the DLL than what is also built into the static library, so I don't think exchanging one for the other is an issue. FYI: With "export-all", druntime has about 14000 exported symbols, and phobos about 13000. That's in the same ballpark of some C++-DLLs, e.g. Qt5 has similar DLLs. |
I've just had a very bad thought about this. This will need to work with dmd and it's related to my report regarding RTInfo. https://github.com/dlang/dmd/blob/master/druntime/src/object.d#L3724 It uses a global which is why it has unresolved symbol errors. |
This is still a good PR and is ready to merge. |
Yes, my objection still stands - breaks existing code and doesn't tackle the root problem, only a minor symptom. |
Just so we are clear:
In the above scenarios, I am not referring to D being used as a shared library, it is for executables. This is guaranteed to cause problems someday. It is a ticking time bomb. I am not recommending the external import path switch for fun. I spent months trying to figure this out. It is the only solution that will work consistently and without problems. It will mean that Martin and I have to work on dub, the refactoring will not be enjoyable. |
Unfortunately, I have hit a scenario where eliding of ModuleInfo for shared libraries is required.
It's a real shame this PR has unintended side effects that cannot go in. |
…e referred to by MIimportedModules
acc1134
to
1b6232e
Compare
…e referred to by MIimportedModules
(Somebody put tabs in transitivevisitor.d, and it got fixed by my detabber.)