]> git.proxmox.com Git - rustc.git/blob - compiler/rustc_codegen_llvm/src/back/archive.rs
New upstream version 1.55.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::{DllCallingConvention, 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(|import: &DllImport| {
212 if self.config.sess.target.arch == "x86" {
213 LlvmArchiveBuilder::i686_decorated_name(import)
214 } else {
215 CString::new(import.name.to_string()).unwrap()
216 }
217 })
218 .collect();
219
220 let output_path_z = rustc_fs_util::path_to_c_string(&output_path);
221
222 tracing::trace!("invoking LLVMRustWriteImportLibrary");
223 tracing::trace!(" dll_name {:#?}", dll_name_z);
224 tracing::trace!(" output_path {}", output_path.display());
225 tracing::trace!(
226 " import names: {}",
227 dll_imports.iter().map(|import| import.name.to_string()).collect::<Vec<_>>().join(", "),
228 );
229
230 let ffi_exports: Vec<LLVMRustCOFFShortExport> = import_name_vector
231 .iter()
232 .map(|name_z| LLVMRustCOFFShortExport::from_name(name_z.as_ptr()))
233 .collect();
234 let result = unsafe {
235 crate::llvm::LLVMRustWriteImportLibrary(
236 dll_name_z.as_ptr(),
237 output_path_z.as_ptr(),
238 ffi_exports.as_ptr(),
239 ffi_exports.len(),
240 llvm_machine_type(&self.config.sess.target.arch) as u16,
241 !self.config.sess.target.is_like_msvc,
242 )
243 };
244
245 if result == crate::llvm::LLVMRustResult::Failure {
246 self.config.sess.fatal(&format!(
247 "Error creating import library for {}: {}",
248 lib_name,
249 llvm::last_error().unwrap_or("unknown LLVM error".to_string())
250 ));
251 }
252
253 self.add_archive(&output_path, |_| false).unwrap_or_else(|e| {
254 self.config.sess.fatal(&format!(
255 "failed to add native library {}: {}",
256 output_path.display(),
257 e
258 ));
259 });
260 }
261 }
262
263 impl<'a> LlvmArchiveBuilder<'a> {
264 fn src_archive(&mut self) -> Option<&ArchiveRO> {
265 if let Some(ref a) = self.src_archive {
266 return a.as_ref();
267 }
268 let src = self.config.src.as_ref()?;
269 self.src_archive = Some(ArchiveRO::open(src).ok());
270 self.src_archive.as_ref().unwrap().as_ref()
271 }
272
273 fn add_archive<F>(&mut self, archive: &Path, skip: F) -> io::Result<()>
274 where
275 F: FnMut(&str) -> bool + 'static,
276 {
277 let archive_ro = match ArchiveRO::open(archive) {
278 Ok(ar) => ar,
279 Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e)),
280 };
281 if self.additions.iter().any(|ar| ar.path() == archive) {
282 return Ok(());
283 }
284 self.additions.push(Addition::Archive {
285 path: archive.to_path_buf(),
286 archive: archive_ro,
287 skip: Box::new(skip),
288 });
289 Ok(())
290 }
291
292 fn llvm_archive_kind(&self) -> Result<ArchiveKind, &str> {
293 let kind = &*self.config.sess.target.archive_format;
294 kind.parse().map_err(|_| kind)
295 }
296
297 fn build_with_llvm(&mut self, kind: ArchiveKind) -> io::Result<()> {
298 let removals = mem::take(&mut self.removals);
299 let mut additions = mem::take(&mut self.additions);
300 let mut strings = Vec::new();
301 let mut members = Vec::new();
302
303 let dst = CString::new(self.config.dst.to_str().unwrap())?;
304 let should_update_symbols = self.should_update_symbols;
305
306 unsafe {
307 if let Some(archive) = self.src_archive() {
308 for child in archive.iter() {
309 let child = child.map_err(string_to_io_error)?;
310 let child_name = match child.name() {
311 Some(s) => s,
312 None => continue,
313 };
314 if removals.iter().any(|r| r == child_name) {
315 continue;
316 }
317
318 let name = CString::new(child_name)?;
319 members.push(llvm::LLVMRustArchiveMemberNew(
320 ptr::null(),
321 name.as_ptr(),
322 Some(child.raw),
323 ));
324 strings.push(name);
325 }
326 }
327 for addition in &mut additions {
328 match addition {
329 Addition::File { path, name_in_archive } => {
330 let path = CString::new(path.to_str().unwrap())?;
331 let name = CString::new(name_in_archive.clone())?;
332 members.push(llvm::LLVMRustArchiveMemberNew(
333 path.as_ptr(),
334 name.as_ptr(),
335 None,
336 ));
337 strings.push(path);
338 strings.push(name);
339 }
340 Addition::Archive { archive, skip, .. } => {
341 for child in archive.iter() {
342 let child = child.map_err(string_to_io_error)?;
343 if !is_relevant_child(&child) {
344 continue;
345 }
346 let child_name = child.name().unwrap();
347 if skip(child_name) {
348 continue;
349 }
350
351 // It appears that LLVM's archive writer is a little
352 // buggy if the name we pass down isn't just the
353 // filename component, so chop that off here and
354 // pass it in.
355 //
356 // See LLVM bug 25877 for more info.
357 let child_name =
358 Path::new(child_name).file_name().unwrap().to_str().unwrap();
359 let name = CString::new(child_name)?;
360 let m = llvm::LLVMRustArchiveMemberNew(
361 ptr::null(),
362 name.as_ptr(),
363 Some(child.raw),
364 );
365 members.push(m);
366 strings.push(name);
367 }
368 }
369 }
370 }
371
372 let r = llvm::LLVMRustWriteArchive(
373 dst.as_ptr(),
374 members.len() as libc::size_t,
375 members.as_ptr() as *const &_,
376 should_update_symbols,
377 kind,
378 );
379 let ret = if r.into_result().is_err() {
380 let err = llvm::LLVMRustGetLastError();
381 let msg = if err.is_null() {
382 "failed to write archive".into()
383 } else {
384 String::from_utf8_lossy(CStr::from_ptr(err).to_bytes())
385 };
386 Err(io::Error::new(io::ErrorKind::Other, msg))
387 } else {
388 Ok(())
389 };
390 for member in members {
391 llvm::LLVMRustArchiveMemberFree(member);
392 }
393 ret
394 }
395 }
396
397 fn i686_decorated_name(import: &DllImport) -> CString {
398 let name = import.name;
399 // We verified during construction that `name` does not contain any NULL characters, so the
400 // conversion to CString is guaranteed to succeed.
401 CString::new(match import.calling_convention {
402 DllCallingConvention::C => format!("_{}", name),
403 DllCallingConvention::Stdcall(arg_list_size) => format!("_{}@{}", name, arg_list_size),
404 DllCallingConvention::Fastcall(arg_list_size) => format!("@{}@{}", name, arg_list_size),
405 DllCallingConvention::Vectorcall(arg_list_size) => {
406 format!("{}@@{}", name, arg_list_size)
407 }
408 })
409 .unwrap()
410 }
411 }
412
413 fn string_to_io_error(s: String) -> io::Error {
414 io::Error::new(io::ErrorKind::Other, format!("bad archive: {}", s))
415 }