feat: refactor code gen to avoid builtin errors when tracing is turned on

This commit is contained in:
microproofs 2024-01-19 13:35:48 -05:00 committed by Kasey
parent b50e4ab63a
commit 956c3d6cf0
4 changed files with 271 additions and 45 deletions

View File

@ -24,13 +24,16 @@ use crate::{
},
builtins::{bool, data, int, list, string, void},
expr::TypedExpr,
gen_uplc::builder::{
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::{
ModuleValueConstructor, PatternConstructor, Type, TypeInfo, ValueConstructor,
@ -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<Name> = Program {
version: (1, 0, 0),
term,
@ -4515,8 +4527,6 @@ impl<'a> CodeGenerator<'a> {
let evaluated_term: Term<NamedDeBruijn> =
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(
.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);

View File

@ -7,6 +7,23 @@ use crate::{
tipo::{Type, ValueConstructor},
};
#[derive(Debug, Clone, PartialEq, Copy)]
pub enum ExpectLevel {
Full,
Items,
None,
}
impl From<bool> 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<Type>,
is_expect: bool,
},
CastToData {
tipo: Rc<Type>,
@ -159,7 +177,7 @@ pub enum Air {
tipo: Rc<Type>,
names: Vec<String>,
tail: bool,
is_expect: bool,
expect_level: ExpectLevel,
},
ListExpose {
tipo: Rc<Type>,

View File

@ -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<Name>, field_type: &Rc<Type>) -> Term<Nam
}
}
pub fn convert_data_to_type_debug(
term: Term<Name>,
field_type: &Rc<Type>,
error_term: Term<Name>,
) -> Term<Name> {
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<Rc<UplcConstant>>) -> Vec<UplcConstant> {
let mut new_constants = vec![];
for constant in constants {
@ -1391,8 +1525,8 @@ pub fn list_access_to_uplc(
names_types_ids: &[(String, Rc<Type>, u64)],
tail: bool,
term: Term<Name>,
check_last_item: bool,
is_list_accessor: bool,
expect_level: ExpectLevel,
error_term: Term<Name>,
) -> Term<Name> {
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,8 +1596,30 @@ pub fn list_access_to_uplc(
acc.lambda(name).apply(head_list).lambda(tail_name)
}
} else if name == "_" {
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())))
.lambda(name)
@ -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,
}

View File

@ -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<String>,
tail: bool,
list: Box<AirTree>,
is_expect: bool,
expect_level: ExpectLevel,
msg: Option<AirMsg>,
},
ListExpose {
@ -272,6 +272,7 @@ pub enum AirExpression {
CastFromData {
tipo: Rc<Type>,
value: Box<AirTree>,
msg: Option<AirMsg>,
},
CastToData {
tipo: Rc<Type>,
@ -502,10 +503,11 @@ impl AirTree {
hoisted_over: None,
}
}
pub fn cast_from_data(value: AirTree, tipo: Rc<Type>) -> AirTree {
pub fn cast_from_data(value: AirTree, tipo: Rc<Type>, msg: Option<AirMsg>) -> AirTree {
AirTree::Expression(AirExpression::CastFromData {
tipo,
value: value.into(),
msg,
})
}
pub fn cast_to_data(value: AirTree, tipo: Rc<Type>) -> 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<AirMsg>,
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<Type>, 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 } => {