#![cfg(feature = "wasm")]
use aver::ast::{TopLevel, Type};
use aver::ir::AnalysisResult;
use aver::replay::{EffectRecord, EffectReplayState, JsonValue, SessionRecording};
use aver::value::Value;
pub enum EffectMode {
Normal,
Record,
Replay(Box<SessionRecording>, bool),
}
pub struct RunConfig {
pub program_args: Vec<String>,
pub entry_info: Option<(String, Vec<Value>)>,
pub mode: EffectMode,
}
pub struct RunOutcome {
pub output: JsonValue,
pub effects_consumed: usize,
pub effects_total: usize,
pub args_diff_count: usize,
pub recorded_effects: Option<Vec<EffectRecord>>,
}
pub struct RunWasmGcHost {
pub program_args: Vec<String>,
pub recorder: Option<EffectReplayState>,
pub caller_fn_table: Vec<String>,
}
pub fn run_in_process(
items: &[TopLevel],
analysis: Option<&AnalysisResult>,
config: RunConfig,
) -> Result<RunOutcome, String> {
let bytes =
aver::codegen::wasm_gc::compile_to_wasm_gc(items, analysis).map_err(|e| format!("{e}"))?;
let entry_fn_name: &str = config
.entry_info
.as_ref()
.map(|(n, _)| n.as_str())
.unwrap_or("main");
let return_ty = find_fn_return_type(items, entry_fn_name);
run_wasm_gc_with_host(
&bytes,
&config.program_args,
&config.mode,
config.entry_info.as_ref(),
&return_ty,
)
.map_err(|e| format!("WASM execution error: {}", e))
}
fn build_caller_fn_table(
store: &mut wasmtime::Store<RunWasmGcHost>,
instance: &wasmtime::Instance,
) -> Result<Vec<String>, String> {
use wasmtime::Val;
let count_fn = match instance.get_func(&mut *store, "__caller_fn_count") {
Some(f) => f,
None => return Ok(Vec::new()),
};
let name_fn = match instance.get_func(&mut *store, "__caller_fn_name") {
Some(f) => f,
None => return Ok(Vec::new()),
};
let to_lm = match instance.get_func(&mut *store, "__rt_string_to_lm") {
Some(f) => f,
None => return Ok(Vec::new()),
};
let memory = match instance.get_memory(&mut *store, "memory") {
Some(m) => m,
None => return Ok(Vec::new()),
};
let mut count_out = [Val::I32(0)];
count_fn
.call(&mut *store, &[], &mut count_out)
.map_err(|e| format!("__caller_fn_count: {e:#}"))?;
let count = match count_out[0] {
Val::I32(n) => n.max(0) as usize,
_ => 0,
};
let mut out = Vec::with_capacity(count);
let mut name_out = [Val::AnyRef(None)];
let mut len_out = [Val::I32(0)];
for i in 0..count {
name_fn
.call(&mut *store, &[Val::I32(i as i32)], &mut name_out)
.map_err(|e| format!("__caller_fn_name({i}): {e:#}"))?;
let any_ref = match &name_out[0] {
Val::AnyRef(Some(r)) => Val::AnyRef(Some(*r)),
_ => {
out.push("main".to_string());
continue;
}
};
to_lm
.call(&mut *store, &[any_ref], &mut len_out)
.map_err(|e| format!("__rt_string_to_lm: {e:#}"))?;
let len = match len_out[0] {
Val::I32(n) => n.max(0) as usize,
_ => 0,
};
let mut buf = vec![0u8; len];
if len > 0 {
memory
.read(&*store, 0, &mut buf)
.map_err(|e| format!("read caller_fn name {i}: {e:#}"))?;
}
out.push(String::from_utf8_lossy(&buf).into_owned());
}
Ok(out)
}
fn find_fn_return_type(items: &[TopLevel], name: &str) -> Type {
for item in items {
if let TopLevel::FnDef(fn_def) = item
&& fn_def.name == name
{
return aver::types::parse_type_str(&fn_def.return_type);
}
}
Type::Unit
}
#[allow(clippy::too_many_arguments)]
fn run_wasm_gc_with_host(
wasm_bytes: &[u8],
program_args: &[String],
mode: &EffectMode,
entry_info: Option<&(String, Vec<Value>)>,
return_ty: &Type,
) -> Result<RunOutcome, String> {
use wasmtime::*;
let mut config = Config::new();
config.wasm_gc(true);
config.wasm_tail_call(true);
config.wasm_function_references(true);
config.wasm_reference_types(true);
config.wasm_multi_value(true);
config.wasm_bulk_memory(true);
config.cranelift_opt_level(OptLevel::Speed);
config.max_wasm_stack(8 * 1024 * 1024);
config.async_stack_size(12 * 1024 * 1024);
let engine = Engine::new(&config).map_err(|e| format!("engine: {e:#}"))?;
let module = Module::new(&engine, wasm_bytes).map_err(|e| format!("module: {e:#}"))?;
let mut recorder = match mode {
EffectMode::Normal => None,
EffectMode::Record => {
let mut r = EffectReplayState::default();
r.start_recording();
Some(r)
}
EffectMode::Replay(recording, check_args) => {
let mut r = EffectReplayState::default();
r.start_replay(recording.effects.clone(), *check_args);
Some(r)
}
};
let mut store = Store::new(
&engine,
RunWasmGcHost {
program_args: program_args.to_vec(),
recorder: recorder.take(),
caller_fn_table: Vec::new(),
},
);
let mut linker: Linker<RunWasmGcHost> = Linker::new(&engine);
for import in module.imports() {
let ExternType::Func(ft) = import.ty() else {
continue;
};
let module_name = import.module().to_string();
let field_name = import.name().to_string();
let result_tys: Vec<ValType> = ft.results().collect();
let func_ty = FuncType::new(&engine, ft.params(), ft.results());
let module_name_for_closure = module_name.clone();
let field_name_for_closure = field_name.clone();
linker
.func_new(
&module_name,
&field_name,
func_ty,
move |mut caller: Caller<'_, RunWasmGcHost>,
params: &[Val],
results: &mut [Val]|
-> Result<(), wasmtime::Error> {
if module_name_for_closure == "aver"
&& imports::dispatch_aver_import(
&field_name_for_closure,
&mut caller,
params,
results,
)?
{
return Ok(());
}
for (slot, ty) in results.iter_mut().zip(result_tys.iter()) {
*slot = match ty {
ValType::I32 => Val::I32(0),
ValType::I64 => Val::I64(0),
ValType::F32 => Val::F32(0),
ValType::F64 => Val::F64(0),
ValType::V128 => Val::V128(0u128.into()),
ValType::Ref(_) => Val::AnyRef(None),
};
}
Ok(())
},
)
.map_err(|e| format!("link {module_name}.{field_name}: {e:#}"))?;
}
let instance = linker
.instantiate(&mut store, &module)
.map_err(|e| format!("instantiate: {e:#}"))?;
let caller_fn_table = build_caller_fn_table(&mut store, &instance)?;
store.data_mut().caller_fn_table = caller_fn_table;
let main_output: JsonValue = if let Some((fn_name, args)) = entry_info {
let func = instance.get_func(&mut store, fn_name).ok_or_else(|| {
format!(
"entry function '{}' not exported by wasm-gc module",
fn_name
)
})?;
let arg_vals = decode::encode_entry_args_for_wasm_gc(&mut store, &instance, args)?;
let n = func.ty(&store).results().len();
let mut out: Vec<Val> = (0..n).map(|_| Val::I32(0)).collect();
func.call(&mut store, &arg_vals, &mut out)
.map_err(|e| format!("entry '{}' trap: {e:#}", fn_name))?;
decode::decode_main_return_typed(&mut store, &instance, &out, return_ty)?
} else if let Some(main) = instance.get_func(&mut store, "main") {
let n = main.ty(&store).results().len();
let mut out: Vec<Val> = (0..n).map(|_| Val::I32(0)).collect();
main.call(&mut store, &[], &mut out)
.map_err(|e| format!("main trap: {e:#}"))?;
decode::decode_main_return_typed(&mut store, &instance, &out, return_ty)?
} else if let Some(start) = instance.get_func(&mut store, "_start") {
start
.call(&mut store, &[], &mut [])
.map_err(|e| format!("_start trap: {e:#}"))?;
JsonValue::Null
} else {
return Err("module exports neither _start nor main".into());
};
let (effects_consumed, effects_total, args_diff_count) = match store.data().recorder.as_ref() {
Some(r) if r.mode() == aver::replay::EffectReplayMode::Replay => {
let (consumed, total) = r.replay_progress();
(consumed, total, r.args_diff_count())
}
Some(r) if r.mode() == aver::replay::EffectReplayMode::Record => {
let n = r.recorded_effects().len();
(n, n, 0)
}
_ => (0, 0, 0),
};
if matches!(mode, EffectMode::Replay(_, _))
&& let Some(r) = store.data().recorder.as_ref()
&& let Err(e) = r.ensure_replay_consumed()
{
return Err(format!("replay incomplete: {:?}", e));
}
let recorded_effects = if matches!(mode, EffectMode::Record)
&& let Some(rec) = store.data_mut().recorder.as_mut()
{
Some(rec.take_recorded_effects())
} else {
None
};
Ok(RunOutcome {
output: main_output,
effects_consumed,
effects_total,
args_diff_count,
recorded_effects,
})
}
mod decode;
mod imports;