From 87b8c9eaf211e91ea1eef9427bd0b05fef5e58cf Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Fri, 3 Feb 2017 14:58:13 +0000 Subject: [PATCH] Add Emscripten-specific linker It claims to accept most GNU linker options, but in fact most of them have no effect and instead it requires some special options which are easier to handle in a separate trait. Currently added: - `export_symbols`: works on executables as special Emscripten case since staticlibs/dylibs aren't compiled to JS, while exports are required to be accessible from JS. Fixes #39171. - `optimize` - translates Rust's optimization level to Emscripten optimization level (whether passed via `-C opt-level=...` or `-O...`). Fixes #36899. - `debuginfo` - translates debug info; Emscripten has 5 debug levels while Rust has 3, so chose to translate `-C debuginfo=1` to `-g3` (preserves whitespace, variable and function names for easy debugging). Fixes #36901. - `no_default_libraries` - tells Emscripten to exlude `memcpy` and co. --- src/Cargo.lock | 1 + src/librustc_trans/Cargo.toml | 1 + src/librustc_trans/back/link.rs | 3 +- src/librustc_trans/back/linker.rs | 159 +++++++++++++++++++++++++++++- src/librustc_trans/lib.rs | 1 + 5 files changed, 162 insertions(+), 3 deletions(-) diff --git a/src/Cargo.lock b/src/Cargo.lock index 06cf32ad0f6b5..75ac4c2a3d69a 100644 --- a/src/Cargo.lock +++ b/src/Cargo.lock @@ -512,6 +512,7 @@ dependencies = [ "rustc_incremental 0.0.0", "rustc_llvm 0.0.0", "rustc_platform_intrinsics 0.0.0", + "serialize 0.0.0", "syntax 0.0.0", "syntax_pos 0.0.0", ] diff --git a/src/librustc_trans/Cargo.toml b/src/librustc_trans/Cargo.toml index fa48a63b6b8f5..b5c67ad998b69 100644 --- a/src/librustc_trans/Cargo.toml +++ b/src/librustc_trans/Cargo.toml @@ -22,5 +22,6 @@ rustc_errors = { path = "../librustc_errors" } rustc_incremental = { path = "../librustc_incremental" } rustc_llvm = { path = "../librustc_llvm" } rustc_platform_intrinsics = { path = "../librustc_platform_intrinsics" } +serialize = { path = "../libserialize" } syntax = { path = "../libsyntax" } syntax_pos = { path = "../libsyntax_pos" } diff --git a/src/librustc_trans/back/link.rs b/src/librustc_trans/back/link.rs index 4ddf8a883bc48..696a8ea200a54 100644 --- a/src/librustc_trans/back/link.rs +++ b/src/librustc_trans/back/link.rs @@ -823,7 +823,8 @@ fn link_args(cmd: &mut Linker, // If we're building a dynamic library then some platforms need to make sure // that all symbols are exported correctly from the dynamic library. - if crate_type != config::CrateTypeExecutable { + if crate_type != config::CrateTypeExecutable || + sess.target.target.options.is_like_emscripten { cmd.export_symbols(tmpdir, crate_type); } diff --git a/src/librustc_trans/back/linker.rs b/src/librustc_trans/back/linker.rs index 7f352f1da517d..29fba51e28b68 100644 --- a/src/librustc_trans/back/linker.rs +++ b/src/librustc_trans/back/linker.rs @@ -23,8 +23,8 @@ use back::symbol_export::{self, ExportedSymbols}; use middle::dependency_format::Linkage; use rustc::hir::def_id::{LOCAL_CRATE, CrateNum}; use session::Session; -use session::config::CrateType; -use session::config; +use session::config::{self, CrateType, OptLevel, DebugInfoLevel}; +use serialize::{json, Encoder}; /// For all the linkers we support, and information they might /// need out of the shared crate context before we get rid of it. @@ -51,6 +51,12 @@ impl<'a, 'tcx> LinkerInfo { sess: sess, info: self }) as Box + } else if sess.target.target.options.is_like_emscripten { + Box::new(EmLinker { + cmd: cmd, + sess: sess, + info: self + }) as Box } else { Box::new(GnuLinker { cmd: cmd, @@ -488,6 +494,155 @@ impl<'a> Linker for MsvcLinker<'a> { } } +pub struct EmLinker<'a> { + cmd: &'a mut Command, + sess: &'a Session, + info: &'a LinkerInfo +} + +impl<'a> Linker for EmLinker<'a> { + fn include_path(&mut self, path: &Path) { + self.cmd.arg("-L").arg(path); + } + + fn link_staticlib(&mut self, lib: &str) { + self.cmd.arg("-l").arg(lib); + } + + fn output_filename(&mut self, path: &Path) { + self.cmd.arg("-o").arg(path); + } + + fn add_object(&mut self, path: &Path) { + self.cmd.arg(path); + } + + fn link_dylib(&mut self, lib: &str) { + // Emscripten always links statically + self.link_staticlib(lib); + } + + fn link_whole_staticlib(&mut self, lib: &str, _search_path: &[PathBuf]) { + // not supported? + self.link_staticlib(lib); + } + + fn link_whole_rlib(&mut self, lib: &Path) { + // not supported? + self.link_rlib(lib); + } + + fn link_rust_dylib(&mut self, lib: &str, _path: &Path) { + self.link_dylib(lib); + } + + fn link_rlib(&mut self, lib: &Path) { + self.add_object(lib); + } + + fn position_independent_executable(&mut self) { + // noop + } + + fn args(&mut self, args: &[String]) { + self.cmd.args(args); + } + + fn framework_path(&mut self, _path: &Path) { + bug!("frameworks are not supported on Emscripten") + } + + fn link_framework(&mut self, _framework: &str) { + bug!("frameworks are not supported on Emscripten") + } + + fn gc_sections(&mut self, _keep_metadata: bool) { + // noop + } + + fn optimize(&mut self) { + // Emscripten performs own optimizations + self.cmd.arg(match self.sess.opts.optimize { + OptLevel::No => "-O0", + OptLevel::Less => "-O1", + OptLevel::Default => "-O2", + OptLevel::Aggressive => "-O3", + OptLevel::Size => "-Os", + OptLevel::SizeMin => "-Oz" + }); + } + + fn debuginfo(&mut self) { + // Preserve names or generate source maps depending + // on debug info + self.cmd.arg(match self.sess.opts.debuginfo { + DebugInfoLevel::NoDebugInfo => "-g0", + DebugInfoLevel::LimitedDebugInfo => "-g3", + DebugInfoLevel::FullDebugInfo => "-g4" + }); + } + + fn no_default_libraries(&mut self) { + self.cmd.arg("-s"); + self.cmd.arg("DEFAULT_LIBRARY_FUNCS_TO_INCLUDE=[]"); + } + + fn build_dylib(&mut self, _out_filename: &Path) { + bug!("building dynamic library is unsupported on Emscripten") + } + + fn whole_archives(&mut self) { + // noop + } + + fn no_whole_archives(&mut self) { + // noop + } + + fn hint_static(&mut self) { + // noop + } + + fn hint_dynamic(&mut self) { + // noop + } + + fn export_symbols(&mut self, _tmpdir: &Path, crate_type: CrateType) { + let mut arg = OsString::new(); + + let symbols = &self.info.exports[&crate_type]; + + debug!("EXPORTED SYMBOLS:"); + + self.cmd.arg("-s"); + arg.push("EXPORTED_FUNCTIONS="); + let mut encoded = String::new(); + + { + let mut encoder = json::Encoder::new(&mut encoded); + let res = encoder.emit_seq(symbols.len(), |encoder| { + for (i, sym) in symbols.iter().enumerate() { + encoder.emit_seq_elt(i, |encoder| { + encoder.emit_str(&("_".to_string() + sym)) + })?; + } + Ok(()) + }); + if let Err(e) = res { + self.sess.fatal(&format!("failed to encode exported symbols: {}", e)); + } + } + debug!("{}", encoded); + arg.push(encoded); + + self.cmd.arg(arg); + } + + fn subsystem(&mut self, _subsystem: &str) { + // noop + } +} + fn exported_symbols(scx: &SharedCrateContext, exported_symbols: &ExportedSymbols, crate_type: CrateType) diff --git a/src/librustc_trans/lib.rs b/src/librustc_trans/lib.rs index 21c92cb4a4ad2..1530fcda3d3ea 100644 --- a/src/librustc_trans/lib.rs +++ b/src/librustc_trans/lib.rs @@ -59,6 +59,7 @@ extern crate rustc_bitflags; #[macro_use] extern crate syntax; extern crate syntax_pos; extern crate rustc_errors as errors; +extern crate serialize; pub use rustc::session; pub use rustc::middle;