From 365ab8bce28596928f318c100c80a8e1ec0b0c43 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Thu, 5 Jan 2023 10:26:08 -0800 Subject: [PATCH] Implement rustc controlled whole-archive linking Rustc as of version 1.61.0 has support for controlling when whole-archive linking takes place, previous to this it tried to make a good guess about what you wanted, which worked most of the time. This is now implemented. Additionally, because meson breaks some rustc assumption about library naming on windows, we have to manually pass whole-archive libraries, bypassing rustc's interface and talking directly to the linker for MSVC (and those imitating it), which we were doing incorrectly. This has been fixed. Fixes: #10723 Fixes: #11247 Co-authored-by: Nirbheek Chauhan --- mesonbuild/backend/ninjabackend.py | 32 ++++++++++++++++++- test cases/rust/3 staticlib/meson.build | 6 ++-- test cases/rust/3 staticlib/other.rs | 5 +++ test cases/rust/3 staticlib/prog.rs | 4 ++- test cases/rust/3 staticlib/stuff.rs | 13 +++++++- test cases/rust/3 staticlib/value.c | 5 +++ test cases/rust/5 polyglot static/clib.c | 12 +++++++ test cases/rust/5 polyglot static/meson.build | 3 +- test cases/rust/5 polyglot static/prog.c | 5 ++- test cases/rust/5 polyglot static/stuff.rs | 2 +- test cases/rust/5 polyglot static/test.json | 2 +- 11 files changed, 78 insertions(+), 11 deletions(-) create mode 100644 test cases/rust/3 staticlib/other.rs create mode 100644 test cases/rust/3 staticlib/value.c create mode 100644 test cases/rust/5 polyglot static/clib.c diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 5722cbb6bcc2..c318997c931f 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -1905,7 +1905,7 @@ def generate_rust_target(self, target: build.BuildTarget) -> None: args += output linkdirs = mesonlib.OrderedSet() external_deps = target.external_deps.copy() - for d in itertools.chain(target.link_targets, target.link_whole_targets): + for d in target.link_targets: linkdirs.add(d.subdir) if d.uses_rust(): # specify `extern CRATE_NAME=OUTPUT_FILE` for each Rust @@ -1926,6 +1926,36 @@ def generate_rust_target(self, target: build.BuildTarget) -> None: # Rust uses -l for non rust dependencies, but we still need to # add dylib=foo args += ['-l', f'dylib={d.name}'] + + # Since 1.61.0 Rust has a special modifier for whole-archive linking, + # before that it would treat linking two static libraries as + # whole-archive linking. + # + # We have to disable bundling When whole-archvie is enabled + # See: https://github.com/rust-lang/rust/issues/99429 + if mesonlib.version_compare(rustc.version, '>= 1.61.0'): + whole_archive = ':+whole-archive,-bundle' + else: + whole_archive = '' + + for d in target.link_whole_targets: + linkdirs.add(d.subdir) + if d.uses_rust(): + # specify `extern CRATE_NAME=OUTPUT_FILE` for each Rust + # dependency, so that collisions with libraries in rustc's + # sysroot don't cause ambiguity + args += ['--extern', '{}={}'.format(d.name, os.path.join(d.subdir, d.filename))] + project_deps.append(RustDep(d.name, self.rust_crates[d.name].order)) + else: + # Rustc doesn't follow Meson's convention that static libraries + # are called .a, and import libraries are .lib, so we have to + # manually handle that. + if rustc.linker.id in {'link', 'lld-link'}: + for link_whole_arg in rustc.linker.get_link_whole_for([self.get_target_filename_for_linking(d)]): + args += ['-C', f'link-arg={link_whole_arg}'] + else: + args += ['-l', f'static{whole_archive}={d.name}'] + external_deps.extend(d.external_deps) for e in external_deps: for a in e.get_link_args(): if a.endswith(('.dll', '.so', '.dylib')): diff --git a/test cases/rust/3 staticlib/meson.build b/test cases/rust/3 staticlib/meson.build index 6769564637b8..cf8e1032e13b 100644 --- a/test cases/rust/3 staticlib/meson.build +++ b/test cases/rust/3 staticlib/meson.build @@ -1,5 +1,7 @@ -project('rust static library', 'rust') +project('rust static library', 'rust', 'c') -l = static_library('stuff', 'stuff.rs', install : true) +o = static_library('other', 'other.rs') +v = static_library('value', 'value.c') +l = static_library('stuff', 'stuff.rs', link_whole : [o, v], install : true) e = executable('prog', 'prog.rs', link_with : l, install : true) test('linktest', e) diff --git a/test cases/rust/3 staticlib/other.rs b/test cases/rust/3 staticlib/other.rs new file mode 100644 index 000000000000..037be33eed4a --- /dev/null +++ b/test cases/rust/3 staticlib/other.rs @@ -0,0 +1,5 @@ +pub fn explore( + value: i32, +) -> String { + format!("library{}string", value) +} diff --git a/test cases/rust/3 staticlib/prog.rs b/test cases/rust/3 staticlib/prog.rs index fbf31815910f..eee26539b363 100644 --- a/test cases/rust/3 staticlib/prog.rs +++ b/test cases/rust/3 staticlib/prog.rs @@ -1,3 +1,5 @@ extern crate stuff; -fn main() { println!("printing: {}", stuff::explore()); } +fn main() { + println!("printing: {}", stuff::explore()); +} diff --git a/test cases/rust/3 staticlib/stuff.rs b/test cases/rust/3 staticlib/stuff.rs index 8cabc62572e3..7cfcdffff119 100644 --- a/test cases/rust/3 staticlib/stuff.rs +++ b/test cases/rust/3 staticlib/stuff.rs @@ -1,3 +1,14 @@ #![crate_name = "stuff"] -pub fn explore() -> &'static str { "librarystring" } +extern crate other; + +extern "C" { + fn c_explore_value() -> i32; +} + +pub fn explore( +) -> String { + unsafe { + other::explore(c_explore_value()) + } +} diff --git a/test cases/rust/3 staticlib/value.c b/test cases/rust/3 staticlib/value.c new file mode 100644 index 000000000000..b71c8060aa22 --- /dev/null +++ b/test cases/rust/3 staticlib/value.c @@ -0,0 +1,5 @@ +int +c_explore_value (void) +{ + return 42; +} diff --git a/test cases/rust/5 polyglot static/clib.c b/test cases/rust/5 polyglot static/clib.c new file mode 100644 index 000000000000..366dbe592cb9 --- /dev/null +++ b/test cases/rust/5 polyglot static/clib.c @@ -0,0 +1,12 @@ +#include + +void hello_from_rust(void); + +static void hello_from_c(void) { + printf("Hello from C!\n"); +} + +void hello_from_both(void) { + hello_from_c(); + hello_from_rust(); +} diff --git a/test cases/rust/5 polyglot static/meson.build b/test cases/rust/5 polyglot static/meson.build index b2a44dad9790..bed7977abaea 100644 --- a/test cases/rust/5 polyglot static/meson.build +++ b/test cases/rust/5 polyglot static/meson.build @@ -8,7 +8,8 @@ deps = [ extra_winlibs = meson.get_compiler('c').get_id() in ['msvc', 'clang-cl'] ? ['userenv.lib', 'ws2_32.lib', 'bcrypt.lib'] : [] -l = static_library('stuff', 'stuff.rs', rust_crate_type : 'staticlib', install : true) +r = static_library('stuff', 'stuff.rs', rust_crate_type : 'staticlib') +l = static_library('clib', 'clib.c', link_with : r, install : true) e = executable('prog', 'prog.c', dependencies: deps, link_with : l, diff --git a/test cases/rust/5 polyglot static/prog.c b/test cases/rust/5 polyglot static/prog.c index dbbd8800aa5d..0a8e0d1baa4b 100644 --- a/test cases/rust/5 polyglot static/prog.c +++ b/test cases/rust/5 polyglot static/prog.c @@ -1,8 +1,7 @@ #include -void f(); +void hello_from_both(); int main(void) { - printf("Hello from C!\n"); - f(); + hello_from_both(); } diff --git a/test cases/rust/5 polyglot static/stuff.rs b/test cases/rust/5 polyglot static/stuff.rs index ecf623c644e1..3777ae85a68e 100644 --- a/test cases/rust/5 polyglot static/stuff.rs +++ b/test cases/rust/5 polyglot static/stuff.rs @@ -1,6 +1,6 @@ #![crate_name = "stuff"] #[no_mangle] -pub extern fn f() { +pub extern "C" fn hello_from_rust() { println!("Hello from Rust!"); } diff --git a/test cases/rust/5 polyglot static/test.json b/test cases/rust/5 polyglot static/test.json index 1d4eff4be6b2..cc0d2da7eefc 100644 --- a/test cases/rust/5 polyglot static/test.json +++ b/test cases/rust/5 polyglot static/test.json @@ -2,6 +2,6 @@ "installed": [ {"type": "exe", "file": "usr/bin/prog"}, {"type": "pdb", "file": "usr/bin/prog"}, - {"type": "file", "file": "usr/lib/libstuff.a"} + {"type": "file", "file": "usr/lib/libclib.a"} ] }