From 956c3d6cf031d8de9e4cd8626f363a20f74801b5 Mon Sep 17 00:00:00 2001 From: microproofs Date: Fri, 19 Jan 2024 13:35:48 -0500 Subject: [PATCH] feat: refactor code gen to avoid builtin errors when tracing is turned on --- crates/aiken-lang/src/gen_uplc.rs | 82 ++++++---- crates/aiken-lang/src/gen_uplc/air.rs | 20 ++- crates/aiken-lang/src/gen_uplc/builder.rs | 184 +++++++++++++++++++++- crates/aiken-lang/src/gen_uplc/tree.rs | 30 ++-- 4 files changed, 271 insertions(+), 45 deletions(-) diff --git a/crates/aiken-lang/src/gen_uplc.rs b/crates/aiken-lang/src/gen_uplc.rs index 3624a844..d4844b56 100644 --- a/crates/aiken-lang/src/gen_uplc.rs +++ b/crates/aiken-lang/src/gen_uplc.rs @@ -24,12 +24,15 @@ use crate::{ }, 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_generic_variant_name, get_line_columns_by_span, - get_src_code_by_span, monomorphize, pattern_has_conditions, wrap_as_multi_validator, - wrap_validator_condition, CodeGenFunction, SpecificClause, + gen_uplc::{ + air::ExpectLevel, + 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_generic_variant_name, get_line_columns_by_span, + get_src_code_by_span, monomorphize, pattern_has_conditions, wrap_as_multi_validator, + wrap_validator_condition, CodeGenFunction, SpecificClause, + }, }, line_numbers::LineNumbers, tipo::{ @@ -840,7 +843,7 @@ impl<'a> CodeGenerator<'a> { // Cast value to or from data so we don't have to worry from this point onward if props.value_type.is_data() && props.kind.is_expect() && !tipo.is_data() { - value = AirTree::cast_from_data(value, tipo.clone()); + value = AirTree::cast_from_data(value, tipo.clone(), props.msg_func.clone()); } else if !props.value_type.is_data() && tipo.is_data() { value = AirTree::cast_to_data(value, props.value_type.clone()); } @@ -1050,7 +1053,11 @@ impl<'a> CodeGenerator<'a> { tail.is_some(), value, props.msg_func, - true, + if props.full_check { + ExpectLevel::Full + } else { + ExpectLevel::Items + }, ) }; @@ -1345,8 +1352,8 @@ impl<'a> CodeGenerator<'a> { vec![fst_name.clone(), snd_name.clone()], inner_list_type.clone(), AirTree::local_var(&pair_name, inner_list_type.clone()), - None, - false, + msg_func.clone(), + msg_func.is_some(), ); let expect_fst = self.expect_type_assign( @@ -1431,6 +1438,7 @@ impl<'a> CodeGenerator<'a> { AirTree::cast_from_data( AirTree::local_var(&item_name, data()), inner_list_type.clone(), + msg_func.clone(), ), defined_data_types, location, @@ -2291,7 +2299,7 @@ impl<'a> CodeGenerator<'a> { // So for the msg we pass in empty string if tracing is on // Since check_last_item is false this will never get added to the final uplc anyway None, - false, + ExpectLevel::None, ) } else { assert!(defined_tails.len() >= defined_heads.len()); @@ -4042,7 +4050,7 @@ impl<'a> CodeGenerator<'a> { // tipo here refers to the list type while the actual return // type is nothing since this is an assignment over some expression tipo, - is_expect, + expect_level, } => { let value = arg_stack.pop().unwrap(); @@ -4071,8 +4079,8 @@ impl<'a> CodeGenerator<'a> { &names_types, tail, term, - is_expect && !tail, true, + expect_level, error_term, ) .apply(value); @@ -4495,12 +4503,16 @@ impl<'a> CodeGenerator<'a> { Some(term) } - Air::CastFromData { tipo } => { + Air::CastFromData { tipo, .. } => { let mut term = arg_stack.pop().unwrap(); - if extract_constant(&term).is_some() { - term = builder::convert_data_to_type(term, &tipo); + term = if error_term == Term::Error { + builder::convert_data_to_type(term, &tipo) + } else { + builder::convert_data_to_type_debug(term, &tipo, error_term) + }; + if extract_constant(&term).is_some() { let mut program: Program = Program { version: (1, 0, 0), term, @@ -4515,8 +4527,6 @@ impl<'a> CodeGenerator<'a> { let evaluated_term: Term = eval_program.eval(ExBudget::default()).result().unwrap(); term = evaluated_term.try_into().unwrap(); - } else { - term = builder::convert_data_to_type(term, &tipo); } Some(term) @@ -4979,8 +4989,8 @@ impl<'a> CodeGenerator<'a> { &names_types, false, term, - is_expect, false, + is_expect.into(), error_term, ); @@ -5213,19 +5223,35 @@ impl<'a> CodeGenerator<'a> { if names[1] != "_" { term = term .lambda(names[1].clone()) - .apply(builder::convert_data_to_type( - Term::snd_pair().apply(Term::var(format!("__tuple_{list_id}"))), - &inner_types[1], - )); + .apply(if error_term != Term::Error { + builder::convert_data_to_type_debug( + Term::snd_pair().apply(Term::var(format!("__tuple_{list_id}"))), + &inner_types[1], + error_term.clone(), + ) + } else { + builder::convert_data_to_type( + Term::snd_pair().apply(Term::var(format!("__tuple_{list_id}"))), + &inner_types[1], + ) + }); } if names[0] != "_" { term = term .lambda(names[0].clone()) - .apply(builder::convert_data_to_type( - Term::fst_pair().apply(Term::var(format!("__tuple_{list_id}"))), - &inner_types[0], - )) + .apply(if error_term != Term::Error { + builder::convert_data_to_type_debug( + Term::snd_pair().apply(Term::var(format!("__tuple_{list_id}"))), + &inner_types[1], + error_term, + ) + } else { + builder::convert_data_to_type( + Term::snd_pair().apply(Term::var(format!("__tuple_{list_id}"))), + &inner_types[1], + ) + }) } term = term.lambda(format!("__tuple_{list_id}")).apply(value); @@ -5250,8 +5276,8 @@ impl<'a> CodeGenerator<'a> { &names_types, false, term, - is_expect, false, + is_expect.into(), error_term, ) .apply(value); diff --git a/crates/aiken-lang/src/gen_uplc/air.rs b/crates/aiken-lang/src/gen_uplc/air.rs index 3ad00a91..9c9fb344 100644 --- a/crates/aiken-lang/src/gen_uplc/air.rs +++ b/crates/aiken-lang/src/gen_uplc/air.rs @@ -7,6 +7,23 @@ use crate::{ tipo::{Type, ValueConstructor}, }; +#[derive(Debug, Clone, PartialEq, Copy)] +pub enum ExpectLevel { + Full, + Items, + None, +} + +impl From for ExpectLevel { + fn from(value: bool) -> Self { + if value { + ExpectLevel::Full + } else { + ExpectLevel::None + } + } +} + #[derive(Debug, Clone, PartialEq)] pub enum Air { // Primitives @@ -83,6 +100,7 @@ pub enum Air { }, CastFromData { tipo: Rc, + is_expect: bool, }, CastToData { tipo: Rc, @@ -159,7 +177,7 @@ pub enum Air { tipo: Rc, names: Vec, tail: bool, - is_expect: bool, + expect_level: ExpectLevel, }, ListExpose { tipo: Rc, diff --git a/crates/aiken-lang/src/gen_uplc/builder.rs b/crates/aiken-lang/src/gen_uplc/builder.rs index e3855a36..6772c04f 100644 --- a/crates/aiken-lang/src/gen_uplc/builder.rs +++ b/crates/aiken-lang/src/gen_uplc/builder.rs @@ -30,7 +30,7 @@ use crate::{ }; use super::{ - air::Air, + air::{Air, ExpectLevel}, tree::{AirExpression, AirMsg, AirStatement, AirTree, TreePath}, }; @@ -1237,6 +1237,140 @@ pub fn convert_data_to_type(term: Term, field_type: &Rc) -> Term, + field_type: &Rc, + error_term: Term, +) -> Term { + if field_type.is_int() { + Term::var("__val") + .delayed_choose_data( + error_term.clone(), + error_term.clone(), + error_term.clone(), + Term::un_i_data().apply(Term::var("__val")), + error_term.clone(), + ) + .lambda("__val") + .apply(term) + } else if field_type.is_bytearray() { + Term::var("__val") + .delayed_choose_data( + error_term.clone(), + error_term.clone(), + error_term.clone(), + error_term.clone(), + Term::un_b_data().apply(Term::var("__val")), + ) + .lambda("__val") + .apply(term) + } else if field_type.is_void() { + Term::var("__val") + .delayed_choose_data( + Term::equals_integer() + .apply(Term::integer(0.into())) + .apply(Term::fst_pair().apply(Term::unconstr_data().apply(Term::var("__val")))) + .delayed_if_then_else(Term::unit(), Term::Error), + error_term.clone(), + error_term.clone(), + error_term.clone(), + error_term.clone(), + ) + .lambda("__val") + .apply(term) + } else if field_type.is_map() { + Term::var("__val") + .delayed_choose_data( + error_term.clone(), + Term::unmap_data().apply(Term::var("__val")), + error_term.clone(), + error_term.clone(), + error_term.clone(), + ) + .lambda("__val") + .apply(term) + } else if field_type.is_string() { + Term::var("__val") + .delayed_choose_data( + error_term.clone(), + error_term.clone(), + error_term.clone(), + error_term.clone(), + Term::Builtin(DefaultFunction::DecodeUtf8) + .apply(Term::un_b_data().apply(Term::var("__val"))), + ) + .lambda("__val") + .apply(term) + } else if field_type.is_tuple() && matches!(field_type.get_uplc_type(), UplcType::Pair(_, _)) { + Term::var("__val") + .delayed_choose_data( + error_term.clone(), + error_term.clone(), + Term::mk_pair_data() + .apply(Term::head_list().apply(Term::var("__list_data"))) + .apply(Term::head_list().apply(Term::var("__tail"))) + .lambda("__tail") + .apply(Term::tail_list().apply(Term::var("__list_data"))) + .lambda("__list_data") + .apply(Term::unlist_data().apply(Term::var("__val"))), + error_term.clone(), + error_term.clone(), + ) + .lambda("__val") + .apply(term) + } else if field_type.is_list() || field_type.is_tuple() { + Term::var("__val") + .delayed_choose_data( + error_term.clone(), + error_term.clone(), + Term::unlist_data().apply(Term::var("__val")), + error_term.clone(), + error_term.clone(), + ) + .lambda("__val") + .apply(term) + } else if field_type.is_bool() { + Term::var("__val") + .delayed_choose_data( + Term::equals_integer() + .apply(Term::integer(1.into())) + .apply(Term::fst_pair().apply(Term::unconstr_data().apply(Term::var("__val")))), + error_term.clone(), + error_term.clone(), + error_term.clone(), + error_term.clone(), + ) + .lambda("__val") + .apply(term) + } else if field_type.is_bls381_12_g1() { + Term::var("__val") + .delayed_choose_data( + error_term.clone(), + error_term.clone(), + error_term.clone(), + error_term.clone(), + Term::bls12_381_g1_uncompress().apply(Term::un_b_data().apply(Term::var("__val"))), + ) + .lambda("__val") + .apply(term) + } else if field_type.is_bls381_12_g2() { + Term::var("__val") + .delayed_choose_data( + error_term.clone(), + error_term.clone(), + error_term.clone(), + error_term.clone(), + Term::bls12_381_g2_uncompress().apply(Term::un_b_data().apply(Term::var("__val"))), + ) + .lambda("__val") + .apply(term) + } else if field_type.is_ml_result() { + panic!("ML Result not supported") + } else { + term + } +} + pub fn convert_constants_to_data(constants: Vec>) -> Vec { let mut new_constants = vec![]; for constant in constants { @@ -1391,8 +1525,8 @@ pub fn list_access_to_uplc( names_types_ids: &[(String, Rc, u64)], tail: bool, term: Term, - check_last_item: bool, is_list_accessor: bool, + expect_level: ExpectLevel, error_term: Term, ) -> Term { let names_len = names_types_ids.len(); @@ -1404,7 +1538,10 @@ pub fn list_access_to_uplc( .collect_vec(); // If the the is just discards and check_last_item then we check for empty list - if no_tailing_discards.is_empty() && !tail && check_last_item { + if no_tailing_discards.is_empty() + && !tail + && matches!(expect_level, ExpectLevel::Full | ExpectLevel::Items) + { return Term::var("empty_list") .delayed_choose_list(term, error_term) .lambda("empty_list"); @@ -1426,6 +1563,12 @@ pub fn list_access_to_uplc( let head_list = if matches!(tipo.get_uplc_type(), UplcType::Pair(_, _)) && is_list_accessor { Term::head_list().apply(Term::var(tail_name.to_string())) + } else if matches!(expect_level, ExpectLevel::Full) && error_term != Term::Error { + convert_data_to_type_debug( + Term::head_list().apply(Term::var(tail_name.to_string())), + &tipo.to_owned(), + error_term.clone(), + ) } else { convert_data_to_type( Term::head_list().apply(Term::var(tail_name.to_string())), @@ -1442,7 +1585,7 @@ pub fn list_access_to_uplc( // case for no tail // name is guaranteed to not be discard at this point - if check_last_item { + if matches!(expect_level, ExpectLevel::Full | ExpectLevel::Items) { Term::tail_list() .apply(Term::var(tail_name.to_string())) .delayed_choose_list(acc, error_term.clone()) @@ -1453,7 +1596,29 @@ pub fn list_access_to_uplc( acc.lambda(name).apply(head_list).lambda(tail_name) } } else if name == "_" { - acc.apply(Term::tail_list().apply(Term::var(tail_name.to_string()))) + if matches!(expect_level, ExpectLevel::Full | ExpectLevel::Items) + && error_term != Term::Error + { + Term::var(tail_name.to_string()) + .delayed_choose_list( + error_term.clone(), + acc.apply(Term::tail_list().apply(Term::var(tail_name.to_string()))), + ) + .lambda(tail_name) + } else { + acc.apply(Term::tail_list().apply(Term::var(tail_name.to_string()))) + .lambda(tail_name) + } + } else if matches!(expect_level, ExpectLevel::Full | ExpectLevel::Items) + && error_term != Term::Error + { + Term::var(tail_name.to_string()) + .delayed_choose_list( + error_term.clone(), + acc.apply(Term::tail_list().apply(Term::var(tail_name.to_string()))) + .lambda(name) + .apply(head_list), + ) .lambda(tail_name) } else { acc.apply(Term::tail_list().apply(Term::var(tail_name.to_string()))) @@ -1791,9 +1956,14 @@ pub fn air_holds_msg(air: &Air) -> bool { Air::AssertConstr { .. } | Air::AssertBool { .. } | Air::FieldsEmpty | Air::ListEmpty => { true } + Air::FieldsExpose { is_expect, .. } - | Air::ListAccessor { is_expect, .. } - | Air::TupleAccessor { is_expect, .. } => *is_expect, + | Air::TupleAccessor { is_expect, .. } + | Air::CastFromData { is_expect, .. } => *is_expect, + + Air::ListAccessor { expect_level, .. } => { + matches!(expect_level, ExpectLevel::Full | ExpectLevel::Items) + } _ => false, } diff --git a/crates/aiken-lang/src/gen_uplc/tree.rs b/crates/aiken-lang/src/gen_uplc/tree.rs index d2e7d290..bc605484 100644 --- a/crates/aiken-lang/src/gen_uplc/tree.rs +++ b/crates/aiken-lang/src/gen_uplc/tree.rs @@ -9,7 +9,7 @@ use crate::{ tipo::{Type, ValueConstructor, ValueConstructorVariant}, }; -use super::air::Air; +use super::air::{Air, ExpectLevel}; #[derive(Clone, Debug, PartialEq)] pub struct TreePath { @@ -179,7 +179,7 @@ pub enum AirStatement { names: Vec, tail: bool, list: Box, - is_expect: bool, + expect_level: ExpectLevel, msg: Option, }, ListExpose { @@ -272,6 +272,7 @@ pub enum AirExpression { CastFromData { tipo: Rc, value: Box, + msg: Option, }, CastToData { tipo: Rc, @@ -502,10 +503,11 @@ impl AirTree { hoisted_over: None, } } - pub fn cast_from_data(value: AirTree, tipo: Rc) -> AirTree { + pub fn cast_from_data(value: AirTree, tipo: Rc, msg: Option) -> AirTree { AirTree::Expression(AirExpression::CastFromData { tipo, value: value.into(), + msg, }) } pub fn cast_to_data(value: AirTree, tipo: Rc) -> AirTree { @@ -733,6 +735,7 @@ impl AirTree { vec![list_of_fields], ), tipo.clone(), + None, ) } @@ -758,7 +761,7 @@ impl AirTree { tail: bool, list: AirTree, msg: Option, - is_expect: bool, + expect_level: ExpectLevel, ) -> AirTree { AirTree::Statement { statement: AirStatement::ListAccessor { @@ -766,7 +769,7 @@ impl AirTree { names, tail, list: list.into(), - is_expect, + expect_level, msg, }, hoisted_over: None, @@ -816,6 +819,7 @@ impl AirTree { vec![tuple], ), tipo.clone(), + None, ) } pub fn error(tipo: Rc, validator: bool) -> AirTree { @@ -1069,13 +1073,13 @@ impl AirTree { tail, list, msg, - is_expect, + expect_level, } => { air_vec.push(Air::ListAccessor { tipo: tipo.clone(), names: names.clone(), tail: *tail, - is_expect: *is_expect, + expect_level: *expect_level, }); if let Some(msg) = msg { @@ -1227,8 +1231,16 @@ impl AirTree { air_vec.push(Air::UnOp { op: *op }); arg.create_air_vec(air_vec); } - AirExpression::CastFromData { tipo, value } => { - air_vec.push(Air::CastFromData { tipo: tipo.clone() }); + AirExpression::CastFromData { tipo, value, msg } => { + air_vec.push(Air::CastFromData { + tipo: tipo.clone(), + is_expect: msg.is_some(), + }); + + if let Some(msg) = msg { + msg.to_air_tree().create_air_vec(air_vec); + } + value.create_air_vec(air_vec); } AirExpression::CastToData { tipo, value } => {