Skip to main content

rustc_metadata/
native_libs.rs

1use std::ops::ControlFlow;
2use std::path::{Path, PathBuf};
3
4use rustc_abi::ExternAbi;
5use rustc_attr_parsing::eval_config_entry;
6use rustc_data_structures::fx::FxHashSet;
7use rustc_hir::attrs::{NativeLibKind, PeImportNameType};
8use rustc_hir::def::DefKind;
9use rustc_hir::find_attr;
10use rustc_middle::bug;
11use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
12use rustc_middle::query::LocalCrate;
13use rustc_middle::ty::{self, List, Ty, TyCtxt};
14use rustc_session::Session;
15use rustc_session::config::CrateType;
16use rustc_session::cstore::{
17    DllCallingConvention, DllImport, DllImportSymbolType, ForeignModule, NativeLib,
18};
19use rustc_session::search_paths::PathKind;
20use rustc_span::Symbol;
21use rustc_span::def_id::{DefId, LOCAL_CRATE};
22use rustc_target::spec::{Arch, BinaryFormat, CfgAbi, Env, LinkSelfContainedComponents, Os};
23
24use crate::diagnostics;
25
26/// The fallback directories are passed to linker, but not used when rustc does the search,
27/// because in the latter case the set of fallback directories cannot always be determined
28/// consistently at the moment.
29pub struct NativeLibSearchFallback<'a> {
30    pub self_contained_components: LinkSelfContainedComponents,
31    pub apple_sdk_root: Option<&'a Path>,
32}
33
34pub fn walk_native_lib_search_dirs<R>(
35    sess: &Session,
36    fallback: Option<NativeLibSearchFallback<'_>>,
37    mut f: impl FnMut(&Path, bool /*is_framework*/) -> ControlFlow<R>,
38) -> ControlFlow<R> {
39    // Library search paths explicitly supplied by user (`-L` on the command line).
40    for search_path in sess.target_filesearch().cli_search_paths(PathKind::Native) {
41        f(&search_path.dir, false)?;
42    }
43    for search_path in sess.target_filesearch().cli_search_paths(PathKind::Framework) {
44        // Frameworks are looked up strictly in framework-specific paths.
45        if search_path.kind != PathKind::All {
46            f(&search_path.dir, true)?;
47        }
48    }
49
50    let Some(NativeLibSearchFallback { self_contained_components, apple_sdk_root }) = fallback
51    else {
52        return ControlFlow::Continue(());
53    };
54
55    // The toolchain ships some native library components and self-contained linking was enabled.
56    // Add the self-contained library directory to search paths.
57    if self_contained_components.intersects(
58        LinkSelfContainedComponents::LIBC
59            | LinkSelfContainedComponents::UNWIND
60            | LinkSelfContainedComponents::MINGW,
61    ) {
62        f(&sess.target_tlib_path.dir.join("self-contained"), false)?;
63    }
64
65    let has_shared_llvm_apple_darwin =
66        sess.target.is_like_darwin && sess.target_tlib_path.dir.join("libLLVM.dylib").exists();
67
68    // Toolchains for some targets may ship `libunwind.a`, but place it into the main sysroot
69    // library directory instead of the self-contained directories.
70    // Sanitizer libraries have the same issue and are also linked by name on Apple targets.
71    // The targets here should be in sync with `copy_third_party_objects` in bootstrap.
72    // On Apple targets, shared LLVM is linked by name, so when `libLLVM.dylib` is
73    // present in the target libdir, add that directory to the linker search path.
74    // FIXME: implement `-Clink-self-contained=+/-unwind,+/-sanitizers`, move the shipped libunwind
75    // and sanitizers to self-contained directory, and stop adding this search path.
76    // FIXME: On AIX this also has the side-effect of making the list of library search paths
77    // non-empty, which is needed or the linker may decide to record the LIBPATH env, if
78    // defined, as the search path instead of appending the default search paths.
79    if sess.target.cfg_abi == CfgAbi::Fortanix
80        || sess.target.os == Os::Linux
81        || sess.target.os == Os::Fuchsia
82        || sess.target.is_like_aix
83        || sess.target.is_like_darwin
84            && (!sess.sanitizers().is_empty() || has_shared_llvm_apple_darwin)
85        || sess.target.os == Os::Windows
86            && sess.target.env == Env::Gnu
87            && sess.target.cfg_abi == CfgAbi::Llvm
88    {
89        f(&sess.target_tlib_path.dir, false)?;
90    }
91
92    // Mac Catalyst uses the macOS SDK, but to link to iOS-specific frameworks
93    // we must have the support library stubs in the library search path (#121430).
94    if let Some(sdk_root) = apple_sdk_root
95        && sess.target.env == Env::MacAbi
96    {
97        f(&sdk_root.join("System/iOSSupport/usr/lib"), false)?;
98        f(&sdk_root.join("System/iOSSupport/System/Library/Frameworks"), true)?;
99    }
100
101    ControlFlow::Continue(())
102}
103
104pub fn try_find_native_static_library(
105    sess: &Session,
106    name: &str,
107    verbatim: bool,
108) -> Option<PathBuf> {
109    let default = sess.staticlib_components(verbatim);
110    let formats = if verbatim {
111        ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [default]))vec![default]
112    } else {
113        // On Windows, static libraries sometimes show up as libfoo.a and other
114        // times show up as foo.lib
115        let unix = ("lib", ".a");
116        if default == unix { ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [default]))vec![default] } else { ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [default, unix]))vec![default, unix] }
117    };
118
119    walk_native_lib_search_dirs(sess, None, |dir, is_framework| {
120        if !is_framework {
121            for (prefix, suffix) in &formats {
122                let test = dir.join(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}{1}{2}", prefix, name, suffix))
    })format!("{prefix}{name}{suffix}"));
123                if test.exists() {
124                    return ControlFlow::Break(test);
125                }
126            }
127        }
128        ControlFlow::Continue(())
129    })
130    .break_value()
131}
132
133pub fn try_find_native_dynamic_library(
134    sess: &Session,
135    name: &str,
136    verbatim: bool,
137) -> Option<PathBuf> {
138    let default = sess.staticlib_components(verbatim);
139    let formats = if verbatim {
140        ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [default]))vec![default]
141    } else {
142        // While the official naming convention for MSVC import libraries
143        // is foo.lib, Meson follows the libfoo.dll.a convention to
144        // disambiguate .a for static libraries
145        let meson = ("lib", ".dll.a");
146        // and MinGW uses .a altogether
147        let mingw = ("lib", ".a");
148        ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [default, meson, mingw]))vec![default, meson, mingw]
149    };
150
151    walk_native_lib_search_dirs(sess, None, |dir, is_framework| {
152        if !is_framework {
153            for (prefix, suffix) in &formats {
154                let test = dir.join(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}{1}{2}", prefix, name, suffix))
    })format!("{prefix}{name}{suffix}"));
155                if test.exists() {
156                    return ControlFlow::Break(test);
157                }
158            }
159        }
160        ControlFlow::Continue(())
161    })
162    .break_value()
163}
164
165pub fn find_native_static_library(name: &str, verbatim: bool, sess: &Session) -> PathBuf {
166    try_find_native_static_library(sess, name, verbatim).unwrap_or_else(|| {
167        sess.dcx().emit_fatal(diagnostics::MissingNativeLibrary::new(name, verbatim))
168    })
169}
170
171fn find_bundled_library(
172    name: Symbol,
173    verbatim: Option<bool>,
174    kind: NativeLibKind,
175    has_cfg: bool,
176    tcx: TyCtxt<'_>,
177) -> Option<Symbol> {
178    let sess = tcx.sess;
179    if let NativeLibKind::Static { bundle: Some(true) | None, whole_archive, .. } = kind
180        && tcx.crate_types().iter().any(|t| #[allow(non_exhaustive_omitted_patterns)] match t {
    &CrateType::Rlib | CrateType::StaticLib => true,
    _ => false,
}matches!(t, &CrateType::Rlib | CrateType::StaticLib))
181        && (sess.opts.unstable_opts.packed_bundled_libs || has_cfg || whole_archive == Some(true))
182    {
183        let verbatim = verbatim.unwrap_or(false);
184        return find_native_static_library(name.as_str(), verbatim, sess)
185            .file_name()
186            .and_then(|s| s.to_str())
187            .map(Symbol::intern);
188    }
189    None
190}
191
192pub(crate) fn collect(tcx: TyCtxt<'_>, LocalCrate: LocalCrate) -> Vec<NativeLib> {
193    let mut collector = Collector { tcx, libs: Vec::new() };
194    if tcx.sess.opts.unstable_opts.link_directives {
195        for module in tcx.foreign_modules(LOCAL_CRATE).values() {
196            collector.process_module(module);
197        }
198    }
199    collector.process_command_line();
200    collector.libs
201}
202
203pub(crate) fn relevant_lib(sess: &Session, lib: &NativeLib) -> bool {
204    match lib.cfg {
205        Some(ref cfg) => eval_config_entry(sess, cfg).as_bool(),
206        None => true,
207    }
208}
209
210struct Collector<'tcx> {
211    tcx: TyCtxt<'tcx>,
212    libs: Vec<NativeLib>,
213}
214
215impl<'tcx> Collector<'tcx> {
216    fn process_module(&mut self, module: &ForeignModule) {
217        let ForeignModule { def_id, abi, ref foreign_items } = *module;
218        let def_id = def_id.expect_local();
219
220        let sess = self.tcx.sess;
221
222        if #[allow(non_exhaustive_omitted_patterns)] match abi {
    ExternAbi::Rust => true,
    _ => false,
}matches!(abi, ExternAbi::Rust) {
223            return;
224        }
225
226        for attr in
227            {
    {
        'done:
            {
            for i in
                ::rustc_hir::attrs::HasAttrs::get_attrs(def_id, &self.tcx) {
                #[allow(unused_imports)]
                use rustc_hir::attrs::AttributeKind::*;
                let i: &rustc_hir::Attribute = i;
                match i {
                    rustc_hir::Attribute::Parsed(Link(links, _)) => {
                        break 'done Some(links);
                    }
                    rustc_hir::Attribute::Unparsed(..) =>
                        {}
                        #[deny(unreachable_patterns)]
                        _ => {}
                }
            }
            None
        }
    }
}find_attr!(self.tcx, def_id, Link(links, _) => links).iter().map(|v| v.iter()).flatten()
228        {
229            let dll_imports = match attr.kind {
230                NativeLibKind::RawDylib { .. } => foreign_items
231                    .iter()
232                    .filter_map(|&child_item| {
233                        self.build_dll_import(
234                            abi,
235                            attr.import_name_type.map(|(import_name_type, _)| import_name_type),
236                            child_item,
237                        )
238                    })
239                    .collect(),
240                _ => {
241                    for &child_item in foreign_items {
242                        if let Some(span) =
243                            {
    {
        'done:
            {
            for i in
                ::rustc_hir::attrs::HasAttrs::get_attrs(child_item, &self.tcx)
                {
                #[allow(unused_imports)]
                use rustc_hir::attrs::AttributeKind::*;
                let i: &rustc_hir::Attribute = i;
                match i {
                    rustc_hir::Attribute::Parsed(LinkOrdinal { span, .. }) => {
                        break 'done Some(*span);
                    }
                    rustc_hir::Attribute::Unparsed(..) =>
                        {}
                        #[deny(unreachable_patterns)]
                        _ => {}
                }
            }
            None
        }
    }
}find_attr!(self.tcx, child_item, LinkOrdinal {span, ..} => *span)
244                        {
245                            sess.dcx().emit_err(diagnostics::LinkOrdinalRawDylib { span });
246                        }
247                    }
248
249                    Vec::new()
250                }
251            };
252
253            let filename = find_bundled_library(
254                attr.name,
255                attr.verbatim,
256                attr.kind,
257                attr.cfg.is_some(),
258                self.tcx,
259            );
260            self.libs.push(NativeLib {
261                name: attr.name,
262                filename,
263                kind: attr.kind,
264                cfg: attr.cfg.clone(),
265                foreign_module: Some(def_id.to_def_id()),
266                verbatim: attr.verbatim,
267                dll_imports,
268            });
269        }
270    }
271
272    // Process libs passed on the command line
273    fn process_command_line(&mut self) {
274        // First, check for errors
275        let mut renames = FxHashSet::default();
276        for lib in &self.tcx.sess.opts.libs {
277            if let NativeLibKind::Framework { .. } = lib.kind
278                && !self.tcx.sess.target.is_like_darwin
279            {
280                // Cannot check this when parsing options because the target is not yet available.
281                self.tcx.dcx().emit_err(diagnostics::LibFrameworkApple);
282            }
283            if let Some(ref new_name) = lib.new_name {
284                let any_duplicate = self.libs.iter().any(|n| n.name.as_str() == lib.name);
285                if new_name.is_empty() {
286                    self.tcx
287                        .dcx()
288                        .emit_err(diagnostics::EmptyRenamingTarget { lib_name: &lib.name });
289                } else if !any_duplicate {
290                    self.tcx.dcx().emit_err(diagnostics::RenamingNoLink { lib_name: &lib.name });
291                } else if !renames.insert(&lib.name) {
292                    self.tcx.dcx().emit_err(diagnostics::MultipleRenamings { lib_name: &lib.name });
293                }
294            }
295        }
296
297        // Update kind and, optionally, the name of all native libraries
298        // (there may be more than one) with the specified name. If any
299        // library is mentioned more than once, keep the latest mention
300        // of it, so that any possible dependent libraries appear before
301        // it. (This ensures that the linker is able to see symbols from
302        // all possible dependent libraries before linking in the library
303        // in question.)
304        for passed_lib in &self.tcx.sess.opts.libs {
305            // If we've already added any native libraries with the same
306            // name, they will be pulled out into `existing`, so that we
307            // can move them to the end of the list below.
308            let mut existing = self
309                .libs
310                .extract_if(.., |lib| {
311                    if lib.name.as_str() == passed_lib.name {
312                        // FIXME: This whole logic is questionable, whether modifiers are
313                        // involved or not, library reordering and kind overriding without
314                        // explicit `:rename` in particular.
315                        if lib.has_modifiers() || passed_lib.has_modifiers() {
316                            match lib.foreign_module {
317                                Some(def_id) => {
318                                    self.tcx.dcx().emit_err(diagnostics::NoLinkModOverride {
319                                        span: Some(self.tcx.def_span(def_id)),
320                                    })
321                                }
322                                None => self
323                                    .tcx
324                                    .dcx()
325                                    .emit_err(diagnostics::NoLinkModOverride { span: None }),
326                            };
327                        }
328                        if passed_lib.kind != NativeLibKind::Unspecified {
329                            lib.kind = passed_lib.kind;
330                        }
331                        if let Some(new_name) = &passed_lib.new_name {
332                            lib.name = Symbol::intern(new_name);
333                        }
334                        lib.verbatim = passed_lib.verbatim;
335                        return true;
336                    }
337                    false
338                })
339                .collect::<Vec<_>>();
340            if existing.is_empty() {
341                // Add if not found
342                let new_name: Option<&str> = passed_lib.new_name.as_deref();
343                let name = Symbol::intern(new_name.unwrap_or(&passed_lib.name));
344                let filename = find_bundled_library(
345                    name,
346                    passed_lib.verbatim,
347                    passed_lib.kind,
348                    false,
349                    self.tcx,
350                );
351                self.libs.push(NativeLib {
352                    name,
353                    filename,
354                    kind: passed_lib.kind,
355                    cfg: None,
356                    foreign_module: None,
357                    verbatim: passed_lib.verbatim,
358                    dll_imports: Vec::new(),
359                });
360            } else {
361                // Move all existing libraries with the same name to the
362                // end of the command line.
363                self.libs.append(&mut existing);
364            }
365        }
366    }
367
368    fn i686_arg_list_size(&self, item: DefId) -> usize {
369        let argument_types: &List<Ty<'_>> = self.tcx.instantiate_bound_regions_with_erased(
370            self.tcx
371                .type_of(item)
372                .instantiate_identity()
373                .skip_norm_wip()
374                .fn_sig(self.tcx)
375                .inputs()
376                .map_bound(|slice| self.tcx.mk_type_list(slice)),
377        );
378
379        argument_types
380            .iter()
381            .map(|ty| {
382                let layout = self
383                    .tcx
384                    .layout_of(ty::TypingEnv::fully_monomorphized().as_query_input(ty))
385                    .expect("layout")
386                    .layout;
387                // In both stdcall and fastcall, we always round up the argument size to the
388                // nearest multiple of 4 bytes.
389                (layout.size().bytes_usize() + 3) & !3
390            })
391            .sum()
392    }
393
394    fn build_dll_import(
395        &self,
396        abi: ExternAbi,
397        import_name_type: Option<PeImportNameType>,
398        item: DefId,
399    ) -> Option<DllImport> {
400        let span = self.tcx.def_span(item);
401
402        // This `extern` block should have been checked for general ABI support before, but let's
403        // double-check that.
404        if !self.tcx.sess.target.is_abi_supported(abi) {
    ::core::panicking::panic("assertion failed: self.tcx.sess.target.is_abi_supported(abi)")
};assert!(self.tcx.sess.target.is_abi_supported(abi));
405
406        // This logic is similar to `AbiMap::canonize_abi` (in rustc_target/src/spec/abi_map.rs) but
407        // we need more detail than those adjustments, and we can't support all ABIs that are
408        // generally supported.
409        let calling_convention = if self.tcx.sess.target.arch == Arch::X86 {
410            match abi {
411                ExternAbi::C { .. } | ExternAbi::Cdecl { .. } => DllCallingConvention::C,
412                ExternAbi::Stdcall { .. } => {
413                    DllCallingConvention::Stdcall(self.i686_arg_list_size(item))
414                }
415                // On Windows, `extern "system"` behaves like msvc's `__stdcall`.
416                // `__stdcall` only applies on x86 and on non-variadic functions:
417                // https://learn.microsoft.com/en-us/cpp/cpp/stdcall?view=msvc-170
418                ExternAbi::System { .. } => {
419                    let c_variadic = self
420                        .tcx
421                        .type_of(item)
422                        .instantiate_identity()
423                        .skip_norm_wip()
424                        .fn_sig(self.tcx)
425                        .c_variadic();
426
427                    if c_variadic {
428                        DllCallingConvention::C
429                    } else {
430                        DllCallingConvention::Stdcall(self.i686_arg_list_size(item))
431                    }
432                }
433                ExternAbi::Fastcall { .. } => {
434                    DllCallingConvention::Fastcall(self.i686_arg_list_size(item))
435                }
436                ExternAbi::Vectorcall { .. } => {
437                    DllCallingConvention::Vectorcall(self.i686_arg_list_size(item))
438                }
439                _ => {
440                    self.tcx.dcx().emit_fatal(diagnostics::RawDylibUnsupportedAbi { span });
441                }
442            }
443        } else {
444            match abi {
445                ExternAbi::C { .. } | ExternAbi::Win64 { .. } | ExternAbi::System { .. } => {
446                    DllCallingConvention::C
447                }
448                _ => {
449                    self.tcx.dcx().emit_fatal(diagnostics::RawDylibUnsupportedAbi { span });
450                }
451            }
452        };
453
454        let codegen_fn_attrs = self.tcx.codegen_fn_attrs(item);
455        let import_name_type = codegen_fn_attrs
456            .link_ordinal
457            .map_or(import_name_type, |ord| Some(PeImportNameType::Ordinal(ord)));
458
459        let name = codegen_fn_attrs.symbol_name.unwrap_or_else(|| self.tcx.item_name(item));
460
461        if self.tcx.sess.target.binary_format == BinaryFormat::Elf {
462            let name = name.as_str();
463            if name.contains('\0') {
464                self.tcx.dcx().emit_err(diagnostics::RawDylibMalformed { span });
465            } else if let Some((left, right)) = name.split_once('@')
466                && (left.is_empty() || right.is_empty() || right.contains('@'))
467            {
468                self.tcx.dcx().emit_err(diagnostics::RawDylibMalformed { span });
469            }
470        }
471
472        let def_kind = self.tcx.def_kind(item);
473        let symbol_type = if def_kind.is_fn_like() {
474            DllImportSymbolType::Function
475        } else if #[allow(non_exhaustive_omitted_patterns)] match def_kind {
    DefKind::Static { .. } => true,
    _ => false,
}matches!(def_kind, DefKind::Static { .. }) {
476            if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::THREAD_LOCAL) {
477                DllImportSymbolType::ThreadLocal
478            } else {
479                DllImportSymbolType::Static
480            }
481        } else if def_kind == DefKind::ForeignTy {
482            return None;
483        } else {
484            ::rustc_middle::util::bug::bug_fmt(format_args!("Unexpected type for raw-dylib: {0}",
        def_kind.descr(item)));bug!("Unexpected type for raw-dylib: {}", def_kind.descr(item));
485        };
486
487        let size = match symbol_type {
488            // We cannot determine the size of a function at compile time, but it shouldn't matter anyway.
489            DllImportSymbolType::Function => rustc_abi::Size::ZERO,
490            DllImportSymbolType::Static | DllImportSymbolType::ThreadLocal => {
491                let ty = self.tcx.type_of(item).instantiate_identity().skip_norm_wip();
492                self.tcx
493                    .layout_of(ty::TypingEnv::fully_monomorphized().as_query_input(ty))
494                    .ok()
495                    .map(|layout| layout.size)
496                    .unwrap_or_else(|| ::rustc_middle::util::bug::bug_fmt(format_args!("Non-function symbols must have a size"))bug!("Non-function symbols must have a size"))
497            }
498        };
499
500        Some(DllImport { name, import_name_type, calling_convention, span, symbol_type, size })
501    }
502}