Skip to content

Commit

Permalink
Auto merge of #100732 - dpaoliello:import_name_type, r=wesleywiser
Browse files Browse the repository at this point in the history
Implementation of import_name_type

Fixes #96534 by implementing rust-lang/compiler-team#525

Symbols that are exported or imported from a binary on 32bit x86 Windows can be named in four separate ways, corresponding to the [import name types](https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#import-name-type) from the PE-COFF spec. The exporting and importing binaries must use the same name encoding, otherwise mismatches can lead to link failures due to "missing symbols" or to 0xc0000139 (`STATUS_ENTRYPOINT_NOT_FOUND`) errors when the executable/library is loaded. For details, see the comments on the raw-dylib feature's #58713. To generate the correct import libraries for these DLLs, therefore, rustc must know the import name type for each `extern` function, and there is currently no way for users to provide this information.

This change adds a new `MetaNameValueStr` key to the `#[link]` attribute called `import_name_type`, and which accepts one of three values: `decorated`, `noprefix`, and `undecorated`.

A single DLL is likely to export all its functions using the same import type name, hence `import_name_type` is a parameter of `#[link]` rather than being its own attribute that is applied per-function. It is possible to have a single DLL that exports different functions using different import name types, but users could express such cases by providing multiple export blocks for the same DLL, each with a different import name type.

Note: there is a fourth import name type defined in the PE-COFF spec, `IMPORT_ORDINAL`. This case is already handled by the `#[link_ordinal]` attribute. While it could be merged into `import_type_name`, that would not make sense as `#[link_ordinal]` provides per-function information (namely the ordinal itself).

Design decisions (these match the MCP linked above):
* For GNU, `decorated` matches the PE Spec and MSVC rather than the default behavior of `dlltool` (i.e., there will be a leading `_` for `stdcall`).
* If `import_name_type` is not present, we will keep our current behavior of matching the environment (MSVC vs GNU) default for decorating.
* Using `import_name_type` on architectures other than 32bit x86 will result in an error.
* Using `import_name_type` with link kinds other than `"raw-dylib"` will result in an error.
  • Loading branch information
bors committed Aug 27, 2022
2 parents bb8a08f + cc49c3e commit 9845f4c
Show file tree
Hide file tree
Showing 31 changed files with 614 additions and 46 deletions.
34 changes: 10 additions & 24 deletions compiler/rustc_codegen_llvm/src/back/archive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ use std::path::{Path, PathBuf};
use std::ptr;
use std::str;

use crate::common;
use crate::llvm::archive_ro::{ArchiveRO, Child};
use crate::llvm::{self, ArchiveKind, LLVMMachineType, LLVMRustCOFFShortExport};
use rustc_codegen_ssa::back::archive::{ArchiveBuilder, ArchiveBuilderBuilder};
use rustc_session::cstore::{DllCallingConvention, DllImport};
use rustc_session::cstore::DllImport;
use rustc_session::Session;

/// Helper for adding many files to an archive.
Expand Down Expand Up @@ -111,21 +112,18 @@ impl ArchiveBuilderBuilder for LlvmArchiveBuilderBuilder {
};

let target = &sess.target;
let mingw_gnu_toolchain = target.vendor == "pc"
&& target.os == "windows"
&& target.env == "gnu"
&& target.abi.is_empty();
let mingw_gnu_toolchain = common::is_mingw_gnu_toolchain(target);

let import_name_and_ordinal_vector: Vec<(String, Option<u16>)> = dll_imports
.iter()
.map(|import: &DllImport| {
if sess.target.arch == "x86" {
(
LlvmArchiveBuilder::i686_decorated_name(import, mingw_gnu_toolchain),
import.ordinal,
common::i686_decorated_name(import, mingw_gnu_toolchain, false),
import.ordinal(),
)
} else {
(import.name.to_string(), import.ordinal)
(import.name.to_string(), import.ordinal())
}
})
.collect();
Expand Down Expand Up @@ -159,6 +157,9 @@ impl ArchiveBuilderBuilder for LlvmArchiveBuilderBuilder {
}
};

// --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 result = std::process::Command::new(dlltool)
.args([
Expand All @@ -168,6 +169,7 @@ impl ArchiveBuilderBuilder for LlvmArchiveBuilderBuilder {
lib_name,
"-l",
output_path.to_str().unwrap(),
"--no-leading-underscore",
])
.output();

Expand Down Expand Up @@ -322,22 +324,6 @@ impl<'a> LlvmArchiveBuilder<'a> {
ret
}
}

fn i686_decorated_name(import: &DllImport, mingw: bool) -> String {
let name = import.name;
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)
}
}
}
}

