Skip to content

Commit

Permalink
Merge pull request #566 from wasmx/rust
Browse files Browse the repository at this point in the history
Rust bindings (unsafe execution)
  • Loading branch information
axic authored Nov 19, 2020
2 parents c290d0d + 9d05550 commit bc60be5
Show file tree
Hide file tree
Showing 2 changed files with 274 additions and 2 deletions.
3 changes: 3 additions & 0 deletions bindings/rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ description = "Bindings to Fizzy"
categories = ["external-ffi-bindings"]
edition = "2018"

[dev-dependencies]
hex = "0.4.2"

[build-dependencies]
bindgen = "0.54.0"
cmake = "0.1"
Expand Down
273 changes: 271 additions & 2 deletions bindings/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,198 @@
// SPDX-License-Identifier: Apache-2.0

//! This is a Rust interface to [Fizzy](https://github.com/wasmx/fizzy), a WebAssembly virtual machine.
//!
//! Currently it is only possible to validate inputs, but soon execution will be supported too.
mod sys;

use std::ffi::CString;
use std::ptr::NonNull;

/// Parse and validate the input according to WebAssembly 1.0 rules. Returns true if the supplied input is valid.
pub fn validate<T: AsRef<[u8]>>(input: T) -> bool {
unsafe { sys::fizzy_validate(input.as_ref().as_ptr(), input.as_ref().len()) }
}

/// A parsed and validated WebAssembly 1.0 module.
// NOTE: cannot use NonNull here given this is *const
pub struct Module(*const sys::FizzyModule);

impl Drop for Module {
fn drop(&mut self) {
unsafe { sys::fizzy_free_module(self.0) }
}
}

/// Parse and validate the input according to WebAssembly 1.0 rules.
pub fn parse<T: AsRef<[u8]>>(input: &T) -> Result<Module, ()> {
let ptr = unsafe { sys::fizzy_parse(input.as_ref().as_ptr(), input.as_ref().len()) };
if ptr.is_null() {
return Err(());
}
Ok(Module { 0: ptr })
}

/// An instance of a module.
pub struct Instance(NonNull<sys::FizzyInstance>);

impl Drop for Instance {
fn drop(&mut self) {
unsafe { sys::fizzy_free_instance(self.0.as_ptr()) }
}
}

impl Module {
/// Create an instance of a module.
// TODO: support imported functions
pub fn instantiate(self) -> Result<Instance, ()> {
if self.0.is_null() {
return Err(());
}
let ptr = unsafe {
sys::fizzy_instantiate(
self.0,
std::ptr::null(),
0,
std::ptr::null(),
std::ptr::null(),
std::ptr::null(),
0,
)
};
// Forget Module (and avoid calling drop) because it has been consumed by instantiate (even if it failed).
core::mem::forget(self);
if ptr.is_null() {
return Err(());
}
Ok(Instance {
0: unsafe { NonNull::new_unchecked(ptr) },
})
}
}

// TODO: add a proper wrapper (enum type for Value, and Result<Value, Trap> for ExecutionResult)

/// A WebAssembly value of i32/i64/f32/f64.
pub type Value = sys::FizzyValue;

// NOTE: the union does not have i32
impl Value {
pub fn as_i32(&self) -> i32 {
unsafe { self.i64 as i32 }
}
pub fn as_u32(&self) -> u32 {
unsafe { self.i64 as u32 }
}
pub fn as_i64(&self) -> i64 {
unsafe { self.i64 as i64 }
}
pub fn as_u64(&self) -> u64 {
unsafe { self.i64 }
}
pub fn as_f32(&self) -> f32 {
unsafe { self.f32 }
}
pub fn as_f64(&self) -> f64 {
unsafe { self.f64 }
}
}

impl From<i32> for Value {
fn from(v: i32) -> Self {
Value { i64: v as u64 }
}
}

impl From<u32> for Value {
fn from(v: u32) -> Self {
Value { i64: v as u64 }
}
}

impl From<i64> for Value {
fn from(v: i64) -> Self {
Value { i64: v as u64 }
}
}

impl From<u64> for Value {
fn from(v: u64) -> Self {
Value { i64: v }
}
}
impl From<f32> for Value {
fn from(v: f32) -> Self {
Value { f32: v }
}
}

impl From<f64> for Value {
fn from(v: f64) -> Self {
Value { f64: v }
}
}

/// The result of an execution.
pub struct ExecutionResult(sys::FizzyExecutionResult);

