null refs

This commit is contained in:
Graham Kelly 2024-04-09 13:52:02 -04:00
parent 9d16b582ea
commit 908ad937e1
6 changed files with 235 additions and 206 deletions

View file

@ -945,6 +945,10 @@ impl<'a> WasmFuncBackend<'a> {
Operator::RefFunc { func_index } => { Operator::RefFunc { func_index } => {
Some(wasm_encoder::Instruction::RefFunc(func_index.index() as u32)) Some(wasm_encoder::Instruction::RefFunc(func_index.index() as u32))
} }
Operator::RefNull { ty } => {
let h: wasm_encoder::RefType = ty.clone().into();
Some(wasm_encoder::Instruction::RefNull(h.heap_type))
}
}; };
if let Some(inst) = inst { if let Some(inst) = inst {

View file

@ -18,7 +18,8 @@ pub struct InterpContext {
pub globals: PerEntity<Global, ConstVal>, pub globals: PerEntity<Global, ConstVal>,
pub fuel: u64, pub fuel: u64,
pub trace_handler: Option<Box<dyn Fn(usize, Vec<ConstVal>) -> bool + Send>>, pub trace_handler: Option<Box<dyn Fn(usize, Vec<ConstVal>) -> bool + Send>>,
pub import_hander: Option<Box<dyn FnMut(&mut InterpContext,&str,&[ConstVal]) -> InterpResult>> pub import_hander:
Option<Box<dyn FnMut(&mut InterpContext, &str, &[ConstVal]) -> InterpResult>>,
} }
type MultiVal = SmallVec<[ConstVal; 2]>; type MultiVal = SmallVec<[ConstVal; 2]>;
@ -93,115 +94,91 @@ impl InterpContext {
pub fn call(&mut self, module: &Module<'_>, mut func: Func, args: &[ConstVal]) -> InterpResult { pub fn call(&mut self, module: &Module<'_>, mut func: Func, args: &[ConstVal]) -> InterpResult {
let mut args = args.to_vec(); let mut args = args.to_vec();
'redo: loop{ 'redo: loop {
let body = match &module.funcs[func] { let body = match &module.funcs[func] {
FuncDecl::Lazy(..) => panic!("Un-expanded function"), FuncDecl::Lazy(..) => panic!("Un-expanded function"),
FuncDecl::Compiled(..) => panic!("Already-compiled function"), FuncDecl::Compiled(..) => panic!("Already-compiled function"),
FuncDecl::Import(..) => { FuncDecl::Import(..) => {
let import = &module.imports[func.index()]; let import = &module.imports[func.index()];
assert_eq!(import.kind, ImportKind::Func(func)); assert_eq!(import.kind, ImportKind::Func(func));
return self.call_import(&import.name[..], &args); return self.call_import(&import.name[..], &args);
} }
FuncDecl::Body(_, _, body) => body, FuncDecl::Body(_, _, body) => body,
FuncDecl::None => panic!("FuncDecl::None in call()"), FuncDecl::None => panic!("FuncDecl::None in call()"),
}; };
log::trace!( log::trace!(
"Interp: entering func {}:\n{}\n", "Interp: entering func {}:\n{}\n",
func, func,
body.display_verbose("| ", Some(module)) body.display_verbose("| ", Some(module))
); );
log::trace!("args: {:?}", args); log::trace!("args: {:?}", args);
let mut frame = InterpStackFrame { let mut frame = InterpStackFrame {
func, func,
cur_block: body.entry, cur_block: body.entry,
values: HashMap::new(), values: HashMap::new(),
}; };
for (&arg, &(_, blockparam)) in args.iter().zip(body.blocks[body.entry].params.iter()) { for (&arg, &(_, blockparam)) in args.iter().zip(body.blocks[body.entry].params.iter()) {
log::trace!("Entry block param {} gets arg value {:?}", blockparam, arg); log::trace!("Entry block param {} gets arg value {:?}", blockparam, arg);
frame.values.insert(blockparam, smallvec![arg]); frame.values.insert(blockparam, smallvec![arg]);
}
loop {
self.fuel -= 1;
if self.fuel == 0 {
return InterpResult::OutOfFuel;
} }
log::trace!("Interpreting block {}", frame.cur_block); loop {
for (inst_idx, &inst) in body.blocks[frame.cur_block].insts.iter().enumerate() { self.fuel -= 1;
log::trace!("Evaluating inst {}", inst); if self.fuel == 0 {
let result = match &body.values[inst] { return InterpResult::OutOfFuel;
&ValueDef::Alias(_) => smallvec![], }
&ValueDef::PickOutput(val, idx, _) => {
let val = body.resolve_alias(val); log::trace!("Interpreting block {}", frame.cur_block);
smallvec![frame.values.get(&val).unwrap()[idx as usize]] for (inst_idx, &inst) in body.blocks[frame.cur_block].insts.iter().enumerate() {
} log::trace!("Evaluating inst {}", inst);
&ValueDef::Operator(Operator::Call { function_index }, args, _) => { let result = match &body.values[inst] {
let args = body.arg_pool[args] &ValueDef::Alias(_) => smallvec![],
.iter() &ValueDef::PickOutput(val, idx, _) => {
.map(|&arg| { let val = body.resolve_alias(val);
let arg = body.resolve_alias(arg); smallvec![frame.values.get(&val).unwrap()[idx as usize]]
let multivalue = frame.values.get(&arg).unwrap();
assert_eq!(multivalue.len(), 1);
multivalue[0]
})
.collect::<Vec<_>>();
let result = self.call(module, function_index, &args[..]);
match result {
InterpResult::Ok(vals) => vals,
_ => return result,
} }
} &ValueDef::Operator(Operator::Call { function_index }, args, _) => {
&ValueDef::Operator(Operator::CallIndirect { table_index, .. }, args, _) => { let args = body.arg_pool[args]
let args = body.arg_pool[args] .iter()
.iter() .map(|&arg| {
.map(|&arg| { let arg = body.resolve_alias(arg);
let arg = body.resolve_alias(arg); let multivalue = frame.values.get(&arg).unwrap();
let multivalue = frame.values.get(&arg).unwrap(); assert_eq!(multivalue.len(), 1);
assert_eq!(multivalue.len(), 1); multivalue[0]
multivalue[0] })
}) .collect::<Vec<_>>();
.collect::<Vec<_>>(); let result = self.call(module, function_index, &args[..]);
let idx = args.last().unwrap().as_u32().unwrap() as usize; match result {
let func = self.tables[table_index].elements[idx]; InterpResult::Ok(vals) => vals,
let result = self.call(module, func, &args[..args.len() - 1]); _ => return result,
match result {
InterpResult::Ok(vals) => vals,
_ => return result,
}
}
&ValueDef::Operator(ref op, args, _) => {
let args = body.arg_pool[args]
.iter()
.map(|&arg| {
let arg = body.resolve_alias(arg);
let multivalue = frame
.values
.get(&arg)
.ok_or_else(|| format!("Unset SSA value: {}", arg))
.unwrap();
assert_eq!(multivalue.len(), 1);
multivalue[0]
})
.collect::<Vec<_>>();
let result = match const_eval(op, &args[..], Some(self)) {
Some(result) => result,
None => {
log::trace!("const_eval failed on {:?} args {:?}", op, args);
return InterpResult::Trap(
frame.func,
frame.cur_block,
inst_idx as u32,
);
} }
}; }
smallvec![result] &ValueDef::Operator(
} Operator::CallIndirect { table_index, .. },
&ValueDef::Trace(id, args) => { args,
if let Some(handler) = self.trace_handler.as_ref() { _,
) => {
let args = body.arg_pool[args]
.iter()
.map(|&arg| {
let arg = body.resolve_alias(arg);
let multivalue = frame.values.get(&arg).unwrap();
assert_eq!(multivalue.len(), 1);
multivalue[0]
})
.collect::<Vec<_>>();
let idx = args.last().unwrap().as_u32().unwrap() as usize;
let func = self.tables[table_index].elements[idx];
let result = self.call(module, func, &args[..args.len() - 1]);
match result {
InterpResult::Ok(vals) => vals,
_ => return result,
}
}
&ValueDef::Operator(ref op, args, _) => {
let args = body.arg_pool[args] let args = body.arg_pool[args]
.iter() .iter()
.map(|&arg| { .map(|&arg| {
@ -214,115 +191,148 @@ impl InterpContext {
assert_eq!(multivalue.len(), 1); assert_eq!(multivalue.len(), 1);
multivalue[0] multivalue[0]
}) })
.collect::<Vec<ConstVal>>(); .collect::<Vec<_>>();
if !handler(id, args) { let result = match const_eval(op, &args[..], Some(self)) {
return InterpResult::TraceHandlerQuit; Some(result) => result,
} None => {
log::trace!("const_eval failed on {:?} args {:?}", op, args);
return InterpResult::Trap(
frame.func,
frame.cur_block,
inst_idx as u32,
);
}
};
smallvec![result]
} }
smallvec![] &ValueDef::Trace(id, args) => {
} if let Some(handler) = self.trace_handler.as_ref() {
&ValueDef::None | &ValueDef::Placeholder(..) | &ValueDef::BlockParam(..) => { let args = body.arg_pool[args]
unreachable!(); .iter()
} .map(|&arg| {
}; let arg = body.resolve_alias(arg);
let multivalue = frame
.values
.get(&arg)
.ok_or_else(|| format!("Unset SSA value: {}", arg))
.unwrap();
assert_eq!(multivalue.len(), 1);
multivalue[0]
})
.collect::<Vec<ConstVal>>();
if !handler(id, args) {
return InterpResult::TraceHandlerQuit;
}
}
smallvec![]
}
&ValueDef::None
| &ValueDef::Placeholder(..)
| &ValueDef::BlockParam(..) => {
unreachable!();
}
};
log::trace!("Inst {} gets result {:?}", inst, result); log::trace!("Inst {} gets result {:?}", inst, result);
frame.values.insert(inst, result); frame.values.insert(inst, result);
} }
match &body.blocks[frame.cur_block].terminator { match &body.blocks[frame.cur_block].terminator {
&Terminator::ReturnCallIndirect { &Terminator::ReturnCallIndirect {
sig, sig,
table, table,
args: ref args2, args: ref args2,
} => { } => {
let args2 = args2 let args2 = args2
.iter() .iter()
.map(|&arg| { .map(|&arg| {
let arg = body.resolve_alias(arg); let arg = body.resolve_alias(arg);
let multivalue = frame.values.get(&arg).unwrap(); let multivalue = frame.values.get(&arg).unwrap();
assert_eq!(multivalue.len(), 1); assert_eq!(multivalue.len(), 1);
multivalue[0] multivalue[0]
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let idx = args2.last().unwrap().as_u32().unwrap() as usize; let idx = args2.last().unwrap().as_u32().unwrap() as usize;
let fu = self.tables[table].elements[idx]; let fu = self.tables[table].elements[idx];
func = fu; func = fu;
args = args2[..args2.len()-1].to_vec(); args = args2[..args2.len() - 1].to_vec();
continue 'redo; continue 'redo;
// let result = self.call(module, func, &args[..args.len() - 1]); // let result = self.call(module, func, &args[..args.len() - 1]);
// return result; // return result;
}
&Terminator::ReturnCall { func: fu, args: ref args2 } => {
let args2 = args2
.iter()
.map(|&arg| {
let arg = body.resolve_alias(arg);
let multivalue = frame.values.get(&arg).unwrap();
assert_eq!(multivalue.len(), 1);
multivalue[0]
})
.collect::<Vec<_>>();
func = fu;
args = args2;
continue 'redo;
}
&Terminator::None => {
return InterpResult::Trap(frame.func, frame.cur_block, u32::MAX)
}
&Terminator::Unreachable => {
return InterpResult::Trap(frame.func, frame.cur_block, u32::MAX)
}
&Terminator::Br { ref target } => {
frame.apply_target(body, target);
}
&Terminator::CondBr {
cond,
ref if_true,
ref if_false,
} => {
let cond = body.resolve_alias(cond);
let cond = frame.values.get(&cond).unwrap();
let cond = cond[0].as_u32().unwrap() != 0;
if cond {
frame.apply_target(body, if_true);
} else {
frame.apply_target(body, if_false);
} }
} &Terminator::ReturnCall {
&Terminator::Select { func: fu,
value, args: ref args2,
ref targets, } => {
ref default, let args2 = args2
} => { .iter()
let value = body.resolve_alias(value); .map(|&arg| {
let value = frame.values.get(&value).unwrap(); let arg = body.resolve_alias(arg);
let value = value[0].as_u32().unwrap() as usize; let multivalue = frame.values.get(&arg).unwrap();
if value < targets.len() { assert_eq!(multivalue.len(), 1);
frame.apply_target(body, &targets[value]); multivalue[0]
} else { })
frame.apply_target(body, default); .collect::<Vec<_>>();
func = fu;
args = args2;
continue 'redo;
}
&Terminator::None => {
return InterpResult::Trap(frame.func, frame.cur_block, u32::MAX)
}
&Terminator::Unreachable => {
return InterpResult::Trap(frame.func, frame.cur_block, u32::MAX)
}
&Terminator::Br { ref target } => {
frame.apply_target(body, target);
}
&Terminator::CondBr {
cond,
ref if_true,
ref if_false,
} => {
let cond = body.resolve_alias(cond);
let cond = frame.values.get(&cond).unwrap();
let cond = cond[0].as_u32().unwrap() != 0;
if cond {
frame.apply_target(body, if_true);
} else {
frame.apply_target(body, if_false);
}
}
&Terminator::Select {
value,
ref targets,
ref default,
} => {
let value = body.resolve_alias(value);
let value = frame.values.get(&value).unwrap();
let value = value[0].as_u32().unwrap() as usize;
if value < targets.len() {
frame.apply_target(body, &targets[value]);
} else {
frame.apply_target(body, default);
}
}
&Terminator::Return { ref values } => {
let values = values
.iter()
.map(|&value| {
let value = body.resolve_alias(value);
frame.values.get(&value).unwrap()[0]
})
.collect();
log::trace!("returning from {}: {:?}", func, values);
return InterpResult::Ok(values);
} }
}
&Terminator::Return { ref values } => {
let values = values
.iter()
.map(|&value| {
let value = body.resolve_alias(value);
frame.values.get(&value).unwrap()[0]
})
.collect();
log::trace!("returning from {}: {:?}", func, values);
return InterpResult::Ok(values);
} }
} }
} }
} }
}
fn call_import(&mut self, name: &str, args: &[ConstVal]) -> InterpResult { fn call_import(&mut self, name: &str, args: &[ConstVal]) -> InterpResult {
let mut r = self.import_hander.take().unwrap(); let mut r = self.import_hander.take().unwrap();
let rs = r(self,name,args); let rs = r(self, name, args);
self.import_hander = Some(r); self.import_hander = Some(r);
return rs; return rs;
} }

View file

@ -27,7 +27,7 @@ impl From<wasmparser::ValType> for Type {
} }
impl From<wasmparser::RefType> for Type { impl From<wasmparser::RefType> for Type {
fn from(ty: wasmparser::RefType) -> Self { fn from(ty: wasmparser::RefType) -> Self {
if ty.is_extern_ref(){ if ty.is_extern_ref() {
return Type::ExternRef; return Type::ExternRef;
} }
match ty.type_index() { match ty.type_index() {
@ -49,7 +49,7 @@ impl std::fmt::Display for Type {
Type::F64 => write!(f, "f64"), Type::F64 => write!(f, "f64"),
Type::V128 => write!(f, "v128"), Type::V128 => write!(f, "v128"),
Type::FuncRef => write!(f, "funcref"), Type::FuncRef => write!(f, "funcref"),
Type::ExternRef => write!(f,"externref"), Type::ExternRef => write!(f, "externref"),
Type::TypedFuncRef(nullable, idx) => write!( Type::TypedFuncRef(nullable, idx) => write!(
f, f,
"funcref({}, {})", "funcref({}, {})",
@ -68,7 +68,9 @@ impl From<Type> for wasm_encoder::ValType {
Type::F32 => wasm_encoder::ValType::F32, Type::F32 => wasm_encoder::ValType::F32,
Type::F64 => wasm_encoder::ValType::F64, Type::F64 => wasm_encoder::ValType::F64,
Type::V128 => wasm_encoder::ValType::V128, Type::V128 => wasm_encoder::ValType::V128,
Type::FuncRef | Type::TypedFuncRef(..) | Type::ExternRef => wasm_encoder::ValType::Ref(ty.into()), Type::FuncRef | Type::TypedFuncRef(..) | Type::ExternRef => {
wasm_encoder::ValType::Ref(ty.into())
}
} }
} }
} }

View file

@ -190,9 +190,9 @@ impl<'a> Module<'a> {
} }
pub fn to_wasm_bytes(&self) -> Result<Vec<u8>> { pub fn to_wasm_bytes(&self) -> Result<Vec<u8>> {
backend::compile(self).map(|a|a.finish()) backend::compile(self).map(|a| a.finish())
} }
pub fn to_encoded_module(&self) -> Result<wasm_encoder::Module>{ pub fn to_encoded_module(&self) -> Result<wasm_encoder::Module> {
backend::compile(self) backend::compile(self)
} }

View file

@ -485,7 +485,10 @@ pub fn op_inputs(
params.push(Type::TypedFuncRef(true, sig_index.index() as u32)); params.push(Type::TypedFuncRef(true, sig_index.index() as u32));
Ok(params.into()) Ok(params.into())
} }
Operator::RefIsNull => Ok(vec![op_stack.context("in getting stack")?.last().unwrap().0].into()), Operator::RefIsNull => {
Ok(vec![op_stack.context("in getting stack")?.last().unwrap().0].into())
}
Operator::RefNull { ty } => Ok(Cow::Borrowed(&[])),
Operator::RefFunc { .. } => Ok(Cow::Borrowed(&[])), Operator::RefFunc { .. } => Ok(Cow::Borrowed(&[])),
Operator::MemoryCopy { .. } => Ok(Cow::Borrowed(&[Type::I32, Type::I32, Type::I32])), Operator::MemoryCopy { .. } => Ok(Cow::Borrowed(&[Type::I32, Type::I32, Type::I32])),
Operator::MemoryFill { .. } => Ok(Cow::Borrowed(&[Type::I32, Type::I32, Type::I32])), Operator::MemoryFill { .. } => Ok(Cow::Borrowed(&[Type::I32, Type::I32, Type::I32])),
@ -961,6 +964,7 @@ pub fn op_outputs(
let ty = module.funcs[*func_index].sig(); let ty = module.funcs[*func_index].sig();
Ok(vec![Type::TypedFuncRef(true, ty.index() as u32)].into()) Ok(vec![Type::TypedFuncRef(true, ty.index() as u32)].into())
} }
Operator::RefNull { ty } => Ok(vec![ty.clone()].into()),
} }
} }
@ -1431,6 +1435,7 @@ impl Operator {
Operator::CallRef { .. } => &[All], Operator::CallRef { .. } => &[All],
Operator::RefIsNull => &[], Operator::RefIsNull => &[],
Operator::RefFunc { .. } => &[], Operator::RefFunc { .. } => &[],
Operator::RefNull { ty } => &[],
} }
} }
@ -1927,6 +1932,7 @@ impl std::fmt::Display for Operator {
Operator::CallRef { sig_index } => write!(f, "call_ref<{}>", sig_index)?, Operator::CallRef { sig_index } => write!(f, "call_ref<{}>", sig_index)?,
Operator::RefIsNull => write!(f, "ref_is_null")?, Operator::RefIsNull => write!(f, "ref_is_null")?,
Operator::RefFunc { func_index } => write!(f, "ref_func<{}>", func_index)?, Operator::RefFunc { func_index } => write!(f, "ref_func<{}>", func_index)?,
Operator::RefNull { ty } => write!(f, "ref_null<{}>", ty)?,
} }
Ok(()) Ok(())

