use std::collections::HashMap;
use anyhow::{Context, Result};
use walrus::{ExportItem, FunctionId, GlobalId, LocalId, Module};
use walrus::ir;
use crate::host_functions::{self, HostFunction};
pub mod simulation;
pub mod analysis;
pub mod helpers;
pub struct AnalyzedModule {
pub module: Module,
pub host_func_map: HashMap<FunctionId, &'static HostFunction>,
}
#[derive(Debug)]
pub struct FunctionAnalysis {
pub export_name: String,
pub export_func_id: FunctionId,
pub impl_func_id: FunctionId,
pub host_calls: Vec<HostCallSite>,
pub local_call_count: usize,
pub has_branches: bool,
pub has_loops: bool,
pub instruction_count: usize,
}
#[derive(Debug, Clone)]
pub struct HostCallSite {
pub semantic_module: String,
pub semantic_name: String,
pub raw_module: String,
pub raw_field: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MemBase {
Local(LocalId),
Global(GlobalId),
}
#[derive(Debug, Clone)]
pub enum StackValue {
Const(ir::Value),
Param(usize),
Local(LocalId),
Global(GlobalId),
CallResult(usize),
BinOp {
op: crate::ir::BinOp,
left: Box<StackValue>,
right: Box<StackValue>,
},
UnOp {
op: crate::ir::UnOp,
operand: Box<StackValue>,
},
Unknown,
}
#[derive(Debug)]
pub struct TrackedHostCall {
pub host_func: &'static HostFunction,
pub func_id: FunctionId,
pub call_site_id: usize,
pub args: Vec<StackValue>,
}
#[derive(Debug)]
pub enum AnalyzedBlock {
HostCall(TrackedHostCall),
If {
condition: Option<StackValue>,
then_block: Vec<AnalyzedBlock>,
else_block: Vec<AnalyzedBlock>,
alt_unreachable: bool,
guard_trap: bool,
},
Loop {
body: Vec<AnalyzedBlock>,
has_back_edge: bool,
},
}
#[derive(Debug)]
pub struct FunctionStackAnalysis {
pub blocks: Vec<AnalyzedBlock>,
pub host_calls: Vec<TrackedHostCall>,
pub return_expr: Option<StackValue>,
pub vec_contents: HashMap<usize, Vec<StackValue>>,
pub map_contents: HashMap<usize, (Vec<String>, Vec<StackValue>)>,
pub unpack_field_ids: HashMap<usize, Vec<usize>>,
pub memory_state: HashMap<(MemBase, i64), StackValue>,
}
impl AnalyzedModule {
pub fn from_wasm(wasm: &[u8]) -> Result<Self> {
let module = Module::from_buffer(wasm)
.context("failed to parse WASM with walrus")?;
let host_func_map = build_host_func_map(&module);
Ok(Self { module, host_func_map })
}
pub fn module(&self) -> &Module {
&self.module
}
pub fn analyze_export(&self, name: &str) -> Result<FunctionAnalysis> {
let export_func_id = self.module.exports.get_func(name)
.with_context(|| format!("no export named '{name}'"))?;
let impl_func_id = self.trace_to_impl(export_func_id);
let stats = self.analyze_function_body(impl_func_id);
Ok(FunctionAnalysis {
export_name: name.to_string(),
export_func_id,
impl_func_id,
host_calls: stats.host_calls,
local_call_count: stats.local_call_count,
has_branches: stats.has_branches,
has_loops: stats.has_loops,
instruction_count: stats.instruction_count,
})
}
pub fn analyze_all_exports(&self) -> Vec<FunctionAnalysis> {
let exports: Vec<(String, FunctionId)> = self.module.exports.iter()
.filter_map(|e| match e.item {
ExportItem::Function(fid) => Some((e.name.clone(), fid)),
_ => None,
})
.collect();
exports.into_iter().filter_map(|(name, fid)| {
let impl_func_id = self.trace_to_impl(fid);
let stats = self.analyze_function_body(impl_func_id);
Some(FunctionAnalysis {
export_name: name,
export_func_id: fid,
impl_func_id,
host_calls: stats.host_calls,
local_call_count: stats.local_call_count,
has_branches: stats.has_branches,
has_loops: stats.has_loops,
instruction_count: stats.instruction_count,
})
}).collect()
}
pub fn get_host_func(&self, func_id: FunctionId) -> Option<&'static HostFunction> {
self.host_func_map.get(&func_id).copied()
}
pub fn wasm_param_count(&self, func_id: FunctionId) -> usize {
let func = self.module.funcs.get(func_id);
let ty = self.module.types.get(func.ty());
ty.params().len()
}
pub fn read_linear_memory(&self, offset: u32, len: u32) -> Option<Vec<u8>> {
use walrus::ConstExpr;
for data in self.module.data.iter() {
if let walrus::DataKind::Active { offset: ref const_expr, .. } = data.kind {
let seg_offset = match const_expr {
ConstExpr::Value(ir::Value::I32(off)) => *off as u32,
ConstExpr::Value(ir::Value::I64(off)) => *off as u32,
_ => continue,
};
let seg_end = seg_offset + data.value.len() as u32;
if offset >= seg_offset && offset.checked_add(len)? <= seg_end {
let local_start = (offset - seg_offset) as usize;
let local_end = local_start + len as usize;
return Some(data.value[local_start..local_end].to_vec());
}
}
}
None
}
}
fn build_host_func_map(module: &Module) -> HashMap<FunctionId, &'static HostFunction> {
let mut map = HashMap::new();
for import in module.imports.iter() {
if let walrus::ImportKind::Function(fid) = import.kind {
if let Some(hf) = host_functions::lookup(&import.module, &import.name) {
map.insert(fid, hf);
}
}
}
map
}