cranelift: Add fuel mechanism to the interpreter

This commit is contained in:
Afonso Bordado
2021-07-02 11:56:58 +01:00
committed by Andrew Brown
parent f2d2f3a841
commit ce537cf431

View File

@@ -21,11 +21,19 @@ use thiserror::Error;
/// semantics for each Cranelift instruction (see [step]). /// semantics for each Cranelift instruction (see [step]).
pub struct Interpreter<'a> { pub struct Interpreter<'a> {
state: InterpreterState<'a>, state: InterpreterState<'a>,
fuel: Option<u64>,
} }
impl<'a> Interpreter<'a> { impl<'a> Interpreter<'a> {
pub fn new(state: InterpreterState<'a>) -> Self { pub fn new(state: InterpreterState<'a>) -> Self {
Self { state } Self { state, fuel: None }
}
/// The `fuel` mechanism sets a number of instructions that
/// the interpreter can execute before stopping. If this
/// value is `None` (the default), no limit is imposed.
pub fn with_fuel(self, fuel: Option<u64>) -> Self {
Self { fuel, ..self }
} }
/// Call a function by name; this is a helpful proxy for [Interpreter::call_by_index]. /// Call a function by name; this is a helpful proxy for [Interpreter::call_by_index].
@@ -83,6 +91,10 @@ impl<'a> Interpreter<'a> {
let layout = &function.layout; let layout = &function.layout;
let mut maybe_inst = layout.first_inst(block); let mut maybe_inst = layout.first_inst(block);
while let Some(inst) = maybe_inst { while let Some(inst) = maybe_inst {
if self.consume_fuel() == FuelResult::Stop {
return Err(InterpreterError::FuelExhausted);
}
let inst_context = DfgInstructionContext::new(inst, &function.dfg); let inst_context = DfgInstructionContext::new(inst, &function.dfg);
match step(&mut self.state, inst_context)? { match step(&mut self.state, inst_context)? {
ControlFlow::Assign(values) => { ControlFlow::Assign(values) => {
@@ -116,6 +128,28 @@ impl<'a> Interpreter<'a> {
} }
Err(InterpreterError::Unreachable) Err(InterpreterError::Unreachable)
} }
fn consume_fuel(&mut self) -> FuelResult {
match self.fuel {
Some(0) => FuelResult::Stop,
Some(ref mut n) => {
*n -= 1;
FuelResult::Continue
}
// We do not have fuel enabled, so unconditionally continue
None => FuelResult::Continue,
}
}
}
#[derive(Debug, PartialEq, Clone)]
/// The result of consuming fuel. Signals if the caller should stop or continue.
pub enum FuelResult {
/// We still have `fuel` available and should continue execution.
Continue,
/// The available `fuel` has been exhausted, we should stop now.
Stop,
} }
/// The ways interpretation can fail. /// The ways interpretation can fail.
@@ -131,6 +165,8 @@ pub enum InterpreterError {
UnknownFunctionName(String), UnknownFunctionName(String),
#[error("value error")] #[error("value error")]
ValueError(#[from] ValueError), ValueError(#[from] ValueError),
#[error("fuel exhausted")]
FuelExhausted,
} }
/// Maintains the [Interpreter]'s state, implementing the [State] trait. /// Maintains the [Interpreter]'s state, implementing the [State] trait.
@@ -334,4 +370,46 @@ mod tests {
state.clear_flags(); state.clear_flags();
assert!(!state.has_iflag(flag)); assert!(!state.has_iflag(flag));
} }
#[test]
fn fuel() {
let code = "function %test() -> b1 {
block0:
v0 = iconst.i32 1
v1 = iadd_imm v0, 1
return v1
}";
let func = parse_functions(code).unwrap().into_iter().next().unwrap();
let mut env = FunctionStore::default();
env.add(func.name.to_string(), &func);
// The default interpreter should not enable the fuel mechanism
let state = InterpreterState::default().with_function_store(env.clone());
let result = Interpreter::new(state)
.call_by_name("%test", &[])
.unwrap()
.unwrap_return();
assert_eq!(result, vec![DataValue::I32(2)]);
// With 2 fuel, we should execute the iconst and iadd, but not the return thus giving a
// fuel exhausted error
let state = InterpreterState::default().with_function_store(env.clone());
let result = Interpreter::new(state)
.with_fuel(Some(2))
.call_by_name("%test", &[]);
match result {
Err(InterpreterError::FuelExhausted) => {}
_ => panic!("Expected Err(FuelExhausted), but got {:?}", result),
}
// With 3 fuel, we should be able to execute the return instruction, and complete the test
let state = InterpreterState::default().with_function_store(env.clone());
let result = Interpreter::new(state)
.with_fuel(Some(3))
.call_by_name("%test", &[])
.unwrap()
.unwrap_return();
assert_eq!(result, vec![DataValue::I32(2)]);
}
} }