1
Fork 0

Rollup merge of #128206 - bjorn3:import_lib_writing_refactor, r=jieyouxu

Make create_dll_import_lib easier to implement

This will make it easier to implement raw-dylib support in cg_clif and cg_gcc. This PR doesn't yet include an create_dll_import_lib implementation for cg_clif as I need to correctly implement dllimport in cg_clif first before raw-dylib can work at all with cg_clif.

Required for https://github.com/rust-lang/rustc_codegen_cranelift/issues/1345
This commit is contained in:
Guillaume Gomez 2024-08-07 15:59:35 +02:00 committed by GitHub
commit 355eb9c79f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 332 additions and 317 deletions

View file

@ -1,4 +1,6 @@
use std::env;
use std::error::Error;
use std::ffi::OsString;
use std::fs::{self, File};
use std::io::{self, Write};
use std::path::{Path, PathBuf};
@ -9,7 +11,6 @@ use object::read::archive::ArchiveFile;
use object::read::macho::FatArch;
use rustc_data_structures::fx::FxIndexSet;
use rustc_data_structures::memmap::Mmap;
use rustc_session::cstore::DllImport;
use rustc_session::Session;
use rustc_span::symbol::Symbol;
use tempfile::Builder as TempFileBuilder;
@ -17,6 +18,7 @@ use tempfile::Builder as TempFileBuilder;
use super::metadata::search_for_section;
// Re-exporting for rustc_codegen_llvm::back::archive
pub use crate::errors::{ArchiveBuildFailure, ExtractBundledLibsError, UnknownArchiveKind};
use crate::errors::{DlltoolFailImportLibrary, ErrorCallingDllTool, ErrorWritingDEFFile};
pub trait ArchiveBuilderBuilder {
fn new_archive_builder<'a>(&self, sess: &'a Session) -> Box<dyn ArchiveBuilder + 'a>;
@ -30,10 +32,9 @@ pub trait ArchiveBuilderBuilder {
&self,
sess: &Session,
lib_name: &str,
dll_imports: &[DllImport],
tmpdir: &Path,
is_direct_dependency: bool,
) -> PathBuf;
import_name_and_ordinal_vector: Vec<(String, Option<u16>)>,
output_path: &Path,
);
fn extract_bundled_libs<'a>(
&'a self,
@ -72,6 +73,130 @@ pub trait ArchiveBuilderBuilder {
}
}
pub fn create_mingw_dll_import_lib(
sess: &Session,
lib_name: &str,
import_name_and_ordinal_vector: Vec<(String, Option<u16>)>,
output_path: &Path,
) {
let def_file_path = output_path.with_extension("def");
let def_file_content = format!(
"EXPORTS\n{}",
import_name_and_ordinal_vector
.into_iter()
.map(|(name, ordinal)| {
match ordinal {
Some(n) => format!("{name} @{n} NONAME"),
None => name,
}
})
.collect::<Vec<String>>()
.join("\n")
);
match std::fs::write(&def_file_path, def_file_content) {
Ok(_) => {}
Err(e) => {
sess.dcx().emit_fatal(ErrorWritingDEFFile { error: e });
}
};
// --no-leading-underscore: For the `import_name_type` feature to work, we need to be
// able to control the *exact* spelling of each of the symbols that are being imported:
// hence we don't want `dlltool` adding leading underscores automatically.
let dlltool = find_binutils_dlltool(sess);
let temp_prefix = {
let mut path = PathBuf::from(&output_path);
path.pop();
path.push(lib_name);
path
};
// dlltool target architecture args from:
// https://github.com/llvm/llvm-project-release-prs/blob/llvmorg-15.0.6/llvm/lib/ToolDrivers/llvm-dlltool/DlltoolDriver.cpp#L69
let (dlltool_target_arch, dlltool_target_bitness) = match sess.target.arch.as_ref() {
"x86_64" => ("i386:x86-64", "--64"),
"x86" => ("i386", "--32"),
"aarch64" => ("arm64", "--64"),
"arm" => ("arm", "--32"),
_ => panic!("unsupported arch {}", sess.target.arch),
};
let mut dlltool_cmd = std::process::Command::new(&dlltool);
dlltool_cmd
.arg("-d")
.arg(def_file_path)
.arg("-D")
.arg(lib_name)
.arg("-l")
.arg(&output_path)
.arg("-m")
.arg(dlltool_target_arch)
.arg("-f")
.arg(dlltool_target_bitness)
.arg("--no-leading-underscore")
.arg("--temp-prefix")
.arg(temp_prefix);
match dlltool_cmd.output() {
Err(e) => {
sess.dcx().emit_fatal(ErrorCallingDllTool {
dlltool_path: dlltool.to_string_lossy(),
error: e,
});
}
// dlltool returns '0' on failure, so check for error output instead.
Ok(output) if !output.stderr.is_empty() => {
sess.dcx().emit_fatal(DlltoolFailImportLibrary {
dlltool_path: dlltool.to_string_lossy(),
dlltool_args: dlltool_cmd
.get_args()
.map(|arg| arg.to_string_lossy())
.collect::<Vec<_>>()
.join(" "),
stdout: String::from_utf8_lossy(&output.stdout),
stderr: String::from_utf8_lossy(&output.stderr),
})
}
_ => {}
}
}
fn find_binutils_dlltool(sess: &Session) -> OsString {
assert!(sess.target.options.is_like_windows && !sess.target.options.is_like_msvc);
if let Some(dlltool_path) = &sess.opts.cg.dlltool {
return dlltool_path.clone().into_os_string();
}
let tool_name: OsString = if sess.host.options.is_like_windows {
// If we're compiling on Windows, always use "dlltool.exe".
"dlltool.exe"
} else {
// On other platforms, use the architecture-specific name.
match sess.target.arch.as_ref() {
"x86_64" => "x86_64-w64-mingw32-dlltool",
"x86" => "i686-w64-mingw32-dlltool",
"aarch64" => "aarch64-w64-mingw32-dlltool",
// For non-standard architectures (e.g., aarch32) fallback to "dlltool".
_ => "dlltool",
}
}
.into();
// NOTE: it's not clear how useful it is to explicitly search PATH.
for dir in env::split_paths(&env::var_os("PATH").unwrap_or_default()) {
let full_path = dir.join(&tool_name);
if full_path.is_file() {
return full_path.into_os_string();
}
}
// The user didn't specify the location of the dlltool binary, and we weren't able
// to find the appropriate one on the PATH. Just return the name of the tool
// and let the invocation fail with a hopefully useful error message.
tool_name
}
pub trait ArchiveBuilder {
fn add_file(&mut self, path: &Path);

View file

@ -51,7 +51,8 @@ use super::linker::{self, Linker};
use super::metadata::{create_wrapper_file, MetadataPosition};
use super::rpath::{self, RPathConfig};
use crate::{
errors, looks_like_rust_object_file, CodegenResults, CompiledModule, CrateInfo, NativeLib,
common, errors, looks_like_rust_object_file, CodegenResults, CompiledModule, CrateInfo,
NativeLib,
};
pub fn ensure_removed(dcx: DiagCtxtHandle<'_>, path: &Path) {
@ -390,17 +391,13 @@ fn link_rlib<'a>(
}
}
for (raw_dylib_name, raw_dylib_imports) in
collate_raw_dylibs(sess, codegen_results.crate_info.used_libraries.iter())?
{
let output_path = archive_builder_builder.create_dll_import_lib(
sess,
&raw_dylib_name,
&raw_dylib_imports,
tmpdir.as_ref(),
true,
);
for output_path in create_dll_import_libs(
sess,
archive_builder_builder,
codegen_results.crate_info.used_libraries.iter(),
tmpdir.as_ref(),
true,
)? {
ab.add_archive(&output_path, Box::new(|_| false)).unwrap_or_else(|error| {
sess.dcx().emit_fatal(errors::AddNativeLibrary { library_path: output_path, error });
});
@ -488,6 +485,47 @@ fn collate_raw_dylibs<'a>(
.collect())
}
fn create_dll_import_libs<'a>(
sess: &Session,
archive_builder_builder: &dyn ArchiveBuilderBuilder,
used_libraries: impl IntoIterator<Item = &'a NativeLib>,
tmpdir: &Path,
is_direct_dependency: bool,
) -> Result<Vec<PathBuf>, ErrorGuaranteed> {
Ok(collate_raw_dylibs(sess, used_libraries)?
.into_iter()
.map(|(raw_dylib_name, raw_dylib_imports)| {
let name_suffix = if is_direct_dependency { "_imports" } else { "_imports_indirect" };
let output_path = tmpdir.join(format!("{raw_dylib_name}{name_suffix}.lib"));
let mingw_gnu_toolchain = common::is_mingw_gnu_toolchain(&sess.target);
let import_name_and_ordinal_vector: Vec<(String, Option<u16>)> = raw_dylib_imports
.iter()
.map(|import: &DllImport| {
if sess.target.arch == "x86" {
(
common::i686_decorated_name(import, mingw_gnu_toolchain, false),
import.ordinal(),
)
} else {
(import.name.to_string(), import.ordinal())
}
})
.collect();
archive_builder_builder.create_dll_import_lib(
sess,
&raw_dylib_name,
import_name_and_ordinal_vector,
&output_path,
);
output_path
})
.collect())
}
/// Create a static archive.
///
/// This is essentially the same thing as an rlib, but it also involves adding all of the upstream
@ -2305,16 +2343,14 @@ fn linker_with_args(
);
// Link with the import library generated for any raw-dylib functions.
for (raw_dylib_name, raw_dylib_imports) in
collate_raw_dylibs(sess, codegen_results.crate_info.used_libraries.iter())?
{
cmd.add_object(&archive_builder_builder.create_dll_import_lib(
sess,
&raw_dylib_name,
&raw_dylib_imports,
tmpdir,
true,
));
for output_path in create_dll_import_libs(
sess,
archive_builder_builder,
codegen_results.crate_info.used_libraries.iter(),
tmpdir,
true,
)? {
cmd.add_object(&output_path);
}
// As with add_upstream_native_libraries, we need to add the upstream raw-dylib symbols in case
// they are used within inlined functions or instantiated generic functions. We do this *after*
@ -2339,16 +2375,14 @@ fn linker_with_args(
.flatten()
.collect::<Vec<_>>();
native_libraries_from_nonstatics.sort_unstable_by(|a, b| a.name.as_str().cmp(b.name.as_str()));
for (raw_dylib_name, raw_dylib_imports) in
collate_raw_dylibs(sess, native_libraries_from_nonstatics)?
{
cmd.add_object(&archive_builder_builder.create_dll_import_lib(
sess,
&raw_dylib_name,
&raw_dylib_imports,
tmpdir,
false,
));
for output_path in create_dll_import_libs(
sess,
archive_builder_builder,
native_libraries_from_nonstatics,
tmpdir,
false,
)? {
cmd.add_object(&output_path);
}
// Library linking above uses some global state for things like `-Bstatic`/`-Bdynamic` to make

View file

@ -4,7 +4,9 @@ use rustc_hir::LangItem;
use rustc_middle::ty::layout::TyAndLayout;
use rustc_middle::ty::{self, Instance, TyCtxt};
use rustc_middle::{bug, mir, span_bug};
use rustc_session::cstore::{DllCallingConvention, DllImport, PeImportNameType};
use rustc_span::Span;
use rustc_target::spec::Target;
use crate::traits::*;
@ -176,3 +178,66 @@ pub fn asm_const_to_str<'tcx>(
_ => span_bug!(sp, "asm const has bad type {}", ty_and_layout.ty),
}
}
pub fn is_mingw_gnu_toolchain(target: &Target) -> bool {
target.vendor == "pc" && target.os == "windows" && target.env == "gnu" && target.abi.is_empty()
}
pub fn i686_decorated_name(
dll_import: &DllImport,
mingw: bool,
disable_name_mangling: bool,
) -> String {
let name = dll_import.name.as_str();
let (add_prefix, add_suffix) = match dll_import.import_name_type {
Some(PeImportNameType::NoPrefix) => (false, true),
Some(PeImportNameType::Undecorated) => (false, false),
_ => (true, true),
};
// Worst case: +1 for disable name mangling, +1 for prefix, +4 for suffix (@@__).
let mut decorated_name = String::with_capacity(name.len() + 6);
if disable_name_mangling {
// LLVM uses a binary 1 ('\x01') prefix to a name to indicate that mangling needs to be disabled.
decorated_name.push('\x01');
}
let prefix = if add_prefix && dll_import.is_fn {
match dll_import.calling_convention {
DllCallingConvention::C | DllCallingConvention::Vectorcall(_) => None,
DllCallingConvention::Stdcall(_) => (!mingw
|| dll_import.import_name_type == Some(PeImportNameType::Decorated))
.then_some('_'),
DllCallingConvention::Fastcall(_) => Some('@'),
}
} else if !dll_import.is_fn && !mingw {
// For static variables, prefix with '_' on MSVC.
Some('_')
} else {
None
};
if let Some(prefix) = prefix {
decorated_name.push(prefix);
}
decorated_name.push_str(name);
if add_suffix && dll_import.is_fn {
use std::fmt::Write;
match dll_import.calling_convention {
DllCallingConvention::C => {}
DllCallingConvention::Stdcall(arg_list_size)
| DllCallingConvention::Fastcall(arg_list_size) => {
write!(&mut decorated_name, "@{arg_list_size}").unwrap();
}
DllCallingConvention::Vectorcall(arg_list_size) => {
write!(&mut decorated_name, "@@{arg_list_size}").unwrap();
}
}
}
decorated_name
}

View file

@ -1026,6 +1026,28 @@ pub struct FailedToGetLayout<'tcx> {
pub err: LayoutError<'tcx>,
}
#[derive(Diagnostic)]
#[diag(codegen_ssa_dlltool_fail_import_library)]
pub(crate) struct DlltoolFailImportLibrary<'a> {
pub dlltool_path: Cow<'a, str>,
pub dlltool_args: String,
pub stdout: Cow<'a, str>,
pub stderr: Cow<'a, str>,
}
#[derive(Diagnostic)]
#[diag(codegen_ssa_error_writing_def_file)]
pub(crate) struct ErrorWritingDEFFile {
pub error: std::io::Error,
}
#[derive(Diagnostic)]
#[diag(codegen_ssa_error_calling_dlltool)]
pub(crate) struct ErrorCallingDllTool<'a> {
pub dlltool_path: Cow<'a, str>,
pub error: std::io::Error,
}
#[derive(Diagnostic)]
#[diag(codegen_ssa_error_creating_remark_dir)]
pub struct ErrorCreatingRemarkDir {