]> git.proxmox.com Git - rustc.git/blame - compiler/rustc_codegen_llvm/src/back/archive.rs
New upstream version 1.60.0+dfsg1
[rustc.git] / compiler / rustc_codegen_llvm / src / back / archive.rs
CommitLineData
c1a9b12d
SL
1//! A helper class for dealing with static archives
2
5099ac24
FG
3use std::env;
4use std::ffi::{CStr, CString, OsString};
c1a9b12d
SL
5use std::io;
6use std::mem;
7use std::path::{Path, PathBuf};
e9174d1e 8use std::ptr;
c1a9b12d
SL
9use std::str;
10
9fa01778 11use crate::llvm::archive_ro::{ArchiveRO, Child};
17df50a5 12use crate::llvm::{self, ArchiveKind, LLVMMachineType, LLVMRustCOFFShortExport};
c295e0f8 13use rustc_codegen_ssa::back::archive::ArchiveBuilder;
17df50a5 14use rustc_data_structures::temp_dir::MaybeTempDir;
c295e0f8 15use rustc_session::cstore::{DllCallingConvention, DllImport};
ba9703b0 16use rustc_session::Session;
c1a9b12d 17
48663c56 18struct ArchiveConfig<'a> {
c1a9b12d
SL
19 pub sess: &'a Session,
20 pub dst: PathBuf,
21 pub src: Option<PathBuf>,
c1a9b12d
SL
22}
23
abe05a73 24/// Helper for adding many files to an archive.
c1a9b12d 25#[must_use = "must call build() to finish building the archive"]
48663c56 26pub struct LlvmArchiveBuilder<'a> {
c1a9b12d 27 config: ArchiveConfig<'a>,
c1a9b12d
SL
28 removals: Vec<String>,
29 additions: Vec<Addition>,
c1a9b12d
SL
30 src_archive: Option<Option<ArchiveRO>>,
31}
32
33enum Addition {
dfeec247
XL
34 File { path: PathBuf, name_in_archive: String },
35 Archive { path: PathBuf, archive: ArchiveRO, skip: Box<dyn FnMut(&str) -> bool> },
c1a9b12d
SL
36}
37
416331ca
XL
38impl Addition {
39 fn path(&self) -> &Path {
40 match self {
41 Addition::File { path, .. } | Addition::Archive { path, .. } => path,
42 }
43 }
44}
45
9fa01778 46fn is_relevant_child(c: &Child<'_>) -> bool {
c1a9b12d
SL
47 match c.name() {
48 Some(name) => !name.contains("SYMDEF"),
49 None => false,
50 }
51}
52
dfeec247 53fn archive_config<'a>(sess: &'a Session, output: &Path, input: Option<&Path>) -> ArchiveConfig<'a> {
c295e0f8 54 ArchiveConfig { sess, dst: output.to_path_buf(), src: input.map(|p| p.to_path_buf()) }
48663c56
XL
55}
56
17df50a5
XL
57/// Map machine type strings to values of LLVM's MachineTypes enum.
58fn llvm_machine_type(cpu: &str) -> LLVMMachineType {
59 match cpu {
60 "x86_64" => LLVMMachineType::AMD64,
61 "x86" => LLVMMachineType::I386,
62 "aarch64" => LLVMMachineType::ARM64,
63 "arm" => LLVMMachineType::ARM,
64 _ => panic!("unsupported cpu type {}", cpu),
65 }
66}
67
48663c56 68impl<'a> ArchiveBuilder<'a> for LlvmArchiveBuilder<'a> {
9fa01778 69 /// Creates a new static archive, ready for modifying the archive specified
c1a9b12d 70 /// by `config`.
dfeec247 71 fn new(sess: &'a Session, output: &Path, input: Option<&Path>) -> LlvmArchiveBuilder<'a> {
48663c56
XL
72 let config = archive_config(sess, output, input);
73 LlvmArchiveBuilder {
3b2f2976 74 config,
c1a9b12d
SL
75 removals: Vec::new(),
76 additions: Vec::new(),
c1a9b12d
SL
77 src_archive: None,
78 }
79 }
80
81 /// Removes a file from this archive
48663c56 82 fn remove_file(&mut self, file: &str) {
c1a9b12d
SL
83 self.removals.push(file.to_string());
84 }
85
86 /// Lists all files in an archive
48663c56 87 fn src_files(&mut self) -> Vec<String> {
c1a9b12d 88 if self.src_archive().is_none() {
dfeec247 89 return Vec::new();
c1a9b12d 90 }
a1dfa0c6 91
c1a9b12d 92 let archive = self.src_archive.as_ref().unwrap().as_ref().unwrap();
a1dfa0c6 93
dfeec247
XL
94 archive
95 .iter()
96 .filter_map(|child| child.ok())
97 .filter(is_relevant_child)
98 .filter_map(|child| child.name())
99 .filter(|name| !self.removals.iter().any(|x| x == name))
100 .map(|name| name.to_owned())
101 .collect()
c1a9b12d
SL
102 }
103
c295e0f8
XL
104 fn add_archive<F>(&mut self, archive: &Path, skip: F) -> io::Result<()>
105 where
106 F: FnMut(&str) -> bool + 'static,
107 {
108 let archive_ro = match ArchiveRO::open(archive) {
109 Ok(ar) => ar,
110 Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e)),
111 };
112 if self.additions.iter().any(|ar| ar.path() == archive) {
113 return Ok(());
114 }
115 self.additions.push(Addition::Archive {
116 path: archive.to_path_buf(),
117 archive: archive_ro,
118 skip: Box::new(skip),
b039eaaf 119 });
c295e0f8 120 Ok(())
c1a9b12d
SL
121 }
122
c1a9b12d 123 /// Adds an arbitrary file to this archive
48663c56 124 fn add_file(&mut self, file: &Path) {
c1a9b12d 125 let name = file.file_name().unwrap().to_str().unwrap();
dfeec247
XL
126 self.additions
127 .push(Addition::File { path: file.to_path_buf(), name_in_archive: name.to_owned() });
c1a9b12d
SL
128 }
129
c1a9b12d
SL
130 /// Combine the provided files, rlibs, and native libraries into a single
131 /// `Archive`.
48663c56 132 fn build(mut self) {
dfeec247
XL
133 let kind = self.llvm_archive_kind().unwrap_or_else(|kind| {
134 self.config.sess.fatal(&format!("Don't know how to build archive of type: {}", kind))
135 });
c1a9b12d 136
3157f602
XL
137 if let Err(e) = self.build_with_llvm(kind) {
138 self.config.sess.fatal(&format!("failed to build archive: {}", e));
c1a9b12d 139 }
c1a9b12d 140 }
17df50a5
XL
141
142 fn inject_dll_import_lib(
143 &mut self,
144 lib_name: &str,
145 dll_imports: &[DllImport],
146 tmpdir: &MaybeTempDir,
147 ) {
148 let output_path = {
149 let mut output_path: PathBuf = tmpdir.as_ref().to_path_buf();
150 output_path.push(format!("{}_imports", lib_name));
151 output_path.with_extension("lib")
152 };
153
5099ac24
FG
154 let mingw_gnu_toolchain = self.config.sess.target.llvm_target.ends_with("pc-windows-gnu");
155
156 let import_name_and_ordinal_vector: Vec<(String, Option<u16>)> = dll_imports
17df50a5 157 .iter()
136023e0
XL
158 .map(|import: &DllImport| {
159 if self.config.sess.target.arch == "x86" {
5099ac24
FG
160 (
161 LlvmArchiveBuilder::i686_decorated_name(import, mingw_gnu_toolchain),
162 import.ordinal,
163 )
136023e0 164 } else {
5099ac24 165 (import.name.to_string(), import.ordinal)
136023e0 166 }
17df50a5
XL
167 })
168 .collect();
169
5099ac24
FG
170 if mingw_gnu_toolchain {
171 // The binutils linker used on -windows-gnu targets cannot read the import
172 // libraries generated by LLVM: in our attempts, the linker produced an .EXE
173 // that loaded but crashed with an AV upon calling one of the imported
174 // functions. Therefore, use binutils to create the import library instead,
175 // by writing a .DEF file to the temp dir and calling binutils's dlltool.
176 let def_file_path =
177 tmpdir.as_ref().join(format!("{}_imports", lib_name)).with_extension("def");
178
179 let def_file_content = format!(
180 "EXPORTS\n{}",
181 import_name_and_ordinal_vector
182 .into_iter()
183 .map(|(name, ordinal)| {
184 match ordinal {
185 Some(n) => format!("{} @{} NONAME", name, n),
186 None => name,
187 }
188 })
189 .collect::<Vec<String>>()
190 .join("\n")
191 );
17df50a5 192
5099ac24
FG
193 match std::fs::write(&def_file_path, def_file_content) {
194 Ok(_) => {}
195 Err(e) => {
196 self.config.sess.fatal(&format!("Error writing .DEF file: {}", e));
197 }
198 };
17df50a5 199
5099ac24
FG
200 let dlltool = find_binutils_dlltool(self.config.sess);
201 let result = std::process::Command::new(dlltool)
202 .args([
203 "-d",
204 def_file_path.to_str().unwrap(),
205 "-D",
206 lib_name,
207 "-l",
208 output_path.to_str().unwrap(),
209 ])
210 .output();
211
212 match result {
213 Err(e) => {
214 self.config.sess.fatal(&format!("Error calling dlltool: {}", e));
215 }
216 Ok(output) if !output.status.success() => self.config.sess.fatal(&format!(
217 "Dlltool could not create import library: {}\n{}",
218 String::from_utf8_lossy(&output.stdout),
219 String::from_utf8_lossy(&output.stderr)
220 )),
221 _ => {}
222 }
223 } else {
224 // we've checked for \0 characters in the library name already
225 let dll_name_z = CString::new(lib_name).unwrap();
226
227 let output_path_z = rustc_fs_util::path_to_c_string(&output_path);
228
229 tracing::trace!("invoking LLVMRustWriteImportLibrary");
230 tracing::trace!(" dll_name {:#?}", dll_name_z);
231 tracing::trace!(" output_path {}", output_path.display());
232 tracing::trace!(
233 " import names: {}",
234 dll_imports
235 .iter()
236 .map(|import| import.name.to_string())
237 .collect::<Vec<_>>()
238 .join(", "),
239 );
17df50a5 240
5099ac24
FG
241 // All import names are Rust identifiers and therefore cannot contain \0 characters.
242 // FIXME: when support for #[link_name] is implemented, ensure that the import names
243 // still don't contain any \0 characters. Also need to check that the names don't
244 // contain substrings like " @" or "NONAME" that are keywords or otherwise reserved
245 // in definition files.
246 let cstring_import_name_and_ordinal_vector: Vec<(CString, Option<u16>)> =
247 import_name_and_ordinal_vector
248 .into_iter()
249 .map(|(name, ordinal)| (CString::new(name).unwrap(), ordinal))
250 .collect();
251
252 let ffi_exports: Vec<LLVMRustCOFFShortExport> = cstring_import_name_and_ordinal_vector
253 .iter()
254 .map(|(name_z, ordinal)| LLVMRustCOFFShortExport::new(name_z.as_ptr(), *ordinal))
255 .collect();
256 let result = unsafe {
257 crate::llvm::LLVMRustWriteImportLibrary(
258 dll_name_z.as_ptr(),
259 output_path_z.as_ptr(),
260 ffi_exports.as_ptr(),
261 ffi_exports.len(),
262 llvm_machine_type(&self.config.sess.target.arch) as u16,
263 !self.config.sess.target.is_like_msvc,
264 )
265 };
266
267 if result == crate::llvm::LLVMRustResult::Failure {
268 self.config.sess.fatal(&format!(
269 "Error creating import library for {}: {}",
270 lib_name,
271 llvm::last_error().unwrap_or("unknown LLVM error".to_string())
272 ));
273 }
274 };
17df50a5
XL
275
276 self.add_archive(&output_path, |_| false).unwrap_or_else(|e| {
277 self.config.sess.fatal(&format!(
278 "failed to add native library {}: {}",
279 output_path.display(),
280 e
281 ));
282 });
283 }
48663c56
XL
284}
285
286impl<'a> LlvmArchiveBuilder<'a> {
287 fn src_archive(&mut self) -> Option<&ArchiveRO> {
288 if let Some(ref a) = self.src_archive {
dfeec247 289 return a.as_ref();
48663c56
XL
290 }
291 let src = self.config.src.as_ref()?;
292 self.src_archive = Some(ArchiveRO::open(src).ok());
293 self.src_archive.as_ref().unwrap().as_ref()
294 }
295
3157f602 296 fn llvm_archive_kind(&self) -> Result<ArchiveKind, &str> {
29967ef6 297 let kind = &*self.config.sess.target.archive_format;
3157f602 298 kind.parse().map_err(|_| kind)
c1a9b12d
SL
299 }
300
301 fn build_with_llvm(&mut self, kind: ArchiveKind) -> io::Result<()> {
416331ca
XL
302 let removals = mem::take(&mut self.removals);
303 let mut additions = mem::take(&mut self.additions);
c1a9b12d
SL
304 let mut strings = Vec::new();
305 let mut members = Vec::new();
b7449926
XL
306
307 let dst = CString::new(self.config.dst.to_str().unwrap())?;
c1a9b12d
SL
308
309 unsafe {
310 if let Some(archive) = self.src_archive() {
311 for child in archive.iter() {
54a0048b 312 let child = child.map_err(string_to_io_error)?;
c1a9b12d
SL
313 let child_name = match child.name() {
314 Some(s) => s,
315 None => continue,
316 };
317 if removals.iter().any(|r| r == child_name) {
dfeec247 318 continue;
c1a9b12d
SL
319 }
320
54a0048b 321 let name = CString::new(child_name)?;
dfeec247
XL
322 members.push(llvm::LLVMRustArchiveMemberNew(
323 ptr::null(),
324 name.as_ptr(),
325 Some(child.raw),
326 ));
c1a9b12d
SL
327 strings.push(name);
328 }
329 }
b7449926 330 for addition in &mut additions {
c1a9b12d
SL
331 match addition {
332 Addition::File { path, name_in_archive } => {
54a0048b 333 let path = CString::new(path.to_str().unwrap())?;
b7449926 334 let name = CString::new(name_in_archive.clone())?;
dfeec247
XL
335 members.push(llvm::LLVMRustArchiveMemberNew(
336 path.as_ptr(),
337 name.as_ptr(),
338 None,
339 ));
c1a9b12d
SL
340 strings.push(path);
341 strings.push(name);
342 }
416331ca 343 Addition::Archive { archive, skip, .. } => {
7453a54e 344 for child in archive.iter() {
54a0048b 345 let child = child.map_err(string_to_io_error)?;
7453a54e 346 if !is_relevant_child(&child) {
dfeec247 347 continue;
7453a54e 348 }
c1a9b12d 349 let child_name = child.name().unwrap();
7453a54e 350 if skip(child_name) {
dfeec247 351 continue;
7453a54e
SL
352 }
353
354 // It appears that LLVM's archive writer is a little
355 // buggy if the name we pass down isn't just the
356 // filename component, so chop that off here and
357 // pass it in.
358 //
359 // See LLVM bug 25877 for more info.
dfeec247
XL
360 let child_name =
361 Path::new(child_name).file_name().unwrap().to_str().unwrap();
54a0048b 362 let name = CString::new(child_name)?;
dfeec247
XL
363 let m = llvm::LLVMRustArchiveMemberNew(
364 ptr::null(),
365 name.as_ptr(),
366 Some(child.raw),
367 );
c1a9b12d
SL
368 members.push(m);
369 strings.push(name);
370 }
c1a9b12d
SL
371 }
372 }
373 }
374
dfeec247
XL
375 let r = llvm::LLVMRustWriteArchive(
376 dst.as_ptr(),
377 members.len() as libc::size_t,
378 members.as_ptr() as *const &_,
5099ac24 379 true,
dfeec247
XL
380 kind,
381 );
5bcae85e 382 let ret = if r.into_result().is_err() {
c1a9b12d
SL
383 let err = llvm::LLVMRustGetLastError();
384 let msg = if err.is_null() {
a1dfa0c6 385 "failed to write archive".into()
c1a9b12d
SL
386 } else {
387 String::from_utf8_lossy(CStr::from_ptr(err).to_bytes())
c1a9b12d
SL
388 };
389 Err(io::Error::new(io::ErrorKind::Other, msg))
390 } else {
391 Ok(())
392 };
393 for member in members {
394 llvm::LLVMRustArchiveMemberFree(member);
395 }
a1dfa0c6 396 ret
c1a9b12d
SL
397 }
398 }
136023e0 399
5099ac24 400 fn i686_decorated_name(import: &DllImport, mingw: bool) -> String {
136023e0 401 let name = import.name;
5099ac24
FG
402 let prefix = if mingw { "" } else { "_" };
403
404 match import.calling_convention {
405 DllCallingConvention::C => format!("{}{}", prefix, name),
406 DllCallingConvention::Stdcall(arg_list_size) => {
407 format!("{}{}@{}", prefix, name, arg_list_size)
408 }
136023e0
XL
409 DllCallingConvention::Fastcall(arg_list_size) => format!("@{}@{}", name, arg_list_size),
410 DllCallingConvention::Vectorcall(arg_list_size) => {
411 format!("{}@@{}", name, arg_list_size)
412 }
5099ac24 413 }
136023e0 414 }
c1a9b12d 415}
7453a54e
SL
416
417fn string_to_io_error(s: String) -> io::Error {
418 io::Error::new(io::ErrorKind::Other, format!("bad archive: {}", s))
419}
5099ac24
FG
420
421fn find_binutils_dlltool(sess: &Session) -> OsString {
422 assert!(sess.target.options.is_like_windows && !sess.target.options.is_like_msvc);
423 if let Some(dlltool_path) = &sess.opts.debugging_opts.dlltool {
424 return dlltool_path.clone().into_os_string();
425 }
426
427 let mut tool_name: OsString = if sess.host.arch != sess.target.arch {
428 // We are cross-compiling, so we need the tool with the prefix matching our target
429 if sess.target.arch == "x86" {
430 "i686-w64-mingw32-dlltool"
431 } else {
432 "x86_64-w64-mingw32-dlltool"
433 }
434 } else {
435 // We are not cross-compiling, so we just want `dlltool`
436 "dlltool"
437 }
438 .into();
439
440 if sess.host.options.is_like_windows {
441 // If we're compiling on Windows, add the .exe suffix
442 tool_name.push(".exe");
443 }
444
445 // NOTE: it's not clear how useful it is to explicitly search PATH.
446 for dir in env::split_paths(&env::var_os("PATH").unwrap_or_default()) {
447 let full_path = dir.join(&tool_name);
448 if full_path.is_file() {
449 return full_path.into_os_string();
450 }
451 }
452
453 // The user didn't specify the location of the dlltool binary, and we weren't able
454 // to find the appropriate one on the PATH. Just return the name of the tool
455 // and let the invocation fail with a hopefully useful error message.
456 tool_name
457}