Open alexcrichton opened 9 years ago
@Diggsey, @angelsl
I think he's referring to compiler plugins rather than application plugins.
I am indeed! (as I mentioned above as well)
@retep998
In the case of cross-compiling wouldn't crates that are used in plugins already be compiled twice?
Unfortunately I think the problem still comes up with build scripts. Both plugins and build scripts are always compiled for the host architecture, and build scripts are linked statically while plugins are dynamic, so if a build script and a plugin share a dependency it's the same problem.
For normal dependencies, however, yes Cargo will compile it twice.
Shouldn't doing something similar in the non-cross case also be okay in order to correctly apply dllimport?
In theory, yeah, but this is something that'd probably need to be designed from both a Cargo and compiler perspective.
@angelsl
Actually, making Cargo compile the dependency twice in the same run seems like the only proper solution here.
I agree in that this is the only solution I know of here. This is just a fundamental problem with the way Cargo compiles dependencies and how they're linked together. I'm wary of starting to do crazy windows-specific things without reconsidering how it affects other platforms (e.g. making tons of windows-specific or unix-specific bugs), but I don't mean to reject anything.
@arielb1, @Diggsey
So we have x86-pc-msvc-static and x86-pc-msvc-dynamic targets?
I agree that this is more of a compilation-option rather than a target-triple option, I would also prefer to not mess with triples.
@alexcrichton
Alright, fair enough.
I believe @retep998 meant something similar to treating them as two targets, just not actually messing with the triples.
Thinking about the current situation, it is already incredibly hairy as soon as you start mixing in dylibs using Cargo because now every single dylib and binary has to make sure they use -Cprefer-dynamic
otherwise you'll get duplicate std errors. Not only that but any dependencies which are in common between two dylibs or a dylib and a binary also have to be built as dylibs or they'll be duplicates as well.
Are there any actual real world situations where Cargo is successfully being used with some dependencies being linked in as dylibs? Would it be okay if Cargo made mixing of static and dynamic linking illegal or is there a valid use case that we'd want to preserve?
Also I think it is really silly that plugins are treated as normal dependencies, so Cargo has to assume that all dependencies will be linked in even if they actually get used as plugins instead. This really hamper's Cargo's ability to be smart about how to handle such dependencies. Also unfortunate that we can't easily add special support for plugins to Cargo.toml
since it would be insta-stable because Cargo has no notion of stability, which isn't optimal considering plugins are unstable.
@retep998
I'm ok saying mixing dynamic and static linking is fine to forbid (so long as we have a good reason) because I highly doubt it happens in the wild today. It's quite rare to specifically request a dylib as an intermediate artifact, and otherwise you either get it because you're a plugin.
Also, are you familiar with Cargo's support for plugins? Your claims that Cargo assumes plugins are linked and how Cargo can't be smart isn't quite correct. Cargo knows exactly which crates are plugins to know which chains of dependencies should be compiled for the host architecture instead of the target.
@alexcrichton
Oh, there is a plugin
field in the [lib]
section of Cargo.toml
. Never noticed that before. Well, I guess that means Cargo does know everything it needs to know. In which case all we'd have to do is forbid mixing of rlib/dylib, make sure the plugin and all its dependencies are built and linked as dylibs, and make sure the dependencies of the binaries are built and linked as rlibs (unless someone decides to do everything as dylib instead because they want to have their own application plugins). Then rustc
would know whether upstream dependencies will be linked dylibs or rlibs when doing code gen and dllimport can be applied precisely and correctly.
Cargo does not currently build dependencies of plugins as dylibs. So Cargo would first have to be modified to build all dependencies of plugins as dylibs. This would result in a shared dependency being built twice, once as an rlib for the binary, and again as a dylib for the plugin. Ideally rustc would be able to do it as a single compilation, only internally duplicating the code generation steps when there's a split between whether to use dllimport or not, but that could potentially be complicated so two separate rustc invocations is the easier way for now. Also -Cprefer-dynamic
would be passed when compiling each dylib.
Once Cargo has been modified then rustc can be taught to always use dllimport when referring to an upstream dylib and never use dllimport when referring to an upstream rlib. It would also be nice to store metadata on this so that rustc doesn't later try to link against the wrong thing as a sanity check.
JFI GCC suggest always use special C++ visibility attributes https://gcc.gnu.org/wiki/Visibility
#if defined _WIN32 || defined __CYGWIN__
#ifdef BUILDING_DLL
#ifdef __GNUC__
#define DLL_PUBLIC __attribute__ ((dllexport))
#else
#define DLL_PUBLIC __declspec(dllexport) // Note: actually gcc seems to also supports this syntax.
#endif
#else
#ifdef __GNUC__
#define DLL_PUBLIC __attribute__ ((dllimport))
#else
#define DLL_PUBLIC __declspec(dllimport) // Note: actually gcc seems to also supports this syntax.
#endif
#endif
#define DLL_LOCAL
#else
#if __GNUC__ >= 4
#define DLL_PUBLIC __attribute__ ((visibility ("default")))
#define DLL_LOCAL __attribute__ ((visibility ("hidden")))
#else
#define DLL_PUBLIC
#define DLL_LOCAL
#endif
#endif
@nikomatsakis
static variables are unusual
FWIW, this is probably not true. Stylo extensively uses static variables to reference string atoms from Gecko side (see this script), so it could potentially be affected. Also, I see tons of warnings like
geckoservo.lib(style.0.o) : warning LNK4217: locally defined symbol ?rubyTextContainer@nsCSSAnonBoxes@@2PEAVnsICSSAnonBoxPseudo@@EA (public: static class nsICSSAnonBoxPseudo * nsCSSAnonBoxes::rubyTextContainer) imported in function _ZN5style19gecko_selector_impl13PseudoElement9from_atom17h219208fbc1c0455dE
when linking Gecko with Servo on Windows.
@upsuper Those warnings mean that static is being referenced with dllimport
despite the static being statically linked in and not actually imported from a DLL. Ideally Rust would know whether to apply dllimport
and you wouldn't see those warnings.
https://github.com/rust-lang/rfcs/pull/1717 should fix this? (I didn't see it fixed)
@Ericson2314 did you mean #31717? Since the issue you mentioned is about changing the precedence of as
...
Sorry, meant to link an RFC. Fixed now.
@Ericson2314 It will fix the situation for FFI. However it doesn't apply to inter-crate linking as Rust still doesn't know whether a dependency will be linked as an rlib or a dylib until link time, when it needs to know it at code generation time.
As an extreme solution to the problem of dllimport
/dllexport
between crates, we can ditch the dylib
crate type completely. Thus rust crates will always be statically linked together so we can simply never apply dllimport
or dllexport
.
FFI currently has this working mostly thanks to kind=dylib
applying dllimport
and kind=static-nobundle
not applying dllimport
. We just need kind=static-nobundle
to be stabilized (or to replace kind=static
wholesale).
Windows-gnu targets now also use dllimport/dllexport for various reasons: https://github.com/rust-lang/rust/pull/72049 Can we update this issue?
@mati865 do you mean to update the top comment? if you want to supply some replacement text, I can insert it (presumin you don't have the required perms)
@nikomatsakis yeah, my proposed changes:
/MSVC
from the titleFor importing constants from other DLLs, however, the linker requires that dllimport is annotated correctly.
-> For importing constants from other DLLs, however, MSVC linker requires that dllimport is annotated correctly. MinGW linkers can sometimes workaround it (see [this commit](https://github.com/llvm/llvm-project/commit/eac1b05f1db91097eb37d975e07789ce039cf7f7) description.
If we're targeting MSVC
-> If we're targeting Windows
Thanks in advance.
I have been struggling with dllimport for the past week, and the current limitations, this is just meant as an overview so that other people that face this issue may understand the current status. Please correct me if I understood anything wrong.
extern
blocks when they are annotated with the #[link]
attribute. The relevant code is here.rusct-link-lib
dllimport is not called on the symbols. This seems to be a limitation of the current code, since it relies on getting the symbols during the attribute codegen.rusct-link-lib
, after they have been annotated with #[link]
, dllimport is still called.#[link]
is currently the only way to call dllimport on Rust.#[link]
is not needed for functions, since MSVC exports both symbols, with and without the __imp_
. However, MSVC only exports the __imp_
prefixed symbol for variables, so #[link]
is a must (or some tricks to link to the symbol directly using link_name
and conditionals, but that seems hacky and fragile).Using the #[link]
attribute is fine in most cases. However, when using something like system-deps
to get and link the system libraries on the build script using pkg-config
files, in the best case it is redundant, but in the worse it gets in the way. This is because if the library has a different name on the system, or if you want to link to a different library (in our case, we want to use gstreamer-full
to replace all of the individual glib
, gobject
, gstreamer-*
and so on), the link attribute still links the one with the previous name as well.
It is possible to rename the libraries from the build script, but it is hard to do so from dependent crates, as described in rust-lang/cargo#6519. The most viable way is using links
and overriding build scripts. However, its support is very restricted at the moment, forcing to use specific targets and not allowing cfg
options (rust-lang/cargo#11042).
While it may not be feasible to automatically detect the symbols in which to apply dllimport when not using a #[link]
attribute, there are some suggestions that might make it easier to work with:
#[link]
attribute is linking to, while keeping the dllimport calls. This has been proposed in this issue and in the rcf for dllimport, using a syntax like rustc-link-lib:LIBNAME:
.#[link]
attribute without a name. This seems difficult since it would be hard to determine which type of linking it needs to have and without a name it is harder to overwrite it later.links
overrides by allowing cfg
flags to configure it, following the issue rust-lang/cargo#11042. This doesn't need to include features or solve rust-lang/cargo#8170, but allowing to configure the rustc-link-lib
calls for dependencies would be very useful and avoids workarounds.To summarize, the main issue right now is that linking to global variables doesn't work on Windows without using #[link]
, and this can cause trouble when linking against alternate libraries.
Would it be possible for the build script to set an env var with the name of the library and then use #[link(name = env!("LIBNAME"))]
? You need #[link]
outside of Windows too of you are building a dylib which links against a C staticlib for rustc to correctly export all imported symbols of the staticlib from the dylib of they may be used outside of the dylib.
Would it be possible for the build script to set an env var with the name of the library and then use
#[link(name = env!("LIBNAME"))]
?
Is it possible to use env!
on an attribute? I thought it was not supported yet (rust-lang/rust#52393). Having support for that would be another possible workaround for this.
You need
#[link]
outside of Windows too of you are building a dylib which links against a C staticlib for rustc to correctly export all imported symbols of the staticlib from the dylib of they may be used outside of the dylib.
No, in our case #[link]
is only needed in Windows for correctly calling dllimport on the symbols when the conditions are right. The rest of the linking happens withing the build script, using system-deps
which ultimately calls rustc-link-lib
.
I believe the specific issue of setting the import library name separately could be adequately solved by allowing a link kind
without a name (your second option):
// Without a `name`, only `kind` is allowed here
// This would be the equivalent of using `__declspec(dllimport)` in Windows C/C++
#[link(kind = "dylib")]
extern "C" {
...
}
// kind=static is redundant here but allowed for consistency
// other kinds are not allowed
#[link(kind = "static")]
extern "C" {
...
}
From what I can see, putting a name on an import block does not in any way connect that name to the functions or statics used in the extern
block (unless using raw-dylib
). That means a (not great) workaround to implement the above in stable Rust is:
// `empty` is just a lib file with no contents that we add to the search path.
// Even more hackily, we could use a well known lib name like "kernel32" which almost certainly exists.
// Either way this allows the real lib name to be supplied separately.
#[link(name = "empty", kind = "dylib")] // The kind here is redundant
extern "C" {
...
}
I believe the specific issue of setting the import library name separately could be adequately solved by allowing a link
kind
without a name
The issue that I can see with this would be how to override the linking type on the build script. If you have a name you can rename the library and change the type later with rustc-link-lib
, but in this case, how would you change the linking type later?
From what I can see, putting a name on an import block does not in any way connect that name to the functions or statics used in the
extern
block (unless usingraw-dylib
). That means a (not great) workaround to implement the above in stable Rust is:
That's not the worst workaround, but it relies on having certain libraries installed which may not be the case for all users.
That's not the worst workaround, but it relies on having certain libraries installed which may not be the case for all users.
You can supply an empty.lib
file (or libempty.a
for mingw) just using a build script without the user needing to have anything installed. Edit: though admittedly, it is a hack.
The issue that I can see with this would be how to override the linking type on the build script. If you have a name you can rename the library and change the type later with
rustc-link-lib
, but in this case, how would you change the linking type later?
True, if you need to switch between dylib and static you'd need to use #[cfg_attr]
to set the #[link]
line with a custom cfg (rather than a Cargo feature).
Oh actually, if you always override the name then it doesn't have to actually exist. I tried name = "<DOESNOTEXIST>"
and it worked with an override.
That's not a bad workaround actually, it beats linking directly to the __imp_*
symbol and manually dereferencing it conditionally. However, the issue with renaming the library is the same as before, it is harder to do from upstream crates, since the instruction has to come from the build script of the crate. You can't pass env variables conditionally, and links overrides are also not very configurable. But using an empty library generated in build.rs like you first suggested may be a reasonable workaround in the meantime. In our project, we ended up being able to remove the one problematic variable and replace it with a function call, but this is not ideal and the problem still persists in other instances.
Currently the compiler makes basically no attempt to correctly use
dllimport
. As a bit of a refresher, the Windows linker requires that if you're importing symbols from a DLL that they're tagged withdllimport
. This helps wire things up correctly at runtime and link-time. To help us out, though, the linker will patch up a few cases wheredllimport
is missing where it would otherwise be required. If a function in another DLL is linked to withoutdllimport
then the linker will inject a local shim which adds a bit of indirection and runtime overhead but allows the crate to link correctly. For importing constants from other DLLs, however, MSVC linker requires that dllimport is annotated correctly. MinGW linkers can sometimes workaround it (see this commit description.If we're targeting windows, then the compiler currently puts
dllimport
on all imported constants from external crates, regardless of whether it's actually being imported from another crate. We rely on the linker fixing up all imports of functions. This ends up meaning that some crates don't link correctly, however (see this comment: https://github.com/rust-lang/rust/issues/26591#issuecomment-123513631).We should fix the compiler's handling of dllimport in a few ways:
dllimport
where appropriatedllimport
where appropriatedllimport
if they're not actually being imported from a DLL.I currently have a few thoughts running around in my head for fixing this, but nothing seems plausible enough to push on.
EDIT: Updated as @mati865 requested here.