Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

std: Add a new wasm32-unknown-unknown target #45905

Merged
merged 1 commit into from
Nov 20, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,9 @@
[submodule "src/tools/miri"]
path = src/tools/miri
url = https://github.com/solson/miri.git
[submodule "src/dlmalloc"]
path = src/dlmalloc
url = https://github.com/alexcrichton/dlmalloc-rs.git
[submodule "src/binaryen"]
path = src/binaryen
url = https://github.com/alexcrichton/binaryen.git
22 changes: 19 additions & 3 deletions src/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/binaryen
Submodule binaryen added at 1c9bf6
3 changes: 2 additions & 1 deletion src/bootstrap/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1211,7 +1211,8 @@ impl Step for Crate {
// ends up messing with various mtime calculations and such.
if !name.contains("jemalloc") &&
*name != *"build_helper" &&
!(name.starts_with("rustc_") && name.ends_with("san")) {
!(name.starts_with("rustc_") && name.ends_with("san")) &&
name != "dlmalloc" {
cargo.arg("-p").arg(&format!("{}:0.0.0", name));
}
for dep in build.crates[&name].deps.iter() {
Expand Down
5 changes: 5 additions & 0 deletions src/bootstrap/dist.rs
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,9 @@ fn copy_src_dirs(build: &Build, src_dirs: &[&str], exclude_dirs: &[&str], dst_di
spath.ends_with(".s")) {
return false
}
if spath.contains("test/emscripten") || spath.contains("test\\emscripten") {
return false
}

let full_path = Path::new(dir).join(path);
if exclude_dirs.iter().any(|excl| full_path == Path::new(excl)) {
Expand Down Expand Up @@ -736,6 +739,7 @@ impl Step for Src {
// (essentially libstd and all of its path dependencies)
let std_src_dirs = [
"src/build_helper",
"src/dlmalloc",
"src/liballoc",
"src/liballoc_jemalloc",
"src/liballoc_system",
Expand All @@ -754,6 +758,7 @@ impl Step for Src {
"src/libunwind",
"src/rustc/compiler_builtins_shim",
"src/rustc/libc_shim",
"src/rustc/dlmalloc_shim",
"src/libtest",
"src/libterm",
"src/jemalloc",
Expand Down
1 change: 1 addition & 0 deletions src/dlmalloc
Submodule dlmalloc added at d3812c
119 changes: 119 additions & 0 deletions src/etc/wasm32-shim.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Copyright 2017 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.

// This is a small "shim" program which is used when wasm32 unit tests are run
// in this repository. This program is intended to be run in node.js and will
// load a wasm module into memory, instantiate it with a set of imports, and
// then run it.
//
// There's a bunch of helper functions defined here in `imports.env`, but note
// that most of them aren't actually needed to execute most programs. Many of
// these are just intended for completeness or debugging. Hopefully over time
// nothing here is needed for completeness.

const fs = require('fs');
const process = require('process');
const buffer = fs.readFileSync(process.argv[2]);

Error.stackTraceLimit = 20;

let m = new WebAssembly.Module(buffer);

let memory = null;

function copystr(a, b) {
if (memory === null) {
return null
}
let view = new Uint8Array(memory.buffer).slice(a, a + b);
return String.fromCharCode.apply(null, view);
}

let imports = {};
imports.env = {
// These are generated by LLVM itself for various intrinsic calls. Hopefully
// one day this is not necessary and something will automatically do this.
fmod: function(x, y) { return x % y; },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason not to implement these on Rust side? (then they would be "automatic" for any consumer)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean manually emulating the operation as if they were soft floats? I'm pretty sure that this would be much much slower than calling from wasm to js, executing a native float op, and returning the result to the wasm executable. Also not sure how WASM compilation works but it seems like it is possible to inline that function when the JS runtime JIT compiles it...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It wouldn't need soft float; it would just be a call to fmod in libm.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to inline that function when the JS runtime JIT compiles it...

It's not because JS and WASM use distinct compilers and runtimes, so every call outside is actually somewhat expensive (just like calls between JS and C++ runtimes).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've discussed this a bit with @nagisa , @rkruppe and @sunfishcode on IRC.

  • There is no (fast) hardware instruction for floating point modulo, so you'll have to come up with a software solution. However, you can use the hardware instructions for other floating point operations to enable this. This gives you a speedup over pure soft-float implementations. I guess due to this point my point that this code is good is moot (as there will be a non-trivial overhead all the time).
  • One can't use libm unless libc is available, as libm is part of libc. The target is named wasm32-unknown-unknown however, it has no libc!
  • @nagisa 's math.rs library is built for 100% soft floats, so it is not fast.... @japaric 's m library doesn't seem to include modulo.
  • As we don't expose libstd, only libcore, we don't need to support the full set of arithmetic operations

So it seems we'd have to implement the functions ourselves.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

math.rs library is built for 100% soft floats, so it is not fast

Might be still worth benchmarking both approaches. It might very well turn out to be still faster than calls to JS side just to modulo two numbers, and even if it's on par, it gives benefit of not forcing each consumer to include these "intrinsics" in their JS consumers.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I'm assuming that one day we can just natively depend on the JS implementation here without implementing our own. Right now though it just requires an opt-in from everyone using it :(

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which is why I'm suggesting just including soft emulation of these functions on Rust side. Might be not too bad (and operators that are slow to emulate like fmod are not used too frequently anyway).

exp2: function(x) { return Math.pow(2, x); },
exp2f: function(x) { return Math.pow(2, x); },
ldexp: function(x, y) { return x * Math.pow(2, y); },
ldexpf: function(x, y) { return x * Math.pow(2, y); },
log10: function(x) { return Math.log10(x); },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw - no need to wrap these, you can just do log10: Math.log10 since context doesn't matter for static Math functions.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! I'll try to get that done in a follow-up commit.

log10f: function(x) { return Math.log10(x); },

// These are called in src/libstd/sys/wasm/stdio.rs and are used when
// debugging is enabled.
rust_wasm_write_stdout: function(a, b) {
let s = copystr(a, b);
if (s !== null) {
process.stdout.write(s);
}
},
rust_wasm_write_stderr: function(a, b) {
let s = copystr(a, b);
if (s !== null) {
process.stderr.write(s);
}
},

// These are called in src/libstd/sys/wasm/args.rs and are used when
// debugging is enabled.
rust_wasm_args_count: function() {
if (memory === null)
return 0;
return process.argv.length - 2;
},
rust_wasm_args_arg_size: function(i) {
return process.argv[i + 2].length;
},
rust_wasm_args_arg_fill: function(idx, ptr) {
let arg = process.argv[idx + 2];
let view = new Uint8Array(memory.buffer);
for (var i = 0; i < arg.length; i++) {
view[ptr + i] = arg.charCodeAt(i);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here about strings, but I guess too late now that it's merged? @alexcrichton

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RReverser yes I will try to fix these when testing later. This doesn't matter for correctness and it's just internal debugging, it's not massively critical this is working.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, even better could be to just have UTF-16 OsString for WASM target - I think it would make sense than Vec<u8> given that in JS string operations are all based on UTF-16 units.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again though the intent here is to not go crazy, if we're dealing with utf-16 then let's just delete this code. I just used it for debugging and it shouldn't affect the standard library in any way. This isn't a node target, it's a wasm target and there's no strings in wasm yet in the abi

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure wasm will ever have strings in the ABI when there is TextEncoder / TextDecoder, and for interaction with any JS it seems only fair to have OsString to be UTF-16 like it is on some other platforms.

}
},

// These are called in src/libstd/sys/wasm/os.rs and are used when
// debugging is enabled.
rust_wasm_getenv_len: function(a, b) {
let key = copystr(a, b);
if (key === null) {
return -1;
}
if (!(key in process.env)) {
return -1;
}
return process.env[key].length;
},
rust_wasm_getenv_data: function(a, b, ptr) {
let key = copystr(a, b);
let value = process.env[key];
let view = new Uint8Array(memory.buffer);
for (var i = 0; i < value.length; i++) {
view[ptr + i] = value.charCodeAt(i);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW this won't work correctly for Unicode chars. Given that this is for Node.js, you might want to do something like new Buffer(value).copy(view, ptr) instead; and, then, for getenv_len you'll have to use Buffer.byteLength(process.env[key]).

}
},
};

let module_imports = WebAssembly.Module.imports(m);

for (var i = 0; i < module_imports.length; i++) {
let imp = module_imports[i];
if (imp.module != 'env') {
continue
}
if (imp.name == 'memory' && imp.kind == 'memory') {
memory = new WebAssembly.Memory({initial: 20});
imports.env.memory = memory;
}
}

let instance = new WebAssembly.Instance(m, imports);
2 changes: 1 addition & 1 deletion src/liballoc_jemalloc/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ fn main() {
let host = env::var("HOST").expect("HOST was not set");
if target.contains("rumprun") || target.contains("bitrig") || target.contains("openbsd") ||
target.contains("msvc") || target.contains("emscripten") || target.contains("fuchsia") ||
target.contains("redox") {
target.contains("redox") || target.contains("wasm32") {
println!("cargo:rustc-cfg=dummy_jemalloc");
return;
}
Expand Down
4 changes: 4 additions & 0 deletions src/liballoc_system/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,7 @@ doc = false
alloc = { path = "../liballoc" }
core = { path = "../libcore" }
libc = { path = "../rustc/libc_shim" }

# See comments in the source for what this dependency is
[target.'cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))'.dependencies]
dlmalloc = { path = "../rustc/dlmalloc_shim" }
90 changes: 90 additions & 0 deletions src/liballoc_system/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,14 @@
target_arch = "powerpc64",
target_arch = "asmjs",
target_arch = "wasm32")))]
#[allow(dead_code)]
const MIN_ALIGN: usize = 8;
#[cfg(all(any(target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "mips64",
target_arch = "s390x",
target_arch = "sparc64")))]
#[allow(dead_code)]
const MIN_ALIGN: usize = 16;

extern crate alloc;
Expand Down Expand Up @@ -458,3 +460,91 @@ mod platform {
}
}
}

