Auto merge of #84171 - ricobbe:raw-dylib-via-llvm, r=petrochenkov

Partial support for raw-dylib linkage

First cut of functionality for issue #58713: add support for `#[link(kind = "raw-dylib")]` on `extern` blocks in lib crates compiled to .rlib files.  Does not yet support `#[link_name]` attributes on functions, or the `#[link_ordinal]` attribute, or `#[link(kind = "raw-dylib")]` on `extern` blocks in bin crates; I intend to publish subsequent PRs to fill those gaps.  It's also not yet clear whether this works for functions in `extern "stdcall"` blocks; I also intend to investigate that shortly and make any necessary changes as a follow-on PR.

This implementation calls out to an LLVM function to construct the actual `.idata` sections as temporary `.lib` files on disk and then links those into the generated .rlib.
This commit is contained in:
bors 2021-06-06 03:59:17 +00:00
commit 9a576175cc
27 changed files with 481 additions and 20 deletions

View File

@ -254,6 +254,15 @@ impl<'a> ArchiveBuilder<'a> for ArArchiveBuilder<'a> {
}
}
}
fn inject_dll_import_lib(
&mut self,
_lib_name: &str,
_dll_imports: &[rustc_middle::middle::cstore::DllImport],
_tmpdir: &rustc_data_structures::temp_dir::MaybeTempDir,
) {
bug!("injecting dll imports is not supported");
}
}
impl<'a> ArArchiveBuilder<'a> {

View File

@ -8,9 +8,11 @@ use std::ptr;
use std::str;
use crate::llvm::archive_ro::{ArchiveRO, Child};
use crate::llvm::{self, ArchiveKind};
use crate::llvm::{self, ArchiveKind, LLVMMachineType, LLVMRustCOFFShortExport};
use rustc_codegen_ssa::back::archive::{find_library, ArchiveBuilder};
use rustc_codegen_ssa::{looks_like_rust_object_file, METADATA_FILENAME};
use rustc_data_structures::temp_dir::MaybeTempDir;
use rustc_middle::middle::cstore::DllImport;
use rustc_session::Session;
use rustc_span::symbol::Symbol;
@ -61,6 +63,17 @@ fn archive_config<'a>(sess: &'a Session, output: &Path, input: Option<&Path>) ->
}
}
/// Map machine type strings to values of LLVM's MachineTypes enum.
fn llvm_machine_type(cpu: &str) -> LLVMMachineType {
match cpu {
"x86_64" => LLVMMachineType::AMD64,
"x86" => LLVMMachineType::I386,
"aarch64" => LLVMMachineType::ARM64,
"arm" => LLVMMachineType::ARM,
_ => panic!("unsupported cpu type {}", cpu),
}
}
impl<'a> ArchiveBuilder<'a> for LlvmArchiveBuilder<'a> {
/// Creates a new static archive, ready for modifying the archive specified
/// by `config`.
@ -175,6 +188,74 @@ impl<'a> ArchiveBuilder<'a> for LlvmArchiveBuilder<'a> {
self.config.sess.fatal(&format!("failed to build archive: {}", e));
}
}
fn inject_dll_import_lib(
&mut self,
lib_name: &str,
dll_imports: &[DllImport],
tmpdir: &MaybeTempDir,
) {
let output_path = {
let mut output_path: PathBuf = tmpdir.as_ref().to_path_buf();
output_path.push(format!("{}_imports", lib_name));
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_vector: Vec<CString> = dll_imports
.iter()
.map(if self.config.sess.target.arch == "x86" {
|import: &DllImport| CString::new(format!("_{}", import.name.to_string())).unwrap()
} else {
|import: &DllImport| CString::new(import.name.to_string()).unwrap()
})
.collect();
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(", "),
);
let ffi_exports: Vec<LLVMRustCOFFShortExport> = import_name_vector
.iter()
.map(|name_z| LLVMRustCOFFShortExport::from_name(name_z.as_ptr()))
.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!(
"failed to add native library {}: {}",
output_path.display(),
e
));
});
}
}
impl<'a> LlvmArchiveBuilder<'a> {

View File

@ -29,6 +29,31 @@ pub enum LLVMRustResult {
Success,
Failure,
}
// Rust version of the C struct with the same name in rustc_llvm/llvm-wrapper/RustWrapper.cpp.
#[repr(C)]
pub struct LLVMRustCOFFShortExport {
pub name: *const c_char,
}
impl LLVMRustCOFFShortExport {
pub fn from_name(name: *const c_char) -> LLVMRustCOFFShortExport {
LLVMRustCOFFShortExport { name }
}
}
/// Translation of LLVM's MachineTypes enum, defined in llvm\include\llvm\BinaryFormat\COFF.h.
///
/// We include only architectures supported on Windows.
#[derive(Copy, Clone, PartialEq)]
#[repr(C)]
pub enum LLVMMachineType {
AMD64 = 0x8664,
I386 = 0x14c,
ARM64 = 0xaa64,
ARM = 0x01c0,
}
// Consts for the LLVM CallConv type, pre-cast to usize.
/// LLVM CallingConv::ID. Should we wrap this?
@ -2265,6 +2290,15 @@ extern "C" {
) -> &'a mut RustArchiveMember<'a>;
pub fn LLVMRustArchiveMemberFree(Member: &'a mut RustArchiveMember<'a>);
pub fn LLVMRustWriteImportLibrary(
ImportName: *const c_char,
Path: *const c_char,
Exports: *const LLVMRustCOFFShortExport,
NumExports: usize,
Machine: u16,
MinGW: bool,
) -> LLVMRustResult;
pub fn LLVMRustSetDataLayoutFromTargetMachine(M: &'a Module, TM: &'a TargetMachine);
pub fn LLVMRustBuildOperandBundleDef(

View File

@ -1,3 +1,5 @@
use rustc_data_structures::temp_dir::MaybeTempDir;
use rustc_middle::middle::cstore::DllImport;
use rustc_session::Session;
use rustc_span::symbol::Symbol;
@ -57,4 +59,11 @@ pub trait ArchiveBuilder<'a> {
fn update_symbols(&mut self);
fn build(self);
fn inject_dll_import_lib(
&mut self,
lib_name: &str,
dll_imports: &[DllImport],
tmpdir: &MaybeTempDir,
);
}

View File

@ -1,9 +1,9 @@
use rustc_data_structures::fx::FxHashSet;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_data_structures::temp_dir::MaybeTempDir;
use rustc_errors::Handler;
use rustc_fs_util::fix_windows_verbatim_for_gcc;
use rustc_hir::def_id::CrateNum;
use rustc_middle::middle::cstore::LibSource;
use rustc_middle::middle::cstore::{DllImport, LibSource};
use rustc_middle::middle::dependency_format::Linkage;
use rustc_session::config::{self, CFGuard, CrateType, DebugInfo};
use rustc_session::config::{OutputFilenames, OutputType, PrintRequest};
@ -34,6 +34,7 @@ use object::write::Object;
use object::{Architecture, BinaryFormat, Endianness, FileFlags, SectionFlags, SectionKind};
use tempfile::Builder as TempFileBuilder;
use std::cmp::Ordering;
use std::ffi::OsString;
use std::path::{Path, PathBuf};
use std::process::{ExitStatus, Output, Stdio};
@ -343,6 +344,12 @@ fn link_rlib<'a, B: ArchiveBuilder<'a>>(
}
}
for (raw_dylib_name, raw_dylib_imports) in
collate_raw_dylibs(&codegen_results.crate_info.used_libraries)
{
ab.inject_dll_import_lib(&raw_dylib_name, &raw_dylib_imports, tmpdir);
}
// After adding all files to the archive, we need to update the
// symbol table of the archive.
ab.update_symbols();
@ -524,6 +531,57 @@ fn link_rlib<'a, B: ArchiveBuilder<'a>>(
}
}
/// Extract all symbols defined in raw-dylib libraries, collated by library name.
///
/// If we have multiple extern blocks that specify symbols defined in the same raw-dylib library,
/// then the CodegenResults value contains one NativeLib instance for each block. However, the
/// linker appears to expect only a single import library for each library used, so we need to
/// collate the symbols together by library name before generating the import libraries.
fn collate_raw_dylibs(used_libraries: &[NativeLib]) -> Vec<(String, Vec<DllImport>)> {
let mut dylib_table: FxHashMap<String, FxHashSet<Symbol>> = FxHashMap::default();
for lib in used_libraries {
if lib.kind == NativeLibKind::RawDylib {
let name = lib.name.unwrap_or_else(||
bug!("`link` attribute with kind = \"raw-dylib\" and no name should have caused error earlier")
);
let name = if matches!(lib.verbatim, Some(true)) {
name.to_string()
} else {
format!("{}.dll", name)
};
dylib_table
.entry(name)
.or_default()
.extend(lib.dll_imports.iter().map(|import| import.name));
}
}
// FIXME: when we add support for ordinals, fix this to propagate ordinals. Also figure out
// what we should do if we have two DllImport values with the same name but different
// ordinals.
let mut result = dylib_table
.into_iter()
.map(|(lib_name, imported_names)| {
let mut names = imported_names
.iter()
.map(|name| DllImport { name: *name, ordinal: None })
.collect::<Vec<_>>();
names.sort_unstable_by(|a: &DllImport, b: &DllImport| {
match a.name.as_str().cmp(&b.name.as_str()) {
Ordering::Equal => a.ordinal.cmp(&b.ordinal),
x => x,
}
});
(lib_name, names)
})
.collect::<Vec<_>>();
result.sort_unstable_by(|a: &(String, Vec<DllImport>), b: &(String, Vec<DllImport>)| {
a.0.cmp(&b.0)
});
result
}
/// Create a static archive.
///
/// This is essentially the same thing as an rlib, but it also involves adding all of the upstream
@ -2303,10 +2361,7 @@ fn add_upstream_native_libraries(
// already included them when we included the rust library
// previously
NativeLibKind::Static { bundle: None | Some(true), .. } => {}
NativeLibKind::RawDylib => {
// FIXME(#58713): Proper handling for raw dylibs.
bug!("raw_dylib feature not yet implemented");
}
NativeLibKind::RawDylib => {}
}
}
}

