-
Notifications
You must be signed in to change notification settings - Fork 13.2k
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
Set external linkage on Rust functions defined with foreign ABIs. #9945
Conversation
This could currently be happening for one of two reasons:
I'm hesitant to land this without any tests. Linkage is something which is becoming quite tricky for us with lots of various cases here and there. I would expose it under a symbol with a particular name and then have that in an For the test, also be sure that it fails on current master and then passes afterwards. No use in committing tests that always work! I think that this location for setting the linkage is fine for now. I think in general this needs to be cleaned up (because linkage is set in so many different places), but that's another problem for another time. |
Another use case is in building a library that needs to use symbols in the main binary it is linked against. For example, a plugin opened with dlopen might need to find symbols in the main binary. (edit: I just read this again and realised it's basically a roundabout way of saying your first reason up there...) I have a test that shows the broken behaviour on untouched master and I am just confirming now that this changeset does in fact solve the problem. In the longer term ideally there'd be a function attribute for this purpose that can define the linkage of the function, such as I'll bump the next commit through once I'm happy with the test. |
@pcmattman: Are you compiling the Rust code with |
@@ -376,6 +376,9 @@ pub fn register_rust_fn_with_foreign_abi(ccx: @mut CrateContext, | |||
node_id, | |||
lib::llvm::CCallConv, | |||
llfn_ty); | |||
unsafe { | |||
llvm::LLVMSetLinkage(llfn, lib::llvm::ExternalLinkage as u32); |
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 was thinking about this, and let's actually move this to base::get_item_val
and you don't have to use the raw LLVM function (there's a helper lib::SetLinkage
I think)
We currently have linkage sprawling throughout, and good to try to keep it in one place!
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.
Agreed - much nicer in base::get_item_val
. Using the helper now as well.
PR updated and squashed into one commit as well.
After thinking about this, I'm not convinced that this is the correct approach to take. I think that you should be able to declare internal I think that the real bug in this situation is that when you're compiling an executable all functions are flagged as internal except for |
@alexcrichton: |
Agree we should not be setting linkage based on the |
Sure, no worries, I'm happy to consider alternative approaches. In the meantime I can solve the problem in my own project with With respect to the visibility system, with this change applied linkage is overridden to internal (implicitly, eek!) if the function is not visible. That is, if a foreign ABI function is not visible to the "parent" of the crate root, it will be emitted with internal linkage. An example (
Only However, as mentioned this internal linkage override is implicit - this patch does not actually explicitly check function visibility before setting external linkage. It would probably be best to make that behaviour explicit for foreign ABI functions, if it ends up being desirable. |
For now I think that our visibility system is sufficient enough to encode what should be exported and what should be kept internal. I thought that the code example you had compiles today, but if it doesn't then that's definitely a bug! |
Apologies, my previous comment was not incredibly clear. To clarify what I was trying to describe slightly (also noting that some of the syntax in my previous comment is not correct - more psuedocode-y), without this pull request applied,
The Now, with this PR applied,
So everything still compiles with or without - no bug there :-) - just an issue with symbol linkage. |
The If it's private when building a library but the path is fully public, that's a bug we should fix (without making it exposed from an executable). |
Good point. No, this is not with building a library, so all behaviour is as-expected and there is not a bug. I presume foreign ABI functions in a bin crate would be expected to be used as parameters to functions in C libraries as the like, rather than called by plugins or other external entities? |
@pcmattman: Yeah, it's just about the ABI. It's confusing that we use the same keyword for declaring blocks of external functions. We desperately need to offer other forms of building crates (static linking, whole program optimization), but I think exposing a symbol from an executable is always going to be an edge case where you probably end up using external compiler/linker calls anyway to link in whatever is going to call that function. |
There is a case where building a binary requires externalizing a symbol, without compiling with -c and linking the object file against some other object file importing Rust functions. That is, if you don't link against a library providing the mem* functions (memcpy, memmove, memset, etc.), LLVM can still generate calls to those functions. This is also the only case that can't be solved by moving everything to Rust (to remove the need for exporting Rust symbols to C or assembly). |
I don't really understand why it would have to be external for that case. Why wouldn't the calls to The current flagging of all functions as internal in a binary could be replaced by reachability-based visibility if it took into account the differences between executables and libraries. In a library, it would have to treat any calls from |
I also don't understand - I'm a bit confused now. It shouldn't be necessary to externalise a symbol such as Is there a code example that shows a binary failing to build because another symbol inside the crate does not have external linkage? |
Also, FWIW
Ahh, the wonderful rabbit hole of linkages, visibility, and reachability. :-) |
Relevant function from clang's CodeGen. |
I don't think |
mem* wouldn't be reachable without a hack, like what clang does for EDIT: Also see this test from my last PR, it should also work with |
I'm suggesting making fully public paths in an executable external again. |
I thought that this was going to make fully-public |
It's making non-fully-private items external too right now. Until the reachability check actually works and takes into account that I really don't understand why visibility would be tied to something exposing a C ABI rather than a Rust ABI. All normal crates are exposing Rust ABI functions. There's nothing stopping interfacing with a Rust ABI function, we just don't guarantee that the ABI remains the same between Rust versions. A library exposing a stable ABI has to go out of the way to make sure none of the types is the signature is ever changed, such as never adding a private field to one. |
@thestinger what do you mean by non-fully-private in this context? |
As in this patch results in functions with paths that are not fully public being exposed as external symbols. It removes the ability to expose only the entry point. |
@thestinger is this in reference to the example in my past comment (at #9945 (comment)), or are there additional cases I haven't considered where functions that are not fully public are exposed by this patch? |
mod foo {
pub extern "C" fn bar() {}
}
pub extern "C" fn baz() {
foo::bar()
}
fn main() {} |
Yup, you're right - after applying
An example of possible desired output would be:
(as |
Okay, I broke the PR with my last push (... figuring that out now... - update: fixed!), but I now have the following output from
(different addresses as this was done on a different host) |
The latest commit adds a |
Fixed broken test - looks like my testing of the tests used a stale file? Full testsuite ( |
Tests failed as OSX doesn't successfully link libraries unless it can resolve all symbols at library creation time (or it has been passed The point of failure is in building the auxiliary library rather than actually executing the test proper. |
I have removed the It could be a The |
Most recent failure due to an undefined reference to @alexcrichton you mentioned yesterday in IRC this happens when |
Once by changes land for extracting libuv to another crate, this should be ready for another go (although it will probably need a rebase). |
This is now freshly rebased to latest master. |
OK after thinking about this, I don't think that this is the right way to do things, and I don't think that the right way to do things will involve this sort of management regardless. I don't think that this should be merged now because I believe that this is going in the opposite direction from where it should. The results of privacy should not be at all used inside of codegen. The privacy of a function is a difficult idea and with this change you would be forced to decide whether privacy or reachability is your visibility constraint. I still believe that we should have a unified "set the linkage of this LLVM value" function, but that may be very far off. For now, I believe that all of these changes should be moved inside of the reachability phase of the compiler instead of the codegen phase. Additionally, I think the check to see if the compiler is building a library and if so mark everything as internal should be moved to reachability as well. The best way to begin unifying how linkage visibility is determined is to begin using one source of information for visibility, and that source should be reachability. So to merge this, I would rather see code removed from trans and moved to reachability (with appropriate checks for building a library or not) and then trans should "do the right thing". Additionally, I'd like to see some more comprehensive tests no matter what comes out of this. I don't think that the way you've set up the tests right now is the best way to do it. It involves weird linker hacks on OSX, won't work at all on windows, and only works by luck on linux. Instead, I would be much more comfortable using the Does that sound OK with you? I can open an issue for this as well if you don't have time to get around to this. I believe though that this is more future-proof along with moving the code in the compiler to where it should be anyway. |
Sounds OK to me - now's the time to get it right, rather than try and fix it later. I should be able to get around to this, assuming there's no significant urgency (~1-2 weeks worst-case, maybe?). Happy to release to somebody else though, if time is of the essence. |
You can certainly claim it! We're certainly in no rush for this, and if you need any help feel free to ping me! |
Hm, so github thinks that this pull request has an infinite number of commits. It sounds like this may need to get reopened. (not sure why it's closed in the first place, but something looks awry). |
Oooer that does not look right. I must've accidentally pushed origin/master into the remote. I'll have a look at figuring out why this is broken when I pick it back up again. Should never have done the work on master in the first place :-) |
…ogiq Re-enable `uninlined_format_args` on multiline `format!` fix rust-lang/rust-clippy#9719 There was an issue with the code suggestion which can be sometimes completely broken (fortunately when applied it's valid), so we do not show it. changelog: [`uninlined_format_args`] re-enable for multiline format expression, but do not show the literal code suggestion in those cases
This fixes an issue where
extern "C"
functions would have internal linkage, making them invisible to anything that might be linked against the resulting object. In my use case, a Rust function is being called from assembly, and because the Rust symbol did not have the right linkage, I ran into linker errors.This has also now been applied to bare Rust functions as well as statics that are publicly visible; that is, those which have a fully public path.