diff --git a/crates/uplc/src/optimize/shrinker.rs b/crates/uplc/src/optimize/shrinker.rs index e2c9d590..78ed3937 100644 --- a/crates/uplc/src/optimize/shrinker.rs +++ b/crates/uplc/src/optimize/shrinker.rs @@ -2,6 +2,7 @@ use std::{rc::Rc, vec}; use indexmap::IndexMap; use itertools::Itertools; + use pallas_primitives::babbage::{BigInt, PlutusData}; use crate::{ @@ -9,14 +10,77 @@ use crate::{ builtins::DefaultFunction, }; -#[derive(Eq, Hash, PartialEq, Clone)] -pub struct Occurrence { - name: Rc, - lambda_count: usize, +#[derive(PartialEq, Clone)] +pub enum CurriedTree { + Branch { + node: Term, + multiple_occurrences: bool, + children: Vec, + scope: Scope, + }, + Leaf { + node: Term, + multiple_occurrences: bool, + scope: Scope, + }, } +pub struct CurriedBuiltin { + pub func: DefaultFunction, + /// For use with subtract integer where we can flip the order of the arguments + /// if the second argument is a constant + pub flipped: bool, + pub children: Vec, +} + +#[derive(Eq, Hash, PartialEq, Clone)] +pub enum ScopePath { + FUNC, + ARG, +} + +#[derive(Eq, Hash, PartialEq, Clone)] +pub struct Scope { + scope: Vec, +} + +impl Scope { + pub fn new() -> Self { + Self { scope: vec![] } + } + + pub fn push(&self, path: ScopePath) -> Self { + let mut new_scope = self.scope.clone(); + new_scope.push(path); + Scope { scope: new_scope } + } + + pub fn pop(&self) -> Self { + let mut new_scope = self.scope.clone(); + new_scope.pop(); + Scope { scope: new_scope } + } + + pub fn common_ancestor(&self, other: &Scope) -> Self { + Scope { + scope: self + .scope + .iter() + .zip(other.scope.iter()) + .map_while(|(a, b)| if a == b { Some(a) } else { None }) + .cloned() + .collect_vec(), + } + } +} + +impl Default for Scope { + fn default() -> Self { + Self::new() + } +} pub struct IdGen { - id: u64, + id: usize, } impl IdGen { @@ -24,7 +88,7 @@ impl IdGen { Self { id: 0 } } - pub fn next_id(&mut self) -> u64 { + pub fn next_id(&mut self) -> usize { self.id += 1; self.id } @@ -36,20 +100,213 @@ impl Default for IdGen { } } +pub fn is_order_agnostic_builtin(func: DefaultFunction) -> bool { + matches!( + func, + DefaultFunction::AddInteger + | DefaultFunction::MultiplyInteger + | DefaultFunction::EqualsInteger + | DefaultFunction::EqualsByteString + | DefaultFunction::EqualsString + | DefaultFunction::EqualsData + | DefaultFunction::Bls12_381_G1_Equal + | DefaultFunction::Bls12_381_G2_Equal + | DefaultFunction::Bls12_381_G1_Add + | DefaultFunction::Bls12_381_G2_Add + ) +} +/// For now all of the curry builtins are not forceable +pub fn can_curry_builtin(func: DefaultFunction) -> bool { + matches!( + func, + DefaultFunction::AddInteger + | DefaultFunction::SubtractInteger + | DefaultFunction::MultiplyInteger + | DefaultFunction::EqualsInteger + | DefaultFunction::EqualsByteString + | DefaultFunction::EqualsString + | DefaultFunction::EqualsData + | DefaultFunction::Bls12_381_G1_Equal + | DefaultFunction::Bls12_381_G2_Equal + | DefaultFunction::LessThanInteger + | DefaultFunction::LessThanEqualsInteger + | DefaultFunction::AppendByteString + | DefaultFunction::ConsByteString + | DefaultFunction::SliceByteString + | DefaultFunction::LengthOfByteString + | DefaultFunction::IndexByteString + | DefaultFunction::Bls12_381_G1_Add + | DefaultFunction::Bls12_381_G2_Add + ) +} + impl Program { - pub fn lambda_reducer(self) -> Program { + fn traverse_uplc_with( + self, + with: &mut impl FnMut(Option, &mut Term, Vec<(usize, Term)>, &Scope), + ) -> Self { let mut term = self.term; - lambda_reducer(&mut term, &mut vec![], &mut vec![], &mut IdGen::new()); + let scope = Scope { scope: vec![] }; + let arg_stack = vec![]; + let mut id_gen = IdGen::new(); + + Self::traverse_uplc_with_helper(&mut term, &scope, arg_stack, &mut id_gen, with); Program { version: self.version, term, } } + fn traverse_uplc_with_helper( + term: &mut Term, + scope: &Scope, + mut arg_stack: Vec<(usize, Term)>, + id_gen: &mut IdGen, + with: &mut impl FnMut(Option, &mut Term, Vec<(usize, Term)>, &Scope), + ) { + match term { + Term::Apply { function, argument } => { + let arg = Rc::make_mut(argument); + let argument_arg_stack = vec![]; + Self::traverse_uplc_with_helper( + arg, + &scope.push(ScopePath::ARG), + argument_arg_stack, + id_gen, + with, + ); + let apply_id = id_gen.next_id(); + + arg_stack.push((apply_id, arg.clone())); + + let func = Rc::make_mut(function); + + Self::traverse_uplc_with_helper( + func, + &scope.push(ScopePath::FUNC), + arg_stack, + id_gen, + with, + ); + + with(Some(apply_id), term, vec![], scope); + } + Term::Delay(d) => { + let d = Rc::make_mut(d); + // First we recurse further to reduce the inner terms before coming back up to the Delay + Self::traverse_uplc_with_helper(d, scope, arg_stack, id_gen, with); + with(None, term, vec![], scope); + } + Term::Lambda { body, .. } => { + let body = Rc::make_mut(body); + // Lambda pops one item off the arg stack. If there is no item then it is a unsaturated lambda + let args = arg_stack.pop().map(|arg| vec![arg]).unwrap_or_default(); + + // Pass in either one or zero args. + Self::traverse_uplc_with_helper(body, scope, arg_stack, id_gen, with); + with(None, term, args, scope); + } + + Term::Force(f) => { + let f = Rc::make_mut(f); + Self::traverse_uplc_with_helper(f, scope, arg_stack, id_gen, with); + with(None, term, vec![], scope); + } + Term::Case { .. } => todo!(), + Term::Constr { .. } => todo!(), + + Term::Builtin(func) => { + let mut args = vec![]; + + for _ in 0..func.arity() { + if let Some(arg) = arg_stack.pop() { + args.push(arg); + } + } + // Pass in either args up to function arity. + with(None, term, args, scope); + } + term => { + with(None, term, vec![], scope); + } + } + } + + pub fn lambda_reducer(self) -> Self { + let mut lambda_applied_ids = vec![]; + + self.traverse_uplc_with(&mut |id, term, mut arg_stack, _scope| { + match term { + Term::Apply { function, .. } => { + // We are apply some arg so now we unwrap the id of the applied arg + let id = id.unwrap(); + + if lambda_applied_ids.contains(&id) { + let func = Rc::make_mut(function); + // we inlined the arg so now remove the apply and arg from the program + *term = func.clone(); + } + } + Term::Lambda { + parameter_name, + body, + } => { + // pops stack here no matter what + if let Some((arg_id, arg_term)) = arg_stack.pop() { + match arg_term { + Term::Constant(c) if matches!(c.as_ref(), Constant::String(_)) => {} + Term::Constant(_) | Term::Var(_) | Term::Builtin(_) => { + let body = Rc::make_mut(body); + // mutates body to replace all var occurrences with the arg + *body = substitute_var(body, parameter_name.clone(), &arg_term); + lambda_applied_ids.push(arg_id); + *term = body.clone(); + } + _ => {} + } + } + } + Term::Case { .. } => todo!(), + Term::Constr { .. } => todo!(), + _ => {} + } + }) + } + pub fn builtin_force_reducer(self) -> Program { - let mut term = self.term; let mut builtin_map = IndexMap::new(); - builtin_force_reducer(&mut term, &mut builtin_map); + + let program = self.traverse_uplc_with(&mut |_id, term, _arg_stack, _scope| { + if let Term::Force(f) = term { + let f = Rc::make_mut(f); + match f { + Term::Force(inner_f) => { + if let Term::Builtin(func) = inner_f.as_ref() { + builtin_map.insert(*func as u8, ()); + *term = Term::Var( + Name { + text: format!("__{}_wrapped", func.aiken_name()), + unique: 0.into(), + } + .into(), + ); + } + } + Term::Builtin(func) => { + builtin_map.insert(*func as u8, ()); + *term = Term::Var( + Name { + text: format!("__{}_wrapped", func.aiken_name()), + unique: 0.into(), + } + .into(), + ); + } + _ => {} + } + } + }); + let mut term = program.term; for default_func_index in builtin_map.keys().sorted().cloned() { let default_func: DefaultFunction = default_func_index.try_into().unwrap(); @@ -64,506 +321,363 @@ impl Program { } Program { - version: self.version, + version: program.version, term, } } pub fn inline_reducer(self) -> Program { - let mut term = self.term; - inline_single_occurrence_reducer(&mut term, &mut vec![], &mut vec![], &mut IdGen::new()); - inline_direct_reducer(&mut term); - inline_identity_reducer(&mut term); + let mut lambda_applied_ids = vec![]; + let mut identity_applied_ids = vec![]; + // TODO: Remove extra traversals + self.traverse_uplc_with(&mut |_id, term, _arg_stack, _scope| { + // Since this one just inlines single occurrences. It's probably not needed + if let Term::Apply { function, argument } = term { + let func = Rc::make_mut(function); - Program { - version: self.version, - term, - } - } - - pub fn force_delay_reducer(self) -> Program { - let mut term = self.term; - force_delay_reducer(&mut term); - Program { - version: self.version, - term, - } - } - - pub fn cast_data_reducer(self) -> Program { - let mut term = self.term; - cast_data_reducer(&mut term); - Program { - version: self.version, - term, - } - } -} - -fn builtin_force_reducer(term: &mut Term, builtin_map: &mut IndexMap) { - match term { - Term::Force(f) => { - let f = Rc::make_mut(f); - - match f { - Term::Force(inner_f) => { - if let Term::Builtin(func) = inner_f.as_ref() { - builtin_map.insert(*func as u8, ()); - *term = Term::Var( - Name { - text: format!("__{}_wrapped", func.aiken_name()), - unique: 0.into(), - } - .into(), - ); - return; + if let Term::Lambda { + parameter_name, + body, + } = func + { + if let Term::Var(name) = body.as_ref() { + if name.as_ref() == parameter_name.as_ref() { + *term = argument.as_ref().clone(); + } } } - Term::Builtin(func) => { - builtin_map.insert(*func as u8, ()); - *term = Term::Var( - Name { - text: format!("__{}_wrapped", func.aiken_name()), - unique: 0.into(), - } - .into(), - ); + } + }) + .traverse_uplc_with(&mut |id, term, mut arg_stack, _scope| { + match term { + Term::Apply { function, .. } => { + // We are apply some arg so now we unwrap the id of the applied arg + let id = id.unwrap(); - return; + if identity_applied_ids.contains(&id) { + let func = Rc::make_mut(function); + // we inlined the arg so now remove the apply and arg from the program + *term = func.clone(); + } } + Term::Lambda { + parameter_name, + body, + } => { + // pops stack here no matter what + if let Some(( + arg_id, + Term::Lambda { + parameter_name: identity_name, + body: identity_body, + }, + )) = arg_stack.pop() + { + if let Term::Var(identity_var) = identity_body.as_ref() { + if identity_var.as_ref() == identity_name.as_ref() { + // Replace all applied usages of identity with the arg + let temp_term = + replace_identity_usage(body.as_ref(), parameter_name.clone()); + // Have to check if the body still has any occurrences of the parameter + // After attempting replacement + if var_occurrences(body.as_ref(), parameter_name.clone()) > 0 { + let body = Rc::make_mut(body); + *body = temp_term; + } else { + identity_applied_ids.push(arg_id); + *term = temp_term; + } + } + } + } + } + + Term::Constr { .. } => todo!(), + Term::Case { .. } => todo!(), _ => {} } - builtin_force_reducer(f, builtin_map); - } - Term::Delay(d) => { - let d = Rc::make_mut(d); - builtin_force_reducer(d, builtin_map); - } - Term::Lambda { body, .. } => { - let body = Rc::make_mut(body); - builtin_force_reducer(body, builtin_map); - } - Term::Apply { function, argument } => { - let func = Rc::make_mut(function); - builtin_force_reducer(func, builtin_map); + }) + .traverse_uplc_with(&mut |id, term, mut arg_stack, _scope| match term { + Term::Apply { function, .. } => { + // We are apply some arg so now we unwrap the id of the applied arg + let id = id.unwrap(); - let arg = Rc::make_mut(argument); - builtin_force_reducer(arg, builtin_map); - } - Term::Case { .. } => todo!(), - Term::Constr { .. } => todo!(), - _ => {} - } -} - -fn force_delay_reducer(term: &mut Term) { - match term { - Term::Force(f) => { - let f = Rc::make_mut(f); - - if let Term::Delay(body) = f { - *term = body.as_ref().clone(); - force_delay_reducer(term); - } else { - force_delay_reducer(f); - } - } - Term::Delay(d) => { - let d = Rc::make_mut(d); - force_delay_reducer(d); - } - Term::Lambda { body, .. } => { - let body = Rc::make_mut(body); - force_delay_reducer(body); - } - Term::Apply { function, argument } => { - let func = Rc::make_mut(function); - force_delay_reducer(func); - - let arg = Rc::make_mut(argument); - force_delay_reducer(arg); - } - Term::Case { .. } => todo!(), - Term::Constr { .. } => todo!(), - _ => {} - } -} - -fn lambda_reducer( - term: &mut Term, - reduce_stack: &mut Vec, u64)>>, - lambda_applied_ids: &mut Vec, - id_gen: &mut IdGen, -) { - match term { - // TODO: change this to handle any amount of consecutive applies and lambdas - Term::Apply { function, argument } => { - let arg = Rc::make_mut(argument); - // SO args can't have existing stack args applied to them anyway, so we pass in empty list - let mut arg_stack = vec![]; - lambda_reducer(arg, &mut arg_stack, lambda_applied_ids, id_gen); - - let next_id = id_gen.next_id(); - - let arg_applied = match arg { - Term::Constant(c) if matches!(c.as_ref(), Constant::String(_)) => None, - Term::Constant(_) | Term::Var(_) | Term::Builtin(_) => Some((arg.clone(), next_id)), - _ => None, - }; - - reduce_stack.push(arg_applied); - - let func = Rc::make_mut(function); - lambda_reducer(func, reduce_stack, lambda_applied_ids, id_gen); - // The reason we don't need to pop is because the Lambda case and will pop for us - // It is guaranteed to pop otherwise the script is not valid anyways - if lambda_applied_ids.contains(&next_id) { - // we inlined the arg so now remove the apply and arg from the program - *term = func.clone(); - } - } - Term::Delay(d) => { - let d = Rc::make_mut(d); - lambda_reducer(d, reduce_stack, lambda_applied_ids, id_gen); - } - Term::Lambda { - parameter_name, - body, - } => { - // pops stack here no matter what - // match on only Some(Some(arg)) because we don't want to inline None - if let Some(Some((arg_term, arg_id))) = reduce_stack.pop() { - let body = Rc::make_mut(body); - *body = substitute_term(body, parameter_name.clone(), &arg_term); - lambda_reducer(body, reduce_stack, lambda_applied_ids, id_gen); - lambda_applied_ids.push(arg_id); - *term = body.clone(); - } else { - let body = Rc::make_mut(body); - lambda_reducer(body, reduce_stack, lambda_applied_ids, id_gen); - } - } - Term::Force(f) => { - let f = Rc::make_mut(f); - lambda_reducer(f, reduce_stack, lambda_applied_ids, id_gen); - } - Term::Case { .. } => todo!(), - Term::Constr { .. } => todo!(), - _ => {} - } -} - -fn inline_direct_reducer(term: &mut Term) { - match term { - Term::Delay(d) => { - let d = Rc::make_mut(d); - inline_direct_reducer(d); - } - Term::Lambda { body, .. } => { - let body = Rc::make_mut(body); - inline_direct_reducer(body); - } - - Term::Apply { function, argument } => { - let func = Rc::make_mut(function); - let arg = Rc::make_mut(argument); - - inline_direct_reducer(func); - inline_direct_reducer(arg); - - let Term::Lambda { - parameter_name, - body, - } = func - else { - return; - }; - - let Term::Var(name) = body.as_ref() else { - return; - }; - - if name.as_ref() == parameter_name.as_ref() { - *term = arg.clone(); - } - } - Term::Force(f) => { - let f = Rc::make_mut(f); - inline_direct_reducer(f); - } - Term::Case { .. } => todo!(), - Term::Constr { .. } => todo!(), - _ => {} - } -} - -fn inline_identity_reducer(term: &mut Term) { - match term { - Term::Delay(d) => { - let d = Rc::make_mut(d); - inline_identity_reducer(d); - } - Term::Lambda { body, .. } => { - let body = Rc::make_mut(body); - inline_identity_reducer(body); - } - Term::Apply { function, argument } => { - let func = Rc::make_mut(function); - let arg = Rc::make_mut(argument); - - inline_identity_reducer(func); - inline_identity_reducer(arg); - - let Term::Lambda { - parameter_name, - body, - } = func - else { - return; - }; - - let Term::Lambda { - parameter_name: identity_name, - body: identity_body, - } = arg - else { - return; - }; - - let Term::Var(identity_var) = Rc::make_mut(identity_body) else { - return; - }; - - if identity_var.as_ref() == identity_name.as_ref() { - let temp_term = replace_identity_usage(body, parameter_name.clone()); - if var_occurrences(body, parameter_name.clone()) > 0 { - let body = Rc::make_mut(body); - *body = temp_term; - } else { - *term = temp_term; + if lambda_applied_ids.contains(&id) { + let func = Rc::make_mut(function); + // we inlined the arg so now remove the apply and arg from the program + *term = func.clone(); } } - } - Term::Force(f) => { - let f = Rc::make_mut(f); - inline_identity_reducer(f); - } - Term::Case { .. } => todo!(), - Term::Constr { .. } => todo!(), - _ => {} - } -} + Term::Lambda { + parameter_name, + body, + } => { + // pops stack here no matter what + if let Some((arg_id, arg_term)) = arg_stack.pop() { + let body = Rc::make_mut(body); + let occurrences = var_occurrences(body, parameter_name.clone()); + let delays = delayed_execution(body); -fn inline_single_occurrence_reducer( - term: &mut Term, - reduce_stack: &mut Vec<(Term, u64)>, - lambda_applied_ids: &mut Vec, - id_gen: &mut IdGen, -) { - match term { - // TODO: change this to handle any amount of consecutive applies and lambdas - Term::Apply { function, argument } => { - let arg = Rc::make_mut(argument); - // SO args can't have existing stack args applied to them anyway, so we pass in empty list - let mut arg_stack = vec![]; - inline_single_occurrence_reducer(arg, &mut arg_stack, lambda_applied_ids, id_gen); + if occurrences == 1 + && (delays == 0 + || matches!( + &arg_term, + Term::Var(_) + | Term::Constant(_) + | Term::Delay(_) + | Term::Lambda { .. } + | Term::Builtin(_), + )) + { + *body = substitute_var(body, parameter_name.clone(), &arg_term); - let next_id = id_gen.next_id(); + lambda_applied_ids.push(arg_id); + *term = body.clone(); - let arg_applied = (arg.clone(), next_id); - - reduce_stack.push(arg_applied); - - let func = Rc::make_mut(function); - inline_single_occurrence_reducer(func, reduce_stack, lambda_applied_ids, id_gen); - // The reason we don't need to pop is because the Lambda case and will pop for us - // It is guaranteed to pop otherwise the script is not valid anyways - if lambda_applied_ids.contains(&next_id) { - // we inlined the arg so now remove the apply and arg from the program - *term = func.clone(); - } - } - Term::Delay(d) => { - let d = Rc::make_mut(d); - inline_single_occurrence_reducer(d, reduce_stack, lambda_applied_ids, id_gen); - } - Term::Lambda { - parameter_name, - body, - } => { - // pops stack here no matter what - // match on only Some(Some(arg)) because we don't want to inline None - - if let Some((arg_term, arg_id)) = reduce_stack.pop() { - let body = Rc::make_mut(body); - inline_single_occurrence_reducer(body, reduce_stack, lambda_applied_ids, id_gen); - let occurrences = var_occurrences(body, parameter_name.clone()); - - let delays = delayed_execution(body); - - if occurrences == 1 { - if delays == 0 - || matches!( - &arg_term, + // This will strip out unused terms that can't throw an error by themselves + } else if occurrences == 0 + && matches!( + arg_term, Term::Var(_) | Term::Constant(_) | Term::Delay(_) | Term::Lambda { .. } - | Term::Builtin(_), + | Term::Builtin(_) ) - { - *body = substitute_term(body, parameter_name.clone(), &arg_term); - - lambda_applied_ids.push(arg_id); - *term = body.clone(); - } - // This will strip out unused terms that can't throw an error by themselves - } else if occurrences == 0 { - if let Term::Var(_) - | Term::Constant(_) - | Term::Delay(_) - | Term::Lambda { .. } - | Term::Builtin(_) = &arg_term { lambda_applied_ids.push(arg_id); *term = body.clone(); } } - } else { - let body = Rc::make_mut(body); - inline_single_occurrence_reducer(body, reduce_stack, lambda_applied_ids, id_gen); } - } - Term::Force(f) => { - let f = Rc::make_mut(f); - inline_single_occurrence_reducer(f, reduce_stack, lambda_applied_ids, id_gen); - } - Term::Case { .. } => todo!(), - Term::Constr { .. } => todo!(), - _ => {} + Term::Constr { .. } => todo!(), + Term::Case { .. } => todo!(), + _ => {} + }) } -} -fn cast_data_reducer(term: &mut Term) { - match term { - Term::Delay(d) => { - cast_data_reducer(Rc::make_mut(d)); - } - Term::Lambda { body, .. } => { - cast_data_reducer(Rc::make_mut(body)); - } - Term::Apply { function, argument } => { - let Term::Builtin(first_action) = function.as_ref() else { - cast_data_reducer(Rc::make_mut(function)); - cast_data_reducer(Rc::make_mut(argument)); - return; - }; + pub fn force_delay_reducer(self) -> Program { + self.traverse_uplc_with(&mut |_id, term, _arg_stack, _scope| { + if let Term::Force(f) = term { + let f = Rc::make_mut(f); - if let Term::Apply { - function: inner_func, - argument: inner_arg, - } = Rc::make_mut(argument) - { - let Term::Builtin(second_action) = inner_func.as_ref() else { - cast_data_reducer(Rc::make_mut(argument)); - return; - }; + if let Term::Delay(body) = f { + *term = body.as_ref().clone(); + } + } + }) + } - match (first_action, second_action) { - (DefaultFunction::UnIData, DefaultFunction::IData) - | (DefaultFunction::IData, DefaultFunction::UnIData) - | (DefaultFunction::BData, DefaultFunction::UnBData) - | (DefaultFunction::UnBData, DefaultFunction::BData) - | (DefaultFunction::ListData, DefaultFunction::UnListData) - | (DefaultFunction::UnListData, DefaultFunction::ListData) - | (DefaultFunction::MapData, DefaultFunction::UnMapData) - | (DefaultFunction::UnMapData, DefaultFunction::MapData) => { - cast_data_reducer(Rc::make_mut(inner_arg)); - *term = inner_arg.as_ref().clone(); - } - _ => { - cast_data_reducer(Rc::make_mut(argument)); + pub fn cast_data_reducer(self) -> Program { + let mut applied_ids = vec![]; + + self.traverse_uplc_with(&mut |id, term, mut arg_stack, _scope| { + match term { + Term::Apply { function, .. } => { + // We are apply some arg so now we unwrap the id of the applied arg + let id = id.unwrap(); + + if applied_ids.contains(&id) { + let func = Rc::make_mut(function); + // we inlined the arg so now remove the apply and arg from the program + *term = func.clone(); } } - } else if let Term::Constant(c) = Rc::make_mut(argument) { - match (first_action, Rc::make_mut(c)) { - ( - DefaultFunction::UnIData, - Constant::Data(PlutusData::BigInt(BigInt::Int(i))), - ) => { - *term = Term::integer(i128::from(*i).into()); - } - (DefaultFunction::IData, Constant::Integer(i)) => { - *term = Term::data(Data::integer(i.clone())); - } - (DefaultFunction::UnBData, Constant::Data(PlutusData::BoundedBytes(b))) => { - *term = Term::byte_string(b.clone().into()); - } - (DefaultFunction::BData, Constant::ByteString(b)) => { - *term = Term::data(Data::bytestring(b.clone())); - } - (DefaultFunction::UnListData, Constant::Data(PlutusData::Array(l))) => { - *term = Term::list_values( - l.iter() - .map(|item| Constant::Data(item.clone())) - .collect_vec(), - ); - } - (DefaultFunction::ListData, Constant::ProtoList(_, l)) => { - *term = Term::data(Data::list( - l.iter() - .map(|item| match item { - Constant::Data(d) => d.clone(), - _ => unreachable!(), - }) - .collect_vec(), - )); - } - (DefaultFunction::MapData, Constant::ProtoList(_, m)) => { - *term = Term::data(Data::map( - m.iter() - .map(|m| match m { - Constant::ProtoPair(_, _, f, s) => { - match (f.as_ref(), s.as_ref()) { - (Constant::Data(d), Constant::Data(d2)) => { - (d.clone(), d2.clone()) + + Term::Builtin(first_function) => { + let Some((arg_id, arg_term)) = arg_stack.pop() else { + return; + }; + + match arg_term { + Term::Apply { function, argument } => { + if let Term::Builtin(second_function) = function.as_ref() { + match (first_function, second_function) { + (DefaultFunction::UnIData, DefaultFunction::IData) + | (DefaultFunction::IData, DefaultFunction::UnIData) + | (DefaultFunction::BData, DefaultFunction::UnBData) + | (DefaultFunction::UnBData, DefaultFunction::BData) + | (DefaultFunction::ListData, DefaultFunction::UnListData) + | (DefaultFunction::UnListData, DefaultFunction::ListData) + | (DefaultFunction::MapData, DefaultFunction::UnMapData) + | (DefaultFunction::UnMapData, DefaultFunction::MapData) => { + applied_ids.push(arg_id); + *term = argument.as_ref().clone(); + } + _ => {} + } + } + } + Term::Constant(c) => match (first_function, c.as_ref()) { + ( + DefaultFunction::UnIData, + Constant::Data(PlutusData::BigInt(BigInt::Int(i))), + ) => { + applied_ids.push(arg_id); + *term = Term::integer(i128::from(*i).into()); + } + (DefaultFunction::IData, Constant::Integer(i)) => { + applied_ids.push(arg_id); + *term = Term::data(Data::integer(i.clone())); + } + ( + DefaultFunction::UnBData, + Constant::Data(PlutusData::BoundedBytes(b)), + ) => { + applied_ids.push(arg_id); + *term = Term::byte_string(b.clone().into()); + } + (DefaultFunction::BData, Constant::ByteString(b)) => { + applied_ids.push(arg_id); + *term = Term::data(Data::bytestring(b.clone())); + } + (DefaultFunction::UnListData, Constant::Data(PlutusData::Array(l))) => { + applied_ids.push(arg_id); + *term = Term::list_values( + l.iter() + .map(|item| Constant::Data(item.clone())) + .collect_vec(), + ); + } + (DefaultFunction::ListData, Constant::ProtoList(_, l)) => { + applied_ids.push(arg_id); + *term = Term::data(Data::list( + l.iter() + .map(|item| match item { + Constant::Data(d) => d.clone(), + _ => unreachable!(), + }) + .collect_vec(), + )); + } + (DefaultFunction::MapData, Constant::ProtoList(_, m)) => { + applied_ids.push(arg_id); + *term = Term::data(Data::map( + m.iter() + .map(|m| match m { + Constant::ProtoPair(_, _, f, s) => { + match (f.as_ref(), s.as_ref()) { + (Constant::Data(d), Constant::Data(d2)) => { + (d.clone(), d2.clone()) + } + _ => unreachable!(), + } } _ => unreachable!(), - } - } - _ => unreachable!(), - }) - .collect_vec(), - )); - } - (DefaultFunction::UnMapData, Constant::Data(PlutusData::Map(m))) => { - *term = Term::map_values( - m.iter() - .map(|item| { - Constant::ProtoPair( - Type::Data, - Type::Data, - Constant::Data(item.0.clone()).into(), - Constant::Data(item.1.clone()).into(), - ) - }) - .collect_vec(), - ); - } - _ => { - cast_data_reducer(Rc::make_mut(argument)); + }) + .collect_vec(), + )); + } + (DefaultFunction::UnMapData, Constant::Data(PlutusData::Map(m))) => { + applied_ids.push(arg_id); + *term = Term::map_values( + m.iter() + .map(|item| { + Constant::ProtoPair( + Type::Data, + Type::Data, + Constant::Data(item.0.clone()).into(), + Constant::Data(item.1.clone()).into(), + ) + }) + .collect_vec(), + ); + } + _ => {} + }, + _ => {} } } - } else { - cast_data_reducer(Rc::make_mut(argument)); - }; - } - Term::Force(f) => { - cast_data_reducer(Rc::make_mut(f)); - } - Term::Case { .. } => todo!(), - Term::Constr { .. } => todo!(), - _ => {} + Term::Constr { .. } => todo!(), + Term::Case { .. } => todo!(), + _ => {} + } + }) + } + // WIP + pub fn builtin_curry_reducer(self) -> Program { + let mut curried_terms = vec![]; + let mut applied_ids = vec![]; + + self.traverse_uplc_with(&mut |id, term, mut arg_stack, scope| match term { + Term::Apply { function, argument } => { + // We are apply some arg so now we unwrap the id of the applied arg + let id = id.unwrap(); + + if applied_ids.contains(&id) { + let func = Rc::make_mut(function); + // we inlined the arg so now remove the apply and arg from the program + *term = func.clone(); + } + } + Term::Builtin(func) => { + if can_curry_builtin(*func) { + let mut scope = scope.pop(); + + let arg_number = func.arity(); + + let is_order_agnostic = is_order_agnostic_builtin(*func); + + if let Some(curried_builtin) = curried_terms + .iter_mut() + .find(|curried_term: &&mut CurriedBuiltin| curried_term.func == *func) + { + let mut current_children = &mut curried_builtin.children; + } else { + let Some(curried_tree) = arg_stack + .into_iter() + .map(|(_, arg)| arg) + .sorted_by(|arg1, arg2| { + if is_order_agnostic { + if matches!(arg1, Term::Constant(_)) + && matches!(arg2, Term::Constant(_)) + { + std::cmp::Ordering::Equal + } else if matches!(arg1, Term::Constant(_)) { + std::cmp::Ordering::Greater + } else if matches!(arg2, Term::Constant(_)) { + std::cmp::Ordering::Less + } else { + std::cmp::Ordering::Equal + } + } else { + std::cmp::Ordering::Equal + } + }) + .fold(None, |acc, arg| match acc { + Some(curry) => Some(CurriedTree::Branch { + node: arg, + multiple_occurrences: false, + children: vec![curry], + scope: scope.clone(), + }), + None => Some(CurriedTree::Leaf { + node: arg, + multiple_occurrences: false, + scope: scope.clone(), + }), + }) + else { + return; + }; + + let curried_builtin = CurriedBuiltin { + func: *func, + // TODO: handle the special case of subtract integer + flipped: false, + + children: vec![curried_tree], + }; + + curried_terms.push(curried_builtin); + } + } + } + + Term::Constr { .. } => todo!(), + Term::Case { .. } => todo!(), + _ => {} + }) } } @@ -619,7 +733,7 @@ fn delayed_execution(term: &Term) -> usize { } } -fn substitute_term(term: &Term, original: Rc, replace_with: &Term) -> Term { +fn substitute_var(term: &Term, original: Rc, replace_with: &Term) -> Term { match term { Term::Var(name) => { if name.as_ref() == original.as_ref() { @@ -629,7 +743,7 @@ fn substitute_term(term: &Term, original: Rc, replace_with: &Term { - Term::Delay(substitute_term(body.as_ref(), original, replace_with).into()) + Term::Delay(substitute_var(body.as_ref(), original, replace_with).into()) } Term::Lambda { parameter_name, @@ -638,7 +752,7 @@ fn substitute_term(term: &Term, original: Rc, replace_with: &Term, original: Rc, replace_with: &Term Term::Apply { - function: Rc::new(substitute_term( + function: Rc::new(substitute_var( function.as_ref(), original.clone(), replace_with, )), - argument: Rc::new(substitute_term(argument.as_ref(), original, replace_with)), + argument: Rc::new(substitute_var(argument.as_ref(), original, replace_with)), }, - Term::Force(x) => Term::Force(Rc::new(substitute_term(x.as_ref(), original, replace_with))), + Term::Force(f) => Term::Force(Rc::new(substitute_var(f.as_ref(), original, replace_with))), Term::Case { .. } => todo!(), Term::Constr { .. } => todo!(), x => x.clone(), @@ -682,20 +796,17 @@ fn replace_identity_usage(term: &Term, original: Rc) -> Term { } } Term::Apply { function, argument } => { - let func = function.as_ref(); - let arg = argument.as_ref(); + let func = replace_identity_usage(function.as_ref(), original.clone()); + let arg = replace_identity_usage(argument.as_ref(), original.clone()); - let func = replace_identity_usage(func, original.clone()); - let arg = replace_identity_usage(arg, original.clone()); - - let Term::Var(f) = function.as_ref() else { + let Term::Var(name) = &func else { return Term::Apply { function: func.into(), argument: arg.into(), }; }; - if f.as_ref() == original.as_ref() { + if name.as_ref() == original.as_ref() { arg } else { Term::Apply { @@ -704,7 +815,7 @@ fn replace_identity_usage(term: &Term, original: Rc) -> Term { } } } - Term::Force(x) => Term::Force(Rc::new(replace_identity_usage(x.as_ref(), original))), + Term::Force(f) => Term::Force(Rc::new(replace_identity_usage(f.as_ref(), original))), Term::Case { .. } => todo!(), Term::Constr { .. } => todo!(), x => x.clone(),