View File

@ -110,11 +110,18 @@ pub struct NativeLib {
pub name: Option<Symbol>,
pub cfg: Option<ast::MetaItem>,
pub verbatim: Option<bool>,
pub dll_imports: Vec<cstore::DllImport>,
}
impl From<&cstore::NativeLib> for NativeLib {
fn from(lib: &cstore::NativeLib) -> Self {
NativeLib { kind: lib.kind, name: lib.name, cfg: lib.cfg.clone(), verbatim: lib.verbatim }
NativeLib {
kind: lib.kind,
name: lib.name,
cfg: lib.cfg.clone(),
verbatim: lib.verbatim,
dll_imports: lib.dll_imports.clone(),
}
}
}

View File

@ -6,6 +6,7 @@
#include "llvm/IR/Instructions.h"
#include "llvm/IR/Intrinsics.h"
#include "llvm/Object/Archive.h"
#include "llvm/Object/COFFImportFile.h"
#include "llvm/Object/ObjectFile.h"
#include "llvm/Bitcode/BitcodeWriterPass.h"
#include "llvm/Support/Signals.h"
@ -1722,3 +1723,54 @@ extern "C" LLVMValueRef
LLVMRustBuildMaxNum(LLVMBuilderRef B, LLVMValueRef LHS, LLVMValueRef RHS) {
return wrap(unwrap(B)->CreateMaxNum(unwrap(LHS),unwrap(RHS)));
}
// This struct contains all necessary info about a symbol exported from a DLL.
// At the moment, it's just the symbol's name, but we use a separate struct to
// make it easier to add other information like ordinal later.
struct LLVMRustCOFFShortExport {
const char* name;
};
// Machine must be a COFF machine type, as defined in PE specs.
extern "C" LLVMRustResult LLVMRustWriteImportLibrary(
const char* ImportName,
const char* Path,
const LLVMRustCOFFShortExport* Exports,
size_t NumExports,
uint16_t Machine,
bool MinGW)
{
std::vector<llvm::object::COFFShortExport> ConvertedExports;
ConvertedExports.reserve(NumExports);
for (size_t i = 0; i < NumExports; ++i) {
ConvertedExports.push_back(llvm::object::COFFShortExport{
Exports[i].name, // Name
std::string{}, // ExtName
std::string{}, // SymbolName
std::string{}, // AliasTarget
0, // Ordinal
false, // Noname
false, // Data
false, // Private
false // Constant
});
}
auto Error = llvm::object::writeImportLibrary(
ImportName,
Path,
ConvertedExports,
static_cast<llvm::COFF::MachineTypes>(Machine),
MinGW);
if (Error) {
std::string errorString;
llvm::raw_string_ostream stream(errorString);
stream << Error;
stream.flush();
LLVMRustSetLastError(errorString.c_str());
return LLVMRustResult::Failure;
} else {
return LLVMRustResult::Success;
}
}

