Rollup merge of #62809 - alexcrichton:wasm-llvm-9, r=nikic

rustc: Update wasm32 support for LLVM 9

This commit brings in a number of minor updates for rustc's support for
the wasm target which has changed in the LLVM 9 update. Notable updates
include:

* The compiler now no longer manually inserts the `producers` section,
  instead relying on LLVM to do so. LLVM uses the `llvm.ident` metadata
  for the `processed-by` directive (which is now emitted on the wasm
  target in this PR) and it uses debuginfo to figure out what `language`
  to put in the `producers` section.

* Threaded WebAssembly code now requires different flags to be passed
  with LLD. In LLD we now pass:

  * `--shared-memory` - required since objects are compiled with
    atomics. This also means that the generated memory will be marked as
    `shared`.
  * `--max-memory=1GB` - required with the `--shared-memory` argument
    since shared memories in WebAssembly must have a maximum size. The
    1GB number is intended to be a conservative estimate for rustc, but
    it should be overridable with `-C link-arg` if necessary.
  * `--passive-segments` - this has become the default for multithreaded
    memory, but when compiling a threaded module all data segments need
    to be marked as passive to ensure they don't re-initialize memory
    for each thread. This will also cause LLD to emit a synthetic
    function to initialize memory which users will have to arrange to
    call.
  * The `__heap_base` and `__data_end` globals are explicitly exported
    since they're now hidden by default due to the `--export` flags we
    pass to LLD.
This commit is contained in:
Mazdak Farrokhzad 2019-07-29 02:10:52 +02:00 committed by GitHub
commit 778b631ff0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 129 additions and 336 deletions

View file

@ -913,9 +913,12 @@ pub fn compile_unit_metadata(
}
debug!("compile_unit_metadata: {:?}", name_in_debuginfo);
let rustc_producer = format!(
"rustc version {}",
option_env!("CFG_VERSION").expect("CFG_VERSION"),
);
// FIXME(#41252) Remove "clang LLVM" if we can get GDB and LLVM to play nice.
let producer = format!("clang LLVM (rustc version {})",
(option_env!("CFG_VERSION")).expect("CFG_VERSION"));
let producer = format!("clang LLVM ({})", rustc_producer);
let name_in_debuginfo = name_in_debuginfo.to_string_lossy();
let name_in_debuginfo = SmallCStr::new(&name_in_debuginfo);
@ -980,6 +983,21 @@ pub fn compile_unit_metadata(
gcov_metadata);
}
// Insert `llvm.ident` metadata on the wasm32 targets since that will
// get hooked up to the "producer" sections `processed-by` information.
if tcx.sess.opts.target_triple.triple().starts_with("wasm32") {
let name_metadata = llvm::LLVMMDStringInContext(
debug_context.llcontext,
rustc_producer.as_ptr() as *const _,
rustc_producer.as_bytes().len() as c_uint,
);
llvm::LLVMAddNamedMetadataOperand(
debug_context.llmod,
const_cstr!("llvm.ident").as_ptr(),
llvm::LLVMMDNodeInContext(debug_context.llcontext, &name_metadata, 1),
);
}
return unit_metadata;
};

View file

@ -678,14 +678,6 @@ fn link_natively<'a, B: ArchiveBuilder<'a>>(sess: &'a Session,
sess.fatal(&format!("failed to run dsymutil: {}", e))
}
}
if sess.opts.target_triple.triple() == "wasm32-unknown-unknown" {
super::wasm::add_producer_section(
&out_filename,
&sess.edition().to_string(),
option_env!("CFG_VERSION").unwrap_or("unknown"),
);
}
}
/// Returns a boolean indicating whether the specified crate should be ignored

View file

