Implement extensible syscall interface for wasm

This commit is contained in:
Diggory Blake 2017-12-31 16:40:34 +00:00
parent def3269a71
commit 36695a37c5
8 changed files with 346 additions and 198 deletions

View File

@ -28,14 +28,76 @@ let m = new WebAssembly.Module(buffer);
let memory = null;
function viewstruct(data, fields) {
return new Uint32Array(memory.buffer).subarray(data/4, data/4 + fields);
}
function copystr(a, b) {
if (memory === null) {
return null
}
let view = new Uint8Array(memory.buffer).slice(a, a + b);
let view = new Uint8Array(memory.buffer).subarray(a, a + b);
return String.fromCharCode.apply(null, view);
}
function syscall_write([fd, ptr, len]) {
let s = copystr(ptr, len);
switch (fd) {
case 1: process.stdout.write(s); break;
case 2: process.stderr.write(s); break;
}
}
function syscall_exit([code]) {
process.exit(code);
}
function syscall_args(params) {
let [ptr, len] = params;
// Calculate total required buffer size
let totalLen = -1;
for (let i = 2; i < process.argv.length; ++i) {
totalLen += Buffer.byteLength(process.argv[i]) + 1;
}
if (totalLen < 0) { totalLen = 0; }
params[2] = totalLen;
// If buffer is large enough, copy data
if (len >= totalLen) {
let view = new Uint8Array(memory.buffer);
for (let i = 2; i < process.argv.length; ++i) {
let value = process.argv[i];
Buffer.from(value).copy(view, ptr);
ptr += Buffer.byteLength(process.argv[i]) + 1;
}
}
}
function syscall_getenv(params) {
let [keyPtr, keyLen, valuePtr, valueLen] = params;
let key = copystr(keyPtr, keyLen);
let value = process.env[key];
if (value == null) {
params[4] = 0xFFFFFFFF;
} else {
let view = new Uint8Array(memory.buffer);
let totalLen = Buffer.byteLength(value);
params[4] = totalLen;
if (valueLen >= totalLen) {
Buffer.from(value).copy(view, valuePtr);
}
}
}
function syscall_time(params) {
let t = Date.now();
let secs = Math.floor(t / 1000);
let millis = t % 1000;
params[1] = Math.floor(secs / 0x100000000);
params[2] = secs % 0x100000000;
params[3] = Math.floor(millis * 1000000);
}
let imports = {};
imports.env = {
// These are generated by LLVM itself for various intrinsic calls. Hopefully
@ -48,68 +110,25 @@ imports.env = {
log10: Math.log10,
log10f: Math.log10,
// 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_syscall: function(index, data) {
switch (index) {
case 1: syscall_write(viewstruct(data, 3)); return true;
case 2: syscall_exit(viewstruct(data, 1)); return true;
case 3: syscall_args(viewstruct(data, 3)); return true;
case 4: syscall_getenv(viewstruct(data, 5)); return true;
case 6: syscall_time(viewstruct(data, 4)); return true;
default:
console.log("Unsupported syscall: " + index);
return false;
}
},
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 Buffer.byteLength(process.argv[i + 2]);
},
rust_wasm_args_arg_fill: function(idx, ptr) {
let arg = process.argv[idx + 2];
let view = new Uint8Array(memory.buffer);
Buffer.from(arg).copy(view, ptr);
},
// 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 Buffer.byteLength(process.env[key]);
},
rust_wasm_getenv_data: function(a, b, ptr) {
let key = copystr(a, b);
let value = process.env[key];
let view = new Uint8Array(memory.buffer);
Buffer.from(value).copy(view, ptr);
},
}
};
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);
memory = instance.exports.memory;
try {
instance.exports.main();
} catch (e) {
console.error(e);
process.exit(101);
}

View File