View File

@ -3,7 +3,7 @@ use rustc_data_structures::fx::FxHashSet;
use rustc_errors::struct_span_err;
use rustc_hir as hir;
use rustc_hir::itemlikevisit::ItemLikeVisitor;
use rustc_middle::middle::cstore::NativeLib;
use rustc_middle::middle::cstore::{DllImport, NativeLib};
use rustc_middle::ty::TyCtxt;
use rustc_session::parse::feature_err;
use rustc_session::utils::NativeLibKind;
@ -33,8 +33,8 @@ struct Collector<'tcx> {
impl ItemLikeVisitor<'tcx> for Collector<'tcx> {
fn visit_item(&mut self, it: &'tcx hir::Item<'tcx>) {
let abi = match it.kind {
hir::ItemKind::ForeignMod { abi, .. } => abi,
let (abi, foreign_mod_items) = match it.kind {
hir::ItemKind::ForeignMod { abi, items } => (abi, items),
_ => return,
};
@ -57,6 +57,7 @@ impl ItemLikeVisitor<'tcx> for Collector<'tcx> {
foreign_module: Some(it.def_id.to_def_id()),
wasm_import_module: None,
verbatim: None,
dll_imports: Vec::new(),
};
let mut kind_specified = false;
@ -196,6 +197,27 @@ impl ItemLikeVisitor<'tcx> for Collector<'tcx> {
.span_label(m.span, "missing `name` argument")
.emit();
}
if lib.kind == NativeLibKind::RawDylib {
match abi {
Abi::C { .. } => (),
Abi::Cdecl => (),
_ => {
if sess.target.arch == "x86" {
sess.span_fatal(
it.span,
r#"`#[link(kind = "raw-dylib")]` only supports C and Cdecl ABIs"#,
);
}
}
};
lib.dll_imports.extend(
foreign_mod_items
.iter()
.map(|child_item| DllImport { name: child_item.ident.name, ordinal: None }),
);
}
self.register_native_lib(Some(m.span), lib);
}
}
@ -253,15 +275,42 @@ impl Collector<'tcx> {
)
.emit();
}
if lib.kind == NativeLibKind::RawDylib && !self.tcx.features().raw_dylib {
feature_err(
&self.tcx.sess.parse_sess,
sym::raw_dylib,
span.unwrap_or(rustc_span::DUMMY_SP),
"kind=\"raw-dylib\" is unstable",
)
.emit();
// this just unwraps lib.name; we already established that it isn't empty above.
if let (NativeLibKind::RawDylib, Some(lib_name)) = (lib.kind, lib.name) {
let span = match span {
Some(s) => s,
None => {
bug!("raw-dylib libraries are not supported on the command line");
}
};
if !self.tcx.sess.target.options.is_like_windows {
self.tcx.sess.span_fatal(
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') {
self.tcx.sess.span_err(span, "library name may not contain NUL characters");
}
if !self.tcx.features().raw_dylib {
feature_err(
&self.tcx.sess.parse_sess,
sym::raw_dylib,
span,
"kind=\"raw-dylib\" is unstable",
)
.emit();
}
}
self.libs.push(lib);
}
@ -337,6 +386,7 @@ impl Collector<'tcx> {
foreign_module: None,
wasm_import_module: None,
verbatim: passed_lib.verbatim,
dll_imports: Vec::new(),
};
self.register_native_lib(None, lib);
} else {

View File

@ -95,6 +95,13 @@ pub struct NativeLib {
pub foreign_module: Option<DefId>,
pub wasm_import_module: Option<Symbol>,
pub verbatim: Option<bool>,
pub dll_imports: Vec<DllImport>,
}
#[derive(Clone, Debug, Encodable, Decodable, HashStable)]
pub struct DllImport {
pub name: Symbol,
pub ordinal: Option<u16>,
}
#[derive(Clone, TyEncodable, TyDecodable, HashStable, Debug)]

View File

@ -0,0 +1,21 @@
# Test the behavior of #[link(.., kind = "raw-dylib")] on windows-msvc
# only-windows
# only-msvc
-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)
$(CC) "$(TMPDIR)"/extern_1.obj -link -dll -out:"$(TMPDIR)"/extern_1.dll
$(CC) "$(TMPDIR)"/extern_2.obj -link -dll -out:"$(TMPDIR)"/extern_2.dll
$(RUSTC) --crate-type lib --crate-name raw_dylib_test lib.rs
$(RUSTC) --crate-type bin driver.rs -L "$(TMPDIR)"
"$(TMPDIR)"/driver > "$(TMPDIR)"/output.txt
ifdef RUSTC_BLESS_TEST
cp "$(TMPDIR)"/output.txt output.txt
else
$(DIFF) output.txt "$(TMPDIR)"/output.txt
endif