@ -901,7 +901,45 @@ pub struct WasmLd<'a> {
}
impl<'a> WasmLd<'a> {
fn new(cmd: Command, sess: &'a Session, info: &'a LinkerInfo) -> WasmLd<'a> {
fn new(mut cmd: Command, sess: &'a Session, info: &'a LinkerInfo) -> WasmLd<'a> {
// If the atomics feature is enabled for wasm then we need a whole bunch
// of flags:
//
// * `--shared-memory` - the link won't even succeed without this, flags
// the one linear memory as `shared`
//
// * `--max-memory=1G` - when specifying a shared memory this must also
// be specified. We conservatively choose 1GB but users should be able
// to override this with `-C link-arg`.
//
// * `--import-memory` - it doesn't make much sense for memory to be
// exported in a threaded module because typically you're
// sharing memory and instantiating the module multiple times. As a
// result if it were exported then we'd just have no sharing.
//
// * `--passive-segments` - all memory segments should be passive to
// prevent each module instantiation from reinitializing memory.
//
// * `--export=__wasm_init_memory` - when using `--passive-segments` the
// linker will synthesize this function, and so we need to make sure
// that our usage of `--export` below won't accidentally cause this
// function to get deleted.
//
// * `--export=*tls*` - when `#[thread_local]` symbols are used these
// symbols are how the TLS segments are initialized and configured.
let atomics = sess.opts.cg.target_feature.contains("+atomics") ||
sess.target.target.options.features.contains("+atomics");
if atomics {
cmd.arg("--shared-memory");
cmd.arg("--max-memory=1073741824");
cmd.arg("--import-memory");
cmd.arg("--passive-segments");
cmd.arg("--export=__wasm_init_memory");
cmd.arg("--export=__wasm_init_tls");
cmd.arg("--export=__tls_size");
cmd.arg("--export=__tls_align");
cmd.arg("--export=__tls_base");
}
WasmLd { cmd, sess, info }
}
}
@ -1004,6 +1042,13 @@ impl<'a> Linker for WasmLd<'a> {
for sym in self.info.exports[&crate_type].iter() {
self.cmd.arg("--export").arg(&sym);
}
// LLD will hide these otherwise-internal symbols since our `--export`
// list above is a whitelist of what to export. Various bits and pieces
// of tooling use this, so be sure these symbols make their way out of
// the linker as well.
self.cmd.arg("--export=__heap_base");
self.cmd.arg("--export=__data_end");
}
fn subsystem(&mut self, _subsystem: &str) {

View file

@ -6,4 +6,3 @@ pub mod command;
pub mod symbol_export;
pub mod archive;
pub mod rpath;
pub mod wasm;

View file

@ -1,191 +0,0 @@
use std::fs;
use std::path::Path;
use std::str;
use rustc_serialize::leb128;
// https://webassembly.github.io/spec/core/binary/modules.html#binary-importsec
const WASM_CUSTOM_SECTION_ID: u8 = 0;
/// Adds or augment the existing `producers` section to encode information about
/// the Rust compiler used to produce the wasm file.
pub fn add_producer_section(
path: &Path,
rust_version: &str,
rustc_version: &str,
) {
struct Field<'a> {
name: &'a str,
values: Vec<FieldValue<'a>>,
}
#[derive(Copy, Clone)]
struct FieldValue<'a> {
name: &'a str,
version: &'a str,
}
let wasm = fs::read(path).expect("failed to read wasm output");
let mut ret = WasmEncoder::new();
ret.data.extend(&wasm[..8]);
// skip the 8 byte wasm/version header
let rustc_value = FieldValue {
name: "rustc",
version: rustc_version,
};
let rust_value = FieldValue {
name: "Rust",
version: rust_version,
};
let mut fields = Vec::new();
let mut wrote_rustc = false;
let mut wrote_rust = false;
// Move all sections from the original wasm file to our output, skipping
// everything except the producers section
for (id, raw) in WasmSections(WasmDecoder::new(&wasm[8..])) {
if id != WASM_CUSTOM_SECTION_ID {
ret.byte(id);
ret.bytes(raw);
continue
}
let mut decoder = WasmDecoder::new(raw);
if decoder.str() != "producers" {
ret.byte(id);
ret.bytes(raw);
continue
}
// Read off the producers section into our fields outside the loop,
// we'll re-encode the producers section when we're done (to handle an
// entirely missing producers section as well).
info!("rewriting existing producers section");
for _ in 0..decoder.u32() {
let name = decoder.str();
let mut values = Vec::new();
for _ in 0..decoder.u32() {
let name = decoder.str();
let version = decoder.str();
values.push(FieldValue { name, version });
}
if name == "language" {
values.push(rust_value);
wrote_rust = true;
} else if name == "processed-by" {
values.push(rustc_value);
wrote_rustc = true;
}
fields.push(Field { name, values });
}
}
if !wrote_rust {
fields.push(Field {
name: "language",
values: vec![rust_value],
});
}
if !wrote_rustc {
fields.push(Field {
name: "processed-by",
values: vec![rustc_value],
});
}
// Append the producers section to the end of the wasm file.
let mut section = WasmEncoder::new();
section.str("producers");
section.u32(fields.len() as u32);
for field in fields {
section.str(field.name);
section.u32(field.values.len() as u32);
for value in field.values {
section.str(value.name);
section.str(value.version);
}
}
ret.byte(WASM_CUSTOM_SECTION_ID);
ret.bytes(&section.data);
fs::write(path, &ret.data).expect("failed to write wasm output");
}
struct WasmSections<'a>(WasmDecoder<'a>);
impl<'a> Iterator for WasmSections<'a> {
type Item = (u8, &'a [u8]);
fn next(&mut self) -> Option<(u8, &'a [u8])> {
if self.0.data.is_empty() {
return None
}
// see https://webassembly.github.io/spec/core/binary/modules.html#sections
let id = self.0.byte();
let section_len = self.0.u32();
info!("new section {} / {} bytes", id, section_len);
let section = self.0.skip(section_len as usize);
Some((id, section))
}
}
struct WasmDecoder<'a> {
data: &'a [u8],
}
impl<'a> WasmDecoder<'a> {
fn new(data: &'a [u8]) -> WasmDecoder<'a> {
WasmDecoder { data }
}
fn byte(&mut self) -> u8 {
self.skip(1)[0]
}
fn u32(&mut self) -> u32 {
let (n, l1) = leb128::read_u32_leb128(self.data);
self.data = &self.data[l1..];
return n
}
fn skip(&mut self, amt: usize) -> &'a [u8] {
let (data, rest) = self.data.split_at(amt);
self.data = rest;
data
}
fn str(&mut self) -> &'a str {
let len = self.u32();
str::from_utf8(self.skip(len as usize)).unwrap()
}
}
struct WasmEncoder {
data: Vec<u8>,
}
impl WasmEncoder {
fn new() -> WasmEncoder {
WasmEncoder { data: Vec::new() }
}
fn u32(&mut self, val: u32) {
leb128::write_u32_leb128(&mut self.data, val);
}
fn byte(&mut self, val: u8) {
self.data.push(val);
}
fn bytes(&mut self, val: &[u8]) {
self.u32(val.len() as u32);
self.data.extend_from_slice(val);
}
fn str(&mut self, val: &str) {
self.bytes(val.as_bytes())
}
}