// This is an implementation of a global allocator on the wasm32 platform when
// emscripten is not in use. In that situation there's no actual runtime for us
// to lean on for allocation, so instead we provide our own!
//
// The wasm32 instruction set has two instructions for getting the current
// amount of memory and growing the amount of memory. These instructions are the
// foundation on which we're able to build an allocator, so we do so! Note that
// the instructions are also pretty "global" and this is the "global" allocator
// after all!
//
// The current allocator here is the `dlmalloc` crate which we've got included
// in the rust-lang/rust repository as a submodule. The crate is a port of
// dlmalloc.c from C to Rust and is basically just so we can have "pure Rust"
// for now which is currently technically required (can't link with C yet).
//
// The crate itself provides a global allocator which on wasm has no
// synchronization as there are no threads!
#[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))]
mod platform {
extern crate dlmalloc;

use alloc::heap::{Alloc, AllocErr, Layout, Excess, CannotReallocInPlace};
use System;
use self::dlmalloc::GlobalDlmalloc;

#[unstable(feature = "allocator_api", issue = "32838")]
unsafe impl<'a> Alloc for &'a System {
#[inline]
unsafe fn alloc(&mut self, layout: Layout) -> Result<*mut u8, AllocErr> {
GlobalDlmalloc.alloc(layout)
}

#[inline]
unsafe fn alloc_zeroed(&mut self, layout: Layout)
-> Result<*mut u8, AllocErr>
{
GlobalDlmalloc.alloc_zeroed(layout)
}

#[inline]
unsafe fn dealloc(&mut self, ptr: *mut u8, layout: Layout) {
GlobalDlmalloc.dealloc(ptr, layout)
}

#[inline]
unsafe fn realloc(&mut self,
ptr: *mut u8,
old_layout: Layout,
new_layout: Layout) -> Result<*mut u8, AllocErr> {
GlobalDlmalloc.realloc(ptr, old_layout, new_layout)
}

#[inline]
fn usable_size(&self, layout: &Layout) -> (usize, usize) {
GlobalDlmalloc.usable_size(layout)
}

#[inline]
unsafe fn alloc_excess(&mut self, layout: Layout) -> Result<Excess, AllocErr> {
GlobalDlmalloc.alloc_excess(layout)
}

#[inline]
unsafe fn realloc_excess(&mut self,
ptr: *mut u8,
layout: Layout,
new_layout: Layout) -> Result<Excess, AllocErr> {
GlobalDlmalloc.realloc_excess(ptr, layout, new_layout)
}

#[inline]
unsafe fn grow_in_place(&mut self,
ptr: *mut u8,
layout: Layout,
new_layout: Layout) -> Result<(), CannotReallocInPlace> {
GlobalDlmalloc.grow_in_place(ptr, layout, new_layout)
}

#[inline]
unsafe fn shrink_in_place(&mut self,
ptr: *mut u8,
layout: Layout,
new_layout: Layout) -> Result<(), CannotReallocInPlace> {
GlobalDlmalloc.shrink_in_place(ptr, layout, new_layout)
}
}
}
3 changes: 0 additions & 3 deletions src/libcore/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ path = "lib.rs"
test = false
bench = false

[dev-dependencies]
rand = "0.3"

[[test]]
name = "coretests"
path = "../libcore/tests/lib.rs"
Expand Down
2 changes: 0 additions & 2 deletions src/libcore/tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
#![feature(iter_rfind)]
#![feature(iter_rfold)]
#![feature(nonzero)]
#![feature(rand)]
#![feature(raw)]
#![feature(refcell_replace_swap)]
#![feature(sip_hash_13)]
Expand All @@ -48,7 +47,6 @@

extern crate core;
extern crate test;
extern crate rand;

mod any;
mod array;
Expand Down
Loading