Skip to content

[WIP] add -Z split_bundled_libs #99773

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

Closed
wants to merge 14 commits into from
90 changes: 62 additions & 28 deletions compiler/rustc_codegen_ssa/src/back/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,36 +318,50 @@ fn link_rlib<'a, B: ArchiveBuilder<'a>>(
// metadata of the rlib we're generating somehow.
for lib in codegen_results.crate_info.used_libraries.iter() {
match lib.kind {
NativeLibKind::Static { bundle: None | Some(true), whole_archive: Some(true) }
if flavor == RlibFlavor::Normal =>
{
// Don't allow mixing +bundle with +whole_archive since an rlib may contain
// multiple native libs, some of which are +whole-archive and some of which are
// -whole-archive and it isn't clear how we can currently handle such a
// situation correctly.
// See https://github.com/rust-lang/rust/issues/88085#issuecomment-901050897
sess.err(
"the linking modifiers `+bundle` and `+whole-archive` are not compatible \
with each other when generating rlibs",
NativeLibKind::Static { bundle: None | Some(true), whole_archive } => {
if flavor == RlibFlavor::Normal
&& whole_archive == Some(true)
&& !sess.opts.unstable_opts.split_bundled_libs
{
// Don't allow mixing +bundle with +whole_archive since an rlib may contain
// multiple native libs, some of which are +whole-archive and some of which are
// -whole-archive and it isn't clear how we can currently handle such a
// situation correctly.
// See https://github.com/rust-lang/rust/issues/88085#issuecomment-901050897
sess.err(
"the linking modifiers `+bundle` and `+whole-archive` are not compatible \
with each other when generating rlibs",
);
}

let Some(name) = lib.name else {
continue;
};

let location = find_library(
name.as_str(),
lib.verbatim.unwrap_or(false),
&lib_search_paths,
sess,
);

if (flavor == RlibFlavor::Normal) && sess.opts.unstable_opts.split_bundled_libs {
let suffix = &sess.target.staticlib_suffix;
let crate_name = out_filename.to_str().unwrap();
let bundle_lib = PathBuf::from(&format!("{crate_name}.bundle.{name}{suffix}"));
fs::copy(location, bundle_lib).unwrap();
continue;
}

ab.add_archive(&location, |_| false).unwrap_or_else(|e| {
sess.fatal(&format!(
"failed to add native library {}: {}",
location.to_string_lossy(),
e
));
});
}
NativeLibKind::Static { bundle: None | Some(true), .. } => {}
NativeLibKind::Static { bundle: Some(false), .. }
| NativeLibKind::Dylib { .. }
| NativeLibKind::Framework { .. }
| NativeLibKind::RawDylib
| NativeLibKind::Unspecified => continue,
}
if let Some(name) = lib.name {
let location =
find_library(name.as_str(), lib.verbatim.unwrap_or(false), &lib_search_paths, sess);
ab.add_archive(&location, |_| false).unwrap_or_else(|e| {
sess.fatal(&format!(
"failed to add native library {}: {}",
location.to_string_lossy(),
e
));
});
_ => {}
}
}

Expand Down Expand Up @@ -2362,6 +2376,26 @@ fn add_upstream_rust_crates<'a, B: ArchiveBuilder<'a>>(
(lib.name, lib.kind, lib.verbatim)
};

if sess.opts.unstable_opts.split_bundled_libs {
if let NativeLibKind::Static {
bundle: Some(true) | None,
whole_archive,
} = lib.kind
{
let suffix = &sess.target.staticlib_suffix;
let crate_path = src.paths().next().unwrap().to_str().unwrap();
let bundle_lib =
PathBuf::from(&format!("{crate_path}.bundle.{name}{suffix}"));
if whole_archive == Some(true) {
cmd.link_whole_rlib(&bundle_lib);
} else {
cmd.link_rlib(&bundle_lib);
}

continue;
}
}

if let NativeLibKind::Static { bundle: Some(false), whole_archive } =
lib.kind
{
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_session/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1504,6 +1504,8 @@ options! {
"control if mem::uninitialized and mem::zeroed panic on more UB"),
strip: Strip = (Strip::None, parse_strip, [UNTRACKED],
"tell the linker which information to strip (`none` (default), `debuginfo` or `symbols`)"),
split_bundled_libs: bool = (false, parse_bool, [TRACKED],
"if libfoo.rlib is the rlib, then libfoo.rlib.bundle.* are the corresponding bundled static libraries"),
split_dwarf_kind: SplitDwarfKind = (SplitDwarfKind::Split, parse_split_dwarf_kind, [TRACKED],
"split dwarf variant (only if -Csplit-debuginfo is enabled and on relevant platform)
(default: `split`)
Expand Down
35 changes: 35 additions & 0 deletions src/test/run-make/native-link-modifier-bundle-split/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# ignore-cross-compile
# ignore-windows-msvc

-include ../../run-make-fulldeps/tools.mk

# We're using the llvm-nm instead of the system nm to ensure it is compatible
# with the LLVM bitcode generated by rustc.
NM = "$(LLVM_BIN_DIR)"/llvm-nm
SPLIT = "-Zsplit-bundled-libs"
BUNDLED_LIB = "libbundled.rlib.bundle.native-staticlib.a"

all: $(call NATIVE_STATICLIB,native-staticlib)
# Build a staticlib and a rlib, the `native_func` symbol will be bundled only into staticlib
$(RUSTC) bundled.rs --crate-type=staticlib --crate-type=rlib $(SPLIT)
$(NM) $(TMPDIR)/libbundled.a | $(CGREP) -e "T _*native_func"
$(NM) $(TMPDIR)/libbundled.a | $(CGREP) -e "U _*native_func"
$(NM) $(TMPDIR)/libbundled.rlib | $(CGREP) -ve "T _*native_func"
$(NM) $(TMPDIR)/libbundled.rlib | $(CGREP) -e "U _*native_func"

# Build a staticlib and a rlib, the `native_func` symbol will not be bundled into it
$(RUSTC) non-bundled.rs --crate-type=staticlib --crate-type=rlib $(SPLIT)
$(NM) $(TMPDIR)/libnon_bundled.a | $(CGREP) -ve "T _*native_func"
$(NM) $(TMPDIR)/libnon_bundled.a | $(CGREP) -e "U _*native_func"
$(NM) $(TMPDIR)/libnon_bundled.rlib | $(CGREP) -ve "T _*native_func"
$(NM) $(TMPDIR)/libnon_bundled.rlib | $(CGREP) -e "U _*native_func"

# Build a cdylib, 'BUNDLED_LIB' will appear on the linker line
# The cdylib will contain the `native_func` symbol in the end
$(RUSTC) cdylib-bundled.rs --crate-type=cdylib --print link-args $(SPLIT) | $(CGREP) -e $(BUNDLED_LIB)
$(NM) $(call DYLIB,cdylib_bundled) | $(CGREP) -e "[Tt] _*native_func"

# Build a cdylib, `native-staticlib` will appear on the linker line because it was not bundled previously
# The cdylib will contain the `native_func` symbol in the end
$(RUSTC) cdylib-non-bundled.rs --crate-type=cdylib --print link-args $(SPLIT) | $(CGREP) -e '-l[" ]*native-staticlib'
$(NM) $(call DYLIB,cdylib_non_bundled) | $(CGREP) -e "[Tt] _*native_func"
11 changes: 11 additions & 0 deletions src/test/run-make/native-link-modifier-bundle-split/bundled.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#[link(name = "native-staticlib", kind = "static", modifiers = "+bundle")]
extern "C" {
pub fn native_func();
}

#[no_mangle]
pub extern "C" fn wrapped_func() {
unsafe {
native_func();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
extern crate bundled;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
extern crate non_bundled;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
void native_func() {}
11 changes: 11 additions & 0 deletions src/test/run-make/native-link-modifier-bundle-split/non-bundled.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#[link(name = "native-staticlib", kind = "static", modifiers = "-bundle")]
extern "C" {
pub fn native_func();
}

#[no_mangle]
pub extern "C" fn wrapped_func() {
unsafe {
native_func();
}
}
1 change: 1 addition & 0 deletions src/test/rustdoc-ui/z-help.stdout
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@
-Z stack-protector=val -- control stack smash protection strategy (`rustc --print stack-protector-strategies` for details)
-Z strict-init-checks=val -- control if mem::uninitialized and mem::zeroed panic on more UB
-Z strip=val -- tell the linker which information to strip (`none` (default), `debuginfo` or `symbols`)
-Z split-bundled-libs=val -- if libfoo.rlib is the rlib, then libfoo.rlib.bundle.* are the corresponding bundled static libraries
-Z split-dwarf-kind=val -- split dwarf variant (only if -Csplit-debuginfo is enabled and on relevant platform)
(default: `split`)

Expand Down