View file

@ -132,6 +132,14 @@ pub fn options() -> TargetOptions {
// non-relative calls and such later on).
relocation_model: "static".to_string(),
// When the atomics feature is activated then these two keys matter,
// otherwise they're basically ignored by the standard library. In this
// mode, however, the `#[thread_local]` attribute works (i.e.
// `has_elf_tls`) and we need to get it to work by specifying
// `local-exec` as that's all that's implemented in LLVM today for wasm.
has_elf_tls: true,
tls_model: "local-exec".to_string(),
.. Default::default()
}
}

View file

@ -75,11 +75,6 @@ panic_immediate_abort = ["core/panic_immediate_abort"]
# requires rebuilding the standard library to use it.
wasm_syscall = []
# An off-by-default features to enable libstd to assume that wasm-bindgen is in
# the environment for hooking up some thread-related information like the
# current thread id and accessing/getting the current thread's TCB
wasm-bindgen-threads = []
# Enable std_detect default features for stdarch/crates/std_detect:
# https://github.com/rust-lang/stdarch/blob/master/crates/std_detect/Cargo.toml
std_detect_file_io = []

View file

@ -47,6 +47,8 @@ pub mod stdio;
pub mod thread;
#[path = "../wasm/thread_local.rs"]
pub mod thread_local;
#[path = "../wasm/fast_thread_local.rs"]
pub mod fast_thread_local;
pub mod time;
pub mod ext;

