Table and FuncRef

In this example, we'll present how to use Table and FuncRef stored in a slot of a Table instance to implement indirect function invocation.

The code in the following example is verified on

  • wasmedge-sdk v0.5.0
  • wasmedge-sys v0.10.0
  • wasmedge-types v0.3.0

Let's start off by getting all imports right away so you can follow along

#![allow(unused)]
fn main() {
// If the version of rust used is less than v1.63, please uncomment the follow attribute.
// #![feature(explicit_generic_args_with_impl_trait)]

#![feature(never_type)]

use wasmedge_sdk::{
    config::{CommonConfigOptions, ConfigBuilder},
    error::HostFuncError,
    host_function, params,
    types::Val,
    Caller, Executor, Func, ImportObjectBuilder, RefType, Store, Table, TableType, ValType,
    WasmVal, WasmValue,
};
}

Define host function

In this example we defines a native function real_add that takes two numbers and returns their sum. This function will be registered as a host function into WasmEdge runtime environment

#![allow(unused)]
fn main() {
#[host_function]
fn real_add(_caller: &Caller, input: Vec<WasmValue>) -> Result<Vec<WasmValue>, HostFuncError> {
    println!("Rust: Entering Rust function real_add");

    if input.len() != 2 {
        return Err(HostFuncError::User(1));
    }

    let a = if input[0].ty() == ValType::I32 {
        input[0].to_i32()
    } else {
        return Err(HostFuncError::User(2));
    };

    let b = if input[1].ty() == ValType::I32 {
        input[1].to_i32()
    } else {
        return Err(HostFuncError::User(3));
    };

    let c = a + b;
    println!("Rust: calculating in real_add c: {:?}", c);

    println!("Rust: Leaving Rust function real_add");
    Ok(vec![WasmValue::from_i32(c)])
}
}

Register Table instance

The first thing we need to do is to create a Table instance. After that, we register the table instance along with an import module into the WasmEdge runtime environment. Now let's see the code.

#![allow(unused)]
fn main() {
// create an executor
let config = ConfigBuilder::new(CommonConfigOptions::default()).build()?;
let mut executor = Executor::new(Some(&config), None)?;

// create a store
let mut store = Store::new()?;

// create a table instance
let result = Table::new(TableType::new(RefType::FuncRef, 10, Some(20)));
assert!(result.is_ok());
let table = result.unwrap();

// create an import object
let import = ImportObjectBuilder::new()
    .with_table("my-table", table)?
    .build("extern")?;

// register the import object into the store
store.register_import_module(&mut executor, &import)?;
}

In the code snippet above, we create a Table instance with the initial size of 10 and the maximum size of 20. The element type of the Table instance is reference to function.

Store a function reference into Table

In the previous steps, we defined a native function real_add and registered a Table instance named my-table into the runtime environment. Now we'll save a reference to read_add function to a slot of my-table.

#![allow(unused)]
fn main() {
// get the imported module instance
let instance = store
    .module_instance("extern")
    .expect("Not found module instance named 'extern'");

// get the exported table instance
let mut table = instance
    .table("my-table")
    .expect("Not found table instance named 'my-table'");

// create a host function
let host_func = Func::wrap::<(i32, i32), i32, !>(Box::new(real_add), None)?;

// store the reference to host_func at the given index of the table instance
table.set(3, Val::FuncRef(Some(host_func.as_ref())))?;
}

We save the reference to host_func into the third slot of my-table. Next, we can retrieve the function reference from the table instance by index and call the function via its reference.

Call native function via FuncRef

#![allow(unused)]
fn main() {
// retrieve the function reference at the given index of the table instance
let value = table.get(3)?;
if let Val::FuncRef(Some(func_ref)) = value {
    // get the function type by func_ref
    let func_ty = func_ref.ty()?;

    // arguments
    assert_eq!(func_ty.args_len(), 2);
    let param_tys = func_ty.args().unwrap();
    assert_eq!(param_tys, [ValType::I32, ValType::I32]);

    // returns
    assert_eq!(func_ty.returns_len(), 1);
    let return_tys = func_ty.returns().unwrap();
    assert_eq!(return_tys, [ValType::I32]);

    // call the function by func_ref
    let returns = func_ref.call(&mut executor, params!(1, 2))?;
    assert_eq!(returns.len(), 1);
    assert_eq!(returns[0].to_i32(), 3);
}
}

The complete code of this example can be found in table_and_funcref.rs.