Skip to content

Commit

Permalink
Changes required to bring in full WASIX support
Browse files Browse the repository at this point in the history
- Implemented multi-threading for both JS and SYS, plus other WASIX implementations
- Added a longjmp capability required for bash and other WASIX implementations
- Added real signals to WASIX
- Added a stack unwinding and winding functionality
- Implemented memory forking which will be used for process forking
- Added the ability to fork the current process
- Added the vfork functionality
- Moved over to the WasiPipe implementation
- Added more syscalls needed for bash on WASIX
- Ported wasmer-os into wasmer
- Added a union file system and the character devices
- Moved the cursors to the file handles rather than the file so that they are multithread safe and can handle concurrent IO
- Reimplemented the poll_oneoff functionality to support full ASYNC
- Added support for mapping directories in the host file system into WASIX sandbox file systems
- Implemented fully ASYNC sockets and emulated ASYNC files
- Made the file locks more granular to allow for concurrent poll and accept operations
- Fixed a race condition on the event notifications
  • Loading branch information
john-sharratt authored and theduke committed Nov 7, 2022
1 parent 39f988a commit 572ea3e
Show file tree
Hide file tree
Showing 167 changed files with 19,965 additions and 6,464 deletions.
684 changes: 556 additions & 128 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/migration_to_3.0.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ import_object.define("env", "host_function", host_function);
let instance = Instance::new(&mut store, &module, &import_object).expect("Could not instantiate module.");
```

For WASI, don't forget to initialize the `WasiEnv` (it will import the memory)
For WASI, don't forget to initialize it

```rust
let mut wasi_env = WasiState::new("hello").finalize()?;
Expand Down
4 changes: 2 additions & 2 deletions examples/imports_function_env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
fn get_counter(env: FunctionEnvMut<Env>) -> i32 {
*env.data().counter.lock().unwrap()
}
fn add_to_counter(mut env: FunctionEnvMut<Env>, add: i32) -> i32 {
let mut counter_ref = env.data_mut().counter.lock().unwrap();
fn add_to_counter(env: FunctionEnvMut<Env>, add: i32) -> i32 {
let mut counter_ref = env.data().counter.lock().unwrap();

*counter_ref += add;
*counter_ref
Expand Down
9 changes: 4 additions & 5 deletions examples/wasi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {

println!("Creating `WasiEnv`...");
// First, we create the `WasiEnv`
let wasi_env = WasiState::new("hello")
let mut wasi_env = WasiState::new("hello")
// .args(&["world"])
// .env("KEY", "Value")
.finalize(&mut store)?;
Expand All @@ -50,10 +50,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let import_object = wasi_env.import_object(&mut store, &module)?;
let instance = Instance::new(&mut store, &module, &import_object)?;

println!("Attach WASI memory...");
// Attach the memory export
let memory = instance.exports.get_memory("memory")?;
wasi_env.data_mut(&mut store).set_memory(memory.clone());
println!("Initializing WASI environment...");
// Initialize the WASI environment (which will attach memory)
wasi_env.initialize(&mut store, &instance).unwrap();

println!("Call WASI `_start` function...");
// And we just call the `_start` function!
Expand Down
7 changes: 3 additions & 4 deletions examples/wasi_pipes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let import_object = wasi_env.import_object(&mut store, &module)?;
let instance = Instance::new(&mut store, &module, &import_object)?;

println!("Attach WASI memory...");
// Attach the memory export
let memory = instance.exports.get_memory("memory")?;
wasi_env.data_mut(&mut store).set_memory(memory.clone());
println!("Initializing WASI environment...");
// Initialize the WASI environment (which will attach memory)
wasi_env.initialize(&mut store, &instance).unwrap();

let msg = "racecar go zoom";
println!("Writing \"{}\" to the WASI stdin...", msg);
Expand Down
1 change: 1 addition & 0 deletions lib/api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ cfg-if = "1.0"
thiserror = "1.0"
more-asserts = "0.2"
bytes = "1"
derivative = { version = "^2" }
# - Optional shared dependencies.
wat = { version = "1.0", optional = true }
tracing = { version = "0.1", optional = true }
Expand Down
83 changes: 81 additions & 2 deletions lib/api/src/js/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ use js_sys::WebAssembly::{Memory, Table};
use serde::{Deserialize, Serialize};
use std::fmt;
use wasm_bindgen::{JsCast, JsValue};
use wasmer_types::{ExternType, FunctionType, GlobalType, MemoryType, TableType, WASM_PAGE_SIZE};
use wasmer_types::{ExternType, FunctionType, GlobalType, MemoryType, TableType, Pages, WASM_PAGE_SIZE, StoreSnapshot};
use crate::MemoryView;
#[cfg(feature="tracing")]
use tracing::trace;

pub use wasmer_types::MemoryError;

/// Represents linear memory that is managed by the javascript runtime
#[derive(Clone, Debug, PartialEq)]
Expand All @@ -25,14 +30,63 @@ struct DummyBuffer {
}

impl VMMemory {
pub(crate) fn new(memory: Memory, ty: MemoryType) -> Self {
/// Creates a new memory directly from a WebAssembly javascript object
pub fn new(memory: Memory, ty: MemoryType) -> Self {
Self { memory, ty }
}

/// Attempts to clone this memory (if its clonable)
pub(crate) fn try_clone(&self) -> Option<VMMemory> {
Some(self.clone())
}

/// Copies this memory to a new memory
pub fn fork(&self) -> Result<VMMemory, wasmer_types::MemoryError> {
let new_memory = crate::Memory::new_internal(self.ty.clone())?;

#[cfg(feature="tracing")]
trace!("memory copy started");

let src = MemoryView::new_raw(&self.memory);
let amount = src.data_size() as usize;
let mut dst = MemoryView::new_raw(&new_memory);
let dst_size = dst.data_size() as usize;

if amount > dst_size {
let delta = amount - dst_size;
let pages = ((delta - 1) / WASM_PAGE_SIZE) + 1;

let our_js_memory: &crate::js::externals::memory::JSMemory = JsCast::unchecked_from_js_ref(&new_memory);
our_js_memory.grow(pages as u32).map_err(|err| {
if err.is_instance_of::<js_sys::RangeError>() {
let cur_pages = dst_size;
MemoryError::CouldNotGrow {
current: Pages(cur_pages as u32),
attempted_delta: Pages(pages as u32),
}
} else {
MemoryError::Generic(err.as_string().unwrap())
}
})?;

dst = MemoryView::new_raw(&new_memory);
}

src.copy_to_memory(amount as u64, &dst)
.map_err(|err| {
wasmer_types::MemoryError::Generic(format!("failed to copy the memory - {}", err))
})?;

#[cfg(feature="tracing")]
trace!("memory copy finished (size={})", dst.size().bytes().0);

Ok(
Self {
memory: new_memory,
ty: self.ty.clone(),
}
)
}
}

#[derive(Clone, Debug, PartialEq)]
Expand All @@ -45,6 +99,31 @@ impl VMGlobal {
pub(crate) fn new(global: Global, ty: GlobalType) -> Self {
Self { global, ty }
}

/// Saves the global value into the snapshot
pub fn save_snapshot(&self, index: usize, snapshot: &mut StoreSnapshot) {
if let Some(val) = self.global.as_f64() {
let entry = snapshot.globals
.entry(index as u32)
.or_default();
*entry = val as u128;
}
}

/// Restores the global value from the snapshot
pub fn restore_snapshot(&mut self, index: usize, snapshot: &StoreSnapshot) {
let index = index as u32;
if let Some(entry) = snapshot.globals.get(&index) {
if let Some(existing) = self.global.as_f64() {
let existing = existing as u128;
if existing == *entry {
return;
}
}
let value = JsValue::from_f64(*entry as _);
self.global.set_value(&value);
}
}
}

unsafe impl Send for VMGlobal {}
Expand Down
3 changes: 3 additions & 0 deletions lib/api/src/js/exports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ pub enum ExportError {
/// This error arises when an export is missing
#[error("Missing export {0}")]
Missing(String),
/// This error arises when an export is missing
#[error("Serialization failed {0}")]
SerializationFailed(String),
}

/// Exports is a special kind of map that allows easily unwrapping
Expand Down
59 changes: 46 additions & 13 deletions lib/api/src/js/externals/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@ pub struct Function {
pub(crate) handle: StoreHandle<VMFunction>,
}

impl Into<Function>
for StoreHandle<VMFunction>
{
fn into(self) -> Function {
Function {
handle: self
}
}
}

impl Function {
/// Creates a new host `Function` (dynamic) with the provided signature.
///
Expand Down Expand Up @@ -393,6 +403,10 @@ impl Function {
store: &mut impl AsStoreMut,
params: &[Value],
) -> Result<Box<[Value]>, RuntimeError> {
#[allow(unused_unsafe)]
let params: Vec<_> = unsafe {
params.iter().map(|a| a.as_raw_value(&store.as_store_ref())).collect()
};
let arr = js_sys::Array::new_with_length(params.len() as u32);

// let raw_env = env.as_raw() as *mut u8;
Expand All @@ -402,11 +416,28 @@ impl Function {
let js_value = param.as_jsvalue(&store.as_store_ref());
arr.set(i as u32, js_value);
}
let result = js_sys::Reflect::apply(
&self.handle.get(store.as_store_ref().objects()).function,
&wasm_bindgen::JsValue::NULL,
&arr,
)?;

let result = {
let mut r;
loop {
r = js_sys::Reflect::apply(
&self.handle.get(store.as_store_ref().objects()).function,
&wasm_bindgen::JsValue::NULL,
&arr,
);
let store_mut = store.as_store_mut();
if let Some(callback) = store_mut.inner.on_called.take() {
match callback(store_mut) {
Ok(wasmer_types::OnCalledAction::InvokeAgain) => { continue; }
Ok(wasmer_types::OnCalledAction::Finish) => { break; }
Ok(wasmer_types::OnCalledAction::Trap(trap)) => { return Err(RuntimeError::user(trap)) },
Err(trap) => { return Err(RuntimeError::user(trap)) },
}
}
break;
}
r?
};

let result_types = self.handle.get(store.as_store_ref().objects()).ty.results();
match result_types.len() {
Expand Down Expand Up @@ -1134,16 +1165,18 @@ mod inner {
T: Send + 'static,
Func: Fn(FunctionEnvMut<'_, T>, $( $x , )*) -> RetsAsResult + 'static,
{
// let env: &Env = unsafe { &*(ptr as *const u8 as *const Env) };
let func: &Func = &*(&() as *const () as *const Func);
let mut store = StoreMut::from_raw(store_ptr as *mut _);
let mut store2 = StoreMut::from_raw(store_ptr as *mut _);

let result = panic::catch_unwind(AssertUnwindSafe(|| {
let handle: StoreHandle<VMFunctionEnvironment> = StoreHandle::from_internal(store2.objects_mut().id(), InternalStoreHandle::from_index(handle_index).unwrap());
let env: FunctionEnvMut<T> = FunctionEnv::from_handle(handle).into_mut(&mut store2);
func(env, $( FromToNativeWasmType::from_native(NativeWasmTypeInto::from_abi(&mut store, $x)) ),* ).into_result()
}));
let result = {
// let env: &Env = unsafe { &*(ptr as *const u8 as *const Env) };
let func: &Func = &*(&() as *const () as *const Func);
panic::catch_unwind(AssertUnwindSafe(|| {
let handle: StoreHandle<VMFunctionEnvironment> = StoreHandle::from_internal(store2.objects_mut().id(), InternalStoreHandle::from_index(handle_index).unwrap());
let env: FunctionEnvMut<T> = FunctionEnv::from_handle(handle).into_mut(&mut store2);
func(env, $( FromToNativeWasmType::from_native(NativeWasmTypeInto::from_abi(&mut store, $x)) ),* ).into_result()
}))
};

match result {
Ok(Ok(result)) => return result.into_c_struct(&mut store),
Expand Down Expand Up @@ -1425,4 +1458,4 @@ mod inner {
}
}
*/
}
}
79 changes: 53 additions & 26 deletions lib/api/src/js/externals/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use tracing::warn;

use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use wasmer_types::Pages;
use wasmer_types::{Pages, WASM_PAGE_SIZE};

use super::MemoryView;

Expand Down Expand Up @@ -86,6 +86,11 @@ impl Memory {
/// let m = Memory::new(&store, MemoryType::new(1, None, false)).unwrap();
/// ```
pub fn new(store: &mut impl AsStoreMut, ty: MemoryType) -> Result<Self, MemoryError> {
let vm_memory = VMMemory::new(Self::new_internal(ty.clone())?, ty);
Ok(Self::from_vm_export(store, vm_memory))
}

pub(crate) fn new_internal(ty: MemoryType) -> Result<js_sys::WebAssembly::Memory, MemoryError> {
let descriptor = js_sys::Object::new();
js_sys::Reflect::set(&descriptor, &"initial".into(), &ty.minimum.0.into()).unwrap();
if let Some(max) = ty.maximum {
Expand All @@ -96,31 +101,9 @@ impl Memory {
let js_memory = js_sys::WebAssembly::Memory::new(&descriptor)
.map_err(|_e| MemoryError::Generic("Error while creating the memory".to_owned()))?;

let vm_memory = VMMemory::new(js_memory, ty);
let handle = StoreHandle::new(store.objects_mut(), vm_memory);
Ok(Self::from_vm_extern(store, handle.internal_handle()))
}

/// Creates a new host `Memory` from provided JavaScript memory.
pub fn new_raw(
store: &mut impl AsStoreMut,
js_memory: js_sys::WebAssembly::Memory,
ty: MemoryType,
) -> Result<Self, MemoryError> {
let vm_memory = VMMemory::new(js_memory, ty);
let handle = StoreHandle::new(store.objects_mut(), vm_memory);
Ok(Self::from_vm_extern(store, handle.internal_handle()))
}

/// Create a memory object from an existing memory and attaches it to the store
pub fn new_from_existing(new_store: &mut impl AsStoreMut, memory: VMMemory) -> Self {
let handle = StoreHandle::new(new_store.objects_mut(), memory);
Self::from_vm_extern(new_store, handle.internal_handle())
}

/// To `VMExtern`.
pub(crate) fn to_vm_extern(&self) -> VMExtern {
VMExtern::Memory(self.handle.internal_handle())
Ok(
js_memory
)
}

/// Creates a new host `Memory` from provided JavaScript memory.
Expand Down Expand Up @@ -211,6 +194,44 @@ impl Memory {
Ok(Pages(new_pages))
}

/// Copies the memory to a new store and returns a memory reference to it
pub fn copy_to_store(
&self,
store: &impl AsStoreRef,
new_store: &mut impl AsStoreMut,
) -> Result<Self, MemoryError>
{
// Create the new memory using the parameters of the existing memory
let view = self.view(store);
let ty = self.ty(store);
let amount = view.data_size() as usize;

let new_memory = Self::new(new_store, ty)?;
let mut new_view = new_memory.view(&new_store);
let new_view_size = new_view.data_size() as usize;
if amount > new_view_size {
let delta = amount - new_view_size;
let pages = ((delta - 1) / WASM_PAGE_SIZE) + 1;
new_memory.grow(new_store, Pages(pages as u32))?;
new_view = new_memory.view(&new_store);
}

// Copy the bytes
view.copy_to_memory(amount as u64, &new_view)
.map_err(|err| {
MemoryError::Generic(err.to_string())
})?;

// Return the new memory
Ok(new_memory)
}

pub(crate) fn from_vm_export(store: &mut impl AsStoreMut, vm_memory: VMMemory) -> Self {
Self {
handle: StoreHandle::new(store.objects_mut(), vm_memory),
}
}

pub(crate) fn from_vm_extern(
store: &mut impl AsStoreMut,
internal: InternalStoreHandle<VMMemory>,
Expand All @@ -228,6 +249,12 @@ impl Memory {
mem.try_clone()
}

/// Copies this memory to a new memory
pub fn fork(&mut self, store: &impl AsStoreRef) -> Result<VMMemory, MemoryError> {
let mem = self.handle.get(store.as_store_ref().objects());
mem.fork()
}

/// Checks whether this `Global` can be used with the given context.
pub fn is_from_store(&self, store: &impl AsStoreRef) -> bool {
self.handle.store_id() == store.as_store_ref().objects().id()
Expand Down
Loading

0 comments on commit 572ea3e

Please sign in to comment.