View file

@ -1,6 +1,7 @@
//! Operators. //! Operators.
use crate::{entity::EntityRef, Func, Global, Memory, Signature, Table, Type}; use crate::{entity::EntityRef, Func, Global, Memory, Signature, Table, Type};
use anyhow::Context;
use std::convert::TryFrom; use std::convert::TryFrom;
pub use wasmparser::{Ieee32, Ieee64}; pub use wasmparser::{Ieee32, Ieee64};
@ -635,6 +636,9 @@ pub enum Operator {
sig_index: Signature, sig_index: Signature,
}, },
RefIsNull, RefIsNull,
RefNull {
ty: Type,
},
RefFunc { RefFunc {
func_index: Func, func_index: Func,
}, },
@ -1289,6 +1293,9 @@ impl<'a, 'b> std::convert::TryFrom<&'b wasmparser::Operator<'a>> for Operator {
&wasmparser::Operator::MemoryFill { mem } => Ok(Operator::MemoryFill { &wasmparser::Operator::MemoryFill { mem } => Ok(Operator::MemoryFill {
mem: Memory::from(mem), mem: Memory::from(mem),
}), }),
&wasmparser::Operator::RefNull { hty } => Ok(Operator::RefNull {
ty: wasmparser::RefType::new(true, hty).unwrap().into(),
}),
_ => Err(()), _ => Err(()),
} }
} }