Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compiler/rustc_codegen_ssa/src/back/linker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1862,7 +1862,7 @@ fn exported_symbols_for_proc_macro_crate(tcx: TyCtxt<'_>) -> Vec<(String, Symbol
}

let stable_crate_id = tcx.stable_crate_id(LOCAL_CRATE);
let proc_macro_decls_name = tcx.sess.generate_proc_macro_decls_symbol(stable_crate_id);
let proc_macro_decls_name = rustc_session::generate_proc_macro_decls_symbol(stable_crate_id);

vec![(proc_macro_decls_name, SymbolExportKind::Data)]
}
Expand Down
142 changes: 2 additions & 140 deletions compiler/rustc_metadata/src/creader.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
//! Validates all used crates and extern libraries and loads their metadata

use std::collections::BTreeMap;
use std::error::Error;
use std::path::Path;
use std::str::FromStr;
use std::time::Duration;
use std::{cmp, env, iter};

use rustc_ast::expand::allocator::{ALLOC_ERROR_HANDLER, AllocatorKind, global_fn_name};
Expand All @@ -15,7 +13,6 @@ use rustc_data_structures::svh::Svh;
use rustc_data_structures::sync::{self, FreezeReadGuard, FreezeWriteGuard};
use rustc_data_structures::unord::UnordMap;
use rustc_expand::base::SyntaxExtension;
use rustc_fs_util::try_canonicalize;
use rustc_hir as hir;
use rustc_hir::def_id::{CrateNum, LOCAL_CRATE, LocalDefId, StableCrateId};
use rustc_hir::definitions::Definitions;
Expand Down Expand Up @@ -653,7 +650,7 @@ impl CStore {
None => (&source, &crate_root),
};
let dlsym_dylib = dlsym_source.dylib.as_ref().expect("no dylib for a proc-macro crate");
Some(self.dlsym_proc_macros(tcx.sess, dlsym_dylib, dlsym_root.stable_crate_id())?)
Some(self.dlsym_proc_macros(dlsym_dylib, dlsym_root.stable_crate_id())?)
} else {
None
};
Expand Down Expand Up @@ -948,31 +945,10 @@ impl CStore {

fn dlsym_proc_macros(
&self,
sess: &Session,
path: &Path,
stable_crate_id: StableCrateId,
) -> Result<&'static [ProcMacroClient], CrateError> {
let sym_name = sess.generate_proc_macro_decls_symbol(stable_crate_id);
debug!("trying to dlsym proc_macros {} for symbol `{}`", path.display(), sym_name);

unsafe {
// FIXME(bjorn3) this depends on the unstable slice memory layout
let result = load_symbol_from_dylib::<*const &[ProcMacroClient]>(path, &sym_name);
match result {
Ok(result) => {
debug!("loaded dlsym proc_macros {} for symbol `{}`", path.display(), sym_name);
Ok(*result)
}
Err(err) => {
debug!(
"failed to dlsym proc_macros {} for symbol `{}`",
path.display(),
sym_name
);
Err(err.into())
}
}
}
Ok(crate::host_dylib::dlsym_proc_macros(path, stable_crate_id)?)
}

