Skip to content
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

Windows: Make -fvisibility=public export all defined symbols and import external data #3703

Merged
merged 11 commits into from
May 21, 2021

Conversation

kinke
Copy link
Member

@kinke kinke commented Apr 23, 2021

To pave the way for simple DLL generation (end goal: druntime-ldc-shared.dll).

The dllexport storage class for functions definitions is enough; the automatically generated import .lib seems to resolve the regular symbol name to the actual symbol (__imp_*), so dllimport for declarations seems superfluous.

For global variables, things are apparently different unfortunately.

@kinke
Copy link
Member Author

kinke commented Apr 23, 2021

@rainers: Do you happen to have any idea about the data symbols?

@kinke
Copy link
Member Author

kinke commented Apr 24, 2021

Okay, non-TLS globals work in some scenarios now. Trying to access TLS globals across DLL boundaries yields a linking error; both MSVC and clang don't support them in C++ and fail at compile-time:

extern __declspec(dllimport) thread_local int impGlobal;

=>

error C2492: 'impGlobal': data with thread storage duration may not have dll interface

@kinke kinke marked this pull request as ready for review April 24, 2021 01:24
@rainers
Copy link
Contributor

rainers commented Apr 25, 2021

@rainers: Do you happen to have any idea about the data symbols?

I guess you are aware of Benjamin Thaut's work, see for example https://forum.dlang.org/thread/shonvwuauwbhrszysmrz@forum.dlang.org

I agree that an "export all" switch avoids much of the hassle of his DIP. It's also what happens on linux, and there haven't really been any complaints recently AFAICT. It's also what I implemented 11 years ago: https://issues.dlang.org/show_bug.cgi?id=4071 ;-)

dllimported data needs different code to access it (IIRC dmd has support for that, probably not too hard to add the extra indirection).

The more problematic case is imported data referenced from preinitialized data. This needs an additional relocation pass at EXE/DLL initialization time using compiler generated info: https://github.com/Ingrater/druntime/blob/DllSupport80/src/rt/sections_win64.d#L432

An alternative might be to emit initializer functions. IIRC that is what MSVC does, though your comment above suggests it is not supported (anymore?).

@kinke
Copy link
Member Author

kinke commented Apr 25, 2021

Thx Rainer.

The more problematic case is imported data referenced from preinitialized data.

Yep exactly, this seems to account for most Phobos linking failures in #3704. It's especially bad for generated TypeInfos, because they reference e.g. the TypeInfo_Class vtable from druntime and builtin TypeInfos defined in rt.util.typeinfo.
With LLVM here, using dllimport makes no difference for the generated assembly, just like for TLS variables - so if the dllimported global isn't defined in the same DLL/exe, linking errors result. I guess for Linux and Mac this is all done automatically, but Windows would either need duplicate definitions in each DLL (well, wouldn't work in all cases, but could work for builtin TypeInfos) or some form of runtime patching.

Living with no direct TLS globals access across DLLs would IMO be doable, but not being able to access non-TLS globals in initializers for static data in other DLLs seems like the biggest hurdle for DLLs on Windows indeed.

To pave the way for simple DLL generation (end goal:
druntime-ldc-shared.dll).

The dllexport storage class for functions definitions is enough; the
automatically generated import .lib seems to resolve the regular symbol
name to the actual symbol (__imp_*), so dllimport for declarations seems
superfluous.

For global variables, things are apparently different unfortunately.
Functions can and apparently are magically stubbed (something like
`void myFunc() { (*__imp_myFunc)(); }`), but it seems we cannot avoid
dllimport for global variables exported from other DLLs/executables.

In order to try to make druntime etc. work as DLL without explicitly
exporting all required globals, `-fvisibility=public` now also assumes
all `extern(D)` globals *not* defined in some root module are exported
from some binary and dllimports them accordingly.

So e.g. compiling Phobos with `-fvisibility=public` means

* Phobos exports all defined symbols,
* Phobos dllimports extern(D) druntime symbols, and
* extern(C) globals, e.g., from the C runtime, remain external and are
  linked regularly.
…ity=public

Mainly, don't export any linkonce_odr symbols.
Otherwise the functions aren't exported. We have some @weak functions in
druntime.
Init symbols, TypeInfos (classes only) & vtables - unless defined in a
root module. For explicitly exported aggregates, or, with
`-fvisibility=public`, all aggregates.
References to dllimported globals in static data initializers lead to
undefined-symbol linker errors. We need to resolve the indirection
manually at runtime.

I went with an extra LLVM pass for Windows targets when using
`-fvisibility=public`, and scanning the initializers of all global
variables (well, only thread-global ones). The problematic pointers in
the initializers are nullified and initialized at runtime early via a
CRT constructor (working with -betterC as well).
@kinke kinke changed the title Windows: Make -fvisibility=public export all defined symbols Windows: Make -fvisibility=public export all defined symbols and import external data Apr 30, 2021
@kinke
Copy link
Member Author

kinke commented Apr 30, 2021

I've added an LLVM pass performing the manual dllimport 'relocation'. The release phobos2-ldc-shared.dll can now be linked successfully against the druntime DLL; debug Phobos still has 1 undefined symbol, a TLS global (core.exception._store; referenced by a staticError instantiation in Phobos).

@rainers
Copy link
Contributor

rainers commented Apr 30, 2021

I've added an LLVM pass performing the manual dllimport 'relocation'.

Having it a in a separate pass is pretty neat :-) For tls-variables, you could export an accessor-function instead that returns a reference/pointer to the variable.

@kinke
Copy link
Member Author

kinke commented Apr 30, 2021

Having it a in a separate pass is pretty neat :-)

:) Yeah, LLVM's rich/heavy IR does have advantages...

For tls-variables, you could export an accessor-function instead that returns a reference/pointer to the variable.

I hope such direct accesses are sufficiently rare that manual workarounds like ldc-developers/druntime@f23246f will do for the/some time being. That was sufficient for the druntime/Phobos DLL boundary; linking all druntime and Phobos DLL variants now works, incl. linking the shared test runner executables (see here).

That should be mostly it from the compiler side, and work can begin on making druntime DLL a truly shared druntime analogous to Posix' _d_dso_registry system, incl. a list of TLS blocks per DLL/executable for each thread. Instead of the CRT ctor/dtor pair in each object file (for the _d_dso_registry() calls, used for Posix), I'd tend towards precompiling a new separate little module and linking that object file automatically with -link-defaultlib-shared on Windows.

kinke added 2 commits May 3, 2021 16:44
This fixes segfaults when running the shared Phobos test runners; it
previously tried to fix up fields of constant data.
@kinke kinke merged commit ac20132 into ldc-developers:master May 21, 2021
@kinke kinke deleted the dll branch May 21, 2021 15:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants