From 0e945411f2f54fdb3404b382844b9818ecc10b89 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 19 Mar 2019 13:06:37 -0700 Subject: [PATCH] rustc: Allow using `clang` for wasm32 targets This commit adds support code for using `clang` directly to link the wasm32-unknown-unknown target. Currently the target is only really configured to link with LLD directly, but this ensures that `clang` can be configured as well. While not immediately useful in the near term it's likely that more wasm32 targets will pop up over time with Clang's new native support for WebAssembly in the 8.0.0 release. Getting support into rustc early should make it easier to experiment with these targets and try out various changes here and there. --- src/librustc_codegen_ssa/back/linker.rs | 77 +++-------- src/librustc_target/spec/mod.rs | 1 + src/librustc_target/spec/wasm32_base.rs | 123 ++++++++++++++++++ .../spec/wasm32_unknown_unknown.rs | 78 ++++------- 4 files changed, 173 insertions(+), 106 deletions(-) create mode 100644 src/librustc_target/spec/wasm32_base.rs diff --git a/src/librustc_codegen_ssa/back/linker.rs b/src/librustc_codegen_ssa/back/linker.rs index e6470dbb61c..0bdac1a51a1 100644 --- a/src/librustc_codegen_ssa/back/linker.rs +++ b/src/librustc_codegen_ssa/back/linker.rs @@ -160,7 +160,16 @@ fn linker_arg(&mut self, arg: S) -> &mut Self } fn takes_hints(&self) -> bool { - !self.sess.target.target.options.is_like_osx + // Really this function only returns true if the underlying linker + // configured for a compiler is binutils `ld.bfd` and `ld.gold`. We + // don't really have a foolproof way to detect that, so rule out some + // platforms where currently this is guaranteed to *not* be the case: + // + // * On OSX they have their own linker, not binutils' + // * For WebAssembly the only functional linker is LLD, which doesn't + // support hint flags + !self.sess.target.target.options.is_like_osx && + self.sess.target.target.arch != "wasm32" } // Some platforms take hints about whether a library is static or dynamic. @@ -375,6 +384,13 @@ fn export_symbols(&mut self, tmpdir: &Path, crate_type: CrateType) { return } + // Symbol visibility takes care of this for the WebAssembly. + // Additionally the only known linker, LLD, doesn't support the script + // arguments just yet + if self.sess.target.target.arch == "wasm32" { + return; + } + let mut arg = OsString::new(); let path = tmpdir.join("list"); @@ -441,13 +457,13 @@ fn finalize(&mut self) -> Command { } fn group_start(&mut self) { - if !self.sess.target.target.options.is_like_osx { + if self.takes_hints() { self.linker_arg("--start-group"); } } fn group_end(&mut self) { - if !self.sess.target.target.options.is_like_osx { + if self.takes_hints() { self.linker_arg("--end-group"); } } @@ -874,59 +890,7 @@ pub struct WasmLd<'a> { } impl<'a> WasmLd<'a> { - fn new(mut cmd: Command, sess: &'a Session, info: &'a LinkerInfo) -> WasmLd<'a> { - // There have been reports in the wild (rustwasm/wasm-bindgen#119) of - // using threads causing weird hangs and bugs. Disable it entirely as - // this isn't yet the bottleneck of compilation at all anyway. - cmd.arg("--no-threads"); - - // By default LLD only gives us one page of stack (64k) which is a - // little small. Default to a larger stack closer to other PC platforms - // (1MB) and users can always inject their own link-args to override this. - cmd.arg("-z").arg("stack-size=1048576"); - - // By default LLD's memory layout is: - // - // 1. First, a blank page - // 2. Next, all static data - // 3. Finally, the main stack (which grows down) - // - // This has the unfortunate consequence that on stack overflows you - // corrupt static data and can cause some exceedingly weird bugs. To - // help detect this a little sooner we instead request that the stack is - // placed before static data. - // - // This means that we'll generate slightly larger binaries as references - // to static data will take more bytes in the ULEB128 encoding, but - // stack overflow will be guaranteed to trap as it underflows instead of - // corrupting static data. - cmd.arg("--stack-first"); - - // FIXME we probably shouldn't pass this but instead pass an explicit - // whitelist of symbols we'll allow to be undefined. Unfortunately - // though we can't handle symbols like `log10` that LLVM injects at a - // super late date without actually parsing object files. For now let's - // stick to this and hopefully fix it before stabilization happens. - cmd.arg("--allow-undefined"); - - // For now we just never have an entry symbol - cmd.arg("--no-entry"); - - // Rust code should never have warnings, and warnings are often - // indicative of bugs, let's prevent them. - cmd.arg("--fatal-warnings"); - - // The symbol visibility story is a bit in flux right now with LLD. - // It's... not entirely clear to me what's going on, but this looks to - // make everything work when `export_symbols` isn't otherwise called for - // things like executables. - cmd.arg("--export-dynamic"); - - // LLD only implements C++-like demangling, which doesn't match our own - // mangling scheme. Tell LLD to not demangle anything and leave it up to - // us to demangle these symbols later. - cmd.arg("--no-demangle"); - + fn new(cmd: Command, sess: &'a Session, info: &'a LinkerInfo) -> WasmLd<'a> { WasmLd { cmd, sess, info } } } @@ -1022,6 +986,7 @@ fn no_default_libraries(&mut self) { } fn build_dylib(&mut self, _out_filename: &Path) { + self.cmd.arg("--no-entry"); } fn export_symbols(&mut self, _tmpdir: &Path, crate_type: CrateType) { diff --git a/src/librustc_target/spec/mod.rs b/src/librustc_target/spec/mod.rs index fdb1db645c3..401b81ee987 100644 --- a/src/librustc_target/spec/mod.rs +++ b/src/librustc_target/spec/mod.rs @@ -66,6 +66,7 @@ mod fuchsia_base; mod redox_base; mod riscv_base; +mod wasm32_base; #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Hash, RustcEncodable, RustcDecodable)] diff --git a/src/librustc_target/spec/wasm32_base.rs b/src/librustc_target/spec/wasm32_base.rs new file mode 100644 index 00000000000..c7e75b4fa09 --- /dev/null +++ b/src/librustc_target/spec/wasm32_base.rs @@ -0,0 +1,123 @@ +use std::collections::BTreeMap; +use super::{LldFlavor, TargetOptions, PanicStrategy, LinkerFlavor}; + +pub fn options() -> TargetOptions { + let mut lld_args = Vec::new(); + let mut clang_args = Vec::new(); + let mut arg = |arg: &str| { + lld_args.push(arg.to_string()); + clang_args.push(format!("-Wl,{}", arg)); + }; + + // There have been reports in the wild (rustwasm/wasm-bindgen#119) of + // using threads causing weird hangs and bugs. Disable it entirely as + // this isn't yet the bottleneck of compilation at all anyway. + // + // FIXME: we should file an upstream issue with LLD about this + arg("--no-threads"); + + // By default LLD only gives us one page of stack (64k) which is a + // little small. Default to a larger stack closer to other PC platforms + // (1MB) and users can always inject their own link-args to override this. + arg("-z"); + arg("stack-size=1048576"); + + // By default LLD's memory layout is: + // + // 1. First, a blank page + // 2. Next, all static data + // 3. Finally, the main stack (which grows down) + // + // This has the unfortunate consequence that on stack overflows you + // corrupt static data and can cause some exceedingly weird bugs. To + // help detect this a little sooner we instead request that the stack is + // placed before static data. + // + // This means that we'll generate slightly larger binaries as references + // to static data will take more bytes in the ULEB128 encoding, but + // stack overflow will be guaranteed to trap as it underflows instead of + // corrupting static data. + arg("--stack-first"); + + // FIXME we probably shouldn't pass this but instead pass an explicit + // whitelist of symbols we'll allow to be undefined. We don't currently have + // a mechanism of knowing, however, which symbols are intended to be + // imported from the environment and which are intended to be imported from + // other objects linked elsewhere. This is a coarse approximation but is + // sure to hide some bugs and frustrate someone at some point, so we should + // ideally work towards a world where we can explicitly list symbols that + // are supposed to be imported and have all other symbols generate errors if + // they remain undefined. + arg("--allow-undefined"); + + // Rust code should never have warnings, and warnings are often + // indicative of bugs, let's prevent them. + arg("--fatal-warnings"); + + // LLD only implements C++-like demangling, which doesn't match our own + // mangling scheme. Tell LLD to not demangle anything and leave it up to + // us to demangle these symbols later. Currently rustc does not perform + // further demangling, but tools like twiggy and wasm-bindgen are intended + // to do so. + arg("--no-demangle"); + + // The symbol visibility story is a bit in flux right now with LLD. + // It's... not entirely clear to me what's going on, but this looks to + // make everything work when `export_symbols` isn't otherwise called for + // things like executables. + // + // This is really only here to get things working. If it can be removed and + // basic tests still work, then sounds like it should be removed! + arg("--export-dynamic"); + + let mut pre_link_args = BTreeMap::new(); + pre_link_args.insert(LinkerFlavor::Lld(LldFlavor::Wasm), lld_args); + pre_link_args.insert(LinkerFlavor::Gcc, clang_args); + + TargetOptions { + // we allow dynamic linking, but only cdylibs. Basically we allow a + // final library artifact that exports some symbols (a wasm module) but + // we don't allow intermediate `dylib` crate types + dynamic_linking: true, + only_cdylib: true, + + // This means we'll just embed a `start` function in the wasm module + executables: true, + + // relatively self-explanatory! + exe_suffix: ".wasm".to_string(), + dll_prefix: String::new(), + dll_suffix: ".wasm".to_string(), + linker_is_gnu: false, + + max_atomic_width: Some(64), + + // Unwinding doesn't work right now, so the whole target unconditionally + // defaults to panic=abort. Note that this is guaranteed to change in + // the future once unwinding is implemented. Don't rely on this as we're + // basically guaranteed to change it once WebAssembly supports + // exceptions. + panic_strategy: PanicStrategy::Abort, + + // Wasm doesn't have atomics yet, so tell LLVM that we're in a single + // threaded model which will legalize atomics to normal operations. + singlethread: true, + + // no dynamic linking, no need for default visibility! + default_hidden_visibility: true, + + // we use the LLD shipped with the Rust toolchain by default + linker: Some("rust-lld".to_owned()), + lld_flavor: LldFlavor::Wasm, + + // No need for indirection here, simd types can always be passed by + // value as the whole module either has simd or not, which is different + // from x86 (for example) where programs can have functions that don't + // enable simd features. + simd_types_indirect: false, + + pre_link_args, + + .. Default::default() + } +} diff --git a/src/librustc_target/spec/wasm32_unknown_unknown.rs b/src/librustc_target/spec/wasm32_unknown_unknown.rs index ee2160c4720..909527d2b61 100644 --- a/src/librustc_target/spec/wasm32_unknown_unknown.rs +++ b/src/librustc_target/spec/wasm32_unknown_unknown.rs @@ -1,70 +1,48 @@ -// The wasm32-unknown-unknown target is currently an experimental version of a -// wasm-based target which does *not* use the Emscripten toolchain. Instead -// this toolchain is based purely on LLVM's own toolchain, using LLVM's native -// WebAssembly backend as well as LLD for a native linker. -// -// There's some trickery below on crate types supported and various defaults -// (aka panic=abort by default), but otherwise this is in general a relatively -// standard target. +//! A "bare wasm" target representing a WebAssembly output that makes zero +//! assumptions about its environment. +//! +//! The `wasm32-unknown-unknown` target is intended to encapsulate use cases +//! that do not rely on any imported functionality. The binaries generated are +//! entirely self-contained by default when using the standard library. Although +//! the standard library is available, most of it returns an error immediately +//! (e.g. trying to create a TCP stream or something like that). +//! +//! This target is more or less managed by the Rust and WebAssembly Working +//! Group nowadays at https://github.com/rustwasm. -use super::{LldFlavor, LinkerFlavor, Target, TargetOptions, PanicStrategy}; +use super::{LldFlavor, LinkerFlavor, Target}; +use super::wasm32_base; pub fn target() -> Result { - let opts = TargetOptions { - // we allow dynamic linking, but only cdylibs. Basically we allow a - // final library artifact that exports some symbols (a wasm module) but - // we don't allow intermediate `dylib` crate types - dynamic_linking: true, - only_cdylib: true, + let mut options = wasm32_base::options(); + let clang_args = options.pre_link_args.get_mut(&LinkerFlavor::Gcc).unwrap(); - // This means we'll just embed a `start` function in the wasm module - executables: true, + // Make sure clang uses LLD as its linker and is configured appropriately + // otherwise + clang_args.push("--target=wasm32-unknown-unknown".to_string()); - // relatively self-explanatory! - exe_suffix: ".wasm".to_string(), - dll_prefix: String::new(), - dll_suffix: ".wasm".to_string(), - linker_is_gnu: false, + // Disable attempting to link crt1.o since it typically isn't present and + // isn't needed currently. + clang_args.push("-nostdlib".to_string()); - max_atomic_width: Some(64), + // For now this target just never has an entry symbol no matter the output + // type, so unconditionally pass this. + clang_args.push("-Wl,--no-entry".to_string()); + options.pre_link_args.get_mut(&LinkerFlavor::Lld(LldFlavor::Wasm)) + .unwrap() + .push("--no-entry".to_string()); - // Unwinding doesn't work right now, so the whole target unconditionally - // defaults to panic=abort. Note that this is guaranteed to change in - // the future once unwinding is implemented. Don't rely on this. - panic_strategy: PanicStrategy::Abort, - - // Wasm doesn't have atomics yet, so tell LLVM that we're in a single - // threaded model which will legalize atomics to normal operations. - singlethread: true, - - // no dynamic linking, no need for default visibility! - default_hidden_visibility: true, - - // we use the LLD shipped with the Rust toolchain by default - linker: Some("rust-lld".to_owned()), - lld_flavor: LldFlavor::Wasm, - - // No need for indirection here, simd types can always be passed by - // value as the whole module either has simd or not, which is different - // from x86 (for example) where programs can have functions that don't - // enable simd features. - simd_types_indirect: false, - - .. Default::default() - }; Ok(Target { llvm_target: "wasm32-unknown-unknown".to_string(), target_endian: "little".to_string(), target_pointer_width: "32".to_string(), target_c_int_width: "32".to_string(), - // This is basically guaranteed to change in the future, don't rely on - // this. Use `not(target_os = "emscripten")` for now. target_os: "unknown".to_string(), target_env: String::new(), target_vendor: "unknown".to_string(), data_layout: "e-m:e-p:32:32-i64:64-n32:64-S128".to_string(), arch: "wasm32".to_string(), linker_flavor: LinkerFlavor::Lld(LldFlavor::Wasm), - options: opts, + options, }) }