]> git.proxmox.com Git - rustc.git/blob - compiler/rustc_codegen_llvm/src/back/archive.rs
New upstream version 1.54.0+dfsg1
[rustc.git] / compiler / rustc_codegen_llvm / src / back / archive.rs
1 //! A helper class for dealing with static archives
2
3 use std::ffi::{CStr, CString};
4 use std::io;
5 use std::mem;
6 use std::path::{Path, PathBuf};
7 use std::ptr;
8 use std::str;
9
10 use crate::llvm::archive_ro::{ArchiveRO, Child};
11 use crate::llvm::{self, ArchiveKind, LLVMMachineType, LLVMRustCOFFShortExport};
12 use rustc_codegen_ssa::back::archive::{find_library, ArchiveBuilder};
13 use rustc_codegen_ssa::{looks_like_rust_object_file, METADATA_FILENAME};
14 use rustc_data_structures::temp_dir::MaybeTempDir;
15 use rustc_middle::middle::cstore::DllImport;
16 use rustc_session::Session;
17 use rustc_span::symbol::Symbol;
18
19 struct ArchiveConfig<'a> {
20 pub sess: &'a Session,
21 pub dst: PathBuf,
22 pub src: Option<PathBuf>,
23 pub lib_search_paths: Vec<PathBuf>,
24 }
25
26 /// Helper for adding many files to an archive.
27 #[must_use = "must call build() to finish building the archive"]
28 pub struct LlvmArchiveBuilder<'a> {
29 config: ArchiveConfig<'a>,
30 removals: Vec<String>,
31 additions: Vec<Addition>,
32 should_update_symbols: bool,
33 src_archive: Option<Option<ArchiveRO>>,
34 }
35
36 enum Addition {
37 File { path: PathBuf, name_in_archive: String },
38 Archive { path: PathBuf, archive: ArchiveRO, skip: Box<dyn FnMut(&str) -> bool> },
39 }
40
41 impl Addition {
42 fn path(&self) -> &Path {
43 match self {
44 Addition::File { path, .. } | Addition::Archive { path, .. } => path,
45 }
46 }
47 }
48
49 fn is_relevant_child(c: &Child<'_>) -> bool {
50 match c.name() {
51 Some(name) => !name.contains("SYMDEF"),
52 None => false,
53 }
54 }
55
56 fn archive_config<'a>(sess: &'a Session, output: &Path, input: Option<&Path>) -> ArchiveConfig<'a> {
57 use rustc_codegen_ssa::back::link::archive_search_paths;
58 ArchiveConfig {
59 sess,
60 dst: output.to_path_buf(),
61 src: input.map(|p| p.to_path_buf()),
62 lib_search_paths: archive_search_paths(sess),
63 }
64 }
65
66 /// Map machine type strings to values of LLVM's MachineTypes enum.
67 fn llvm_machine_type(cpu: &str) -> LLVMMachineType {
68 match cpu {
69 "x86_64" => LLVMMachineType::AMD64,
70 "x86" => LLVMMachineType::I386,
71 "aarch64" => LLVMMachineType::ARM64,
72 "arm" => LLVMMachineType::ARM,
73 _ => panic!("unsupported cpu type {}", cpu),
74 }
75 }
76
77 impl<'a> ArchiveBuilder<'a> for LlvmArchiveBuilder<'a> {
78 /// Creates a new static archive, ready for modifying the archive specified
79 /// by `config`.
80 fn new(sess: &'a Session, output: &Path, input: Option<&Path>) -> LlvmArchiveBuilder<'a> {
81 let config = archive_config(sess, output, input);
82 LlvmArchiveBuilder {
83 config,
84 removals: Vec::new(),
85 additions: Vec::new(),
86 should_update_symbols: false,
87 src_archive: None,
88 }
89 }
90
91 /// Removes a file from this archive
92 fn remove_file(&mut self, file: &str) {
93 self.removals.push(file.to_string());
94 }
95
96 /// Lists all files in an archive
97 fn src_files(&mut self) -> Vec<String> {
98 if self.src_archive().is_none() {
99 return Vec::new();
100 }
101
102 let archive = self.src_archive.as_ref().unwrap().as_ref().unwrap();
103
104 archive
105 .iter()
106 .filter_map(|child| child.ok())
107 .filter(is_relevant_child)
108 .filter_map(|child| child.name())
109 .filter(|name| !self.removals.iter().any(|x| x == name))
110 .map(|name| name.to_owned())
111 .collect()
112 }
113
114 /// Adds all of the contents of a native library to this archive. This will
115 /// search in the relevant locations for a library named `name`.
116 fn add_native_library(&mut self, name: Symbol, verbatim: bool) {
117 let location =
118 find_library(name, verbatim, &self.config.lib_search_paths, self.config.sess);
119 self.add_archive(&location, |_| false).unwrap_or_else(|e| {
120 self.config.sess.fatal(&format!(
121 "failed to add native library {}: {}",
122 location.to_string_lossy(),
123 e
124 ));
125 });
126 }
127
128 /// Adds all of the contents of the rlib at the specified path to this
129 /// archive.
130 ///
131 /// This ignores adding the bytecode from the rlib, and if LTO is enabled
132 /// then the object file also isn't added.
133 fn add_rlib(
134 &mut self,
135 rlib: &Path,
136 name: &str,
137 lto: bool,
138 skip_objects: bool,
139 ) -> io::Result<()> {
140 // Ignoring obj file starting with the crate name
141 // as simple comparison is not enough - there
142 // might be also an extra name suffix
143 let obj_start = name.to_owned();
144
145 self.add_archive(rlib, move |fname: &str| {
146 // Ignore metadata files, no matter the name.
147 if fname == METADATA_FILENAME {
148 return true;
149 }
150
151 // Don't include Rust objects if LTO is enabled
152 if lto && looks_like_rust_object_file(fname) {
153 return true;
154 }
155
156 // Otherwise if this is *not* a rust object and we're skipping
157 // objects then skip this file
158 if skip_objects && (!fname.starts_with(&obj_start) || !fname.ends_with(".o")) {
159 return true;
160 }
161
162 // ok, don't skip this
163 false
164 })
165 }
166
167 /// Adds an arbitrary file to this archive
168 fn add_file(&mut self, file: &Path) {
169 let name = file.file_name().unwrap().to_str().unwrap();
170 self.additions
171 .push(Addition::File { path: file.to_path_buf(), name_in_archive: name.to_owned() });
172 }
173
174 /// Indicate that the next call to `build` should update all symbols in
175 /// the archive (equivalent to running 'ar s' over it).
176 fn update_symbols(&mut self) {
177 self.should_update_symbols = true;
178 }
179
180 /// Combine the provided files, rlibs, and native libraries into a single
181 /// `Archive`.
182 fn build(mut self) {
183 let kind = self.llvm_archive_kind().unwrap_or_else(|kind| {
184 self.config.sess.fatal(&format!("Don't know how to build archive of type: {}", kind))
185 });
186
187 if let Err(e) = self.build_with_llvm(kind) {
188 self.config.sess.fatal(&format!("failed to build archive: {}", e));
189 }
190 }
191
192 fn inject_dll_import_lib(
193 &mut self,
194 lib_name: &str,
195 dll_imports: &[DllImport],
196 tmpdir: &MaybeTempDir,
197 ) {
198 let output_path = {
199 let mut output_path: PathBuf = tmpdir.as_ref().to_path_buf();
200 output_path.push(format!("{}_imports", lib_name));
201 output_path.with_extension("lib")
202 };
203
204 // we've checked for \0 characters in the library name already
205 let dll_name_z = CString::new(lib_name).unwrap();
206 // All import names are Rust identifiers and therefore cannot contain \0 characters.
207 // FIXME: when support for #[link_name] implemented, ensure that import.name values don't
208 // have any \0 characters
209 let import_name_vector: Vec<CString> = dll_imports
210 .iter()
211 .map(if self.config.sess.target.arch == "x86" {
212 |import: &DllImport| CString::new(format!("_{}", import.name.to_string())).unwrap()
213 } else {
214 |import: &DllImport| CString::new(import.name.to_string()).unwrap()
215 })
216 .collect();
217
218 let output_path_z = rustc_fs_util::path_to_c_string(&output_path);
219
220 tracing::trace!("invoking LLVMRustWriteImportLibrary");
221 tracing::trace!(" dll_name {:#?}", dll_name_z);
222 tracing::trace!(" output_path {}", output_path.display());
223 tracing::trace!(
224 " import names: {}",
225 dll_imports.iter().map(|import| import.name.to_string()).collect::<Vec<_>>().join(", "),
226 );
227
228 let ffi_exports: Vec<LLVMRustCOFFShortExport> = import_name_vector
229 .iter()
230 .map(|name_z| LLVMRustCOFFShortExport::from_name(name_z.as_ptr()))
231 .collect();
232 let result = unsafe {
233 crate::llvm::LLVMRustWriteImportLibrary(
234 dll_name_z.as_ptr(),
235 output_path_z.as_ptr(),
236 ffi_exports.as_ptr(),
237 ffi_exports.len(),
238 llvm_machine_type(&self.config.sess.target.arch) as u16,
239 !self.config.sess.target.is_like_msvc,
240 )
241 };
242
243 if result == crate::llvm::LLVMRustResult::Failure {
244 self.config.sess.fatal(&format!(
245 "Error creating import library for {}: {}",
246 lib_name,
247 llvm::last_error().unwrap_or("unknown LLVM error".to_string())
248 ));
249 }
250
251 self.add_archive(&output_path, |_| false).unwrap_or_else(|e| {
252 self.config.sess.fatal(&format!(
253 "failed to add native library {}: {}",
254 output_path.display(),
255 e
256 ));
257 });
258 }
259 }
260
261 impl<'a> LlvmArchiveBuilder<'a> {
262 fn src_archive(&mut self) -> Option<&ArchiveRO> {
263 if let Some(ref a) = self.src_archive {
264 return a.as_ref();
265 }
266 let src = self.config.src.as_ref()?;
267 self.src_archive = Some(ArchiveRO::open(src).ok());
268 self.src_archive.as_ref().unwrap().as_ref()
269 }
270
271 fn add_archive<F>(&mut self, archive: &Path, skip: F) -> io::Result<()>
272 where
273 F: FnMut(&str) -> bool + 'static,
274 {
275 let archive_ro = match ArchiveRO::open(archive) {
276 Ok(ar) => ar,
277 Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e)),
278 };
279 if self.additions.iter().any(|ar| ar.path() == archive) {
280 return Ok(());
281 }
282 self.additions.push(Addition::Archive {
283 path: archive.to_path_buf(),
284 archive: archive_ro,
285 skip: Box::new(skip),
286 });
287 Ok(())
288 }
289
290 fn llvm_archive_kind(&self) -> Result<ArchiveKind, &str> {
291 let kind = &*self.config.sess.target.archive_format;
292 kind.parse().map_err(|_| kind)
293 }
294
295 fn build_with_llvm(&mut self, kind: ArchiveKind) -> io::Result<()> {
296 let removals = mem::take(&mut self.removals);
297 let mut additions = mem::take(&mut self.additions);
298 let mut strings = Vec::new();
299 let mut members = Vec::new();
300
301 let dst = CString::new(self.config.dst.to_str().unwrap())?;
302 let should_update_symbols = self.should_update_symbols;
303
304 unsafe {
305 if let Some(archive) = self.src_archive() {
306 for child in archive.iter() {
307 let child = child.map_err(string_to_io_error)?;
308 let child_name = match child.name() {
309 Some(s) => s,
310 None => continue,
311 };
312 if removals.iter().any(|r| r == child_name) {
313 continue;
314 }
315
316 let name = CString::new(child_name)?;
317 members.push(llvm::LLVMRustArchiveMemberNew(
318 ptr::null(),
319 name.as_ptr(),
320 Some(child.raw),
321 ));
322 strings.push(name);
323 }
324 }
325 for addition in &mut additions {
326 match addition {
327 Addition::File { path, name_in_archive } => {
328 let path = CString::new(path.to_str().unwrap())?;
329 let name = CString::new(name_in_archive.clone())?;
330 members.push(llvm::LLVMRustArchiveMemberNew(
331 path.as_ptr(),
332 name.as_ptr(),
333 None,
334 ));
335 strings.push(path);
336 strings.push(name);
337 }
338 Addition::Archive { archive, skip, .. } => {
339 for child in archive.iter() {
340 let child = child.map_err(string_to_io_error)?;
341 if !is_relevant_child(&child) {
342 continue;
343 }
344 let child_name = child.name().unwrap();
345 if skip(child_name) {
346 continue;
347 }
348
349 // It appears that LLVM's archive writer is a little
350 // buggy if the name we pass down isn't just the
351 // filename component, so chop that off here and
352 // pass it in.
353 //
354 // See LLVM bug 25877 for more info.
355 let child_name =
356 Path::new(child_name).file_name().unwrap().to_str().unwrap();
357 let name = CString::new(child_name)?;
358 let m = llvm::LLVMRustArchiveMemberNew(
359 ptr::null(),
360 name.as_ptr(),
361 Some(child.raw),
362 );
363 members.push(m);
364 strings.push(name);
365 }
366 }
367 }
368 }
369
370 let r = llvm::LLVMRustWriteArchive(
371 dst.as_ptr(),
372 members.len() as libc::size_t,
373 members.as_ptr() as *const &_,
374 should_update_symbols,
375 kind,
376 );
377 let ret = if r.into_result().is_err() {
378 let err = llvm::LLVMRustGetLastError();
379 let msg = if err.is_null() {
380 "failed to write archive".into()
381 } else {
382 String::from_utf8_lossy(CStr::from_ptr(err).to_bytes())
383 };
384 Err(io::Error::new(io::ErrorKind::Other, msg))
385 } else {
386 Ok(())
387 };
388 for member in members {
389 llvm::LLVMRustArchiveMemberFree(member);
390 }
391 ret
392 }
393 }
394 }
395
396 fn string_to_io_error(s: String) -> io::Error {
397 io::Error::new(io::ErrorKind::Other, format!("bad archive: {}", s))
398 }