From 0bf93e59b521b93789dc94d8d10eed62420870a4 Mon Sep 17 00:00:00 2001 From: Kasey White Date: Wed, 6 Jul 2022 02:11:46 -0400 Subject: [PATCH] add evaluation for terms Co-authored-by: rvcas --- crates/uplc/src/interpreter.rs | 150 --------------------- crates/uplc/src/lib.rs | 1 - crates/uplc/src/machine.rs | 225 ++++++++++++++++++++++++++++--- crates/uplc/src/machine/error.rs | 12 +- 4 files changed, 220 insertions(+), 168 deletions(-) delete mode 100644 crates/uplc/src/interpreter.rs diff --git a/crates/uplc/src/interpreter.rs b/crates/uplc/src/interpreter.rs deleted file mode 100644 index 4ccfe9a2..00000000 --- a/crates/uplc/src/interpreter.rs +++ /dev/null @@ -1,150 +0,0 @@ -use flat_rs::de; - -use crate::{ - ast::{Constant, NamedDeBruijn, Term}, - builtins::DefaultFunction, -}; - -#[repr(u8)] -#[derive(Debug, Clone, PartialEq, Copy)] -enum StepKind { - BConst = 0, - BVar = 1, - BLamAbs = 2, - BApply = 3, - BDelay = 4, - BForce = 5, - BBuiltin = 6, -} - -impl TryFrom for StepKind { - type Error = de::Error; - - fn try_from(value: u8) -> Result { - match value { - 0 => Ok(StepKind::BConst), - 1 => Ok(StepKind::BVar), - 2 => Ok(StepKind::BLamAbs), - 3 => Ok(StepKind::BApply), - 4 => Ok(StepKind::BDelay), - 5 => Ok(StepKind::BForce), - 6 => Ok(StepKind::BBuiltin), - v => Err(de::Error::Message(format!( - "Default Function not found - {}", - v - ))), - } - } -} - -enum ExBudgetCategory { - BStep(StepKind), - BBuiltinApp(DefaultFunction), - BStartup, -} - -/// Can be negative -#[derive(Debug, Clone, PartialEq)] -struct ExBudget { - mem: i32, - cpu: i32, -} - -impl std::ops::Mul for ExBudget { - type Output = ExBudget; - - fn mul(self, rhs: i32) -> ExBudget { - ExBudget { - mem: self.mem * rhs, - cpu: self.cpu * rhs, - } - } -} - -enum CekValue { - VCon(Constant), - VDelay(Term, Vec), - VLamAbs(NamedDeBruijn, Term, Vec), - VBuiltin( - DefaultFunction, - Term, - // Need to figure out run time stuff - // BuiltinRuntime (CekValue uni fun) - ), -} - -enum Context { - FrameApplyFun(Term, Term), - FrameApplyArg(Vec, Term, Box), - FrameForce(Box), - NoFrame, -} - -// For now let's just start with running cek on term with generic params -fn run_cek_debruijn(term: Term) -> (Term, usize, Vec) { - //paramerterize this - let mut initial_budget = ExBudget { - mem: 1000, - cpu: 1000, - }; - let startup_budget = ExBudget { mem: 10, cpu: 10 }; - spend_budget_cek( - &mut initial_budget, - ExBudgetCategory::BStartup, - startup_budget, - ); - let evaluation = enter_compute_cek(Context::NoFrame, Vec::new(), term, &mut initial_budget); - todo!() -} -fn enter_compute_cek( - frame: Context, - env: Vec, - term: Term, - current_budget: &mut ExBudget, -) -> (Term, usize, Vec) { - //enter compute - compute_cek(&mut Vec::with_capacity(8), frame, env, term, current_budget) -} - -fn compute_cek( - unbudgeted_steps: &mut Vec, - frame: Context, - env: Vec, - term: Term, - current_budget: &mut ExBudget, -) -> (Term, usize, Vec) { - //TODO: parameterize slippage - let slippage = 200; - match (term) { - a @ Term::Var(_) => { - unbudgeted_steps[2] += 1; - unbudgeted_steps[7] += 1; - if unbudgeted_steps[7] >= slippage { - spend_accumulated_budget_cek(unbudgeted_steps, current_budget); - } - todo!() - } - Term::Delay(_) => todo!(), - Term::Lambda { - parameter_name, - body, - } => todo!(), - Term::Apply { function, argument } => todo!(), - Term::Constant(_) => todo!(), - Term::Force(_) => todo!(), - Term::Error => todo!(), - Term::Builtin(_) => todo!(), - } -} - -fn spend_accumulated_budget_cek(unbudgeted_steps: &mut Vec, current_budget: &mut ExBudget) { - //only spend each step kind and not total which is index - for i in 0..unbudgeted_steps.len() - 1 { - let step = StepKind::try_from(i as u8).unwrap(); - spend_budget_cek( - current_budget, - ExBudgetCategory::BStep(step), - get_cost_by_step(step) * unbudgeted_steps[i] as i32, - ); - } -} diff --git a/crates/uplc/src/lib.rs b/crates/uplc/src/lib.rs index 713d5565..ce1c2da4 100644 --- a/crates/uplc/src/lib.rs +++ b/crates/uplc/src/lib.rs @@ -2,7 +2,6 @@ pub mod ast; pub mod builtins; mod debruijn; mod flat; -pub mod interpreter; pub mod machine; pub mod parser; mod pretty; diff --git a/crates/uplc/src/machine.rs b/crates/uplc/src/machine.rs index 19bab916..eabcda15 100644 --- a/crates/uplc/src/machine.rs +++ b/crates/uplc/src/machine.rs @@ -1,5 +1,5 @@ use crate::{ - ast::{Constant, DeBruijn, Term}, + ast::{Constant, NamedDeBruijn, Term}, builtins::DefaultFunction, }; @@ -13,6 +13,7 @@ pub struct Machine { frames: Vec, slippage: u32, env: Vec, + unbudgeted_steps: Vec, } impl Machine { @@ -23,23 +24,172 @@ impl Machine { slippage, frames: vec![], env: vec![], + unbudgeted_steps: vec![0; 8], } } pub fn run( &mut self, - term: &Term, - ) -> Result<(Term, usize, Vec), Error> { + term: &Term, + ) -> Result<(Term, usize, Vec), Error> { let startup_budget = self.costs.get(StepKind::StartUp); self.spend_budget(startup_budget)?; self.push_frame(Context::NoFrame); - self.enter_compute(term) + self.enter_compute(term)?; + todo!() } - fn enter_compute(&mut self, term: &Term) {} + fn enter_compute(&mut self, term: &Term) -> Result, Error> { + match term { + Term::Var(name) => { + self.unbudgeted_steps[1] += 1; + self.unbudgeted_steps[7] += 1; + if self.unbudgeted_steps[7] >= self.slippage { + self.spend_unbudgeted_steps()?; + } + let val = self.lookup_var(name.clone())?; + self.return_compute(val) + } + Term::Delay(body) => { + self.unbudgeted_steps[4] += 1; + self.unbudgeted_steps[7] += 1; + if self.unbudgeted_steps[7] >= self.slippage { + self.spend_unbudgeted_steps()?; + } + self.return_compute(Value::Delay(*body.clone())) + } + Term::Lambda { + parameter_name, + body, + } => { + self.unbudgeted_steps[2] += 1; + self.unbudgeted_steps[7] += 1; + if self.unbudgeted_steps[7] >= self.slippage { + self.spend_unbudgeted_steps()?; + } + self.return_compute(Value::Lambda { + parameter_name: parameter_name.clone(), + body: *body.clone(), + }) + } + Term::Apply { function, argument } => { + self.unbudgeted_steps[3] += 1; + self.unbudgeted_steps[7] += 1; + if self.unbudgeted_steps[7] >= self.slippage { + self.spend_unbudgeted_steps()?; + } + self.push_frame(Context::FrameApplyArg(*argument.clone())); + self.enter_compute(function) + } + Term::Constant(x) => { + self.unbudgeted_steps[0] += 1; + self.unbudgeted_steps[7] += 1; + if self.unbudgeted_steps[7] >= self.slippage { + self.spend_unbudgeted_steps()?; + } + self.return_compute(Value::Con(x.clone())) + } + Term::Force(body) => { + self.unbudgeted_steps[5] += 1; + self.unbudgeted_steps[7] += 1; + if self.unbudgeted_steps[7] >= self.slippage { + self.spend_unbudgeted_steps()?; + } + self.push_frame(Context::FrameForce); + self.enter_compute(body) + } + Term::Error => Err(Error::EvaluationFailure), + Term::Builtin(_) => todo!(), + } + } + + fn return_compute(&mut self, value: Value) -> Result, Error> { + let frame = self.frames.last().cloned().unwrap(); + match frame { + Context::FrameApplyFun(function) => { + self.pop_frame(); + self.apply_evaluate(function, value) + } + Context::FrameApplyArg(arg) => { + self.pop_frame(); + self.push_frame(Context::FrameApplyFun(value)); + self.enter_compute(&arg) + } + Context::FrameForce => { + self.pop_frame(); + self.force_evaluate(value) + } + Context::NoFrame => { + if self.unbudgeted_steps[7] > 0 { + self.spend_unbudgeted_steps()?; + } + + let term = self.discharge_value(value); + Ok(term) + } + } + } + + fn discharge_value(&mut self, value: Value) -> Term { + match value { + Value::Con(x) => Term::Constant(x), + Value::Builtin(_, t) => t, + Value::Delay(_) => todo!(), + Value::Lambda { + parameter_name, + body, + } => self.discharge_value_env(Term::Lambda { + parameter_name: NamedDeBruijn { + text: parameter_name.text, + index: 0.into(), + }, + body: Box::new(body), + }), + } + } + + fn discharge_value_env(&mut self, term: Term) -> Term { + fn rec(i: u32, t: Term) -> Term { + match t { + Term::Var(x) => todo!(), + Term::Lambda { + parameter_name, + body, + } => Term::Lambda { + parameter_name, + body: Box::new(rec(i + 1, *body)), + }, + Term::Apply { function, argument } => Term::Apply { + function: Box::new(rec(i, *function)), + argument: Box::new(rec(i, *argument)), + }, + + Term::Delay(x) => Term::Delay(Box::new(rec(i, *x))), + Term::Force(x) => Term::Force(Box::new(rec(i, *x))), + rest => rest, + } + } + rec(0, term) + } + + fn force_evaluate(&mut self, value: Value) -> Result, Error> { + match value { + Value::Delay(body) => self.enter_compute(&body), + Value::Builtin(_, _) => todo!(), + rest => Err(Error::NonPolymorphicInstantiation(rest)), + } + } + + fn apply_evaluate( + &mut self, + function: Value, + argument: Value, + ) -> Result, Error> { + todo!() + } fn spend_budget(&mut self, spend_budget: ExBudget) -> Result<(), Error> { self.ex_budget.mem -= spend_budget.mem; @@ -55,30 +205,56 @@ impl Machine { fn push_frame(&mut self, frame: Context) { self.frames.push(frame); } + + fn pop_frame(&mut self) { + self.frames.pop(); + } + + fn lookup_var(&mut self, name: NamedDeBruijn) -> Result { + self.env + .get::(name.index.into()) + .cloned() + .ok_or(Error::OpenTermEvaluated(Term::Var(name))) + } + + fn spend_unbudgeted_steps(&mut self) -> Result<(), Error> { + for i in 0..self.unbudgeted_steps.len() - 1 { + let mut unspent_step_budget = self.costs.get(StepKind::try_from(i as u8)?); + unspent_step_budget.occurence(self.unbudgeted_steps[i] as i32); + self.spend_budget(unspent_step_budget)?; + } + self.unbudgeted_steps = vec![0; 8]; + Ok(()) + } } +#[derive(Clone)] enum Context { - FrameApplyFun(Term, Term), - FrameApplyArg(Vec, Term, Box), - FrameForce(Box), + FrameApplyFun(Value), + FrameApplyArg(Term), + FrameForce, NoFrame, } -enum Value { +#[derive(Clone, Debug)] +pub enum Value { Con(Constant), - Delay(Term, Vec), - Lambda(DeBruijn, Term, Vec), + Delay(Term), + Lambda { + parameter_name: NamedDeBruijn, + body: Term, + }, Builtin( DefaultFunction, - Term, + Term, // Need to figure out run time stuff // BuiltinRuntime (CekValue uni fun) ), } /// Can be negative -#[derive(Debug, Clone, PartialEq)] -struct ExBudget { +#[derive(Debug, Clone, PartialEq, Copy)] +pub struct ExBudget { mem: i32, cpu: i32, } @@ -90,7 +266,7 @@ impl ExBudget { } } -enum StepKind { +pub enum StepKind { Constant, Var, Lambda, @@ -101,9 +277,26 @@ enum StepKind { StartUp, } +impl TryFrom for StepKind { + type Error = error::Error; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(StepKind::Constant), + 1 => Ok(StepKind::Var), + 2 => Ok(StepKind::Lambda), + 3 => Ok(StepKind::Apply), + 4 => Ok(StepKind::Delay), + 5 => Ok(StepKind::Force), + 6 => Ok(StepKind::Builtin), + v => Err(error::Error::InvalidStepKind(v)), + } + } +} + /// There's no entry for Error since we'll be exiting anyway; also, what would /// happen if calling 'Error' caused the budget to be exceeded? -struct Costs { +pub struct Costs { startup: ExBudget, var: ExBudget, constant: ExBudget, diff --git a/crates/uplc/src/machine/error.rs b/crates/uplc/src/machine/error.rs index 83350b1b..9ee363a8 100644 --- a/crates/uplc/src/machine/error.rs +++ b/crates/uplc/src/machine/error.rs @@ -1,9 +1,19 @@ use thiserror::Error; -use super::ExBudget; +use crate::ast::{NamedDeBruijn, Term}; + +use super::{ExBudget, Value}; #[derive(Error, Debug)] pub enum Error { #[error("Over budget mem: {} & cpu: {}", .0.mem, .0.cpu)] OutOfExError(ExBudget), + #[error("Invalid Stepkind: {0}")] + InvalidStepKind(u8), + #[error("Cannot evaluate an open term: {0:#?}")] + OpenTermEvaluated(Term), + #[error("The provided Plutus code called 'error'.")] + EvaluationFailure, + #[error("Attempted to instantiate a non-polymorphic term: {0:#?}")] + NonPolymorphicInstantiation(Value), }