fn string_to_io_error(s: String) -> io::Error {
Expand Down
12 changes: 9 additions & 3 deletions compiler/rustc_codegen_llvm/src/callee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use crate::abi::FnAbiLlvmExt;
use crate::attributes;
use crate::common;
use crate::context::CodegenCx;
use crate::llvm;
use crate::value::Value;
Expand Down Expand Up @@ -79,13 +80,18 @@ pub fn get_fn<'ll, 'tcx>(cx: &CodegenCx<'ll, 'tcx>, instance: Instance<'tcx>) ->
llfn
}
} else {
let llfn = cx.declare_fn(sym, fn_abi);
let instance_def_id = instance.def_id();
let llfn = if tcx.sess.target.arch == "x86" &&
let Some(dllimport) = common::get_dllimport(tcx, instance_def_id, sym)
{
cx.declare_fn(&common::i686_decorated_name(&dllimport, common::is_mingw_gnu_toolchain(&tcx.sess.target), true), fn_abi)
} else {
cx.declare_fn(sym, fn_abi)
};
debug!("get_fn: not casting pointer!");

attributes::from_fn_attrs(cx, llfn, instance);

let instance_def_id = instance.def_id();

// Apply an appropriate linkage/visibility value to our item that we
// just declared.
//
Expand Down
76 changes: 76 additions & 0 deletions compiler/rustc_codegen_llvm/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,17 @@ use crate::value::Value;
use rustc_ast::Mutability;
use rustc_codegen_ssa::mir::place::PlaceRef;
use rustc_codegen_ssa::traits::*;
use rustc_hir::def_id::DefId;
use rustc_middle::bug;
use rustc_middle::mir::interpret::{ConstAllocation, GlobalAlloc, Scalar};
use rustc_middle::ty::layout::{LayoutOf, TyAndLayout};
use rustc_middle::ty::TyCtxt;
use rustc_session::cstore::{DllCallingConvention, DllImport, PeImportNameType};
use rustc_target::abi::{self, AddressSpace, HasDataLayout, Pointer, Size};
use rustc_target::spec::Target;

use libc::{c_char, c_uint};
use std::fmt::Write;
use tracing::debug;

/*
Expand Down Expand Up @@ -357,3 +362,74 @@ fn hi_lo_to_u128(lo: u64, hi: u64) -> u128 {
fn try_as_const_integral(v: &Value) -> Option<&ConstantInt> {
unsafe { llvm::LLVMIsAConstantInt(v) }
}

pub(crate) fn get_dllimport<'tcx>(
tcx: TyCtxt<'tcx>,
id: DefId,
name: &str,
) -> Option<&'tcx DllImport> {
tcx.native_library(id)
.map(|lib| lib.dll_imports.iter().find(|di| di.name.as_str() == name))
.flatten()
}

pub(crate) fn is_mingw_gnu_toolchain(target: &Target) -> bool {
target.vendor == "pc" && target.os == "windows" && target.env == "gnu" && target.abi.is_empty()
}

pub(crate) 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 {
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
}
12 changes: 8 additions & 4 deletions compiler/rustc_codegen_llvm/src/consts.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::base;
use crate::common::CodegenCx;
use crate::common::{self, CodegenCx};
use crate::debuginfo;
use crate::llvm::{self, True};
use crate::llvm_util;
Expand Down Expand Up @@ -160,7 +160,7 @@ fn check_and_apply_linkage<'ll, 'tcx>(
attrs: &CodegenFnAttrs,
ty: Ty<'tcx>,
sym: &str,
span_def_id: DefId,
def_id: DefId,
) -> &'ll Value {
let llty = cx.layout_of(ty).llvm_type(cx);
if let Some(linkage) = attrs.linkage {
Expand All @@ -175,7 +175,7 @@ fn check_and_apply_linkage<'ll, 'tcx>(
cx.layout_of(mt.ty).llvm_type(cx)
} else {
cx.sess().span_fatal(
cx.tcx.def_span(span_def_id),
cx.tcx.def_span(def_id),
"must have type `*const T` or `*mut T` due to `#[linkage]` attribute",
)
};
Expand All @@ -194,14 +194,18 @@ fn check_and_apply_linkage<'ll, 'tcx>(
real_name.push_str(sym);
let g2 = cx.define_global(&real_name, llty).unwrap_or_else(|| {
cx.sess().span_fatal(
cx.tcx.def_span(span_def_id),
cx.tcx.def_span(def_id),
&format!("symbol `{}` is already defined", &sym),
)
});
llvm::LLVMRustSetLinkage(g2, llvm::Linkage::InternalLinkage);
llvm::LLVMSetInitializer(g2, g1);
g2
}
} else if cx.tcx.sess.target.arch == "x86" &&
let Some(dllimport) = common::get_dllimport(cx.tcx, def_id, sym)
{
cx.declare_global(&common::i686_decorated_name(&dllimport, common::is_mingw_gnu_toolchain(&cx.tcx.sess.target), true), llty)
} else {
// Generate an external declaration.
// FIXME(nagisa): investigate whether it can be changed into define_global
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_feature/src/builtin_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
// ABI, linking, symbols, and FFI
ungated!(
link, Normal,
template!(List: r#"name = "...", /*opt*/ kind = "dylib|static|...", /*opt*/ wasm_import_module = "...""#),
template!(List: r#"name = "...", /*opt*/ kind = "dylib|static|...", /*opt*/ wasm_import_module = "...", /*opt*/ import_name_type = "decorated|noprefix|undecorated""#),
DuplicatesOk,
),
ungated!(link_name, Normal, template!(NameValueStr: "name"), FutureWarnPreceding),
Expand Down
79 changes: 74 additions & 5 deletions compiler/rustc_metadata/src/native_libs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use rustc_errors::struct_span_err;
use rustc_hir as hir;
use rustc_hir::def::DefKind;
use rustc_middle::ty::{List, ParamEnv, ParamEnvAnd, Ty, TyCtxt};
use rustc_session::cstore::{DllCallingConvention, DllImport, NativeLib};
use rustc_session::cstore::{DllCallingConvention, DllImport, NativeLib, PeImportNameType};
use rustc_session::parse::feature_err;
use rustc_session::utils::NativeLibKind;
use rustc_session::Session;
Expand Down Expand Up @@ -61,6 +61,7 @@ impl<'tcx> Collector<'tcx> {
let mut modifiers = None;
let mut cfg = None;
let mut wasm_import_module = None;
let mut import_name_type = None;
for item in items.iter() {
match item.name_or_empty() {
sym::name => {
Expand Down Expand Up @@ -199,9 +200,51 @@ impl<'tcx> Collector<'tcx> {
};
wasm_import_module = Some((link_wasm_import_module, item.span()));
}
sym::import_name_type => {
if import_name_type.is_some() {
let msg = "multiple `import_name_type` arguments in a single `#[link]` attribute";
sess.span_err(item.span(), msg);
continue;
}
let Some(link_import_name_type) = item.value_str() else {
let msg = "import name type must be of the form `import_name_type = \"string\"`";
sess.span_err(item.span(), msg);
continue;
};
if self.tcx.sess.target.arch != "x86" {
let msg = "import name type is only supported on x86";
sess.span_err(item.span(), msg);
continue;
}

let link_import_name_type = match link_import_name_type.as_str() {
"decorated" => PeImportNameType::Decorated,
"noprefix" => PeImportNameType::NoPrefix,
"undecorated" => PeImportNameType::Undecorated,
import_name_type => {
let msg = format!(
"unknown import name type `{import_name_type}`, expected one of: \
decorated, noprefix, undecorated"
);
sess.span_err(item.span(), msg);
continue;
}
};
if !features.raw_dylib {
let span = item.name_value_literal_span().unwrap();
feature_err(
&sess.parse_sess,
sym::raw_dylib,
span,
"import name type is unstable",
)
.emit();
}
import_name_type = Some((link_import_name_type, item.span()));
}
_ => {
let msg = "unexpected `#[link]` argument, expected one of: \
name, kind, modifiers, cfg, wasm_import_module";
name, kind, modifiers, cfg, wasm_import_module, import_name_type";
sess.span_err(item.span(), msg);
}
}
Expand Down Expand Up @@ -315,6 +358,14 @@ impl<'tcx> Collector<'tcx> {
.emit();
}

// Do this outside of the loop so that `import_name_type` can be specified before `kind`.
if let Some((_, span)) = import_name_type {
if kind != Some(NativeLibKind::RawDylib) {
let msg = "import name type can only be used with link kind `raw-dylib`";
sess.span_err(span, msg);
}
}

let dll_imports = match kind {
Some(NativeLibKind::RawDylib) => {
if let Some((name, span)) = name && name.as_str().contains('\0') {
Expand All @@ -325,7 +376,13 @@ impl<'tcx> Collector<'tcx> {
}
foreign_mod_items
.iter()
.map(|child_item| self.build_dll_import(abi, child_item))
.map(|child_item| {
self.build_dll_import(
abi,
import_name_type.map(|(import_name_type, _)| import_name_type),
child_item,
)
})
.collect()
}
_ => {
Expand Down Expand Up @@ -486,7 +543,12 @@ impl<'tcx> Collector<'tcx> {
.sum()
}

fn build_dll_import(&self, abi: Abi, item: &hir::ForeignItemRef) -> DllImport {
fn build_dll_import(
&self,
abi: Abi,
import_name_type: Option<PeImportNameType>,
item: &hir::ForeignItemRef,
) -> DllImport {
let calling_convention = if self.tcx.sess.target.arch == "x86" {
match abi {
Abi::C { .. } | Abi::Cdecl { .. } => DllCallingConvention::C,
Expand Down Expand Up @@ -518,11 +580,18 @@ impl<'tcx> Collector<'tcx> {
}
};

let import_name_type = self
.tcx
.codegen_fn_attrs(item.id.def_id)
.link_ordinal
.map_or(import_name_type, |ord| Some(PeImportNameType::Ordinal(ord)));

DllImport {
name: item.ident.name,
ordinal: self.tcx.codegen_fn_attrs(item.id.def_id).link_ordinal,
import_name_type,
calling_convention,
span: item.span,
is_fn: self.tcx.def_kind(item.id.def_id).is_fn_like(),
}
}
}
Loading

0 comments on commit 9845f4c

Please sign in to comment.