View File

@ -0,0 +1,5 @@
extern crate raw_dylib_test;
fn main() {
raw_dylib_test::library_function();
}

View File

@ -0,0 +1,16 @@
#include <stdio.h>
__declspec(dllexport) void extern_fn_1() {
printf("extern_fn_1\n");
fflush(stdout);
}
__declspec(dllexport) void extern_fn_2() {
printf("extern_fn_2; didn't get the rename\n");
fflush(stdout);
}
__declspec(dllexport) void extern_fn_with_long_name() {
printf("extern_fn_with_long_name; got the rename\n");
fflush(stdout);
}

View File

@ -0,0 +1,6 @@
#include <stdio.h>
__declspec(dllexport) void extern_fn_3() {
printf("extern_fn_3\n");
fflush(stdout);
}

View File

@ -0,0 +1,22 @@
#![feature(raw_dylib, native_link_modifiers, native_link_modifiers_verbatim)]
#[link(name = "extern_1.dll", kind = "raw-dylib", modifiers = "+verbatim")]
extern {
fn extern_fn_1();
}
#[link(name = "extern_2", kind = "raw-dylib")]
extern {
fn extern_fn_3();
}
pub fn library_function() {
#[link(name = "extern_1", kind = "raw-dylib")]
extern { fn extern_fn_2(); }
unsafe {
extern_fn_1();
extern_fn_2();
extern_fn_3();
}
}