View file

@ -0,0 +1,9 @@
#![unstable(feature = "thread_local_internals", issue = "0")]
pub unsafe fn register_dtor(_t: *mut u8, _dtor: unsafe extern fn(*mut u8)) {
// FIXME: right now there is no concept of "thread exit", but this is likely
// going to show up at some point in the form of an exported symbol that the
// wasm runtime is oging to be expected to call. For now we basically just
// ignore the arguments, but if such a function starts to exist it will
// likely look like the OSX implementation in `unix/fast_thread_local.rs`
}

View file

@ -37,6 +37,8 @@ pub mod stack_overflow;
pub mod thread;
pub mod time;
pub mod stdio;
pub mod thread_local;
pub mod fast_thread_local;
pub use crate::sys_common::os_str_bytes as os_str;
@ -48,13 +50,10 @@ cfg_if::cfg_if! {
pub mod mutex;
#[path = "rwlock_atomics.rs"]
pub mod rwlock;
#[path = "thread_local_atomics.rs"]
pub mod thread_local;
} else {
pub mod condvar;
pub mod mutex;
pub mod rwlock;
pub mod thread_local;
}
}

View file

@ -59,48 +59,40 @@ pub mod guard {
pub unsafe fn init() -> Option<Guard> { None }
}
cfg_if::cfg_if! {
if #[cfg(all(target_feature = "atomics", feature = "wasm-bindgen-threads"))] {
#[link(wasm_import_module = "__wbindgen_thread_xform__")]
extern {
fn __wbindgen_current_id() -> u32;
fn __wbindgen_tcb_get() -> u32;
fn __wbindgen_tcb_set(ptr: u32);
}
pub fn my_id() -> u32 {
unsafe { __wbindgen_current_id() }
}
// This is only used by atomics primitives when the `atomics` feature is
// enabled. In that mode we currently just use our own thread-local to store our
// current thread's ID, and then we lazily initialize it to something allocated
// from a global counter.
#[cfg(target_feature = "atomics")]
pub fn my_id() -> u32 {
use crate::sync::atomic::{AtomicU32, Ordering::SeqCst};
// These are currently only ever used in `thread_local_atomics.rs`, if
// you'd like to use them be sure to update that and make sure everyone
// agrees what's what.
pub fn tcb_get() -> *mut u8 {
use crate::mem;
assert_eq!(mem::size_of::<*mut u8>(), mem::size_of::<u32>());
unsafe { __wbindgen_tcb_get() as *mut u8 }
}
static NEXT_ID: AtomicU32 = AtomicU32::new(0);
pub fn tcb_set(ptr: *mut u8) {
unsafe { __wbindgen_tcb_set(ptr as u32); }
}
#[thread_local]
static mut MY_ID: u32 = 0;
// FIXME: still need something for hooking exiting a thread to free
// data...
} else if #[cfg(target_feature = "atomics")] {
pub fn my_id() -> u32 {
panic!("thread ids not implemented on wasm with atomics yet")
unsafe {
// If our thread ID isn't set yet then we need to allocate one. Do so
// with with a simple "atomically add to a global counter" strategy.
// This strategy doesn't handled what happens when the counter
// overflows, however, so just abort everything once the counter
// overflows and eventually we could have some sort of recycling scheme
// (or maybe this is all totally irrelevant by that point!). In any case
// though we're using a CAS loop instead of a `fetch_add` to ensure that
// the global counter never overflows.
if MY_ID == 0 {
let mut cur = NEXT_ID.load(SeqCst);
MY_ID = loop {
let next = cur.checked_add(1).unwrap_or_else(|| {
crate::arch::wasm32::unreachable()
});
match NEXT_ID.compare_exchange(cur, next, SeqCst, SeqCst) {
Ok(_) => break next,
Err(i) => cur = i,
}
};
}
pub fn tcb_get() -> *mut u8 {
panic!("thread local data not implemented on wasm with atomics yet")
}
pub fn tcb_set(_ptr: *mut u8) {
panic!("thread local data not implemented on wasm with atomics yet")
}
} else {
// stubbed out because no functions actually access these intrinsics
// unless atomics are enabled
MY_ID
}
}

View file

@ -1,40 +1,26 @@
use crate::boxed::Box;
use crate::ptr;
pub type Key = usize;
struct Allocated {
value: *mut u8,
dtor: Option<unsafe extern fn(*mut u8)>,
#[inline]
pub unsafe fn create(_dtor: Option<unsafe extern fn(*mut u8)>) -> Key {
panic!("should not be used on the wasm target");
}
#[inline]
pub unsafe fn create(dtor: Option<unsafe extern fn(*mut u8)>) -> Key {
Box::into_raw(Box::new(Allocated {
value: ptr::null_mut(),
dtor,
})) as usize
pub unsafe fn set(_key: Key, _value: *mut u8) {
panic!("should not be used on the wasm target");
}
#[inline]
pub unsafe fn set(key: Key, value: *mut u8) {
(*(key as *mut Allocated)).value = value;
pub unsafe fn get(_key: Key) -> *mut u8 {
panic!("should not be used on the wasm target");
}
#[inline]
pub unsafe fn get(key: Key) -> *mut u8 {
(*(key as *mut Allocated)).value
}
#[inline]
pub unsafe fn destroy(key: Key) {
let key = Box::from_raw(key as *mut Allocated);
if let Some(f) = key.dtor {
f(key.value);
}
pub unsafe fn destroy(_key: Key) {
panic!("should not be used on the wasm target");
}
#[inline]
pub fn requires_synchronized_create() -> bool {
false
panic!("should not be used on the wasm target");
}

View file

@ -1,61 +0,0 @@
use crate::sys::thread;
use crate::sync::atomic::{AtomicUsize, Ordering::SeqCst};
const MAX_KEYS: usize = 128;
static NEXT_KEY: AtomicUsize = AtomicUsize::new(0);
struct ThreadControlBlock {
keys: [*mut u8; MAX_KEYS],
}
impl ThreadControlBlock {
fn new() -> ThreadControlBlock {
ThreadControlBlock {
keys: [core::ptr::null_mut(); MAX_KEYS],
}
}
fn get() -> *mut ThreadControlBlock {
let ptr = thread::tcb_get();
if !ptr.is_null() {
return ptr as *mut ThreadControlBlock
}
let tcb = Box::into_raw(Box::new(ThreadControlBlock::new()));
thread::tcb_set(tcb as *mut u8);
tcb
}
}
pub type Key = usize;
pub unsafe fn create(dtor: Option<unsafe extern fn(*mut u8)>) -> Key {
drop(dtor); // FIXME: need to figure out how to hook thread exit to run this
let key = NEXT_KEY.fetch_add(1, SeqCst);
if key >= MAX_KEYS {
NEXT_KEY.store(MAX_KEYS, SeqCst);
panic!("cannot allocate space for more TLS keys");
}
// offset by 1 so we never hand out 0. This is currently required by
// `sys_common/thread_local.rs` where it can't cope with keys of value 0
// because it messes up the atomic management.
return key + 1
}
pub unsafe fn set(key: Key, value: *mut u8) {
(*ThreadControlBlock::get()).keys[key - 1] = value;
}
pub unsafe fn get(key: Key) -> *mut u8 {
(*ThreadControlBlock::get()).keys[key - 1]
}
pub unsafe fn destroy(_key: Key) {
// FIXME: should implement this somehow, this isn't typically called but it
// can be called if two threads race to initialize a TLS slot and one ends
// up not being needed.
}
#[inline]
pub fn requires_synchronized_create() -> bool {
false
}

@ -1 +1 @@
Subproject commit f6446fa8e9629ffb1861303f17930c3aa83ef660
Subproject commit 9b64ca5b7e1e3583978f9ac8af6d93b220a13d90