1 // Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
11 use std
::collections
::HashMap
;
12 use std
::ffi
::{OsStr, OsString}
;
13 use std
::fs
::{self, File}
;
14 use std
::io
::prelude
::*;
15 use std
::io
::{self, BufWriter}
;
16 use std
::path
::{Path, PathBuf}
;
19 use back
::command
::Command
;
20 use back
::symbol_export
;
21 use rustc
::hir
::def_id
::{LOCAL_CRATE, CrateNum}
;
22 use rustc
::middle
::dependency_format
::Linkage
;
23 use rustc
::session
::Session
;
24 use rustc
::session
::config
::{self, CrateType, OptLevel, DebugInfoLevel}
;
25 use rustc
::ty
::TyCtxt
;
26 use rustc_back
::LinkerFlavor
;
27 use serialize
::{json, Encoder}
;
29 /// For all the linkers we support, and information they might
30 /// need out of the shared crate context before we get rid of it.
31 pub struct LinkerInfo
{
32 exports
: HashMap
<CrateType
, Vec
<String
>>,
36 pub fn new(tcx
: TyCtxt
) -> LinkerInfo
{
38 exports
: tcx
.sess
.crate_types
.borrow().iter().map(|&c
| {
39 (c
, exported_symbols(tcx
, c
))
44 pub fn to_linker
<'a
>(&'a
self,
46 sess
: &'a Session
) -> Box
<Linker
+'a
> {
47 match sess
.linker_flavor() {
48 LinkerFlavor
::Msvc
=> {
62 LinkerFlavor
::Gcc
=> {
80 LinkerFlavor
::Binaryen
=> {
81 panic
!("can't instantiate binaryen linker")
87 /// Linker abstraction used by back::link to build up the command to invoke a
90 /// This trait is the total list of requirements needed by `back::link` and
91 /// represents the meaning of each option being passed down. This trait is then
92 /// used to dispatch on whether a GNU-like linker (generally `ld.exe`) or an
93 /// MSVC linker (e.g. `link.exe`) is being used.
95 fn link_dylib(&mut self, lib
: &str);
96 fn link_rust_dylib(&mut self, lib
: &str, path
: &Path
);
97 fn link_framework(&mut self, framework
: &str);
98 fn link_staticlib(&mut self, lib
: &str);
99 fn link_rlib(&mut self, lib
: &Path
);
100 fn link_whole_rlib(&mut self, lib
: &Path
);
101 fn link_whole_staticlib(&mut self, lib
: &str, search_path
: &[PathBuf
]);
102 fn include_path(&mut self, path
: &Path
);
103 fn framework_path(&mut self, path
: &Path
);
104 fn output_filename(&mut self, path
: &Path
);
105 fn add_object(&mut self, path
: &Path
);
106 fn gc_sections(&mut self, keep_metadata
: bool
);
107 fn position_independent_executable(&mut self);
108 fn partial_relro(&mut self);
109 fn full_relro(&mut self);
110 fn optimize(&mut self);
111 fn debuginfo(&mut self);
112 fn no_default_libraries(&mut self);
113 fn build_dylib(&mut self, out_filename
: &Path
);
114 fn build_static_executable(&mut self);
115 fn args(&mut self, args
: &[String
]);
116 fn export_symbols(&mut self, tmpdir
: &Path
, crate_type
: CrateType
);
117 fn subsystem(&mut self, subsystem
: &str);
118 // Should have been finalize(self), but we don't support self-by-value on trait objects (yet?).
119 fn finalize(&mut self) -> Command
;
122 pub struct GccLinker
<'a
> {
125 info
: &'a LinkerInfo
,
126 hinted_static
: bool
, // Keeps track of the current hinting mode.
131 impl<'a
> GccLinker
<'a
> {
132 /// Argument that must be passed *directly* to the linker
134 /// These arguments need to be prepended with '-Wl,' when a gcc-style linker is used
135 fn linker_arg
<S
>(&mut self, arg
: S
) -> &mut Self
136 where S
: AsRef
<OsStr
>
139 let mut os
= OsString
::from("-Wl,");
140 os
.push(arg
.as_ref());
148 fn takes_hints(&self) -> bool
{
149 !self.sess
.target
.target
.options
.is_like_osx
152 // Some platforms take hints about whether a library is static or dynamic.
153 // For those that support this, we ensure we pass the option if the library
154 // was flagged "static" (most defaults are dynamic) to ensure that if
155 // libfoo.a and libfoo.so both exist that the right one is chosen.
156 fn hint_static(&mut self) {
157 if !self.takes_hints() { return }
158 if !self.hinted_static
{
159 self.linker_arg("-Bstatic");
160 self.hinted_static
= true;
164 fn hint_dynamic(&mut self) {
165 if !self.takes_hints() { return }
166 if self.hinted_static
{
167 self.linker_arg("-Bdynamic");
168 self.hinted_static
= false;
173 impl<'a
> Linker
for GccLinker
<'a
> {
174 fn link_dylib(&mut self, lib
: &str) { self.hint_dynamic(); self.cmd.arg("-l").arg(lib); }
175 fn link_staticlib(&mut self, lib
: &str) { self.hint_static(); self.cmd.arg("-l").arg(lib); }
176 fn link_rlib(&mut self, lib
: &Path
) { self.hint_static(); self.cmd.arg(lib); }
177 fn include_path(&mut self, path
: &Path
) { self.cmd.arg("-L").arg(path); }
178 fn framework_path(&mut self, path
: &Path
) { self.cmd.arg("-F").arg(path); }
179 fn output_filename(&mut self, path
: &Path
) { self.cmd.arg("-o").arg(path); }
180 fn add_object(&mut self, path
: &Path
) { self.cmd.arg(path); }
181 fn position_independent_executable(&mut self) { self.cmd.arg("-pie"); }
182 fn partial_relro(&mut self) { self.linker_arg("-z,relro"); }
183 fn full_relro(&mut self) { self.linker_arg("-z,relro,-z,now"); }
184 fn build_static_executable(&mut self) { self.cmd.arg("-static"); }
185 fn args(&mut self, args
: &[String
]) { self.cmd.args(args); }
187 fn link_rust_dylib(&mut self, lib
: &str, _path
: &Path
) {
189 self.cmd
.arg("-l").arg(lib
);
192 fn link_framework(&mut self, framework
: &str) {
194 self.cmd
.arg("-framework").arg(framework
);
197 // Here we explicitly ask that the entire archive is included into the
198 // result artifact. For more details see #15460, but the gist is that
199 // the linker will strip away any unused objects in the archive if we
200 // don't otherwise explicitly reference them. This can occur for
201 // libraries which are just providing bindings, libraries with generic
203 fn link_whole_staticlib(&mut self, lib
: &str, search_path
: &[PathBuf
]) {
205 let target
= &self.sess
.target
.target
;
206 if !target
.options
.is_like_osx
{
207 self.linker_arg("--whole-archive").cmd
.arg("-l").arg(lib
);
208 self.linker_arg("--no-whole-archive");
210 // -force_load is the macOS equivalent of --whole-archive, but it
211 // involves passing the full path to the library to link.
212 let mut v
= OsString
::from("-force_load,");
213 v
.push(&archive
::find_library(lib
, search_path
, &self.sess
));
218 fn link_whole_rlib(&mut self, lib
: &Path
) {
220 if self.sess
.target
.target
.options
.is_like_osx
{
221 let mut v
= OsString
::from("-force_load,");
225 self.linker_arg("--whole-archive").cmd
.arg(lib
);
226 self.linker_arg("--no-whole-archive");
230 fn gc_sections(&mut self, keep_metadata
: bool
) {
231 // The dead_strip option to the linker specifies that functions and data
232 // unreachable by the entry point will be removed. This is quite useful
233 // with Rust's compilation model of compiling libraries at a time into
234 // one object file. For example, this brings hello world from 1.7MB to
237 // Note that this is done for both executables and dynamic libraries. We
238 // won't get much benefit from dylibs because LLVM will have already
239 // stripped away as much as it could. This has not been seen to impact
240 // link times negatively.
242 // -dead_strip can't be part of the pre_link_args because it's also used
243 // for partial linking when using multiple codegen units (-r). So we
245 if self.sess
.target
.target
.options
.is_like_osx
{
246 self.linker_arg("-dead_strip");
247 } else if self.sess
.target
.target
.options
.is_like_solaris
{
248 self.linker_arg("-z");
249 self.linker_arg("ignore");
251 // If we're building a dylib, we don't use --gc-sections because LLVM
252 // has already done the best it can do, and we also don't want to
253 // eliminate the metadata. If we're building an executable, however,
254 // --gc-sections drops the size of hello world from 1.8MB to 597K, a 67%
256 } else if !keep_metadata
{
257 self.linker_arg("--gc-sections");
261 fn optimize(&mut self) {
262 if !self.sess
.target
.target
.options
.linker_is_gnu { return }
264 // GNU-style linkers support optimization with -O. GNU ld doesn't
265 // need a numeric argument, but other linkers do.
266 if self.sess
.opts
.optimize
== config
::OptLevel
::Default
||
267 self.sess
.opts
.optimize
== config
::OptLevel
::Aggressive
{
268 self.linker_arg("-O1");
272 fn debuginfo(&mut self) {
273 // Don't do anything special here for GNU-style linkers.
276 fn no_default_libraries(&mut self) {
278 self.cmd
.arg("-nodefaultlibs");
282 fn build_dylib(&mut self, out_filename
: &Path
) {
283 // On mac we need to tell the linker to let this library be rpathed
284 if self.sess
.target
.target
.options
.is_like_osx
{
285 self.cmd
.arg("-dynamiclib");
286 self.linker_arg("-dylib");
288 // Note that the `osx_rpath_install_name` option here is a hack
289 // purely to support rustbuild right now, we should get a more
290 // principled solution at some point to force the compiler to pass
291 // the right `-Wl,-install_name` with an `@rpath` in it.
292 if self.sess
.opts
.cg
.rpath
||
293 self.sess
.opts
.debugging_opts
.osx_rpath_install_name
{
294 let mut v
= OsString
::from("-install_name,@rpath/");
295 v
.push(out_filename
.file_name().unwrap());
299 self.cmd
.arg("-shared");
303 fn export_symbols(&mut self, tmpdir
: &Path
, crate_type
: CrateType
) {
304 // If we're compiling a dylib, then we let symbol visibility in object
305 // files to take care of whether they're exported or not.
307 // If we're compiling a cdylib, however, we manually create a list of
308 // exported symbols to ensure we don't expose any more. The object files
309 // have far more public symbols than we actually want to export, so we
310 // hide them all here.
311 if crate_type
== CrateType
::CrateTypeDylib
||
312 crate_type
== CrateType
::CrateTypeProcMacro
{
316 let mut arg
= OsString
::new();
317 let path
= tmpdir
.join("list");
319 debug
!("EXPORTED SYMBOLS:");
321 if self.sess
.target
.target
.options
.is_like_osx
{
322 // Write a plain, newline-separated list of symbols
323 let res
= (|| -> io
::Result
<()> {
324 let mut f
= BufWriter
::new(File
::create(&path
)?
);
325 for sym
in self.info
.exports
[&crate_type
].iter() {
327 writeln
!(f
, "_{}", sym
)?
;
331 if let Err(e
) = res
{
332 self.sess
.fatal(&format
!("failed to write lib.def file: {}", e
));
335 // Write an LD version script
336 let res
= (|| -> io
::Result
<()> {
337 let mut f
= BufWriter
::new(File
::create(&path
)?
);
338 writeln
!(f
, "{{\n global:")?
;
339 for sym
in self.info
.exports
[&crate_type
].iter() {
341 writeln
!(f
, " {};", sym
)?
;
343 writeln
!(f
, "\n local:\n *;\n}};")?
;
346 if let Err(e
) = res
{
347 self.sess
.fatal(&format
!("failed to write version script: {}", e
));
351 if self.sess
.target
.target
.options
.is_like_osx
{
355 arg
.push("-exported_symbols_list,");
356 } else if self.sess
.target
.target
.options
.is_like_solaris
{
365 arg
.push("--version-script=");
372 fn subsystem(&mut self, subsystem
: &str) {
373 self.linker_arg(&format
!("--subsystem,{}", subsystem
));
376 fn finalize(&mut self) -> Command
{
377 self.hint_dynamic(); // Reset to default before returning the composed command line.
378 let mut cmd
= Command
::new("");
379 ::std
::mem
::swap(&mut cmd
, &mut self.cmd
);
384 pub struct MsvcLinker
<'a
> {
390 impl<'a
> Linker
for MsvcLinker
<'a
> {
391 fn link_rlib(&mut self, lib
: &Path
) { self.cmd.arg(lib); }
392 fn add_object(&mut self, path
: &Path
) { self.cmd.arg(path); }
393 fn args(&mut self, args
: &[String
]) { self.cmd.args(args); }
395 fn build_dylib(&mut self, out_filename
: &Path
) {
396 self.cmd
.arg("/DLL");
397 let mut arg
: OsString
= "/IMPLIB:".into();
398 arg
.push(out_filename
.with_extension("dll.lib"));
402 fn build_static_executable(&mut self) {
406 fn gc_sections(&mut self, _keep_metadata
: bool
) {
407 // MSVC's ICF (Identical COMDAT Folding) link optimization is
408 // slow for Rust and thus we disable it by default when not in
409 // optimization build.
410 if self.sess
.opts
.optimize
!= config
::OptLevel
::No
{
411 self.cmd
.arg("/OPT:REF,ICF");
413 // It is necessary to specify NOICF here, because /OPT:REF
414 // implies ICF by default.
415 self.cmd
.arg("/OPT:REF,NOICF");
419 fn link_dylib(&mut self, lib
: &str) {
420 self.cmd
.arg(&format
!("{}.lib", lib
));
423 fn link_rust_dylib(&mut self, lib
: &str, path
: &Path
) {
424 // When producing a dll, the MSVC linker may not actually emit a
425 // `foo.lib` file if the dll doesn't actually export any symbols, so we
426 // check to see if the file is there and just omit linking to it if it's
428 let name
= format
!("{}.dll.lib", lib
);
429 if fs
::metadata(&path
.join(&name
)).is_ok() {
434 fn link_staticlib(&mut self, lib
: &str) {
435 self.cmd
.arg(&format
!("{}.lib", lib
));
438 fn position_independent_executable(&mut self) {
442 fn partial_relro(&mut self) {
446 fn full_relro(&mut self) {
450 fn no_default_libraries(&mut self) {
451 // Currently we don't pass the /NODEFAULTLIB flag to the linker on MSVC
452 // as there's been trouble in the past of linking the C++ standard
453 // library required by LLVM. This likely needs to happen one day, but
454 // in general Windows is also a more controlled environment than
455 // Unix, so it's not necessarily as critical that this be implemented.
457 // Note that there are also some licensing worries about statically
458 // linking some libraries which require a specific agreement, so it may
459 // not ever be possible for us to pass this flag.
462 fn include_path(&mut self, path
: &Path
) {
463 let mut arg
= OsString
::from("/LIBPATH:");
468 fn output_filename(&mut self, path
: &Path
) {
469 let mut arg
= OsString
::from("/OUT:");
474 fn framework_path(&mut self, _path
: &Path
) {
475 bug
!("frameworks are not supported on windows")
477 fn link_framework(&mut self, _framework
: &str) {
478 bug
!("frameworks are not supported on windows")
481 fn link_whole_staticlib(&mut self, lib
: &str, _search_path
: &[PathBuf
]) {
483 self.link_staticlib(lib
);
485 fn link_whole_rlib(&mut self, path
: &Path
) {
487 self.link_rlib(path
);
489 fn optimize(&mut self) {
490 // Needs more investigation of `/OPT` arguments
493 fn debuginfo(&mut self) {
494 // This will cause the Microsoft linker to generate a PDB file
495 // from the CodeView line tables in the object files.
496 self.cmd
.arg("/DEBUG");
498 // This will cause the Microsoft linker to embed .natvis info into the the PDB file
499 let sysroot
= self.sess
.sysroot();
500 let natvis_dir_path
= sysroot
.join("lib\\rustlib\\etc");
501 if let Ok(natvis_dir
) = fs
::read_dir(&natvis_dir_path
) {
502 // LLVM 5.0.0's lld-link frontend doesn't yet recognize, and chokes
503 // on, the /NATVIS:... flags. LLVM 6 (or earlier) should at worst ignore
504 // them, eventually mooting this workaround, per this landed patch:
505 // https://github.com/llvm-mirror/lld/commit/27b9c4285364d8d76bb43839daa100
506 if let Some(ref linker_path
) = self.sess
.opts
.cg
.linker
{
507 if let Some(linker_name
) = Path
::new(&linker_path
).file_stem() {
508 if linker_name
.to_str().unwrap().to_lowercase() == "lld-link" {
509 self.sess
.warn("not embedding natvis: lld-link may not support the flag");
514 for entry
in natvis_dir
{
517 let path
= entry
.path();
518 if path
.extension() == Some("natvis".as_ref()) {
519 let mut arg
= OsString
::from("/NATVIS:");
525 self.sess
.warn(&format
!("error enumerating natvis directory: {}", err
));
532 // Currently the compiler doesn't use `dllexport` (an LLVM attribute) to
533 // export symbols from a dynamic library. When building a dynamic library,
534 // however, we're going to want some symbols exported, so this function
535 // generates a DEF file which lists all the symbols.
537 // The linker will read this `*.def` file and export all the symbols from
538 // the dynamic library. Note that this is not as simple as just exporting
539 // all the symbols in the current crate (as specified by `trans.reachable`)
540 // but rather we also need to possibly export the symbols of upstream
541 // crates. Upstream rlibs may be linked statically to this dynamic library,
542 // in which case they may continue to transitively be used and hence need
543 // their symbols exported.
544 fn export_symbols(&mut self,
546 crate_type
: CrateType
) {
547 let path
= tmpdir
.join("lib.def");
548 let res
= (|| -> io
::Result
<()> {
549 let mut f
= BufWriter
::new(File
::create(&path
)?
);
551 // Start off with the standard module name header and then go
552 // straight to exports.
553 writeln
!(f
, "LIBRARY")?
;
554 writeln
!(f
, "EXPORTS")?
;
555 for symbol
in self.info
.exports
[&crate_type
].iter() {
556 debug
!(" _{}", symbol
);
557 writeln
!(f
, " {}", symbol
)?
;
561 if let Err(e
) = res
{
562 self.sess
.fatal(&format
!("failed to write lib.def file: {}", e
));
564 let mut arg
= OsString
::from("/DEF:");
569 fn subsystem(&mut self, subsystem
: &str) {
570 // Note that previous passes of the compiler validated this subsystem,
571 // so we just blindly pass it to the linker.
572 self.cmd
.arg(&format
!("/SUBSYSTEM:{}", subsystem
));
574 // Windows has two subsystems we're interested in right now, the console
575 // and windows subsystems. These both implicitly have different entry
576 // points (starting symbols). The console entry point starts with
577 // `mainCRTStartup` and the windows entry point starts with
578 // `WinMainCRTStartup`. These entry points, defined in system libraries,
579 // will then later probe for either `main` or `WinMain`, respectively to
580 // start the application.
582 // In Rust we just always generate a `main` function so we want control
583 // to always start there, so we force the entry point on the windows
584 // subsystem to be `mainCRTStartup` to get everything booted up
587 // For more information see RFC #1665
588 if subsystem
== "windows" {
589 self.cmd
.arg("/ENTRY:mainCRTStartup");
593 fn finalize(&mut self) -> Command
{
594 let mut cmd
= Command
::new("");
595 ::std
::mem
::swap(&mut cmd
, &mut self.cmd
);
600 pub struct EmLinker
<'a
> {
606 impl<'a
> Linker
for EmLinker
<'a
> {
607 fn include_path(&mut self, path
: &Path
) {
608 self.cmd
.arg("-L").arg(path
);
611 fn link_staticlib(&mut self, lib
: &str) {
612 self.cmd
.arg("-l").arg(lib
);
615 fn output_filename(&mut self, path
: &Path
) {
616 self.cmd
.arg("-o").arg(path
);
619 fn add_object(&mut self, path
: &Path
) {
623 fn link_dylib(&mut self, lib
: &str) {
624 // Emscripten always links statically
625 self.link_staticlib(lib
);
628 fn link_whole_staticlib(&mut self, lib
: &str, _search_path
: &[PathBuf
]) {
630 self.link_staticlib(lib
);
633 fn link_whole_rlib(&mut self, lib
: &Path
) {
638 fn link_rust_dylib(&mut self, lib
: &str, _path
: &Path
) {
639 self.link_dylib(lib
);
642 fn link_rlib(&mut self, lib
: &Path
) {
643 self.add_object(lib
);
646 fn position_independent_executable(&mut self) {
650 fn partial_relro(&mut self) {
654 fn full_relro(&mut self) {
658 fn args(&mut self, args
: &[String
]) {
662 fn framework_path(&mut self, _path
: &Path
) {
663 bug
!("frameworks are not supported on Emscripten")
666 fn link_framework(&mut self, _framework
: &str) {
667 bug
!("frameworks are not supported on Emscripten")
670 fn gc_sections(&mut self, _keep_metadata
: bool
) {
674 fn optimize(&mut self) {
675 // Emscripten performs own optimizations
676 self.cmd
.arg(match self.sess
.opts
.optimize
{
677 OptLevel
::No
=> "-O0",
678 OptLevel
::Less
=> "-O1",
679 OptLevel
::Default
=> "-O2",
680 OptLevel
::Aggressive
=> "-O3",
681 OptLevel
::Size
=> "-Os",
682 OptLevel
::SizeMin
=> "-Oz"
684 // Unusable until https://github.com/rust-lang/rust/issues/38454 is resolved
685 self.cmd
.args(&["--memory-init-file", "0"]);
688 fn debuginfo(&mut self) {
689 // Preserve names or generate source maps depending on debug info
690 self.cmd
.arg(match self.sess
.opts
.debuginfo
{
691 DebugInfoLevel
::NoDebugInfo
=> "-g0",
692 DebugInfoLevel
::LimitedDebugInfo
=> "-g3",
693 DebugInfoLevel
::FullDebugInfo
=> "-g4"
697 fn no_default_libraries(&mut self) {
698 self.cmd
.args(&["-s", "DEFAULT_LIBRARY_FUNCS_TO_INCLUDE=[]"]);
701 fn build_dylib(&mut self, _out_filename
: &Path
) {
702 bug
!("building dynamic library is unsupported on Emscripten")
705 fn build_static_executable(&mut self) {
709 fn export_symbols(&mut self, _tmpdir
: &Path
, crate_type
: CrateType
) {
710 let symbols
= &self.info
.exports
[&crate_type
];
712 debug
!("EXPORTED SYMBOLS:");
716 let mut arg
= OsString
::from("EXPORTED_FUNCTIONS=");
717 let mut encoded
= String
::new();
720 let mut encoder
= json
::Encoder
::new(&mut encoded
);
721 let res
= encoder
.emit_seq(symbols
.len(), |encoder
| {
722 for (i
, sym
) in symbols
.iter().enumerate() {
723 encoder
.emit_seq_elt(i
, |encoder
| {
724 encoder
.emit_str(&("_".to_string() + sym
))
729 if let Err(e
) = res
{
730 self.sess
.fatal(&format
!("failed to encode exported symbols: {}", e
));
733 debug
!("{}", encoded
);
739 fn subsystem(&mut self, _subsystem
: &str) {
743 fn finalize(&mut self) -> Command
{
744 let mut cmd
= Command
::new("");
745 ::std
::mem
::swap(&mut cmd
, &mut self.cmd
);
750 fn exported_symbols(tcx
: TyCtxt
, crate_type
: CrateType
) -> Vec
<String
> {
751 let mut symbols
= Vec
::new();
753 let export_threshold
= symbol_export
::crates_export_threshold(&[crate_type
]);
754 for &(ref name
, _
, level
) in tcx
.exported_symbols(LOCAL_CRATE
).iter() {
755 if level
.is_below_threshold(export_threshold
) {
756 symbols
.push(name
.clone());
760 let formats
= tcx
.sess
.dependency_formats
.borrow();
761 let deps
= formats
[&crate_type
].iter();
763 for (index
, dep_format
) in deps
.enumerate() {
764 let cnum
= CrateNum
::new(index
+ 1);
765 // For each dependency that we are linking to statically ...
766 if *dep_format
== Linkage
::Static
{
767 // ... we add its symbol list to our export list.
768 for &(ref name
, _
, level
) in tcx
.exported_symbols(cnum
).iter() {
769 if level
.is_below_threshold(export_threshold
) {
770 symbols
.push(name
.clone());