mod interpreter;
#[cfg(all(target_arch = "x86_64", feature = "std"))]
mod jit;
mod tests;
use alloc::{string::String, vec::Vec};
use core::fmt;
use smallvec::SmallVec;
#[derive(Clone)]
pub struct Module {
inner: ModuleInner,
}
#[derive(Clone)]
enum ModuleInner {
#[cfg(all(target_arch = "x86_64", feature = "std"))]
Jit(jit::Module),
Interpreter(interpreter::Module),
}
impl Module {
pub fn new(module: impl AsRef<[u8]>, exec_hint: ExecHint) -> Result<Self, ModuleError> {
Ok(Module {
inner: match exec_hint {
#[cfg(all(target_arch = "x86_64", feature = "std"))]
ExecHint::CompileAheadOfTime => ModuleInner::Jit(jit::Module::new(module)?),
#[cfg(not(all(target_arch = "x86_64", feature = "std")))]
ExecHint::CompileAheadOfTime => {
ModuleInner::Interpreter(interpreter::Module::new(module)?)
}
ExecHint::Oneshot | ExecHint::Untrusted | ExecHint::ForceWasmi => {
ModuleInner::Interpreter(interpreter::Module::new(module)?)
}
#[cfg(all(target_arch = "x86_64", feature = "std"))]
ExecHint::ForceWasmtime => ModuleInner::Jit(jit::Module::new(module)?),
},
})
}
}
pub struct VirtualMachinePrototype {
inner: VirtualMachinePrototypeInner,
}
enum VirtualMachinePrototypeInner {
#[cfg(all(target_arch = "x86_64", feature = "std"))]
Jit(jit::JitPrototype),
Interpreter(interpreter::InterpreterPrototype),
}
impl VirtualMachinePrototype {
pub fn new(
module: &Module,
symbols: impl FnMut(&str, &str, &Signature) -> Result<usize, ()>,
) -> Result<Self, NewErr> {
Ok(VirtualMachinePrototype {
inner: match &module.inner {
ModuleInner::Interpreter(module) => VirtualMachinePrototypeInner::Interpreter(
interpreter::InterpreterPrototype::new(module, symbols)?,
),
#[cfg(all(target_arch = "x86_64", feature = "std"))]
ModuleInner::Jit(module) => {
VirtualMachinePrototypeInner::Jit(jit::JitPrototype::new(module, symbols)?)
}
},
})
}
pub fn global_value(&mut self, name: &str) -> Result<u32, GlobalValueErr> {
match &mut self.inner {
#[cfg(all(target_arch = "x86_64", feature = "std"))]
VirtualMachinePrototypeInner::Jit(inner) => inner.global_value(name),
VirtualMachinePrototypeInner::Interpreter(inner) => inner.global_value(name),
}
}
pub fn memory_max_pages(&self) -> Option<HeapPages> {
match &self.inner {
#[cfg(all(target_arch = "x86_64", feature = "std"))]
VirtualMachinePrototypeInner::Jit(inner) => inner.memory_max_pages(),
VirtualMachinePrototypeInner::Interpreter(inner) => inner.memory_max_pages(),
}
}
pub fn start(
mut self,
min_memory_pages: HeapPages,
function_name: &str,
params: &[WasmValue],
) -> Result<VirtualMachine, (StartErr, Self)> {
Ok(VirtualMachine {
inner: match self.inner {
#[cfg(all(target_arch = "x86_64", feature = "std"))]
VirtualMachinePrototypeInner::Jit(inner) => {
match inner.start(min_memory_pages, function_name, params) {
Ok(vm) => VirtualMachineInner::Jit(vm),
Err((err, proto)) => {
self.inner = VirtualMachinePrototypeInner::Jit(proto);
return Err((err, self));
}
}
}
VirtualMachinePrototypeInner::Interpreter(inner) => {
match inner.start(min_memory_pages, function_name, params) {
Ok(vm) => VirtualMachineInner::Interpreter(vm),
Err((err, proto)) => {
self.inner = VirtualMachinePrototypeInner::Interpreter(proto);
return Err((err, self));
}
}
}
},
})
}
}
impl fmt::Debug for VirtualMachinePrototype {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self.inner {
#[cfg(all(target_arch = "x86_64", feature = "std"))]
VirtualMachinePrototypeInner::Jit(inner) => fmt::Debug::fmt(inner, f),
VirtualMachinePrototypeInner::Interpreter(inner) => fmt::Debug::fmt(inner, f),
}
}
}
pub struct VirtualMachine {
inner: VirtualMachineInner,
}
enum VirtualMachineInner {
#[cfg(all(target_arch = "x86_64", feature = "std"))]
Jit(jit::Jit),
Interpreter(interpreter::Interpreter),
}
impl VirtualMachine {
pub fn run(&mut self, value: Option<WasmValue>) -> Result<ExecOutcome, RunErr> {
match &mut self.inner {
#[cfg(all(target_arch = "x86_64", feature = "std"))]
VirtualMachineInner::Jit(inner) => inner.run(value),
VirtualMachineInner::Interpreter(inner) => inner.run(value),
}
}
pub fn memory_size(&self) -> HeapPages {
match &self.inner {
#[cfg(all(target_arch = "x86_64", feature = "std"))]
VirtualMachineInner::Jit(inner) => inner.memory_size(),
VirtualMachineInner::Interpreter(inner) => inner.memory_size(),
}
}
pub fn read_memory(
&'_ self,
offset: u32,
size: u32,
) -> Result<impl AsRef<[u8]> + '_, OutOfBoundsError> {
Ok(match &self.inner {
#[cfg(all(target_arch = "x86_64", feature = "std"))]
VirtualMachineInner::Jit(inner) => either::Left(inner.read_memory(offset, size)?),
#[cfg(all(target_arch = "x86_64", feature = "std"))]
VirtualMachineInner::Interpreter(inner) => {
either::Right(inner.read_memory(offset, size)?)
}
#[cfg(not(all(target_arch = "x86_64", feature = "std")))]
VirtualMachineInner::Interpreter(inner) => inner.read_memory(offset, size)?,
})
}
pub fn write_memory(&mut self, offset: u32, value: &[u8]) -> Result<(), OutOfBoundsError> {
match &mut self.inner {
#[cfg(all(target_arch = "x86_64", feature = "std"))]
VirtualMachineInner::Jit(inner) => inner.write_memory(offset, value),
VirtualMachineInner::Interpreter(inner) => inner.write_memory(offset, value),
}
}
pub fn grow_memory(&mut self, additional: HeapPages) -> Result<(), OutOfBoundsError> {
match &mut self.inner {
#[cfg(all(target_arch = "x86_64", feature = "std"))]
VirtualMachineInner::Jit(inner) => inner.grow_memory(additional),
VirtualMachineInner::Interpreter(inner) => inner.grow_memory(additional),
}
}
pub fn into_prototype(self) -> VirtualMachinePrototype {
VirtualMachinePrototype {
inner: match self.inner {
#[cfg(all(target_arch = "x86_64", feature = "std"))]
VirtualMachineInner::Jit(inner) => {
VirtualMachinePrototypeInner::Jit(inner.into_prototype())
}
VirtualMachineInner::Interpreter(inner) => {
VirtualMachinePrototypeInner::Interpreter(inner.into_prototype())
}
},
}
}
}
impl fmt::Debug for VirtualMachine {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self.inner {
#[cfg(all(target_arch = "x86_64", feature = "std"))]
VirtualMachineInner::Jit(inner) => fmt::Debug::fmt(inner, f),
VirtualMachineInner::Interpreter(inner) => fmt::Debug::fmt(inner, f),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ExecHint {
CompileAheadOfTime,
Oneshot,
Untrusted,
ForceWasmi,
#[cfg(all(target_arch = "x86_64", feature = "std"))]
#[cfg_attr(docsrs, doc(cfg(all(target_arch = "x86_64", feature = "std"))))]
ForceWasmtime,
}
impl ExecHint {
pub fn force_wasmtime_if_available() -> Option<ExecHint> {
#[cfg(all(target_arch = "x86_64", feature = "std"))]
fn value() -> Option<ExecHint> {
Some(ExecHint::ForceWasmtime)
}
#[cfg(not(all(target_arch = "x86_64", feature = "std")))]
fn value() -> Option<ExecHint> {
None
}
value()
}
}
#[derive(
Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, derive_more::Add, derive_more::Sub,
)]
pub struct HeapPages(u32);
impl HeapPages {
pub const fn new(v: u32) -> Self {
HeapPages(v)
}
}
impl From<u32> for HeapPages {
fn from(v: u32) -> Self {
HeapPages(v)
}
}
impl From<HeapPages> for u32 {
fn from(v: HeapPages) -> Self {
v.0
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Signature {
params: SmallVec<[ValueType; 8]>,
ret_ty: Option<ValueType>,
}
impl Signature {
pub fn new(
params: impl Iterator<Item = ValueType>,
ret_ty: impl Into<Option<ValueType>>,
) -> Signature {
Signature {
params: params.collect(),
ret_ty: ret_ty.into(),
}
}
pub fn parameters(&self) -> impl ExactSizeIterator<Item = &ValueType> {
self.params.iter()
}
pub fn return_type(&self) -> Option<&ValueType> {
self.ret_ty.as_ref()
}
}
impl<'a> From<&'a Signature> for wasmi::Signature {
fn from(sig: &'a Signature) -> Self {
wasmi::Signature::new(
sig.params
.iter()
.copied()
.map(wasmi::ValueType::from)
.collect::<Vec<_>>(),
sig.ret_ty.map(wasmi::ValueType::from),
)
}
}
impl From<Signature> for wasmi::Signature {
fn from(sig: Signature) -> wasmi::Signature {
wasmi::Signature::from(&sig)
}
}
impl<'a> TryFrom<&'a wasmi::Signature> for Signature {
type Error = UnsupportedTypeError;
fn try_from(sig: &'a wasmi::Signature) -> Result<Self, Self::Error> {
Ok(Signature {
params: sig
.params()
.iter()
.copied()
.map(ValueType::try_from)
.collect::<Result<_, _>>()?,
ret_ty: sig.return_type().map(ValueType::try_from).transpose()?,
})
}
}
#[cfg(all(target_arch = "x86_64", feature = "std"))]
impl<'a> TryFrom<&'a wasmtime::FuncType> for Signature {
type Error = UnsupportedTypeError;
fn try_from(sig: &'a wasmtime::FuncType) -> Result<Self, Self::Error> {
if sig.results().len() > 1 {
return Err(UnsupportedTypeError);
}
Ok(Signature {
params: sig
.params()
.map(ValueType::try_from)
.collect::<Result<_, _>>()?,
ret_ty: sig.results().next().map(ValueType::try_from).transpose()?,
})
}
}
impl TryFrom<wasmi::Signature> for Signature {
type Error = UnsupportedTypeError;
fn try_from(sig: wasmi::Signature) -> Result<Self, Self::Error> {
Signature::try_from(&sig)
}
}
#[derive(Debug, Copy, Clone)]
pub enum WasmValue {
I32(i32),
I64(i64),
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum ValueType {
I32,
I64,
}
impl WasmValue {
pub fn ty(&self) -> ValueType {
match self {
WasmValue::I32(_) => ValueType::I32,
WasmValue::I64(_) => ValueType::I64,
}
}
pub fn into_i32(self) -> Option<i32> {
if let WasmValue::I32(v) = self {
Some(v)
} else {
None
}
}
pub fn into_i64(self) -> Option<i64> {
if let WasmValue::I64(v) = self {
Some(v)
} else {
None
}
}
}
impl TryFrom<wasmi::RuntimeValue> for WasmValue {
type Error = UnsupportedTypeError;
fn try_from(val: wasmi::RuntimeValue) -> Result<Self, Self::Error> {
match val {
wasmi::RuntimeValue::I32(v) => Ok(WasmValue::I32(v)),
wasmi::RuntimeValue::I64(v) => Ok(WasmValue::I64(v)),
_ => Err(UnsupportedTypeError),
}
}
}
impl From<WasmValue> for wasmi::RuntimeValue {
fn from(val: WasmValue) -> Self {
match val {
WasmValue::I32(v) => wasmi::RuntimeValue::I32(v),
WasmValue::I64(v) => wasmi::RuntimeValue::I64(v),
}
}
}
#[cfg(all(target_arch = "x86_64", feature = "std"))]
impl From<WasmValue> for wasmtime::Val {
fn from(val: WasmValue) -> Self {
match val {
WasmValue::I32(v) => wasmtime::Val::I32(v),
WasmValue::I64(v) => wasmtime::Val::I64(v),
}
}
}
#[cfg(all(target_arch = "x86_64", feature = "std"))]
impl<'a> TryFrom<&'a wasmtime::Val> for WasmValue {
type Error = UnsupportedTypeError;
fn try_from(val: &'a wasmtime::Val) -> Result<Self, Self::Error> {
match val {
wasmtime::Val::I32(v) => Ok(WasmValue::I32(*v)),
wasmtime::Val::I64(v) => Ok(WasmValue::I64(*v)),
_ => Err(UnsupportedTypeError),
}
}
}
impl From<ValueType> for wasmi::ValueType {
fn from(ty: ValueType) -> wasmi::ValueType {
match ty {
ValueType::I32 => wasmi::ValueType::I32,
ValueType::I64 => wasmi::ValueType::I64,
}
}
}
impl TryFrom<wasmi::ValueType> for ValueType {
type Error = UnsupportedTypeError;
fn try_from(val: wasmi::ValueType) -> Result<Self, Self::Error> {
match val {
wasmi::ValueType::I32 => Ok(ValueType::I32),
wasmi::ValueType::I64 => Ok(ValueType::I64),
_ => Err(UnsupportedTypeError),
}
}
}
#[cfg(all(target_arch = "x86_64", feature = "std"))]
impl TryFrom<wasmtime::ValType> for ValueType {
type Error = UnsupportedTypeError;
fn try_from(val: wasmtime::ValType) -> Result<Self, Self::Error> {
match val {
wasmtime::ValType::I32 => Ok(ValueType::I32),
wasmtime::ValType::I64 => Ok(ValueType::I64),
_ => Err(UnsupportedTypeError),
}
}
}
#[derive(Debug, derive_more::Display)]
pub struct UnsupportedTypeError;
#[derive(Debug)]
pub enum ExecOutcome {
Finished {
return_value: Result<Option<WasmValue>, Trap>,
},
Interrupted {
id: usize,
params: Vec<WasmValue>,
},
}
#[derive(Debug, derive_more::Display, Clone)]
#[display(fmt = "{}", _0)]
pub struct Trap(String);
#[derive(Debug, derive_more::Display, Clone)]
pub enum NewErr {
#[display(fmt = "Unresolved function `{}`:`{}`", module_name, function)]
UnresolvedFunctionImport {
function: String,
module_name: String,
},
#[display(fmt = "Start function not supported")]
StartFunctionNotSupported,
#[display(fmt = "If a \"memory\" symbol is provided, it must be a memory.")]
MemoryIsntMemory,
MemoryNotNamedMemory,
NoMemory,
TwoMemories,
#[display(fmt = "If a \"__indirect_function_table\" symbol is provided, it must be a table.")]
IndirectTableIsntTable,
CouldntAllocateMemory,
#[display(fmt = "{}", _0)]
ModuleError(ModuleError),
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
impl std::error::Error for NewErr {}
#[derive(Debug, Clone, derive_more::Display)]
pub enum StartErr {
RequiredMemoryTooLarge,
#[display(fmt = "Function to start was not found.")]
FunctionNotFound,
#[display(fmt = "Symbol to start is not a function.")]
NotAFunction,
#[display(fmt = "Function to start uses unsupported signature.")]
SignatureNotSupported,
}
#[derive(Debug, derive_more::Display, Clone)]
#[display(fmt = "{}", _0)]
pub struct ModuleError(String);
#[derive(Debug, derive_more::Display)]
#[display(fmt = "Out of bounds when accessing virtual machine memory")]
pub struct OutOfBoundsError;
#[derive(Debug, derive_more::Display)]
pub enum RunErr {
#[display(fmt = "State machine is poisoned")]
Poisoned,
#[display(
fmt = "Expected value of type {:?} but got {:?} instead",
expected,
obtained
)]
BadValueTy {
expected: Option<ValueType>,
obtained: Option<ValueType>,
},
}
#[derive(Debug, derive_more::Display)]
pub enum GlobalValueErr {
NotFound,
Invalid,
}