View File

@ -0,0 +1,3 @@
extern_fn_1
extern_fn_2; didn't get the rename
extern_fn_3

View File

@ -0,0 +1,8 @@
// gate-test-raw_dylib
// only-windows-gnu
#[link(name = "foo", kind = "raw-dylib")]
//~^ ERROR: kind="raw-dylib" is unstable
//~| WARNING: `#[link(...)]` with `kind = "raw-dylib"` not supported on windows-gnu
extern "C" {}
fn main() {}

View File

@ -0,0 +1,18 @@
warning: `#[link(...)]` with `kind = "raw-dylib"` not supported on windows-gnu
--> $DIR/feature-gate-raw-dylib-windows-gnu.rs:3:1
|
LL | #[link(name = "foo", kind = "raw-dylib")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error[E0658]: kind="raw-dylib" is unstable
--> $DIR/feature-gate-raw-dylib-windows-gnu.rs:3:1
|
LL | #[link(name = "foo", kind = "raw-dylib")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: see issue #58713 <https://github.com/rust-lang/rust/issues/58713> for more information
= help: add `#![feature(raw_dylib)]` to the crate attributes to enable
error: aborting due to previous error; 1 warning emitted
For more information about this error, try `rustc --explain E0658`.

View File

@ -1,3 +1,5 @@
// gate-test-raw_dylib
// only-windows-msvc
#[link(name = "foo", kind = "raw-dylib")]
//~^ ERROR: kind="raw-dylib" is unstable
extern "C" {}

