feat: Rework codegen traces to prevent repeated messages from taking up uplc script space

This commit is contained in:
microproofs 2023-10-07 14:43:12 -04:00 committed by Kasey
parent 44021cde19
commit 7a6ddc45a0
2 changed files with 224 additions and 94 deletions

View File

@ -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<FunctionAccessKey, &'a TypedFunction>,
data_types: IndexMap<DataTypeKey, &'a TypedDataType>,
module_types: IndexMap<&'a String, &'a TypeInfo>,
needs_field_access: bool,
special_functions: CodeGenSpecialFuncs,
code_gen_functions: IndexMap<String, CodeGenFunction>,
zero_arg_functions: IndexMap<(FunctionAccessKey, Variant), Vec<Air>>,
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<Name>) -> Program<Name> {
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);

View File

@ -36,6 +36,13 @@ pub type Params = Vec<String>;
pub type CycleFunctionNames = Vec<String>;
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<String>,
pub key_to_func: IndexMap<String, Term<Name>>,
}
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<Name> {
self.key_to_func[func_name].clone()
}
pub fn apply_used_functions(&self, mut term: Term<Name>) -> Term<Name> {
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<Type>)> {
let mut generics_ids = vec![];
@ -1265,27 +1357,16 @@ pub fn convert_type_to_data(term: Term<Name>, field_type: &Rc<Type>) -> Term<Nam
#[allow(clippy::too_many_arguments)]
pub fn list_access_to_uplc(
names: &[String],
names_types: &[(String, Rc<Type>)],
id_list: &[u64],
tail: bool,
current_index: usize,
term: Term<Name>,
tipos: Vec<Rc<Type>>,
check_last_item: bool,
is_list_accessor: bool,
tracing: bool,
error_term: Term<Name>,
) -> Term<Name> {
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 {