1 use rustc_ast::CRATE_NODE_ID;
2 use rustc_attr as attr;
3 use rustc_data_structures::fx::FxHashSet;
4 use rustc_errors::struct_span_err;
6 use rustc_hir::def::DefKind;
7 use rustc_middle::ty::{List, ParamEnv, ParamEnvAnd, Ty, TyCtxt};
8 use rustc_session::cstore::{DllCallingConvention, DllImport, NativeLib};
9 use rustc_session::parse::feature_err;
10 use rustc_session::utils::NativeLibKind;
11 use rustc_session::Session;
12 use rustc_span::symbol::{kw, sym, Symbol};
14 use rustc_target::spec::abi::Abi;
16 crate fn collect(tcx: TyCtxt<'_>) -> Vec<NativeLib> {
17 let mut collector = Collector { tcx, libs: Vec::new() };
18 for id in tcx.hir().items() {
19 collector.process_item(id);
21 collector.process_command_line();
25 crate fn relevant_lib(sess: &Session, lib: &NativeLib) -> bool {
27 Some(ref cfg) => attr::cfg_matches(cfg, &sess.parse_sess, CRATE_NODE_ID, None),
32 struct Collector<'tcx> {
37 impl<'tcx> Collector<'tcx> {
38 fn process_item(&mut self, id: rustc_hir::ItemId) {
39 if !matches!(self.tcx.def_kind(id.def_id), DefKind::ForeignMod) {
43 let it = self.tcx.hir().item(id);
44 let hir::ItemKind::ForeignMod { abi, items: foreign_mod_items } = it.kind else {
48 if abi == Abi::Rust || abi == Abi::RustIntrinsic || abi == Abi::PlatformIntrinsic {
52 // Process all of the #[link(..)]-style arguments
53 let sess = &self.tcx.sess;
54 for m in self.tcx.hir().attrs(it.hir_id()).iter().filter(|a| a.has_name(sym::link)) {
55 let Some(items) = m.meta_item_list() else {
58 let mut lib = NativeLib {
60 kind: NativeLibKind::Unspecified,
62 foreign_module: Some(it.def_id.to_def_id()),
63 wasm_import_module: None,
65 dll_imports: Vec::new(),
67 let mut kind_specified = false;
69 for item in items.iter() {
70 if item.has_name(sym::kind) {
71 kind_specified = true;
72 let Some(kind) = item.value_str() else {
73 continue; // skip like historical compilers
75 lib.kind = match kind.as_str() {
76 "static" => NativeLibKind::Static { bundle: None, whole_archive: None },
77 "static-nobundle" => {
78 sess.struct_span_warn(
80 "library kind `static-nobundle` has been superseded by specifying \
81 modifier `-bundle` with library kind `static`",
84 if !self.tcx.features().static_nobundle {
86 &self.tcx.sess.parse_sess,
89 "kind=\"static-nobundle\" is unstable",
93 NativeLibKind::Static { bundle: Some(false), whole_archive: None }
95 "dylib" => NativeLibKind::Dylib { as_needed: None },
96 "framework" => NativeLibKind::Framework { as_needed: None },
97 "raw-dylib" => NativeLibKind::RawDylib,
99 struct_span_err!(sess, item.span(), E0458, "unknown kind: `{}`", k)
100 .span_label(item.span(), "unknown kind")
101 .span_label(m.span, "")
103 NativeLibKind::Unspecified
106 } else if item.has_name(sym::name) {
107 lib.name = item.value_str();
108 } else if item.has_name(sym::cfg) {
109 let Some(cfg) = item.meta_item_list() else {
110 continue; // skip like historical compilers
113 sess.span_err(item.span(), "`cfg()` must have an argument");
114 } else if let cfg @ Some(..) = cfg[0].meta_item() {
115 lib.cfg = cfg.cloned();
117 sess.span_err(cfg[0].span(), "invalid argument for `cfg(..)`");
119 } else if item.has_name(sym::wasm_import_module) {
120 match item.value_str() {
121 Some(s) => lib.wasm_import_module = Some(s),
123 let msg = "must be of the form `#[link(wasm_import_module = \"...\")]`";
124 sess.span_err(item.span(), msg);
128 // currently, like past compilers, ignore unknown
133 // Do this outside the above loop so we don't depend on modifiers coming
135 let mut modifiers_count = 0;
136 for item in items.iter().filter(|item| item.has_name(sym::modifiers)) {
137 if let Some(modifiers) = item.value_str() {
138 modifiers_count += 1;
139 let span = item.name_value_literal_span().unwrap();
140 let mut has_duplicate_modifiers = false;
141 for modifier in modifiers.as_str().split(',') {
142 let (modifier, value) = match modifier.strip_prefix(&['+', '-']) {
143 Some(m) => (m, modifier.starts_with('+')),
145 // Note: this error also excludes the case with empty modifier
146 // string, like `modifiers = ""`.
149 "invalid linking modifier syntax, expected '+' or '-' prefix \
150 before one of: bundle, verbatim, whole-archive, as-needed",
156 match (modifier, &mut lib.kind) {
157 ("bundle", NativeLibKind::Static { bundle, .. }) => {
158 if bundle.is_some() {
159 has_duplicate_modifiers = true;
161 *bundle = Some(value);
166 "bundle linking modifier is only compatible with \
167 `static` linking kind",
172 if lib.verbatim.is_some() {
173 has_duplicate_modifiers = true;
175 lib.verbatim = Some(value);
178 ("whole-archive", NativeLibKind::Static { whole_archive, .. }) => {
179 if whole_archive.is_some() {
180 has_duplicate_modifiers = true;
182 *whole_archive = Some(value);
184 ("whole-archive", _) => {
187 "whole-archive linking modifier is only compatible with \
188 `static` linking kind",
192 ("as-needed", NativeLibKind::Dylib { as_needed })
193 | ("as-needed", NativeLibKind::Framework { as_needed }) => {
194 if as_needed.is_some() {
195 has_duplicate_modifiers = true;
197 *as_needed = Some(value);
199 ("as-needed", _) => {
202 "as-needed linking modifier is only compatible with \
203 `dylib` and `framework` linking kinds",
211 "unrecognized linking modifier `{}`, expected one \
212 of: bundle, verbatim, whole-archive, as-needed",
219 if has_duplicate_modifiers {
221 "same modifier is used multiple times in a single `modifiers` argument";
222 sess.span_err(item.span(), msg);
225 let msg = "must be of the form `#[link(modifiers = \"...\")]`";
226 sess.span_err(item.span(), msg);
230 if modifiers_count > 1 {
231 let msg = "multiple `modifiers` arguments in a single `#[link]` attribute";
232 sess.span_err(m.span, msg);
235 // In general we require #[link(name = "...")] but we allow
236 // #[link(wasm_import_module = "...")] without the `name`.
237 let requires_name = kind_specified || lib.wasm_import_module.is_none();
238 if lib.name.is_none() && requires_name {
243 "`#[link(...)]` specified without \
246 .span_label(m.span, "missing `name` argument")
250 if lib.kind == NativeLibKind::RawDylib {
251 lib.dll_imports.extend(
254 .map(|child_item| self.build_dll_import(abi, child_item)),
258 self.register_native_lib(Some(m.span), lib);
262 fn register_native_lib(&mut self, span: Option<Span>, lib: NativeLib) {
263 if lib.name.as_ref().map_or(false, |&s| s == kw::Empty) {
270 "`#[link(name = \"\")]` given with empty name"
272 .span_label(span, "empty name given")
276 self.tcx.sess.err("empty library name given via `-l`");
281 let is_osx = self.tcx.sess.target.is_like_osx;
282 if matches!(lib.kind, NativeLibKind::Framework { .. }) && !is_osx {
283 let msg = "native frameworks are only available on macOS targets";
286 struct_span_err!(self.tcx.sess, span, E0455, "{}", msg).emit();
289 self.tcx.sess.err(msg);
293 if lib.cfg.is_some() && !self.tcx.features().link_cfg {
295 &self.tcx.sess.parse_sess,
298 "kind=\"link_cfg\" is unstable",
302 // this just unwraps lib.name; we already established that it isn't empty above.
303 if let (NativeLibKind::RawDylib, Some(lib_name)) = (lib.kind, lib.name) {
304 let Some(span) = span else {
305 bug!("raw-dylib libraries are not supported on the command line");
308 if !self.tcx.sess.target.options.is_like_windows {
309 self.tcx.sess.span_fatal(
311 "`#[link(...)]` with `kind = \"raw-dylib\"` only supported on Windows",
315 if lib_name.as_str().contains('\0') {
316 self.tcx.sess.span_err(span, "library name may not contain NUL characters");
319 if !self.tcx.features().raw_dylib {
321 &self.tcx.sess.parse_sess,
324 "kind=\"raw-dylib\" is unstable",
333 // Process libs passed on the command line
334 fn process_command_line(&mut self) {
335 // First, check for errors
336 let mut renames = FxHashSet::default();
337 for lib in &self.tcx.sess.opts.libs {
338 if let Some(ref new_name) = lib.new_name {
339 let any_duplicate = self
342 .filter_map(|lib| lib.name.as_ref())
343 .any(|n| n.as_str() == lib.name);
344 if new_name.is_empty() {
345 self.tcx.sess.err(&format!(
346 "an empty renaming target was specified for library `{}`",
349 } else if !any_duplicate {
350 self.tcx.sess.err(&format!(
351 "renaming of the library `{}` was specified, \
352 however this crate contains no `#[link(...)]` \
353 attributes referencing this library",
356 } else if !renames.insert(&lib.name) {
357 self.tcx.sess.err(&format!(
358 "multiple renamings were \
359 specified for library `{}`",
366 // Update kind and, optionally, the name of all native libraries
367 // (there may be more than one) with the specified name. If any
368 // library is mentioned more than once, keep the latest mention
369 // of it, so that any possible dependent libraries appear before
370 // it. (This ensures that the linker is able to see symbols from
371 // all possible dependent libraries before linking in the library
373 for passed_lib in &self.tcx.sess.opts.libs {
374 // If we've already added any native libraries with the same
375 // name, they will be pulled out into `existing`, so that we
376 // can move them to the end of the list below.
377 let mut existing = self
379 .drain_filter(|lib| {
380 if let Some(lib_name) = lib.name {
381 if lib_name.as_str() == passed_lib.name {
382 // FIXME: This whole logic is questionable, whether modifiers are
383 // involved or not, library reordering and kind overriding without
384 // explicit `:rename` in particular.
385 if lib.has_modifiers() || passed_lib.has_modifiers() {
386 self.tcx.sess.span_err(
387 self.tcx.def_span(lib.foreign_module.unwrap()),
388 "overriding linking modifiers from command line is not supported"
391 if passed_lib.kind != NativeLibKind::Unspecified {
392 lib.kind = passed_lib.kind;
394 if let Some(new_name) = &passed_lib.new_name {
395 lib.name = Some(Symbol::intern(new_name));
397 lib.verbatim = passed_lib.verbatim;
403 .collect::<Vec<_>>();
404 if existing.is_empty() {
406 let new_name: Option<&str> = passed_lib.new_name.as_deref();
407 let lib = NativeLib {
408 name: Some(Symbol::intern(new_name.unwrap_or(&passed_lib.name))),
409 kind: passed_lib.kind,
411 foreign_module: None,
412 wasm_import_module: None,
413 verbatim: passed_lib.verbatim,
414 dll_imports: Vec::new(),
416 self.register_native_lib(None, lib);
418 // Move all existing libraries with the same name to the
419 // end of the command line.
420 self.libs.append(&mut existing);
425 fn i686_arg_list_size(&self, item: &hir::ForeignItemRef) -> usize {
426 let argument_types: &List<Ty<'_>> = self.tcx.erase_late_bound_regions(
428 .type_of(item.id.def_id)
431 .map_bound(|slice| self.tcx.mk_type_list(slice.iter())),
439 .layout_of(ParamEnvAnd { param_env: ParamEnv::empty(), value: ty })
442 // In both stdcall and fastcall, we always round up the argument size to the
443 // nearest multiple of 4 bytes.
444 (layout.size().bytes_usize() + 3) & !3
449 fn build_dll_import(&self, abi: Abi, item: &hir::ForeignItemRef) -> DllImport {
450 let calling_convention = if self.tcx.sess.target.arch == "x86" {
452 Abi::C { .. } | Abi::Cdecl { .. } => DllCallingConvention::C,
453 Abi::Stdcall { .. } | Abi::System { .. } => {
454 DllCallingConvention::Stdcall(self.i686_arg_list_size(item))
456 Abi::Fastcall { .. } => {
457 DllCallingConvention::Fastcall(self.i686_arg_list_size(item))
459 // Vectorcall is intentionally not supported at this time.
461 self.tcx.sess.span_fatal(
463 r#"ABI not supported by `#[link(kind = "raw-dylib")]` on i686"#,
469 Abi::C { .. } | Abi::Win64 { .. } | Abi::System { .. } => DllCallingConvention::C,
471 self.tcx.sess.span_fatal(
473 r#"ABI not supported by `#[link(kind = "raw-dylib")]` on this architecture"#,
480 name: item.ident.name,
481 ordinal: self.tcx.codegen_fn_attrs(item.id.def_id).link_ordinal,