Skip to content

Commit e3de042

Browse files
committed
cleanup redundancy and duplication around UPLC/program generation
The code is sufficiently complexe already, and the addition of the source map context makes it a bit more. That's okay. But duplicating entire chunks of logic with no changes when we have generics, is not! There's a similar cleanup to be done around the 'coverage' command, which is, in most part, a duplicate of the `check` command. Note that this changes also drop the 'finalize_minimal' and make the `no_optimize` flag now unused. It is not a mistake. That flag should not exist and shall be removed in upcoming commits. Signed-off-by: KtorZ <matthias.benkort@gmail.com>
1 parent 79fc43c commit e3de042

14 files changed

Lines changed: 229 additions & 370 deletions

File tree

crates/aiken-lang/src/ast.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2489,7 +2489,7 @@ impl chumsky::Span for Span {
24892489

24902490
/// A source location that includes both the module name and the byte span.
24912491
/// Used for tracking source locations across module boundaries during code generation.
2492-
#[derive(Clone, Default, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
2492+
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
24932493
pub struct SourceLocation {
24942494
/// The module name (e.g., "aiken/collection/list" or "validators/my_validator")
24952495
pub module: String,
@@ -2510,11 +2510,11 @@ impl SourceLocation {
25102510
}
25112511

25122512
pub fn is_empty(&self) -> bool {
2513-
self.module.is_empty() && self.span.start == 0 && self.span.end == 0
2513+
self.module.is_empty() && self.span == Span::default()
25142514
}
25152515
}
25162516

2517-
impl fmt::Debug for SourceLocation {
2517+
impl fmt::Display for SourceLocation {
25182518
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
25192519
write!(f, "{}:{:?}", self.module, self.span.range())
25202520
}

crates/aiken-lang/src/gen_uplc.rs

Lines changed: 21 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,7 @@ use uplc::{
5656
builder::{CONSTR_FIELDS_EXPOSER, CONSTR_INDEX_EXPOSER, EXPECT_ON_LIST},
5757
builtins::DefaultFunction,
5858
machine::cost_model::ExBudget,
59-
optimize::{
60-
aiken_optimize_and_intern, aiken_optimize_minimal_with_context,
61-
aiken_optimize_with_context, interner::CodeGenInterner, shrinker::NO_INLINE,
62-
},
59+
optimize::{aiken_optimize_and_intern, interner::CodeGenInterner, shrinker::NO_INLINE},
6360
};
6461

6562
type Otherwise = Option<AirTree>;
@@ -138,18 +135,13 @@ impl<'a> CodeGenerator<'a> {
138135
}
139136
}
140137

141-
pub fn generate(&mut self, validator: &TypedValidator, module_name: &str) -> Program<Name> {
142-
let (program, _term_with_spans) = self.generate_with_term(validator, module_name);
143-
program
144-
}
145-
146138
/// Generate a validator program and return both the finalized program and the term with spans.
147139
/// The term with spans is useful for generating source maps.
148-
pub fn generate_with_term(
140+
pub fn generate(
149141
&mut self,
150142
validator: &TypedValidator,
151143
module_name: &str,
152-
) -> (Program<Name>, Term<Name, SourceLocation>) {
144+
) -> Program<Name, SourceLocation> {
153145
let context_name = "__context__".to_string();
154146
let context_name_interned = introduce_name(&mut self.interner, &context_name);
155147
validator.params.iter().for_each(|arg| {
@@ -190,20 +182,15 @@ impl<'a> CodeGenerator<'a> {
190182
});
191183

192184
// Finalize with spans preserved for source map generation
193-
let program_with_spans = self.finalize_with_spans(term);
194-
195-
// Strip spans for the program (compiled code doesn't need them)
196-
let program = program_with_spans.clone().map_context(|_| ());
197-
198-
(program, program_with_spans.term)
185+
self.finalize(term)
199186
}
200187

201188
pub fn generate_raw(
202189
&mut self,
203190
body: &TypedExpr,
204191
args: &[TypedArg],
205192
module_name: &str,
206-
) -> Program<Name> {
193+
) -> Program<Name, SourceLocation> {
207194
args.iter().for_each(|arg| {
208195
arg.get_variable_name()
209196
.iter()
@@ -233,50 +220,7 @@ impl<'a> CodeGenerator<'a> {
233220
.for_each(|arg_name| self.interner.pop_text(arg_name.to_string()))
234221
});
235222

236-
self.finalize(term.map_context(|_| ()))
237-
}
238-
239-
/// Returns the raw Term with Span context preserved.
240-
/// This applies used functions (critical for recursive functions to work)
241-
/// and is useful for source map generation.
242-
pub fn generate_raw_with_spans(
243-
&mut self,
244-
body: &TypedExpr,
245-
args: &[TypedArg],
246-
module_name: &str,
247-
) -> Term<Name, SourceLocation> {
248-
args.iter().for_each(|arg| {
249-
arg.get_variable_name()
250-
.iter()
251-
.for_each(|arg_name| self.interner.intern(arg_name.to_string()))
252-
});
253-
254-
let mut air_tree = self.build(body, module_name, &[]);
255-
256-
air_tree = AirTree::no_op(air_tree, SourceLocation::empty());
257-
258-
let full_tree = self.hoist_functions_to_validator(air_tree);
259-
260-
let full_vec = full_tree.to_vec();
261-
262-
let mut term = self.uplc_code_gen(full_vec);
263-
264-
term = if args.is_empty() {
265-
term
266-
} else {
267-
cast_validator_args(term, args, &self.interner, &self.data_types)
268-
};
269-
270-
args.iter().for_each(|arg| {
271-
arg.get_variable_name()
272-
.iter()
273-
.for_each(|arg_name| self.interner.pop_text(arg_name.to_string()))
274-
});
275-
276-
// Apply used functions (critical for recursive functions to work)
277-
term = self.special_functions.apply_used_functions(term);
278-
279-
term
223+
self.finalize(term)
280224
}
281225

282226
fn new_program<T, C>(&self, term: Term<T, C>) -> Program<T, C> {
@@ -288,7 +232,12 @@ impl<'a> CodeGenerator<'a> {
288232
Program { version, term }
289233
}
290234

291-
fn finalize(&mut self, mut term: Term<Name>) -> Program<Name> {
235+
/// Finalize and optimize a program while preserving source location context.
236+
/// Uses the subset of optimizations that are generic over context type.
237+
fn finalize<C: Default + Clone + PartialEq>(
238+
&mut self,
239+
mut term: Term<Name, C>,
240+
) -> Program<Name, C> {
292241
term = self.special_functions.apply_used_functions(term);
293242

294243
let program = aiken_optimize_and_intern(self.new_program(term));
@@ -305,41 +254,6 @@ impl<'a> CodeGenerator<'a> {
305254
program
306255
}
307256

308-
/// Finalize and optimize a program while preserving source location context.
309-
/// Uses the subset of optimizations that are generic over context type.
310-
pub fn finalize_with_spans(
311-
&mut self,
312-
mut term: Term<Name, SourceLocation>,
313-
) -> Program<Name, SourceLocation> {
314-
// Apply used functions (critical for recursive functions to work)
315-
term = self.special_functions.apply_used_functions(term);
316-
317-
let program = aiken_optimize_with_context(self.new_program(term));
318-
319-
// Reset is important for reusing the generator instance
320-
self.reset(true);
321-
322-
program
323-
}
324-
325-
/// Finalize with minimal optimization, preserving source location context.
326-
/// Skips performance optimizations like inlining and lambda reduction.
327-
/// Produces larger but more readable code that maps directly to source.
328-
pub fn finalize_minimal_with_spans(
329-
&mut self,
330-
mut term: Term<Name, SourceLocation>,
331-
) -> Program<Name, SourceLocation> {
332-
// Apply used functions (critical for recursive functions to work)
333-
term = self.special_functions.apply_used_functions(term);
334-
335-
let program = aiken_optimize_minimal_with_context(self.new_program(term));
336-
337-
// Reset is important for reusing the generator instance
338-
self.reset(true);
339-
340-
program
341-
}
342-
343257
// TODO: pass mono types to build so monomorphization
344258
// happens as we build the AIR Tree rather than after
345259
fn build(
@@ -4259,7 +4173,7 @@ impl<'a> CodeGenerator<'a> {
42594173

42604174
let mut program = self.new_program(
42614175
self.special_functions
4262-
.apply_used_functions(term.map_context(|_| ())),
4176+
.apply_used_functions(term.strip_context()),
42634177
);
42644178

42654179
let mut interner = CodeGenInterner::new();
@@ -4275,7 +4189,7 @@ impl<'a> CodeGenerator<'a> {
42754189
.unwrap_or_else(|e| panic!("Failed to evaluate constant: {e:#?}"))
42764190
.try_into()
42774191
.unwrap();
4278-
Some(result.map_context(|_| location.clone()))
4192+
Some(result.map_context(&|_| location.clone()))
42794193
}
42804194
ValueConstructorVariant::ModuleFn {
42814195
name: func_name,
@@ -4438,7 +4352,7 @@ impl<'a> CodeGenerator<'a> {
44384352
term = term.lambda(format!("arg_{index}"))
44394353
}
44404354
}
4441-
Some(term.map_context(|_| location.clone()))
4355+
Some(term.map_context(&|_| location.clone()))
44424356
}
44434357
}
44444358
},
@@ -5048,7 +4962,7 @@ impl<'a> CodeGenerator<'a> {
50484962
};
50494963

50504964
if extract_constant(term.pierce_no_inlines_ref()).is_some() {
5051-
let mut program = self.new_program(term.clone().map_context(|_| ()));
4965+
let mut program = self.new_program(term.clone().strip_context());
50524966

50534967
let mut interner = CodeGenInterner::new();
50544968

@@ -5063,7 +4977,7 @@ impl<'a> CodeGenerator<'a> {
50634977
.expect("Evaluated on unwrapping a data constant and got an error");
50644978

50654979
let result: Term<Name> = evaluated_term.try_into().unwrap();
5066-
term = result.map_context(|_| location.clone());
4980+
term = result.map_context(&|_| location.clone());
50674981
}
50684982

50694983
Some(term)
@@ -5074,7 +4988,7 @@ impl<'a> CodeGenerator<'a> {
50744988
if extract_constant(term.pierce_no_inlines_ref()).is_some() {
50754989
term = builder::convert_type_to_data(term, &tipo, &self.data_types);
50764990

5077-
let mut program = self.new_program(term.clone().map_context(|_| ()));
4991+
let mut program = self.new_program(term.clone().strip_context());
50784992

50794993
let mut interner = CodeGenInterner::new();
50804994

@@ -5089,7 +5003,7 @@ impl<'a> CodeGenerator<'a> {
50895003
.expect("Evaluated on wrapping a constant into data and got an error");
50905004

50915005
let result: Term<Name> = evaluated_term.try_into().unwrap();
5092-
term = result.map_context(|_| location.clone());
5006+
term = result.map_context(&|_| location.clone());
50935007
} else {
50945008
term = builder::convert_type_to_data(term, &tipo, &self.data_types);
50955009
}
@@ -5294,7 +5208,7 @@ impl<'a> CodeGenerator<'a> {
52945208
let maybe_const = extract_constant(item.pierce_no_inlines_ref());
52955209
maybe_const.is_some()
52965210
}) {
5297-
let mut program = self.new_program(term.clone().map_context(|_| ()));
5211+
let mut program = self.new_program(term.clone().strip_context());
52985212

52995213
let mut interner = CodeGenInterner::new();
53005214

@@ -5309,7 +5223,7 @@ impl<'a> CodeGenerator<'a> {
53095223
.expect("Evaluated a constant record with args and got an error");
53105224

53115225
let result: Term<Name> = evaluated_term.try_into().unwrap();
5312-
term = result.map_context(|_| location.clone());
5226+
term = result.map_context(&|_| location.clone());
53135227
}
53145228

53155229
Some(term)

crates/aiken-lang/src/gen_uplc/builder.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ impl CodeGenSpecialFuncs {
150150
) -> Term<Name, C> {
151151
for func_name in self.used_funcs.iter() {
152152
// Convert the stored function (Term<Name>) to Term<Name, C>
153-
let func = self.get_function(func_name).map_context(|_| C::default());
153+
let func = self.get_function(func_name).map_context(&|_| C::default());
154154
term = term.lambda(func_name).apply(func);
155155
}
156156
term

crates/aiken-lang/src/test_framework.rs

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -68,18 +68,21 @@ impl Test {
6868
module_name: String,
6969
input_path: PathBuf,
7070
) -> Test {
71-
let program = generator.generate_raw(&test.body, &[], &module_name);
71+
let program = generator
72+
.generate_raw(&test.body, &[], &module_name)
73+
.strip_context();
7274

7375
let assertion = match test.body.try_into() {
7476
Err(..) => None,
7577
Ok(Assertion { bin_op, head, tail }) => {
7678
let as_constant = |generator: &mut CodeGenerator<'_>, side| {
77-
Program::<NamedDeBruijn>::try_from(generator.generate_raw(
79+
Program::<NamedDeBruijn, _>::try_from(generator.generate_raw(
7880
&side,
7981
&[],
8082
&module_name,
8183
))
8284
.expect("failed to convert assertion operaand to NamedDeBruijn")
85+
.strip_context()
8386
.eval(ExBudget::max())
8487
.unwrap_constant()
8588
.map(|cst| (cst, side.tipo()))
@@ -146,19 +149,25 @@ impl Test {
146149

147150
let stripped_type_info = convert_opaque_type(&type_info, generator.data_types(), true);
148151

149-
let program = generator.clone().generate_raw(
150-
&test.body,
151-
&[TypedArg {
152-
tipo: stripped_type_info.clone(),
153-
..parameter.clone().into()
154-
}],
155-
&module_name,
156-
);
152+
let program = generator
153+
.clone()
154+
.generate_raw(
155+
&test.body,
156+
&[TypedArg {
157+
tipo: stripped_type_info.clone(),
158+
..parameter.clone().into()
159+
}],
160+
&module_name,
161+
)
162+
.strip_context();
157163

158164
// NOTE: We need not to pass any parameter to the fuzzer/sampler here because the fuzzer
159165
// argument is a Data constructor which needs not any conversion. So we can just safely
160166
// apply onto it later.
161-
let generator_program = generator.clone().generate_raw(&via, &[], &module_name);
167+
let generator_program = generator
168+
.clone()
169+
.generate_raw(&via, &[], &module_name)
170+
.strip_context();
162171

163172
match kind {
164173
RunnableKind::Bench => Test::Benchmark(Benchmark {
Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
use aiken_lang::{ast::SourceLocation, ast::TypedValidator, gen_uplc::CodeGenerator};
2-
use uplc::ast::{DeBruijn, Name, Program, Term};
1+
use aiken_lang::{
2+
ast::{SourceLocation, TypedValidator},
3+
gen_uplc::CodeGenerator,
4+
};
5+
use uplc::ast::{Name, Program};
36

47
#[derive(Default)]
58
pub struct MemoProgram {
6-
program: Option<Program<DeBruijn>>,
7-
term_with_spans: Option<Term<Name, SourceLocation>>,
9+
program: Option<Program<Name, SourceLocation>>,
810
}
911

1012
impl MemoProgram {
@@ -13,24 +15,14 @@ impl MemoProgram {
1315
generator: &mut CodeGenerator,
1416
def: &TypedValidator,
1517
module_name: &str,
16-
) -> Program<DeBruijn> {
18+
) -> Program<Name, SourceLocation> {
1719
match self.program.take() {
1820
None => {
19-
let (program, term_with_spans) = generator.generate_with_term(def, module_name);
20-
let new_program = program.to_debruijn().unwrap();
21-
21+
let new_program = generator.generate(def, module_name);
2222
self.program.replace(new_program.clone());
23-
self.term_with_spans.replace(term_with_spans);
24-
2523
new_program
2624
}
2725
Some(program) => program,
2826
}
2927
}
30-
31-
/// Get the term with source locations, if available.
32-
/// This is only available after `get()` has been called.
33-
pub fn get_term_with_spans(&self) -> Option<&Term<Name, SourceLocation>> {
34-
self.term_with_spans.as_ref()
35-
}
3628
}

0 commit comments

Comments
 (0)