From 99b0e03e4e88aec8161a752bb6049f5f1b9c1b2a Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Wed, 19 Jun 2019 18:06:27 +0200 Subject: [PATCH] Add feature flag for opt-in thread-safety The `atomic` dependency is necessary because Rusts's Atomic type doesn't support generic inner values. --- Cargo.toml | 2 ++ src/func.rs | 23 +++++++++---------- src/global.rs | 10 ++++----- src/lib.rs | 12 ++++++++++ src/memory.rs | 31 ++++++++++++-------------- src/module.rs | 41 ++++++++++++++++------------------ src/not_threadsafe.rs | 5 +++++ src/table.rs | 10 ++++----- src/threadsafe.rs | 52 +++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 123 insertions(+), 63 deletions(-) create mode 100644 src/not_threadsafe.rs create mode 100644 src/threadsafe.rs diff --git a/Cargo.toml b/Cargo.toml index a75cfbb..21d9944 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ memory_units = "0.3.0" libm = { version = "0.1.2", optional = true } num-rational = "0.2.2" num-traits = "0.2.8" +atomic = { version = "0.4", optional = true } [dev-dependencies] assert_matches = "1.1" @@ -24,6 +25,7 @@ rand = "0.4.2" wabt = "0.6" [features] +threadsafe = ["atomic"] default = ["std"] # Disable for no_std support std = [ diff --git a/src/func.rs b/src/func.rs index 5cc4a15..50aeeb8 100644 --- a/src/func.rs +++ b/src/func.rs @@ -1,6 +1,5 @@ #[allow(unused_imports)] use alloc::prelude::v1::*; -use alloc::rc::{Rc, Weak}; use core::fmt; use host::Externals; use isa; @@ -17,7 +16,7 @@ use {Signature, Trap}; /// /// [`FuncInstance`]: struct.FuncInstance.html #[derive(Clone, Debug)] -pub struct FuncRef(Rc); +pub struct FuncRef(::MyRc); impl ::core::ops::Deref for FuncRef { type Target = FuncInstance; @@ -45,9 +44,9 @@ pub struct FuncInstance(FuncInstanceInternal); #[derive(Clone)] pub(crate) enum FuncInstanceInternal { Internal { - signature: Rc, - module: Weak, - body: Rc, + signature: ::MyRc, + module: ::MyWeak, + body: ::MyRc, }, Host { signature: Signature, @@ -84,7 +83,7 @@ impl FuncInstance { signature, host_func_index, }; - FuncRef(Rc::new(FuncInstance(func))) + FuncRef(::MyRc::new(FuncInstance(func))) } /// Returns [signature] of this function instance. @@ -104,21 +103,21 @@ impl FuncInstance { } pub(crate) fn alloc_internal( - module: Weak, - signature: Rc, + module: ::MyWeak, + signature: ::MyRc, body: FuncBody, ) -> FuncRef { let func = FuncInstanceInternal::Internal { signature, module: module, - body: Rc::new(body), + body: ::MyRc::new(body), }; - FuncRef(Rc::new(FuncInstance(func))) + FuncRef(::MyRc::new(FuncInstance(func))) } - pub(crate) fn body(&self) -> Option> { + pub(crate) fn body(&self) -> Option<::MyRc> { match *self.as_internal() { - FuncInstanceInternal::Internal { ref body, .. } => Some(Rc::clone(body)), + FuncInstanceInternal::Internal { ref body, .. } => Some(::MyRc::clone(body)), FuncInstanceInternal::Host { .. } => None, } } diff --git a/src/global.rs b/src/global.rs index cde0d63..9174c1d 100644 --- a/src/global.rs +++ b/src/global.rs @@ -1,5 +1,3 @@ -use alloc::rc::Rc; -use core::cell::Cell; use parity_wasm::elements::ValueType as EValueType; use types::ValueType; use value::RuntimeValue; @@ -11,7 +9,7 @@ use Error; /// /// [`GlobalInstance`]: struct.GlobalInstance.html #[derive(Clone, Debug)] -pub struct GlobalRef(Rc); +pub struct GlobalRef(::MyRc); impl ::core::ops::Deref for GlobalRef { type Target = GlobalInstance; @@ -33,7 +31,7 @@ impl ::core::ops::Deref for GlobalRef { /// [`I64`]: enum.RuntimeValue.html#variant.I64 #[derive(Debug)] pub struct GlobalInstance { - val: Cell, + val: ::MyCell, mutable: bool, } @@ -43,8 +41,8 @@ impl GlobalInstance { /// Since it is possible to export only immutable globals, /// users likely want to set `mutable` to `false`. pub fn alloc(val: RuntimeValue, mutable: bool) -> GlobalRef { - GlobalRef(Rc::new(GlobalInstance { - val: Cell::new(val), + GlobalRef(::MyRc::new(GlobalInstance { + val: ::MyCell::new(val), mutable, })) } diff --git a/src/lib.rs b/src/lib.rs index 9384147..95384c3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -396,6 +396,12 @@ mod table; mod types; mod value; +#[cfg(feature = "threadsafe")] +mod threadsafe; + +#[cfg(not(feature = "threadsafe"))] +mod not_threadsafe; + #[cfg(test)] mod tests; @@ -410,6 +416,12 @@ pub use self::table::{TableInstance, TableRef}; pub use self::types::{GlobalDescriptor, MemoryDescriptor, Signature, TableDescriptor, ValueType}; pub use self::value::{Error as ValueError, FromRuntimeValue, LittleEndianConvert, RuntimeValue}; +#[cfg(feature = "threadsafe")] +pub use self::threadsafe::*; + +#[cfg(not(feature = "threadsafe"))] +pub use self::not_threadsafe::*; + /// WebAssembly-specific sizes and units. pub mod memory_units { pub use memory_units_crate::wasm32::*; diff --git a/src/memory.rs b/src/memory.rs index a1cbbb8..bf13183 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -1,8 +1,6 @@ #[allow(unused_imports)] use alloc::prelude::v1::*; -use alloc::rc::Rc; use core::{ - cell::{Cell, RefCell}, cmp, fmt, ops::Range, u32, @@ -26,7 +24,7 @@ pub const LINEAR_MEMORY_PAGE_SIZE: Bytes = Bytes(65536); /// [`MemoryInstance`]: struct.MemoryInstance.html /// #[derive(Clone, Debug)] -pub struct MemoryRef(Rc); +pub struct MemoryRef(::MyRc); impl ::core::ops::Deref for MemoryRef { type Target = MemoryInstance; @@ -52,11 +50,11 @@ pub struct MemoryInstance { /// Memory limits. limits: ResizableLimits, /// Linear memory buffer with lazy allocation. - buffer: RefCell>, + buffer: ::MyRefCell>, initial: Pages, - current_size: Cell, + current_size: ::MyCell, maximum: Option, - lowest_used: Cell, + lowest_used: ::MyCell, } impl fmt::Debug for MemoryInstance { @@ -127,7 +125,7 @@ impl MemoryInstance { } let memory = MemoryInstance::new(initial, maximum); - Ok(MemoryRef(Rc::new(memory))) + Ok(MemoryRef(::MyRc::new(memory))) } /// Create new linear memory instance. @@ -137,11 +135,11 @@ impl MemoryInstance { let initial_size: Bytes = initial.into(); MemoryInstance { limits: limits, - buffer: RefCell::new(Vec::with_capacity(4096)), + buffer: ::MyRefCell::new(Vec::with_capacity(4096)), initial: initial, - current_size: Cell::new(initial_size.0), + current_size: ::MyCell::new(initial_size.0), maximum: maximum, - lowest_used: Cell::new(u32::max_value()), + lowest_used: ::MyCell::new(u32::max_value()), } } @@ -558,7 +556,6 @@ mod tests { use super::{MemoryInstance, MemoryRef, LINEAR_MEMORY_PAGE_SIZE}; use memory_units::Pages; - use std::rc::Rc; use Error; #[test] @@ -660,8 +657,8 @@ mod tests { #[test] fn transfer_works() { - let src = MemoryRef(Rc::new(create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]))); - let dst = MemoryRef(Rc::new(create_memory(&[ + let src = MemoryRef(::MyRc::new(create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]))); + let dst = MemoryRef(::MyRc::new(create_memory(&[ 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, ]))); @@ -676,7 +673,7 @@ mod tests { #[test] fn transfer_still_works_with_same_memory() { - let src = MemoryRef(Rc::new(create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]))); + let src = MemoryRef(::MyRc::new(create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]))); MemoryInstance::transfer(&src, 4, &src, 0, 3).unwrap(); @@ -685,7 +682,7 @@ mod tests { #[test] fn transfer_oob_with_same_memory_errors() { - let src = MemoryRef(Rc::new(create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]))); + let src = MemoryRef(::MyRc::new(create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]))); assert!(MemoryInstance::transfer(&src, 65535, &src, 0, 3).is_err()); // Check that memories content left untouched @@ -694,8 +691,8 @@ mod tests { #[test] fn transfer_oob_errors() { - let src = MemoryRef(Rc::new(create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]))); - let dst = MemoryRef(Rc::new(create_memory(&[ + let src = MemoryRef(::MyRc::new(create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]))); + let dst = MemoryRef(::MyRc::new(create_memory(&[ 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, ]))); diff --git a/src/module.rs b/src/module.rs index dbcbc99..d167fe0 100644 --- a/src/module.rs +++ b/src/module.rs @@ -1,13 +1,10 @@ #[allow(unused_imports)] use alloc::prelude::v1::*; -use alloc::rc::Rc; -use core::cell::RefCell; use core::fmt; use Trap; use alloc::collections::BTreeMap; -use core::cell::Ref; use func::{FuncBody, FuncInstance, FuncRef}; use global::{GlobalInstance, GlobalRef}; use host::Externals; @@ -35,7 +32,7 @@ use {Error, MemoryInstance, Module, RuntimeValue, Signature, TableInstance}; /// /// [`ModuleInstance`]: struct.ModuleInstance.html #[derive(Clone, Debug)] -pub struct ModuleRef(pub(crate) Rc); +pub struct ModuleRef(pub(crate) ::MyRc); impl ::core::ops::Deref for ModuleRef { type Target = ModuleInstance; @@ -154,23 +151,23 @@ impl ExternVal { /// [`invoke_export`]: #method.invoke_export #[derive(Debug)] pub struct ModuleInstance { - signatures: RefCell>>, - tables: RefCell>, - funcs: RefCell>, - memories: RefCell>, - globals: RefCell>, - exports: RefCell>, + signatures: ::MyRefCell>>, + tables: ::MyRefCell>, + funcs: ::MyRefCell>, + memories: ::MyRefCell>, + globals: ::MyRefCell>, + exports: ::MyRefCell>, } impl ModuleInstance { fn default() -> Self { ModuleInstance { - funcs: RefCell::new(Vec::new()), - signatures: RefCell::new(Vec::new()), - tables: RefCell::new(Vec::new()), - memories: RefCell::new(Vec::new()), - globals: RefCell::new(Vec::new()), - exports: RefCell::new(BTreeMap::new()), + funcs: ::MyRefCell::new(Vec::new()), + signatures: ::MyRefCell::new(Vec::new()), + tables: ::MyRefCell::new(Vec::new()), + memories: ::MyRefCell::new(Vec::new()), + globals: ::MyRefCell::new(Vec::new()), + exports: ::MyRefCell::new(BTreeMap::new()), } } @@ -190,7 +187,7 @@ impl ModuleInstance { self.funcs.borrow().get(idx as usize).cloned() } - pub(crate) fn signature_by_index(&self, idx: u32) -> Option> { + pub(crate) fn signature_by_index(&self, idx: u32) -> Option<::MyRc> { self.signatures.borrow().get(idx as usize).cloned() } @@ -198,7 +195,7 @@ impl ModuleInstance { self.funcs.borrow_mut().push(func); } - fn push_signature(&self, signature: Rc) { + fn push_signature(&self, signature: ::MyRc) { self.signatures.borrow_mut().push(signature) } @@ -216,7 +213,7 @@ impl ModuleInstance { /// Access all globals. This is a non-standard API so it's unlikely to be /// portable to other engines. - pub fn globals<'a>(&self) -> Ref> { + pub fn globals<'a>(&self) -> ::MyRef> { self.globals.borrow() } @@ -229,10 +226,10 @@ impl ModuleInstance { extern_vals: I, ) -> Result { let module = loaded_module.module(); - let instance = ModuleRef(Rc::new(ModuleInstance::default())); + let instance = ModuleRef(::MyRc::new(ModuleInstance::default())); for &Type::Function(ref ty) in module.type_section().map(|ts| ts.types()).unwrap_or(&[]) { - let signature = Rc::new(Signature::from_elements(ty)); + let signature = ::MyRc::new(Signature::from_elements(ty)); instance.push_signature(signature); } @@ -327,7 +324,7 @@ impl ModuleInstance { code: code, }; let func_instance = - FuncInstance::alloc_internal(Rc::downgrade(&instance.0), signature, func_body); + FuncInstance::alloc_internal(::MyRc::downgrade(&instance.0), signature, func_body); instance.push_func(func_instance); } } diff --git a/src/not_threadsafe.rs b/src/not_threadsafe.rs new file mode 100644 index 0000000..d17d25a --- /dev/null +++ b/src/not_threadsafe.rs @@ -0,0 +1,5 @@ +pub use alloc::rc::Rc as MyRc; +pub use alloc::rc::Weak as MyWeak; +pub use core::cell::Cell as MyCell; +pub use core::cell::RefCell as MyRefCell; +pub use core::cell::Ref as MyRef; diff --git a/src/table.rs b/src/table.rs index 63b633f..8ca77e1 100644 --- a/src/table.rs +++ b/src/table.rs @@ -1,7 +1,5 @@ #[allow(unused_imports)] use alloc::prelude::v1::*; -use alloc::rc::Rc; -use core::cell::RefCell; use core::fmt; use core::u32; use func::FuncRef; @@ -16,7 +14,7 @@ use Error; /// [`TableInstance`]: struct.TableInstance.html /// #[derive(Clone, Debug)] -pub struct TableRef(Rc); +pub struct TableRef(::MyRc); impl ::core::ops::Deref for TableRef { type Target = TableInstance; @@ -42,7 +40,7 @@ pub struct TableInstance { /// Table limits. limits: ResizableLimits, /// Table memory buffer. - buffer: RefCell>>, + buffer: ::MyRefCell>>, } impl fmt::Debug for TableInstance { @@ -67,13 +65,13 @@ impl TableInstance { /// Returns `Err` if `initial_size` is greater than `maximum_size`. pub fn alloc(initial_size: u32, maximum_size: Option) -> Result { let table = TableInstance::new(ResizableLimits::new(initial_size, maximum_size))?; - Ok(TableRef(Rc::new(table))) + Ok(TableRef(::MyRc::new(table))) } fn new(limits: ResizableLimits) -> Result { check_limits(&limits)?; Ok(TableInstance { - buffer: RefCell::new(vec![None; limits.initial() as usize]), + buffer: ::MyRefCell::new(vec![None; limits.initial() as usize]), limits: limits, }) } diff --git a/src/threadsafe.rs b/src/threadsafe.rs new file mode 100644 index 0000000..0705c2f --- /dev/null +++ b/src/threadsafe.rs @@ -0,0 +1,52 @@ +extern crate atomic; + +use alloc::sync::{Arc, Mutex}; + +pub use alloc::sync::{ + Arc as MyRc, Weak as MyWeak, MutexGuard as MyRef, +}; +pub use self::atomic::{ + Atomic, Ordering::Relaxed as Ordering, +}; + +/// Thread-safe wrapper which can be used in place of a `RefCell`. +#[derive(Debug)] +pub struct MyRefCell(Arc>); + +impl MyRefCell { + /// Create new wrapper object. + pub fn new(obj: T) -> MyRefCell { + MyRefCell(Arc::new(Mutex::new(obj))) + } + + /// Borrow a `MyRef` to the inner value. + pub fn borrow(&self) -> ::MyRef { + self.0.lock().expect("bar") + } + + /// Borrow a mutable `MyRef` to the inner value. + pub fn borrow_mut(&self) -> ::MyRef { + self.0.lock().expect("bar") + } +} + +/// Thread-safe wrapper which can be used in place of a `Cell`. +#[derive(Debug)] +pub struct MyCell(Atomic) where T: Copy; + +impl MyCell where T: Copy { + /// Create new wrapper object. + pub fn new(obj: T) -> MyCell { + MyCell(Atomic::new(obj)) + } + + /// Returns the inner value. + pub fn get(&self) -> T { + self.0.load(::Ordering) + } + + /// Sets the inner value. + pub fn set(&self, val: T) { + self.0.store(val, ::Ordering); + } +}