From dfce9c1d96efd8d2a45be6aba7c5129d16e5a9b5 Mon Sep 17 00:00:00 2001 From: microproofs Date: Wed, 24 Jul 2024 09:59:12 -0400 Subject: [PATCH] feat: Add multivalidator as an AIR tree opcode. feat: Add uplc eval optimization --- crates/aiken-lang/src/gen_uplc.rs | 100 +++++++++++++--------- crates/aiken-lang/src/gen_uplc/air.rs | 4 + crates/aiken-lang/src/gen_uplc/tree.rs | 91 +++++++++++++++----- crates/uplc/src/optimize.rs | 1 + crates/uplc/src/optimize/shrinker.rs | 74 +++++++++++++++- examples/acceptance_tests/073/aiken.lock | 2 +- examples/acceptance_tests/077/aiken.lock | 2 +- examples/acceptance_tests/077/plutus.json | 10 +-- 8 files changed, 212 insertions(+), 72 deletions(-) diff --git a/crates/aiken-lang/src/gen_uplc.rs b/crates/aiken-lang/src/gen_uplc.rs index 707ff3e5..7a2727ed 100644 --- a/crates/aiken-lang/src/gen_uplc.rs +++ b/crates/aiken-lang/src/gen_uplc.rs @@ -132,6 +132,46 @@ impl<'a> CodeGenerator<'a> { let mut validator_args_tree = self.check_validator_args(&fun.arguments, true, air_tree_fun, src_code, lines); + if let Some(other) = other_fun { + let mut air_tree_fun_other = self.build(&other.body, module_name, &[]); + + air_tree_fun_other = wrap_validator_condition(air_tree_fun_other, self.tracing); + + let validator_args_tree_other = self.check_validator_args( + &other.arguments, + true, + air_tree_fun_other, + src_code, + lines, + ); + + let (spend, spend_name, mint, mint_name) = + if other.arguments.len() > fun.arguments.len() { + ( + validator_args_tree_other, + other.name.clone(), + validator_args_tree, + fun.name.clone(), + ) + } else { + ( + validator_args_tree, + fun.name.clone(), + validator_args_tree_other, + other.name.clone(), + ) + }; + + validator_args_tree = AirTree::multi_validator(mint_name, mint, spend_name, spend); + + // Special Case with multi_validators + self.special_functions + .use_function_uplc(CONSTR_FIELDS_EXPOSER.to_string()); + + self.special_functions + .use_function_uplc(CONSTR_INDEX_EXPOSER.to_string()); + } + validator_args_tree = AirTree::no_op(validator_args_tree); let full_tree = self.hoist_functions_to_validator(validator_args_tree); @@ -142,48 +182,6 @@ impl<'a> CodeGenerator<'a> { let mut term = self.uplc_code_gen(full_vec); - if let Some(other) = other_fun { - self.reset(false); - - let mut air_tree_fun_other = self.build(&other.body, module_name, &[]); - - air_tree_fun_other = wrap_validator_condition(air_tree_fun_other, self.tracing); - - let mut validator_args_tree_other = self.check_validator_args( - &other.arguments, - true, - air_tree_fun_other, - src_code, - lines, - ); - - validator_args_tree_other = AirTree::no_op(validator_args_tree_other); - - let full_tree_other = self.hoist_functions_to_validator(validator_args_tree_other); - - // optimizations on air tree - - let full_vec_other = full_tree_other.to_vec(); - - let other_term = self.uplc_code_gen(full_vec_other); - - let (spend, spend_name, mint, mint_name) = - if other.arguments.len() > fun.arguments.len() { - (other_term, other.name.clone(), term, fun.name.clone()) - } else { - (term, fun.name.clone(), other_term, other.name.clone()) - }; - - // Special Case with multi_validators - self.special_functions - .use_function_uplc(CONSTR_FIELDS_EXPOSER.to_string()); - - self.special_functions - .use_function_uplc(CONSTR_INDEX_EXPOSER.to_string()); - - term = wrap_as_multi_validator(spend, mint, self.tracing, spend_name, mint_name); - } - term = cast_validator_args(term, params); self.finalize(term) @@ -5700,6 +5698,24 @@ impl<'a> CodeGenerator<'a> { } Air::NoOp => None, + Air::MultiValidator { + two_arg_name, + three_arg_name, + } => { + let two_arg = arg_stack.pop().unwrap(); + + let three_arg = arg_stack.pop().unwrap(); + + let term = wrap_as_multi_validator( + three_arg, + two_arg, + self.tracing, + three_arg_name, + two_arg_name, + ); + + Some(term) + } } } } diff --git a/crates/aiken-lang/src/gen_uplc/air.rs b/crates/aiken-lang/src/gen_uplc/air.rs index 57a55b9c..07322314 100644 --- a/crates/aiken-lang/src/gen_uplc/air.rs +++ b/crates/aiken-lang/src/gen_uplc/air.rs @@ -224,4 +224,8 @@ pub enum Air { NoOp, FieldsEmpty, ListEmpty, + MultiValidator { + two_arg_name: String, + three_arg_name: String, + }, } diff --git a/crates/aiken-lang/src/gen_uplc/tree.rs b/crates/aiken-lang/src/gen_uplc/tree.rs index d3d9f13c..34847148 100644 --- a/crates/aiken-lang/src/gen_uplc/tree.rs +++ b/crates/aiken-lang/src/gen_uplc/tree.rs @@ -402,6 +402,12 @@ pub enum AirTree { then: Box, }, // End Expressions + MultiValidator { + two_arg_name: String, + two_arg: Box, + three_arg_name: String, + three_arg: Box, + }, } impl AirTree { @@ -1001,26 +1007,19 @@ impl AirTree { } } - // pub fn hoist_over(mut self, next_exp: AirTree) -> AirTree { - // match &mut self { - // AirTree::Statement { hoisted_over, .. } => { - // assert!(hoisted_over.is_none()); - // *hoisted_over = Some(next_exp.into()); - // self - // } - - // AirTree::Expression(_) => { - // unreachable!("Trying to hoist an expression onto an expression.") - // } - // AirTree::UnhoistedSequence(seq) => { - // let mut final_exp = next_exp; - // while let Some(assign) = seq.pop() { - // final_exp = assign.hoist_over(final_exp); - // } - // final_exp - // } - // } - // } + pub fn multi_validator( + two_arg_name: String, + two_arg: AirTree, + three_arg_name: String, + three_arg: AirTree, + ) -> AirTree { + AirTree::MultiValidator { + two_arg_name, + two_arg: two_arg.into(), + three_arg_name, + three_arg: three_arg.into(), + } + } pub fn expect_on_list() -> AirTree { let list_var = AirTree::local_var("__list_to_check", list(data())); @@ -1595,6 +1594,20 @@ impl AirTree { msg.create_air_vec(air_vec); then.create_air_vec(air_vec); } + AirTree::MultiValidator { + two_arg, + three_arg, + two_arg_name, + three_arg_name, + } => { + air_vec.push(Air::MultiValidator { + two_arg_name: two_arg_name.clone(), + three_arg_name: three_arg_name.clone(), + }); + + two_arg.create_air_vec(air_vec); + three_arg.create_air_vec(air_vec); + } } } @@ -1649,6 +1662,7 @@ impl AirTree { | AirTree::FieldsEmpty { then, .. } | AirTree::ListEmpty { then, .. } | AirTree::NoOp { then } => then.return_type(), + AirTree::MultiValidator { .. } => void(), } } @@ -1725,7 +1739,8 @@ impl AirTree { | AirTree::Fn { .. } | AirTree::UnOp { .. } | AirTree::WrapClause { .. } - | AirTree::Finally { .. } => vec![], + | AirTree::Finally { .. } + | AirTree::MultiValidator { .. } => vec![], } } @@ -2055,7 +2070,8 @@ impl AirTree { | AirTree::Constr { .. } | AirTree::RecordUpdate { .. } | AirTree::ErrorTerm { .. } - | AirTree::Trace { .. } => {} + | AirTree::Trace { .. } + | AirTree::MultiValidator { .. } => {} } if !apply_with_func_last { @@ -2711,6 +2727,27 @@ impl AirTree { apply_with_func_last, ); } + AirTree::MultiValidator { + two_arg_name: _, + two_arg, + three_arg_name: _, + three_arg, + } => { + two_arg.do_traverse_tree_with( + tree_path, + current_depth + 1, + Fields::SecondField, + with, + apply_with_func_last, + ); + three_arg.do_traverse_tree_with( + tree_path, + current_depth + 1, + Fields::FourthField, + with, + apply_with_func_last, + ) + } } if apply_with_func_last { @@ -3077,6 +3114,16 @@ impl AirTree { | AirTree::ErrorTerm { .. } => { panic!("A tree node with no children was encountered with a longer tree path.") } + AirTree::MultiValidator { + two_arg_name: _, + two_arg, + three_arg_name: _, + three_arg, + } => match field { + Fields::SecondField => two_arg.as_mut().do_find_air_tree_node(tree_path_iter), + Fields::FourthField => three_arg.as_mut().do_find_air_tree_node(tree_path_iter), + _ => panic!("Tree Path index outside tree children nodes"), + }, } } else { self diff --git a/crates/uplc/src/optimize.rs b/crates/uplc/src/optimize.rs index 94608ba1..550a020f 100644 --- a/crates/uplc/src/optimize.rs +++ b/crates/uplc/src/optimize.rs @@ -14,6 +14,7 @@ pub fn aiken_optimize_and_intern(program: Program) -> Program { .inline_reducer() .force_delay_reducer() .cast_data_reducer() + .builtin_eval_reducer() .convert_arithmetic_ops() .builtin_curry_reducer() .lambda_reducer() diff --git a/crates/uplc/src/optimize/shrinker.rs b/crates/uplc/src/optimize/shrinker.rs index a5cdaf8b..288bd9b5 100644 --- a/crates/uplc/src/optimize/shrinker.rs +++ b/crates/uplc/src/optimize/shrinker.rs @@ -9,6 +9,7 @@ use crate::{ ast::{Constant, Data, Name, NamedDeBruijn, Program, Term, Type}, builder::{CONSTR_FIELDS_EXPOSER, CONSTR_INDEX_EXPOSER}, builtins::DefaultFunction, + machine::cost_model::ExBudget, }; use super::interner::CodeGenInterner; @@ -360,7 +361,7 @@ pub enum BuiltinArgs { impl BuiltinArgs { fn args_from_arg_stack(stack: Vec<(usize, Term)>, func: DefaultFunction) -> Self { - let error_safe = func.is_error_safe(&stack.iter().map(|(_, term)| term).collect_vec()); + let error_safe = false; let mut ordered_arg_stack = stack.into_iter().sorted_by(|(_, arg1), (_, arg2)| { // sort by constant first if the builtin is order agnostic @@ -986,6 +987,27 @@ impl Term { } } } + + fn pierce_no_inlines<'a>(&'a self) -> &'a Self { + let mut term = self; + loop { + match term { + Term::Lambda { + parameter_name, + body, + } => { + if parameter_name.as_ref().text == NO_INLINE { + term = body; + } else { + break; + } + } + _ => break, + } + } + + term + } } impl Program { @@ -1708,6 +1730,56 @@ impl Program { step_b } + + pub fn builtin_eval_reducer(self) -> Self { + let mut applied_ids = vec![]; + + self.traverse_uplc_with(false, &mut |id, term, arg_stack, _scope| match term { + Term::Builtin(func) => { + let args = arg_stack + .iter() + .map(|(_, term)| term.pierce_no_inlines()) + .collect_vec(); + if func.can_curry_builtin() + && arg_stack.len() == func.arity() + && func.is_error_safe(&args) + { + let applied_term = + arg_stack + .into_iter() + .fold(Term::Builtin(*func), |acc, item| { + applied_ids.push(item.0); + acc.apply(item.1.pierce_no_inlines().clone()) + }); + + // Check above for is error safe + let eval_term: Term = Program { + version: (1, 0, 0), + term: applied_term, + } + .to_named_debruijn() + .unwrap() + .eval(ExBudget::max()) + .result() + .unwrap() + .try_into() + .unwrap(); + + *term = eval_term; + } + } + Term::Apply { function, .. } => { + let id = id.unwrap(); + + if applied_ids.contains(&id) { + *term = function.as_ref().clone(); + } + } + Term::Constr { .. } => todo!(), + Term::Case { .. } => todo!(), + _ => {} + }) + } } fn id_vec_function_to_var(func_name: &str, id_vec: &[usize]) -> String { diff --git a/examples/acceptance_tests/073/aiken.lock b/examples/acceptance_tests/073/aiken.lock index 2fc372da..94825e81 100644 --- a/examples/acceptance_tests/073/aiken.lock +++ b/examples/acceptance_tests/073/aiken.lock @@ -13,4 +13,4 @@ requirements = [] source = "github" [etags] -"aiken-lang/stdlib@main" = [{ secs_since_epoch = 1719353073, nanos_since_epoch = 642619000 }, "a746f5b5cd3c2ca5dc19c43bcfc64230c546fafea2ba5f8e340c227b85886078"] +"aiken-lang/stdlib@main" = [{ secs_since_epoch = 1721828369, nanos_since_epoch = 230591000 }, "a746f5b5cd3c2ca5dc19c43bcfc64230c546fafea2ba5f8e340c227b85886078"] diff --git a/examples/acceptance_tests/077/aiken.lock b/examples/acceptance_tests/077/aiken.lock index a7ec3aa1..1903ef0d 100644 --- a/examples/acceptance_tests/077/aiken.lock +++ b/examples/acceptance_tests/077/aiken.lock @@ -13,4 +13,4 @@ requirements = [] source = "github" [etags] -"aiken-lang/stdlib@main" = [{ secs_since_epoch = 1719352929, nanos_since_epoch = 982815000 }, "a746f5b5cd3c2ca5dc19c43bcfc64230c546fafea2ba5f8e340c227b85886078"] +"aiken-lang/stdlib@main" = [{ secs_since_epoch = 1721828402, nanos_since_epoch = 276492000 }, "a746f5b5cd3c2ca5dc19c43bcfc64230c546fafea2ba5f8e340c227b85886078"] diff --git a/examples/acceptance_tests/077/plutus.json b/examples/acceptance_tests/077/plutus.json index 6ffce1f2..c07ddab3 100644 --- a/examples/acceptance_tests/077/plutus.json +++ b/examples/acceptance_tests/077/plutus.json @@ -5,7 +5,7 @@ "plutusVersion": "v2", "compiler": { "name": "Aiken", - "version": "v1.0.29-alpha+e856fc6" + "version": "v1.0.29-alpha+06ac851" } }, "validators": [ @@ -31,8 +31,8 @@ } } ], - "compiledCode": "590347010000323232323232323232323223222323232322533300c323232533300f3007301137540022646464a66602c002020264a66602e603400426464a66602a601a602e6ea803854ccc054c8cc004004018894ccc06c004528099299980c19baf301e301b3754603c00402629444cc00c00c004c07800454ccc054c0300044cdc78010088a5015330164901536578706563740a202020202020202020206c6973742e616e7928696e707574732c20666e28696e70757429207b20696e7075742e6f75747075745f7265666572656e6365203d3d207574786f5f726566207d2900161533016491046275726e0016375a602e0046eb8c054004044c060004c94ccc048c024c050dd50008a5eb7bdb1804dd5980c180a9baa001323233001001323300100137566034603660366036603600a44a666032002297adef6c6013232323253330193372291100002153330193371e9101000021003100513301e337606ea4008dd3000998030030019bab301b003375c6032004603a004603600244a666030002298103d87a800013232323253330183372200e0042a66603066e3c01c0084cdd2a40006603a6e980052f5c02980103d87a8000133006006003375660340066eb8c060008c070008c068004dd7180b980a1baa0033758602c00260246ea800854cc041241236578706563742074782e4d696e7428706f6c6963795f696429203d20707572706f73650016301430150023013001300f37540022930a99806a491856616c696461746f722072657475726e65642066616c7365001365632533300b30030011533300f300e37540082930050a99980598010008a99980798071baa0041498028028c030dd50019b8748008dc3a4000a66666601e002200200c00c00c00c6eb800454cc00d24018f657870656374205b506169722861737365745f6e616d652c20616d6f756e74295d203d0a2020202020206d696e740a20202020202020207c3e2076616c75652e66726f6d5f6d696e7465645f76616c75650a20202020202020207c3e2076616c75652e746f6b656e7328706f6c6963795f6964290a20202020202020207c3e20646963742e746f5f70616972732829001615330024910c72646d723a20416374696f6e00165734ae7155ceaab9e5573eae815d0aba257481", - "hash": "e8c0b6d7c88bce7578f598ed61f172854b78a78c4ec251b45d6da4c4" + "compiledCode": "5901e4010000323232323232323223222323232322533300a323232533300d3007300e37540022646464a6660260022c264a666028602e00426464a666026601a60286ea803854ccc04cc8cc004004018894ccc060004528099299980b19baf301b30183754603600402629444cc00c00c004c06c00454ccc04cc0300044cdc78010088a501616375a60280046eb8c04800458c054004c94ccc040c024c044dd50008a5eb7bdb1804dd5980a98091baa00132323300100132330010013756602e603060306030603000a44a66602c002297adef6c60132323232533301733722910100002153330173371e9101000021003100513301b337606ea4008dd3000998030030019bab3018003375c602c0046034004603000244a66602a002298103d87a800013232323253330163372200e0042a66602c66e3c01c0084cdd2a4000660346e980052f5c02980103d87a80001330060060033756602e0066eb8c054008c064008c05c004dd7180a18089baa00337586026002601e6ea800858c044c048008c040004c030dd50008a4c26cac64a66601260060022a66601860166ea8010526161533300930020011533300c300b37540082930b0b18049baa003370e90011b87480014cccccc030004400458585858dd7000ab9a5573aaae7955cfaba05742ae895d201", + "hash": "41f948bc5dcda5f8813cc42ffde5c8d981f37be1475a5d242d44e2fd" }, { "title": "spend2.backtrace", @@ -48,8 +48,8 @@ "$ref": "#/definitions/Void" } }, - "compiledCode": "5901680100003232323232323232323232232322322533300953330093005300b375464660020026eb0c040c044c044c034dd5180818069baa00222533300f00114c0103d87a800013232533300d4a2266e952000330120024bd70099802002000980980118088008a51153300a4914765787065637420536f6d65285f29203d206c6973742e66696e6428636f6e746578742e7472616e73616374696f6e2e6f7574707574732c20666e285f29207b2054727565207d290016149854cc0292411856616c696461746f722072657475726e65642066616c73650013656533333300f001153330073003300937540022a66601660146ea8004526005005005005005005533333300d002153330053001300737540042a66601260106ea8008526004004004004004004370e90000a99801a4810f5f72656465656d65723a20566f6964001615330024910c5f646174756d3a20566f696400165734ae7155ceaab9e5573eae815d0aba257481", - "hash": "f86c88144df93f3925cf048d1b50615fa95249f063a07242f70a6bd8" + "compiledCode": "58c401000032323232323232322323223225333007533300730053008375464660020026eb0c034c038c038c028dd5180698051baa00222533300c00114c0103d87a800013232533300b4a2266e9520003300f0024bd70099802002000980800118070008a511614984d9594cccccc03000454ccc014c00cc018dd50008a99980418039baa00114985858585858594cccccc02800854ccc00cc004c010dd50010a99980318029baa0021498585858585858dc3a4000ae6955ceaab9e5573eae815d0aba25749", + "hash": "419ffec4259b90352f8fde4cd451f98d01132f6cb7b1c382c9a3e810" } ], "definitions": {