Skip to content

Commit

Permalink
Rollup merge of rust-lang#90782 - ricobbe:binutils-dlltool, r=michael…
Browse files Browse the repository at this point in the history
…woerister

Implement raw-dylib support for windows-gnu

Add support for `#[link(kind = "raw-dylib")]` on windows-gnu targets.  Work around binutils's linker's inability to read import libraries produced by LLVM by calling out to the binutils `dlltool` utility to create an import library from a temporary .DEF file; this approach is effectively a slightly refined version of ``@mati865's`` earlier attempt at this strategy in PR rust-lang#88801.  (In particular, this attempt at this strategy adds support for `#[link_ordinal(...)]` as well.)

In support of rust-lang#58713.
  • Loading branch information
matthiaskrgr authored Jan 14, 2022
2 parents 22e491a + 0cf7fd1 commit 6987359
Show file tree
Hide file tree
Showing 24 changed files with 252 additions and 113 deletions.
205 changes: 159 additions & 46 deletions compiler/rustc_codegen_llvm/src/back/archive.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! A helper class for dealing with static archives

use std::ffi::{CStr, CString};
use std::env;
use std::ffi::{CStr, CString, OsString};
use std::io;
use std::mem;
use std::path::{Path, PathBuf};
Expand Down Expand Up @@ -158,54 +159,127 @@ impl<'a> ArchiveBuilder<'a> for LlvmArchiveBuilder<'a> {
output_path.with_extension("lib")
};

// we've checked for \0 characters in the library name already
let dll_name_z = CString::new(lib_name).unwrap();
// All import names are Rust identifiers and therefore cannot contain \0 characters.
// FIXME: when support for #[link_name] implemented, ensure that import.name values don't
// have any \0 characters
let import_name_and_ordinal_vector: Vec<(CString, Option<u16>)> = dll_imports
let mingw_gnu_toolchain = self.config.sess.target.llvm_target.ends_with("pc-windows-gnu");

let import_name_and_ordinal_vector: Vec<(String, Option<u16>)> = dll_imports
.iter()
.map(|import: &DllImport| {
if self.config.sess.target.arch == "x86" {
(LlvmArchiveBuilder::i686_decorated_name(import), import.ordinal)
(
LlvmArchiveBuilder::i686_decorated_name(import, mingw_gnu_toolchain),
import.ordinal,
)
} else {
(CString::new(import.name.to_string()).unwrap(), import.ordinal)
(import.name.to_string(), import.ordinal)
}
})
.collect();

let output_path_z = rustc_fs_util::path_to_c_string(&output_path);
if mingw_gnu_toolchain {
// The binutils linker used on -windows-gnu targets cannot read the import
// libraries generated by LLVM: in our attempts, the linker produced an .EXE
// that loaded but crashed with an AV upon calling one of the imported
// functions. Therefore, use binutils to create the import library instead,
// by writing a .DEF file to the temp dir and calling binutils's dlltool.
let def_file_path =
tmpdir.as_ref().join(format!("{}_imports", lib_name)).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!("{} @{} NONAME", name, n),
None => name,
}
})
.collect::<Vec<String>>()
.join("\n")
);

tracing::trace!("invoking LLVMRustWriteImportLibrary");
tracing::trace!(" dll_name {:#?}", dll_name_z);
tracing::trace!(" output_path {}", output_path.display());
tracing::trace!(
" import names: {}",
dll_imports.iter().map(|import| import.name.to_string()).collect::<Vec<_>>().join(", "),
);
match std::fs::write(&def_file_path, def_file_content) {
Ok(_) => {}
Err(e) => {
self.config.sess.fatal(&format!("Error writing .DEF file: {}", e));
}
};