fn inject_panic_runtime(&mut self, tcx: TyCtxt<'_>, krate: &ast::Crate) {
Expand Down Expand Up @@ -1409,117 +1385,3 @@ fn fn_spans(krate: &ast::Crate, name: Symbol) -> Vec<Span> {
visit::walk_crate(&mut f, krate);
f.spans
}

fn format_dlopen_err(e: &(dyn std::error::Error + 'static)) -> String {
e.sources().map(|e| format!(": {e}")).collect()
}

fn attempt_load_dylib(path: &Path) -> Result<libloading::Library, libloading::Error> {
#[cfg(target_os = "aix")]
if let Some(ext) = path.extension()
&& ext.eq("a")
{
// On AIX, we ship all libraries as .a big_af archive
// the expected format is lib<name>.a(libname.so) for the actual
// dynamic library
let library_name = path.file_stem().expect("expect a library name");
let mut archive_member = std::ffi::OsString::from("a(");
archive_member.push(library_name);
archive_member.push(".so)");
let new_path = path.with_extension(archive_member);

// On AIX, we need RTLD_MEMBER to dlopen an archived shared
let flags = libc::RTLD_LAZY | libc::RTLD_LOCAL | libc::RTLD_MEMBER;
return unsafe { libloading::os::unix::Library::open(Some(&new_path), flags) }
.map(|lib| lib.into());
}

unsafe { libloading::Library::new(&path) }
}

// On Windows the compiler would sometimes intermittently fail to open the
// proc-macro DLL with `Error::LoadLibraryExW`. It is suspected that something in the
// system still holds a lock on the file, so we retry a few times before calling it
// an error.
fn load_dylib(path: &Path, max_attempts: usize) -> Result<libloading::Library, String> {
assert!(max_attempts > 0);

let mut last_error = None;

for attempt in 0..max_attempts {
debug!("Attempt to load proc-macro `{}`.", path.display());
match attempt_load_dylib(path) {
Ok(lib) => {
if attempt > 0 {
debug!(
"Loaded proc-macro `{}` after {} attempts.",
path.display(),
attempt + 1
);
}
return Ok(lib);
}
Err(err) => {
// Only try to recover from this specific error.
if !matches!(err, libloading::Error::LoadLibraryExW { .. }) {
debug!("Failed to load proc-macro `{}`. Not retrying", path.display());
let err = format_dlopen_err(&err);
// We include the path of the dylib in the error ourselves, so
// if it's in the error, we strip it.
if let Some(err) = err.strip_prefix(&format!(": {}", path.display())) {
return Err(err.to_string());
}
return Err(err);
}

last_error = Some(err);
std::thread::sleep(Duration::from_millis(100));
debug!("Failed to load proc-macro `{}`. Retrying.", path.display());
}
}
}

debug!("Failed to load proc-macro `{}` even after {} attempts.", path.display(), max_attempts);

let last_error = last_error.unwrap();
let message = if let Some(src) = last_error.source() {
format!("{} ({src}) (retried {max_attempts} times)", format_dlopen_err(&last_error))
} else {
format!("{} (retried {max_attempts} times)", format_dlopen_err(&last_error))
};
Err(message)
}

pub enum DylibError {
DlOpen(String, String),
DlSym(String, String),
}

impl From<DylibError> for CrateError {
fn from(err: DylibError) -> CrateError {
match err {
DylibError::DlOpen(path, err) => CrateError::DlOpen(path, err),
DylibError::DlSym(path, err) => CrateError::DlSym(path, err),
}
}
}

pub unsafe fn load_symbol_from_dylib<T: Copy>(
path: &Path,
sym_name: &str,
) -> Result<T, DylibError> {
// Make sure the path contains a / or the linker will search for it.
let path = try_canonicalize(path).unwrap();
let lib =
load_dylib(&path, 5).map_err(|err| DylibError::DlOpen(path.display().to_string(), err))?;

let sym = unsafe { lib.get::<T>(sym_name.as_bytes()) }
.map_err(|err| DylibError::DlSym(path.display().to_string(), format_dlopen_err(&err)))?;

// Intentionally leak the dynamic library. We can't ever unload it
// since the library can make things that will live arbitrarily long.
let sym = unsafe { sym.into_raw() };
std::mem::forget(lib);

Ok(*sym)
}
147 changes: 147 additions & 0 deletions compiler/rustc_metadata/src/host_dylib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
use std::error::Error;
use std::path::Path;
use std::time::Duration;

use rustc_fs_util::try_canonicalize;
use rustc_proc_macro::bridge::client::Client as ProcMacroClient;
use rustc_session::StableCrateId;
use tracing::debug;

use crate::locator::CrateError;

fn format_dlopen_err(e: &(dyn std::error::Error + 'static)) -> String {
e.sources().map(|e| format!(": {e}")).collect()
}

fn attempt_load_dylib(path: &Path) -> Result<libloading::Library, libloading::Error> {
#[cfg(target_os = "aix")]
if let Some(ext) = path.extension()
&& ext.eq("a")
{
// On AIX, we ship all libraries as .a big_af archive
// the expected format is lib<name>.a(libname.so) for the actual
// dynamic library
let library_name = path.file_stem().expect("expect a library name");
let mut archive_member = std::ffi::OsString::from("a(");
archive_member.push(library_name);
archive_member.push(".so)");
let new_path = path.with_extension(archive_member);

// On AIX, we need RTLD_MEMBER to dlopen an archived shared
let flags = libc::RTLD_LAZY | libc::RTLD_LOCAL | libc::RTLD_MEMBER;
return unsafe { libloading::os::unix::Library::open(Some(&new_path), flags) }
.map(|lib| lib.into());
}

unsafe { libloading::Library::new(&path) }
}

// On Windows the compiler would sometimes intermittently fail to open the
// proc-macro DLL with `Error::LoadLibraryExW`. It is suspected that something in the
// system still holds a lock on the file, so we retry a few times before calling it
// an error.
fn load_dylib(path: &Path, max_attempts: usize) -> Result<libloading::Library, String> {
assert!(max_attempts > 0);

let mut last_error = None;

for attempt in 0..max_attempts {
debug!("Attempt to load proc-macro `{}`.", path.display());
match attempt_load_dylib(path) {
Ok(lib) => {
if attempt > 0 {
debug!(
"Loaded proc-macro `{}` after {} attempts.",
path.display(),
attempt + 1
);
}
return Ok(lib);
}
Err(err) => {
// Only try to recover from this specific error.
if !matches!(err, libloading::Error::LoadLibraryExW { .. }) {
debug!("Failed to load proc-macro `{}`. Not retrying", path.display());
let err = format_dlopen_err(&err);
// We include the path of the dylib in the error ourselves, so
// if it's in the error, we strip it.
if let Some(err) = err.strip_prefix(&format!(": {}", path.display())) {
return Err(err.to_string());
}
return Err(err);
}

last_error = Some(err);
std::thread::sleep(Duration::from_millis(100));
debug!("Failed to load proc-macro `{}`. Retrying.", path.display());
}
}
}

debug!("Failed to load proc-macro `{}` even after {} attempts.", path.display(), max_attempts);

let last_error = last_error.unwrap();
let message = if let Some(src) = last_error.source() {
format!("{} ({src}) (retried {max_attempts} times)", format_dlopen_err(&last_error))
} else {
format!("{} (retried {max_attempts} times)", format_dlopen_err(&last_error))
};
Err(message)
}

pub enum DylibError {
DlOpen(String, String),
DlSym(String, String),
}

impl From<DylibError> for CrateError {
fn from(err: DylibError) -> CrateError {
match err {
DylibError::DlOpen(path, err) => CrateError::DlOpen(path, err),
DylibError::DlSym(path, err) => CrateError::DlSym(path, err),
}
}
}

pub unsafe fn load_symbol_from_dylib<T: Copy>(
path: &Path,
sym_name: &str,
) -> Result<T, DylibError> {
// Make sure the path contains a / or the linker will search for it.
let path = try_canonicalize(path).unwrap();
let lib =
load_dylib(&path, 5).map_err(|err| DylibError::DlOpen(path.display().to_string(), err))?;

let sym = unsafe { lib.get::<T>(sym_name.as_bytes()) }
.map_err(|err| DylibError::DlSym(path.display().to_string(), format_dlopen_err(&err)))?;

// Intentionally leak the dynamic library. We can't ever unload it
// since the library can make things that will live arbitrarily long.
let sym = unsafe { sym.into_raw() };
std::mem::forget(lib);

Ok(*sym)
}

pub(crate) fn dlsym_proc_macros(
path: &Path,
stable_crate_id: StableCrateId,
) -> Result<&'static [ProcMacroClient], DylibError> {
let sym_name = rustc_session::generate_proc_macro_decls_symbol(stable_crate_id);
debug!("trying to dlsym proc_macros {} for symbol `{}`", path.display(), sym_name);

unsafe {
// FIXME(bjorn3) this depends on the unstable slice memory layout
let result = crate::load_symbol_from_dylib::<*const &[ProcMacroClient]>(path, &sym_name);
match result {
Ok(result) => {
debug!("loaded dlsym proc_macros {} for symbol `{}`", path.display(), sym_name);
Ok(*result)
}
Err(err) => {
debug!("failed to dlsym proc_macros {} for symbol `{}`", path.display(), sym_name);
Err(err.into())
}
}
}
}
3 changes: 2 additions & 1 deletion compiler/rustc_metadata/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub use rmeta::provide;
mod dependency_format;
mod eii;
mod foreign_modules;
mod host_dylib;
mod native_libs;
mod rmeta;

Expand All @@ -25,8 +26,8 @@ pub mod errors;
pub mod fs;
pub mod locator;

pub use creader::{DylibError, load_symbol_from_dylib};
pub use fs::{METADATA_FILENAME, emit_wrapper_file};
pub use host_dylib::{DylibError, load_symbol_from_dylib};
pub use native_libs::{
NativeLibSearchFallback, find_native_static_library, try_find_native_dynamic_library,
try_find_native_static_library, walk_native_lib_search_dirs,
Expand Down
Loading
Loading