@ -824,9 +824,7 @@ fn binaryen_assemble(cgcx: &CodegenContext,
if cgcx.debuginfo != config::NoDebugInfo {
options.debuginfo(true);
}
if cgcx.crate_types.contains(&config::CrateTypeExecutable) {
options.start("main");
}
options.stack(1024 * 1024);
options.import_memory(cgcx.wasm_import_memory);
let assembled = input.and_then(|input| {
@ -1452,7 +1450,7 @@ fn start_executing_work(tcx: TyCtxt,
target_pointer_width: tcx.sess.target.target.target_pointer_width.clone(),
binaryen_linker: tcx.sess.linker_flavor() == LinkerFlavor::Binaryen,
debuginfo: tcx.sess.opts.debuginfo,
wasm_import_memory: wasm_import_memory,
wasm_import_memory,
assembler_cmd,
};

View File

@ -48,3 +48,4 @@ jemalloc = ["alloc_jemalloc"]
force_alloc_system = []
panic-unwind = ["panic_unwind"]
profiler = ["profiler_builtins"]
wasm_syscall = []

View File

@ -10,8 +10,8 @@
use ffi::OsString;
use marker::PhantomData;
use mem;
use vec;
use sys::ArgsSysCall;
pub unsafe fn init(_argc: isize, _argv: *const *const u8) {
// On wasm these should always be null, so there's nothing for us to do here
@ -21,38 +21,10 @@ pub unsafe fn cleanup() {
}
pub fn args() -> Args {
// When the runtime debugging is enabled we'll link to some extra runtime
// functions to actually implement this. These are for now just implemented
// in a node.js script but they're off by default as they're sort of weird
// in a web-wasm world.
if !super::DEBUG {
return Args {
iter: Vec::new().into_iter(),
_dont_send_or_sync_me: PhantomData,
}
}
// You'll find the definitions of these in `src/etc/wasm32-shim.js`. These
// are just meant for debugging and should not be relied on.
extern {
fn rust_wasm_args_count() -> usize;
fn rust_wasm_args_arg_size(a: usize) -> usize;
fn rust_wasm_args_arg_fill(a: usize, ptr: *mut u8);
}
unsafe {
let cnt = rust_wasm_args_count();
let mut v = Vec::with_capacity(cnt);
for i in 0..cnt {
let n = rust_wasm_args_arg_size(i);
let mut data = vec![0; n];
rust_wasm_args_arg_fill(i, data.as_mut_ptr());
v.push(mem::transmute::<Vec<u8>, OsString>(data));
}
Args {
iter: v.into_iter(),
_dont_send_or_sync_me: PhantomData,
}
let v = ArgsSysCall::perform();
Args {
iter: v.into_iter(),
_dont_send_or_sync_me: PhantomData,
}
}

View File

@ -26,17 +26,11 @@
use io;
use os::raw::c_char;
// Right now the wasm backend doesn't even have the ability to print to the
// console by default. Wasm can't import anything from JS! (you have to
// explicitly provide it).
//
// Sometimes that's a real bummer, though, so this flag can be set to `true` to
// enable calling various shims defined in `src/etc/wasm32-shim.js` which should
// help receive debug output and see what's going on. In general this flag
// currently controls "will we call out to our own defined shims in node.js",
// and this flag should always be `false` for release builds.
const DEBUG: bool = false;
use ptr;
use sys::os_str::Buf;
use sys_common::{AsInner, FromInner};
use ffi::{OsString, OsStr};
use time::Duration;
pub mod args;
#[cfg(feature = "backtrace")]
@ -92,7 +86,7 @@ pub unsafe fn strlen(mut s: *const c_char) -> usize {
}
pub unsafe fn abort_internal() -> ! {
::intrinsics::abort();
ExitSysCall::perform(1)
}
// We don't have randomness yet, but I totally used a random number generator to
@ -103,3 +97,218 @@ pub unsafe fn abort_internal() -> ! {
pub fn hashmap_random_keys() -> (u64, u64) {
(1, 2)
}
// Implement a minimal set of system calls to enable basic IO
pub enum SysCallIndex {
Read = 0,
Write = 1,
Exit = 2,
Args = 3,
GetEnv = 4,
SetEnv = 5,
Time = 6,
}
#[repr(C)]
pub struct ReadSysCall {
fd: usize,
ptr: *mut u8,
len: usize,
result: usize,
}
impl ReadSysCall {
pub fn perform(fd: usize, buffer: &mut [u8]) -> usize {
let mut call_record = ReadSysCall {
fd,
len: buffer.len(),
ptr: buffer.as_mut_ptr(),
result: 0
};
if unsafe { syscall(SysCallIndex::Read, &mut call_record) } {
call_record.result
} else {
0
}
}
}
#[repr(C)]
pub struct WriteSysCall {
fd: usize,
ptr: *const u8,
len: usize,
}
impl WriteSysCall {
pub fn perform(fd: usize, buffer: &[u8]) {
let mut call_record = WriteSysCall {
fd,
len: buffer.len(),
ptr: buffer.as_ptr()
};
unsafe { syscall(SysCallIndex::Write, &mut call_record); }
}
}
#[repr(C)]
pub struct ExitSysCall {
code: usize,
}
impl ExitSysCall {
pub fn perform(code: usize) -> ! {
let mut call_record = ExitSysCall {
code
};
unsafe {
syscall(SysCallIndex::Exit, &mut call_record);
::intrinsics::abort();
}
}
}
fn receive_buffer<E, F: FnMut(&mut [u8]) -> Result<usize, E>>(estimate: usize, mut f: F)
-> Result<Vec<u8>, E>
{
let mut buffer = vec![0; estimate];
loop {
let result = f(&mut buffer)?;
if result <= buffer.len() {
buffer.truncate(result);
break;
}
buffer.resize(result, 0);
}
Ok(buffer)
}
#[repr(C)]
pub struct ArgsSysCall {
ptr: *mut u8,
len: usize,
result: usize
}
impl ArgsSysCall {
pub fn perform() -> Vec<OsString> {
receive_buffer(1024, |buffer| -> Result<usize, !> {
let mut call_record = ArgsSysCall {
len: buffer.len(),
ptr: buffer.as_mut_ptr(),
result: 0
};
if unsafe { syscall(SysCallIndex::Args, &mut call_record) } {
Ok(call_record.result)
} else {
Ok(0)
}
})
.unwrap()
.split(|b| *b == 0)
.map(|s| FromInner::from_inner(Buf { inner: s.to_owned() }))
.collect()
}
}
#[repr(C)]
pub struct GetEnvSysCall {
key_ptr: *const u8,
key_len: usize,
value_ptr: *mut u8,
value_len: usize,
result: usize
}
impl GetEnvSysCall {
pub fn perform(key: &OsStr) -> Option<OsString> {
let key_buf = &AsInner::as_inner(key).inner;
receive_buffer(64, |buffer| {
let mut call_record = GetEnvSysCall {
key_len: key_buf.len(),
key_ptr: key_buf.as_ptr(),
value_len: buffer.len(),
value_ptr: buffer.as_mut_ptr(),
result: !0usize
};
if unsafe { syscall(SysCallIndex::GetEnv, &mut call_record) } {
if call_record.result == !0usize {
Err(())
} else {
Ok(call_record.result)
}
} else {
Err(())
}
}).ok().map(|s| {
FromInner::from_inner(Buf { inner: s })
})
}
}
#[repr(C)]
pub struct SetEnvSysCall {
key_ptr: *const u8,
key_len: usize,
value_ptr: *const u8,
value_len: usize
}
impl SetEnvSysCall {
pub fn perform(key: &OsStr, value: Option<&OsStr>) {
let key_buf = &AsInner::as_inner(key).inner;
let value_buf = value.map(|v| &AsInner::as_inner(v).inner);
let mut call_record = SetEnvSysCall {
key_len: key_buf.len(),
key_ptr: key_buf.as_ptr(),
value_len: value_buf.map(|v| v.len()).unwrap_or(!0usize),
value_ptr: value_buf.map(|v| v.as_ptr()).unwrap_or(ptr::null())
};
unsafe { syscall(SysCallIndex::SetEnv, &mut call_record); }
}
}
pub enum TimeClock {
Monotonic = 0,
System = 1,
}
#[repr(C)]
pub struct TimeSysCall {
clock: usize,
secs_hi: usize,
secs_lo: usize,
nanos: usize
}
impl TimeSysCall {
pub fn perform(clock: TimeClock) -> Duration {
let mut call_record = TimeSysCall {
clock: clock as usize,
secs_hi: 0,
secs_lo: 0,
nanos: 0
};
if unsafe { syscall(SysCallIndex::Time, &mut call_record) } {
Duration::new(
((call_record.secs_hi as u64) << 32) | (call_record.secs_lo as u64),
call_record.nanos as u32
)
} else {
panic!("Time system call is not implemented by WebAssembly host");
}
}
}
unsafe fn syscall<T>(index: SysCallIndex, data: &mut T) -> bool {
#[cfg(feature = "wasm_syscall")]
extern {
#[no_mangle]
fn rust_wasm_syscall(index: usize, data: *mut Void) -> usize;
}
#[cfg(not(feature = "wasm_syscall"))]
unsafe fn rust_wasm_syscall(_index: usize, _data: *mut Void) -> usize { 0 }
rust_wasm_syscall(index as usize, data as *mut T as *mut Void) != 0
}

View File

@ -8,16 +8,13 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use core::intrinsics;
use error::Error as StdError;
use ffi::{OsString, OsStr};
use fmt;
use io;
use mem;
use path::{self, PathBuf};
use str;
use sys::{unsupported, Void};
use sys::{unsupported, Void, ExitSysCall, GetEnvSysCall, SetEnvSysCall};
pub fn errno() -> i32 {
0
@ -87,36 +84,15 @@ pub fn env() -> Env {
}
pub fn getenv(k: &OsStr) -> io::Result<Option<OsString>> {
// If we're debugging the runtime then we actually probe node.js to ask for
// the value of environment variables to help provide inputs to programs.
// The `extern` shims here are defined in `src/etc/wasm32-shim.js` and are
// intended for debugging only, you should not rely on them.
if !super::DEBUG {
return Ok(None)
}
extern {
fn rust_wasm_getenv_len(k: *const u8, kl: usize) -> isize;
fn rust_wasm_getenv_data(k: *const u8, kl: usize, v: *mut u8);
}
unsafe {
let k: &[u8] = mem::transmute(k);
let n = rust_wasm_getenv_len(k.as_ptr(), k.len());
if n == -1 {
return Ok(None)
}
let mut data = vec![0; n as usize];
rust_wasm_getenv_data(k.as_ptr(), k.len(), data.as_mut_ptr());
Ok(Some(mem::transmute(data)))
}
Ok(GetEnvSysCall::perform(k))
}
pub fn setenv(_k: &OsStr, _v: &OsStr) -> io::Result<()> {
unsupported()
pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {
Ok(SetEnvSysCall::perform(k, Some(v)))
}
pub fn unsetenv(_n: &OsStr) -> io::Result<()> {
unsupported()
pub fn unsetenv(k: &OsStr) -> io::Result<()> {
Ok(SetEnvSysCall::perform(k, None))
}
pub fn temp_dir() -> PathBuf {
@ -128,7 +104,7 @@ pub fn home_dir() -> Option<PathBuf> {
}
pub fn exit(_code: i32) -> ! {
unsafe { intrinsics::abort() }
ExitSysCall::perform(_code as isize as usize)
}
pub fn getpid() -> u32 {

View File

@ -9,19 +9,19 @@
// except according to those terms.
use io;
use sys::{Void, unsupported};
use sys::{ReadSysCall, WriteSysCall};
pub struct Stdin(Void);
pub struct Stdin;
pub struct Stdout;
pub struct Stderr;
impl Stdin {
pub fn new() -> io::Result<Stdin> {
unsupported()
Ok(Stdin)
}
pub fn read(&self, _data: &mut [u8]) -> io::Result<usize> {
match self.0 {}
pub fn read(&self, data: &mut [u8]) -> io::Result<usize> {
Ok(ReadSysCall::perform(0, data))
}
}
@ -31,19 +31,7 @@ impl Stdout {
}
pub fn write(&self, data: &[u8]) -> io::Result<usize> {
// If runtime debugging is enabled at compile time we'll invoke some
// runtime functions that are defined in our src/etc/wasm32-shim.js
// debugging script. Note that this ffi function call is intended
// *purely* for debugging only and should not be relied upon.
if !super::DEBUG {
return unsupported()
}
extern {
fn rust_wasm_write_stdout(data: *const u8, len: usize);
}
unsafe {
rust_wasm_write_stdout(data.as_ptr(), data.len())
}
WriteSysCall::perform(1, data);
Ok(data.len())
}
@ -58,16 +46,7 @@ impl Stderr {
}
pub fn write(&self, data: &[u8]) -> io::Result<usize> {
// See comments in stdout for what's going on here.
if !super::DEBUG {
return unsupported()
}
extern {
fn rust_wasm_write_stderr(data: *const u8, len: usize);
}
unsafe {
rust_wasm_write_stderr(data.as_ptr(), data.len())
}
WriteSysCall::perform(2, data);
Ok(data.len())
}

View File

@ -8,56 +8,50 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use fmt;
use time::Duration;
use sys::{TimeSysCall, TimeClock};
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
pub struct Instant;
pub struct Instant(Duration);
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SystemTime;
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
pub struct SystemTime(Duration);
pub const UNIX_EPOCH: SystemTime = SystemTime;
pub const UNIX_EPOCH: SystemTime = SystemTime(Duration::from_secs(0));
impl Instant {
pub fn now() -> Instant {
panic!("not supported on web assembly");
Instant(TimeSysCall::perform(TimeClock::Monotonic))
}
pub fn sub_instant(&self, _other: &Instant) -> Duration {
panic!("can't sub yet");
pub fn sub_instant(&self, other: &Instant) -> Duration {
self.0 - other.0
}
pub fn add_duration(&self, _other: &Duration) -> Instant {
panic!("can't add yet");
pub fn add_duration(&self, other: &Duration) -> Instant {
Instant(self.0 + *other)
}
pub fn sub_duration(&self, _other: &Duration) -> Instant {
panic!("can't sub yet");
pub fn sub_duration(&self, other: &Duration) -> Instant {
Instant(self.0 - *other)
}
}
impl SystemTime {
pub fn now() -> SystemTime {
panic!("not supported on web assembly");
SystemTime(TimeSysCall::perform(TimeClock::System))
}
pub fn sub_time(&self, _other: &SystemTime)
pub fn sub_time(&self, other: &SystemTime)
-> Result<Duration, Duration> {
panic!()
self.0.checked_sub(other.0).ok_or_else(|| other.0 - self.0)
}
pub fn add_duration(&self, _other: &Duration) -> SystemTime {
panic!()
pub fn add_duration(&self, other: &Duration) -> SystemTime {
SystemTime(self.0 + *other)
}
pub fn sub_duration(&self, _other: &Duration) -> SystemTime {
panic!()
}
}
impl fmt::Debug for SystemTime {
fn fmt(&self, _f: &mut fmt::Formatter) -> fmt::Result {
panic!()
pub fn sub_duration(&self, other: &Duration) -> SystemTime {
SystemTime(self.0 - *other)
}
}