let ffi_exports: Vec<LLVMRustCOFFShortExport> = import_name_and_ordinal_vector
.iter()
.map(|(name_z, ordinal)| LLVMRustCOFFShortExport::new(name_z.as_ptr(), *ordinal))
.collect();
let result = unsafe {
crate::llvm::LLVMRustWriteImportLibrary(
dll_name_z.as_ptr(),
output_path_z.as_ptr(),
ffi_exports.as_ptr(),
ffi_exports.len(),
llvm_machine_type(&self.config.sess.target.arch) as u16,
!self.config.sess.target.is_like_msvc,
)
};
let dlltool = find_binutils_dlltool(self.config.sess);
let result = std::process::Command::new(dlltool)
.args([
"-d",
def_file_path.to_str().unwrap(),
"-D",
lib_name,
"-l",
output_path.to_str().unwrap(),
])
.output();

match result {
Err(e) => {
self.config.sess.fatal(&format!("Error calling dlltool: {}", e.to_string()));
}
Ok(output) if !output.status.success() => self.config.sess.fatal(&format!(
"Dlltool could not create import library: {}\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
)),
_ => {}
}
} else {
// we've checked for \0 characters in the library name already
let dll_name_z = CString::new(lib_name).unwrap();

let output_path_z = rustc_fs_util::path_to_c_string(&output_path);

tracing::trace!("invoking LLVMRustWriteImportLibrary");
tracing::trace!(" dll_name {:#?}", dll_name_z);
tracing::trace!(" output_path {}", output_path.display());
tracing::trace!(
" import names: {}",
dll_imports
.iter()
.map(|import| import.name.to_string())
.collect::<Vec<_>>()
.join(", "),
);

if result == crate::llvm::LLVMRustResult::Failure {
self.config.sess.fatal(&format!(
"Error creating import library for {}: {}",
lib_name,
llvm::last_error().unwrap_or("unknown LLVM error".to_string())
));
}
// All import names are Rust identifiers and therefore cannot contain \0 characters.
// FIXME: when support for #[link_name] is implemented, ensure that the import names
// still don't contain any \0 characters. Also need to check that the names don't
// contain substrings like " @" or "NONAME" that are keywords or otherwise reserved
// in definition files.
let cstring_import_name_and_ordinal_vector: Vec<(CString, Option<u16>)> =
import_name_and_ordinal_vector
.into_iter()
.map(|(name, ordinal)| (CString::new(name).unwrap(), ordinal))
.collect();

let ffi_exports: Vec<LLVMRustCOFFShortExport> = cstring_import_name_and_ordinal_vector
.iter()
.map(|(name_z, ordinal)| LLVMRustCOFFShortExport::new(name_z.as_ptr(), *ordinal))
.collect();
let result = unsafe {
crate::llvm::LLVMRustWriteImportLibrary(
dll_name_z.as_ptr(),
output_path_z.as_ptr(),
ffi_exports.as_ptr(),
ffi_exports.len(),
llvm_machine_type(&self.config.sess.target.arch) as u16,
!self.config.sess.target.is_like_msvc,
)
};

if result == crate::llvm::LLVMRustResult::Failure {
self.config.sess.fatal(&format!(
"Error creating import library for {}: {}",
lib_name,
llvm::last_error().unwrap_or("unknown LLVM error".to_string())
));
}
};

