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

Linker error when calling inline extern "C" function #61820

Closed
thedataking opened this issue Jun 14, 2019 · 4 comments
Closed

Linker error when calling inline extern "C" function #61820

thedataking opened this issue Jun 14, 2019 · 4 comments
Labels
A-FFI Area: Foreign function interface (FFI) A-linkage Area: linking into static, shared libraries and binaries C-bug Category: This is a bug. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@thedataking
Copy link

Minimized example (problem discovered when translating bzip2 to Rust with C2Rust).

main.rs:

mod caller;
#[no_mangle]
#[inline]
pub unsafe extern "C" fn ext_fn() -> i32 { 0 }
pub fn main() { caller::call(); }

caller.rs

extern "C" { #[no_mangle] fn ext_fn() -> i32; }
pub fn call() { unsafe { ext_fn() }; }

NOTE: the problem has to do with the inline attribute; linking succeeds when removed.

$ rustc --version
rustc 1.35.0 (3c235d560 2019-05-20)
$ cargo build
undefined reference to `ext_fn'

whereas the following works (on stable and nightly):

$ RUSTFLAGS="-C link-dead-code" cargo build 

Maybe this is not expected to work since ext_fn isn't defined in a foreign library? On the other hand, it seems like a bug since using the inline attribute shouldn't prevent linking.

@jonas-schievink jonas-schievink added A-linkage Area: linking into static, shared libraries and binaries C-bug Category: This is a bug. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. A-FFI Area: Foreign function interface (FFI) labels Jun 14, 2019
@nagisa
Copy link
Member

nagisa commented Jun 16, 2019

This is not a bug, I think. The catch here is that ext_fn is defined in an "executable", so there is nothing that semantically requires the symbol ext_fn to exist in the final artefact – in fact there are multiple stages in compilation which may end up removing the symbol/function/etc – optimisation, ThinLTO, LTO, etc. #[inline] is a red herring here.

There are ways to ensure that a symbol ends up being present in an executable, regardless of whether inline attribute is specified or not. It involves a combination of #[used] attribute and, possibly, a linker script or options. Another option is to take the address of the function somewhere in the code in a way that’s opaque to the compiler and linker.

@thedataking
Copy link
Author

@nagisa appreciate the suggestions. A couple of thoughts:

  • the #[used] attribute can only be applied to static items according to the docs.

  • RUSTFLAGS="-C opt-level=0" cargo build triggers the linking error too so I'm not convinced that optimization is the root cause. (Maybe ld.bfd eliminates the function; I didn't see a way to disable optimizations that would affect linking of executables though.)

@nagisa
Copy link
Member

nagisa commented Jun 16, 2019

You would usually use the #[used] flag as such: #[used] static _USED_THINGS: fn() -> i32 = ext_fn. This ensures that the ext_fn symbol gets to the linker. It is a fairly low-level and rarely necessary feature, so making it convenient to use was never much of a concern.

However, in your specific case it also matters how the compiler distributes code between multiple codegen units. As the compiler has no reason to know about the relationship between main::ext_fn and caller::ext_fn, it has no means to construct the codegen units in a correct way or pass them in a correct order to the linker. So even if the functions were generated, the objects may be given to the linker in an incorrect order and linking still would fail.

To elaborate on what the semantics are:

  • caller::ext_fn is declared to be an externally supplied function by some linked-in library;
    • Other than that rustc only knows the type of this function (unsafe extern fn() -> i32);
    • Notably, it is not required for the compiler to establish knowledge that main::ext_fn is the expected definition for this declaration.
  • main::ext_fn is a function definition with no uses as far as rustc is concerned:
    • There are 0 uses of this name via regular regular means available in Rust (name resolution);
    • The function is public, but the generated output is an executable, so visibility here is irrelevant;
  • rustc is not required to keep or process dead function definitions past what is required to check correctness of the program.
    • I’m actually not sure why it does generate machine code for the function when #[inline] is omitted!
    • This notably means that “optimisation” of eliminating such dead functions may occur even before code optimisation occurs (and thus -Copt-level is irrelevant).

The correct way to handle this would be to modify the auto-generated code to refer to the function via the usual rust syntax (i.e. in caller.rs write pub use super::ext_fn instead of extern { ... }). Another approach, as this issue at its core arises from incompatible compilation models between C and Rust modules, would be to make c2rust generate a crate for each C object instead of a rust module.

Alternatively you can hack around the issue with a #[used] static and a -Ccodegen-units=1 flag, however this hack may break in the future as other features are implemented in rustc, so I would not recommend that.

@thedataking
Copy link
Author

It appears that we are able to work around this issue by adding #[linkage = "external"] to Rust functions that were translated from inline & extern C functions (C2Rust PR). This is a bit cleaner than emitting static variables decorated with the #[used] attribute. Nevertheless, a huge thanks to @nagisa for chiming in; I agree with their assessment that this is likely not a bug so I'm closing the issue.

Also, it turns out that -C link-dead-code avoids the linker issue is because it enables "eager" monomorphization which causes function items to be emitted even if they are not referenced.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-FFI Area: Foreign function interface (FFI) A-linkage Area: linking into static, shared libraries and binaries C-bug Category: This is a bug. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

3 participants