View File

@ -1,5 +1,5 @@
error[E0658]: kind="raw-dylib" is unstable
--> $DIR/feature-gate-raw-dylib.rs:1:1
--> $DIR/feature-gate-raw-dylib-windows-msvc.rs:3:1
|
LL | #[link(name = "foo", kind = "raw-dylib")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -0,0 +1,5 @@
// compile-flags:-l raw-dylib=foo
// error-pattern: unknown library kind `raw-dylib`, expected one of dylib, framework, or static
fn main() {
}

View File

@ -0,0 +1,2 @@
error: unknown library kind `raw-dylib`, expected one of dylib, framework, or static

View File

@ -0,0 +1,8 @@
// only-windows-gnu
// check-pass
// compile-flags: --crate-type lib
#![feature(raw_dylib)]
//~^ WARNING: the feature `raw_dylib` is incomplete
#[link(name = "foo", kind = "raw-dylib")]
//~^ WARNING: `#[link(...)]` with `kind = "raw-dylib"` not supported on windows-gnu
extern "C" {}

View File

@ -0,0 +1,17 @@
warning: the feature `raw_dylib` is incomplete and may not be safe to use and/or cause compiler crashes
--> $DIR/raw-dylib-msvc-only.rs:4:12
|
LL | #![feature(raw_dylib)]
| ^^^^^^^^^
|
= note: `#[warn(incomplete_features)]` on by default
= note: see issue #58713 <https://github.com/rust-lang/rust/issues/58713> for more information
warning: `#[link(...)]` with `kind = "raw-dylib"` not supported on windows-gnu
--> $DIR/raw-dylib-msvc-only.rs:6:1
|
LL | #[link(name = "foo", kind = "raw-dylib")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
warning: 2 warnings emitted

View File

@ -0,0 +1,7 @@
// ignore-windows
// compile-flags: --crate-type lib
#![feature(raw_dylib)]
//~^ WARNING: the feature `raw_dylib` is incomplete
#[link(name = "foo", kind = "raw-dylib")]
//~^ ERROR: `#[link(...)]` with `kind = "raw-dylib"` only supported on Windows
extern "C" {}

View File

@ -0,0 +1,17 @@
warning: the feature `raw_dylib` is incomplete and may not be safe to use and/or cause compiler crashes
--> $DIR/raw-dylib-windows-only.rs:3:12
|
LL | #![feature(raw_dylib)]
| ^^^^^^^^^
|
= note: `#[warn(incomplete_features)]` on by default
= note: see issue #58713 <https://github.com/rust-lang/rust/issues/58713> for more information
error: `#[link(...)]` with `kind = "raw-dylib"` only supported on Windows
--> $DIR/raw-dylib-windows-only.rs:5:1
|
LL | #[link(name = "foo", kind = "raw-dylib")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: aborting due to previous error; 1 warning emitted