impl ExecutionResult {
/// True if execution has resulted in a trap.
pub fn trapped(&self) -> bool {
self.0.trapped
}

/// The optional return value. Only a single return value is allowed in WebAssembly 1.0.
pub fn value(&self) -> Option<Value> {
if self.0.has_value {
assert!(!self.0.trapped);
Some(self.0.value)
} else {
None
}
}
}

impl From<ExecutionResult> for sys::FizzyExecutionResult {
fn from(v: ExecutionResult) -> Self {
v.0
}
}

impl Instance {
/// Find index of exported function by name.
pub fn find_exported_function_index(&self, name: &str) -> Option<u32> {
let module = unsafe { sys::fizzy_get_instance_module(self.0.as_ptr()) };
let name = CString::new(name).expect("CString::new failed");
let mut func_idx: u32 = 0;
let found = unsafe {
sys::fizzy_find_exported_function_index(module, name.as_ptr(), &mut func_idx)
};
if found {
Some(func_idx)
} else {
None
}
}

/// Unsafe execution of a given function index `func_idx` with the given values `args`.
///
/// An invalid index, invalid inputs, or invalid depth can cause undefined behaviour.
///
/// The `depth` argument sets the initial call depth. Should be set to `0`.
///
/// # Safety
/// This function expects a valid `func_idx`, appropriate number of `args`, and valid `depth`.
pub unsafe fn unsafe_execute(
&mut self,
func_idx: u32,
args: &[Value],
depth: i32,
) -> ExecutionResult {
ExecutionResult {
0: sys::fizzy_execute(self.0.as_ptr(), func_idx, args.as_ptr(), depth),
}
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand All @@ -34,4 +216,91 @@ mod tests {
false
);
}

#[test]
fn parse_wasm() {
assert!(parse(&[0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00]).is_ok());
assert!(parse(&[0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x01]).is_err());
}

#[test]
fn instantiate_wasm() {
let module = parse(&[0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00]);
assert!(module.is_ok());
let instance = module.unwrap().instantiate();
assert!(instance.is_ok());
}

#[test]
fn find_exported_function_index() {
/* wat2wasm
(module
(func $f (export "foo") (result i32) (i32.const 42))
(global (export "g1") i32 (i32.const 0))
(table (export "tab") 0 anyfunc)
(memory (export "mem") 1 2)
)
*/
let input = hex::decode(
"0061736d010000000105016000017f030201000404017000000504010101020606017f0041000b07180403666f6f00000267310300037461620100036d656d02000a06010400412a0b").unwrap();

let module = parse(&input);
assert!(module.is_ok());
let instance = module.unwrap().instantiate();
assert!(instance.is_ok());
let mut instance = instance.unwrap();

let func_idx = instance.find_exported_function_index(&"foo");
assert!(func_idx.is_some());
assert_eq!(func_idx.unwrap(), 0);

assert!(instance.find_exported_function_index(&"bar").is_none());
assert!(instance.find_exported_function_index(&"g1").is_none());
assert!(instance.find_exported_function_index(&"tab").is_none());
assert!(instance.find_exported_function_index(&"mem").is_none());
}

#[test]
fn unsafe_execute_wasm() {
/* wat2wasm
(func)
(func (result i32) i32.const 42)
(func (param i32 i32) (result i32)
(i32.div_u (local.get 0) (local.get 1))
)
(func unreachable)
*/
let input = hex::decode("0061736d01000000010e036000006000017f60027f7f017f030504000102000a150402000b0400412a0b0700200020016e0b0300000b").unwrap();
let module = parse(&input);
assert!(module.is_ok());
let instance = module.unwrap().instantiate();
assert!(instance.is_ok());
let mut instance = instance.unwrap();

let result = unsafe { instance.unsafe_execute(0, &[], 0) };
assert!(!result.trapped());
assert!(!result.value().is_some());

let result = unsafe { instance.unsafe_execute(1, &[], 0) };
assert!(!result.trapped());
assert!(result.value().is_some());
assert_eq!(result.value().unwrap().as_i32(), 42);

// Explicit type specification
let result =
unsafe { instance.unsafe_execute(2, &[(42 as i32).into(), (2 as i32).into()], 0) };
assert!(!result.trapped());
assert!(result.value().is_some());
assert_eq!(result.value().unwrap().as_i32(), 21);

// Implicit i64 types (even though the code expects i32)
let result = unsafe { instance.unsafe_execute(2, &[42.into(), 2.into()], 0) };
assert!(!result.trapped());
assert!(result.value().is_some());
assert_eq!(result.value().unwrap().as_i32(), 21);

let result = unsafe { instance.unsafe_execute(3, &[], 0) };
assert!(result.trapped());
assert!(!result.value().is_some());
}
}

0 comments on commit bc60be5

Please sign in to comment.