From 7a6ddc45a0e91a8c232e66703c2b05e2ea845861 Mon Sep 17 00:00:00 2001 From: microproofs Date: Sat, 7 Oct 2023 14:43:12 -0400 Subject: [PATCH] feat: Rework codegen traces to prevent repeated messages from taking up uplc script space --- crates/aiken-lang/src/gen_uplc.rs | 179 ++++++++++++++-------- crates/aiken-lang/src/gen_uplc/builder.rs | 139 +++++++++++++---- 2 files changed, 224 insertions(+), 94 deletions(-) diff --git a/crates/aiken-lang/src/gen_uplc.rs b/crates/aiken-lang/src/gen_uplc.rs index 309f19fd..5765ec4b 100644 --- a/crates/aiken-lang/src/gen_uplc.rs +++ b/crates/aiken-lang/src/gen_uplc.rs @@ -22,13 +22,14 @@ use crate::{ AssignmentKind, BinOp, Pattern, Span, TypedArg, TypedClause, TypedDataType, TypedFunction, TypedPattern, TypedValidator, UnOp, }, - builtins::{bool, data, int, list, void}, + builtins::{bool, data, int, list, string, void}, expr::TypedExpr, gen_uplc::builder::{ check_replaceable_opaque_type, convert_opaque_type, erase_opaque_type_operations, find_and_replace_generics, find_list_clause_or_default_first, get_arg_type_name, get_generic_id_and_type, get_variant_name, monomorphize, pattern_has_conditions, wrap_as_multi_validator, wrap_validator_condition, CodeGenFunction, SpecificClause, + CONSTR_INDEX_MISMATCH, }, tipo::{ ModuleValueConstructor, PatternConstructor, Type, TypeInfo, ValueConstructor, @@ -42,8 +43,9 @@ use self::{ builder::{ cast_validator_args, constants_ir, convert_type_to_data, extract_constant, lookup_data_type_by_tipo, modify_cyclic_calls, modify_self_calls, rearrange_list_clauses, - AssignmentProperties, ClauseProperties, CycleFunctionNames, DataTypeKey, FunctionAccessKey, - HoistableFunction, Variant, + AssignmentProperties, ClauseProperties, CodeGenSpecialFuncs, CycleFunctionNames, + DataTypeKey, FunctionAccessKey, HoistableFunction, Variant, CONSTR_NOT_EMPTY, + INCORRECT_BOOLEAN, INCORRECT_CONSTR, LIST_NOT_EMPTY, TOO_MANY_ITEMS, }, tree::{AirExpression, AirTree, TreePath}, }; @@ -54,7 +56,7 @@ pub struct CodeGenerator<'a> { functions: IndexMap, data_types: IndexMap, module_types: IndexMap<&'a String, &'a TypeInfo>, - needs_field_access: bool, + special_functions: CodeGenSpecialFuncs, code_gen_functions: IndexMap, zero_arg_functions: IndexMap<(FunctionAccessKey, Variant), Vec>, cyclic_functions: @@ -75,7 +77,7 @@ impl<'a> CodeGenerator<'a> { functions, data_types, module_types, - needs_field_access: false, + special_functions: CodeGenSpecialFuncs::new(), code_gen_functions: IndexMap::new(), zero_arg_functions: IndexMap::new(), cyclic_functions: IndexMap::new(), @@ -87,7 +89,7 @@ impl<'a> CodeGenerator<'a> { pub fn reset(&mut self) { self.code_gen_functions = IndexMap::new(); self.zero_arg_functions = IndexMap::new(); - self.needs_field_access = false; + self.special_functions = CodeGenSpecialFuncs::new(); self.defined_functions = IndexMap::new(); self.cyclic_functions = IndexMap::new(); self.id_gen = IdGenerator::new(); @@ -160,9 +162,11 @@ impl<'a> CodeGenerator<'a> { (term, other_term) }; - term = wrap_as_multi_validator(spend, mint); + // Special Case with multi_validators + self.special_functions.use_function(CONSTR_FIELDS_EXPOSER); + self.special_functions.use_function(CONSTR_INDEX_EXPOSER); - self.needs_field_access = true; + term = wrap_as_multi_validator(spend, mint); } term = cast_validator_args(term, params); @@ -186,9 +190,7 @@ impl<'a> CodeGenerator<'a> { } fn finalize(&mut self, mut term: Term) -> Program { - if self.needs_field_access { - term = term.constr_fields_exposer().constr_index_exposer(); - } + term = self.special_functions.apply_used_functions(term); // TODO: Once SOP is implemented, new version is 1.1.0 let mut program = Program { @@ -547,7 +549,6 @@ impl<'a> CodeGenerator<'a> { if check_replaceable_opaque_type(&record.tipo(), &self.data_types) { self.build(record) } else { - self.needs_field_access = true; let function_name = format!("__access_index_{}", *index); if self.code_gen_functions.get(&function_name).is_none() { @@ -573,7 +574,10 @@ impl<'a> CodeGenerator<'a> { } let list_of_fields = AirTree::call( - AirTree::local_var(CONSTR_FIELDS_EXPOSER, void()), + AirTree::local_var( + self.special_functions.use_function(CONSTR_FIELDS_EXPOSER), + void(), + ), list(data()), vec![self.build(record)], ); @@ -1468,7 +1472,10 @@ impl<'a> CodeGenerator<'a> { let error_term = if self.tracing { AirTree::trace( - AirTree::string("Constr index didn't match a type variant"), + AirTree::local_var( + self.special_functions.use_function(CONSTR_INDEX_MISMATCH), + string(), + ), tipo.clone(), AirTree::error(tipo.clone(), false), ) @@ -3819,31 +3826,44 @@ impl<'a> CodeGenerator<'a> { id_list.push(self.id_gen.next()); } - let inner_types = tipo + let names_empty = names.is_empty(); + + let names_types = tipo .get_inner_types() .into_iter() .cycle() .take(names.len()) + .zip(names) + .map(|(tipo, name)| (name, tipo)) .collect_vec(); - if !names.is_empty() { + if !names_empty { + let error_term = if self.tracing { + Term::Error.trace(Term::var( + self.special_functions.use_function(TOO_MANY_ITEMS), + )) + } else { + Term::Error + }; + term = builder::list_access_to_uplc( - &names, + &names_types, &id_list, tail, 0, term, - inner_types, check_last_item, true, - self.tracing, + error_term, ) .apply(value); arg_stack.push(term); } else if check_last_item { let trace_term = if self.tracing { - Term::Error.trace(Term::string("Expected no items for List")) + Term::Error.trace(Term::var( + self.special_functions.use_function(LIST_NOT_EMPTY), + )) } else { Term::Error }; @@ -4323,20 +4343,24 @@ impl<'a> CodeGenerator<'a> { arg_stack.push(term); } Air::AssertConstr { constr_index } => { - self.needs_field_access = true; let constr = arg_stack.pop().unwrap(); let mut term = arg_stack.pop().unwrap(); let trace_term = if self.tracing { - Term::Error.trace(Term::string("Expected on incorrect Constr variant")) + Term::Error.trace(Term::var( + self.special_functions.use_function(INCORRECT_CONSTR), + )) } else { Term::Error }; term = Term::equals_integer() .apply(Term::integer(constr_index.into())) - .apply(Term::var(CONSTR_INDEX_EXPOSER).apply(constr)) + .apply( + Term::var(self.special_functions.use_function(CONSTR_INDEX_EXPOSER)) + .apply(constr), + ) .delayed_if_else(term, trace_term); arg_stack.push(term); @@ -4346,7 +4370,9 @@ impl<'a> CodeGenerator<'a> { let mut term = arg_stack.pop().unwrap(); let trace_term = if self.tracing { - Term::Error.trace(Term::string("Expected on incorrect bool variant")) + Term::Error.trace(Term::var( + self.special_functions.use_function(INCORRECT_BOOLEAN), + )) } else { Term::Error }; @@ -4375,8 +4401,8 @@ impl<'a> CodeGenerator<'a> { { subject } else { - self.needs_field_access = true; - Term::var(CONSTR_INDEX_EXPOSER).apply(subject) + Term::var(self.special_functions.use_function(CONSTR_INDEX_EXPOSER)) + .apply(subject) }; let mut term = arg_stack.pop().unwrap(); @@ -4576,10 +4602,10 @@ impl<'a> CodeGenerator<'a> { } else if tipo.is_list() || tipo.is_tuple() { unreachable!() } else { - self.needs_field_access = true; - Term::equals_integer() - .apply(checker) - .apply(Term::var(CONSTR_INDEX_EXPOSER).apply(Term::var(subject_name))) + Term::equals_integer().apply(checker).apply( + Term::var(self.special_functions.use_function(CONSTR_INDEX_EXPOSER)) + .apply(Term::var(subject_name)), + ) }; let term = condition @@ -4721,7 +4747,6 @@ impl<'a> CodeGenerator<'a> { indices, check_last_item, } => { - self.needs_field_access = true; let mut id_list = vec![]; let value = arg_stack.pop().unwrap(); @@ -4736,33 +4761,48 @@ impl<'a> CodeGenerator<'a> { let current_index = 0; - let names = indices.iter().cloned().map(|item| item.1).collect_vec(); - let inner_types = indices.iter().cloned().map(|item| item.2).collect_vec(); + let names_types = indices + .iter() + .cloned() + .map(|item| (item.1, item.2)) + .collect_vec(); if !indices.is_empty() { - term = builder::list_access_to_uplc( - &names, - &id_list, - false, - current_index, - term, - inner_types, - check_last_item, - false, - self.tracing, - ); - - term = term.apply(Term::var(CONSTR_FIELDS_EXPOSER).apply(value)); - - arg_stack.push(term); - } else if check_last_item { - let trace_term = if self.tracing { - Term::Error.trace(Term::string("Expected no fields for Constr")) + let error_term = if self.tracing { + Term::Error.trace(Term::var( + self.special_functions.use_function(TOO_MANY_ITEMS), + )) } else { Term::Error }; - term = Term::var(CONSTR_FIELDS_EXPOSER) + term = builder::list_access_to_uplc( + &names_types, + &id_list, + false, + current_index, + term, + check_last_item, + false, + error_term, + ); + + term = term.apply( + Term::var(self.special_functions.use_function(CONSTR_FIELDS_EXPOSER)) + .apply(value), + ); + + arg_stack.push(term); + } else if check_last_item { + let trace_term = if self.tracing { + Term::Error.trace(Term::var( + self.special_functions.use_function(CONSTR_NOT_EMPTY), + )) + } else { + Term::Error + }; + + term = Term::var(self.special_functions.use_function(CONSTR_FIELDS_EXPOSER)) .apply(value) .delayed_choose_list(term, trace_term); @@ -4772,18 +4812,18 @@ impl<'a> CodeGenerator<'a> { }; } Air::FieldsEmpty => { - self.needs_field_access = true; - let value = arg_stack.pop().unwrap(); let mut term = arg_stack.pop().unwrap(); let trace_term = if self.tracing { - Term::Error.trace(Term::string("Expected no fields for Constr")) + Term::Error.trace(Term::var( + self.special_functions.use_function(CONSTR_NOT_EMPTY), + )) } else { Term::Error }; - term = Term::var(CONSTR_FIELDS_EXPOSER) + term = Term::var(self.special_functions.use_function(CONSTR_FIELDS_EXPOSER)) .apply(value) .delayed_choose_list(term, trace_term); @@ -4794,7 +4834,9 @@ impl<'a> CodeGenerator<'a> { let mut term = arg_stack.pop().unwrap(); let trace_term = if self.tracing { - Term::Error.trace(Term::string("Expected no items for List")) + Term::Error.trace(Term::var( + self.special_functions.use_function(LIST_NOT_EMPTY), + )) } else { Term::Error }; @@ -4867,7 +4909,6 @@ impl<'a> CodeGenerator<'a> { indices, tipo, } => { - self.needs_field_access = true; let tail_name_prefix = "__tail_index"; let data_type = lookup_data_type_by_tipo(&self.data_types, &tipo) @@ -4946,9 +4987,10 @@ impl<'a> CodeGenerator<'a> { } } - term = term - .lambda(format!("{tail_name_prefix}_0")) - .apply(Term::var(CONSTR_FIELDS_EXPOSER).apply(record)); + term = term.lambda(format!("{tail_name_prefix}_0")).apply( + Term::var(self.special_functions.use_function(CONSTR_FIELDS_EXPOSER)) + .apply(record), + ); arg_stack.push(term); } @@ -5018,16 +5060,25 @@ impl<'a> CodeGenerator<'a> { id_list.push(self.id_gen.next()); } + let names_types = names.into_iter().zip(inner_types).collect_vec(); + + let error_term = if self.tracing { + Term::Error.trace(Term::var( + self.special_functions.use_function(TOO_MANY_ITEMS), + )) + } else { + Term::Error + }; + term = builder::list_access_to_uplc( - &names, + &names_types, &id_list, false, 0, term, - tipo.get_inner_types(), check_last_item, false, - self.tracing, + error_term, ) .apply(value); diff --git a/crates/aiken-lang/src/gen_uplc/builder.rs b/crates/aiken-lang/src/gen_uplc/builder.rs index 79051b56..3fd3e979 100644 --- a/crates/aiken-lang/src/gen_uplc/builder.rs +++ b/crates/aiken-lang/src/gen_uplc/builder.rs @@ -36,6 +36,13 @@ pub type Params = Vec; pub type CycleFunctionNames = Vec; +pub const TOO_MANY_ITEMS: &str = "TOO_MANY_ITEMS"; +pub const LIST_NOT_EMPTY: &str = "LIST_NOT_EMPTY"; +pub const CONSTR_NOT_EMPTY: &str = "CONSTR_NOT_EMPTY"; +pub const INCORRECT_BOOLEAN: &str = "INCORRECT_BOOLEAN"; +pub const INCORRECT_CONSTR: &str = "INCORRECT_CONSTR"; +pub const CONSTR_INDEX_MISMATCH: &str = "CONSTR_INDEX_MISMATCH"; + #[derive(Clone, Debug)] pub enum CodeGenFunction { Function { body: AirTree, params: Params }, @@ -181,6 +188,91 @@ impl ClauseProperties { } } +#[derive(Clone, Debug)] +pub struct CodeGenSpecialFuncs { + pub used_funcs: Vec, + pub key_to_func: IndexMap>, +} + +impl CodeGenSpecialFuncs { + pub fn new() -> Self { + let mut key_to_func = IndexMap::new(); + + key_to_func.insert( + CONSTR_FIELDS_EXPOSER.to_string(), + Term::snd_pair() + .apply(Term::unconstr_data().apply(Term::var("__constr_var"))) + .lambda("__constr_var"), + ); + + key_to_func.insert( + CONSTR_INDEX_EXPOSER.to_string(), + Term::fst_pair() + .apply(Term::unconstr_data().apply(Term::var("__constr_var"))) + .lambda("__constr_var"), + ); + + key_to_func.insert( + TOO_MANY_ITEMS.to_string(), + Term::string("List/Tuple/Constr contains more items than expected"), + ); + + key_to_func.insert( + LIST_NOT_EMPTY.to_string(), + Term::string("Expected no items for List"), + ); + + key_to_func.insert( + CONSTR_NOT_EMPTY.to_string(), + Term::string("Expected no fields for Constr"), + ); + + key_to_func.insert( + INCORRECT_BOOLEAN.to_string(), + Term::string("Expected on incorrect Boolean variant"), + ); + + key_to_func.insert( + INCORRECT_CONSTR.to_string(), + Term::string("Expected on incorrect Constr variant"), + ); + + key_to_func.insert( + CONSTR_INDEX_MISMATCH.to_string(), + Term::string("Constr index didn't match a type variant"), + ); + + CodeGenSpecialFuncs { + used_funcs: vec![], + key_to_func, + } + } + + pub fn use_function(&mut self, func_name: &'static str) -> &'static str { + if !self.used_funcs.contains(&func_name.to_string()) { + self.used_funcs.push(func_name.to_string()); + } + func_name + } + + pub fn get_function(&self, func_name: &String) -> Term { + self.key_to_func[func_name].clone() + } + + pub fn apply_used_functions(&self, mut term: Term) -> Term { + for func_name in self.used_funcs.iter() { + term = term.lambda(func_name).apply(self.get_function(func_name)); + } + term + } +} + +impl Default for CodeGenSpecialFuncs { + fn default() -> Self { + Self::new() + } +} + pub fn get_generic_id_and_type(tipo: &Type, param: &Type) -> Vec<(u64, Rc)> { let mut generics_ids = vec![]; @@ -1265,27 +1357,16 @@ pub fn convert_type_to_data(term: Term, field_type: &Rc) -> Term)], id_list: &[u64], tail: bool, current_index: usize, term: Term, - tipos: Vec>, check_last_item: bool, is_list_accessor: bool, - tracing: bool, + error_term: Term, ) -> Term { - let trace_term = if tracing { - Term::Error.trace(Term::string( - "List/Tuple/Constr contains more items than expected", - )) - } else { - Term::Error - }; - - if let Some((first, names)) = names.split_first() { - let (current_tipo, tipos) = tipos.split_first().unwrap(); - + if let Some(((first, current_tipo), names_types)) = names_types.split_first() { let head_list = if matches!(current_tipo.get_uplc_type(), UplcType::Pair(_, _)) && is_list_accessor { Term::head_list().apply(Term::var(format!( @@ -1302,11 +1383,11 @@ pub fn list_access_to_uplc( ) }; - if names.len() == 1 && tail { - if first == "_" && names[0] == "_" { + if names_types.len() == 1 && tail { + if first == "_" && names_types[0].0 == "_" { term.lambda("_") } else if first == "_" { - term.lambda(names[0].clone()) + term.lambda(&names_types[0].0) .apply(Term::tail_list().apply(Term::var(format!( "tail_index_{}_{}", current_index, id_list[current_index] @@ -1315,25 +1396,25 @@ pub fn list_access_to_uplc( "tail_index_{}_{}", current_index, id_list[current_index] )) - } else if names[0] == "_" { - term.lambda(first.clone()).apply(head_list).lambda(format!( + } else if names_types[0].0 == "_" { + term.lambda(first).apply(head_list).lambda(format!( "tail_index_{}_{}", current_index, id_list[current_index] )) } else { - term.lambda(names[0].clone()) + term.lambda(&names_types[0].0) .apply(Term::tail_list().apply(Term::var(format!( "tail_index_{}_{}", current_index, id_list[current_index] )))) - .lambda(first.clone()) + .lambda(first) .apply(head_list) .lambda(format!( "tail_index_{}_{}", current_index, id_list[current_index] )) } - } else if names.is_empty() { + } else if names_types.is_empty() { if first == "_" { if check_last_item { Term::tail_list() @@ -1341,7 +1422,7 @@ pub fn list_access_to_uplc( "tail_index_{}_{}", current_index, id_list[current_index] ))) - .delayed_choose_list(term, trace_term) + .delayed_choose_list(term, error_term) } else { term } @@ -1357,7 +1438,7 @@ pub fn list_access_to_uplc( "tail_index_{}_{}", current_index, id_list[current_index] ))) - .delayed_choose_list(term, trace_term) + .delayed_choose_list(term, error_term) } else { term } @@ -1370,15 +1451,14 @@ pub fn list_access_to_uplc( } } else if first == "_" { let mut list_access_inner = list_access_to_uplc( - names, + names_types, id_list, tail, current_index + 1, term, - tipos.to_owned(), check_last_item, is_list_accessor, - tracing, + error_term, ); list_access_inner = match &list_access_inner { @@ -1409,15 +1489,14 @@ pub fn list_access_to_uplc( } } else { let mut list_access_inner = list_access_to_uplc( - names, + names_types, id_list, tail, current_index + 1, term, - tipos.to_owned(), check_last_item, is_list_accessor, - tracing, + error_term, ); list_access_inner = match &list_access_inner {