From f9de964ccf767498ed7b1b4a879aaca1777a9d3d Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 29 Jun 2015 16:07:45 -0700 Subject: [PATCH 1/7] msvc: Enable landing pads by default This commit turns on landing pads for MSVC by default, which means that we'll now be running cleanups for values on the stack when an exception is thrown. This commit "fixes" the previously seen LLVM abort by attaching the `noinline` attribute to all generated drop glue to prevent landing pads from being inlined into other landing pads. The performance of MSVC is highly likely to decrease from this commit, but there are various routes we can taken in the future if this ends up staying for quite a while, such as generating a shim function only called from landing pads which calls the actual drop glue, and this shim is marked noinline. For now, however, this patch enables MSVC to successfully bootstrap itself! --- src/librustc_trans/trans/base.rs | 7 +------ src/librustc_trans/trans/glue.rs | 23 ++++++++++++++++++++++- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/librustc_trans/trans/base.rs b/src/librustc_trans/trans/base.rs index 0cd6bbad03aa9..b829a9c6ec449 100644 --- a/src/librustc_trans/trans/base.rs +++ b/src/librustc_trans/trans/base.rs @@ -751,12 +751,7 @@ pub fn invoke<'blk, 'tcx>(bcx: Block<'blk, 'tcx>, } pub fn need_invoke(bcx: Block) -> bool { - // FIXME(#25869) currently unwinding is not implemented for MSVC and our - // normal unwinding infrastructure ends up just causing linker - // errors with the current LLVM implementation, so landing - // pads are disabled entirely for MSVC targets - if bcx.sess().no_landing_pads() || - bcx.sess().target.target.options.is_like_msvc { + if bcx.sess().no_landing_pads() { return false; } diff --git a/src/librustc_trans/trans/glue.rs b/src/librustc_trans/trans/glue.rs index 3bcdcd89c47f5..be8510c8e968b 100644 --- a/src/librustc_trans/trans/glue.rs +++ b/src/librustc_trans/trans/glue.rs @@ -22,8 +22,9 @@ use middle::lang_items::ExchangeFreeFnLangItem; use middle::subst; use middle::subst::{Subst, Substs}; use middle::ty::{self, Ty}; -use trans::adt; use trans::adt::GetDtorType; // for tcx.dtor_type() +use trans::adt; +use trans::attributes; use trans::base::*; use trans::build::*; use trans::callee; @@ -43,6 +44,7 @@ use trans::type_::Type; use arena::TypedArena; use libc::c_uint; use syntax::ast; +use syntax::attr::InlineAttr; pub fn trans_exchange_free_dyn<'blk, 'tcx>(cx: Block<'blk, 'tcx>, v: ValueRef, @@ -250,6 +252,25 @@ fn get_drop_glue_core<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>, update_linkage(ccx, llfn, None, OriginalTranslation); + // FIXME: Currently LLVM has a bug where if an SSA value is created in one + // landing pad and then used in another it will abort during + // compilation. The compiler never actually generates nested landing + // pads, but this often arises when destructors are inlined into + // other functions. To prevent this inlining from happening (and thus + // preventing the LLVM abort) we mark all drop glue as inline(never) + // on MSVC. + // + // For more information about the bug, see: + // + // https://llvm.org/bugs/show_bug.cgi?id=23884 + // + // This is clearly not the ideal solution to the problem (due to the + // perf hits), so this should be removed once the upstream bug is + // fixed. + if ccx.sess().target.target.options.is_like_msvc { + attributes::inline(llfn, InlineAttr::Never); + } + ccx.stats().n_glues_created.set(ccx.stats().n_glues_created.get() + 1); // All glue functions take values passed *by alias*; this is a // requirement since in many contexts glue is invoked indirectly and From 91c22b63020e15908859b11bfe777d65bc55aa98 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 29 Jun 2015 23:16:24 -0700 Subject: [PATCH 2/7] msvc: Lookup linker in windows registry This commit alters the compiler to no longer "just run link.exe" but instead probe the system's registry to find where the linker is located. The default library search path (normally found through LIB) is also found through the registry. This also brings us in line with the default behavior of Clang, and much of the logic of where to look for information is copied over from Clang as well. Finally, this commit removes the makefile logic for updating the environment variables for the compiler, except for stage0 where it's still necessary. The motivation for this change is rooted in two positions: * Not having to set up these environment variables is much less hassle both for the bootstrap and for running the compiler itself. This means that the compiler can be run outside of VS shells and be run inside of cmd.exe or a MSYS shell. * When dealing with cross compilation, there's not actually a set of environment variables that can be set for the compiler. This means, for example, if a Cargo compilation is targeting 32-bit from 64-bit you can't actually set up one set of environment variables. Having the compiler deal with the logic instead is generally much more convenient! --- mk/target.mk | 10 +- src/librustc_trans/back/link.rs | 16 +- src/librustc_trans/back/msvc/mod.rs | 239 +++++++++++++++++++++++ src/librustc_trans/back/msvc/registry.rs | 170 ++++++++++++++++ src/librustc_trans/back/write.rs | 17 +- src/librustc_trans/lib.rs | 2 +- 6 files changed, 430 insertions(+), 24 deletions(-) create mode 100644 src/librustc_trans/back/msvc/mod.rs create mode 100644 src/librustc_trans/back/msvc/registry.rs diff --git a/mk/target.mk b/mk/target.mk index c398950965f54..c2de9af39c764 100644 --- a/mk/target.mk +++ b/mk/target.mk @@ -249,11 +249,9 @@ endef $(foreach host,$(CFG_HOST), \ $(foreach target,$(CFG_TARGET), \ - $(foreach stage,$(STAGES), \ - $(foreach crate,$(CRATES), \ - $(eval $(call SETUP_LIB_MSVC_ENV_VARS,$(stage),$(target),$(host),$(crate))))))) + $(foreach crate,$(CRATES), \ + $(eval $(call SETUP_LIB_MSVC_ENV_VARS,0,$(target),$(host),$(crate)))))) $(foreach host,$(CFG_HOST), \ $(foreach target,$(CFG_TARGET), \ - $(foreach stage,$(STAGES), \ - $(foreach tool,$(TOOLS), \ - $(eval $(call SETUP_TOOL_MSVC_ENV_VARS,$(stage),$(target),$(host),$(tool))))))) + $(foreach tool,$(TOOLS), \ + $(eval $(call SETUP_TOOL_MSVC_ENV_VARS,0,$(target),$(host),$(tool)))))) diff --git a/src/librustc_trans/back/link.rs b/src/librustc_trans/back/link.rs index cf5feabcc57e2..e0495226d9061 100644 --- a/src/librustc_trans/back/link.rs +++ b/src/librustc_trans/back/link.rs @@ -12,6 +12,7 @@ use super::archive::{Archive, ArchiveBuilder, ArchiveConfig, METADATA_FILENAME}; use super::linker::{Linker, GnuLinker, MsvcLinker}; use super::rpath::RPathConfig; use super::rpath; +use super::msvc; use super::svh::Svh; use session::config; use session::config::NoDebugInfo; @@ -358,10 +359,14 @@ pub fn mangle_internal_name_by_path_and_seq(path: PathElems, flav: &str) -> Stri mangle(path.chain(Some(gensym_name(flav))), None) } -pub fn get_cc_prog(sess: &Session) -> String { - match sess.opts.cg.linker { - Some(ref linker) => return linker.to_string(), - None => sess.target.target.options.linker.clone(), +pub fn get_linker(sess: &Session) -> (String, Command) { + if let Some(ref linker) = sess.opts.cg.linker { + (linker.clone(), Command::new(linker)) + } else if sess.target.target.options.is_like_msvc { + ("link.exe".to_string(), msvc::link_exe_cmd(sess)) + } else { + (sess.target.target.options.linker.clone(), + Command::new(&sess.target.target.options.linker)) } } @@ -807,8 +812,7 @@ fn link_natively(sess: &Session, trans: &CrateTranslation, dylib: bool, let tmpdir = TempDir::new("rustc").ok().expect("needs a temp dir"); // The invocations of cc share some flags across platforms - let pname = get_cc_prog(sess); - let mut cmd = Command::new(&pname); + let (pname, mut cmd) = get_linker(sess); cmd.env("PATH", command_path(sess)); let root = sess.target_filesearch(PathKind::Native).get_lib_path(); diff --git a/src/librustc_trans/back/msvc/mod.rs b/src/librustc_trans/back/msvc/mod.rs new file mode 100644 index 0000000000000..0077e7eed52d0 --- /dev/null +++ b/src/librustc_trans/back/msvc/mod.rs @@ -0,0 +1,239 @@ +// Copyright 2015 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! MSVC-specific logic for linkers and such. +//! +//! This module contains a cross-platform interface but has a blank unix +//! implementation. The Windows implementation builds on top of Windows native +//! libraries (reading registry keys), so it otherwise wouldn't link on unix. +//! +//! Note that we don't have much special logic for finding the system linker on +//! any other platforms, so it may seem a little odd to single out MSVC to have +//! a good deal of code just to find the linker. Unlike Unix systems, however, +//! the MSVC linker is not in the system PATH by default. It also additionally +//! needs a few environment variables or command line flags to be able to link +//! against system libraries. +//! +//! In order to have a nice smooth experience on Windows, the logic in this file +//! is here to find the MSVC linker and set it up in the default configuration +//! one would need to set up anyway. This means that the Rust compiler can be +//! run not only in the developer shells of MSVC but also the standard cmd.exe +//! shell or MSYS shells. +//! +//! As a high-level note, all logic in this module for looking up various +//! paths/files is copied over from Clang in its MSVCToolChain.cpp file, but +//! comments can also be found below leading through the various code paths. + +use std::process::Command; +use session::Session; + +#[cfg(windows)] +mod registry; + +#[cfg(windows)] +pub fn link_exe_cmd(sess: &Session) -> Command { + use std::env; + use std::ffi::OsString; + use std::fs; + use std::path::PathBuf; + use self::registry::{RegistryKey, LOCAL_MACHINE}; + + // When finding the link.exe binary the 32-bit version is at the top level + // but the versions to cross to other architectures are stored in + // sub-folders. Unknown architectures also just bail out early to return the + // standard `link.exe` command. + let extra = match &sess.target.target.arch[..] { + "x86" => "", + "x86_64" => "amd64", + "arm" => "arm", + _ => return Command::new("link.exe"), + }; + + let vs_install_dir = get_vs_install_dir(); + + // First up, we need to find the `link.exe` binary itself, and there's a few + // locations that we can look. First up is the standard VCINSTALLDIR + // environment variable which is normally set by the vcvarsall.bat file. If + // an environment is set up manually by whomever's driving the compiler then + // we shouldn't muck with that decision and should instead respect that. + // + // Next up is looking in PATH itself. Here we look for `cl.exe` and then + // assume that `link.exe` is next to it if we find it. Note that we look for + // `cl.exe` because MinGW ships /usr/bin/link.exe which is normally found in + // PATH but we're not interested in finding that. + // + // Finally we read the Windows registry to discover the VS install root. + // From here we probe for `link.exe` just to make sure that it exists. + let mut cmd = env::var_os("VCINSTALLDIR").and_then(|dir| { + let mut p = PathBuf::from(dir); + p.push("bin"); + p.push(extra); + p.push("link.exe"); + if fs::metadata(&p).is_ok() {Some(p)} else {None} + }).or_else(|| { + env::var_os("PATH").and_then(|path| { + env::split_paths(&path).find(|path| { + fs::metadata(&path.join("cl.exe")).is_ok() + }).map(|p| { + p.join("link.exe") + }) + }) + }).or_else(|| { + vs_install_dir.as_ref().and_then(|p| { + let mut p = p.join("VC/bin"); + p.push(extra); + p.push("link.exe"); + if fs::metadata(&p).is_ok() {Some(p)} else {None} + }) + }).map(|linker| { + Command::new(linker) + }).unwrap_or_else(|| { + Command::new("link.exe") + }); + + // The MSVC linker uses the LIB environment variable as the default lookup + // path for libraries. This environment variable is normally set up by the + // VS shells, so we only want to start adding our own pieces if it's not + // set. + // + // If we're adding our own pieces, then we need to add two primary + // directories to the default search path for the linker. The first is in + // the VS install direcotry and the next is the Windows SDK directory. + if env::var_os("LIB").is_none() { + if let Some(mut vs_install_dir) = vs_install_dir { + vs_install_dir.push("VC/lib"); + vs_install_dir.push(extra); + let mut arg = OsString::from("/LIBPATH:"); + arg.push(&vs_install_dir); + cmd.arg(arg); + } + if let Some(path) = get_windows_sdk_lib_path(sess) { + let mut arg = OsString::from("/LIBPATH:"); + arg.push(&path); + cmd.arg(arg); + } + } + + return cmd; + + // When looking for the Visual Studio installation directory we look in a + // number of locations in varying degrees of precedence: + // + // 1. The Visual Studio registry keys + // 2. The Visual Studio Express registry keys + // 3. A number of somewhat standard environment variables + // + // If we find a hit from any of these keys then we strip off the IDE/Tools + // folders which are typically found at the end. + // + // As a final note, when we take a look at the registry keys they're + // typically found underneath the version of what's installed, but we don't + // quite know what's installed. As a result we probe all sub-keys of the two + // keys we're looking at to find out the maximum version of what's installed + // and we use that root directory. + fn get_vs_install_dir() -> Option { + LOCAL_MACHINE.open(r"SOFTWARE\Microsoft\VisualStudio".as_ref()).or_else(|_| { + LOCAL_MACHINE.open(r"SOFTWARE\Microsoft\VCExpress".as_ref()) + }).ok().and_then(|key| { + max_version(&key).and_then(|(_vers, key)| { + key.query_str("InstallDir").ok() + }) + }).or_else(|| { + env::var_os("VS120COMNTOOLS") + }).or_else(|| { + env::var_os("VS100COMNTOOLS") + }).or_else(|| { + env::var_os("VS90COMNTOOLS") + }).or_else(|| { + env::var_os("VS80COMNTOOLS") + }).map(PathBuf::from).and_then(|mut dir| { + if dir.ends_with("Common7/IDE") || dir.ends_with("Common7/Tools") { + dir.pop(); + dir.pop(); + Some(dir) + } else { + None + } + }) + } + + // Given a registry key, look at all the sub keys and find the one which has + // the maximal numeric value. + // + // Returns the name of the maximal key as well as the opened maximal key. + fn max_version(key: &RegistryKey) -> Option<(OsString, RegistryKey)> { + let mut max_vers = 0; + let mut max_key = None; + for subkey in key.iter().filter_map(|k| k.ok()) { + let val = subkey.to_str().and_then(|s| { + s.trim_left_matches("v").replace(".", "").parse().ok() + }); + let val = match val { + Some(s) => s, + None => continue, + }; + if val > max_vers { + if let Ok(k) = key.open(&subkey) { + max_vers = val; + max_key = Some((subkey, k)); + } + } + } + return max_key + } + + fn get_windows_sdk_lib_path(sess: &Session) -> Option { + let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows"; + let key = LOCAL_MACHINE.open(key.as_ref()); + let (n, k) = match key.ok().as_ref().and_then(max_version) { + Some(p) => p, + None => return None, + }; + let mut parts = n.to_str().unwrap().trim_left_matches("v").splitn(2, "."); + let major = parts.next().unwrap().parse::().unwrap(); + let _minor = parts.next().unwrap().parse::().unwrap(); + let path = match k.query_str("InstallationFolder") { + Ok(p) => PathBuf::from(p).join("Lib"), + Err(..) => return None, + }; + if major <= 7 { + // In Windows SDK 7.x, x86 libraries are directly in the Lib folder, + // x64 libraries are inside, and it's not necessary to link agains + // the SDK 7.x when targeting ARM or other architectures. + let x86 = match &sess.target.target.arch[..] { + "x86" => true, + "x86_64" => false, + _ => return None, + }; + Some(if x86 {path} else {path.join("x64")}) + } else { + // Windows SDK 8.x installes libraries in a folder whose names + // depend on the version of the OS you're targeting. By default + // choose the newest, which usually corresponds to the version of + // the OS you've installed the SDK on. + let extra = match &sess.target.target.arch[..] { + "x86" => "x86", + "x86_64" => "x64", + "arm" => "arm", + _ => return None, + }; + ["winv6.3", "win8", "win7"].iter().map(|p| path.join(p)).find(|part| { + fs::metadata(part).is_ok() + }).map(|path| { + path.join("um").join(extra) + }) + } + } +} + +#[cfg(not(windows))] +pub fn link_exe_cmd(_sess: &Session) -> Command { + Command::new("link.exe") +} diff --git a/src/librustc_trans/back/msvc/registry.rs b/src/librustc_trans/back/msvc/registry.rs new file mode 100644 index 0000000000000..97fd7f99d197e --- /dev/null +++ b/src/librustc_trans/back/msvc/registry.rs @@ -0,0 +1,170 @@ +// Copyright 2015 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::io; +use std::ffi::{OsString, OsStr}; +use std::os::windows::prelude::*; +use std::ops::RangeFrom; +use libc::{DWORD, LPCWSTR, LONG, LPDWORD, LPBYTE, ERROR_SUCCESS}; + +const HKEY_LOCAL_MACHINE: HKEY = 0x80000002 as HKEY; +const KEY_WOW64_32KEY: REGSAM = 0x0200; +const KEY_READ: REGSAM = (STANDARD_RIGTS_READ | KEY_QUERY_VALUE | + KEY_ENUMERATE_SUB_KEYS | KEY_NOTIFY) & !SYNCHRONIZE; +const STANDARD_RIGTS_READ: REGSAM = READ_CONTROL; +const READ_CONTROL: REGSAM = 0x00020000; +const KEY_QUERY_VALUE: REGSAM = 0x0001; +const KEY_ENUMERATE_SUB_KEYS: REGSAM = 0x0008; +const KEY_NOTIFY: REGSAM = 0x0010; +const SYNCHRONIZE: REGSAM = 0x00100000; +const REG_SZ: DWORD = 1; +const ERROR_NO_MORE_ITEMS: DWORD = 259; + +enum __HKEY__ {} +pub type HKEY = *mut __HKEY__; +pub type PHKEY = *mut HKEY; +pub type REGSAM = DWORD; +pub type LPWSTR = *mut u16; +pub type PFILETIME = *mut (); + +#[link(name = "advapi32")] +extern "system" { + fn RegOpenKeyExW(hKey: HKEY, + lpSubKey: LPCWSTR, + ulOptions: DWORD, + samDesired: REGSAM, + phkResult: PHKEY) -> LONG; + fn RegQueryValueExW(hKey: HKEY, + lpValueName: LPCWSTR, + lpReserved: LPDWORD, + lpType: LPDWORD, + lpData: LPBYTE, + lpcbData: LPDWORD) -> LONG; + fn RegEnumKeyExW(hKey: HKEY, + dwIndex: DWORD, + lpName: LPWSTR, + lpcName: LPDWORD, + lpReserved: LPDWORD, + lpClass: LPWSTR, + lpcClass: LPDWORD, + lpftLastWriteTime: PFILETIME) -> LONG; + fn RegCloseKey(hKey: HKEY) -> LONG; +} + +pub struct RegistryKey(Repr); + +struct OwnedKey(HKEY); + +enum Repr { + Const(HKEY), + Owned(OwnedKey), +} + +pub struct Iter<'a> { + idx: RangeFrom, + key: &'a RegistryKey, +} + +unsafe impl Sync for RegistryKey {} +unsafe impl Send for RegistryKey {} + +pub static LOCAL_MACHINE: RegistryKey = RegistryKey(Repr::Const(HKEY_LOCAL_MACHINE)); + +impl RegistryKey { + fn raw(&self) -> HKEY { + match self.0 { + Repr::Const(val) => val, + Repr::Owned(ref val) => val.0, + } + } + + pub fn open(&self, key: &OsStr) -> io::Result { + let key = key.encode_wide().chain(Some(0)).collect::>(); + let mut ret = 0 as *mut _; + let err = unsafe { + RegOpenKeyExW(self.raw(), key.as_ptr(), 0, + KEY_READ | KEY_WOW64_32KEY, &mut ret) + }; + if err == ERROR_SUCCESS { + Ok(RegistryKey(Repr::Owned(OwnedKey(ret)))) + } else { + Err(io::Error::from_raw_os_error(err as i32)) + } + } + + pub fn iter(&self) -> Iter { + Iter { idx: 0.., key: self } + } + + pub fn query_str(&self, name: &str) -> io::Result { + let name: &OsStr = name.as_ref(); + let name = name.encode_wide().chain(Some(0)).collect::>(); + let mut len = 0; + let mut kind = 0; + unsafe { + let err = RegQueryValueExW(self.raw(), name.as_ptr(), 0 as *mut _, + &mut kind, 0 as *mut _, &mut len); + if err != ERROR_SUCCESS { + return Err(io::Error::from_raw_os_error(err as i32)) + } + if kind != REG_SZ { + return Err(io::Error::new(io::ErrorKind::Other, + "registry key wasn't a string")) + } + + // The length here is the length in bytes, but we're using wide + // characters so we need to be sure to halve it for the capacity + // passed in. + let mut v = Vec::with_capacity(len as usize / 2); + let err = RegQueryValueExW(self.raw(), name.as_ptr(), 0 as *mut _, + 0 as *mut _, v.as_mut_ptr() as *mut _, + &mut len); + if err != ERROR_SUCCESS { + return Err(io::Error::from_raw_os_error(err as i32)) + } + v.set_len(len as usize / 2); + + // Some registry keys may have a terminating nul character, but + // we're not interested in that, so chop it off if it's there. + if v[v.len() - 1] == 0 { + v.pop(); + } + Ok(OsString::from_wide(&v)) + } + } +} + +impl Drop for OwnedKey { + fn drop(&mut self) { + unsafe { RegCloseKey(self.0); } + } +} + +impl<'a> Iterator for Iter<'a> { + type Item = io::Result; + + fn next(&mut self) -> Option> { + self.idx.next().and_then(|i| unsafe { + let mut v = Vec::with_capacity(256); + let mut len = v.capacity() as DWORD; + let ret = RegEnumKeyExW(self.key.raw(), i, v.as_mut_ptr(), &mut len, + 0 as *mut _, 0 as *mut _, 0 as *mut _, + 0 as *mut _); + if ret == ERROR_NO_MORE_ITEMS as LONG { + None + } else if ret != ERROR_SUCCESS { + Some(Err(io::Error::from_raw_os_error(ret as i32))) + } else { + v.set_len(len as usize); + Some(Ok(OsString::from_wide(&v))) + } + }) + } +} diff --git a/src/librustc_trans/back/write.rs b/src/librustc_trans/back/write.rs index 267f0b6d95329..90ddba4e09c58 100644 --- a/src/librustc_trans/back/write.rs +++ b/src/librustc_trans/back/write.rs @@ -9,7 +9,7 @@ // except according to those terms. use back::lto; -use back::link::{get_cc_prog, remove}; +use back::link::{get_linker, remove}; use session::config::{OutputFilenames, Passes, SomePasses, AllPasses}; use session::Session; use session::config; @@ -27,7 +27,7 @@ use std::ffi::{CStr, CString}; use std::fs; use std::mem; use std::path::Path; -use std::process::{Command, Stdio}; +use std::process::Stdio; use std::ptr; use std::str; use std::sync::{Arc, Mutex}; @@ -737,8 +737,7 @@ pub fn run_passes(sess: &Session, None }; - let pname = get_cc_prog(sess); - let mut cmd = Command::new(&pname[..]); + let (pname, mut cmd) = get_linker(sess); cmd.args(&sess.target.target.options.pre_link_args); cmd.arg("-nostdlib"); @@ -767,8 +766,7 @@ pub fn run_passes(sess: &Session, }, Err(e) => { sess.err(&format!("could not exec the linker `{}`: {}", - pname, - e)); + pname, e)); sess.abort_if_errors(); }, } @@ -986,8 +984,7 @@ fn run_work_multithreaded(sess: &Session, } pub fn run_assembler(sess: &Session, outputs: &OutputFilenames) { - let pname = get_cc_prog(sess); - let mut cmd = Command::new(&pname[..]); + let (pname, mut cmd) = get_linker(sess); cmd.arg("-c").arg("-o").arg(&outputs.path(config::OutputTypeObject)) .arg(&outputs.temp_path(config::OutputTypeAssembly)); @@ -1007,9 +1004,7 @@ pub fn run_assembler(sess: &Session, outputs: &OutputFilenames) { } }, Err(e) => { - sess.err(&format!("could not exec the linker `{}`: {}", - pname, - e)); + sess.err(&format!("could not exec the linker `{}`: {}", pname, e)); sess.abort_if_errors(); } } diff --git a/src/librustc_trans/lib.rs b/src/librustc_trans/lib.rs index dc692b0e765dd..66881edaa8b86 100644 --- a/src/librustc_trans/lib.rs +++ b/src/librustc_trans/lib.rs @@ -83,7 +83,7 @@ pub mod back { pub mod link; pub mod lto; pub mod write; - + pub mod msvc; } pub mod trans; From ae0eb675db97a57a63f941b29754d8e32040ecc9 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 29 Jun 2015 23:28:39 -0700 Subject: [PATCH 3/7] msvc: Fix TLS destructors Just like the original article our Windows TLS support is based on predicted, this symbol must be linked in on MSVC to pull in the necessary support for TLS variables. This commit fixes a number of unit tests which require that TLS destructors are run. --- src/libstd/lib.rs | 1 + src/libstd/sys/windows/thread_local.rs | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/libstd/lib.rs b/src/libstd/lib.rs index 2117291817754..73e45619774d7 100644 --- a/src/libstd/lib.rs +++ b/src/libstd/lib.rs @@ -151,6 +151,7 @@ #![cfg_attr(windows, feature(str_utf16))] #![cfg_attr(test, feature(float_from_str_radix, range_inclusive, float_extras))] #![cfg_attr(test, feature(test, rustc_private, float_consts))] +#![cfg_attr(target_env = "msvc", feature(link_args))] // Don't link to std. We are std. #![no_std] diff --git a/src/libstd/sys/windows/thread_local.rs b/src/libstd/sys/windows/thread_local.rs index 5002de559884d..a2dbb0f834243 100644 --- a/src/libstd/sys/windows/thread_local.rs +++ b/src/libstd/sys/windows/thread_local.rs @@ -221,8 +221,8 @@ unsafe fn unregister_dtor(key: Key) -> bool { // // # The article mentions crazy stuff about "/INCLUDE"? // -// It sure does! This seems to work for now, so maybe we'll just run into -// that if we start linking with msvc? +// It sure does! We include it below for MSVC targets, but it look like for GNU +// targets we don't require it. #[link_section = ".CRT$XLB"] #[linkage = "external"] @@ -231,6 +231,10 @@ pub static p_thread_callback: unsafe extern "system" fn(LPVOID, DWORD, LPVOID) = on_tls_callback; +#[cfg(target_env = "msvc")] +#[link_args = "/INCLUDE:_tls_used"] +extern {} + #[allow(warnings)] unsafe extern "system" fn on_tls_callback(h: LPVOID, dwReason: DWORD, From 407fb293db8ef375ab78500a62d9320c24c77a18 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 29 Jun 2015 23:30:12 -0700 Subject: [PATCH 4/7] msvc: Ignore extern-pass-empty on MSVC The MSVC compiler doesn't like empty structs, so this test won't link on MSVC, so it's ignored. --- src/test/run-pass/extern-pass-empty.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/run-pass/extern-pass-empty.rs b/src/test/run-pass/extern-pass-empty.rs index 17b0bb580fce2..21948d2e5ad23 100644 --- a/src/test/run-pass/extern-pass-empty.rs +++ b/src/test/run-pass/extern-pass-empty.rs @@ -11,6 +11,7 @@ // Test a foreign function that accepts empty struct. // pretty-expanded FIXME #23616 +// ignore-msvc struct TwoU8s { one: u8, From 18c39e126a833f66f58fe6cbf7b97ca4262f1c90 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 29 Jun 2015 23:30:49 -0700 Subject: [PATCH 5/7] msvc: Fix the link name for the lgamma The function is apparently just called lgamma on MSVC --- src/test/run-pass/issue-2214.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/run-pass/issue-2214.rs b/src/test/run-pass/issue-2214.rs index 775cfb0ee4219..316e379e664ae 100644 --- a/src/test/run-pass/issue-2214.rs +++ b/src/test/run-pass/issue-2214.rs @@ -37,7 +37,7 @@ mod m { #[link_name="lgamma_r"] pub fn lgamma(n: c_double, sign: &mut c_int) -> c_double; #[cfg(windows)] - #[link_name="__lgamma_r"] + #[link_name="lgamma"] pub fn lgamma(n: c_double, sign: &mut c_int) -> c_double; } } From 83ee47b054deb5939be20d7d6ce03ad33d005424 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 30 Jun 2015 21:55:00 -0700 Subject: [PATCH 6/7] windows: Don't link rust_builtin This library has no shims which are actually needed on Windows now, so translate that last easy one into Rust and then don't link it at all on Windows. --- src/libstd/rt/util.rs | 15 +++++++++++---- src/libstd/rtdeps.rs | 4 ++-- src/libtest/lib.rs | 20 +++++++++++++++++--- src/rt/rust_builtin.c | 36 +++++------------------------------- 4 files changed, 35 insertions(+), 40 deletions(-) diff --git a/src/libstd/rt/util.rs b/src/libstd/rt/util.rs index 04f36d99c8eb5..031fda089c84c 100644 --- a/src/libstd/rt/util.rs +++ b/src/libstd/rt/util.rs @@ -13,7 +13,6 @@ use io::prelude::*; use env; use fmt; use intrinsics; -use libc::uintptr_t; use sync::atomic::{self, Ordering}; use sys::stdio::Stderr; @@ -22,10 +21,18 @@ use sys::stdio::Stderr; /// can't run correctly un-altered. Valgrind is there to help /// you notice weirdness in normal, un-doctored code paths! pub fn running_on_valgrind() -> bool { - extern { - fn rust_running_on_valgrind() -> uintptr_t; + return on_valgrind(); + #[cfg(windows)] + fn on_valgrind() -> bool { false } + + #[cfg(unix)] + fn on_valgrind() -> bool { + use libc::uintptr_t; + extern { + fn rust_running_on_valgrind() -> uintptr_t; + } + unsafe { rust_running_on_valgrind() != 0 } } - unsafe { rust_running_on_valgrind() != 0 } } /// Valgrind has a fixed-sized array (size around 2000) of segment descriptors diff --git a/src/libstd/rtdeps.rs b/src/libstd/rtdeps.rs index be674c83e2213..b7839423e5225 100644 --- a/src/libstd/rtdeps.rs +++ b/src/libstd/rtdeps.rs @@ -12,8 +12,8 @@ //! the standard library This varies per-platform, but these libraries are //! necessary for running libstd. -// All platforms need to link to rustrt -#[cfg(not(test))] +// A few small shims in C that haven't been translated to Rust yet +#[cfg(all(not(test), not(windows)))] #[link(name = "rust_builtin", kind = "static")] extern {} diff --git a/src/libtest/lib.rs b/src/libtest/lib.rs index 0a3c350086cdd..724c0b2a8927f 100644 --- a/src/libtest/lib.rs +++ b/src/libtest/lib.rs @@ -872,7 +872,7 @@ fn run_tests(opts: &TestOpts, #[allow(deprecated)] fn get_concurrency() -> usize { - match env::var("RUST_TEST_THREADS") { + return match env::var("RUST_TEST_THREADS") { Ok(s) => { let opt_n: Option = s.parse().ok(); match opt_n { @@ -884,10 +884,24 @@ fn get_concurrency() -> usize { if std::rt::util::limit_thread_creation_due_to_osx_and_valgrind() { 1 } else { - extern { fn rust_get_num_cpus() -> libc::uintptr_t; } - unsafe { rust_get_num_cpus() as usize } + num_cpus() } } + }; + + #[cfg(windows)] + fn num_cpus() -> usize { + unsafe { + let mut sysinfo = std::mem::zeroed(); + libc::GetSystemInfo(&mut sysinfo); + sysinfo.dwNumberOfProcessors as usize + } + } + + #[cfg(unix)] + fn num_cpus() -> usize { + extern { fn rust_get_num_cpus() -> libc::uintptr_t; } + unsafe { rust_get_num_cpus() as usize } } } diff --git a/src/rt/rust_builtin.c b/src/rt/rust_builtin.c index 1a2917a1dd67f..76a3debef59a4 100644 --- a/src/rt/rust_builtin.c +++ b/src/rt/rust_builtin.c @@ -8,6 +8,8 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +#if !defined(_WIN32) + #include #include #include @@ -15,7 +17,6 @@ #include -#if !defined(_WIN32) #include #include #include @@ -23,12 +24,6 @@ #include #include #include -#else -#include -#include -#include -#include -#endif #ifdef __APPLE__ #include @@ -41,17 +36,8 @@ /* Foreign builtins. */ //include valgrind.h after stdint.h so that uintptr_t is defined for msys2 w64 -#ifndef _WIN32 #include "valgrind/valgrind.h" -#endif - -#if defined(_MSC_VER) -# define RUST_BUILTIN_API __declspec(dllexport) -#else -# define RUST_BUILTIN_API -#endif -#ifndef _WIN32 char* rust_list_dir_val(struct dirent* entry_ptr) { return entry_ptr->d_name; @@ -92,17 +78,8 @@ int rust_dirent_t_size() { return sizeof(struct dirent); } -#endif - -#if defined(_WIN32) -int -get_num_cpus() { - SYSTEM_INFO sysinfo; - GetSystemInfo(&sysinfo); - return (int) sysinfo.dwNumberOfProcessors; -} -#elif defined(__BSD__) +#if defined(__BSD__) int get_num_cpus() { /* swiped from http://stackoverflow.com/questions/150355/ @@ -136,7 +113,6 @@ get_num_cpus() { } #endif -RUST_BUILTIN_API uintptr_t rust_get_num_cpus() { return get_num_cpus(); @@ -144,11 +120,7 @@ rust_get_num_cpus() { uintptr_t rust_running_on_valgrind() { -#ifdef _WIN32 - return 0; -#else return RUNNING_ON_VALGRIND; -#endif } #if defined(__DragonFly__) @@ -484,6 +456,8 @@ const char * rust_current_exe() { #endif +#endif // !defined(_WIN32) + // // Local Variables: // mode: C++ From 3e26e56a79ae33dfc8f2f4d0123b5080fd0a7853 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 30 Jun 2015 10:28:24 -0700 Subject: [PATCH 7/7] rustc_trans: Disable landing pads on 32-bit MSVC This is currently quite buggy in LLVM from what I can tell, so just disable it entirely. This commit also adds preliminary support, however, to actually target 32-bit MSVC by making sure the `rust_try_msvc_32.ll` file exists and wiring up exceptions to `_except_handler3` instead of `__C_specific_handler` (which doesn't exist on 32-bit). --- src/librustc_trans/trans/base.rs | 9 ++++++- src/librustc_trans/trans/cleanup.rs | 12 +++++---- src/rt/rust_try_msvc_32.ll | 42 +++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 6 deletions(-) create mode 100644 src/rt/rust_try_msvc_32.ll diff --git a/src/librustc_trans/trans/base.rs b/src/librustc_trans/trans/base.rs index b829a9c6ec449..079cd9c3757e5 100644 --- a/src/librustc_trans/trans/base.rs +++ b/src/librustc_trans/trans/base.rs @@ -752,7 +752,14 @@ pub fn invoke<'blk, 'tcx>(bcx: Block<'blk, 'tcx>, pub fn need_invoke(bcx: Block) -> bool { if bcx.sess().no_landing_pads() { - return false; + return false + } + + // Currently 32-bit MSVC unwinding is not super well implemented in LLVM, so + // we avoid it entirely. + if bcx.sess().target.target.options.is_like_msvc && + bcx.sess().target.target.arch == "x86" { + return false } // Avoid using invoke if we are already inside a landing pad. diff --git a/src/librustc_trans/trans/cleanup.rs b/src/librustc_trans/trans/cleanup.rs index 588e4cea5048b..1891320313a85 100644 --- a/src/librustc_trans/trans/cleanup.rs +++ b/src/librustc_trans/trans/cleanup.rs @@ -851,8 +851,8 @@ impl<'blk, 'tcx> CleanupHelperMethods<'blk, 'tcx> for FunctionContext<'blk, 'tcx // an "exception", but for MSVC we want to force SEH. This means that we // can't actually have the personality function be our standard // `rust_eh_personality` function, but rather we wired it up to the - // CRT's custom `__C_specific_handler` personality funciton, which - // forces LLVM to consider landing pads as "landing pads for SEH". + // CRT's custom personality function, which forces LLVM to consider + // landing pads as "landing pads for SEH". let target = &self.ccx.sess().target.target; let llpersonality = match pad_bcx.tcx().lang_items.eh_personality() { Some(def_id) if !target.options.is_like_msvc => { @@ -864,10 +864,12 @@ impl<'blk, 'tcx> CleanupHelperMethods<'blk, 'tcx> for FunctionContext<'blk, 'tcx match *personality { Some(llpersonality) => llpersonality, None => { - let name = if target.options.is_like_msvc { - "__C_specific_handler" - } else { + let name = if !target.options.is_like_msvc { "rust_eh_personality" + } else if target.arch == "x86" { + "_except_handler3" + } else { + "__C_specific_handler" }; let fty = Type::variadic_func(&[], &Type::i32(self.ccx)); let f = declare::declare_cfn(self.ccx, name, fty, diff --git a/src/rt/rust_try_msvc_32.ll b/src/rt/rust_try_msvc_32.ll new file mode 100644 index 0000000000000..bdee53b136e10 --- /dev/null +++ b/src/rt/rust_try_msvc_32.ll @@ -0,0 +1,42 @@ +; Copyright 2015 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 or the MIT license +; , at your +; option. This file may not be copied, modified, or distributed +; except according to those terms. + +; For more comments about what's going on here see rust_try_msvc_64.ll. The only +; difference between that and this file is the personality function used as it's +; different for 32-bit MSVC than it is for 64-bit. + +define i8* @rust_try(void (i8*)* %f, i8* %env) + personality i8* bitcast (i32 (...)* @_except_handler3 to i8*) +{ + invoke void %f(i8* %env) + to label %normal + unwind label %catch + +normal: + ret i8* null +catch: + %vals = landingpad { i8*, i32 } + catch i8* bitcast (i32 (i8*, i8*)* @__rust_try_filter to i8*) + %ehptr = extractvalue { i8*, i32 } %vals, 0 + %sel = extractvalue { i8*, i32 } %vals, 1 + %filter_sel = call i32 @llvm.eh.typeid.for(i8* bitcast (i32 (i8*, i8*)* @__rust_try_filter to i8*)) + %is_filter = icmp eq i32 %sel, %filter_sel + br i1 %is_filter, label %catch-return, label %catch-resume + +catch-return: + ret i8* %ehptr + +catch-resume: + resume { i8*, i32 } %vals +} + +declare i32 @_except_handler3(...) +declare i32 @__rust_try_filter(i8*, i8*) +declare i32 @llvm.eh.typeid.for(i8*) readnone nounwind