rust/compiler/rustc_metadata/src/native_libs.rs

438 lines
18 KiB
Rust
Raw Normal View History

use rustc_ast::CRATE_NODE_ID;
use rustc_attr as attr;
2019-12-23 22:02:53 -06:00
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::ty::{List, ParamEnv, ParamEnvAnd, Ty, TyCtxt};
use rustc_session::cstore::{DllCallingConvention, DllImport, NativeLib};
use rustc_session::parse::feature_err;
use rustc_session::utils::NativeLibKind;
use rustc_session::Session;
2020-01-01 12:30:57 -06:00
use rustc_span::symbol::{kw, sym, Symbol};
use rustc_span::Span;
use rustc_target::spec::abi::Abi;
crate fn collect(tcx: TyCtxt<'_>) -> Vec<NativeLib> {
2019-12-22 16:42:04 -06:00
let mut collector = Collector { tcx, libs: Vec::new() };
tcx.hir().visit_all_item_likes(&mut collector);
collector.process_command_line();
collector.libs
}
crate fn relevant_lib(sess: &Session, lib: &NativeLib) -> bool {
match lib.cfg {
Some(ref cfg) => attr::cfg_matches(cfg, &sess.parse_sess, CRATE_NODE_ID, None),
None => true,
}
}
struct Collector<'tcx> {
2019-06-13 16:48:52 -05:00
tcx: TyCtxt<'tcx>,
libs: Vec<NativeLib>,
}
impl<'tcx> ItemLikeVisitor<'tcx> for Collector<'tcx> {
2019-11-28 12:28:50 -06:00
fn visit_item(&mut self, it: &'tcx hir::Item<'tcx>) {
2022-02-18 17:48:49 -06:00
let hir::ItemKind::ForeignMod { abi, items: foreign_mod_items } = it.kind else {
return;
};
2020-11-11 15:40:09 -06:00
if abi == Abi::Rust || abi == Abi::RustIntrinsic || abi == Abi::PlatformIntrinsic {
2019-12-22 16:42:04 -06:00
return;
}
// Process all of the #[link(..)]-style arguments
let sess = &self.tcx.sess;
for m in self.tcx.hir().attrs(it.hir_id()).iter().filter(|a| a.has_name(sym::link)) {
2022-02-18 17:48:49 -06:00
let Some(items) = m.meta_item_list() else {
continue;
};
let mut lib = NativeLib {
name: None,
kind: NativeLibKind::Unspecified,
cfg: None,
foreign_module: Some(it.def_id.to_def_id()),
wasm_import_module: None,
verbatim: None,
dll_imports: Vec::new(),
};
let mut kind_specified = false;
for item in items.iter() {
if item.has_name(sym::kind) {
kind_specified = true;
2022-02-18 17:48:49 -06:00
let Some(kind) = item.value_str() else {
continue; // skip like historical compilers
};
lib.kind = match kind.as_str() {
"static" => NativeLibKind::Static { bundle: None, whole_archive: None },
"static-nobundle" => {
sess.struct_span_warn(
item.span(),
"library kind `static-nobundle` has been superseded by specifying \
modifier `-bundle` with library kind `static`",
)
.emit();
if !self.tcx.features().static_nobundle {
feature_err(
&self.tcx.sess.parse_sess,
sym::static_nobundle,
item.span(),
"kind=\"static-nobundle\" is unstable",
)
.emit();
}
NativeLibKind::Static { bundle: Some(false), whole_archive: None }
}
"dylib" => NativeLibKind::Dylib { as_needed: None },
"framework" => NativeLibKind::Framework { as_needed: None },
"raw-dylib" => NativeLibKind::RawDylib,
k => {
struct_span_err!(sess, item.span(), E0458, "unknown kind: `{}`", k)
.span_label(item.span(), "unknown kind")
.span_label(m.span, "")
.emit();
NativeLibKind::Unspecified
}
};
} else if item.has_name(sym::name) {
lib.name = item.value_str();
} else if item.has_name(sym::cfg) {
2022-02-18 17:48:49 -06:00
let Some(cfg) = item.meta_item_list() else {
continue; // skip like historical compilers
};
if cfg.is_empty() {
sess.span_err(item.span(), "`cfg()` must have an argument");
} else if let cfg @ Some(..) = cfg[0].meta_item() {
lib.cfg = cfg.cloned();
} else {
sess.span_err(cfg[0].span(), "invalid argument for `cfg(..)`");
}
} else if item.has_name(sym::wasm_import_module) {
match item.value_str() {
Some(s) => lib.wasm_import_module = Some(s),
None => {
let msg = "must be of the form `#[link(wasm_import_module = \"...\")]`";
sess.span_err(item.span(), msg);
}
}
} else {
// currently, like past compilers, ignore unknown
// directives here.
}
}
// Do this outside the above loop so we don't depend on modifiers coming
// after kinds
if let Some(item) = items.iter().find(|item| item.has_name(sym::modifiers)) {
if let Some(modifiers) = item.value_str() {
let span = item.name_value_literal_span().unwrap();
for modifier in modifiers.as_str().split(',') {
2021-12-02 20:06:36 -06:00
let (modifier, value) = match modifier.strip_prefix(&['+', '-']) {
Some(m) => (m, modifier.starts_with('+')),
None => {
sess.span_err(
span,
"invalid linking modifier syntax, expected '+' or '-' prefix \
before one of: bundle, verbatim, whole-archive, as-needed",
);
continue;
}
};
match (modifier, &mut lib.kind) {
("bundle", NativeLibKind::Static { bundle, .. }) => {
*bundle = Some(value);
}
("bundle", _) => sess.span_err(
span,
"bundle linking modifier is only compatible with \
`static` linking kind",
),
("verbatim", _) => lib.verbatim = Some(value),
("whole-archive", NativeLibKind::Static { whole_archive, .. }) => {
*whole_archive = Some(value);
}
("whole-archive", _) => sess.span_err(
span,
"whole-archive linking modifier is only compatible with \
`static` linking kind",
),
("as-needed", NativeLibKind::Dylib { as_needed })
| ("as-needed", NativeLibKind::Framework { as_needed }) => {
*as_needed = Some(value);
}
("as-needed", _) => sess.span_err(
span,
"as-needed linking modifier is only compatible with \
`dylib` and `framework` linking kinds",
),
_ => sess.span_err(
span,
&format!(
"unrecognized linking modifier `{}`, expected one \
of: bundle, verbatim, whole-archive, as-needed",
modifier
),
),
}
}
} else {
let msg = "must be of the form `#[link(modifiers = \"...\")]`";
sess.span_err(item.span(), msg);
}
}
// In general we require #[link(name = "...")] but we allow
// #[link(wasm_import_module = "...")] without the `name`.
let requires_name = kind_specified || lib.wasm_import_module.is_none();
if lib.name.is_none() && requires_name {
2019-12-22 16:42:04 -06:00
struct_span_err!(
sess,
2019-12-22 16:42:04 -06:00
m.span,
E0459,
"`#[link(...)]` specified without \
`name = \"foo\"`"
)
.span_label(m.span, "missing `name` argument")
.emit();
}
if lib.kind == NativeLibKind::RawDylib {
lib.dll_imports.extend(
foreign_mod_items
.iter()
.map(|child_item| self.build_dll_import(abi, child_item)),
);
}
self.register_native_lib(Some(m.span), lib);
}
}
2019-11-28 14:47:10 -06:00
fn visit_trait_item(&mut self, _it: &'tcx hir::TraitItem<'tcx>) {}
2019-11-28 15:16:44 -06:00
fn visit_impl_item(&mut self, _it: &'tcx hir::ImplItem<'tcx>) {}
2020-11-11 14:57:54 -06:00
fn visit_foreign_item(&mut self, _it: &'tcx hir::ForeignItem<'tcx>) {}
}
impl Collector<'_> {
fn register_native_lib(&mut self, span: Option<Span>, lib: NativeLib) {
if lib.name.as_ref().map_or(false, |&s| s == kw::Empty) {
match span {
Some(span) => {
2019-12-22 16:42:04 -06:00
struct_span_err!(
self.tcx.sess,
span,
E0454,
"`#[link(name = \"\")]` given with empty name"
)
.span_label(span, "empty name given")
.emit();
}
None => {
self.tcx.sess.err("empty library name given via `-l`");
}
}
2019-12-22 16:42:04 -06:00
return;
}
let is_osx = self.tcx.sess.target.is_like_osx;
if matches!(lib.kind, NativeLibKind::Framework { .. }) && !is_osx {
let msg = "native frameworks are only available on macOS targets";
match span {
Some(span) => {
struct_span_err!(self.tcx.sess, span, E0455, "{}", msg).emit();
}
None => self.tcx.sess.err(msg),
}
}
2018-02-14 09:11:02 -06:00
if lib.cfg.is_some() && !self.tcx.features().link_cfg {
feature_err(
&self.tcx.sess.parse_sess,
sym::link_cfg,
span.unwrap(),
"kind=\"link_cfg\" 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) {
2022-02-18 17:48:49 -06:00
let Some(span) = span else {
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",
);
}
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();
}
2019-08-27 09:42:44 -05:00
}
self.libs.push(lib);
}
// Process libs passed on the command line
fn process_command_line(&mut self) {
// First, check for errors
let mut renames = FxHashSet::default();
for lib in &self.tcx.sess.opts.libs {
if let Some(ref new_name) = lib.new_name {
2019-12-22 16:42:04 -06:00
let any_duplicate = self
.libs
.iter()
.filter_map(|lib| lib.name.as_ref())
2021-11-07 03:33:27 -06:00
.any(|n| n.as_str() == lib.name);
if new_name.is_empty() {
2019-12-22 16:42:04 -06:00
self.tcx.sess.err(&format!(
"an empty renaming target was specified for library `{}`",
lib.name
2019-12-22 16:42:04 -06:00
));
} else if !any_duplicate {
2019-12-22 16:42:04 -06:00
self.tcx.sess.err(&format!(
"renaming of the library `{}` was specified, \
however this crate contains no `#[link(...)]` \
2021-10-03 01:53:02 -05:00
attributes referencing this library",
lib.name
2019-12-22 16:42:04 -06:00
));
} else if !renames.insert(&lib.name) {
2019-12-22 16:42:04 -06:00
self.tcx.sess.err(&format!(
"multiple renamings were \
2021-10-03 01:53:02 -05:00
specified for library `{}`",
lib.name
2019-12-22 16:42:04 -06:00
));
}
}
}
2018-08-19 08:30:23 -05:00
// Update kind and, optionally, the name of all native libraries
// (there may be more than one) with the specified name. If any
// library is mentioned more than once, keep the latest mention
// of it, so that any possible dependent libraries appear before
// it. (This ensures that the linker is able to see symbols from
// all possible dependent libraries before linking in the library
// in question.)
for passed_lib in &self.tcx.sess.opts.libs {
// If we've already added any native libraries with the same
2018-12-20 15:19:55 -06:00
// name, they will be pulled out into `existing`, so that we
// can move them to the end of the list below.
2019-12-22 16:42:04 -06:00
let mut existing = self
.libs
.drain_filter(|lib| {
if let Some(lib_name) = lib.name {
if lib_name.as_str() == passed_lib.name {
if passed_lib.kind != NativeLibKind::Unspecified {
lib.kind = passed_lib.kind;
2019-12-22 16:42:04 -06:00
}
if let Some(new_name) = &passed_lib.new_name {
2019-12-22 16:42:04 -06:00
lib.name = Some(Symbol::intern(new_name));
}
lib.verbatim = passed_lib.verbatim;
2019-12-22 16:42:04 -06:00
return true;
}
}
2019-12-22 16:42:04 -06:00
false
})
.collect::<Vec<_>>();
if existing.is_empty() {
// Add if not found
2021-10-07 16:18:39 -05:00
let new_name: Option<&str> = passed_lib.new_name.as_deref();
let lib = NativeLib {
name: Some(Symbol::intern(new_name.unwrap_or(&passed_lib.name))),
kind: passed_lib.kind,
cfg: None,
rustc: Add a `#[wasm_import_module]` attribute This commit adds a new attribute to the Rust compiler specific to the wasm target (and no other targets). The `#[wasm_import_module]` attribute is used to specify the module that a name is imported from, and is used like so: #[wasm_import_module = "./foo.js"] extern { fn some_js_function(); } Here the import of the symbol `some_js_function` is tagged with the `./foo.js` module in the wasm output file. Wasm-the-format includes two fields on all imports, a module and a field. The field is the symbol name (`some_js_function` above) and the module has historically unconditionally been `"env"`. I'm not sure if this `"env"` convention has asm.js or LLVM roots, but regardless we'd like the ability to configure it! The proposed ES module integration with wasm (aka a wasm module is "just another ES module") requires that the import module of wasm imports is interpreted as an ES module import, meaning that you'll need to encode paths, NPM packages, etc. As a result, we'll need this to be something other than `"env"`! Unfortunately neither our version of LLVM nor LLD supports custom import modules (aka anything not `"env"`). My hope is that by the time LLVM 7 is released both will have support, but in the meantime this commit adds some primitive encoding/decoding of wasm files to the compiler. This way rustc postprocesses the wasm module that LLVM emits to ensure it's got all the imports we'd like to have in it. Eventually I'd ideally like to unconditionally require this attribute to be placed on all `extern { ... }` blocks. For now though it seemed prudent to add it as an unstable attribute, so for now it's not required (as that'd force usage of a feature gate). Hopefully it doesn't take too long to "stabilize" this! cc rust-lang-nursery/rust-wasm#29
2018-02-10 16:28:17 -06:00
foreign_module: None,
wasm_import_module: None,
verbatim: passed_lib.verbatim,
dll_imports: Vec::new(),
};
self.register_native_lib(None, lib);
} else {
// Move all existing libraries with the same name to the
// end of the command line.
self.libs.append(&mut existing);
}
}
}
2021-07-15 15:19:39 -05:00
fn i686_arg_list_size(&self, item: &hir::ForeignItemRef) -> usize {
let argument_types: &List<Ty<'_>> = self.tcx.erase_late_bound_regions(
self.tcx
.type_of(item.id.def_id)
.fn_sig(self.tcx)
.inputs()
.map_bound(|slice| self.tcx.mk_type_list(slice.iter())),
);
argument_types
.iter()
.map(|ty| {
let layout = self
.tcx
.layout_of(ParamEnvAnd { param_env: ParamEnv::empty(), value: ty })
.expect("layout")
.layout;
// In both stdcall and fastcall, we always round up the argument size to the
// nearest multiple of 4 bytes.
(layout.size.bytes_usize() + 3) & !3
})
.sum()
}
2021-07-15 15:19:39 -05:00
fn build_dll_import(&self, abi: Abi, item: &hir::ForeignItemRef) -> DllImport {
let calling_convention = if self.tcx.sess.target.arch == "x86" {
match abi {
Abi::C { .. } | Abi::Cdecl { .. } => DllCallingConvention::C,
Abi::Stdcall { .. } | Abi::System { .. } => {
DllCallingConvention::Stdcall(self.i686_arg_list_size(item))
}
Abi::Fastcall { .. } => {
DllCallingConvention::Fastcall(self.i686_arg_list_size(item))
}
// Vectorcall is intentionally not supported at this time.
_ => {
self.tcx.sess.span_fatal(
item.span,
r#"ABI not supported by `#[link(kind = "raw-dylib")]` on i686"#,
);
}
}
} else {
match abi {
Abi::C { .. } | Abi::Win64 { .. } | Abi::System { .. } => DllCallingConvention::C,
_ => {
self.tcx.sess.span_fatal(
item.span,
r#"ABI not supported by `#[link(kind = "raw-dylib")]` on this architecture"#,
);
}
}
};
DllImport {
name: item.ident.name,
ordinal: self.tcx.codegen_fn_attrs(item.id.def_id).link_ordinal,
calling_convention,
span: item.span,
}
}
}