use super::func::compile_function_to_wasm;
use crate::vm::instruction::CompiledProgram;
pub struct WasmTier {
entries: std::collections::HashMap<u16, TierEntry>,
threshold: u32,
hits: u64,
}
enum TierEntry {
Pending(u32),
Ready(ReadyModule),
Ineligible,
}
#[cfg(not(target_arch = "wasm32"))]
struct ReadyModule {
store: wasmi::Store<()>,
func: wasmi::Func,
}
#[cfg(target_arch = "wasm32")]
struct ReadyModule {
func: js_sys::Function,
}
impl ReadyModule {
#[cfg(not(target_arch = "wasm32"))]
fn call(&mut self, args: &[i64]) -> Option<i64> {
let argv: Vec<wasmi::Value> = args.iter().map(|&a| wasmi::Value::I64(a)).collect();
let mut results = [wasmi::Value::I64(0)];
self.func.call(&mut self.store, &argv, &mut results).ok()?;
match results[0] {
wasmi::Value::I64(v) => Some(v),
_ => None,
}
}
#[cfg(target_arch = "wasm32")]
fn call(&mut self, args: &[i64]) -> Option<i64> {
call_host_func(&self.func, args)
}
}
impl WasmTier {
pub fn new(threshold: u32) -> Self {
WasmTier {
entries: std::collections::HashMap::new(),
threshold: threshold.max(1),
hits: 0,
}
}
pub fn hits(&self) -> u64 {
self.hits
}
pub fn on_call(&mut self, program: &CompiledProgram, func: u16, args: &[i64]) -> Option<i64> {
self.entries.entry(func).or_insert(TierEntry::Pending(0));
if let Some(TierEntry::Pending(count)) = self.entries.get_mut(&func) {
*count += 1;
if *count < self.threshold {
return None;
}
}
if matches!(self.entries.get(&func), Some(TierEntry::Pending(_))) {
match instantiate(program, func) {
Some(m) => {
self.entries.insert(func, TierEntry::Ready(m));
}
None => {
self.entries.insert(func, TierEntry::Ineligible);
return None;
}
}
}
if let Some(TierEntry::Ready(m)) = self.entries.get_mut(&func) {
if let Some(v) = m.call(args) {
self.hits += 1;
return Some(v);
}
}
None
}
}
#[cfg(not(target_arch = "wasm32"))]
fn instantiate(program: &CompiledProgram, func: u16) -> Option<ReadyModule> {
let bytes = compile_function_to_wasm(program, func as usize)?;
let engine = wasmi::Engine::default();
let module = wasmi::Module::new(&engine, &bytes[..]).ok()?;
let mut store = wasmi::Store::new(&engine, ());
let instance = wasmi::Linker::<()>::new(&engine)
.instantiate(&mut store, &module)
.ok()?
.start(&mut store)
.ok()?;
let f = instance.get_func(&store, "f")?;
Some(ReadyModule { store, func: f })
}
#[cfg(target_arch = "wasm32")]
fn instantiate(program: &CompiledProgram, func: u16) -> Option<ReadyModule> {
let bytes = compile_function_to_wasm(program, func as usize)?;
Some(ReadyModule { func: instantiate_on_host(&bytes)? })
}
#[cfg(target_arch = "wasm32")]
fn instantiate_on_host(bytes: &[u8]) -> Option<js_sys::Function> {
use wasm_bindgen::JsCast;
let arr = js_sys::Uint8Array::from(bytes);
let module = js_sys::WebAssembly::Module::new(arr.as_ref()).ok()?;
let instance = js_sys::WebAssembly::Instance::new(&module, &js_sys::Object::new()).ok()?;
js_sys::Reflect::get(instance.exports().as_ref(), &wasm_bindgen::JsValue::from_str("f"))
.ok()?
.dyn_into::<js_sys::Function>()
.ok()
}
#[cfg(target_arch = "wasm32")]
fn call_host_func(f: &js_sys::Function, args: &[i64]) -> Option<i64> {
use wasm_bindgen::JsCast;
let arr = js_sys::Array::new();
for &a in args {
arr.push(&wasm_bindgen::JsValue::from(js_sys::BigInt::from(a)));
}
let result = f.apply(&wasm_bindgen::JsValue::NULL, &arr).ok()?;
let bigint = result.unchecked_into::<js_sys::BigInt>();
let decimal = wasm_bindgen::JsValue::from(bigint.to_string(10).ok()?).as_string()?;
decimal.parse::<i64>().ok()
}
#[cfg(target_arch = "wasm32")]
pub fn run_on_host(bytes: &[u8], args: &[i64]) -> Option<i64> {
call_host_func(&instantiate_on_host(bytes)?, args)
}