self.add_archive(&output_path, |_| false).unwrap_or_else(|e| {
self.config.sess.fatal(&format!(
Expand Down Expand Up @@ -332,22 +406,61 @@ impl<'a> LlvmArchiveBuilder<'a> {
}
}

fn i686_decorated_name(import: &DllImport) -> CString {
fn i686_decorated_name(import: &DllImport, mingw: bool) -> String {
let name = import.name;
// We verified during construction that `name` does not contain any NULL characters, so the
// conversion to CString is guaranteed to succeed.
CString::new(match import.calling_convention {
DllCallingConvention::C => format!("_{}", name),
DllCallingConvention::Stdcall(arg_list_size) => format!("_{}@{}", name, arg_list_size),
let prefix = if mingw { "" } else { "_" };

match import.calling_convention {
DllCallingConvention::C => format!("{}{}", prefix, name),
DllCallingConvention::Stdcall(arg_list_size) => {
format!("{}{}@{}", prefix, name, arg_list_size)
}
DllCallingConvention::Fastcall(arg_list_size) => format!("@{}@{}", name, arg_list_size),
DllCallingConvention::Vectorcall(arg_list_size) => {
format!("{}@@{}", name, arg_list_size)
}
})
.unwrap()
}
}
}

fn string_to_io_error(s: String) -> io::Error {
io::Error::new(io::ErrorKind::Other, format!("bad archive: {}", s))
}

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.debugging_opts.dlltool {
return dlltool_path.clone().into_os_string();
}

let mut tool_name: OsString = if sess.host.arch != sess.target.arch {
// We are cross-compiling, so we need the tool with the prefix matching our target
if sess.target.arch == "x86" {
"i686-w64-mingw32-dlltool"
} else {
"x86_64-w64-mingw32-dlltool"
}
} else {
// We are not cross-compiling, so we just want `dlltool`
"dlltool"
}
.into();

if sess.host.options.is_like_windows {
// If we're compiling on Windows, add the .exe suffix
tool_name.push(".exe");
}

// 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
}
1 change: 1 addition & 0 deletions compiler/rustc_interface/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,7 @@ fn test_debugging_options_tracking_hash() {
untracked!(borrowck, String::from("other"));
untracked!(deduplicate_diagnostics, false);
untracked!(dep_tasks, true);
untracked!(dlltool, Some(PathBuf::from("custom_dlltool.exe")));
untracked!(dont_buffer_diagnostics, true);
untracked!(dump_dep_graph, true);
untracked!(dump_mir, Some(String::from("abc")));
Expand Down
5 changes: 0 additions & 5 deletions compiler/rustc_metadata/src/native_libs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,11 +274,6 @@ impl Collector<'_> {
span,
"`#[link(...)]` with `kind = \"raw-dylib\"` only supported on Windows",
);
} else if !self.tcx.sess.target.options.is_like_msvc {
self.tcx.sess.span_warn(
span,
"`#[link(...)]` with `kind = \"raw-dylib\"` not supported on windows-gnu",
);
}

if lib_name.as_str().contains('\0') {
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 @@ -1161,6 +1161,8 @@ options! {
dep_tasks: bool = (false, parse_bool, [UNTRACKED],
"print tasks that execute and the color their dep node gets (requires debug build) \
(default: no)"),
dlltool: Option<PathBuf> = (None, parse_opt_pathbuf, [UNTRACKED],
"import library generation tool (windows-gnu only)"),
dont_buffer_diagnostics: bool = (false, parse_bool, [UNTRACKED],
"emit diagnostics rather than buffering (breaks NLL error downgrading, sorting) \
(default: no)"),
Expand Down
7 changes: 6 additions & 1 deletion src/test/run-make/raw-dylib-c/Makefile
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
# Test the behavior of #[link(.., kind = "raw-dylib")] on windows-msvc

# only-windows-msvc
# only-windows

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

all:
$(call COMPILE_OBJ,"$(TMPDIR)"/extern_1.obj,extern_1.c)
$(call COMPILE_OBJ,"$(TMPDIR)"/extern_2.obj,extern_2.c)
ifdef IS_MSVC
$(CC) "$(TMPDIR)"/extern_1.obj -link -dll -out:"$(TMPDIR)"/extern_1.dll
$(CC) "$(TMPDIR)"/extern_2.obj -link -dll -out:"$(TMPDIR)"/extern_2.dll
else
$(CC) "$(TMPDIR)"/extern_1.obj -shared -o "$(TMPDIR)"/extern_1.dll
$(CC) "$(TMPDIR)"/extern_2.obj -shared -o "$(TMPDIR)"/extern_2.dll
endif
$(RUSTC) --crate-type lib --crate-name raw_dylib_test lib.rs
$(RUSTC) --crate-type bin driver.rs -L "$(TMPDIR)"
"$(TMPDIR)"/driver > "$(TMPDIR)"/output.txt
Expand Down
6 changes: 5 additions & 1 deletion src/test/run-make/raw-dylib-link-ordinal/Makefile
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
# Test the behavior of #[link(.., kind = "raw-dylib")] and #[link_ordinal] on windows-msvc

# only-windows-msvc
# only-windows

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

all:
$(call COMPILE_OBJ,"$(TMPDIR)"/exporter.obj,exporter.c)
ifdef IS_MSVC
$(CC) "$(TMPDIR)"/exporter.obj exporter.def -link -dll -out:"$(TMPDIR)"/exporter.dll
else
$(CC) "$(TMPDIR)"/exporter.obj exporter.def -shared -o "$(TMPDIR)"/exporter.dll
endif
$(RUSTC) --crate-type lib --crate-name raw_dylib_test lib.rs
$(RUSTC) --crate-type bin driver.rs -L "$(TMPDIR)"
"$(TMPDIR)"/driver > "$(TMPDIR)"/output.txt
Expand Down
23 changes: 23 additions & 0 deletions src/test/run-make/raw-dylib-stdcall-ordinal/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Test the behavior of #[link(.., kind = "raw-dylib")], #[link_ordinal], and alternative calling conventions on i686 windows.

# only-x86
# only-windows

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

all:
$(call COMPILE_OBJ,"$(TMPDIR)"/exporter.obj,exporter.c)
ifdef IS_MSVC
$(CC) "$(TMPDIR)"/exporter.obj exporter-msvc.def -link -dll -out:"$(TMPDIR)"/exporter.dll
else
$(CC) "$(TMPDIR)"/exporter.obj exporter-gnu.def -shared -o "$(TMPDIR)"/exporter.dll
endif
$(RUSTC) --crate-type lib --crate-name raw_dylib_test lib.rs
$(RUSTC) --crate-type bin driver.rs -L "$(TMPDIR)"
"$(TMPDIR)"/driver > "$(TMPDIR)"/actual_output.txt

ifdef RUSTC_BLESS_TEST
cp "$(TMPDIR)"/actual_output.txt expected_output.txt
else
$(DIFF) expected_output.txt "$(TMPDIR)"/actual_output.txt
endif
5 changes: 5 additions & 0 deletions src/test/run-make/raw-dylib-stdcall-ordinal/driver.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
extern crate raw_dylib_test;

fn main() {
raw_dylib_test::library_function();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
exported_function_stdcall(6)
exported_function_fastcall(125)
4 changes: 4 additions & 0 deletions src/test/run-make/raw-dylib-stdcall-ordinal/exporter-gnu.def
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
LIBRARY exporter
EXPORTS
exported_function_stdcall@4 @15 NONAME
@exported_function_fastcall@4 @18 NONAME
4 changes: 4 additions & 0 deletions src/test/run-make/raw-dylib-stdcall-ordinal/exporter-msvc.def
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
LIBRARY exporter
EXPORTS
_exported_function_stdcall@4 @15 NONAME
@exported_function_fastcall@4 @18 NONAME
11 changes: 11 additions & 0 deletions src/test/run-make/raw-dylib-stdcall-ordinal/exporter.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#include <stdio.h>

void __stdcall exported_function_stdcall(int i) {
printf("exported_function_stdcall(%d)\n", i);
fflush(stdout);
}

void __fastcall exported_function_fastcall(int i) {
printf("exported_function_fastcall(%d)\n", i);
fflush(stdout);
}
20 changes: 20 additions & 0 deletions src/test/run-make/raw-dylib-stdcall-ordinal/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#![feature(raw_dylib)]

#[link(name = "exporter", kind = "raw-dylib")]
extern "stdcall" {
#[link_ordinal(15)]
fn imported_function_stdcall(i: i32);
}

#[link(name = "exporter", kind = "raw-dylib")]
extern "fastcall" {
#[link_ordinal(18)]
fn imported_function_fastcall(i: i32);
}

pub fn library_function() {
unsafe {
imported_function_stdcall(6);
imported_function_fastcall(125);
}
}

This file was deleted.

Loading

0 comments on commit 6987359

Please sign in to comment.