From c4b48155fb8168102e676ded36f3b1ab5e3bcf36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20L=C3=B6thberg?= Date: Sun, 13 Nov 2022 14:31:02 +0100 Subject: [PATCH] Add generic interface for conditionally halting the interpreter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Johannes Löthberg --- cranefack/src/backends/interpreter.rs | 59 ++++++++++++++++++------ cranefack/src/errors.rs | 7 +++ cranefack/src/lib.rs | 1 + cranefack/src/limiters.rs | 66 +++++++++++++++++++++++++++ 4 files changed, 118 insertions(+), 15 deletions(-) create mode 100644 cranefack/src/limiters.rs diff --git a/cranefack/src/backends/interpreter.rs b/cranefack/src/backends/interpreter.rs index b94af23..48fba20 100644 --- a/cranefack/src/backends/interpreter.rs +++ b/cranefack/src/backends/interpreter.rs @@ -4,12 +4,14 @@ use std::ops::Range; use crate::errors::RuntimeError; use crate::ir::ops::{LoopDecrement, Op, OpType}; +use crate::limiters::{Limiter, LimiterResult, Unlimited}; use crate::parser::Program; const MAX_HEAP_SIZE: usize = 16 * 1024 * 1024; /// Interpreter to execute a program -pub struct Interpreter { +pub struct Interpreter { + limiter: L, max_heap_size: usize, pub(crate) heap: Vec, pointer: usize, @@ -17,10 +19,25 @@ pub struct Interpreter { output: W, } -impl Interpreter { +impl Interpreter { /// Create a default interpreter - pub fn new(input: R, output: W) -> Interpreter { + pub fn new(input: R, output: W) -> Interpreter { Interpreter { + limiter: Unlimited, + max_heap_size: MAX_HEAP_SIZE, + heap: vec![0; 1024], + pointer: 0, + input, + output, + } + } +} + +impl Interpreter { + /// Create an interpreter with specific limiter + pub fn with_limiter(input: R, output: W, limiter: L) -> Interpreter { + Interpreter { + limiter, max_heap_size: MAX_HEAP_SIZE, heap: vec![0; 1024], pointer: 0, @@ -31,10 +48,16 @@ impl Interpreter { /// Execute program pub fn execute(&mut self, program: &Program) -> Result<(), RuntimeError> { - self.execute_ops(&program.ops) + self.execute_ops(&program.ops, &(0..1)) } - fn execute_ops(&mut self, ops: &[Op]) -> Result<(), RuntimeError> { + fn execute_ops(&mut self, ops: &[Op], span: &Range) -> Result<(), RuntimeError> { + if let LimiterResult::Halt = self.limiter.execute_op() { + return Err(RuntimeError::LimiterTriggered { + span: span.clone(), + }); + } + for op in ops { self.execute_op(op)?; } @@ -43,6 +66,12 @@ impl Interpreter { } fn execute_op(&mut self, op: &Op) -> Result<(), RuntimeError> { + if let LimiterResult::Halt = self.limiter.execute_op() { + return Err(RuntimeError::LimiterTriggered { + span: op.span.clone(), + }); + } + match &op.op_type { OpType::Start => { // ignore @@ -125,14 +154,14 @@ impl Interpreter { OpType::PutString(array) => self.put_string(&op.span, array)?, OpType::DLoop(ops, _) => { while *self.heap_value(&op.span)? > 0 { - self.execute_ops(ops)?; + self.execute_ops(ops, &op.span)?; } } OpType::LLoop(ops, _) => { let heap_pointer = self.pointer; while *self.heap_value(&op.span)? > 0 { - self.execute_ops(ops)?; + self.execute_ops(ops, &op.span)?; self.pointer = heap_pointer; } } @@ -144,7 +173,7 @@ impl Interpreter { while left > 0 { left = left.wrapping_sub(*step); *self.heap_value(&op.span)? = left; - self.execute_ops(ops)?; + self.execute_ops(ops, &op.span)?; self.pointer = heap_pointer; } @@ -155,7 +184,7 @@ impl Interpreter { let mut left = *self.heap_value(&op.span)?; while left > 0 { - self.execute_ops(ops)?; + self.execute_ops(ops, &op.span)?; left = left.wrapping_sub(*step); *self.heap_value(&op.span)? = left; self.pointer = heap_pointer; @@ -168,7 +197,7 @@ impl Interpreter { let mut left = *self.heap_value(&op.span)?; while left > 0 { - self.execute_ops(ops)?; + self.execute_ops(ops, &op.span)?; left = left.wrapping_sub(*step); self.pointer = heap_pointer; } @@ -185,7 +214,7 @@ impl Interpreter { while left > 0 { left = left.wrapping_sub(1); *self.heap_value(&op.span)? = left; - self.execute_ops(ops)?; + self.execute_ops(ops, &op.span)?; self.pointer = heap_pointer; } @@ -197,7 +226,7 @@ impl Interpreter { *self.heap_value(&op.span)? = *iterations; let mut left = *self.heap_value(&op.span)?; while left > 0 { - self.execute_ops(ops)?; + self.execute_ops(ops, &op.span)?; self.pointer = heap_pointer; left = left.wrapping_sub(1); *self.heap_value(&op.span)? = left; @@ -208,7 +237,7 @@ impl Interpreter { LoopDecrement::Auto => { let heap_pointer = self.pointer; for _ in 0..*iterations { - self.execute_ops(ops)?; + self.execute_ops(ops, &op.span)?; self.pointer = heap_pointer; } *self.heap_value(&op.span)? = 0; @@ -218,7 +247,7 @@ impl Interpreter { if *self.heap_value(&op.span)? != 0 { let heap_pointer = self.pointer; - self.execute_ops(ops)?; + self.execute_ops(ops, &op.span)?; self.pointer = heap_pointer; *self.heap_value(&op.span)? = 0; @@ -226,7 +255,7 @@ impl Interpreter { } OpType::DTNz(ops, _, _) => { if *self.heap_value(&op.span)? > 0 { - self.execute_ops(ops)?; + self.execute_ops(ops, &op.span)?; } } OpType::SearchZero(step, _) => { diff --git a/cranefack/src/errors.rs b/cranefack/src/errors.rs index 9d54c4c..364c579 100644 --- a/cranefack/src/errors.rs +++ b/cranefack/src/errors.rs @@ -99,6 +99,11 @@ impl CraneFackError for ParserError { /// Runtime errors for interpreter invocations #[derive(Debug)] pub enum RuntimeError { + /// The interpreter's configured [`Limiter`][crate::limiters::Limiter] triggered + LimiterTriggered { + span: Range, + }, + /// The program tries to use a heap cell beyond the maximu allowed size MaxHeapSizeReached { span: Range, @@ -125,6 +130,7 @@ impl Error for RuntimeError { impl Display for RuntimeError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { + RuntimeError::LimiterTriggered { .. } => write!(f, "Max number of cycles reached"), RuntimeError::MaxHeapSizeReached { max_heap_size, required, @@ -142,6 +148,7 @@ impl Display for RuntimeError { impl CraneFackError for RuntimeError { fn get_message(&self) -> (Option>, String, Option) { match self { + RuntimeError::LimiterTriggered { span } => (Some(span.clone()), self.to_string(), None), RuntimeError::MaxHeapSizeReached { span, .. } => { (Some(span.clone()), self.to_string(), None) } diff --git a/cranefack/src/lib.rs b/cranefack/src/lib.rs index 6023345..77f9e5d 100644 --- a/cranefack/src/lib.rs +++ b/cranefack/src/lib.rs @@ -55,6 +55,7 @@ mod analyzer; mod backends; mod errors; mod ir; +pub mod limiters; mod optimizations; mod parser; diff --git a/cranefack/src/limiters.rs b/cranefack/src/limiters.rs new file mode 100644 index 0000000..2883e45 --- /dev/null +++ b/cranefack/src/limiters.rs @@ -0,0 +1,66 @@ +pub enum LimiterResult { + /// Continue executing + Continue, + /// Halt execution and return [`LimiterTriggered`][crate::errors::RuntimeError::LimiterTriggered] + Halt, +} + +/// Generic interface for determining whether to continue or stop execution. +pub trait Limiter { + fn execute_op(&mut self) -> LimiterResult; +} + +/// Always returns [`Continue`][LimiterResult::Continue]. +pub struct Unlimited; + +impl Limiter for Unlimited { + fn execute_op(&mut self) -> LimiterResult { + LimiterResult::Continue + } +} + +/// Limit execution to within the configured number of cycles. +pub struct CycleLimiter(usize); + +impl CycleLimiter { + pub fn new(max_cycles: usize) -> Self { + CycleLimiter(max_cycles) + } +} + +impl Limiter for CycleLimiter { + fn execute_op(&mut self) -> LimiterResult { + self.0 = self.0.saturating_sub(1); + if self.0 == 0 { + LimiterResult::Halt + } else { + LimiterResult::Continue + } + } +} + +/// Limits execution to within a given [`Duration`](std::time::Duration) of time. +pub struct TimeLimiter { + max_duration: std::time::Duration, + started_at: Option, +} + +impl TimeLimiter { + pub fn new(max_duration: std::time::Duration) -> Self { + TimeLimiter { + max_duration, + started_at: None, + } + } +} + +impl Limiter for TimeLimiter { + fn execute_op(&mut self) -> LimiterResult { + let started_at = self.started_at.get_or_insert(std::time::Instant::now()); + if started_at.elapsed() > self.max_duration { + LimiterResult::Halt + } else { + LimiterResult::Continue + } + } +}