Skip to content
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

Implement raw-dylib support for windows-gnu #90782

Merged
merged 1 commit into from
Jan 19, 2022
Merged
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
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};
@@ -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 {
ricobbe marked this conversation as resolved.
Show resolved Hide resolved
// 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!(
@@ -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"
}
michaelwoerister marked this conversation as resolved.
Show resolved Hide resolved
} 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
@@ -640,6 +640,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")));
5 changes: 0 additions & 5 deletions compiler/rustc_metadata/src/native_libs.rs
Original file line number Diff line number Diff line change
@@ -274,11 +274,6 @@ impl Collector<'tcx> {
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') {
2 changes: 2 additions & 0 deletions compiler/rustc_session/src/options.rs
Original file line number Diff line number Diff line change
@@ -1073,6 +1073,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)"),
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
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
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);
michaelwoerister marked this conversation as resolved.
Show resolved Hide resolved
}

#[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