rust/src/librustc_back/archive.rs
bors 43cf733bfa Auto merge of #25350 - alexcrichton:msvc, r=brson
Special thanks to @retep998 for the [excellent writeup](https://github.com/rust-lang/rfcs/issues/1061) of tasks to be done and @ricky26 for initially blazing the trail here!

# MSVC Support

This goal of this series of commits is to add MSVC support to the Rust compiler
and build system, allowing it more easily interoperate with Visual Studio
installations and native libraries compiled outside of MinGW.

The tl;dr; of this change is that there is a new target of the compiler,
`x86_64-pc-windows-msvc`, which will not interact with the MinGW toolchain at
all and will instead use `link.exe` to assemble output artifacts.

## Why try to use MSVC?

With today's Rust distribution, when you install a compiler on Windows you also
install `gcc.exe` and a number of supporting libraries by default (this can be
opted out of). This allows installations to remain independent of MinGW
installations, but it still generally requires native code to be linked with
MinGW instead of MSVC. Some more background can also be found in #1768 about the
incompatibilities between MinGW and MSVC.

Overall the current installation strategy is quite nice so long as you don't
interact with native code, but once you do the usage of a MinGW-based `gcc.exe`
starts to get quite painful.

Relying on a nonstandard Windows toolchain has also been a long-standing "code
smell" of Rust and has been slated for remedy for quite some time now. Using a
standard toolchain is a great motivational factor for improving the
interoperability of Rust code with the native system.

## What does it mean to use MSVC?

"Using MSVC" can be a bit of a nebulous concept, but this PR defines it as:

* The build system for Rust will build as much code as possible with the MSVC
  compiler, `cl.exe`.
* The build system will use native MSVC tools for managing archives.
* The compiler will link all output with `link.exe` instead of `gcc.exe`.

None of these are currently implemented today, but all are required for the
compiler to fluently interoperate with MSVC.

## How does this all work?

At the highest level, this PR adds a new target triple to the Rust compiler:

    x86_64-pc-windows-msvc

All logic for using MSVC or not is scoped within this triple and code can
conditionally build for MSVC or MinGW via:

    #[cfg(target_env = "msvc")]

It is expected that auto builders will be set up for MSVC-based compiles in
addition to the existing MinGW-based compiles, and we will likely soon start
shipping MSVC nightlies where `x86_64-pc-windows-msvc` is the host target triple
of the compiler.

# Summary of changes

Here I'll explain at a high level what many of the changes made were targeted
at, but many more details can be found in the commits themselves. Many thanks to
@retep998 for the excellent writeup in rust-lang/rfcs#1061 and @rick26 for a lot
of the initial proof-of-concept work!

## Build system changes

As is probably expected, a large chunk of this PR is changes to Rust's build
system to build with MSVC. At a high level **it is an explicit non goal** to
enable building outside of a MinGW shell, instead all Makefile infrastructure we
have today is retrofitted with support to use MSVC instead of the standard MSVC
toolchain. Some of the high-level changes are:

* The configure script now detects when MSVC is being targeted and adds a number
  of additional requirements about the build environment:
  * The `--msvc-root` option must be specified or `cl.exe` must be in PATH to
    discover where MSVC is installed. The compiler in use is also required to
    target x86_64.
  * Once the MSVC root is known, the INCLUDE/LIB environment variables are
    scraped so they can be reexported by the build system.
  * CMake is required to build LLVM with MSVC (and LLVM is also configured with
    CMake instead of the normal configure script).
  * jemalloc is currently unconditionally disabled for MSVC targets as jemalloc
    isn't a hard requirement and I don't know how to build it with MSVC.
* Invocations of a C and/or C++ compiler are now abstracted behind macros to
  appropriately call the underlying compiler with the correct format of
  arguments, for example there is now a macro for "assemble an archive from
  objects" instead of hard-coded invocations of `$(AR) crus liboutput.a ...`
* The output filenames for standard libraries such as morestack/compiler-rt are
  now "more correct" on windows as they are shipped as `foo.lib` instead of
  `libfoo.a`.
* Rust targets can now depend on native tools provided by LLVM, and as you'll
  see in the commits the entire MSVC target depends on `llvm-ar.exe`.
* Support for custom arbitrary makefile dependencies of Rust targets has been
  added. The MSVC target for `rustc_llvm` currently requires a custom `.DEF`
  file to be passed to the linker to get further linkages to complete.

## Compiler changes

The modifications made to the compiler have so far largely been minor tweaks
here and there, mostly just adding a layer of abstraction over whether MSVC or a
GNU-like linker is being used. At a high-level these changes are:

* The section name for metadata storage in dynamic libraries is called `.rustc`
  for MSVC-based platorms as section names cannot contain more than 8
  characters.
* The implementation of `rustc_back::Archive` was refactored, but the
  functionality has remained the same.
* Targets can now specify the default `ar` utility to use, and for MSVC this
  defaults to `llvm-ar.exe`
* The building of the linker command in `rustc_trans:🔙:link` has been
  abstracted behind a trait for the same code path to be used between GNU and
  MSVC linkers.

## Standard library changes

Only a few small changes were required to the stadnard library itself, and only
for minor differences between the C runtime of msvcrt.dll and MinGW's libc.a

* Some function names for floating point functions have leading underscores, and
  some are not present at all.
* Linkage to the `advapi32` library for crypto-related functions is now
  explicit.
* Some small bits of C code here and there were fixed for compatibility with
  MSVC's cl.exe compiler.

# Future Work

This commit is not yet a 100% complete port to using MSVC as there are still
some key components missing as well as some unimplemented optimizations. This PR
is already getting large enough that I wanted to draw the line here, but here's
a list of what is not implemented in this PR, on purpose:

## Unwinding

The revision of our LLVM submodule [does not seem to implement][llvm] does not
support lowering SEH exception handling on the Windows MSVC targets, so
unwinding support is not currently implemented for the standard library (it's
lowered to an abort).

[llvm]: https://github.com/rust-lang/llvm/blob/rust-llvm-2015-02-19/lib/CodeGen/Passes.cpp#L454-L461

It looks like, however, that upstream LLVM has quite a bit more support for SEH
unwinding and landing pads than the current revision we have, so adding support
will likely just involve updating LLVM and then adding some shims of our own
here and there.

## dllimport and dllexport

An interesting part of Windows which MSVC forces our hand on (and apparently
MinGW didn't) is the usage of `dllimport` and `dllexport` attributes in LLVM IR
as well as native dependencies (in C these correspond to
`__declspec(dllimport)`).

Whenever a dynamic library is built by MSVC it must have its public interface
specified by functions tagged with `dllexport` or otherwise they're not
available to be linked against. This poses a few problems for the compiler, some
of which are somewhat fundamental, but this commit alters the compiler to attach
the `dllexport` attribute to all LLVM functions that are reachable (e.g. they're
already tagged with external linkage). This is suboptimal for a few reasons:

* If an object file will never be included in a dynamic library, there's no need
  to attach the dllexport attribute. Most object files in Rust are not destined
  to become part of a dll as binaries are statically linked by default.
* If the compiler is emitting both an rlib and a dylib, the same source object
  file is currently used but with MSVC this may be less feasible. The compiler
  may be able to get around this, but it may involve some invasive changes to
  deal with this.

The flipside of this situation is that whenever you link to a dll and you import
a function from it, the import should be tagged with `dllimport`. At this time,
however, the compiler does not emit `dllimport` for any declarations other than
constants (where it is required), which is again suboptimal for even more
reasons!

* Calling a function imported from another dll without using `dllimport` causes
  the linker/compiler to have extra overhead (one `jmp` instruction on x86) when
  calling the function.
* The same object file may be used in different circumstances, so a function may
  be imported from a dll if the object is linked into a dll, but it may be
  just linked against if linked into an rlib.
* The compiler has no knowledge about whether native functions should be tagged
  dllimport or not.

For now the compiler takes the perf hit (I do not have any numbers to this
effect) by marking very little as `dllimport` and praying the linker will take
care of everything. Fixing this problem will likely require adding a few
attributes to Rust itself (feature gated at the start) and then strongly
recommending static linkage on Windows! This may also involve shipping a
statically linked compiler on Windows instead of a dynamically linked compiler,
but these sorts of changes are pretty invasive and aren't part of this PR.

## CI integration

Thankfully we don't need to set up a new snapshot bot for the changes made here as our snapshots are freestanding already, we should be able to use the same snapshot to bootstrap both MinGW and MSVC compilers (once a new snapshot is made from these changes).

I plan on setting up a new suite of auto bots which are testing MSVC configurations for now as well, for now they'll just be bootstrapping and not running tests, but once unwinding is implemented they'll start running all tests as well and we'll eventually start gating on them as well.

---

I'd love as many eyes on this as we've got as this was one of my first interactions with MSVC and Visual Studio, so there may be glaring holes that I'm missing here and there!

cc @retep998, @ricky26, @vadimcn, @klutzy 

r? @brson
2015-05-20 00:31:55 +00:00

359 lines
14 KiB
Rust

// Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! A helper class for dealing with static archives
use std::env;
use std::fs::{self, File};
use std::io::prelude::*;
use std::io;
use std::path::{Path, PathBuf};
use std::process::{Command, Output, Stdio};
use std::str;
use syntax::diagnostic::Handler as ErrorHandler;
use rustc_llvm::archive_ro::ArchiveRO;
use tempdir::TempDir;
pub const METADATA_FILENAME: &'static str = "rust.metadata.bin";
pub struct ArchiveConfig<'a> {
pub handler: &'a ErrorHandler,
pub dst: PathBuf,
pub lib_search_paths: Vec<PathBuf>,
pub slib_prefix: String,
pub slib_suffix: String,
pub ar_prog: String
}
pub struct Archive<'a> {
config: ArchiveConfig<'a>,
}
/// Helper for adding many files to an archive with a single invocation of
/// `ar`.
#[must_use = "must call build() to finish building the archive"]
pub struct ArchiveBuilder<'a> {
archive: Archive<'a>,
work_dir: TempDir,
/// Filename of each member that should be added to the archive.
members: Vec<PathBuf>,
should_update_symbols: bool,
}
enum Action<'a> {
Remove(&'a Path),
AddObjects(&'a [&'a PathBuf], bool),
UpdateSymbols,
}
pub fn find_library(name: &str, osprefix: &str, ossuffix: &str,
search_paths: &[PathBuf],
handler: &ErrorHandler) -> PathBuf {
// On Windows, static libraries sometimes show up as libfoo.a and other
// times show up as foo.lib
let oslibname = format!("{}{}{}", osprefix, name, ossuffix);
let unixlibname = format!("lib{}.a", name);
for path in search_paths {
debug!("looking for {} inside {:?}", name, path);
let test = path.join(&oslibname[..]);
if test.exists() { return test }
if oslibname != unixlibname {
let test = path.join(&unixlibname[..]);
if test.exists() { return test }
}
}
handler.fatal(&format!("could not find native static library `{}`, \
perhaps an -L flag is missing?",
name));
}
impl<'a> Archive<'a> {
fn new(config: ArchiveConfig<'a>) -> Archive<'a> {
Archive { config: config }
}
/// Opens an existing static archive
pub fn open(config: ArchiveConfig<'a>) -> Archive<'a> {
let archive = Archive::new(config);
assert!(archive.config.dst.exists());
archive
}
/// Removes a file from this archive
pub fn remove_file(&mut self, file: &str) {
self.run(None, Action::Remove(Path::new(file)));
}
/// Lists all files in an archive
pub fn files(&self) -> Vec<String> {
let archive = match ArchiveRO::open(&self.config.dst) {
Some(ar) => ar,
None => return Vec::new(),
};
let ret = archive.iter().filter_map(|child| child.name())
.map(|name| name.to_string())
.collect();
return ret;
}
/// Creates an `ArchiveBuilder` for adding files to this archive.
pub fn extend(self) -> ArchiveBuilder<'a> {
ArchiveBuilder::new(self)
}
fn run(&self, cwd: Option<&Path>, action: Action) -> Output {
let abs_dst = env::current_dir().unwrap().join(&self.config.dst);
let ar = &self.config.ar_prog;
let mut cmd = Command::new(ar);
cmd.stdout(Stdio::piped()).stderr(Stdio::piped());
self.prepare_ar_action(&mut cmd, &abs_dst, action);
info!("{:?}", cmd);
if let Some(p) = cwd {
cmd.current_dir(p);
info!("inside {:?}", p.display());
}
let handler = &self.config.handler;
match cmd.spawn() {
Ok(prog) => {
let o = prog.wait_with_output().unwrap();
if !o.status.success() {
handler.err(&format!("{:?} failed with: {}", cmd, o.status));
handler.note(&format!("stdout ---\n{}",
str::from_utf8(&o.stdout).unwrap()));
handler.note(&format!("stderr ---\n{}",
str::from_utf8(&o.stderr).unwrap()));
handler.abort_if_errors();
}
o
},
Err(e) => {
handler.err(&format!("could not exec `{}`: {}",
self.config.ar_prog, e));
handler.abort_if_errors();
panic!("rustc::back::archive::run() should not reach this point");
}
}
}
fn prepare_ar_action(&self, cmd: &mut Command, dst: &Path, action: Action) {
match action {
Action::Remove(file) => {
cmd.arg("d").arg(dst).arg(file);
}
Action::AddObjects(objs, update_symbols) => {
cmd.arg(if update_symbols {"crus"} else {"cruS"})
.arg(dst)
.args(objs);
}
Action::UpdateSymbols => {
cmd.arg("s").arg(dst);
}
}
}
}
impl<'a> ArchiveBuilder<'a> {
fn new(archive: Archive<'a>) -> ArchiveBuilder<'a> {
ArchiveBuilder {
archive: archive,
work_dir: TempDir::new("rsar").unwrap(),
members: vec![],
should_update_symbols: false,
}
}
/// Create a new static archive, ready for adding files.
pub fn create(config: ArchiveConfig<'a>) -> ArchiveBuilder<'a> {
let archive = Archive::new(config);
ArchiveBuilder::new(archive)
}
/// Adds all of the contents of a native library to this archive. This will
/// search in the relevant locations for a library named `name`.
pub fn add_native_library(&mut self, name: &str) -> io::Result<()> {
let location = find_library(name,
&self.archive.config.slib_prefix,
&self.archive.config.slib_suffix,
&self.archive.config.lib_search_paths,
self.archive.config.handler);
self.add_archive(&location, name, |_| false)
}
/// Adds all of the contents of the rlib at the specified path to this
/// archive.
///
/// This ignores adding the bytecode from the rlib, and if LTO is enabled
/// then the object file also isn't added.
pub fn add_rlib(&mut self, rlib: &Path, name: &str,
lto: bool) -> io::Result<()> {
// Ignoring obj file starting with the crate name
// as simple comparison is not enough - there
// might be also an extra name suffix
let obj_start = format!("{}", name);
let obj_start = &obj_start[..];
// Ignoring all bytecode files, no matter of
// name
let bc_ext = ".bytecode.deflate";
self.add_archive(rlib, &name[..], |fname: &str| {
let skip_obj = lto && fname.starts_with(obj_start)
&& fname.ends_with(".o");
skip_obj || fname.ends_with(bc_ext) || fname == METADATA_FILENAME
})
}
/// Adds an arbitrary file to this archive
pub fn add_file(&mut self, file: &Path) -> io::Result<()> {
let filename = Path::new(file.file_name().unwrap());
let new_file = self.work_dir.path().join(&filename);
try!(fs::copy(file, &new_file));
self.members.push(filename.to_path_buf());
Ok(())
}
/// Indicate that the next call to `build` should updates all symbols in
/// the archive (run 'ar s' over it).
pub fn update_symbols(&mut self) {
self.should_update_symbols = true;
}
/// Combine the provided files, rlibs, and native libraries into a single
/// `Archive`.
pub fn build(self) -> Archive<'a> {
// Get an absolute path to the destination, so `ar` will work even
// though we run it from `self.work_dir`.
let mut objects = Vec::new();
let mut total_len = self.archive.config.dst.to_string_lossy().len();
if self.members.is_empty() {
if self.should_update_symbols {
self.archive.run(Some(self.work_dir.path()),
Action::UpdateSymbols);
}
return self.archive;
}
// Don't allow the total size of `args` to grow beyond 32,000 bytes.
// Windows will raise an error if the argument string is longer than
// 32,768, and we leave a bit of extra space for the program name.
const ARG_LENGTH_LIMIT: usize = 32_000;
for member_name in &self.members {
let len = member_name.to_string_lossy().len();
// `len + 1` to account for the space that's inserted before each
// argument. (Windows passes command-line arguments as a single
// string, not an array of strings.)
if total_len + len + 1 > ARG_LENGTH_LIMIT {
// Add the archive members seen so far, without updating the
// symbol table.
self.archive.run(Some(self.work_dir.path()),
Action::AddObjects(&objects, false));
objects.clear();
total_len = self.archive.config.dst.to_string_lossy().len();
}
objects.push(member_name);
total_len += len + 1;
}
// Add the remaining archive members, and update the symbol table if
// necessary.
self.archive.run(Some(self.work_dir.path()),
Action::AddObjects(&objects, self.should_update_symbols));
self.archive
}
fn add_archive<F>(&mut self, archive: &Path, name: &str,
mut skip: F) -> io::Result<()>
where F: FnMut(&str) -> bool,
{
let archive = match ArchiveRO::open(archive) {
Some(ar) => ar,
None => return Err(io::Error::new(io::ErrorKind::Other,
"failed to open archive")),
};
// Next, we must rename all of the inputs to "guaranteed unique names".
// We write each file into `self.work_dir` under its new unique name.
// The reason for this renaming is that archives are keyed off the name
// of the files, so if two files have the same name they will override
// one another in the archive (bad).
//
// We skip any files explicitly desired for skipping, and we also skip
// all SYMDEF files as these are just magical placeholders which get
// re-created when we make a new archive anyway.
for file in archive.iter() {
let filename = match file.name() {
Some(s) => s,
None => continue,
};
if filename.contains(".SYMDEF") { continue }
if skip(filename) { continue }
let filename = Path::new(filename).file_name().unwrap()
.to_str().unwrap();
// Archives on unix systems typically do not have slashes in
// filenames as the `ar` utility generally only uses the last
// component of a path for the filename list in the archive. On
// Windows, however, archives assembled with `lib.exe` will preserve
// the full path to the file that was placed in the archive,
// including path separators.
//
// The code below is munging paths so it'll go wrong pretty quickly
// if there's some unexpected slashes in the filename, so here we
// just chop off everything but the filename component. Note that
// this can cause duplicate filenames, but that's also handled below
// as well.
let filename = Path::new(filename).file_name().unwrap()
.to_str().unwrap();
// An archive can contain files of the same name multiple times, so
// we need to be sure to not have them overwrite one another when we
// extract them. Consequently we need to find a truly unique file
// name for us!
let mut new_filename = String::new();
for n in 0.. {
let n = if n == 0 {String::new()} else {format!("-{}", n)};
new_filename = format!("r{}-{}-{}", n, name, filename);
// LLDB (as mentioned in back::link) crashes on filenames of
// exactly
// 16 bytes in length. If we're including an object file with
// exactly 16-bytes of characters, give it some prefix so
// that it's not 16 bytes.
new_filename = if new_filename.len() == 16 {
format!("lldb-fix-{}", new_filename)
} else {
new_filename
};
let present = self.members.iter().filter_map(|p| {
p.file_name().and_then(|f| f.to_str())
}).any(|s| s == new_filename);
if !present {
break
}
}
let dst = self.work_dir.path().join(&new_filename);
try!(try!(File::create(&dst)).write_all(file.data()));
self.members.push(PathBuf::from(new_filename));
}
Ok(())
}
}