From 8d236ff354c23669ba4b84ad5b161385c986dc42 Mon Sep 17 00:00:00 2001 From: arty Date: Tue, 28 Oct 2025 10:28:37 -0700 Subject: [PATCH 01/10] start unstacking Co-authored-by: arty --- src/compiler/evaluate.rs | 87 ++++++++++++++++++++++++++++++---------- 1 file changed, 65 insertions(+), 22 deletions(-) diff --git a/src/compiler/evaluate.rs b/src/compiler/evaluate.rs index 2dd062ca5..612ed7f07 100644 --- a/src/compiler/evaluate.rs +++ b/src/compiler/evaluate.rs @@ -29,6 +29,14 @@ use crate::util::{number_from_u8, u8_from_number, Number}; const PRIM_RUN_LIMIT: usize = 1000000; pub const EVAL_STACK_LIMIT: usize = 200; +pub trait Process { + fn run(&self) -> Result; +} + +pub enum EvalResult { + Body(Rc), +} + // Stack depth checker. #[derive(Clone, Debug, Default)] pub struct VisitedInfo { @@ -1301,25 +1309,60 @@ impl<'info> Evaluator { )) } - // A frontend language evaluator and minifier fn shrink_bodyform_visited( &self, context: &mut BasicCompileContext, - visited_: &'info mut VisitedMarker<'_, VisitedInfo>, + visited: &'info mut VisitedMarker<'_, VisitedInfo>, prog_args: Rc, env: &HashMap, Rc>, body: Rc, only_inline: bool, ) -> Result, CompileErr> { + let mut result = None; + + loop { + match result { + None => { + result = Some(self.shrink_bodyform_visited_main( + context, + visited, + prog_args.clone(), + env, + body.clone(), + only_inline, + )?); + } + Some(EvalResult::Body(b)) => { + return Ok(b.clone()); + } + /* + Some(EvalResult::MoreProcessing(p)) => { + result = p.run(); + } + */ + } + } + } + + // A frontend language evaluator and minifier + fn shrink_bodyform_visited_main( + &self, + context: &mut BasicCompileContext, + visited_: &'info mut VisitedMarker<'_, VisitedInfo>, + prog_args: Rc, + env: &HashMap, Rc>, + body: Rc, + only_inline: bool, + ) -> Result { let mut visited = VisitedMarker::again(body.loc(), visited_)?; match body.borrow() { BodyForm::Let(LetFormKind::Parallel, letdata) => { if eval_dont_expand_let(&letdata.inline_hint) && only_inline { - return Ok(body.clone()); + return Ok(EvalResult::Body(body.clone())); } let updated_bindings = update_parallel_bindings(env, &letdata.bindings); - self.shrink_bodyform_visited( + self.shrink_bodyform_visited_main( context, &mut visited, prog_args, @@ -1330,11 +1373,11 @@ impl<'info> Evaluator { } BodyForm::Let(LetFormKind::Sequential, letdata) => { if eval_dont_expand_let(&letdata.inline_hint) && only_inline { - return Ok(body.clone()); + return Ok(EvalResult::Body(body.clone())); } if letdata.bindings.is_empty() { - self.shrink_bodyform_visited( + self.shrink_bodyform_visited_main( context, &mut visited, prog_args, @@ -1349,7 +1392,7 @@ impl<'info> Evaluator { letdata.bindings.iter().skip(1).cloned().collect(); let updated_bindings = update_parallel_bindings(env, &first_binding_as_list); - self.shrink_bodyform_visited( + self.shrink_bodyform_visited_main( context, &mut visited, prog_args, @@ -1367,10 +1410,10 @@ impl<'info> Evaluator { } BodyForm::Let(LetFormKind::Assign, letdata) => { if eval_dont_expand_let(&letdata.inline_hint) && only_inline { - return Ok(body.clone()); + return Ok(EvalResult::Body(body.clone())); } - self.shrink_bodyform_visited( + self.shrink_bodyform_visited_main( context, &mut visited, prog_args, @@ -1379,11 +1422,11 @@ impl<'info> Evaluator { only_inline, ) } - BodyForm::Quoted(_) => Ok(body.clone()), + BodyForm::Quoted(_) => Ok(EvalResult::Body(body.clone())), BodyForm::Value(SExp::Atom(l, name)) => { if name == &"@".as_bytes().to_vec() { let literal_args = synthesize_args(prog_args.clone(), env)?; - self.shrink_bodyform_visited( + self.shrink_bodyform_visited_main( context, &mut visited, prog_args, @@ -1392,7 +1435,7 @@ impl<'info> Evaluator { only_inline, ) } else if let Some(function) = self.get_function(name) { - self.shrink_bodyform_visited( + self.shrink_bodyform_visited_main( context, &mut visited, prog_args, @@ -1404,9 +1447,9 @@ impl<'info> Evaluator { env.get(name) .map(|x| { if reflex_capture(name, x.clone()) { - Ok(x.clone()) + Ok(EvalResult::Body(x.clone())) } else { - self.shrink_bodyform_visited( + self.shrink_bodyform_visited_main( context, &mut visited, prog_args.clone(), @@ -1419,7 +1462,7 @@ impl<'info> Evaluator { .unwrap_or_else(|| { self.get_constant(name) .map(|x| { - self.shrink_bodyform_visited( + self.shrink_bodyform_visited_main( context, &mut visited, prog_args.clone(), @@ -1429,15 +1472,15 @@ impl<'info> Evaluator { ) }) .unwrap_or_else(|| { - Ok(Rc::new(BodyForm::Value(SExp::Atom( + Ok(EvalResult::Body(Rc::new(BodyForm::Value(SExp::Atom( l.clone(), name.clone(), - )))) + ))))) }) }) } } - BodyForm::Value(v) => Ok(Rc::new(BodyForm::Quoted(v.clone()))), + BodyForm::Value(v) => Ok(EvalResult::Body(Rc::new(BodyForm::Quoted(v.clone())))), BodyForm::Call(l, parts, tail) => { if parts.is_empty() { return Err(CompileErr( @@ -1465,7 +1508,7 @@ impl<'info> Evaluator { &arguments_to_convert, env, only_inline, - ), + ).map(|r| EvalResult::Body(r)), BodyForm::Value(SExp::Integer(_call_loc, call_int)) => self.handle_invoke( context, &mut visited, @@ -1480,7 +1523,7 @@ impl<'info> Evaluator { &arguments_to_convert, env, only_inline, - ), + ).map(|r| EvalResult::Body(r)), _ => Err(CompileErr( l.clone(), format!("Don't know how to call {}", head_expr.to_sexp()), @@ -1494,7 +1537,7 @@ impl<'info> Evaluator { let mut context_wrapper = CompileContextWrapper::new(self.runner.clone(), &mut symbols, optimizer); let code = codegen(context_wrapper.context(), self.opts.clone(), program)?; - Ok(Rc::new(BodyForm::Quoted(code))) + Ok(EvalResult::Body(Rc::new(BodyForm::Quoted(code)))) } BodyForm::Lambda(ldata) => self.enrich_lambda_site_info( context, @@ -1503,7 +1546,7 @@ impl<'info> Evaluator { env, ldata, only_inline, - ), + ).map(|r| EvalResult::Body(r)), } } From 0c5054c367a33c1348515b0d012ec9dfb9af2ed3 Mon Sep 17 00:00:00 2001 From: arty Date: Wed, 29 Oct 2025 12:13:53 -0700 Subject: [PATCH 02/10] Rc hashmap Co-authored-by: arty --- src/classic/bins/shrink.rs | 2 +- src/compiler/codegen.rs | 2 +- src/compiler/evaluate.rs | 65 ++++++++++++++++++---------------- src/compiler/optimize/mod.rs | 4 +-- src/compiler/repl.rs | 2 +- src/compiler/usecheck.rs | 2 +- src/tests/compiler/evaluate.rs | 2 +- 7 files changed, 41 insertions(+), 38 deletions(-) diff --git a/src/classic/bins/shrink.rs b/src/classic/bins/shrink.rs index 29efa7d0c..13a7507bf 100644 --- a/src/classic/bins/shrink.rs +++ b/src/classic/bins/shrink.rs @@ -45,7 +45,7 @@ fn main() { e.shrink_bodyform( &mut context, program.args.clone(), - &HashMap::new(), + Rc::new(HashMap::new()), program.exp, false, Some(EVAL_STACK_LIMIT), diff --git a/src/compiler/codegen.rs b/src/compiler/codegen.rs index c12c250d9..599fd12a8 100644 --- a/src/compiler/codegen.rs +++ b/src/compiler/codegen.rs @@ -2049,7 +2049,7 @@ fn generate_complex_constant_body( let constant_result = evaluator.shrink_bodyform( context, Rc::new(SExp::Nil(defc.loc.clone())), - &HashMap::new(), + Rc::new(HashMap::new()), defc.body.clone(), false, Some(EVAL_STACK_LIMIT), diff --git a/src/compiler/evaluate.rs b/src/compiler/evaluate.rs index 612ed7f07..ef3f41653 100644 --- a/src/compiler/evaluate.rs +++ b/src/compiler/evaluate.rs @@ -168,10 +168,11 @@ fn compute_paths_of_destructure( } fn update_parallel_bindings( - bindings: &HashMap, Rc>, + bindings: Rc, Rc>>, have_bindings: &[Rc], ) -> HashMap, Rc> { - let mut new_bindings = bindings.clone(); + let new_bindings_ref: &HashMap, Rc> = bindings.borrow(); + let mut new_bindings = new_bindings_ref.clone(); for b in have_bindings.iter() { match &b.pattern { BindingPattern::Name(name) => { @@ -448,7 +449,7 @@ pub fn second_of_alist(lst: Rc) -> Result, CompileErr> { fn synthesize_args( template: Rc, - env: &HashMap, Rc>, + env: Rc, Rc>>, ) -> Result, CompileErr> { match template.borrow() { SExp::Atom(_, name) => env.get(name).map(|x| Ok(x.clone())).unwrap_or_else(|| { @@ -465,7 +466,7 @@ fn synthesize_args( l.clone(), vec![ Rc::new(BodyForm::Value(SExp::atom_from_string(template.loc(), "c"))), - synthesize_args(f.clone(), env)?, + synthesize_args(f.clone(), env.clone())?, synthesize_args(r.clone(), env)?, ], None, @@ -682,10 +683,10 @@ pub fn eval_dont_expand_let(inline_hint: &Option) -> bool { matches!(inline_hint, Some(LetFormInlineHint::NonInline(_))) } -pub fn filter_capture_args(args: Rc, name_map: &HashMap, Rc>) -> Rc { +pub fn filter_capture_args(args: Rc, name_map: Rc, Rc>>) -> Rc { match args.borrow() { SExp::Cons(l, a, b) => { - let a_filtered = filter_capture_args(a.clone(), name_map); + let a_filtered = filter_capture_args(a.clone(), name_map.clone()); let b_filtered = filter_capture_args(b.clone(), name_map); if !truthy(a_filtered.clone()) && !truthy(b_filtered.clone()) { return Rc::new(SExp::Nil(l.clone())); @@ -740,7 +741,7 @@ impl<'info> Evaluator { program: Rc, prog_args: Rc, arguments_to_convert: &[Rc], - env: &HashMap, Rc>, + env: Rc, Rc>>, ) -> Result, CompileErr> { // Pass the SExp representation of the expressions into // the macro after forming an argument sexp and then @@ -792,7 +793,7 @@ impl<'info> Evaluator { context: &mut BasicCompileContext, visited_: &'info mut VisitedMarker<'_, VisitedInfo>, prog_args: Rc, - env: &HashMap, Rc>, + env: Rc, Rc>>, parts: &[Rc], only_inline: bool, ) -> Result, CompileErr> { @@ -802,7 +803,7 @@ impl<'info> Evaluator { context, &mut visited, prog_args.clone(), - env, + env.clone(), parts[1].clone(), only_inline, )?; @@ -831,11 +832,12 @@ impl<'info> Evaluator { context: &mut BasicCompileContext, visited: &mut VisitedMarker<'info, VisitedInfo>, prog_args: Rc, - env: &HashMap, Rc>, + env: Rc, Rc>>, lapply: &LambdaApply, only_inline: bool, ) -> Result, CompileErr> { - let mut lambda_env = env.clone(); + let lambda_env_ref: &HashMap, Rc> = env.borrow(); + let mut lambda_env = lambda_env_ref.clone(); // Finish eta-expansion. @@ -868,7 +870,7 @@ impl<'info> Evaluator { context, visited, lapply.lambda.args.clone(), - &lambda_env, + Rc::new(lambda_env), lapply.body.clone(), only_inline, ) @@ -882,7 +884,7 @@ impl<'info> Evaluator { call: &CallSpec, prog_args: Rc, arguments_to_convert: &[Rc], - env: &HashMap, Rc>, + env: Rc, Rc>>, only_inline: bool, ) -> Result, CompileErr> { let mut all_primitive = true; @@ -929,7 +931,7 @@ impl<'info> Evaluator { context, &mut visited, prog_args.clone(), - env, + env.clone(), arguments_to_convert[i].clone(), only_inline, )?; @@ -968,7 +970,7 @@ impl<'info> Evaluator { context, &mut visited, prog_args.clone(), - env, + env.clone(), &target_vec, only_inline, )? { @@ -1017,7 +1019,7 @@ impl<'info> Evaluator { context, visited, Rc::new(SExp::Nil(run_program.loc())), - &bindings, + Rc::new(bindings), program, false, )?; @@ -1127,7 +1129,7 @@ impl<'info> Evaluator { call: &CallSpec, prog_args: Rc, arguments_to_convert: &[Rc], - env: &HashMap, Rc>, + env: Rc, Rc>>, only_inline: bool, ) -> Result, CompileErr> { let helper = select_helper(&self.helpers, call.name); @@ -1160,7 +1162,7 @@ impl<'info> Evaluator { context, visited, prog_args.clone(), - env, + env.clone(), t.clone(), only_inline, )?) @@ -1183,7 +1185,7 @@ impl<'info> Evaluator { context, visited, prog_args.clone(), - env, + env.clone(), kv.1.clone(), only_inline, )?; @@ -1195,7 +1197,7 @@ impl<'info> Evaluator { context, visited, defun.args.clone(), - &argument_captures, + Rc::new(argument_captures), defun.body, only_inline, ) @@ -1219,7 +1221,7 @@ impl<'info> Evaluator { context: &mut BasicCompileContext, visited: &'info mut VisitedMarker<'_, VisitedInfo>, prog_args: Rc, - env: &HashMap, Rc>, + env: Rc, Rc>>, ldata: &LambdaData, only_inline: bool, ) -> Result, CompileErr> { @@ -1264,17 +1266,18 @@ impl<'info> Evaluator { )); // Eliminate the captures via beta substituion. + let interpretable_rc = Rc::new(interpretable_captures); let simplified_body = self.shrink_bodyform_visited( context, visited, combined_args.clone(), - &interpretable_captures, + interpretable_rc.clone(), ldata.body.clone(), only_inline, )?; let new_capture_args = - filter_capture_args(ldata.capture_args.clone(), &interpretable_captures); + filter_capture_args(ldata.capture_args.clone(), interpretable_rc); Ok(Rc::new(BodyForm::Lambda(Box::new(LambdaData { args: ldata.args.clone(), capture_args: new_capture_args, @@ -1314,7 +1317,7 @@ impl<'info> Evaluator { context: &mut BasicCompileContext, visited: &'info mut VisitedMarker<'_, VisitedInfo>, prog_args: Rc, - env: &HashMap, Rc>, + env: Rc, Rc>>, body: Rc, only_inline: bool, ) -> Result, CompileErr> { @@ -1327,7 +1330,7 @@ impl<'info> Evaluator { context, visited, prog_args.clone(), - env, + env.clone(), body.clone(), only_inline, )?); @@ -1350,7 +1353,7 @@ impl<'info> Evaluator { context: &mut BasicCompileContext, visited_: &'info mut VisitedMarker<'_, VisitedInfo>, prog_args: Rc, - env: &HashMap, Rc>, + env: Rc, Rc>>, body: Rc, only_inline: bool, ) -> Result { @@ -1366,7 +1369,7 @@ impl<'info> Evaluator { context, &mut visited, prog_args, - &updated_bindings, + Rc::new(updated_bindings), letdata.body.clone(), only_inline, ) @@ -1396,7 +1399,7 @@ impl<'info> Evaluator { context, &mut visited, prog_args, - &updated_bindings, + Rc::new(updated_bindings), Rc::new(BodyForm::Let( LetFormKind::Sequential, Box::new(LetData { @@ -1425,7 +1428,7 @@ impl<'info> Evaluator { BodyForm::Quoted(_) => Ok(EvalResult::Body(body.clone())), BodyForm::Value(SExp::Atom(l, name)) => { if name == &"@".as_bytes().to_vec() { - let literal_args = synthesize_args(prog_args.clone(), env)?; + let literal_args = synthesize_args(prog_args.clone(), env.clone())?; self.shrink_bodyform_visited_main( context, &mut visited, @@ -1453,7 +1456,7 @@ impl<'info> Evaluator { context, &mut visited, prog_args.clone(), - env, + env.clone(), x.clone(), only_inline, ) @@ -1569,7 +1572,7 @@ impl<'info> Evaluator { &self, context: &mut BasicCompileContext, prog_args: Rc, - env: &HashMap, Rc>, + env: Rc, Rc>>, body: Rc, only_inline: bool, stack_limit: Option, diff --git a/src/compiler/optimize/mod.rs b/src/compiler/optimize/mod.rs index b2543f80f..7824326df 100644 --- a/src/compiler/optimize/mod.rs +++ b/src/compiler/optimize/mod.rs @@ -650,7 +650,7 @@ fn fe_opt( let body_rc = evaluator.shrink_bodyform( context, defun.args.clone(), - &env, + Rc::new(env), defun.body.clone(), true, Some(EVAL_STACK_LIMIT), @@ -674,7 +674,7 @@ fn fe_opt( let shrunk = new_evaluator.shrink_bodyform( context, Rc::new(SExp::Nil(compileform.args.loc())), - &HashMap::new(), + Rc::new(HashMap::new()), compileform.exp.clone(), true, Some(EVAL_STACK_LIMIT), diff --git a/src/compiler/repl.rs b/src/compiler/repl.rs index c2dfab982..e51733079 100644 --- a/src/compiler/repl.rs +++ b/src/compiler/repl.rs @@ -207,7 +207,7 @@ impl Repl { self.evaluator.shrink_bodyform( context, program.args.clone(), - &HashMap::new(), + Rc::new(HashMap::new()), program.exp, false, self.stack_limit, diff --git a/src/compiler/usecheck.rs b/src/compiler/usecheck.rs index 3c7ca0553..6bda0a29a 100644 --- a/src/compiler/usecheck.rs +++ b/src/compiler/usecheck.rs @@ -106,7 +106,7 @@ pub fn check_parameters_used_compileform( let result = e.shrink_bodyform( &mut context, program.args.clone(), - &env, + Rc::new(env), program.exp.clone(), false, Some(EVAL_STACK_LIMIT), diff --git a/src/tests/compiler/evaluate.rs b/src/tests/compiler/evaluate.rs index b7231f8da..242e5fa20 100644 --- a/src/tests/compiler/evaluate.rs +++ b/src/tests/compiler/evaluate.rs @@ -39,7 +39,7 @@ fn shrink_expr_from_string(s: String) -> Result { return e.shrink_bodyform( &mut context, program.args.clone(), - &HashMap::new(), + Rc::new(HashMap::new()), program.exp.clone(), false, Some(EVAL_STACK_LIMIT), From c328238e4374908086ab9278b168a3fb74831ca7 Mon Sep 17 00:00:00 2001 From: arty Date: Wed, 29 Oct 2025 14:22:23 -0700 Subject: [PATCH 03/10] Convert some repeated args to a single package Co-authored-by: arty --- src/compiler/evaluate.rs | 297 ++++++++++++++++++++++----------------- 1 file changed, 170 insertions(+), 127 deletions(-) diff --git a/src/compiler/evaluate.rs b/src/compiler/evaluate.rs index ef3f41653..2f1014552 100644 --- a/src/compiler/evaluate.rs +++ b/src/compiler/evaluate.rs @@ -27,7 +27,7 @@ use crate::compiler::CompileContextWrapper; use crate::util::{number_from_u8, u8_from_number, Number}; const PRIM_RUN_LIMIT: usize = 1000000; -pub const EVAL_STACK_LIMIT: usize = 200; +pub const EVAL_STACK_LIMIT: usize = 150; pub trait Process { fn run(&self) -> Result; @@ -37,6 +37,14 @@ pub enum EvalResult { Body(Rc), } +#[derive(Clone)] +pub struct EvalData { + prog_args: Rc, + env: Rc, Rc>>, + body: Rc, + only_inline: bool +} + // Stack depth checker. #[derive(Clone, Debug, Default)] pub struct VisitedInfo { @@ -771,10 +779,12 @@ impl<'info> Evaluator { self.shrink_bodyform_visited( context, visited, - prog_args.clone(), - env, - program.exp, - false, + Rc::new(EvalData { + prog_args: prog_args.clone(), + env, + body: program.exp, + only_inline: false, + }), ) }) } else { @@ -802,18 +812,22 @@ impl<'info> Evaluator { let evaluated_prog = self.shrink_bodyform_visited( context, &mut visited, - prog_args.clone(), - env.clone(), - parts[1].clone(), - only_inline, + Rc::new(EvalData { + prog_args: prog_args.clone(), + env: env.clone(), + body: parts[1].clone(), + only_inline: only_inline, + }) )?; let evaluated_env = self.shrink_bodyform_visited( context, &mut visited, - prog_args, - env, - parts[2].clone(), - only_inline, + Rc::new(EvalData { + prog_args, + env, + body: parts[2].clone(), + only_inline, + }) )?; if let BodyForm::Lambda(ldata) = evaluated_prog.borrow() { return Ok(Some(LambdaApply { @@ -850,10 +864,12 @@ impl<'info> Evaluator { let reified_captures = self.shrink_bodyform_visited( context, visited, - prog_args, - env, - lapply.lambda.captures.clone(), - only_inline, + Rc::new(EvalData { + prog_args, + env, + body: lapply.lambda.captures.clone(), + only_inline, + }) )?; let formed_caps = ArgInputs::Whole(reified_captures); create_argument_captures( @@ -869,10 +885,12 @@ impl<'info> Evaluator { self.shrink_bodyform_visited( context, visited, - lapply.lambda.args.clone(), - Rc::new(lambda_env), - lapply.body.clone(), - only_inline, + Rc::new(EvalData { + prog_args: lapply.lambda.args.clone(), + env: Rc::new(lambda_env), + body: lapply.body.clone(), + only_inline, + }) ) } @@ -930,10 +948,12 @@ impl<'info> Evaluator { let shrunk = self.shrink_bodyform_visited( context, &mut visited, - prog_args.clone(), - env.clone(), - arguments_to_convert[i].clone(), - only_inline, + Rc::new(EvalData { + prog_args: prog_args.clone(), + env: env.clone(), + body: arguments_to_convert[i].clone(), + only_inline, + }) )?; target_vec[i + 1] = shrunk.clone(); @@ -1018,10 +1038,12 @@ impl<'info> Evaluator { let apply_result = self.shrink_bodyform_visited( context, visited, - Rc::new(SExp::Nil(run_program.loc())), - Rc::new(bindings), - program, - false, + Rc::new(EvalData { + prog_args: Rc::new(SExp::Nil(run_program.loc())), + env: Rc::new(bindings), + body: program, + only_inline: false, + }) )?; self.chase_apply(context, visited, apply_result) } @@ -1161,10 +1183,12 @@ impl<'info> Evaluator { Some(self.shrink_bodyform_visited( context, visited, - prog_args.clone(), - env.clone(), - t.clone(), - only_inline, + Rc::new(EvalData { + prog_args: prog_args.clone(), + env: env.clone(), + body: t.clone(), + only_inline, + }) )?) } else { None @@ -1184,10 +1208,12 @@ impl<'info> Evaluator { let shrunk = self.shrink_bodyform_visited( context, visited, - prog_args.clone(), - env.clone(), - kv.1.clone(), - only_inline, + Rc::new(EvalData { + prog_args: prog_args.clone(), + env: env.clone(), + body: kv.1.clone(), + only_inline, + }) )?; argument_captures.insert(kv.0.clone(), shrunk.clone()); @@ -1196,10 +1222,12 @@ impl<'info> Evaluator { self.shrink_bodyform_visited( context, visited, - defun.args.clone(), - Rc::new(argument_captures), - defun.body, - only_inline, + Rc::new(EvalData { + prog_args: defun.args.clone(), + env: Rc::new(argument_captures), + body: defun.body, + only_inline, + }) ) } _ => self @@ -1233,10 +1261,12 @@ impl<'info> Evaluator { let new_captures = self.shrink_bodyform_visited( context, visited, - prog_args.clone(), - env, - ldata.captures.clone(), - only_inline, + Rc::new(EvalData { + prog_args: prog_args.clone(), + env, + body: ldata.captures.clone(), + only_inline, + }) )?; // Break up and make binding map. @@ -1270,10 +1300,12 @@ impl<'info> Evaluator { let simplified_body = self.shrink_bodyform_visited( context, visited, - combined_args.clone(), - interpretable_rc.clone(), - ldata.body.clone(), - only_inline, + Rc::new(EvalData { + prog_args: combined_args.clone(), + env: interpretable_rc.clone(), + body: ldata.body.clone(), + only_inline, + }) )?; let new_capture_args = @@ -1316,23 +1348,19 @@ impl<'info> Evaluator { &self, context: &mut BasicCompileContext, visited: &'info mut VisitedMarker<'_, VisitedInfo>, - prog_args: Rc, - env: Rc, Rc>>, - body: Rc, - only_inline: bool, + eval_data: Rc, ) -> Result, CompileErr> { let mut result = None; + let mut eval_stack = vec![eval_data]; loop { + let eval_data = eval_stack[eval_stack.len()-1].clone(); match result { None => { result = Some(self.shrink_bodyform_visited_main( context, visited, - prog_args.clone(), - env.clone(), - body.clone(), - only_inline, + eval_data.clone(), )?); } Some(EvalResult::Body(b)) => { @@ -1352,41 +1380,42 @@ impl<'info> Evaluator { &self, context: &mut BasicCompileContext, visited_: &'info mut VisitedMarker<'_, VisitedInfo>, - prog_args: Rc, - env: Rc, Rc>>, - body: Rc, - only_inline: bool, + eval_data: Rc, ) -> Result { - let mut visited = VisitedMarker::again(body.loc(), visited_)?; - match body.borrow() { + let mut visited = VisitedMarker::again(eval_data.body.loc(), visited_)?; + match eval_data.body.borrow() { BodyForm::Let(LetFormKind::Parallel, letdata) => { - if eval_dont_expand_let(&letdata.inline_hint) && only_inline { - return Ok(EvalResult::Body(body.clone())); + if eval_dont_expand_let(&letdata.inline_hint) && eval_data.only_inline { + return Ok(EvalResult::Body(eval_data.body.clone())); } - let updated_bindings = update_parallel_bindings(env, &letdata.bindings); + let updated_bindings = update_parallel_bindings(eval_data.env.clone(), &letdata.bindings); self.shrink_bodyform_visited_main( context, &mut visited, - prog_args, - Rc::new(updated_bindings), - letdata.body.clone(), - only_inline, + Rc::new(EvalData { + prog_args: eval_data.prog_args.clone(), + env: Rc::new(updated_bindings), + body: letdata.body.clone(), + only_inline: eval_data.only_inline, + }) ) } BodyForm::Let(LetFormKind::Sequential, letdata) => { - if eval_dont_expand_let(&letdata.inline_hint) && only_inline { - return Ok(EvalResult::Body(body.clone())); + if eval_dont_expand_let(&letdata.inline_hint) && eval_data.only_inline { + return Ok(EvalResult::Body(eval_data.body.clone())); } if letdata.bindings.is_empty() { self.shrink_bodyform_visited_main( context, &mut visited, - prog_args, - env, - letdata.body.clone(), - only_inline, + Rc::new(EvalData { + prog_args: eval_data.prog_args.clone(), + env: eval_data.env.clone(), + body: letdata.body.clone(), + only_inline: eval_data.only_inline, + }) ) } else { let first_binding_as_list: Vec> = @@ -1394,60 +1423,68 @@ impl<'info> Evaluator { let rest_of_bindings: Vec> = letdata.bindings.iter().skip(1).cloned().collect(); - let updated_bindings = update_parallel_bindings(env, &first_binding_as_list); + let updated_bindings = update_parallel_bindings(eval_data.env.clone(), &first_binding_as_list); self.shrink_bodyform_visited_main( context, &mut visited, - prog_args, - Rc::new(updated_bindings), - Rc::new(BodyForm::Let( - LetFormKind::Sequential, - Box::new(LetData { - bindings: rest_of_bindings, - ..*letdata.clone() - }), - )), - only_inline, + Rc::new(EvalData { + prog_args: eval_data.prog_args.clone(), + env: Rc::new(updated_bindings), + body: Rc::new(BodyForm::Let( + LetFormKind::Sequential, + Box::new(LetData { + bindings: rest_of_bindings, + ..*letdata.clone() + }), + )), + only_inline: eval_data.only_inline, + }) ) } } BodyForm::Let(LetFormKind::Assign, letdata) => { - if eval_dont_expand_let(&letdata.inline_hint) && only_inline { - return Ok(EvalResult::Body(body.clone())); + if eval_dont_expand_let(&letdata.inline_hint) && eval_data.only_inline { + return Ok(EvalResult::Body(eval_data.body.clone())); } self.shrink_bodyform_visited_main( context, &mut visited, - prog_args, - env, - Rc::new(hoist_assign_form(letdata)?), - only_inline, + Rc::new(EvalData { + prog_args: eval_data.prog_args.clone(), + env: eval_data.env.clone(), + body: Rc::new(hoist_assign_form(letdata)?), + only_inline: eval_data.only_inline, + }) ) } - BodyForm::Quoted(_) => Ok(EvalResult::Body(body.clone())), + BodyForm::Quoted(_) => Ok(EvalResult::Body(eval_data.body.clone())), BodyForm::Value(SExp::Atom(l, name)) => { if name == &"@".as_bytes().to_vec() { - let literal_args = synthesize_args(prog_args.clone(), env.clone())?; + let literal_args = synthesize_args(eval_data.prog_args.clone(), eval_data.env.clone())?; self.shrink_bodyform_visited_main( context, &mut visited, - prog_args, - env, - literal_args, - only_inline, + Rc::new(EvalData { + prog_args: eval_data.prog_args.clone(), + env: eval_data.env.clone(), + body: literal_args, + only_inline: eval_data.only_inline, + }) ) } else if let Some(function) = self.get_function(name) { self.shrink_bodyform_visited_main( context, &mut visited, - prog_args, - env, - self.create_mod_for_fun(l, function.borrow()), - only_inline, + Rc::new(EvalData { + prog_args: eval_data.prog_args.clone(), + env: eval_data.env.clone(), + body: self.create_mod_for_fun(l, function.borrow()), + only_inline: eval_data.only_inline, + }) ) } else { - env.get(name) + eval_data.env.get(name) .map(|x| { if reflex_capture(name, x.clone()) { Ok(EvalResult::Body(x.clone())) @@ -1455,10 +1492,12 @@ impl<'info> Evaluator { self.shrink_bodyform_visited_main( context, &mut visited, - prog_args.clone(), - env.clone(), - x.clone(), - only_inline, + Rc::new(EvalData { + prog_args: eval_data.prog_args.clone(), + env: eval_data.env.clone(), + body: x.clone(), + only_inline: eval_data.only_inline, + }) ) } }) @@ -1468,10 +1507,12 @@ impl<'info> Evaluator { self.shrink_bodyform_visited_main( context, &mut visited, - prog_args.clone(), - env, - x, - only_inline, + Rc::new(EvalData { + prog_args: eval_data.prog_args.clone(), + env: eval_data.env.clone(), + body: x, + only_inline: eval_data.only_inline, + }) ) }) .unwrap_or_else(|| { @@ -1504,13 +1545,13 @@ impl<'info> Evaluator { loc: l.clone(), name: call_name, args: parts, - original: body.clone(), + original: eval_data.body.clone(), tail: tail.clone(), }, - prog_args, + eval_data.prog_args.clone(), &arguments_to_convert, - env, - only_inline, + eval_data.env.clone(), + eval_data.only_inline, ).map(|r| EvalResult::Body(r)), BodyForm::Value(SExp::Integer(_call_loc, call_int)) => self.handle_invoke( context, @@ -1519,13 +1560,13 @@ impl<'info> Evaluator { loc: l.clone(), name: &u8_from_number(call_int.clone()), args: parts, - original: body.clone(), + original: eval_data.body.clone(), tail: None, }, - prog_args, + eval_data.prog_args.clone(), &arguments_to_convert, - env, - only_inline, + eval_data.env.clone(), + eval_data.only_inline, ).map(|r| EvalResult::Body(r)), _ => Err(CompileErr( l.clone(), @@ -1545,10 +1586,10 @@ impl<'info> Evaluator { BodyForm::Lambda(ldata) => self.enrich_lambda_site_info( context, &mut visited, - prog_args, - env, + eval_data.prog_args.clone(), + eval_data.env.clone(), ldata, - only_inline, + eval_data.only_inline, ).map(|r| EvalResult::Body(r)), } } @@ -1585,10 +1626,12 @@ impl<'info> Evaluator { self.shrink_bodyform_visited( context, &mut visited_marker, - prog_args, - env, - body, - only_inline, + Rc::new(EvalData { + prog_args, + env, + body, + only_inline, + }) ) } From a6a3bcbe5cbaf376e23489b6afbaffb1c67bdc30 Mon Sep 17 00:00:00 2001 From: arty Date: Thu, 30 Oct 2025 02:34:03 -0700 Subject: [PATCH 04/10] Primitive evaluation fixed Co-authored-by: arty --- src/compiler/evaluate.rs | 273 +++++++++++++++++++++++++-------------- 1 file changed, 179 insertions(+), 94 deletions(-) diff --git a/src/compiler/evaluate.rs b/src/compiler/evaluate.rs index 2f1014552..c548e8841 100644 --- a/src/compiler/evaluate.rs +++ b/src/compiler/evaluate.rs @@ -1,4 +1,4 @@ -use std::borrow::Borrow; +use std::borrow::{Borrow, BorrowMut}; use std::collections::{HashMap, HashSet}; use std::rc::Rc; @@ -33,11 +33,26 @@ pub trait Process { fn run(&self) -> Result; } +#[derive(Debug)] +pub struct Call { + loc: Srcloc, + name: Vec, + tail: Option>, + processed_tail: Option>, + args: Vec>, + processed_args: Vec>, + original: Rc, +} + +#[derive(Debug)] pub enum EvalResult { Body(Rc), + Process(Rc), + Call(Call, Rc), + Invoke(Call, Rc), } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct EvalData { prog_args: Rc, env: Rc, Rc>>, @@ -45,6 +60,16 @@ pub struct EvalData { only_inline: bool } +impl EvalData { + fn with_body_or_env(&self, new_body: Option>, new_env: Option, Rc>>>) -> EvalData { + EvalData { + env: new_env.unwrap_or_else(|| self.env.clone()), + body: new_body.unwrap_or_else(|| self.body.clone()), + .. self.clone() + } + } +} + // Stack depth checker. #[derive(Clone, Debug, Default)] pub struct VisitedInfo { @@ -906,7 +931,7 @@ impl<'info> Evaluator { only_inline: bool, ) -> Result, CompileErr> { let mut all_primitive = true; - let mut target_vec: Vec> = call.args.to_owned(); + let mut target_vec: Vec> = call.args.to_vec(); let mut visited = VisitedMarker::again(call.loc.clone(), visited_)?; if call.name == "@".as_bytes() { @@ -943,20 +968,21 @@ impl<'info> Evaluator { // Reduce all arguments. let mut converted_args = SExp::Nil(call.loc.clone()); - for i_reverse in 0..arguments_to_convert.len() { - let i = arguments_to_convert.len() - i_reverse - 1; + eprintln!("args to convert {arguments_to_convert:?}"); + for (i, element) in arguments_to_convert.iter().enumerate().skip(1).rev() { let shrunk = self.shrink_bodyform_visited( context, &mut visited, Rc::new(EvalData { prog_args: prog_args.clone(), env: env.clone(), - body: arguments_to_convert[i].clone(), + body: element.clone(), only_inline, }) )?; - target_vec[i + 1] = shrunk.clone(); + eprintln!("shrunk {shrunk:?}"); + target_vec[i] = shrunk.clone(); if !arg_inputs_primitive(Rc::new(ArgInputs::Whole(shrunk.clone()))) { all_primitive = false; @@ -1150,10 +1176,12 @@ impl<'info> Evaluator { visited: &'_ mut VisitedMarker<'info, VisitedInfo>, call: &CallSpec, prog_args: Rc, + arguments: &[Rc], arguments_to_convert: &[Rc], + translated_tail: Option>, env: Rc, Rc>>, only_inline: bool, - ) -> Result, CompileErr> { + ) -> Result { let helper = select_helper(&self.helpers, call.name); match helper { Some(HelperForm::Defmacro(mac)) => { @@ -1172,63 +1200,26 @@ impl<'info> Evaluator { prog_args, arguments_to_convert, env, - ) + ).map(|r| EvalResult::Body(r)) } Some(HelperForm::Defun(inline, defun)) => { if !inline && only_inline { - return Ok(call.original.clone()); + return Ok(EvalResult::Body(call.original.clone())); } - let translated_tail = if let Some(t) = call.tail.as_ref() { - Some(self.shrink_bodyform_visited( - context, - visited, - Rc::new(EvalData { - prog_args: prog_args.clone(), - env: env.clone(), - body: t.clone(), - only_inline, - }) - )?) - } else { - None - }; - - let argument_captures_untranslated = build_argument_captures( + let argument_captures = build_argument_captures( &call.loc.clone(), arguments_to_convert, translated_tail.clone(), defun.args.clone(), )?; - let mut argument_captures = HashMap::new(); - // Do this to protect against misalignment - // between argument vec and destructuring. - for kv in argument_captures_untranslated.iter() { - let shrunk = self.shrink_bodyform_visited( - context, - visited, - Rc::new(EvalData { - prog_args: prog_args.clone(), - env: env.clone(), - body: kv.1.clone(), - only_inline, - }) - )?; - - argument_captures.insert(kv.0.clone(), shrunk.clone()); - } - - self.shrink_bodyform_visited( - context, - visited, - Rc::new(EvalData { - prog_args: defun.args.clone(), - env: Rc::new(argument_captures), - body: defun.body, - only_inline, - }) - ) + Ok(EvalResult::Process(Rc::new(EvalData { + prog_args: defun.args.clone(), + env: Rc::new(argument_captures), + body: defun.body, + only_inline, + }))) } _ => self .invoke_primitive( @@ -1240,7 +1231,8 @@ impl<'info> Evaluator { env, only_inline, ) - .and_then(|res| self.chase_apply(context, visited, res)), + .and_then(|res| self.chase_apply(context, visited, res)) + .map(|r| EvalResult::Body(r)), } } @@ -1350,27 +1342,126 @@ impl<'info> Evaluator { visited: &'info mut VisitedMarker<'_, VisitedInfo>, eval_data: Rc, ) -> Result, CompileErr> { - let mut result = None; - let mut eval_stack = vec![eval_data]; + let mut result_stack = vec![EvalResult::Process(eval_data.clone())]; + + let choose_call_target = |call: &Call| -> Option> { + if let Some(t) = call.tail.as_ref() { + if call.processed_tail.is_none() { + return Some(t.clone()); + } + } + + if call.processed_args.len() >= call.args.len() { + return None; + } + + Some(call.args[call.processed_args.len()].clone()) + }; + + let handle_call = |result_stack: &mut Vec, call: Call, eval_data: Rc| -> Result<(), CompileErr> { + if let Some(t) = choose_call_target(&call) { + result_stack.push(EvalResult::Call(call, eval_data.clone())); + result_stack.push(EvalResult::Process( + Rc::new(eval_data.with_body_or_env(Some(t.clone()), None)), + )); + } else { + result_stack.push(EvalResult::Invoke( + call, + eval_data.clone(), + )); + } + + Ok(()) + }; loop { - let eval_data = eval_stack[eval_stack.len()-1].clone(); + let result = result_stack.pop(); + eprintln!("result {result:?} {result_stack:?}"); match result { None => { - result = Some(self.shrink_bodyform_visited_main( - context, - visited, - eval_data.clone(), - )?); + return Err(CompileErr(eval_data.body.loc(), "empty eval stack".to_string())); } Some(EvalResult::Body(b)) => { - return Ok(b.clone()); + match result_stack.pop() { + None => { + return Ok(b.clone()); + } + Some(EvalResult::Body(_)) => { + return Err(CompileErr(b.loc(), "can't collapse concrete value".to_string())); + } + Some(EvalResult::Process(_)) => { + result_stack.push(EvalResult::Body(b)); + } + Some(EvalResult::Call(mut call, eval_data)) => { + let need_tail = call.tail.is_some() && call.processed_tail.is_none(); + let need_arg = call.processed_args.len() < call.args.len(); + if need_tail || need_arg { + if need_tail { + call.processed_tail = Some(b.clone()); + } else if need_arg { + call.processed_args.push(b.clone()); + } + } + + if call.processed_args.len() < call.args.len() { + let new_arg = call.args[call.processed_args.len()].clone(); + result_stack.push(EvalResult::Call(call, eval_data.clone())); + result_stack.push(EvalResult::Process(Rc::new(eval_data.with_body_or_env(Some(new_arg), None)))); + } else { + result_stack.push(EvalResult::Invoke(call, eval_data.clone())); + } + } + Some(EvalResult::Invoke(call, eval_data)) => { + result_stack.push(self.handle_invoke( + allocator, + visited, + &CallSpec { + args: &call.args, + name: &call.name, + loc: call.loc.clone(), + original: call.original.clone(), + tail: call.tail.clone(), + }, + eval_data.prog_args.clone(), + &call.processed_args, + &call.args, + call.processed_tail.clone(), + eval_data.env.clone(), + eval_data.only_inline + )?); + } + } } - /* - Some(EvalResult::MoreProcessing(p)) => { - result = p.run(); + Some(EvalResult::Call(call, eval_data)) => { + handle_call(&mut result_stack, call, eval_data.clone())?; + } + Some(EvalResult::Invoke(call, eval_data)) => { + eprintln!("invoke {:?} {:?}", call.args, call.processed_args); + result_stack.push(self.handle_invoke( + allocator, + visited, + &CallSpec { + loc: call.loc.clone(), + name: &call.name, + args: &call.args, + original: call.original.clone(), + tail: call.tail.clone(), + }, + eval_data.prog_args.clone(), + &call.args, + &call.processed_args, + call.processed_tail.clone(), + eval_data.env.clone(), + eval_data.only_inline, + )?); + } + Some(EvalResult::Process(eval_data)) => { + result_stack.push(self.shrink_bodyform_visited_main( + allocator, + visited, + eval_data.clone() + )?); } - */ } } } @@ -1534,40 +1625,34 @@ impl<'info> Evaluator { } let head_expr = parts[0].clone(); - let arguments_to_convert: Vec> = - parts.iter().skip(1).cloned().collect(); + + eprintln!("call parts {parts:?}"); match head_expr.borrow() { - BodyForm::Value(SExp::Atom(_call_loc, call_name)) => self.handle_invoke( - context, - &mut visited, - &CallSpec { + BodyForm::Value(SExp::Atom(_call_loc, call_name)) => Ok(EvalResult::Call( + Call { loc: l.clone(), - name: call_name, - args: parts, - original: eval_data.body.clone(), + name: call_name.clone(), tail: tail.clone(), + processed_tail: None, + args: parts.clone(), + processed_args: vec![parts[0].clone()], + original: eval_data.body.clone(), }, - eval_data.prog_args.clone(), - &arguments_to_convert, - eval_data.env.clone(), - eval_data.only_inline, - ).map(|r| EvalResult::Body(r)), - BodyForm::Value(SExp::Integer(_call_loc, call_int)) => self.handle_invoke( - context, - &mut visited, - &CallSpec { + eval_data.clone(), + )), + BodyForm::Value(SExp::Integer(_call_loc, call_int)) => Ok(EvalResult::Call( + Call { loc: l.clone(), - name: &u8_from_number(call_int.clone()), - args: parts, - original: eval_data.body.clone(), + name: u8_from_number(call_int.clone()), tail: None, + processed_tail: None, + args: parts.clone(), + processed_args: vec![parts[0].clone()], + original: eval_data.body.clone(), }, - eval_data.prog_args.clone(), - &arguments_to_convert, - eval_data.env.clone(), - eval_data.only_inline, - ).map(|r| EvalResult::Body(r)), + eval_data.clone() + )), _ => Err(CompileErr( l.clone(), format!("Don't know how to call {}", head_expr.to_sexp()), From 07a4499b26f224e00778d0e2b9dc9bf633513502 Mon Sep 17 00:00:00 2001 From: arty Date: Wed, 5 Nov 2025 00:05:45 -0800 Subject: [PATCH 05/10] Simple functions unstacked --- src/compiler/evaluate.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/compiler/evaluate.rs b/src/compiler/evaluate.rs index c548e8841..56c6be932 100644 --- a/src/compiler/evaluate.rs +++ b/src/compiler/evaluate.rs @@ -968,7 +968,6 @@ impl<'info> Evaluator { // Reduce all arguments. let mut converted_args = SExp::Nil(call.loc.clone()); - eprintln!("args to convert {arguments_to_convert:?}"); for (i, element) in arguments_to_convert.iter().enumerate().skip(1).rev() { let shrunk = self.shrink_bodyform_visited( context, @@ -981,7 +980,6 @@ impl<'info> Evaluator { }) )?; - eprintln!("shrunk {shrunk:?}"); target_vec[i] = shrunk.clone(); if !arg_inputs_primitive(Rc::new(ArgInputs::Whole(shrunk.clone()))) { @@ -1209,7 +1207,7 @@ impl<'info> Evaluator { let argument_captures = build_argument_captures( &call.loc.clone(), - arguments_to_convert, + &arguments[1..], translated_tail.clone(), defun.args.clone(), )?; @@ -1376,7 +1374,6 @@ impl<'info> Evaluator { loop { let result = result_stack.pop(); - eprintln!("result {result:?} {result_stack:?}"); match result { None => { return Err(CompileErr(eval_data.body.loc(), "empty eval stack".to_string())); @@ -1436,7 +1433,6 @@ impl<'info> Evaluator { handle_call(&mut result_stack, call, eval_data.clone())?; } Some(EvalResult::Invoke(call, eval_data)) => { - eprintln!("invoke {:?} {:?}", call.args, call.processed_args); result_stack.push(self.handle_invoke( allocator, visited, @@ -1626,8 +1622,6 @@ impl<'info> Evaluator { let head_expr = parts[0].clone(); - eprintln!("call parts {parts:?}"); - match head_expr.borrow() { BodyForm::Value(SExp::Atom(_call_loc, call_name)) => Ok(EvalResult::Call( Call { From 63938a746489d59053395cafc480f29143a48030 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 6 Aug 2026 02:28:37 +0000 Subject: [PATCH 06/10] Remove incomplete evaluator prototype Co-authored-by: arty --- src/classic/bins/shrink.rs | 2 +- src/compiler/codegen.rs | 2 +- src/compiler/evaluate.rs | 576 ++++++++++++--------------------- src/compiler/optimize/mod.rs | 4 +- src/compiler/repl.rs | 2 +- src/compiler/usecheck.rs | 2 +- src/tests/compiler/evaluate.rs | 2 +- 7 files changed, 211 insertions(+), 379 deletions(-) diff --git a/src/classic/bins/shrink.rs b/src/classic/bins/shrink.rs index 13a7507bf..29efa7d0c 100644 --- a/src/classic/bins/shrink.rs +++ b/src/classic/bins/shrink.rs @@ -45,7 +45,7 @@ fn main() { e.shrink_bodyform( &mut context, program.args.clone(), - Rc::new(HashMap::new()), + &HashMap::new(), program.exp, false, Some(EVAL_STACK_LIMIT), diff --git a/src/compiler/codegen.rs b/src/compiler/codegen.rs index 599fd12a8..c12c250d9 100644 --- a/src/compiler/codegen.rs +++ b/src/compiler/codegen.rs @@ -2049,7 +2049,7 @@ fn generate_complex_constant_body( let constant_result = evaluator.shrink_bodyform( context, Rc::new(SExp::Nil(defc.loc.clone())), - Rc::new(HashMap::new()), + &HashMap::new(), defc.body.clone(), false, Some(EVAL_STACK_LIMIT), diff --git a/src/compiler/evaluate.rs b/src/compiler/evaluate.rs index 56c6be932..2dd062ca5 100644 --- a/src/compiler/evaluate.rs +++ b/src/compiler/evaluate.rs @@ -1,4 +1,4 @@ -use std::borrow::{Borrow, BorrowMut}; +use std::borrow::Borrow; use std::collections::{HashMap, HashSet}; use std::rc::Rc; @@ -27,48 +27,7 @@ use crate::compiler::CompileContextWrapper; use crate::util::{number_from_u8, u8_from_number, Number}; const PRIM_RUN_LIMIT: usize = 1000000; -pub const EVAL_STACK_LIMIT: usize = 150; - -pub trait Process { - fn run(&self) -> Result; -} - -#[derive(Debug)] -pub struct Call { - loc: Srcloc, - name: Vec, - tail: Option>, - processed_tail: Option>, - args: Vec>, - processed_args: Vec>, - original: Rc, -} - -#[derive(Debug)] -pub enum EvalResult { - Body(Rc), - Process(Rc), - Call(Call, Rc), - Invoke(Call, Rc), -} - -#[derive(Clone, Debug)] -pub struct EvalData { - prog_args: Rc, - env: Rc, Rc>>, - body: Rc, - only_inline: bool -} - -impl EvalData { - fn with_body_or_env(&self, new_body: Option>, new_env: Option, Rc>>>) -> EvalData { - EvalData { - env: new_env.unwrap_or_else(|| self.env.clone()), - body: new_body.unwrap_or_else(|| self.body.clone()), - .. self.clone() - } - } -} +pub const EVAL_STACK_LIMIT: usize = 200; // Stack depth checker. #[derive(Clone, Debug, Default)] @@ -201,11 +160,10 @@ fn compute_paths_of_destructure( } fn update_parallel_bindings( - bindings: Rc, Rc>>, + bindings: &HashMap, Rc>, have_bindings: &[Rc], ) -> HashMap, Rc> { - let new_bindings_ref: &HashMap, Rc> = bindings.borrow(); - let mut new_bindings = new_bindings_ref.clone(); + let mut new_bindings = bindings.clone(); for b in have_bindings.iter() { match &b.pattern { BindingPattern::Name(name) => { @@ -482,7 +440,7 @@ pub fn second_of_alist(lst: Rc) -> Result, CompileErr> { fn synthesize_args( template: Rc, - env: Rc, Rc>>, + env: &HashMap, Rc>, ) -> Result, CompileErr> { match template.borrow() { SExp::Atom(_, name) => env.get(name).map(|x| Ok(x.clone())).unwrap_or_else(|| { @@ -499,7 +457,7 @@ fn synthesize_args( l.clone(), vec![ Rc::new(BodyForm::Value(SExp::atom_from_string(template.loc(), "c"))), - synthesize_args(f.clone(), env.clone())?, + synthesize_args(f.clone(), env)?, synthesize_args(r.clone(), env)?, ], None, @@ -716,10 +674,10 @@ pub fn eval_dont_expand_let(inline_hint: &Option) -> bool { matches!(inline_hint, Some(LetFormInlineHint::NonInline(_))) } -pub fn filter_capture_args(args: Rc, name_map: Rc, Rc>>) -> Rc { +pub fn filter_capture_args(args: Rc, name_map: &HashMap, Rc>) -> Rc { match args.borrow() { SExp::Cons(l, a, b) => { - let a_filtered = filter_capture_args(a.clone(), name_map.clone()); + let a_filtered = filter_capture_args(a.clone(), name_map); let b_filtered = filter_capture_args(b.clone(), name_map); if !truthy(a_filtered.clone()) && !truthy(b_filtered.clone()) { return Rc::new(SExp::Nil(l.clone())); @@ -774,7 +732,7 @@ impl<'info> Evaluator { program: Rc, prog_args: Rc, arguments_to_convert: &[Rc], - env: Rc, Rc>>, + env: &HashMap, Rc>, ) -> Result, CompileErr> { // Pass the SExp representation of the expressions into // the macro after forming an argument sexp and then @@ -804,12 +762,10 @@ impl<'info> Evaluator { self.shrink_bodyform_visited( context, visited, - Rc::new(EvalData { - prog_args: prog_args.clone(), - env, - body: program.exp, - only_inline: false, - }), + prog_args.clone(), + env, + program.exp, + false, ) }) } else { @@ -828,7 +784,7 @@ impl<'info> Evaluator { context: &mut BasicCompileContext, visited_: &'info mut VisitedMarker<'_, VisitedInfo>, prog_args: Rc, - env: Rc, Rc>>, + env: &HashMap, Rc>, parts: &[Rc], only_inline: bool, ) -> Result, CompileErr> { @@ -837,22 +793,18 @@ impl<'info> Evaluator { let evaluated_prog = self.shrink_bodyform_visited( context, &mut visited, - Rc::new(EvalData { - prog_args: prog_args.clone(), - env: env.clone(), - body: parts[1].clone(), - only_inline: only_inline, - }) + prog_args.clone(), + env, + parts[1].clone(), + only_inline, )?; let evaluated_env = self.shrink_bodyform_visited( context, &mut visited, - Rc::new(EvalData { - prog_args, - env, - body: parts[2].clone(), - only_inline, - }) + prog_args, + env, + parts[2].clone(), + only_inline, )?; if let BodyForm::Lambda(ldata) = evaluated_prog.borrow() { return Ok(Some(LambdaApply { @@ -871,12 +823,11 @@ impl<'info> Evaluator { context: &mut BasicCompileContext, visited: &mut VisitedMarker<'info, VisitedInfo>, prog_args: Rc, - env: Rc, Rc>>, + env: &HashMap, Rc>, lapply: &LambdaApply, only_inline: bool, ) -> Result, CompileErr> { - let lambda_env_ref: &HashMap, Rc> = env.borrow(); - let mut lambda_env = lambda_env_ref.clone(); + let mut lambda_env = env.clone(); // Finish eta-expansion. @@ -889,12 +840,10 @@ impl<'info> Evaluator { let reified_captures = self.shrink_bodyform_visited( context, visited, - Rc::new(EvalData { - prog_args, - env, - body: lapply.lambda.captures.clone(), - only_inline, - }) + prog_args, + env, + lapply.lambda.captures.clone(), + only_inline, )?; let formed_caps = ArgInputs::Whole(reified_captures); create_argument_captures( @@ -910,12 +859,10 @@ impl<'info> Evaluator { self.shrink_bodyform_visited( context, visited, - Rc::new(EvalData { - prog_args: lapply.lambda.args.clone(), - env: Rc::new(lambda_env), - body: lapply.body.clone(), - only_inline, - }) + lapply.lambda.args.clone(), + &lambda_env, + lapply.body.clone(), + only_inline, ) } @@ -927,11 +874,11 @@ impl<'info> Evaluator { call: &CallSpec, prog_args: Rc, arguments_to_convert: &[Rc], - env: Rc, Rc>>, + env: &HashMap, Rc>, only_inline: bool, ) -> Result, CompileErr> { let mut all_primitive = true; - let mut target_vec: Vec> = call.args.to_vec(); + let mut target_vec: Vec> = call.args.to_owned(); let mut visited = VisitedMarker::again(call.loc.clone(), visited_)?; if call.name == "@".as_bytes() { @@ -968,19 +915,18 @@ impl<'info> Evaluator { // Reduce all arguments. let mut converted_args = SExp::Nil(call.loc.clone()); - for (i, element) in arguments_to_convert.iter().enumerate().skip(1).rev() { + for i_reverse in 0..arguments_to_convert.len() { + let i = arguments_to_convert.len() - i_reverse - 1; let shrunk = self.shrink_bodyform_visited( context, &mut visited, - Rc::new(EvalData { - prog_args: prog_args.clone(), - env: env.clone(), - body: element.clone(), - only_inline, - }) + prog_args.clone(), + env, + arguments_to_convert[i].clone(), + only_inline, )?; - target_vec[i] = shrunk.clone(); + target_vec[i + 1] = shrunk.clone(); if !arg_inputs_primitive(Rc::new(ArgInputs::Whole(shrunk.clone()))) { all_primitive = false; @@ -1014,7 +960,7 @@ impl<'info> Evaluator { context, &mut visited, prog_args.clone(), - env.clone(), + env, &target_vec, only_inline, )? { @@ -1062,12 +1008,10 @@ impl<'info> Evaluator { let apply_result = self.shrink_bodyform_visited( context, visited, - Rc::new(EvalData { - prog_args: Rc::new(SExp::Nil(run_program.loc())), - env: Rc::new(bindings), - body: program, - only_inline: false, - }) + Rc::new(SExp::Nil(run_program.loc())), + &bindings, + program, + false, )?; self.chase_apply(context, visited, apply_result) } @@ -1174,12 +1118,10 @@ impl<'info> Evaluator { visited: &'_ mut VisitedMarker<'info, VisitedInfo>, call: &CallSpec, prog_args: Rc, - arguments: &[Rc], arguments_to_convert: &[Rc], - translated_tail: Option>, - env: Rc, Rc>>, + env: &HashMap, Rc>, only_inline: bool, - ) -> Result { + ) -> Result, CompileErr> { let helper = select_helper(&self.helpers, call.name); match helper { Some(HelperForm::Defmacro(mac)) => { @@ -1198,26 +1140,57 @@ impl<'info> Evaluator { prog_args, arguments_to_convert, env, - ).map(|r| EvalResult::Body(r)) + ) } Some(HelperForm::Defun(inline, defun)) => { if !inline && only_inline { - return Ok(EvalResult::Body(call.original.clone())); + return Ok(call.original.clone()); } - let argument_captures = build_argument_captures( + let translated_tail = if let Some(t) = call.tail.as_ref() { + Some(self.shrink_bodyform_visited( + context, + visited, + prog_args.clone(), + env, + t.clone(), + only_inline, + )?) + } else { + None + }; + + let argument_captures_untranslated = build_argument_captures( &call.loc.clone(), - &arguments[1..], + arguments_to_convert, translated_tail.clone(), defun.args.clone(), )?; - Ok(EvalResult::Process(Rc::new(EvalData { - prog_args: defun.args.clone(), - env: Rc::new(argument_captures), - body: defun.body, + let mut argument_captures = HashMap::new(); + // Do this to protect against misalignment + // between argument vec and destructuring. + for kv in argument_captures_untranslated.iter() { + let shrunk = self.shrink_bodyform_visited( + context, + visited, + prog_args.clone(), + env, + kv.1.clone(), + only_inline, + )?; + + argument_captures.insert(kv.0.clone(), shrunk.clone()); + } + + self.shrink_bodyform_visited( + context, + visited, + defun.args.clone(), + &argument_captures, + defun.body, only_inline, - }))) + ) } _ => self .invoke_primitive( @@ -1229,8 +1202,7 @@ impl<'info> Evaluator { env, only_inline, ) - .and_then(|res| self.chase_apply(context, visited, res)) - .map(|r| EvalResult::Body(r)), + .and_then(|res| self.chase_apply(context, visited, res)), } } @@ -1239,7 +1211,7 @@ impl<'info> Evaluator { context: &mut BasicCompileContext, visited: &'info mut VisitedMarker<'_, VisitedInfo>, prog_args: Rc, - env: Rc, Rc>>, + env: &HashMap, Rc>, ldata: &LambdaData, only_inline: bool, ) -> Result, CompileErr> { @@ -1251,12 +1223,10 @@ impl<'info> Evaluator { let new_captures = self.shrink_bodyform_visited( context, visited, - Rc::new(EvalData { - prog_args: prog_args.clone(), - env, - body: ldata.captures.clone(), - only_inline, - }) + prog_args.clone(), + env, + ldata.captures.clone(), + only_inline, )?; // Break up and make binding map. @@ -1286,20 +1256,17 @@ impl<'info> Evaluator { )); // Eliminate the captures via beta substituion. - let interpretable_rc = Rc::new(interpretable_captures); let simplified_body = self.shrink_bodyform_visited( context, visited, - Rc::new(EvalData { - prog_args: combined_args.clone(), - env: interpretable_rc.clone(), - body: ldata.body.clone(), - only_inline, - }) + combined_args.clone(), + &interpretable_captures, + ldata.body.clone(), + only_inline, )?; let new_capture_args = - filter_capture_args(ldata.capture_args.clone(), interpretable_rc); + filter_capture_args(ldata.capture_args.clone(), &interpretable_captures); Ok(Rc::new(BodyForm::Lambda(Box::new(LambdaData { args: ldata.args.clone(), capture_args: new_capture_args, @@ -1334,175 +1301,46 @@ impl<'info> Evaluator { )) } - fn shrink_bodyform_visited( - &self, - context: &mut BasicCompileContext, - visited: &'info mut VisitedMarker<'_, VisitedInfo>, - eval_data: Rc, - ) -> Result, CompileErr> { - let mut result_stack = vec![EvalResult::Process(eval_data.clone())]; - - let choose_call_target = |call: &Call| -> Option> { - if let Some(t) = call.tail.as_ref() { - if call.processed_tail.is_none() { - return Some(t.clone()); - } - } - - if call.processed_args.len() >= call.args.len() { - return None; - } - - Some(call.args[call.processed_args.len()].clone()) - }; - - let handle_call = |result_stack: &mut Vec, call: Call, eval_data: Rc| -> Result<(), CompileErr> { - if let Some(t) = choose_call_target(&call) { - result_stack.push(EvalResult::Call(call, eval_data.clone())); - result_stack.push(EvalResult::Process( - Rc::new(eval_data.with_body_or_env(Some(t.clone()), None)), - )); - } else { - result_stack.push(EvalResult::Invoke( - call, - eval_data.clone(), - )); - } - - Ok(()) - }; - - loop { - let result = result_stack.pop(); - match result { - None => { - return Err(CompileErr(eval_data.body.loc(), "empty eval stack".to_string())); - } - Some(EvalResult::Body(b)) => { - match result_stack.pop() { - None => { - return Ok(b.clone()); - } - Some(EvalResult::Body(_)) => { - return Err(CompileErr(b.loc(), "can't collapse concrete value".to_string())); - } - Some(EvalResult::Process(_)) => { - result_stack.push(EvalResult::Body(b)); - } - Some(EvalResult::Call(mut call, eval_data)) => { - let need_tail = call.tail.is_some() && call.processed_tail.is_none(); - let need_arg = call.processed_args.len() < call.args.len(); - if need_tail || need_arg { - if need_tail { - call.processed_tail = Some(b.clone()); - } else if need_arg { - call.processed_args.push(b.clone()); - } - } - - if call.processed_args.len() < call.args.len() { - let new_arg = call.args[call.processed_args.len()].clone(); - result_stack.push(EvalResult::Call(call, eval_data.clone())); - result_stack.push(EvalResult::Process(Rc::new(eval_data.with_body_or_env(Some(new_arg), None)))); - } else { - result_stack.push(EvalResult::Invoke(call, eval_data.clone())); - } - } - Some(EvalResult::Invoke(call, eval_data)) => { - result_stack.push(self.handle_invoke( - allocator, - visited, - &CallSpec { - args: &call.args, - name: &call.name, - loc: call.loc.clone(), - original: call.original.clone(), - tail: call.tail.clone(), - }, - eval_data.prog_args.clone(), - &call.processed_args, - &call.args, - call.processed_tail.clone(), - eval_data.env.clone(), - eval_data.only_inline - )?); - } - } - } - Some(EvalResult::Call(call, eval_data)) => { - handle_call(&mut result_stack, call, eval_data.clone())?; - } - Some(EvalResult::Invoke(call, eval_data)) => { - result_stack.push(self.handle_invoke( - allocator, - visited, - &CallSpec { - loc: call.loc.clone(), - name: &call.name, - args: &call.args, - original: call.original.clone(), - tail: call.tail.clone(), - }, - eval_data.prog_args.clone(), - &call.args, - &call.processed_args, - call.processed_tail.clone(), - eval_data.env.clone(), - eval_data.only_inline, - )?); - } - Some(EvalResult::Process(eval_data)) => { - result_stack.push(self.shrink_bodyform_visited_main( - allocator, - visited, - eval_data.clone() - )?); - } - } - } - } - // A frontend language evaluator and minifier - fn shrink_bodyform_visited_main( + fn shrink_bodyform_visited( &self, context: &mut BasicCompileContext, visited_: &'info mut VisitedMarker<'_, VisitedInfo>, - eval_data: Rc, - ) -> Result { - let mut visited = VisitedMarker::again(eval_data.body.loc(), visited_)?; - match eval_data.body.borrow() { + prog_args: Rc, + env: &HashMap, Rc>, + body: Rc, + only_inline: bool, + ) -> Result, CompileErr> { + let mut visited = VisitedMarker::again(body.loc(), visited_)?; + match body.borrow() { BodyForm::Let(LetFormKind::Parallel, letdata) => { - if eval_dont_expand_let(&letdata.inline_hint) && eval_data.only_inline { - return Ok(EvalResult::Body(eval_data.body.clone())); + if eval_dont_expand_let(&letdata.inline_hint) && only_inline { + return Ok(body.clone()); } - let updated_bindings = update_parallel_bindings(eval_data.env.clone(), &letdata.bindings); - self.shrink_bodyform_visited_main( + let updated_bindings = update_parallel_bindings(env, &letdata.bindings); + self.shrink_bodyform_visited( context, &mut visited, - Rc::new(EvalData { - prog_args: eval_data.prog_args.clone(), - env: Rc::new(updated_bindings), - body: letdata.body.clone(), - only_inline: eval_data.only_inline, - }) + prog_args, + &updated_bindings, + letdata.body.clone(), + only_inline, ) } BodyForm::Let(LetFormKind::Sequential, letdata) => { - if eval_dont_expand_let(&letdata.inline_hint) && eval_data.only_inline { - return Ok(EvalResult::Body(eval_data.body.clone())); + if eval_dont_expand_let(&letdata.inline_hint) && only_inline { + return Ok(body.clone()); } if letdata.bindings.is_empty() { - self.shrink_bodyform_visited_main( + self.shrink_bodyform_visited( context, &mut visited, - Rc::new(EvalData { - prog_args: eval_data.prog_args.clone(), - env: eval_data.env.clone(), - body: letdata.body.clone(), - only_inline: eval_data.only_inline, - }) + prog_args, + env, + letdata.body.clone(), + only_inline, ) } else { let first_binding_as_list: Vec> = @@ -1510,108 +1348,96 @@ impl<'info> Evaluator { let rest_of_bindings: Vec> = letdata.bindings.iter().skip(1).cloned().collect(); - let updated_bindings = update_parallel_bindings(eval_data.env.clone(), &first_binding_as_list); - self.shrink_bodyform_visited_main( + let updated_bindings = update_parallel_bindings(env, &first_binding_as_list); + self.shrink_bodyform_visited( context, &mut visited, - Rc::new(EvalData { - prog_args: eval_data.prog_args.clone(), - env: Rc::new(updated_bindings), - body: Rc::new(BodyForm::Let( - LetFormKind::Sequential, - Box::new(LetData { - bindings: rest_of_bindings, - ..*letdata.clone() - }), - )), - only_inline: eval_data.only_inline, - }) + prog_args, + &updated_bindings, + Rc::new(BodyForm::Let( + LetFormKind::Sequential, + Box::new(LetData { + bindings: rest_of_bindings, + ..*letdata.clone() + }), + )), + only_inline, ) } } BodyForm::Let(LetFormKind::Assign, letdata) => { - if eval_dont_expand_let(&letdata.inline_hint) && eval_data.only_inline { - return Ok(EvalResult::Body(eval_data.body.clone())); + if eval_dont_expand_let(&letdata.inline_hint) && only_inline { + return Ok(body.clone()); } - self.shrink_bodyform_visited_main( + self.shrink_bodyform_visited( context, &mut visited, - Rc::new(EvalData { - prog_args: eval_data.prog_args.clone(), - env: eval_data.env.clone(), - body: Rc::new(hoist_assign_form(letdata)?), - only_inline: eval_data.only_inline, - }) + prog_args, + env, + Rc::new(hoist_assign_form(letdata)?), + only_inline, ) } - BodyForm::Quoted(_) => Ok(EvalResult::Body(eval_data.body.clone())), + BodyForm::Quoted(_) => Ok(body.clone()), BodyForm::Value(SExp::Atom(l, name)) => { if name == &"@".as_bytes().to_vec() { - let literal_args = synthesize_args(eval_data.prog_args.clone(), eval_data.env.clone())?; - self.shrink_bodyform_visited_main( + let literal_args = synthesize_args(prog_args.clone(), env)?; + self.shrink_bodyform_visited( context, &mut visited, - Rc::new(EvalData { - prog_args: eval_data.prog_args.clone(), - env: eval_data.env.clone(), - body: literal_args, - only_inline: eval_data.only_inline, - }) + prog_args, + env, + literal_args, + only_inline, ) } else if let Some(function) = self.get_function(name) { - self.shrink_bodyform_visited_main( + self.shrink_bodyform_visited( context, &mut visited, - Rc::new(EvalData { - prog_args: eval_data.prog_args.clone(), - env: eval_data.env.clone(), - body: self.create_mod_for_fun(l, function.borrow()), - only_inline: eval_data.only_inline, - }) + prog_args, + env, + self.create_mod_for_fun(l, function.borrow()), + only_inline, ) } else { - eval_data.env.get(name) + env.get(name) .map(|x| { if reflex_capture(name, x.clone()) { - Ok(EvalResult::Body(x.clone())) + Ok(x.clone()) } else { - self.shrink_bodyform_visited_main( + self.shrink_bodyform_visited( context, &mut visited, - Rc::new(EvalData { - prog_args: eval_data.prog_args.clone(), - env: eval_data.env.clone(), - body: x.clone(), - only_inline: eval_data.only_inline, - }) + prog_args.clone(), + env, + x.clone(), + only_inline, ) } }) .unwrap_or_else(|| { self.get_constant(name) .map(|x| { - self.shrink_bodyform_visited_main( + self.shrink_bodyform_visited( context, &mut visited, - Rc::new(EvalData { - prog_args: eval_data.prog_args.clone(), - env: eval_data.env.clone(), - body: x, - only_inline: eval_data.only_inline, - }) + prog_args.clone(), + env, + x, + only_inline, ) }) .unwrap_or_else(|| { - Ok(EvalResult::Body(Rc::new(BodyForm::Value(SExp::Atom( + Ok(Rc::new(BodyForm::Value(SExp::Atom( l.clone(), name.clone(), - ))))) + )))) }) }) } } - BodyForm::Value(v) => Ok(EvalResult::Body(Rc::new(BodyForm::Quoted(v.clone())))), + BodyForm::Value(v) => Ok(Rc::new(BodyForm::Quoted(v.clone()))), BodyForm::Call(l, parts, tail) => { if parts.is_empty() { return Err(CompileErr( @@ -1621,32 +1447,40 @@ impl<'info> Evaluator { } let head_expr = parts[0].clone(); + let arguments_to_convert: Vec> = + parts.iter().skip(1).cloned().collect(); match head_expr.borrow() { - BodyForm::Value(SExp::Atom(_call_loc, call_name)) => Ok(EvalResult::Call( - Call { + BodyForm::Value(SExp::Atom(_call_loc, call_name)) => self.handle_invoke( + context, + &mut visited, + &CallSpec { loc: l.clone(), - name: call_name.clone(), + name: call_name, + args: parts, + original: body.clone(), tail: tail.clone(), - processed_tail: None, - args: parts.clone(), - processed_args: vec![parts[0].clone()], - original: eval_data.body.clone(), }, - eval_data.clone(), - )), - BodyForm::Value(SExp::Integer(_call_loc, call_int)) => Ok(EvalResult::Call( - Call { + prog_args, + &arguments_to_convert, + env, + only_inline, + ), + BodyForm::Value(SExp::Integer(_call_loc, call_int)) => self.handle_invoke( + context, + &mut visited, + &CallSpec { loc: l.clone(), - name: u8_from_number(call_int.clone()), + name: &u8_from_number(call_int.clone()), + args: parts, + original: body.clone(), tail: None, - processed_tail: None, - args: parts.clone(), - processed_args: vec![parts[0].clone()], - original: eval_data.body.clone(), }, - eval_data.clone() - )), + prog_args, + &arguments_to_convert, + env, + only_inline, + ), _ => Err(CompileErr( l.clone(), format!("Don't know how to call {}", head_expr.to_sexp()), @@ -1660,16 +1494,16 @@ impl<'info> Evaluator { let mut context_wrapper = CompileContextWrapper::new(self.runner.clone(), &mut symbols, optimizer); let code = codegen(context_wrapper.context(), self.opts.clone(), program)?; - Ok(EvalResult::Body(Rc::new(BodyForm::Quoted(code)))) + Ok(Rc::new(BodyForm::Quoted(code))) } BodyForm::Lambda(ldata) => self.enrich_lambda_site_info( context, &mut visited, - eval_data.prog_args.clone(), - eval_data.env.clone(), + prog_args, + env, ldata, - eval_data.only_inline, - ).map(|r| EvalResult::Body(r)), + only_inline, + ), } } @@ -1692,7 +1526,7 @@ impl<'info> Evaluator { &self, context: &mut BasicCompileContext, prog_args: Rc, - env: Rc, Rc>>, + env: &HashMap, Rc>, body: Rc, only_inline: bool, stack_limit: Option, @@ -1705,12 +1539,10 @@ impl<'info> Evaluator { self.shrink_bodyform_visited( context, &mut visited_marker, - Rc::new(EvalData { - prog_args, - env, - body, - only_inline, - }) + prog_args, + env, + body, + only_inline, ) } diff --git a/src/compiler/optimize/mod.rs b/src/compiler/optimize/mod.rs index 7824326df..b2543f80f 100644 --- a/src/compiler/optimize/mod.rs +++ b/src/compiler/optimize/mod.rs @@ -650,7 +650,7 @@ fn fe_opt( let body_rc = evaluator.shrink_bodyform( context, defun.args.clone(), - Rc::new(env), + &env, defun.body.clone(), true, Some(EVAL_STACK_LIMIT), @@ -674,7 +674,7 @@ fn fe_opt( let shrunk = new_evaluator.shrink_bodyform( context, Rc::new(SExp::Nil(compileform.args.loc())), - Rc::new(HashMap::new()), + &HashMap::new(), compileform.exp.clone(), true, Some(EVAL_STACK_LIMIT), diff --git a/src/compiler/repl.rs b/src/compiler/repl.rs index e51733079..c2dfab982 100644 --- a/src/compiler/repl.rs +++ b/src/compiler/repl.rs @@ -207,7 +207,7 @@ impl Repl { self.evaluator.shrink_bodyform( context, program.args.clone(), - Rc::new(HashMap::new()), + &HashMap::new(), program.exp, false, self.stack_limit, diff --git a/src/compiler/usecheck.rs b/src/compiler/usecheck.rs index 6bda0a29a..3c7ca0553 100644 --- a/src/compiler/usecheck.rs +++ b/src/compiler/usecheck.rs @@ -106,7 +106,7 @@ pub fn check_parameters_used_compileform( let result = e.shrink_bodyform( &mut context, program.args.clone(), - Rc::new(env), + &env, program.exp.clone(), false, Some(EVAL_STACK_LIMIT), diff --git a/src/tests/compiler/evaluate.rs b/src/tests/compiler/evaluate.rs index 242e5fa20..b7231f8da 100644 --- a/src/tests/compiler/evaluate.rs +++ b/src/tests/compiler/evaluate.rs @@ -39,7 +39,7 @@ fn shrink_expr_from_string(s: String) -> Result { return e.shrink_bodyform( &mut context, program.args.clone(), - Rc::new(HashMap::new()), + &HashMap::new(), program.exp.clone(), false, Some(EVAL_STACK_LIMIT), From 214bcc0411035cc9b686817426671d34e0a68701 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 6 Aug 2026 02:35:48 +0000 Subject: [PATCH 07/10] Evaluate with an explicit continuation stack Co-authored-by: arty --- src/compiler/evaluate.rs | 1700 ++++++++++++++++++++------------ src/tests/compiler/evaluate.rs | 74 +- 2 files changed, 1137 insertions(+), 637 deletions(-) diff --git a/src/compiler/evaluate.rs b/src/compiler/evaluate.rs index 2dd062ca5..98d41893b 100644 --- a/src/compiler/evaluate.rs +++ b/src/compiler/evaluate.rs @@ -13,7 +13,7 @@ use crate::compiler::clvm::{run, truthy}; use crate::compiler::codegen::{codegen, hoist_assign_form}; use crate::compiler::compiler::is_at_capture; use crate::compiler::comptypes::{ - Binding, BindingPattern, BodyForm, CallSpec, CompileErr, CompileForm, CompilerOpts, DefunData, + Binding, BindingPattern, BodyForm, CompileErr, CompileForm, CompilerOpts, DefunData, HelperForm, LambdaData, LetData, LetFormInlineHint, LetFormKind, }; use crate::compiler::frontend::frontend; @@ -21,7 +21,6 @@ use crate::compiler::optimize::get_optimizer; use crate::compiler::runtypes::RunFailure; use crate::compiler::sexp::SExp; use crate::compiler::srcloc::Srcloc; -use crate::compiler::stackvisit::{HasDepthLimit, VisitedMarker}; use crate::compiler::BasicCompileContext; use crate::compiler::CompileContextWrapper; use crate::util::{number_from_u8, u8_from_number, Number}; @@ -29,47 +28,210 @@ use crate::util::{number_from_u8, u8_from_number, Number}; const PRIM_RUN_LIMIT: usize = 1000000; pub const EVAL_STACK_LIMIT: usize = 200; -// Stack depth checker. #[derive(Clone, Debug, Default)] pub struct VisitedInfo { functions: HashMap, Rc>, max_depth: Option, } -impl HasDepthLimit for VisitedInfo { - fn depth_limit(&self) -> Option { - self.max_depth - } - fn stack_err(&self, loc: Srcloc) -> CompileErr { - CompileErr(loc, "stack limit exceeded".to_string()) - } +#[derive(Clone)] +pub struct LambdaApply { + lambda: LambdaData, + body: Rc, + env: Rc, } -trait VisitedInfoAccess { - fn get_function(&mut self, name: &[u8]) -> Option>; - fn insert_function(&mut self, name: Vec, body: Rc); +type EvalEnv = Rc, Rc>>; + +#[derive(Clone)] +struct OwnedCallSpec { + loc: Srcloc, + name: Vec, + args: Vec>, + tail: Option>, + original: Rc, } -impl VisitedInfoAccess for VisitedMarker<'_, VisitedInfo> { - fn get_function(&mut self, name: &[u8]) -> Option> { - if let Some(ref mut info) = self.info { - info.functions.get(name).cloned() - } else { - None - } - } +struct ShrinkRequest { + prog_args: Rc, + env: EvalEnv, + body: Rc, + only_inline: bool, + depth: usize, +} - fn insert_function(&mut self, name: Vec, body: Rc) { - if let Some(ref mut info) = self.info { - info.functions.insert(name, body); - } - } +struct IsLambdaRequest { + prog_args: Rc, + env: EvalEnv, + parts: Vec>, + only_inline: bool, + depth: usize, } -pub struct LambdaApply { - lambda: LambdaData, +struct PrimitiveRequest { + call: OwnedCallSpec, + prog_args: Rc, + arguments: Vec>, + env: EvalEnv, + only_inline: bool, + depth: usize, +} + +struct LambdaRequest { + prog_args: Rc, + env: EvalEnv, + lapply: LambdaApply, + only_inline: bool, + depth: usize, +} + +struct InvokeRequest { + call: OwnedCallSpec, + prog_args: Rc, + arguments: Vec>, + env: EvalEnv, + only_inline: bool, + depth: usize, +} + +struct ChaseRequest { body: Rc, + depth: usize, +} + +struct MashRequest { + maybe_condition: Rc, env: Rc, + depth: usize, +} + +struct EnrichRequest { + prog_args: Rc, + env: EvalEnv, + ldata: LambdaData, + only_inline: bool, + depth: usize, +} + +enum EvalRequest { + Shrink(ShrinkRequest), + IsLambda(IsLambdaRequest), + Primitive(PrimitiveRequest), + Lambda(LambdaRequest), + Invoke(InvokeRequest), + Chase(ChaseRequest), + Mash(MashRequest), + Enrich(EnrichRequest), +} + +impl EvalRequest { + fn loc(&self) -> Srcloc { + match self { + Self::Shrink(request) => request.body.loc(), + Self::IsLambda(request) => request + .parts + .first() + .map(|part| part.loc()) + .unwrap_or_else(|| Srcloc::start(&"*evaluator*".to_string())), + Self::Primitive(request) => request.call.loc.clone(), + Self::Lambda(request) => request.lapply.body.loc(), + Self::Invoke(request) => request.call.loc.clone(), + Self::Chase(request) => request.body.loc(), + Self::Mash(request) => request.maybe_condition.loc(), + Self::Enrich(request) => request.ldata.loc.clone(), + } + } +} + +enum EvalValue { + Body(Rc), + Lambda(Option), +} + +type EvalResult = Result; + +struct PrimitiveState { + call: OwnedCallSpec, + prog_args: Rc, + arguments: Vec>, + env: EvalEnv, + only_inline: bool, + depth: usize, + prim: Rc, + target: Vec>, + converted: Vec>>, + next: usize, + all_primitive: bool, +} + +struct DefunState { + defun: Box, + prog_args: Rc, + arguments: Vec>, + env: EvalEnv, + only_inline: bool, + depth: usize, + call_loc: Srcloc, +} + +struct CaptureState { + defun: Box, + prog_args: Rc, + env: EvalEnv, + only_inline: bool, + depth: usize, + captures: Vec<(Vec, Rc)>, + translated: HashMap, Rc>, + next: usize, +} + +enum Continuation { + Identity, + IsLambdaProgram(IsLambdaRequest), + IsLambdaEnv { + evaluated_prog: Rc, + request: IsLambdaRequest, + }, + LambdaCaptures(LambdaRequest), + PrimitiveArg(PrimitiveState), + PrimitiveLambda(PrimitiveState), + Chase { + depth: usize, + }, + ContinueApply { + depth: usize, + }, + MashOrOriginal { + original: Rc, + }, + MashTrue { + x_head: Rc, + cond: Rc, + iffalse: Rc, + apply_head: Rc, + env: Rc, + location: Srcloc, + depth: usize, + }, + MashFalse { + x_head: Rc, + cond: Rc, + true_result: Rc, + location: Srcloc, + }, + DefunTail(DefunState), + DefunCapture(CaptureState), + EnrichCaptures(EnrichRequest), + EnrichBody { + ldata: LambdaData, + new_captures: Rc, + interpretable: HashMap, Rc>, + }, +} + +enum EvalStep { + Request(EvalRequest, Continuation), + Complete(EvalResult), } // Frontend evaluator based on my fuzzer representation and direct interpreter of @@ -695,7 +857,7 @@ pub fn filter_capture_args(args: Rc, name_map: &HashMap, Rc Evaluator { +impl Evaluator { pub fn new( opts: Rc, runner: Rc, @@ -722,28 +884,58 @@ impl<'info> Evaluator { } } + fn body_result(result: EvalResult, loc: Srcloc) -> Result, CompileErr> { + match result? { + EvalValue::Body(body) => Ok(body), + EvalValue::Lambda(_) => Err(CompileErr( + loc, + "internal evaluator return type mismatch".to_string(), + )), + } + } + + fn body_done(result: Result, CompileErr>) -> EvalStep { + EvalStep::Complete(result.map(EvalValue::Body)) + } + + fn increment_depth( + state: &VisitedInfo, + depth: usize, + loc: Srcloc, + ) -> Result { + if state.max_depth.is_some_and(|limit| depth >= limit) { + Err(CompileErr(loc, "stack limit exceeded".to_string())) + } else { + Ok(depth + 1) + } + } + + fn request_body(request: ShrinkRequest, continuation: Continuation) -> EvalStep { + EvalStep::Request(EvalRequest::Shrink(request), continuation) + } + #[allow(clippy::too_many_arguments)] fn invoke_macro_expansion( &self, context: &mut BasicCompileContext, - visited: &'_ mut VisitedMarker<'info, VisitedInfo>, l: Srcloc, call_loc: Srcloc, program: Rc, prog_args: Rc, - arguments_to_convert: &[Rc], - env: &HashMap, Rc>, - ) -> Result, CompileErr> { - // Pass the SExp representation of the expressions into - // the macro after forming an argument sexp and then + arguments: Vec>, + env: EvalEnv, + depth: usize, + ) -> EvalStep { let mut macro_args = Rc::new(SExp::Nil(l.clone())); - for i_reverse in 0..arguments_to_convert.len() { - let i = arguments_to_convert.len() - i_reverse - 1; - let arg_repr = arguments_to_convert[i].to_sexp(); + for argument in arguments.iter().rev() { + let arg_repr = argument.to_sexp(); macro_args = Rc::new(SExp::Cons(l.clone(), arg_repr, macro_args)); } - let macro_expansion = self.expand_macro(context, l.clone(), program, macro_args)?; + let macro_expansion = match self.expand_macro(context, l.clone(), program, macro_args) { + Ok(expansion) => expansion, + Err(error) => return Self::body_done(Err(error)), + }; if let Ok(input) = dequote(call_loc, macro_expansion.clone()) { let frontend_macro_input = Rc::new(SExp::Cons( @@ -756,524 +948,401 @@ impl<'info> Evaluator { )), )); - frontend(self.opts.clone(), &[frontend_macro_input]) - .map(|p| p.compileform().clone()) - .and_then(|program| { - self.shrink_bodyform_visited( - context, - visited, - prog_args.clone(), + match frontend(self.opts.clone(), &[frontend_macro_input]) { + Ok(program) => Self::request_body( + ShrinkRequest { + prog_args, env, - program.exp, - false, - ) - }) + body: program.compileform().exp.clone(), + only_inline: false, + depth, + }, + Continuation::Identity, + ), + Err(error) => Self::body_done(Err(error)), + } } else { - promote_program_to_bodyform( + Self::body_done(promote_program_to_bodyform( macro_expansion.to_sexp(), Rc::new(BodyForm::Value(SExp::Atom( macro_expansion.loc(), vec![b'@'], ))), - ) + )) } } - fn is_lambda_apply( - &self, - context: &mut BasicCompileContext, - visited_: &'info mut VisitedMarker<'_, VisitedInfo>, - prog_args: Rc, - env: &HashMap, Rc>, - parts: &[Rc], - only_inline: bool, - ) -> Result, CompileErr> { - if parts.len() == 3 && is_apply_atom(parts[0].to_sexp()) { - let mut visited = VisitedMarker::again(parts[0].loc(), visited_)?; - let evaluated_prog = self.shrink_bodyform_visited( - context, - &mut visited, - prog_args.clone(), - env, - parts[1].clone(), - only_inline, - )?; - let evaluated_env = self.shrink_bodyform_visited( - context, - &mut visited, - prog_args, - env, - parts[2].clone(), - only_inline, - )?; - if let BodyForm::Lambda(ldata) = evaluated_prog.borrow() { - return Ok(Some(LambdaApply { - lambda: *ldata.clone(), - body: ldata.body.clone(), - env: evaluated_env, - })); - } + fn is_lambda_apply(&self, request: IsLambdaRequest) -> EvalStep { + if request.parts.len() != 3 || !is_apply_atom(request.parts[0].to_sexp()) { + return EvalStep::Complete(Ok(EvalValue::Lambda(None))); } - Ok(None) + Self::request_body( + ShrinkRequest { + prog_args: request.prog_args.clone(), + env: request.env.clone(), + body: request.parts[1].clone(), + only_inline: request.only_inline, + depth: request.depth, + }, + Continuation::IsLambdaProgram(request), + ) } - fn do_lambda_apply( - &self, - context: &mut BasicCompileContext, - visited: &mut VisitedMarker<'info, VisitedInfo>, - prog_args: Rc, - env: &HashMap, Rc>, - lapply: &LambdaApply, - only_inline: bool, - ) -> Result, CompileErr> { - let mut lambda_env = env.clone(); - - // Finish eta-expansion. - - // We're carrying an enriched environment which we can use to enrich - // the env map at this time. Once we do that we can expand the body - // fully because we're carring the info that goes with the primary - // arguments. - // - // Generate the enriched environment. - let reified_captures = self.shrink_bodyform_visited( - context, - visited, - prog_args, - env, - lapply.lambda.captures.clone(), - only_inline, - )?; - let formed_caps = ArgInputs::Whole(reified_captures); - create_argument_captures( - &mut lambda_env, - &formed_caps, - lapply.lambda.capture_args.clone(), - )?; - - // Create captures with the actual parameters. - let formed_args = ArgInputs::Whole(lapply.env.clone()); - create_argument_captures(&mut lambda_env, &formed_args, lapply.lambda.args.clone())?; - - self.shrink_bodyform_visited( - context, - visited, - lapply.lambda.args.clone(), - &lambda_env, - lapply.body.clone(), - only_inline, + fn do_lambda_apply(&self, request: LambdaRequest) -> EvalStep { + Self::request_body( + ShrinkRequest { + prog_args: request.prog_args.clone(), + env: request.env.clone(), + body: request.lapply.lambda.captures.clone(), + only_inline: request.only_inline, + depth: request.depth, + }, + Continuation::LambdaCaptures(request), ) } - #[allow(clippy::too_many_arguments)] fn invoke_primitive( &self, context: &mut BasicCompileContext, - visited_: &'_ mut VisitedMarker<'info, VisitedInfo>, - call: &CallSpec, - prog_args: Rc, - arguments_to_convert: &[Rc], - env: &HashMap, Rc>, - only_inline: bool, - ) -> Result, CompileErr> { - let mut all_primitive = true; - let mut target_vec: Vec> = call.args.to_owned(); - let mut visited = VisitedMarker::again(call.loc.clone(), visited_)?; - - if call.name == "@".as_bytes() { - // Synthesize the environment for this function - Ok(Rc::new(BodyForm::Quoted(SExp::Cons( - call.loc.clone(), - Rc::new(SExp::Nil(call.loc.clone())), - prog_args, - )))) - } else if call.name == "com".as_bytes() { + request: PrimitiveRequest, + ) -> EvalStep { + if request.call.name == b"@" { + return Self::body_done(Ok(Rc::new(BodyForm::Quoted(SExp::Cons( + request.call.loc.clone(), + Rc::new(SExp::Nil(request.call.loc)), + request.prog_args, + ))))); + } + + if request.call.name == b"com" { let mut end_of_list = Rc::new(SExp::Cons( - call.loc.clone(), - arguments_to_convert[0].to_sexp(), - Rc::new(SExp::Nil(call.loc.clone())), + request.call.loc.clone(), + request.arguments[0].to_sexp(), + Rc::new(SExp::Nil(request.call.loc.clone())), )); - for h in self.helpers.iter() { - end_of_list = Rc::new(SExp::Cons(call.loc.clone(), h.to_sexp(), end_of_list)) + end_of_list = Rc::new(SExp::Cons( + request.call.loc.clone(), + h.to_sexp(), + end_of_list, + )) } - let use_body = SExp::Cons( - call.loc.clone(), - Rc::new(SExp::Atom(call.loc.clone(), "mod".as_bytes().to_vec())), - Rc::new(SExp::Cons(call.loc.clone(), prog_args, end_of_list)), + request.call.loc.clone(), + Rc::new(SExp::Atom(request.call.loc.clone(), b"mod".to_vec())), + Rc::new(SExp::Cons( + request.call.loc.clone(), + request.prog_args, + end_of_list, + )), ); + return match self.compile_code(context, false, Rc::new(use_body)) { + Ok(compiled) => { + Self::body_done(Ok(Rc::new(BodyForm::Quoted(compiled.as_ref().clone())))) + } + Err(error) => Self::body_done(Err(error)), + }; + } - let compiled = self.compile_code(context, false, Rc::new(use_body))?; - let compiled_borrowed: &SExp = compiled.borrow(); - Ok(Rc::new(BodyForm::Quoted(compiled_borrowed.clone()))) - } else { - let pres = self - .lookup_prim(call.loc.clone(), call.name) - .map(|prim| { - // Reduce all arguments. - let mut converted_args = SExp::Nil(call.loc.clone()); - - for i_reverse in 0..arguments_to_convert.len() { - let i = arguments_to_convert.len() - i_reverse - 1; - let shrunk = self.shrink_bodyform_visited( - context, - &mut visited, - prog_args.clone(), - env, - arguments_to_convert[i].clone(), - only_inline, - )?; - - target_vec[i + 1] = shrunk.clone(); + let Some(prim) = self.lookup_prim(request.call.loc.clone(), &request.call.name) else { + return Self::body_done(Err(CompileErr( + request.call.loc, + format!( + "Don't yet support this call type {} {:?}", + request.call.original.to_sexp(), + request.call.original + ), + ))); + }; - if !arg_inputs_primitive(Rc::new(ArgInputs::Whole(shrunk.clone()))) { - all_primitive = false; - } + let count = request.arguments.len(); + let state = PrimitiveState { + call: request.call, + prog_args: request.prog_args, + arguments: request.arguments, + env: request.env, + only_inline: request.only_inline, + depth: request.depth, + prim, + target: Vec::new(), + converted: vec![None; count], + next: count, + all_primitive: true, + }; + self.next_primitive_argument(context, state) + } - converted_args = - SExp::Cons(call.loc.clone(), shrunk.to_sexp(), Rc::new(converted_args)); - } + fn next_primitive_argument( + &self, + context: &mut BasicCompileContext, + mut state: PrimitiveState, + ) -> EvalStep { + if state.target.is_empty() { + state.target = state.call.args.clone(); + } + if state.next > 0 { + let index = state.next - 1; + state.next = index; + return Self::request_body( + ShrinkRequest { + prog_args: state.prog_args.clone(), + env: state.env.clone(), + body: state.arguments[index].clone(), + only_inline: state.only_inline, + depth: state.depth, + }, + Continuation::PrimitiveArg(state), + ); + } - if all_primitive { - match self.run_prim( - context.allocator(), - call.loc.clone(), - make_prim_call(call.loc.clone(), prim, Rc::new(converted_args)), - Rc::new(SExp::Nil(call.loc.clone())), - ) { - Ok(res) => Ok(res), - Err(e) => { - if only_inline || self.ignore_exn { - Ok(Rc::new(BodyForm::Call( - call.loc.clone(), - target_vec.clone(), - None, - ))) - } else { - Err(e) - } - } - } - } else if let Some(applied_lambda) = self.is_lambda_apply( - context, - &mut visited, - prog_args.clone(), - env, - &target_vec, - only_inline, - )? { - self.do_lambda_apply( - context, - &mut visited, - prog_args.clone(), - env, - &applied_lambda, - only_inline, - ) - } else { - // Since this is a primitive, there's no tail transform. - let reformed = - BodyForm::Call(call.loc.clone(), target_vec.clone(), call.tail.clone()); - self.chase_apply(context, &mut visited, Rc::new(reformed)) - } - }) - .unwrap_or_else(|| { - // Build SExp arguments for external call or - // return the unevaluated chunk with minimized - // arguments. - Err(CompileErr( - call.loc.clone(), - format!( - "Don't yet support this call type {} {:?}", - call.original.to_sexp(), - call.original - ), - )) - })?; - Ok(pres) + let mut converted_args = SExp::Nil(state.call.loc.clone()); + for converted in state.converted.iter().rev() { + converted_args = SExp::Cons( + state.call.loc.clone(), + converted.as_ref().expect("converted argument").clone(), + Rc::new(converted_args), + ); + } + if state.all_primitive { + let result = self.run_prim( + context.allocator(), + state.call.loc.clone(), + make_prim_call(state.call.loc.clone(), state.prim, Rc::new(converted_args)), + Rc::new(SExp::Nil(state.call.loc.clone())), + ); + return match result { + Ok(body) => Self::body_done(Ok(body)), + Err(_) if state.only_inline || self.ignore_exn => Self::body_done(Ok(Rc::new( + BodyForm::Call(state.call.loc, state.target, None), + ))), + Err(error) => Self::body_done(Err(error)), + }; } + + EvalStep::Request( + EvalRequest::IsLambda(IsLambdaRequest { + prog_args: state.prog_args.clone(), + env: state.env.clone(), + parts: state.target.clone(), + only_inline: state.only_inline, + depth: state.depth, + }), + Continuation::PrimitiveLambda(state), + ) } - fn continue_apply( - &self, - context: &mut BasicCompileContext, - visited: &'_ mut VisitedMarker<'info, VisitedInfo>, - env: Rc, - run_program: Rc, - ) -> Result, CompileErr> { - let bindings = HashMap::new(); - let program = promote_program_to_bodyform(run_program.clone(), env)?; - let apply_result = self.shrink_bodyform_visited( - context, - visited, - Rc::new(SExp::Nil(run_program.loc())), - &bindings, - program, - false, - )?; - self.chase_apply(context, visited, apply_result) + fn continue_apply(&self, env: Rc, run_program: Rc, depth: usize) -> EvalStep { + match promote_program_to_bodyform(run_program.clone(), env) { + Ok(program) => Self::request_body( + ShrinkRequest { + prog_args: Rc::new(SExp::Nil(run_program.loc())), + env: Rc::new(HashMap::new()), + body: program, + only_inline: false, + depth, + }, + Continuation::ContinueApply { depth }, + ), + Err(error) => Self::body_done(Err(error)), + } } - fn do_mash_condition( - &self, - context: &mut BasicCompileContext, - visited: &'_ mut VisitedMarker<'info, VisitedInfo>, - maybe_condition: Rc, - env: Rc, - ) -> Result, CompileErr> { - // The inner part could be an 'i' which we know passes on - // one of the two conditional arguments. This was an apply so - // we can distribute over the conditional arguments. - if let Some((cond, iftrue, iffalse)) = match_i_op(maybe_condition.clone()) { + fn do_mash_condition(&self, request: MashRequest, state: &mut VisitedInfo) -> EvalStep { + if let Some((cond, iftrue, iffalse)) = match_i_op(request.maybe_condition.clone()) { let x_head = Rc::new(BodyForm::Value(SExp::Atom(cond.loc(), vec![b'x']))); let apply_head = Rc::new(BodyForm::Value(SExp::Atom(iftrue.loc(), vec![2]))); let where_from = cond.loc().to_string(); let where_from_vec = where_from.as_bytes().to_vec(); - if let Some(present) = visited.get_function(&where_from_vec) { - return Ok(present); + if let Some(present) = state.functions.get(&where_from_vec) { + return Self::body_done(Ok(present.clone())); } - - visited.insert_function( + state.functions.insert( where_from_vec, Rc::new(BodyForm::Call( - maybe_condition.loc(), + request.maybe_condition.loc(), vec![x_head.clone(), cond.clone()], None, )), ); - - let surrogate_apply_true = self.chase_apply( - context, - visited, - Rc::new(BodyForm::Call( - iftrue.loc(), - vec![apply_head.clone(), iftrue.clone(), env.clone()], - None, - )), - ); - - let surrogate_apply_false = self.chase_apply( - context, - visited, - Rc::new(BodyForm::Call( - iffalse.loc(), - vec![apply_head, iffalse.clone(), env], - None, - )), - ); - - // Reproduce the equivalent hull over the used values of - // (a (i cond surrogate_apply_true surrogate_apply_false)) - // Flatten and short circuit any farther evaluation since we just - // want the argument names passed through from the environment. - let res = Rc::new(BodyForm::Call( - maybe_condition.loc(), - vec![ + return EvalStep::Request( + EvalRequest::Chase(ChaseRequest { + body: Rc::new(BodyForm::Call( + iftrue.loc(), + vec![apply_head.clone(), iftrue.clone(), request.env.clone()], + None, + )), + depth: request.depth, + }), + Continuation::MashTrue { x_head, - flatten_expression_to_names(cond.to_sexp()), - flatten_expression_to_names(surrogate_apply_true?.to_sexp()), - flatten_expression_to_names(surrogate_apply_false?.to_sexp()), - ], - None, - )); - - return Ok(res); + cond, + iffalse, + apply_head, + env: request.env, + location: request.maybe_condition.loc(), + depth: request.depth, + }, + ); } - - Err(CompileErr(maybe_condition.loc(), "not i op".to_string())) + Self::body_done(Err(CompileErr( + request.maybe_condition.loc(), + "not i op".to_string(), + ))) } - fn chase_apply( - &self, - context: &mut BasicCompileContext, - visited: &'_ mut VisitedMarker<'info, VisitedInfo>, - body: Rc, - ) -> Result, CompileErr> { - if let BodyForm::Call(l, vec, None) = body.borrow() { - if is_apply_atom(vec[0].to_sexp()) { + fn chase_apply(&self, request: ChaseRequest) -> EvalStep { + if let BodyForm::Call(l, vec, None) = request.body.borrow() { + if !vec.is_empty() && is_apply_atom(vec[0].to_sexp()) { if let Ok(run_program) = dequote(l.clone(), vec[1].clone()) { - return self.continue_apply(context, visited, vec[2].clone(), run_program); + return self.continue_apply(vec[2].clone(), run_program, request.depth); } - if self.mash_conditions { - if let Ok(mashed) = - self.do_mash_condition(context, visited, vec[1].clone(), vec[2].clone()) - { - return Ok(mashed); - } + return EvalStep::Request( + EvalRequest::Mash(MashRequest { + maybe_condition: vec[1].clone(), + env: vec[2].clone(), + depth: request.depth, + }), + Continuation::MashOrOriginal { + original: request.body.clone(), + }, + ); } } } - - Ok(body) + Self::body_done(Ok(request.body)) } - #[allow(clippy::too_many_arguments)] - fn handle_invoke( - &self, - context: &mut BasicCompileContext, - visited: &'_ mut VisitedMarker<'info, VisitedInfo>, - call: &CallSpec, - prog_args: Rc, - arguments_to_convert: &[Rc], - env: &HashMap, Rc>, - only_inline: bool, - ) -> Result, CompileErr> { - let helper = select_helper(&self.helpers, call.name); + fn handle_invoke(&self, context: &mut BasicCompileContext, request: InvokeRequest) -> EvalStep { + let helper = select_helper(&self.helpers, &request.call.name); match helper { Some(HelperForm::Defmacro(mac)) => { - if call.tail.is_some() { - return Err(CompileErr( - call.loc.clone(), + if request.call.tail.is_some() { + return Self::body_done(Err(CompileErr( + request.call.loc, "Macros cannot use runtime rest arguments".to_string(), - )); + ))); } self.invoke_macro_expansion( context, - visited, mac.loc.clone(), - call.loc.clone(), + request.call.loc, mac.program, - prog_args, - arguments_to_convert, - env, + request.prog_args, + request.arguments, + request.env, + request.depth, ) } Some(HelperForm::Defun(inline, defun)) => { - if !inline && only_inline { - return Ok(call.original.clone()); + if !inline && request.only_inline { + return Self::body_done(Ok(request.call.original)); } - - let translated_tail = if let Some(t) = call.tail.as_ref() { - Some(self.shrink_bodyform_visited( - context, - visited, - prog_args.clone(), - env, - t.clone(), - only_inline, - )?) - } else { - None + let state = DefunState { + defun, + prog_args: request.prog_args, + arguments: request.arguments, + env: request.env, + only_inline: request.only_inline, + depth: request.depth, + call_loc: request.call.loc, }; - - let argument_captures_untranslated = build_argument_captures( - &call.loc.clone(), - arguments_to_convert, - translated_tail.clone(), - defun.args.clone(), - )?; - - let mut argument_captures = HashMap::new(); - // Do this to protect against misalignment - // between argument vec and destructuring. - for kv in argument_captures_untranslated.iter() { - let shrunk = self.shrink_bodyform_visited( - context, - visited, - prog_args.clone(), - env, - kv.1.clone(), - only_inline, - )?; - - argument_captures.insert(kv.0.clone(), shrunk.clone()); + if let Some(tail) = request.call.tail { + Self::request_body( + ShrinkRequest { + prog_args: state.prog_args.clone(), + env: state.env.clone(), + body: tail, + only_inline: state.only_inline, + depth: state.depth, + }, + Continuation::DefunTail(state), + ) + } else { + self.start_defun_captures(state, None) } - - self.shrink_bodyform_visited( - context, - visited, - defun.args.clone(), - &argument_captures, - defun.body, - only_inline, - ) } - _ => self - .invoke_primitive( - context, - visited, - call, - prog_args, - arguments_to_convert, - env, - only_inline, - ) - .and_then(|res| self.chase_apply(context, visited, res)), + _ => EvalStep::Request( + EvalRequest::Primitive(PrimitiveRequest { + call: request.call, + prog_args: request.prog_args, + arguments: request.arguments, + env: request.env, + only_inline: request.only_inline, + depth: request.depth, + }), + Continuation::Chase { + depth: request.depth, + }, + ), } } - fn enrich_lambda_site_info( - &self, - context: &mut BasicCompileContext, - visited: &'info mut VisitedMarker<'_, VisitedInfo>, - prog_args: Rc, - env: &HashMap, Rc>, - ldata: &LambdaData, - only_inline: bool, - ) -> Result, CompileErr> { - if !truthy(ldata.capture_args.clone()) { - return Ok(Rc::new(BodyForm::Lambda(Box::new(ldata.clone())))); - } + fn start_defun_captures(&self, state: DefunState, tail: Option>) -> EvalStep { + let captures = match build_argument_captures( + &state.call_loc, + &state.arguments, + tail, + state.defun.args.clone(), + ) { + Ok(captures) => captures.into_iter().collect(), + Err(error) => return Self::body_done(Err(error)), + }; + self.next_defun_capture(CaptureState { + defun: state.defun, + prog_args: state.prog_args, + env: state.env, + only_inline: state.only_inline, + depth: state.depth, + captures, + translated: HashMap::new(), + next: 0, + }) + } - // Rewrite the captures based on what we know at the call site. - let new_captures = self.shrink_bodyform_visited( - context, - visited, - prog_args.clone(), - env, - ldata.captures.clone(), - only_inline, - )?; - - // Break up and make binding map. - let deconsed_args = decons_args(new_captures.clone()); - let mut arg_captures = HashMap::new(); - create_argument_captures( - &mut arg_captures, - &deconsed_args, - ldata.capture_args.clone(), - )?; - - // Filter out elements that are not interpretable yet. - let mut interpretable_captures = HashMap::new(); - for (n, v) in arg_captures.iter() { - if dequote(v.loc(), v.clone()).is_ok() { - // This capture has already been made into a literal. - // We will substitute it in the lambda body and remove it - // from the capture set. - interpretable_captures.insert(n.clone(), v.clone()); - } + fn next_defun_capture(&self, mut state: CaptureState) -> EvalStep { + if state.next < state.captures.len() { + let body = state.captures[state.next].1.clone(); + state.next += 1; + return Self::request_body( + ShrinkRequest { + prog_args: state.prog_args.clone(), + env: state.env.clone(), + body, + only_inline: state.only_inline, + depth: state.depth, + }, + Continuation::DefunCapture(state), + ); } + Self::request_body( + ShrinkRequest { + prog_args: state.defun.args.clone(), + env: Rc::new(state.translated), + body: state.defun.body, + only_inline: state.only_inline, + depth: state.depth, + }, + Continuation::Identity, + ) + } - let combined_args = Rc::new(SExp::Cons( - ldata.loc.clone(), - ldata.capture_args.clone(), - ldata.args.clone(), - )); - - // Eliminate the captures via beta substituion. - let simplified_body = self.shrink_bodyform_visited( - context, - visited, - combined_args.clone(), - &interpretable_captures, - ldata.body.clone(), - only_inline, - )?; - - let new_capture_args = - filter_capture_args(ldata.capture_args.clone(), &interpretable_captures); - Ok(Rc::new(BodyForm::Lambda(Box::new(LambdaData { - args: ldata.args.clone(), - capture_args: new_capture_args, - captures: new_captures, - body: simplified_body, - ..ldata.clone() - })))) + fn enrich_lambda_site_info(&self, request: EnrichRequest) -> EvalStep { + if !truthy(request.ldata.capture_args.clone()) { + return Self::body_done(Ok(Rc::new(BodyForm::Lambda(Box::new(request.ldata))))); + } + Self::request_body( + ShrinkRequest { + prog_args: request.prog_args.clone(), + env: request.env.clone(), + body: request.ldata.captures.clone(), + only_inline: request.only_inline, + depth: request.depth, + }, + Continuation::EnrichCaptures(request), + ) } fn get_function(&self, name: &[u8]) -> Option> { @@ -1301,46 +1370,96 @@ impl<'info> Evaluator { )) } - // A frontend language evaluator and minifier - fn shrink_bodyform_visited( + fn dispatch( &self, context: &mut BasicCompileContext, - visited_: &'info mut VisitedMarker<'_, VisitedInfo>, - prog_args: Rc, - env: &HashMap, Rc>, - body: Rc, - only_inline: bool, - ) -> Result, CompileErr> { - let mut visited = VisitedMarker::again(body.loc(), visited_)?; + request: EvalRequest, + state: &mut VisitedInfo, + ) -> EvalStep { + match request { + EvalRequest::Shrink(mut request) => { + request.depth = + match Self::increment_depth(state, request.depth, request.body.loc()) { + Ok(depth) => depth, + Err(error) => return Self::body_done(Err(error)), + }; + self.shrink_bodyform_visited(context, request) + } + EvalRequest::IsLambda(mut request) => { + let loc = request + .parts + .first() + .map(|part| part.loc()) + .unwrap_or_else(|| Srcloc::start(&"*evaluator*".to_string())); + request.depth = match Self::increment_depth(state, request.depth, loc) { + Ok(depth) => depth, + Err(error) => { + return EvalStep::Complete(Err(error)); + } + }; + self.is_lambda_apply(request) + } + EvalRequest::Primitive(mut request) => { + request.depth = + match Self::increment_depth(state, request.depth, request.call.loc.clone()) { + Ok(depth) => depth, + Err(error) => return Self::body_done(Err(error)), + }; + self.invoke_primitive(context, request) + } + EvalRequest::Lambda(request) => self.do_lambda_apply(request), + EvalRequest::Invoke(request) => self.handle_invoke(context, request), + EvalRequest::Chase(request) => self.chase_apply(request), + EvalRequest::Mash(request) => self.do_mash_condition(request, state), + EvalRequest::Enrich(request) => self.enrich_lambda_site_info(request), + } + } + + fn shrink_bodyform_visited( + &self, + _context: &mut BasicCompileContext, + request: ShrinkRequest, + ) -> EvalStep { + let ShrinkRequest { + prog_args, + env, + body, + only_inline, + depth, + } = request; match body.borrow() { BodyForm::Let(LetFormKind::Parallel, letdata) => { if eval_dont_expand_let(&letdata.inline_hint) && only_inline { - return Ok(body.clone()); + return Self::body_done(Ok(body.clone())); } - let updated_bindings = update_parallel_bindings(env, &letdata.bindings); - self.shrink_bodyform_visited( - context, - &mut visited, - prog_args, - &updated_bindings, - letdata.body.clone(), - only_inline, + let updated_bindings = update_parallel_bindings(&env, &letdata.bindings); + Self::request_body( + ShrinkRequest { + prog_args, + env: Rc::new(updated_bindings), + body: letdata.body.clone(), + only_inline, + depth, + }, + Continuation::Identity, ) } BodyForm::Let(LetFormKind::Sequential, letdata) => { if eval_dont_expand_let(&letdata.inline_hint) && only_inline { - return Ok(body.clone()); + return Self::body_done(Ok(body.clone())); } if letdata.bindings.is_empty() { - self.shrink_bodyform_visited( - context, - &mut visited, - prog_args, - env, - letdata.body.clone(), - only_inline, + Self::request_body( + ShrinkRequest { + prog_args, + env, + body: letdata.body.clone(), + only_inline, + depth, + }, + Continuation::Identity, ) } else { let first_binding_as_list: Vec> = @@ -1348,162 +1467,436 @@ impl<'info> Evaluator { let rest_of_bindings: Vec> = letdata.bindings.iter().skip(1).cloned().collect(); - let updated_bindings = update_parallel_bindings(env, &first_binding_as_list); - self.shrink_bodyform_visited( - context, - &mut visited, - prog_args, - &updated_bindings, - Rc::new(BodyForm::Let( - LetFormKind::Sequential, - Box::new(LetData { - bindings: rest_of_bindings, - ..*letdata.clone() - }), - )), - only_inline, + let updated_bindings = update_parallel_bindings(&env, &first_binding_as_list); + Self::request_body( + ShrinkRequest { + prog_args, + env: Rc::new(updated_bindings), + body: Rc::new(BodyForm::Let( + LetFormKind::Sequential, + Box::new(LetData { + bindings: rest_of_bindings, + ..*letdata.clone() + }), + )), + only_inline, + depth, + }, + Continuation::Identity, ) } } BodyForm::Let(LetFormKind::Assign, letdata) => { if eval_dont_expand_let(&letdata.inline_hint) && only_inline { - return Ok(body.clone()); + return Self::body_done(Ok(body.clone())); } - self.shrink_bodyform_visited( - context, - &mut visited, - prog_args, - env, - Rc::new(hoist_assign_form(letdata)?), - only_inline, - ) + match hoist_assign_form(letdata) { + Ok(hoisted) => Self::request_body( + ShrinkRequest { + prog_args, + env, + body: Rc::new(hoisted), + only_inline, + depth, + }, + Continuation::Identity, + ), + Err(error) => Self::body_done(Err(error)), + } } - BodyForm::Quoted(_) => Ok(body.clone()), + BodyForm::Quoted(_) => Self::body_done(Ok(body.clone())), BodyForm::Value(SExp::Atom(l, name)) => { - if name == &"@".as_bytes().to_vec() { - let literal_args = synthesize_args(prog_args.clone(), env)?; - self.shrink_bodyform_visited( - context, - &mut visited, - prog_args, - env, - literal_args, - only_inline, - ) + if name == b"@" { + match synthesize_args(prog_args.clone(), &env) { + Ok(literal_args) => Self::request_body( + ShrinkRequest { + prog_args, + env, + body: literal_args, + only_inline, + depth, + }, + Continuation::Identity, + ), + Err(error) => Self::body_done(Err(error)), + } } else if let Some(function) = self.get_function(name) { - self.shrink_bodyform_visited( - context, - &mut visited, - prog_args, - env, - self.create_mod_for_fun(l, function.borrow()), - only_inline, + Self::request_body( + ShrinkRequest { + prog_args, + env, + body: self.create_mod_for_fun(l, function.borrow()), + only_inline, + depth, + }, + Continuation::Identity, + ) + } else if let Some(value) = env.get(name) { + let value = value.clone(); + if reflex_capture(name, value.clone()) { + Self::body_done(Ok(value)) + } else { + Self::request_body( + ShrinkRequest { + prog_args, + env, + body: value, + only_inline, + depth, + }, + Continuation::Identity, + ) + } + } else if let Some(constant) = self.get_constant(name) { + Self::request_body( + ShrinkRequest { + prog_args, + env, + body: constant, + only_inline, + depth, + }, + Continuation::Identity, ) } else { - env.get(name) - .map(|x| { - if reflex_capture(name, x.clone()) { - Ok(x.clone()) - } else { - self.shrink_bodyform_visited( - context, - &mut visited, - prog_args.clone(), - env, - x.clone(), - only_inline, - ) - } - }) - .unwrap_or_else(|| { - self.get_constant(name) - .map(|x| { - self.shrink_bodyform_visited( - context, - &mut visited, - prog_args.clone(), - env, - x, - only_inline, - ) - }) - .unwrap_or_else(|| { - Ok(Rc::new(BodyForm::Value(SExp::Atom( - l.clone(), - name.clone(), - )))) - }) - }) + Self::body_done(Ok(Rc::new(BodyForm::Value(SExp::Atom( + l.clone(), + name.clone(), + ))))) } } - BodyForm::Value(v) => Ok(Rc::new(BodyForm::Quoted(v.clone()))), + BodyForm::Value(v) => Self::body_done(Ok(Rc::new(BodyForm::Quoted(v.clone())))), BodyForm::Call(l, parts, tail) => { if parts.is_empty() { - return Err(CompileErr( + return Self::body_done(Err(CompileErr( l.clone(), "Impossible empty call list".to_string(), - )); + ))); } let head_expr = parts[0].clone(); - let arguments_to_convert: Vec> = - parts.iter().skip(1).cloned().collect(); - - match head_expr.borrow() { - BodyForm::Value(SExp::Atom(_call_loc, call_name)) => self.handle_invoke( - context, - &mut visited, - &CallSpec { - loc: l.clone(), - name: call_name, - args: parts, - original: body.clone(), - tail: tail.clone(), - }, - prog_args, - &arguments_to_convert, - env, - only_inline, - ), - BodyForm::Value(SExp::Integer(_call_loc, call_int)) => self.handle_invoke( - context, - &mut visited, - &CallSpec { - loc: l.clone(), - name: &u8_from_number(call_int.clone()), - args: parts, - original: body.clone(), - tail: None, - }, + let arguments: Vec> = parts.iter().skip(1).cloned().collect(); + + let call = match head_expr.borrow() { + BodyForm::Value(SExp::Atom(_, call_name)) => OwnedCallSpec { + loc: l.clone(), + name: call_name.clone(), + args: parts.clone(), + original: body.clone(), + tail: tail.clone(), + }, + BodyForm::Value(SExp::Integer(_, call_int)) => OwnedCallSpec { + loc: l.clone(), + name: u8_from_number(call_int.clone()), + args: parts.clone(), + original: body.clone(), + tail: None, + }, + _ => { + return Self::body_done(Err(CompileErr( + l.clone(), + format!("Don't know how to call {}", head_expr.to_sexp()), + ))) + } + }; + EvalStep::Request( + EvalRequest::Invoke(InvokeRequest { + call, prog_args, - &arguments_to_convert, + arguments, env, only_inline, - ), - _ => Err(CompileErr( - l.clone(), - format!("Don't know how to call {}", head_expr.to_sexp()), - )), - } + depth, + }), + Continuation::Identity, + ) } BodyForm::Mod(l, program) => { - // A mod form yields the compiled code. let mut symbols = HashMap::new(); - let optimizer = get_optimizer(l, self.opts.clone())?; + let optimizer = match get_optimizer(l, self.opts.clone()) { + Ok(optimizer) => optimizer, + Err(error) => return Self::body_done(Err(error)), + }; let mut context_wrapper = CompileContextWrapper::new(self.runner.clone(), &mut symbols, optimizer); - let code = codegen(context_wrapper.context(), self.opts.clone(), program)?; - Ok(Rc::new(BodyForm::Quoted(code))) + Self::body_done( + codegen(context_wrapper.context(), self.opts.clone(), program) + .map(|code| Rc::new(BodyForm::Quoted(code))), + ) } - BodyForm::Lambda(ldata) => self.enrich_lambda_site_info( - context, - &mut visited, - prog_args, + BodyForm::Lambda(ldata) => EvalStep::Request( + EvalRequest::Enrich(EnrichRequest { + prog_args, + env, + ldata: *ldata.clone(), + only_inline, + depth, + }), + Continuation::Identity, + ), + } + } + + fn resume( + &self, + context: &mut BasicCompileContext, + continuation: Continuation, + result: EvalResult, + ) -> EvalStep { + match continuation { + Continuation::Identity => EvalStep::Complete(result), + Continuation::IsLambdaProgram(request) => { + let evaluated_prog = match Self::body_result(result, request.parts[1].loc()) { + Ok(body) => body, + Err(error) => return EvalStep::Complete(Err(error)), + }; + Self::request_body( + ShrinkRequest { + prog_args: request.prog_args.clone(), + env: request.env.clone(), + body: request.parts[2].clone(), + only_inline: request.only_inline, + depth: request.depth, + }, + Continuation::IsLambdaEnv { + evaluated_prog, + request, + }, + ) + } + Continuation::IsLambdaEnv { + evaluated_prog, + request, + } => { + let evaluated_env = match Self::body_result(result, request.parts[2].loc()) { + Ok(body) => body, + Err(error) => return EvalStep::Complete(Err(error)), + }; + let applied = match evaluated_prog.borrow() { + BodyForm::Lambda(ldata) => Some(LambdaApply { + lambda: *ldata.clone(), + body: ldata.body.clone(), + env: evaluated_env, + }), + _ => None, + }; + EvalStep::Complete(Ok(EvalValue::Lambda(applied))) + } + Continuation::LambdaCaptures(request) => { + let reified = match Self::body_result(result, request.lapply.lambda.captures.loc()) + { + Ok(body) => body, + Err(error) => return Self::body_done(Err(error)), + }; + let mut lambda_env = (*request.env).clone(); + if let Err(error) = create_argument_captures( + &mut lambda_env, + &ArgInputs::Whole(reified), + request.lapply.lambda.capture_args.clone(), + ) { + return Self::body_done(Err(error)); + } + if let Err(error) = create_argument_captures( + &mut lambda_env, + &ArgInputs::Whole(request.lapply.env.clone()), + request.lapply.lambda.args.clone(), + ) { + return Self::body_done(Err(error)); + } + Self::request_body( + ShrinkRequest { + prog_args: request.lapply.lambda.args, + env: Rc::new(lambda_env), + body: request.lapply.body, + only_inline: request.only_inline, + depth: request.depth, + }, + Continuation::Identity, + ) + } + Continuation::PrimitiveArg(mut state) => { + let index = state.next; + let shrunk = match Self::body_result(result, state.arguments[index].loc()) { + Ok(body) => body, + Err(error) => return Self::body_done(Err(error)), + }; + state.target[index + 1] = shrunk.clone(); + state.converted[index] = Some(shrunk.to_sexp()); + state.all_primitive &= arg_inputs_primitive(Rc::new(ArgInputs::Whole(shrunk))); + self.next_primitive_argument(context, state) + } + Continuation::PrimitiveLambda(state) => match result { + Ok(EvalValue::Lambda(Some(applied))) => EvalStep::Request( + EvalRequest::Lambda(LambdaRequest { + prog_args: state.prog_args, + env: state.env, + lapply: applied, + only_inline: state.only_inline, + depth: state.depth, + }), + Continuation::Identity, + ), + Ok(EvalValue::Lambda(None)) => EvalStep::Request( + EvalRequest::Chase(ChaseRequest { + body: Rc::new(BodyForm::Call( + state.call.loc, + state.target, + state.call.tail, + )), + depth: state.depth, + }), + Continuation::Identity, + ), + Ok(EvalValue::Body(_)) => Self::body_done(Err(CompileErr( + state.call.loc, + "internal evaluator return type mismatch".to_string(), + ))), + Err(error) => Self::body_done(Err(error)), + }, + Continuation::Chase { depth } => match result { + Ok(EvalValue::Body(body)) => EvalStep::Request( + EvalRequest::Chase(ChaseRequest { body, depth }), + Continuation::Identity, + ), + other => EvalStep::Complete(other), + }, + Continuation::ContinueApply { depth } => match result { + Ok(EvalValue::Body(body)) => EvalStep::Request( + EvalRequest::Chase(ChaseRequest { body, depth }), + Continuation::Identity, + ), + other => EvalStep::Complete(other), + }, + Continuation::MashOrOriginal { original } => match result { + Ok(value) => EvalStep::Complete(Ok(value)), + Err(_) => Self::body_done(Ok(original)), + }, + Continuation::MashTrue { + x_head, + cond, + iffalse, + apply_head, env, + location, + depth, + } => { + let true_result = match Self::body_result(result, location.clone()) { + Ok(body) => body, + Err(error) => return Self::body_done(Err(error)), + }; + EvalStep::Request( + EvalRequest::Chase(ChaseRequest { + body: Rc::new(BodyForm::Call( + iffalse.loc(), + vec![apply_head, iffalse, env], + None, + )), + depth, + }), + Continuation::MashFalse { + x_head, + cond, + true_result, + location, + }, + ) + } + Continuation::MashFalse { + x_head, + cond, + true_result, + location, + } => { + let false_result = match Self::body_result(result, location.clone()) { + Ok(body) => body, + Err(error) => return Self::body_done(Err(error)), + }; + Self::body_done(Ok(Rc::new(BodyForm::Call( + location, + vec![ + x_head, + flatten_expression_to_names(cond.to_sexp()), + flatten_expression_to_names(true_result.to_sexp()), + flatten_expression_to_names(false_result.to_sexp()), + ], + None, + )))) + } + Continuation::DefunTail(state) => { + let tail = match Self::body_result(result, state.call_loc.clone()) { + Ok(body) => body, + Err(error) => return Self::body_done(Err(error)), + }; + self.start_defun_captures(state, Some(tail)) + } + Continuation::DefunCapture(mut state) => { + let index = state.next - 1; + let shrunk = match Self::body_result(result, state.captures[index].1.loc()) { + Ok(body) => body, + Err(error) => return Self::body_done(Err(error)), + }; + state + .translated + .insert(state.captures[index].0.clone(), shrunk); + self.next_defun_capture(state) + } + Continuation::EnrichCaptures(request) => { + let new_captures = match Self::body_result(result, request.ldata.captures.loc()) { + Ok(body) => body, + Err(error) => return Self::body_done(Err(error)), + }; + let mut arg_captures = HashMap::new(); + if let Err(error) = create_argument_captures( + &mut arg_captures, + &decons_args(new_captures.clone()), + request.ldata.capture_args.clone(), + ) { + return Self::body_done(Err(error)); + } + let interpretable: HashMap<_, _> = arg_captures + .into_iter() + .filter(|(_, value)| dequote(value.loc(), value.clone()).is_ok()) + .collect(); + let combined_args = Rc::new(SExp::Cons( + request.ldata.loc.clone(), + request.ldata.capture_args.clone(), + request.ldata.args.clone(), + )); + Self::request_body( + ShrinkRequest { + prog_args: combined_args, + env: Rc::new(interpretable.clone()), + body: request.ldata.body.clone(), + only_inline: request.only_inline, + depth: request.depth, + }, + Continuation::EnrichBody { + ldata: request.ldata, + new_captures, + interpretable, + }, + ) + } + Continuation::EnrichBody { ldata, - only_inline, - ), + new_captures, + interpretable, + } => { + let simplified = match Self::body_result(result, ldata.body.loc()) { + Ok(body) => body, + Err(error) => return Self::body_done(Err(error)), + }; + let new_capture_args = + filter_capture_args(ldata.capture_args.clone(), &interpretable); + Self::body_done(Ok(Rc::new(BodyForm::Lambda(Box::new(LambdaData { + args: ldata.args.clone(), + capture_args: new_capture_args, + captures: new_captures, + body: simplified, + ..ldata + }))))) + } } } @@ -1531,19 +1924,54 @@ impl<'info> Evaluator { only_inline: bool, stack_limit: Option, ) -> Result, CompileErr> { - let visited_info = VisitedInfo { + let mut state = VisitedInfo { max_depth: stack_limit, ..Default::default() }; - let mut visited_marker = VisitedMarker::new(visited_info); - self.shrink_bodyform_visited( + let mut continuations = Vec::new(); + let mut step = self.dispatch( context, - &mut visited_marker, - prog_args, - env, - body, - only_inline, - ) + EvalRequest::Shrink(ShrinkRequest { + prog_args, + env: Rc::new(env.clone()), + body, + only_inline, + depth: 1, + }), + &mut state, + ); + loop { + step = match step { + EvalStep::Request(request, continuation) => { + if state + .max_depth + .is_some_and(|limit| continuations.len() >= limit) + { + self.resume( + context, + continuation, + Err(CompileErr( + request.loc(), + "stack limit exceeded".to_string(), + )), + ) + } else { + continuations.push(continuation); + self.dispatch(context, request, &mut state) + } + } + EvalStep::Complete(result) => { + if let Some(continuation) = continuations.pop() { + self.resume(context, continuation, result) + } else { + return Self::body_result( + result, + Srcloc::start(&"*evaluator*".to_string()), + ); + } + } + }; + } } fn expand_macro( diff --git a/src/tests/compiler/evaluate.rs b/src/tests/compiler/evaluate.rs index b7231f8da..7de044664 100644 --- a/src/tests/compiler/evaluate.rs +++ b/src/tests/compiler/evaluate.rs @@ -5,7 +5,7 @@ use clvm_rs::allocator::Allocator; use crate::compiler::compiler::compile_file; use crate::compiler::compiler::DefaultCompilerOpts; -use crate::compiler::comptypes::{CompileErr, CompilerOpts}; +use crate::compiler::comptypes::{BodyForm, CompileErr, CompilerOpts, LetData, LetFormKind}; use crate::compiler::evaluate::{Evaluator, EVAL_STACK_LIMIT}; use crate::compiler::frontend::{from_clvm, frontend}; use crate::compiler::optimize::get_optimizer; @@ -51,6 +51,78 @@ fn shrink_expr_from_string(s: String) -> Result { Ok(result_sexp.to_string()) } +fn evaluator_and_context() -> (Evaluator, BasicCompileContext, Rc, Srcloc) { + let runner = Rc::new(DefaultProgramRunner::new()); + let opts: Rc = Rc::new(DefaultCompilerOpts::new(&"*program*".to_string())); + let loc = Srcloc::start(&"*program*".to_string()); + let context = BasicCompileContext::new( + Allocator::new(), + runner.clone(), + HashMap::new(), + get_optimizer(&loc, opts.clone()).unwrap(), + ); + ( + Evaluator::new(opts.clone(), runner, Vec::new()), + context, + opts, + loc, + ) +} + +#[test] +fn test_explicit_stack_limit_boundary() { + let (evaluator, mut context, _, loc) = evaluator_and_context(); + let body = Rc::new(BodyForm::Quoted(SExp::Integer(loc.clone(), 1.into()))); + let args = Rc::new(SExp::Nil(loc.clone())); + let env = HashMap::new(); + + let error = evaluator + .shrink_bodyform( + &mut context, + args.clone(), + &env, + body.clone(), + false, + Some(1), + ) + .unwrap_err(); + assert_eq!(error.1, "stack limit exceeded"); + + assert!(evaluator + .shrink_bodyform(&mut context, args, &env, body, false, Some(2)) + .is_ok()); +} + +#[test] +fn test_deep_evaluation_uses_trampoline() { + let (evaluator, mut context, _, loc) = evaluator_and_context(); + let mut body = Rc::new(BodyForm::Quoted(SExp::Integer(loc.clone(), 7.into()))); + for _ in 0..10_000 { + body = Rc::new(BodyForm::Let( + LetFormKind::Sequential, + Box::new(LetData { + loc: loc.clone(), + kw: None, + inline_hint: None, + bindings: Vec::new(), + body, + }), + )); + } + + let result = evaluator + .shrink_bodyform( + &mut context, + Rc::new(SExp::Nil(loc)), + &HashMap::new(), + body, + false, + None, + ) + .unwrap(); + assert_eq!(result.to_sexp().to_string(), "(q . 7)"); +} + #[test] fn test_basic_shrink_arithmetic() { assert_eq!( From c93d37081352589f37002966ff3ced3117de9a4d Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 6 Aug 2026 02:36:18 +0000 Subject: [PATCH 08/10] Test deep evaluator stack limits Co-authored-by: arty --- src/tests/compiler/evaluate.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/tests/compiler/evaluate.rs b/src/tests/compiler/evaluate.rs index 7de044664..1d8764b30 100644 --- a/src/tests/compiler/evaluate.rs +++ b/src/tests/compiler/evaluate.rs @@ -110,6 +110,18 @@ fn test_deep_evaluation_uses_trampoline() { )); } + let error = evaluator + .shrink_bodyform( + &mut context, + Rc::new(SExp::Nil(loc.clone())), + &HashMap::new(), + body.clone(), + false, + Some(100), + ) + .unwrap_err(); + assert_eq!(error.1, "stack limit exceeded"); + let result = evaluator .shrink_bodyform( &mut context, From 3b0dc5ce74f15d09c0985b7cd4953415440f6c72 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 6 Aug 2026 02:40:51 +0000 Subject: [PATCH 09/10] Preserve logical evaluator depth semantics Co-authored-by: arty --- src/compiler/evaluate.rs | 37 ++----------------------------------- 1 file changed, 2 insertions(+), 35 deletions(-) diff --git a/src/compiler/evaluate.rs b/src/compiler/evaluate.rs index 98d41893b..e33b23da3 100644 --- a/src/compiler/evaluate.rs +++ b/src/compiler/evaluate.rs @@ -124,25 +124,6 @@ enum EvalRequest { Enrich(EnrichRequest), } -impl EvalRequest { - fn loc(&self) -> Srcloc { - match self { - Self::Shrink(request) => request.body.loc(), - Self::IsLambda(request) => request - .parts - .first() - .map(|part| part.loc()) - .unwrap_or_else(|| Srcloc::start(&"*evaluator*".to_string())), - Self::Primitive(request) => request.call.loc.clone(), - Self::Lambda(request) => request.lapply.body.loc(), - Self::Invoke(request) => request.call.loc.clone(), - Self::Chase(request) => request.body.loc(), - Self::Mash(request) => request.maybe_condition.loc(), - Self::Enrich(request) => request.ldata.loc.clone(), - } - } -} - enum EvalValue { Body(Rc), Lambda(Option), @@ -1943,22 +1924,8 @@ impl Evaluator { loop { step = match step { EvalStep::Request(request, continuation) => { - if state - .max_depth - .is_some_and(|limit| continuations.len() >= limit) - { - self.resume( - context, - continuation, - Err(CompileErr( - request.loc(), - "stack limit exceeded".to_string(), - )), - ) - } else { - continuations.push(continuation); - self.dispatch(context, request, &mut state) - } + continuations.push(continuation); + self.dispatch(context, request, &mut state) } EvalStep::Complete(result) => { if let Some(continuation) = continuations.pop() { From 9e70922d57bc506acbbefc3a976fff68f83c07c1 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 6 Aug 2026 17:27:14 +0000 Subject: [PATCH 10/10] Finish evaluator trampoline cleanup Co-authored-by: arty --- src/compiler/evaluate.rs | 41 ++++++++++---------- src/compiler/mod.rs | 2 - src/compiler/stackvisit.rs | 77 -------------------------------------- 3 files changed, 21 insertions(+), 99 deletions(-) delete mode 100644 src/compiler/stackvisit.rs diff --git a/src/compiler/evaluate.rs b/src/compiler/evaluate.rs index e33b23da3..11927ad96 100644 --- a/src/compiler/evaluate.rs +++ b/src/compiler/evaluate.rs @@ -211,7 +211,7 @@ enum Continuation { } enum EvalStep { - Request(EvalRequest, Continuation), + Request(Box, Box), Complete(EvalResult), } @@ -892,7 +892,11 @@ impl Evaluator { } fn request_body(request: ShrinkRequest, continuation: Continuation) -> EvalStep { - EvalStep::Request(EvalRequest::Shrink(request), continuation) + Self::request(EvalRequest::Shrink(request), continuation) + } + + fn request(request: EvalRequest, continuation: Continuation) -> EvalStep { + EvalStep::Request(Box::new(request), Box::new(continuation)) } #[allow(clippy::too_many_arguments)] @@ -1101,7 +1105,7 @@ impl Evaluator { }; } - EvalStep::Request( + Self::request( EvalRequest::IsLambda(IsLambdaRequest { prog_args: state.prog_args.clone(), env: state.env.clone(), @@ -1147,7 +1151,7 @@ impl Evaluator { None, )), ); - return EvalStep::Request( + return Self::request( EvalRequest::Chase(ChaseRequest { body: Rc::new(BodyForm::Call( iftrue.loc(), @@ -1180,7 +1184,7 @@ impl Evaluator { return self.continue_apply(vec[2].clone(), run_program, request.depth); } if self.mash_conditions { - return EvalStep::Request( + return Self::request( EvalRequest::Mash(MashRequest { maybe_condition: vec[1].clone(), env: vec[2].clone(), @@ -1245,7 +1249,7 @@ impl Evaluator { self.start_defun_captures(state, None) } } - _ => EvalStep::Request( + _ => Self::request( EvalRequest::Primitive(PrimitiveRequest { call: request.call, prog_args: request.prog_args, @@ -1371,7 +1375,7 @@ impl Evaluator { .parts .first() .map(|part| part.loc()) - .unwrap_or_else(|| Srcloc::start(&"*evaluator*".to_string())); + .unwrap_or_else(|| Srcloc::start("*evaluator*")); request.depth = match Self::increment_depth(state, request.depth, loc) { Ok(depth) => depth, Err(error) => { @@ -1581,7 +1585,7 @@ impl Evaluator { ))) } }; - EvalStep::Request( + Self::request( EvalRequest::Invoke(InvokeRequest { call, prog_args, @@ -1606,7 +1610,7 @@ impl Evaluator { .map(|code| Rc::new(BodyForm::Quoted(code))), ) } - BodyForm::Lambda(ldata) => EvalStep::Request( + BodyForm::Lambda(ldata) => Self::request( EvalRequest::Enrich(EnrichRequest { prog_args, env, @@ -1708,7 +1712,7 @@ impl Evaluator { self.next_primitive_argument(context, state) } Continuation::PrimitiveLambda(state) => match result { - Ok(EvalValue::Lambda(Some(applied))) => EvalStep::Request( + Ok(EvalValue::Lambda(Some(applied))) => Self::request( EvalRequest::Lambda(LambdaRequest { prog_args: state.prog_args, env: state.env, @@ -1718,7 +1722,7 @@ impl Evaluator { }), Continuation::Identity, ), - Ok(EvalValue::Lambda(None)) => EvalStep::Request( + Ok(EvalValue::Lambda(None)) => Self::request( EvalRequest::Chase(ChaseRequest { body: Rc::new(BodyForm::Call( state.call.loc, @@ -1736,14 +1740,14 @@ impl Evaluator { Err(error) => Self::body_done(Err(error)), }, Continuation::Chase { depth } => match result { - Ok(EvalValue::Body(body)) => EvalStep::Request( + Ok(EvalValue::Body(body)) => Self::request( EvalRequest::Chase(ChaseRequest { body, depth }), Continuation::Identity, ), other => EvalStep::Complete(other), }, Continuation::ContinueApply { depth } => match result { - Ok(EvalValue::Body(body)) => EvalStep::Request( + Ok(EvalValue::Body(body)) => Self::request( EvalRequest::Chase(ChaseRequest { body, depth }), Continuation::Identity, ), @@ -1766,7 +1770,7 @@ impl Evaluator { Ok(body) => body, Err(error) => return Self::body_done(Err(error)), }; - EvalStep::Request( + Self::request( EvalRequest::Chase(ChaseRequest { body: Rc::new(BodyForm::Call( iffalse.loc(), @@ -1925,16 +1929,13 @@ impl Evaluator { step = match step { EvalStep::Request(request, continuation) => { continuations.push(continuation); - self.dispatch(context, request, &mut state) + self.dispatch(context, *request, &mut state) } EvalStep::Complete(result) => { if let Some(continuation) = continuations.pop() { - self.resume(context, continuation, result) + self.resume(context, *continuation, result) } else { - return Self::body_result( - result, - Srcloc::start(&"*evaluator*".to_string()), - ); + return Self::body_result(result, Srcloc::start("*evaluator*")); } } }; diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs index d63c68e21..3fee0c9e2 100644 --- a/src/compiler/mod.rs +++ b/src/compiler/mod.rs @@ -77,8 +77,6 @@ pub mod runtypes; pub mod sexp; /// Support for preserving the association between clvm data and locations in the source code. pub mod srcloc; -/// Support for limiting stack depth during evaluation. -pub mod stackvisit; /// Support for determining whether program argument values will be used statically. pub mod usecheck; diff --git a/src/compiler/stackvisit.rs b/src/compiler/stackvisit.rs deleted file mode 100644 index 276191cf2..000000000 --- a/src/compiler/stackvisit.rs +++ /dev/null @@ -1,77 +0,0 @@ -use std::mem::swap; - -pub trait HasDepthLimit { - fn depth_limit(&self) -> Option; - fn stack_err(&self, loc: L) -> E; -} - -pub trait Unvisit { - fn give_back(&mut self, info: Option>); - fn take(&mut self) -> Option>; - fn depth(&self) -> usize; -} - -pub struct VisitedMarker<'info, T> { - pub info: Option>, - pub prev: Option<&'info mut dyn Unvisit>, - pub depth: usize, -} - -impl<'info, T> VisitedMarker<'info, T> { - pub fn new(info: T) -> VisitedMarker<'static, T> { - VisitedMarker { - info: Some(Box::new(info)), - prev: None, - depth: 1, - } - } - - // Each new level takes the info box and adds one depth. - pub fn again( - loc: L, - prev: &'info mut dyn Unvisit, - ) -> Result, E> - where - T: HasDepthLimit, - { - let info = prev.take(); - let depth = prev.depth(); - if let Some(ref info) = info { - if let Some(limit) = info.depth_limit() { - if depth >= limit { - return Err(info.stack_err(loc)); - } - } - } - Ok(VisitedMarker { - info, - prev: Some(prev), - depth: depth + 1, - }) - } -} - -impl Unvisit for VisitedMarker<'_, T> { - fn give_back(&mut self, info: Option>) { - self.info = info; - } - fn take(&mut self) -> Option> { - let mut info = None; - swap(&mut self.info, &mut info); - info - } - fn depth(&self) -> usize { - self.depth - } -} - -// When dropped, the info box is handed back. -impl Drop for VisitedMarker<'_, T> { - fn drop(&mut self) { - let mut info = None; - swap(&mut self.info, &mut info); - if let Some(ref mut prev) = self.prev { - prev.give_back(info); - } - } -}