From 5fc7677f92e3a635b98d8a6312079cfb0fd75c73 Mon Sep 17 00:00:00 2001 From: arty Date: Wed, 25 Feb 2026 09:31:49 -0800 Subject: [PATCH 01/26] part 1, introduce the cache --- src/classic/bins/repl.rs | 8 +- src/classic/bins/shrink.rs | 8 +- src/compiler/codegen.rs | 102 +++++++++++++++++- src/compiler/mod.rs | 24 ++--- src/compiler/usecheck.rs | 12 +-- src/tests/compiler/compiler.rs | 24 ++--- src/tests/compiler/evaluate.rs | 12 +-- .../compiler/optimizer/cse_regression.rs | 24 ++--- src/tests/compiler/repl.rs | 12 +-- src/tests/compiler/resolve.rs | 12 +-- 10 files changed, 165 insertions(+), 73 deletions(-) diff --git a/src/classic/bins/repl.rs b/src/classic/bins/repl.rs index 9375f2f96..b5d190eb5 100644 --- a/src/classic/bins/repl.rs +++ b/src/classic/bins/repl.rs @@ -25,12 +25,8 @@ fn main() { } }; let runner = Rc::new(DefaultProgramRunner::new()); - let mut context = BasicCompileContext { - allocator: Allocator::new(), - runner: runner.clone(), - symbols: HashMap::new(), - optimizer, - }; + let mut context = + BasicCompileContext::new(Allocator::new(), runner.clone(), HashMap::new(), optimizer); let stdin = io::stdin(); let mut repl = Repl::new(opts, runner); diff --git a/src/classic/bins/shrink.rs b/src/classic/bins/shrink.rs index ffbbbdd81..29efa7d0c 100644 --- a/src/classic/bins/shrink.rs +++ b/src/classic/bins/shrink.rs @@ -27,12 +27,8 @@ fn main() { return; } }; - let mut context = BasicCompileContext { - allocator: Allocator::new(), - runner: runner.clone(), - symbols: HashMap::new(), - optimizer, - }; + let mut context = + BasicCompileContext::new(Allocator::new(), runner.clone(), HashMap::new(), optimizer); let args: Vec = env::args().collect(); if args.len() < 2 { println!("give a chialisp program to minify"); diff --git a/src/compiler/codegen.rs b/src/compiler/codegen.rs index f45a08000..23bd96941 100644 --- a/src/compiler/codegen.rs +++ b/src/compiler/codegen.rs @@ -8,7 +8,7 @@ use num_bigint::ToBigInt; use crate::classic::clvm::__type_compatibility__::bi_one; -use crate::compiler::clvm::{run, truthy}; +use crate::compiler::clvm::{run, sha256tree, truthy}; use crate::compiler::compiler::is_at_capture; use crate::compiler::comptypes::{ fold_m, join_vecs_to_string, list_to_cons, Binding, BindingPattern, BodyForm, CallSpec, @@ -26,6 +26,7 @@ use crate::compiler::prims::{primapply, primcons, primquote}; use crate::compiler::runtypes::RunFailure; use crate::compiler::sexp::{decode_string, printable, SExp}; use crate::compiler::srcloc::Srcloc; +use crate::compiler::Funcache; use crate::compiler::StartOfCodegenOptimization; use crate::compiler::{BasicCompileContext, CompileContextWrapper}; use crate::util::{toposort, u8_from_number, TopoSortItem}; @@ -839,6 +840,79 @@ fn fail_if_present( } } +// Remove anything in the environment that isn't used by the caller and replace it with nil. +// the effect of this is to make only positional changes of things this function depends on +// matter. If a rearrangement occurs in things the caller doesn't depend on that leads to +// the same arrangement of things it does, then the output will be the same. +fn filter_env(used: &[Vec], nil: Rc, env: Rc) -> Rc { + match env.atomize() { + SExp::Atom(_l, n) => { + if used.iter().any(|u| *u != n) { + return nil.clone(); + } + env + } + SExp::Cons(l, a, b) => { + let new_a = filter_env(used, nil.clone(), a.clone()); + let new_b = filter_env(used, nil.clone(), b.clone()); + let old_a_ptr = &*a; + let old_b_ptr = &*b; + let new_a_ptr = &*new_a; + let new_b_ptr = &*new_b; + if old_a_ptr == new_a_ptr && old_b_ptr == new_b_ptr { + return env; + } + Rc::new(SExp::Cons(l.clone(), new_a, new_b)) + } + _ => env, + } +} + +// Make an sexp that contains the form of everything the caller depends on. Any change in these, +// such as whether a function is inline or not, the form of the environment or whether any constant +// is inline or tabled, will change this value and therefore its hash. If nohting relevant changed +// then this value will be the same and a previously generated version of the caller's function +// body can be used. +fn get_depended_on_forms( + compiler: &PrimaryCodegen, + loc: Srcloc, + mut env_shape: Rc, + used: &[Vec], +) -> Rc { + for name in used.iter() { + if let Some(c) = compiler.constants.get(name) { + env_shape = Rc::new(SExp::Cons(loc.clone(), c.clone(), env_shape)); + } else if let Some(c) = compiler.tabled_constants.get(name) { + env_shape = Rc::new(SExp::Cons(loc.clone(), c.clone(), env_shape)); + } else if let Some(i) = compiler.inlines.get(name) { + env_shape = Rc::new(SExp::Cons(loc.clone(), i.to_sexp(), env_shape)); + } + } + env_shape +} + +fn get_function_cache_key( + compiler: &PrimaryCodegen, + fc: &Funcache, + helper: &HelperForm, +) -> Vec { + let mut depends_on_set = HashSet::new(); + fc.dependency_graph + .get_full_depends_on(&mut depends_on_set, helper.name()); + let mut depends_on: Vec<_> = depends_on_set.into_iter().collect(); + depends_on.sort(); + // Collect depended on function forms + let filtered_env = filter_env( + &depends_on, + Rc::new(SExp::Nil(helper.loc())), + compiler.env.clone(), + ); + let depended_on = + get_depended_on_forms(compiler, helper.loc(), filtered_env.clone(), &depends_on); + let hashable = Rc::new(SExp::Cons(helper.loc(), helper.to_sexp(), depended_on)); + sha256tree(hashable) +} + fn codegen_( context: &mut BasicCompileContext, opts: Rc, @@ -859,6 +933,28 @@ fn codegen_( }, )) } else { + let cache_key = context + .funcache + .as_ref() + .map(|fc| get_function_cache_key(compiler, fc, h)); + + if let Some(code) = cache_key.as_ref().and_then(|key| { + context + .funcache + .as_ref() + .and_then(|c| c.function_outputs.get(key).cloned()) + }) { + return Ok(compiler.add_defun( + &defun.name, + defun.orig_args.clone(), + DefunCall { + required_env: defun.args.clone(), + code: code.clone(), + }, + true, // Always take left env for now + )); + } + let updated_opts = opts .set_code_generator(compiler.clone()) .set_in_defun(true) @@ -900,6 +996,10 @@ fn codegen_( fail_if_present(defun.loc.clone(), &compiler.defuns, &defun.name, code) }) .map(|code| { + if let (Some(fc), Some(hash)) = (&mut context.funcache, cache_key) { + fc.function_outputs.insert(hash, code.clone()); + } + compiler.add_defun( &defun.name, defun.orig_args.clone(), diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs index 44826f16b..7c0fda046 100644 --- a/src/compiler/mod.rs +++ b/src/compiler/mod.rs @@ -45,9 +45,15 @@ use crate::classic::clvm_tools::stages::stage_0::TRunProgram; use crate::compiler::comptypes::{ BodyForm, CompileErr, CompileForm, CompilerOpts, DefunData, HelperForm, PrimaryCodegen, }; +use crate::compiler::optimize::depgraph::FunctionDependencyGraph; use crate::compiler::optimize::Optimization; use crate::compiler::sexp::SExp; +pub struct Funcache { + function_outputs: HashMap, Rc>, + dependency_graph: FunctionDependencyGraph, +} + /// An object which represents the standard set of mutable items passed down the /// stack when compiling chialisp. pub struct BasicCompileContext { @@ -55,6 +61,9 @@ pub struct BasicCompileContext { pub runner: Rc, pub symbols: HashMap, pub optimizer: Box, + /// Given the operative environment and a serialization of the helper, this is the generated + /// code from that helper. + pub funcache: Option, } impl BasicCompileContext { @@ -202,6 +211,7 @@ impl BasicCompileContext { runner, symbols, optimizer, + funcache: None, } } } @@ -241,12 +251,7 @@ impl<'a> CompileContextWrapper<'a> { symbols: &'a mut HashMap, optimizer: Box, ) -> Self { - let bcc = BasicCompileContext { - allocator: Allocator::new(), - runner, - symbols: HashMap::new(), - optimizer, - }; + let bcc = BasicCompileContext::new(Allocator::new(), runner, HashMap::new(), optimizer); let mut wrapper = CompileContextWrapper { allocator, symbols, @@ -262,12 +267,7 @@ impl<'a> CompileContextWrapper<'a> { ) -> Self { let runner = context.runner(); let optimizer = context.optimizer.duplicate(); - let bcc = BasicCompileContext { - allocator: Allocator::new(), - runner, - symbols: HashMap::new(), - optimizer, - }; + let bcc = BasicCompileContext::new(Allocator::new(), runner, HashMap::new(), optimizer); let mut wrapper = CompileContextWrapper { allocator: &mut context.allocator, symbols, diff --git a/src/compiler/usecheck.rs b/src/compiler/usecheck.rs index c2fe0b640..3c7ca0553 100644 --- a/src/compiler/usecheck.rs +++ b/src/compiler/usecheck.rs @@ -97,12 +97,12 @@ pub fn check_parameters_used_compileform( program.args.clone(), ); - let mut context = BasicCompileContext { - allocator: Allocator::new(), - runner: runner.clone(), - symbols: HashMap::new(), - optimizer: get_optimizer(&program.loc(), opts.clone())?, - }; + let mut context = BasicCompileContext::new( + Allocator::new(), + runner.clone(), + HashMap::new(), + get_optimizer(&program.loc(), opts.clone())?, + ); let result = e.shrink_bodyform( &mut context, program.args.clone(), diff --git a/src/tests/compiler/compiler.rs b/src/tests/compiler/compiler.rs index e4969f5c3..8af21c158 100644 --- a/src/tests/compiler/compiler.rs +++ b/src/tests/compiler/compiler.rs @@ -2427,12 +2427,12 @@ fn test_handle_explicit_empty_atom() { ]); let runner = Rc::new(DefaultProgramRunner::new()); - let mut context = BasicCompileContext { - allocator: Allocator::new(), - runner: runner.clone(), - symbols: HashMap::new(), - optimizer: get_optimizer(&program.loc(), opts.clone()).unwrap(), - }; + let mut context = BasicCompileContext::new( + Allocator::new(), + runner.clone(), + HashMap::new(), + get_optimizer(&program.loc(), opts.clone()).unwrap(), + ); let compiled = opts .compile_program(&mut context, program) .expect("should compile"); @@ -2515,12 +2515,12 @@ fn test_exhaustive_chars() { let dialect = KNOWN_DIALECTS["*standard-cl-23.1*"].accepted.clone(); opts = opts.set_dialect(dialect); - let mut context = BasicCompileContext { - allocator: Allocator::new(), - runner: runner.clone(), - symbols: HashMap::new(), - optimizer: get_optimizer(&sub_qe.loc(), opts.clone()).unwrap(), - }; + let mut context = BasicCompileContext::new( + Allocator::new(), + runner.clone(), + HashMap::new(), + get_optimizer(&sub_qe.loc(), opts.clone()).unwrap(), + ); let compiled = opts .compile_program(&mut context, make_test_program(sub_qe)) .expect("should compile"); diff --git a/src/tests/compiler/evaluate.rs b/src/tests/compiler/evaluate.rs index 6c45c5f28..b7231f8da 100644 --- a/src/tests/compiler/evaluate.rs +++ b/src/tests/compiler/evaluate.rs @@ -22,12 +22,12 @@ fn shrink_expr_from_string(s: String) -> Result { let runner = Rc::new(DefaultProgramRunner::new()); let opts = Rc::new(DefaultCompilerOpts::new(&"*program*".to_string())); let loc = Srcloc::start(&"*program*".to_string()); - let mut context = BasicCompileContext { - allocator: Allocator::new(), - runner: runner.clone(), - symbols: HashMap::new(), - optimizer: get_optimizer(&loc, opts.clone()).unwrap(), - }; + let mut context = BasicCompileContext::new( + Allocator::new(), + runner.clone(), + HashMap::new(), + get_optimizer(&loc, opts.clone()).unwrap(), + ); let result = parse_sexp(loc.clone(), s.bytes()) .err_into() .and_then(|parsed_program| { diff --git a/src/tests/compiler/optimizer/cse_regression.rs b/src/tests/compiler/optimizer/cse_regression.rs index b3396968f..7e5a93d98 100644 --- a/src/tests/compiler/optimizer/cse_regression.rs +++ b/src/tests/compiler/optimizer/cse_regression.rs @@ -126,21 +126,21 @@ fn test_cse_merge_regression() { .set_frontend_opt(false) .set_diag_flags(old_flags); let runner = Rc::new(DefaultProgramRunner::new()); - let mut new_context = BasicCompileContext { - allocator: Allocator::new(), - runner: runner.clone(), - symbols: HashMap::new(), - optimizer: get_optimizer(&program_sexp.loc(), new_opts.clone()).unwrap(), - }; + let mut new_context = BasicCompileContext::new( + Allocator::new(), + runner.clone(), + HashMap::new(), + get_optimizer(&program_sexp.loc(), new_opts.clone()).unwrap(), + ); let new_compiled = new_opts .compile_program(&mut new_context, program_sexp.clone()) .expect("should compile (new)"); - let mut old_context = BasicCompileContext { - allocator: Allocator::new(), - runner: runner.clone(), - symbols: HashMap::new(), - optimizer: get_optimizer(&program_sexp.loc(), old_opts.clone()).unwrap(), - }; + let mut old_context = BasicCompileContext::new( + Allocator::new(), + runner.clone(), + HashMap::new(), + get_optimizer(&program_sexp.loc(), old_opts.clone()).unwrap(), + ); let old_compiled = old_opts .compile_program(&mut old_context, program_sexp) .expect("should compile (old)"); diff --git a/src/tests/compiler/repl.rs b/src/tests/compiler/repl.rs index a82a9a918..db8a4d785 100644 --- a/src/tests/compiler/repl.rs +++ b/src/tests/compiler/repl.rs @@ -27,12 +27,12 @@ where repl.set_stack_limit(Some(limit)); } - let mut context = BasicCompileContext { - allocator: Allocator::new(), - runner: runner.clone(), - symbols: HashMap::new(), - optimizer: get_optimizer(&Srcloc::start("*test*"), opts.clone()).unwrap(), - }; + let mut context = BasicCompileContext::new( + Allocator::new(), + runner.clone(), + HashMap::new(), + get_optimizer(&Srcloc::start("*test*"), opts.clone()).unwrap(), + ); for i in inputs.iter() { res = res.and_then(|_| repl.process_line(&mut context, i.to_string())); } diff --git a/src/tests/compiler/resolve.rs b/src/tests/compiler/resolve.rs index 8ba384102..6dd44549d 100644 --- a/src/tests/compiler/resolve.rs +++ b/src/tests/compiler/resolve.rs @@ -25,12 +25,12 @@ fn compile_program_get_result( let processed = frontend(opts.clone(), &parsed)?; let resolved = resolve_namespaces(opts.clone(), &processed.compileform())?; let desugared = do_desugar(&resolved)?; - let mut context = BasicCompileContext { - allocator: Allocator::new(), - runner: Rc::new(DefaultProgramRunner::new()), - symbols: HashMap::new(), - optimizer: get_optimizer(&loc, opts.clone())?, - }; + let mut context = BasicCompileContext::new( + Allocator::new(), + Rc::new(DefaultProgramRunner::new()), + HashMap::new(), + get_optimizer(&loc, opts.clone())?, + ); let generated = codegen(&mut context, opts.clone(), &desugared)?; run( &mut context.allocator, From d341b90fcb648d67569301406ac39155b4e16193 Mon Sep 17 00:00:00 2001 From: arty Date: Mon, 2 Mar 2026 12:27:08 -0800 Subject: [PATCH 02/26] pin chia-bls until the ci gets changed --- Cargo.lock | 6 +- Cargo.toml | 2 +- resources/tests/module/cache-test-1.clsp | 31 ++ resources/tests/module/std/prelude.clinc | 7 - src/compiler/codegen.rs | 53 +++- src/compiler/dialect.rs | 2 +- src/compiler/mod.rs | 26 +- src/tests/compiler/codegen_funcache.rs | 361 +++++++++++++++++++++++ src/tests/compiler/mod.rs | 1 + src/tests/compiler/modules.rs | 6 - 10 files changed, 466 insertions(+), 29 deletions(-) create mode 100644 resources/tests/module/cache-test-1.clsp create mode 100644 src/tests/compiler/codegen_funcache.rs diff --git a/Cargo.lock b/Cargo.lock index 1839f9ef9..b77f725b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -120,9 +120,9 @@ dependencies = [ [[package]] name = "chia-bls" -version = "0.36.1" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f02cbfd038d9050d45edbe8f38e09391c73479c0cca5b37925daf48c4d4fcd4" +checksum = "7d02accd77f2a373e2f50645e2e42ebcb484d8c6a94f5eda6332ad8bc21f48e6" dependencies = [ "blst", "chia-sha2 0.36.1", @@ -203,7 +203,7 @@ name = "chialisp" version = "0.4.1" dependencies = [ "binascii", - "chia-bls 0.36.1", + "chia-bls 0.36.0", "clvmr", "do-notation", "getrandom 0.2.15", diff --git a/Cargo.toml b/Cargo.toml index bcb0793a4..909aff611 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ python-source = "python" [dependencies] binascii = "0.1.4" -chia-bls = "0.36.0" +chia-bls = "=0.36.0" clvmr = { version = "0.16.2", features = ["pre-eval"] } do-notation = "0.1.3" hashlink = "0.11.0" diff --git a/resources/tests/module/cache-test-1.clsp b/resources/tests/module/cache-test-1.clsp new file mode 100644 index 000000000..62dcba11b --- /dev/null +++ b/resources/tests/module/cache-test-1.clsp @@ -0,0 +1,31 @@ +(include *standard-cl-24*) + +(defconst C 33) +(defconst CC 99) + +(defun F (X) + (+ X C) + ) + +(defun G (X) + (sha256 X CC) + ) + +(defun H (X) + (sha256 1 X CC) + ) + +(defun FF (X Y) + (assign + Z (concat X Y) + (sha256 (F X) Y (fake-letbinding Z)) + ) + ) + +(defun-inline fake-letbinding (X) X) + +(defun GG (X Y) + (sha256 (G X) (H Y)) + ) + +(export (X Y) (c (FF X Y) (GG X Y))) diff --git a/resources/tests/module/std/prelude.clinc b/resources/tests/module/std/prelude.clinc index 650da5ebd..f9f176b88 100644 --- a/resources/tests/module/std/prelude.clinc +++ b/resources/tests/module/std/prelude.clinc @@ -34,10 +34,3 @@ @ ) ) - -(defun-inline c* (A B) (c A B)) -(defun-inline a* (A B) (a A B)) -(defun-inline coerce (X) : (Any -> Any) X) -(defun-inline explode (X) : (forall a ((Exec a) -> a)) X) -(defun-inline bless (X) : (forall a ((Pair a Unit) -> (Exec a))) (coerce X))(defun-inline lift (X V) : (forall a (forall b ((Pair (Exec a) (Pair b Unit)) -> (Exec (Pair a b))))) (coerce X)) -(defun-inline unlift (X) : (forall a (forall b ((Pair (Exec (Pair a b)) Unit) -> (Exec b)))) (coerce X)) diff --git a/src/compiler/codegen.rs b/src/compiler/codegen.rs index 23bd96941..2236d95be 100644 --- a/src/compiler/codegen.rs +++ b/src/compiler/codegen.rs @@ -26,9 +26,9 @@ use crate::compiler::prims::{primapply, primcons, primquote}; use crate::compiler::runtypes::RunFailure; use crate::compiler::sexp::{decode_string, printable, SExp}; use crate::compiler::srcloc::Srcloc; -use crate::compiler::Funcache; use crate::compiler::StartOfCodegenOptimization; use crate::compiler::{BasicCompileContext, CompileContextWrapper}; +use crate::compiler::{Funcache, FunctionEntry}; use crate::util::{toposort, u8_from_number, TopoSortItem}; const MACRO_TIME_LIMIT: usize = 1000000; @@ -879,14 +879,43 @@ fn get_depended_on_forms( mut env_shape: Rc, used: &[Vec], ) -> Rc { + let append_with_name = |env_shape: Rc, name: &[u8], body: Rc| { + Rc::new(SExp::Cons( + loc.clone(), + Rc::new(SExp::Cons( + loc.clone(), + Rc::new(SExp::Atom(loc.clone(), name.to_vec())), + body, + )), + env_shape, + )) + }; for name in used.iter() { if let Some(c) = compiler.constants.get(name) { - env_shape = Rc::new(SExp::Cons(loc.clone(), c.clone(), env_shape)); + env_shape = append_with_name( + env_shape, + name, + Rc::new(SExp::Cons( + c.loc(), + Rc::new(SExp::Nil(loc.clone())), + c.clone(), + )), + ); } else if let Some(c) = compiler.tabled_constants.get(name) { - env_shape = Rc::new(SExp::Cons(loc.clone(), c.clone(), env_shape)); + env_shape = append_with_name( + env_shape, + name, + Rc::new(SExp::Cons( + c.loc(), + Rc::new(SExp::Atom(loc.clone(), vec![1])), + c.clone(), + )), + ); } else if let Some(i) = compiler.inlines.get(name) { - env_shape = Rc::new(SExp::Cons(loc.clone(), i.to_sexp(), env_shape)); + env_shape = append_with_name(env_shape, name, i.to_sexp()); } + // Out of line functions are captured by the environment and it's ok for them + // to not have a full definition when generating code for other out of line functions. } env_shape } @@ -910,10 +939,11 @@ fn get_function_cache_key( let depended_on = get_depended_on_forms(compiler, helper.loc(), filtered_env.clone(), &depends_on); let hashable = Rc::new(SExp::Cons(helper.loc(), helper.to_sexp(), depended_on)); + eprintln!("cache key {}: {}", decode_string(helper.name()), hashable); sha256tree(hashable) } -fn codegen_( +pub fn codegen_( context: &mut BasicCompileContext, opts: Rc, compiler: &PrimaryCodegen, @@ -942,14 +972,15 @@ fn codegen_( context .funcache .as_ref() - .and_then(|c| c.function_outputs.get(key).cloned()) + .and_then(|c| c.function_outputs.get(key).map(|e| e.code.clone())) }) { + eprintln!("cache hit {}: {}", decode_string(h.name()), code); return Ok(compiler.add_defun( &defun.name, defun.orig_args.clone(), DefunCall { required_env: defun.args.clone(), - code: code.clone(), + code: code, }, true, // Always take left env for now )); @@ -997,7 +1028,13 @@ fn codegen_( }) .map(|code| { if let (Some(fc), Some(hash)) = (&mut context.funcache, cache_key) { - fc.function_outputs.insert(hash, code.clone()); + fc.function_outputs.insert( + hash, + FunctionEntry { + code: code.clone(), + name: h.name().to_vec(), + }, + ); } compiler.add_defun( diff --git a/src/compiler/dialect.rs b/src/compiler/dialect.rs index 9824fcea7..e4ddf7fc8 100644 --- a/src/compiler/dialect.rs +++ b/src/compiler/dialect.rs @@ -11,7 +11,7 @@ use crate::compiler::sexp::decode_string; /// BASE_STEPPING, all of which should use similar optimizer objects. pub const OPT_STRATEGY_BASE_STEPPING: i32 = 23; /// The maximum stepping of the language so far. -pub const MAX_STEPPING: i32 = 24; +pub const MAX_STEPPING: i32 = 25; /// Specifying how the language is spoken. #[derive(Clone, Debug, Default)] diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs index 7c0fda046..d85b43adf 100644 --- a/src/compiler/mod.rs +++ b/src/compiler/mod.rs @@ -45,13 +45,33 @@ use crate::classic::clvm_tools::stages::stage_0::TRunProgram; use crate::compiler::comptypes::{ BodyForm, CompileErr, CompileForm, CompilerOpts, DefunData, HelperForm, PrimaryCodegen, }; -use crate::compiler::optimize::depgraph::FunctionDependencyGraph; +use crate::compiler::optimize::depgraph::{DepgraphOptions, FunctionDependencyGraph}; use crate::compiler::optimize::Optimization; use crate::compiler::sexp::SExp; +#[derive(Clone)] +pub struct FunctionEntry { + pub name: Vec, + pub code: Rc, +} + pub struct Funcache { - function_outputs: HashMap, Rc>, - dependency_graph: FunctionDependencyGraph, + pub function_outputs: HashMap, FunctionEntry>, + pub dependency_graph: FunctionDependencyGraph, +} + +impl Funcache { + pub fn new(cf: &CompileForm) -> Self { + Funcache { + function_outputs: HashMap::new(), + dependency_graph: FunctionDependencyGraph::new_with_options( + &cf, + DepgraphOptions { + with_constants: true, + }, + ), + } + } } /// An object which represents the standard set of mutable items passed down the diff --git a/src/tests/compiler/codegen_funcache.rs b/src/tests/compiler/codegen_funcache.rs new file mode 100644 index 000000000..b5bb7f183 --- /dev/null +++ b/src/tests/compiler/codegen_funcache.rs @@ -0,0 +1,361 @@ +use std::collections::HashMap; +use std::rc::Rc; + +use clvmr::Allocator; + +use crate::classic::clvm_tools::stages::stage_0::DefaultProgramRunner; + +use crate::compiler::clvm::run; +use crate::compiler::codegen::codegen; +use crate::compiler::compiler::{do_desugar, DefaultCompilerOpts}; +use crate::compiler::comptypes::{ + BodyForm, CompileErr, CompileForm, CompilerOpts, DefconstData, Export, FrontendOutput, + HelperForm, +}; +use crate::compiler::dialect::AcceptedDialect; +use crate::compiler::frontend::frontend; +use crate::compiler::optimize::above22::Strategy23; +use crate::compiler::rename::rename_args_compileform; +use crate::compiler::sexp::{decode_string, parse_sexp, SExp}; +use crate::compiler::srcloc::Srcloc; +use crate::compiler::{BasicCompileContext, Funcache, FunctionEntry}; +use crate::tests::compiler::modules::TestModuleCompilerOpts; + +fn get_expected_outcome( + opts: Rc, + run_arg: Rc, +) -> Result, CompileErr> { + let mut allocator = Allocator::new(); + let runner = Rc::new(DefaultProgramRunner::new()); + let ff_program_base = parse_sexp( + Srcloc::start("*run-ff*"), + "(sha256 (+ 2 (q . 33)) 5 (concat 2 5))".bytes(), + ) + .unwrap()[0] + .clone(); + let gg_program_base = parse_sexp( + Srcloc::start("*run-gg*"), + "(sha256 (sha256 2 (q . 99)) (sha256 (q . 1) 5 (q . 99)))".bytes(), + ) + .unwrap()[0] + .clone(); + let ff_base_outcome = run( + &mut allocator, + runner.clone(), + opts.prim_map(), + ff_program_base, + run_arg.clone(), + None, + None, + )?; + let gg_base_outcome = run( + &mut allocator, + runner.clone(), + opts.prim_map(), + gg_program_base, + run_arg.clone(), + None, + None, + )?; + Ok(Rc::new(SExp::Cons( + run_arg.loc(), + ff_base_outcome.clone(), + gg_base_outcome.clone(), + ))) +} + +fn extract_helper_gen(map: &HashMap, FunctionEntry>, name: &str) -> Option> { + map.values() + .filter(|v| decode_string(&v.name) == name) + .next() + .map(|v| v.code.clone()) +} + +fn flip_inline(h: &HelperForm, toward: bool) -> Option { + if let HelperForm::Defun(_inl, defdata) = h { + return Some(HelperForm::Defun(toward, defdata.clone())); + } + None +} + +fn match_name_prefix(prefix: &str, h: &HelperForm) -> bool { + decode_string(h.name()).starts_with(prefix) +} + +fn untable_constant(h: &HelperForm) -> Option { + if let HelperForm::Defconstant(defc) = h { + return Some(HelperForm::Defconstant(DefconstData { + tabled: false, + ..defc.clone() + })); + } + None +} + +fn change_constant_value(h: &HelperForm, body: Rc) -> Option { + if let HelperForm::Defconstant(defc) = h { + return Some(HelperForm::Defconstant(DefconstData { + body, + ..defc.clone() + })); + } + None +} + +fn transform_helper Option>( + compileform: &mut CompileForm, + transform: F, +) { + for h in compileform.helpers.iter_mut() { + if let Some(res) = transform(&h) { + eprintln!("{} became {}", h.to_sexp(), res.to_sexp()); + *h = res; + } + } +} + +fn diffmap(m1: &mut HashMap, m2: &HashMap) { + for key in m2.keys() { + m1.remove(key); + } +} + +#[test] +fn test_codegen_function_cache() { + let orig_opts: Rc = Rc::new(DefaultCompilerOpts::new(&"*test*".to_string())); + let orig_opts = orig_opts + .set_search_paths(&["resources/tests/module".to_string(), ".".to_string()]) + .set_stdenv(false) + .set_dialect(AcceptedDialect { + stepping: Some(25), + strict: true, + int_fix: true, + }); + let fs_opts = TestModuleCompilerOpts::new(orig_opts); + let opts: Rc = Rc::new(fs_opts.clone()); + let (_, content) = opts + .read_new_file( + opts.filename(), + "resources/tests/module/cache-test-1.clsp".to_string(), + ) + .expect("ok"); + let parsed = parse_sexp(Srcloc::start(&opts.filename()), content.iter().cloned()).expect("ok"); + let program = frontend(opts.clone(), &parsed).expect("ok"); + let (mut compileform, exports) = if let FrontendOutput::Module(compileform, exports) = program { + (compileform, exports) + } else { + panic!(); + }; + + // Module style programs produce exports that are handled separately. They're turned into full + // programs in this way: + (compileform.args, compileform.exp) = if let Export::MainProgram(ep) = &exports[0] { + (ep.args.clone(), ep.expr.clone()) + } else { + panic!(); + }; + compileform = rename_args_compileform(&compileform).unwrap(); + + // + // Now we've got a compileform with some exports and some intermediate functions. + // check-1 depends on FF which depends on F, which depends on C + // check-2 depends on GG which depends on G and H, which depend on CC + // We should be able to change FF without changing the output of and G or H without + // chaning the output of GG unless the environment changes. + // We should be able to change C without changing the output of GG and CC without + // changing the output of FF. + // + // conversely, we should always pick up a change in C or F in FF. + // and CC, G, H in GG. + // + let runner = Rc::new(DefaultProgramRunner::new()); + let mut context = BasicCompileContext::new( + Allocator::new(), + runner.clone(), + HashMap::new(), + Box::new(Strategy23 {}), + ); + + // This enables caching. + context.funcache = Some(Funcache::new(&compileform)); + + let mut desugared = do_desugar(&compileform).unwrap(); + + // Set the constant CC to not be tabled so we can test inlined constant changes. + transform_helper(&mut desugared, |h| { + if match_name_prefix("CC", h) { + return untable_constant(h); + } + None + }); + + eprintln!("compileform {}", desugared.to_sexp()); + + let base_generated = Rc::new(codegen(&mut context, opts.clone(), &desugared).unwrap()); + eprintln!("generated code {base_generated}"); + + let original_map = context.funcache.as_ref().unwrap().function_outputs.clone(); + let mut allocator = Allocator::new(); + let run_arg = parse_sexp(Srcloc::start("*args*"), b"(3 7)".iter().cloned()).unwrap()[0].clone(); + eprintln!("use args {}", run_arg); + let expected_outcome = get_expected_outcome(opts.clone(), run_arg.clone()).unwrap(); + let run_outcome_base = run( + &mut allocator, + runner.clone(), + opts.prim_map(), + base_generated.clone(), + run_arg.clone(), + None, + None, + ) + .unwrap(); + assert_eq!(run_outcome_base, expected_outcome); + + // Ensure the cache is being used. We should have entries for FF, GG. + let want_set = vec!["FF", "GG", "F", "G", "H"]; + let names: Vec<_> = context + .funcache + .as_ref() + .map(|fc| { + fc.function_outputs + .values() + .map(|e| decode_string(&e.name)) + .collect() + }) + .unwrap_or_default(); + // maps names to their original hashes. + let original_name_map: HashMap<_, _> = context + .funcache + .as_ref() + .map(|fc| { + fc.function_outputs + .iter() + .map(|(k, v)| (decode_string(&v.name), k.to_vec())) + .collect() + }) + .unwrap_or_default(); + + // A let binding will have been desugared from FF. + // Show all the expected names are present in the cache along with the desugared assignment. + assert!(!names.is_empty()); + assert_eq!(names.len(), want_set.len() + 1); + assert_eq!(names.len(), original_name_map.len()); + + for name in want_set.iter() { + assert!(names.iter().any(|n| n == name)); + } + + let mut flipped_letbinding_program = desugared.clone(); + + transform_helper(&mut flipped_letbinding_program, |h| { + if match_name_prefix("letbinding_$_", h) { + return flip_inline(h, true); + } + None + }); + + // If the code wasn't regenerated in response to this, we'd have the same program. + let generated_flipped_let_binding = + Rc::new(codegen(&mut context, opts.clone(), &flipped_letbinding_program).unwrap()); + let mut flipped_map = context.funcache.as_ref().unwrap().function_outputs.clone(); + diffmap(&mut flipped_map, &original_map); + + assert_ne!( + base_generated.to_string(), + generated_flipped_let_binding.to_string() + ); + + // Extract the generated FF and verify that it's different. + let old_ff = extract_helper_gen(&original_map, "FF").unwrap(); + let new_ff = extract_helper_gen(&flipped_map, "FF").unwrap(); + + let old_h = extract_helper_gen(&original_map, "H").unwrap(); + + // GG will have been different here because of the environment shift. + // We'll keep a representation of it to know for sure. + let old_gg = extract_helper_gen(&original_map, "GG").unwrap(); + let new_gg = extract_helper_gen(&flipped_map, "GG").unwrap(); + + // They aren't the same function code. + assert_ne!(old_ff, new_ff); + + // Verify the same run outcome. + let run_outcome_flipped = run( + &mut allocator, + runner.clone(), + opts.prim_map(), + generated_flipped_let_binding.clone(), + run_arg.clone(), + None, + None, + ) + .unwrap(); + assert_eq!(expected_outcome, run_outcome_flipped); + + // Swap dummy and the let binding. That should leave the environment in the same shape. + // If we've done things correctly, GG should be the same function in generated_flipped_let + // as in generated_double_flip. + let mut fake_flip_program = flipped_letbinding_program.clone(); + transform_helper(&mut fake_flip_program, |h| { + if match_name_prefix("fake-letbinding", h) { + return flip_inline(h, false); + } + None + }); + + let generated_double_flip = + Rc::new(codegen(&mut context, opts.clone(), &fake_flip_program).unwrap()); + + // We'll zap the cache and do it again. The version we get for GG should be the same as in + // the original because we transformed the environment back to the same shape observed in the + // original while involving only functions that GG doesn't use. + context.funcache = Some(Funcache::new(&compileform)); + let generated_double_flip_clean_cache = + Rc::new(codegen(&mut context, opts.clone(), &fake_flip_program).unwrap()); + + // They're the same program, so we can use the clean cache below to check generated bodies. + assert_eq!(generated_double_flip, generated_double_flip_clean_cache); + + let fake_flip_map_clean_cache = context.funcache.as_ref().unwrap().function_outputs.clone(); + + let swap_ff = extract_helper_gen(&fake_flip_map_clean_cache, "FF").unwrap(); + let swap_gg = extract_helper_gen(&fake_flip_map_clean_cache, "GG").unwrap(); + + // FF should not be the same + assert_ne!(swap_ff, old_ff); + // GG should be the same, so we removed it by filtering. + assert_eq!(swap_gg, old_gg); + assert_ne!(swap_gg, new_gg); + + // Modify a constant affecting H. + let mut constant_diff_program = desugared.clone(); + transform_helper(&mut constant_diff_program, |h| { + if match_name_prefix("CC", h) { + return change_constant_value( + h, + Rc::new(BodyForm::Quoted(SExp::Atom(h.loc(), vec![37]))), + ); + } + None + }); + + let generated_diff_c = + Rc::new(codegen(&mut context, opts.clone(), &constant_diff_program).unwrap()); + + // Clobber cache and generate again so we can see exactly what was generated. + context.funcache = Some(Funcache::new(&compileform)); + let generated_diff_c_clean = + Rc::new(codegen(&mut context, opts.clone(), &constant_diff_program).unwrap()); + let generated_diff_c_map_clean = context.funcache.as_ref().unwrap().function_outputs.clone(); + assert_eq!(generated_diff_c, generated_diff_c_clean); + + // // Now we have a different value of GG, but same value of FF. + let diffcc_ff = extract_helper_gen(&generated_diff_c_map_clean, "FF").unwrap(); + let diffcc_gg = extract_helper_gen(&generated_diff_c_map_clean, "GG").unwrap(); + let diffcc_h = extract_helper_gen(&generated_diff_c_map_clean, "H").unwrap(); + eprintln!("new H {diffcc_h}"); + + assert_eq!(diffcc_ff, old_ff); + assert_eq!(diffcc_gg, old_gg); + assert_ne!(diffcc_h, old_h); +} diff --git a/src/tests/compiler/mod.rs b/src/tests/compiler/mod.rs index 5b24a3107..e0dcd7f6a 100644 --- a/src/tests/compiler/mod.rs +++ b/src/tests/compiler/mod.rs @@ -7,6 +7,7 @@ use crate::compiler::srcloc::{Srcloc, Until}; mod assign; mod cldb; mod clvm; +mod codegen_funcache; mod compiler; mod evaluate; mod fuzz; diff --git a/src/tests/compiler/modules.rs b/src/tests/compiler/modules.rs index 7e5e8da7e..b589b3e5f 100644 --- a/src/tests/compiler/modules.rs +++ b/src/tests/compiler/modules.rs @@ -53,12 +53,6 @@ impl TestModuleCompilerOpts { let wf_ref: &mut HashMap> = &mut wf_refcell.borrow_mut(); wf_ref.insert(name, content); } - - pub fn erase_written<'a>(&'a self, name: &str) { - let wf_refcell: &RefCell>> = self.written_files.borrow(); - let wf_ref: &mut HashMap> = &mut wf_refcell.borrow_mut(); - wf_ref.remove(name); - } } impl HasCompilerOptsDelegation for TestModuleCompilerOpts { From 5d0717236e70b2ce99b72d38dd3f4ef2b9b6ec67 Mon Sep 17 00:00:00 2001 From: arty Date: Mon, 2 Mar 2026 12:28:31 -0800 Subject: [PATCH 03/26] introduce function cache --- Cargo.lock | 6 +++--- Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1839f9ef9..b77f725b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -120,9 +120,9 @@ dependencies = [ [[package]] name = "chia-bls" -version = "0.36.1" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f02cbfd038d9050d45edbe8f38e09391c73479c0cca5b37925daf48c4d4fcd4" +checksum = "7d02accd77f2a373e2f50645e2e42ebcb484d8c6a94f5eda6332ad8bc21f48e6" dependencies = [ "blst", "chia-sha2 0.36.1", @@ -203,7 +203,7 @@ name = "chialisp" version = "0.4.1" dependencies = [ "binascii", - "chia-bls 0.36.1", + "chia-bls 0.36.0", "clvmr", "do-notation", "getrandom 0.2.15", diff --git a/Cargo.toml b/Cargo.toml index bcb0793a4..909aff611 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ python-source = "python" [dependencies] binascii = "0.1.4" -chia-bls = "0.36.0" +chia-bls = "=0.36.0" clvmr = { version = "0.16.2", features = ["pre-eval"] } do-notation = "0.1.3" hashlink = "0.11.0" From bf231aba298cde47c73a414ebd8eb65ac1e4cf5b Mon Sep 17 00:00:00 2001 From: arty Date: Mon, 2 Mar 2026 12:39:59 -0800 Subject: [PATCH 04/26] update arm64 macos version --- .github/workflows/build-m1-wheel.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-m1-wheel.yml b/.github/workflows/build-m1-wheel.yml index e8e4574ad..d6afdbd79 100644 --- a/.github/workflows/build-m1-wheel.yml +++ b/.github/workflows/build-m1-wheel.yml @@ -22,7 +22,7 @@ permissions: jobs: build_wheels: name: Build wheel on Mac M1 - runs-on: macos-13-arm64 + runs-on: macos-15-arm64 strategy: fail-fast: false From 2d21d263f816bba8f548f72691a0f0301a9f2236 Mon Sep 17 00:00:00 2001 From: arty Date: Mon, 2 Mar 2026 12:41:13 -0800 Subject: [PATCH 05/26] higher macos version --- .github/workflows/build-m1-wheel.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-m1-wheel.yml b/.github/workflows/build-m1-wheel.yml index e8e4574ad..d6afdbd79 100644 --- a/.github/workflows/build-m1-wheel.yml +++ b/.github/workflows/build-m1-wheel.yml @@ -22,7 +22,7 @@ permissions: jobs: build_wheels: name: Build wheel on Mac M1 - runs-on: macos-13-arm64 + runs-on: macos-15-arm64 strategy: fail-fast: false From eb18ba31b11d8fabecae3263ecce9abc99c08d76 Mon Sep 17 00:00:00 2001 From: arty Date: Mon, 2 Mar 2026 12:44:10 -0800 Subject: [PATCH 06/26] fmt + clippy --- src/compiler/codegen.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/codegen.rs b/src/compiler/codegen.rs index 2236d95be..71735bf6e 100644 --- a/src/compiler/codegen.rs +++ b/src/compiler/codegen.rs @@ -980,7 +980,7 @@ pub fn codegen_( defun.orig_args.clone(), DefunCall { required_env: defun.args.clone(), - code: code, + code, }, true, // Always take left env for now )); From 0a50a5209a0d9ed5ce9d9f12a8d54f148e36ce28 Mon Sep 17 00:00:00 2001 From: arty Date: Mon, 2 Mar 2026 12:46:00 -0800 Subject: [PATCH 07/26] clippy --- src/compiler/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs index d85b43adf..7b6286bdc 100644 --- a/src/compiler/mod.rs +++ b/src/compiler/mod.rs @@ -65,7 +65,7 @@ impl Funcache { Funcache { function_outputs: HashMap::new(), dependency_graph: FunctionDependencyGraph::new_with_options( - &cf, + cf, DepgraphOptions { with_constants: true, }, From 5e2d3090dd5eccc105031627ee26ab16d2bee8df Mon Sep 17 00:00:00 2001 From: arty Date: Mon, 2 Mar 2026 13:40:30 -0800 Subject: [PATCH 08/26] try to fix workflow --- .github/workflows/build-m1-wheel.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-m1-wheel.yml b/.github/workflows/build-m1-wheel.yml index d6afdbd79..a837a904f 100644 --- a/.github/workflows/build-m1-wheel.yml +++ b/.github/workflows/build-m1-wheel.yml @@ -22,7 +22,7 @@ permissions: jobs: build_wheels: name: Build wheel on Mac M1 - runs-on: macos-15-arm64 + runs-on: macos-15 strategy: fail-fast: false From 5836fbba39b05a90bb69ceb454d3cdbcdd799a94 Mon Sep 17 00:00:00 2001 From: arty Date: Mon, 2 Mar 2026 16:07:59 -0800 Subject: [PATCH 09/26] remove --- src/tests/compiler/modules.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/tests/compiler/modules.rs b/src/tests/compiler/modules.rs index 7e5e8da7e..b589b3e5f 100644 --- a/src/tests/compiler/modules.rs +++ b/src/tests/compiler/modules.rs @@ -53,12 +53,6 @@ impl TestModuleCompilerOpts { let wf_ref: &mut HashMap> = &mut wf_refcell.borrow_mut(); wf_ref.insert(name, content); } - - pub fn erase_written<'a>(&'a self, name: &str) { - let wf_refcell: &RefCell>> = self.written_files.borrow(); - let wf_ref: &mut HashMap> = &mut wf_refcell.borrow_mut(); - wf_ref.remove(name); - } } impl HasCompilerOptsDelegation for TestModuleCompilerOpts { From 4fc95eadb3a2691564d19e115b115885ff99d4c5 Mon Sep 17 00:00:00 2001 From: arty Date: Mon, 2 Mar 2026 17:53:41 -0800 Subject: [PATCH 10/26] remove eprintln --- src/compiler/codegen.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/compiler/codegen.rs b/src/compiler/codegen.rs index 71735bf6e..c24352bb9 100644 --- a/src/compiler/codegen.rs +++ b/src/compiler/codegen.rs @@ -939,7 +939,6 @@ fn get_function_cache_key( let depended_on = get_depended_on_forms(compiler, helper.loc(), filtered_env.clone(), &depends_on); let hashable = Rc::new(SExp::Cons(helper.loc(), helper.to_sexp(), depended_on)); - eprintln!("cache key {}: {}", decode_string(helper.name()), hashable); sha256tree(hashable) } @@ -974,7 +973,6 @@ pub fn codegen_( .as_ref() .and_then(|c| c.function_outputs.get(key).map(|e| e.code.clone())) }) { - eprintln!("cache hit {}: {}", decode_string(h.name()), code); return Ok(compiler.add_defun( &defun.name, defun.orig_args.clone(), From 09156e10d3e187408259cb2eec0f99b38404c3a8 Mon Sep 17 00:00:00 2001 From: arty Date: Fri, 6 Mar 2026 14:57:12 -0800 Subject: [PATCH 11/26] Nilify environment atom if it matches no used name --- src/compiler/codegen.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/codegen.rs b/src/compiler/codegen.rs index c24352bb9..bd0a01294 100644 --- a/src/compiler/codegen.rs +++ b/src/compiler/codegen.rs @@ -847,7 +847,7 @@ fn fail_if_present( fn filter_env(used: &[Vec], nil: Rc, env: Rc) -> Rc { match env.atomize() { SExp::Atom(_l, n) => { - if used.iter().any(|u| *u != n) { + if used.iter().all(|u| *u != n) { return nil.clone(); } env From 0d1b59beb48027a283291ea0efd6b722025edcb3 Mon Sep 17 00:00:00 2001 From: arty Date: Fri, 6 Mar 2026 17:29:49 -0800 Subject: [PATCH 12/26] Explicit pointer types --- src/compiler/codegen.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/compiler/codegen.rs b/src/compiler/codegen.rs index bd0a01294..c2539d0b9 100644 --- a/src/compiler/codegen.rs +++ b/src/compiler/codegen.rs @@ -855,10 +855,10 @@ fn filter_env(used: &[Vec], nil: Rc, env: Rc) -> Rc { SExp::Cons(l, a, b) => { let new_a = filter_env(used, nil.clone(), a.clone()); let new_b = filter_env(used, nil.clone(), b.clone()); - let old_a_ptr = &*a; - let old_b_ptr = &*b; - let new_a_ptr = &*new_a; - let new_b_ptr = &*new_b; + let old_a_ptr: *const SExp = &*a; + let old_b_ptr: *const SExp = &*b; + let new_a_ptr: *const SExp = &*new_a; + let new_b_ptr: *const SExp = &*new_b; if old_a_ptr == new_a_ptr && old_b_ptr == new_b_ptr { return env; } From f61786c416892ee35511242eb065dbfb03048056 Mon Sep 17 00:00:00 2001 From: arty Date: Fri, 6 Mar 2026 17:50:29 -0800 Subject: [PATCH 13/26] pub unneeded --- src/compiler/codegen.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/codegen.rs b/src/compiler/codegen.rs index c2539d0b9..52a284a53 100644 --- a/src/compiler/codegen.rs +++ b/src/compiler/codegen.rs @@ -942,7 +942,7 @@ fn get_function_cache_key( sha256tree(hashable) } -pub fn codegen_( +fn codegen_( context: &mut BasicCompileContext, opts: Rc, compiler: &PrimaryCodegen, From a81f523b2a23536ca940674f27962aa6bde58bdd Mon Sep 17 00:00:00 2001 From: arty Date: Mon, 9 Mar 2026 10:51:13 -0700 Subject: [PATCH 14/26] Move dependency graph so we can let the caller ensure it aligns with the provided compileform. This eliminates the possibility of the context containing a stale dependency graph. --- src/compiler/codegen.rs | 18 ++++++++------- src/compiler/compiler.rs | 2 +- src/compiler/evaluate.rs | 2 +- src/compiler/mod.rs | 13 ++++------- src/compiler/optimize/deinline.rs | 4 ++-- src/compiler/optimize/mod.rs | 2 +- src/tests/compiler/codegen_funcache.rs | 31 +++++++++++++++++--------- src/tests/compiler/resolve.rs | 10 ++++++++- 8 files changed, 48 insertions(+), 34 deletions(-) diff --git a/src/compiler/codegen.rs b/src/compiler/codegen.rs index 52a284a53..6d4b446ba 100644 --- a/src/compiler/codegen.rs +++ b/src/compiler/codegen.rs @@ -22,13 +22,14 @@ use crate::compiler::frontend::{compile_bodyform, make_provides_set}; use crate::compiler::gensym::gensym; use crate::compiler::inline::{replace_in_inline, synthesize_args}; use crate::compiler::lambda::lambda_codegen; +use crate::compiler::optimize::depgraph::FunctionDependencyGraph; use crate::compiler::prims::{primapply, primcons, primquote}; use crate::compiler::runtypes::RunFailure; use crate::compiler::sexp::{decode_string, printable, SExp}; use crate::compiler::srcloc::Srcloc; use crate::compiler::StartOfCodegenOptimization; use crate::compiler::{BasicCompileContext, CompileContextWrapper}; -use crate::compiler::{Funcache, FunctionEntry}; +use crate::compiler::FunctionEntry; use crate::util::{toposort, u8_from_number, TopoSortItem}; const MACRO_TIME_LIMIT: usize = 1000000; @@ -645,7 +646,7 @@ pub fn do_mod_codegen( &mut throwaway_symbols, optimizer, ); - let code = codegen(&mut context_wrapper.context, without_env, program)?; + let code = codegen(&mut context_wrapper.context, without_env, None, program)?; Ok(CompiledCode( program.loc.clone(), Rc::new(SExp::Cons( @@ -922,11 +923,11 @@ fn get_depended_on_forms( fn get_function_cache_key( compiler: &PrimaryCodegen, - fc: &Funcache, + dependency_graph: &FunctionDependencyGraph, helper: &HelperForm, ) -> Vec { let mut depends_on_set = HashSet::new(); - fc.dependency_graph + dependency_graph .get_full_depends_on(&mut depends_on_set, helper.name()); let mut depends_on: Vec<_> = depends_on_set.into_iter().collect(); depends_on.sort(); @@ -945,6 +946,7 @@ fn get_function_cache_key( fn codegen_( context: &mut BasicCompileContext, opts: Rc, + dependency_graph: Option<&FunctionDependencyGraph>, compiler: &PrimaryCodegen, h: &HelperForm, ) -> Result { @@ -962,10 +964,9 @@ fn codegen_( }, )) } else { - let cache_key = context - .funcache + let cache_key = dependency_graph .as_ref() - .map(|fc| get_function_cache_key(compiler, fc, h)); + .map(|d| get_function_cache_key(compiler, d, h)); if let Some(code) = cache_key.as_ref().and_then(|key| { context @@ -1779,6 +1780,7 @@ fn dummy_functions(compiler: &PrimaryCodegen) -> Result, + dependency_graph: Option<&FunctionDependencyGraph>, cmod: &CompileForm, ) -> Result { let mut start_of_codegen_optimization = StartOfCodegenOptimization { @@ -1824,7 +1826,7 @@ pub fn codegen( let to_process = code_generator.to_process.clone(); for f in to_process { - code_generator = codegen_(context, opts.clone(), &code_generator, &f)?; + code_generator = codegen_(context, opts.clone(), dependency_graph.clone(), &code_generator, &f)?; } // If stepping 23 or greater, we support no-env mode. diff --git a/src/compiler/compiler.rs b/src/compiler/compiler.rs index b92891cac..97600d3e1 100644 --- a/src/compiler/compiler.rs +++ b/src/compiler/compiler.rs @@ -140,7 +140,7 @@ pub fn finish_compilation( let p3 = context.post_desugar_optimization(opts.clone(), p2)?; // generate code from AST, optionally with optimization - let generated = codegen(context, opts.clone(), &p3)?; + let generated = codegen(context, opts.clone(), None, &p3)?; let g2 = context.post_codegen_output_optimize(opts, generated)?; diff --git a/src/compiler/evaluate.rs b/src/compiler/evaluate.rs index 8b37f62b1..f0125c40c 100644 --- a/src/compiler/evaluate.rs +++ b/src/compiler/evaluate.rs @@ -1497,7 +1497,7 @@ impl<'info> Evaluator { &mut symbols, optimizer, ); - let code = codegen(&mut context_wrapper.context, self.opts.clone(), program)?; + let code = codegen(&mut context_wrapper.context, self.opts.clone(), None, program)?; Ok(Rc::new(BodyForm::Quoted(code))) } BodyForm::Lambda(ldata) => self.enrich_lambda_site_info( diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs index 45265e8c7..f29ea50ca 100644 --- a/src/compiler/mod.rs +++ b/src/compiler/mod.rs @@ -45,7 +45,6 @@ use crate::classic::clvm_tools::stages::stage_0::TRunProgram; use crate::compiler::comptypes::{ BodyForm, CompileErr, CompileForm, CompilerOpts, DefunData, HelperForm, PrimaryCodegen, }; -use crate::compiler::optimize::depgraph::{DepgraphOptions, FunctionDependencyGraph}; use crate::compiler::optimize::Optimization; use crate::compiler::sexp::SExp; @@ -57,19 +56,12 @@ pub struct FunctionEntry { pub struct Funcache { pub function_outputs: HashMap, FunctionEntry>, - pub dependency_graph: FunctionDependencyGraph, } impl Funcache { - pub fn new(cf: &CompileForm) -> Self { + pub fn new() -> Self { Funcache { function_outputs: HashMap::new(), - dependency_graph: FunctionDependencyGraph::new_with_options( - cf, - DepgraphOptions { - with_constants: true, - }, - ), } } } @@ -83,6 +75,9 @@ pub struct BasicCompileContext { pub optimizer: Box, /// Given the operative environment and a serialization of the helper, this is the generated /// code from that helper. + /// + /// Since this is for speeding up optimization-time work, generation of the dependency graph + /// must follow desugaring. pub funcache: Option, } diff --git a/src/compiler/optimize/deinline.rs b/src/compiler/optimize/deinline.rs index b071822c8..6a1cc068d 100644 --- a/src/compiler/optimize/deinline.rs +++ b/src/compiler/optimize/deinline.rs @@ -57,7 +57,7 @@ pub fn deinline_opt( let depgraph = FunctionDependencyGraph::new(&compileform); let mut best_compileform = compileform.clone(); - let generated_program = codegen(context, opts.clone(), &best_compileform)?; + let generated_program = codegen(context, opts.clone(), Some(&depgraph), &best_compileform)?; let mut metric = sexp_scale(&generated_program); let flip_helper = |h: &mut HelperForm| { if let HelperForm::Defun(inline, defun) = h { @@ -231,7 +231,7 @@ pub fn deinline_opt( continue; } - let maybe_smaller_program = codegen(context, opts.clone(), &compileform)?; + let maybe_smaller_program = codegen(context, opts.clone(), Some(&depgraph), &compileform)?; let new_metric = sexp_scale(&maybe_smaller_program); // Don't keep this change if it made things worse. diff --git a/src/compiler/optimize/mod.rs b/src/compiler/optimize/mod.rs index a62620f9d..5d172bd43 100644 --- a/src/compiler/optimize/mod.rs +++ b/src/compiler/optimize/mod.rs @@ -334,7 +334,7 @@ fn constant_fun_result( let mut wrapper = CompileContextWrapper::new(allocator, runner.clone(), &mut symbols, optimizer); - if let Ok(code) = codegen(&mut wrapper.context, opts.clone(), &to_compile) { + if let Ok(code) = codegen(&mut wrapper.context, opts.clone(), None, &to_compile) { code } else { return None; diff --git a/src/tests/compiler/codegen_funcache.rs b/src/tests/compiler/codegen_funcache.rs index b5bb7f183..9d10e4f85 100644 --- a/src/tests/compiler/codegen_funcache.rs +++ b/src/tests/compiler/codegen_funcache.rs @@ -15,6 +15,7 @@ use crate::compiler::comptypes::{ use crate::compiler::dialect::AcceptedDialect; use crate::compiler::frontend::frontend; use crate::compiler::optimize::above22::Strategy23; +use crate::compiler::optimize::depgraph::{DepgraphOptions, FunctionDependencyGraph}; use crate::compiler::rename::rename_args_compileform; use crate::compiler::sexp::{decode_string, parse_sexp, SExp}; use crate::compiler::srcloc::Srcloc; @@ -176,11 +177,11 @@ fn test_codegen_function_cache() { Box::new(Strategy23 {}), ); - // This enables caching. - context.funcache = Some(Funcache::new(&compileform)); - let mut desugared = do_desugar(&compileform).unwrap(); + // This enables caching. + context.funcache = Some(Funcache::new()); + // Set the constant CC to not be tabled so we can test inlined constant changes. transform_helper(&mut desugared, |h| { if match_name_prefix("CC", h) { @@ -191,7 +192,15 @@ fn test_codegen_function_cache() { eprintln!("compileform {}", desugared.to_sexp()); - let base_generated = Rc::new(codegen(&mut context, opts.clone(), &desugared).unwrap()); + let dependency_graph = + FunctionDependencyGraph::new_with_options( + &desugared, + DepgraphOptions { + with_constants: true, + }, + ); + + let base_generated = Rc::new(codegen(&mut context, opts.clone(), Some(&dependency_graph), &desugared).unwrap()); eprintln!("generated code {base_generated}"); let original_map = context.funcache.as_ref().unwrap().function_outputs.clone(); @@ -256,7 +265,7 @@ fn test_codegen_function_cache() { // If the code wasn't regenerated in response to this, we'd have the same program. let generated_flipped_let_binding = - Rc::new(codegen(&mut context, opts.clone(), &flipped_letbinding_program).unwrap()); + Rc::new(codegen(&mut context, opts.clone(), Some(&dependency_graph), &flipped_letbinding_program).unwrap()); let mut flipped_map = context.funcache.as_ref().unwrap().function_outputs.clone(); diffmap(&mut flipped_map, &original_map); @@ -304,14 +313,14 @@ fn test_codegen_function_cache() { }); let generated_double_flip = - Rc::new(codegen(&mut context, opts.clone(), &fake_flip_program).unwrap()); + Rc::new(codegen(&mut context, opts.clone(), Some(&dependency_graph), &fake_flip_program).unwrap()); // We'll zap the cache and do it again. The version we get for GG should be the same as in // the original because we transformed the environment back to the same shape observed in the // original while involving only functions that GG doesn't use. - context.funcache = Some(Funcache::new(&compileform)); + context.funcache = Some(Funcache::new()); let generated_double_flip_clean_cache = - Rc::new(codegen(&mut context, opts.clone(), &fake_flip_program).unwrap()); + Rc::new(codegen(&mut context, opts.clone(), Some(&dependency_graph), &fake_flip_program).unwrap()); // They're the same program, so we can use the clean cache below to check generated bodies. assert_eq!(generated_double_flip, generated_double_flip_clean_cache); @@ -340,12 +349,12 @@ fn test_codegen_function_cache() { }); let generated_diff_c = - Rc::new(codegen(&mut context, opts.clone(), &constant_diff_program).unwrap()); + Rc::new(codegen(&mut context, opts.clone(), Some(&dependency_graph), &constant_diff_program).unwrap()); // Clobber cache and generate again so we can see exactly what was generated. - context.funcache = Some(Funcache::new(&compileform)); + context.funcache = Some(Funcache::new()); let generated_diff_c_clean = - Rc::new(codegen(&mut context, opts.clone(), &constant_diff_program).unwrap()); + Rc::new(codegen(&mut context, opts.clone(), Some(&dependency_graph), &constant_diff_program).unwrap()); let generated_diff_c_map_clean = context.funcache.as_ref().unwrap().function_outputs.clone(); assert_eq!(generated_diff_c, generated_diff_c_clean); diff --git a/src/tests/compiler/resolve.rs b/src/tests/compiler/resolve.rs index f5acb2da4..cb35363bd 100644 --- a/src/tests/compiler/resolve.rs +++ b/src/tests/compiler/resolve.rs @@ -11,6 +11,7 @@ use crate::compiler::comptypes::{ BodyForm, CompileErr, CompileForm, CompilerOpts, DefunData, HelperForm, LetFormKind, }; use crate::compiler::frontend::frontend; +use crate::compiler::optimize::depgraph::{DepgraphOptions, FunctionDependencyGraph}; use crate::compiler::optimize::get_optimizer; use crate::compiler::resolve::resolve_namespaces; use crate::compiler::sexp::{decode_string, parse_sexp, SExp}; @@ -47,7 +48,14 @@ fn compile_program_get_result( HashMap::new(), get_optimizer(&loc, opts.clone())?, ); - let generated = codegen(&mut context, opts.clone(), &desugared)?; + let dependency_graph = + FunctionDependencyGraph::new_with_options( + &desugared, + DepgraphOptions { + with_constants: true, + }, + ); + let generated = codegen(&mut context, opts.clone(), Some(&dependency_graph), &desugared)?; run( &mut context.allocator, context.runner.clone(), From ca29fe204cd608d09a75e7111687f350ed138ec5 Mon Sep 17 00:00:00 2001 From: arty Date: Mon, 9 Mar 2026 10:51:23 -0700 Subject: [PATCH 15/26] fmt --- src/compiler/codegen.rs | 13 +++-- src/compiler/evaluate.rs | 7 ++- src/compiler/optimize/deinline.rs | 3 +- src/tests/compiler/codegen_funcache.rs | 78 ++++++++++++++++++++------ src/tests/compiler/resolve.rs | 20 ++++--- 5 files changed, 89 insertions(+), 32 deletions(-) diff --git a/src/compiler/codegen.rs b/src/compiler/codegen.rs index 6d4b446ba..22adae886 100644 --- a/src/compiler/codegen.rs +++ b/src/compiler/codegen.rs @@ -27,9 +27,9 @@ use crate::compiler::prims::{primapply, primcons, primquote}; use crate::compiler::runtypes::RunFailure; use crate::compiler::sexp::{decode_string, printable, SExp}; use crate::compiler::srcloc::Srcloc; +use crate::compiler::FunctionEntry; use crate::compiler::StartOfCodegenOptimization; use crate::compiler::{BasicCompileContext, CompileContextWrapper}; -use crate::compiler::FunctionEntry; use crate::util::{toposort, u8_from_number, TopoSortItem}; const MACRO_TIME_LIMIT: usize = 1000000; @@ -927,8 +927,7 @@ fn get_function_cache_key( helper: &HelperForm, ) -> Vec { let mut depends_on_set = HashSet::new(); - dependency_graph - .get_full_depends_on(&mut depends_on_set, helper.name()); + dependency_graph.get_full_depends_on(&mut depends_on_set, helper.name()); let mut depends_on: Vec<_> = depends_on_set.into_iter().collect(); depends_on.sort(); // Collect depended on function forms @@ -1826,7 +1825,13 @@ pub fn codegen( let to_process = code_generator.to_process.clone(); for f in to_process { - code_generator = codegen_(context, opts.clone(), dependency_graph.clone(), &code_generator, &f)?; + code_generator = codegen_( + context, + opts.clone(), + dependency_graph.clone(), + &code_generator, + &f, + )?; } // If stepping 23 or greater, we support no-env mode. diff --git a/src/compiler/evaluate.rs b/src/compiler/evaluate.rs index f0125c40c..82b484bbf 100644 --- a/src/compiler/evaluate.rs +++ b/src/compiler/evaluate.rs @@ -1497,7 +1497,12 @@ impl<'info> Evaluator { &mut symbols, optimizer, ); - let code = codegen(&mut context_wrapper.context, self.opts.clone(), None, program)?; + let code = codegen( + &mut context_wrapper.context, + self.opts.clone(), + None, + program, + )?; Ok(Rc::new(BodyForm::Quoted(code))) } BodyForm::Lambda(ldata) => self.enrich_lambda_site_info( diff --git a/src/compiler/optimize/deinline.rs b/src/compiler/optimize/deinline.rs index 6a1cc068d..11ec87359 100644 --- a/src/compiler/optimize/deinline.rs +++ b/src/compiler/optimize/deinline.rs @@ -231,7 +231,8 @@ pub fn deinline_opt( continue; } - let maybe_smaller_program = codegen(context, opts.clone(), Some(&depgraph), &compileform)?; + let maybe_smaller_program = + codegen(context, opts.clone(), Some(&depgraph), &compileform)?; let new_metric = sexp_scale(&maybe_smaller_program); // Don't keep this change if it made things worse. diff --git a/src/tests/compiler/codegen_funcache.rs b/src/tests/compiler/codegen_funcache.rs index 9d10e4f85..f6126397f 100644 --- a/src/tests/compiler/codegen_funcache.rs +++ b/src/tests/compiler/codegen_funcache.rs @@ -192,15 +192,22 @@ fn test_codegen_function_cache() { eprintln!("compileform {}", desugared.to_sexp()); - let dependency_graph = - FunctionDependencyGraph::new_with_options( - &desugared, - DepgraphOptions { - with_constants: true, - }, - ); + let dependency_graph = FunctionDependencyGraph::new_with_options( + &desugared, + DepgraphOptions { + with_constants: true, + }, + ); - let base_generated = Rc::new(codegen(&mut context, opts.clone(), Some(&dependency_graph), &desugared).unwrap()); + let base_generated = Rc::new( + codegen( + &mut context, + opts.clone(), + Some(&dependency_graph), + &desugared, + ) + .unwrap(), + ); eprintln!("generated code {base_generated}"); let original_map = context.funcache.as_ref().unwrap().function_outputs.clone(); @@ -264,8 +271,15 @@ fn test_codegen_function_cache() { }); // If the code wasn't regenerated in response to this, we'd have the same program. - let generated_flipped_let_binding = - Rc::new(codegen(&mut context, opts.clone(), Some(&dependency_graph), &flipped_letbinding_program).unwrap()); + let generated_flipped_let_binding = Rc::new( + codegen( + &mut context, + opts.clone(), + Some(&dependency_graph), + &flipped_letbinding_program, + ) + .unwrap(), + ); let mut flipped_map = context.funcache.as_ref().unwrap().function_outputs.clone(); diffmap(&mut flipped_map, &original_map); @@ -312,15 +326,29 @@ fn test_codegen_function_cache() { None }); - let generated_double_flip = - Rc::new(codegen(&mut context, opts.clone(), Some(&dependency_graph), &fake_flip_program).unwrap()); + let generated_double_flip = Rc::new( + codegen( + &mut context, + opts.clone(), + Some(&dependency_graph), + &fake_flip_program, + ) + .unwrap(), + ); // We'll zap the cache and do it again. The version we get for GG should be the same as in // the original because we transformed the environment back to the same shape observed in the // original while involving only functions that GG doesn't use. context.funcache = Some(Funcache::new()); - let generated_double_flip_clean_cache = - Rc::new(codegen(&mut context, opts.clone(), Some(&dependency_graph), &fake_flip_program).unwrap()); + let generated_double_flip_clean_cache = Rc::new( + codegen( + &mut context, + opts.clone(), + Some(&dependency_graph), + &fake_flip_program, + ) + .unwrap(), + ); // They're the same program, so we can use the clean cache below to check generated bodies. assert_eq!(generated_double_flip, generated_double_flip_clean_cache); @@ -348,13 +376,27 @@ fn test_codegen_function_cache() { None }); - let generated_diff_c = - Rc::new(codegen(&mut context, opts.clone(), Some(&dependency_graph), &constant_diff_program).unwrap()); + let generated_diff_c = Rc::new( + codegen( + &mut context, + opts.clone(), + Some(&dependency_graph), + &constant_diff_program, + ) + .unwrap(), + ); // Clobber cache and generate again so we can see exactly what was generated. context.funcache = Some(Funcache::new()); - let generated_diff_c_clean = - Rc::new(codegen(&mut context, opts.clone(), Some(&dependency_graph), &constant_diff_program).unwrap()); + let generated_diff_c_clean = Rc::new( + codegen( + &mut context, + opts.clone(), + Some(&dependency_graph), + &constant_diff_program, + ) + .unwrap(), + ); let generated_diff_c_map_clean = context.funcache.as_ref().unwrap().function_outputs.clone(); assert_eq!(generated_diff_c, generated_diff_c_clean); diff --git a/src/tests/compiler/resolve.rs b/src/tests/compiler/resolve.rs index cb35363bd..1c9249375 100644 --- a/src/tests/compiler/resolve.rs +++ b/src/tests/compiler/resolve.rs @@ -48,14 +48,18 @@ fn compile_program_get_result( HashMap::new(), get_optimizer(&loc, opts.clone())?, ); - let dependency_graph = - FunctionDependencyGraph::new_with_options( - &desugared, - DepgraphOptions { - with_constants: true, - }, - ); - let generated = codegen(&mut context, opts.clone(), Some(&dependency_graph), &desugared)?; + let dependency_graph = FunctionDependencyGraph::new_with_options( + &desugared, + DepgraphOptions { + with_constants: true, + }, + ); + let generated = codegen( + &mut context, + opts.clone(), + Some(&dependency_graph), + &desugared, + )?; run( &mut context.allocator, context.runner.clone(), From 1dfeb8e71e3e6ee45a1048a23a2387ad708dd2d7 Mon Sep 17 00:00:00 2001 From: arty Date: Mon, 9 Mar 2026 11:23:37 -0700 Subject: [PATCH 16/26] fmt + clippy --- src/compiler/codegen.rs | 8 +------- src/compiler/mod.rs | 9 +-------- src/tests/compiler/codegen_funcache.rs | 6 +++--- 3 files changed, 5 insertions(+), 18 deletions(-) diff --git a/src/compiler/codegen.rs b/src/compiler/codegen.rs index 22adae886..a962267be 100644 --- a/src/compiler/codegen.rs +++ b/src/compiler/codegen.rs @@ -1825,13 +1825,7 @@ pub fn codegen( let to_process = code_generator.to_process.clone(); for f in to_process { - code_generator = codegen_( - context, - opts.clone(), - dependency_graph.clone(), - &code_generator, - &f, - )?; + code_generator = codegen_(context, opts.clone(), dependency_graph, &code_generator, &f)?; } // If stepping 23 or greater, we support no-env mode. diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs index f29ea50ca..48cf5d623 100644 --- a/src/compiler/mod.rs +++ b/src/compiler/mod.rs @@ -54,18 +54,11 @@ pub struct FunctionEntry { pub code: Rc, } +#[derive(Default)] pub struct Funcache { pub function_outputs: HashMap, FunctionEntry>, } -impl Funcache { - pub fn new() -> Self { - Funcache { - function_outputs: HashMap::new(), - } - } -} - /// An object which represents the standard set of mutable items passed down the /// stack when compiling chialisp. pub struct BasicCompileContext { diff --git a/src/tests/compiler/codegen_funcache.rs b/src/tests/compiler/codegen_funcache.rs index f6126397f..196015c64 100644 --- a/src/tests/compiler/codegen_funcache.rs +++ b/src/tests/compiler/codegen_funcache.rs @@ -180,7 +180,7 @@ fn test_codegen_function_cache() { let mut desugared = do_desugar(&compileform).unwrap(); // This enables caching. - context.funcache = Some(Funcache::new()); + context.funcache = Some(Funcache::default()); // Set the constant CC to not be tabled so we can test inlined constant changes. transform_helper(&mut desugared, |h| { @@ -339,7 +339,7 @@ fn test_codegen_function_cache() { // We'll zap the cache and do it again. The version we get for GG should be the same as in // the original because we transformed the environment back to the same shape observed in the // original while involving only functions that GG doesn't use. - context.funcache = Some(Funcache::new()); + context.funcache = Some(Funcache::default()); let generated_double_flip_clean_cache = Rc::new( codegen( &mut context, @@ -387,7 +387,7 @@ fn test_codegen_function_cache() { ); // Clobber cache and generate again so we can see exactly what was generated. - context.funcache = Some(Funcache::new()); + context.funcache = Some(Funcache::default()); let generated_diff_c_clean = Rc::new( codegen( &mut context, From ea82771a8cd7cbdc505fee4612ddcd961e092a60 Mon Sep 17 00:00:00 2001 From: arty Date: Mon, 9 Mar 2026 13:22:01 -0700 Subject: [PATCH 17/26] don't compute a cache key unless funcache is present --- src/compiler/codegen.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/compiler/codegen.rs b/src/compiler/codegen.rs index a962267be..193ade12a 100644 --- a/src/compiler/codegen.rs +++ b/src/compiler/codegen.rs @@ -965,6 +965,7 @@ fn codegen_( } else { let cache_key = dependency_graph .as_ref() + .filter(|_| context.funcache.is_some()) .map(|d| get_function_cache_key(compiler, d, h)); if let Some(code) = cache_key.as_ref().and_then(|key| { From 09243649451d398540e35d98c151717d7fac857b Mon Sep 17 00:00:00 2001 From: arty Date: Mon, 9 Mar 2026 14:02:46 -0700 Subject: [PATCH 18/26] ensure 'already present' checks are performed along the cache path as well. not really necessary since this was checked when the first generation happened, but its ok --- src/compiler/codegen.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/compiler/codegen.rs b/src/compiler/codegen.rs index 193ade12a..9b8028340 100644 --- a/src/compiler/codegen.rs +++ b/src/compiler/codegen.rs @@ -963,6 +963,12 @@ fn codegen_( }, )) } else { + let check_already_present = |code| { + fail_if_present(defun.loc.clone(), &compiler.inlines, &defun.name, code) + .and_then(|code| { + fail_if_present(defun.loc.clone(), &compiler.defuns, &defun.name, code) + }) + }; let cache_key = dependency_graph .as_ref() .filter(|_| context.funcache.is_some()) @@ -974,6 +980,7 @@ fn codegen_( .as_ref() .and_then(|c| c.function_outputs.get(key).map(|e| e.code.clone())) }) { + check_already_present(code.clone())?; return Ok(compiler.add_defun( &defun.name, defun.orig_args.clone(), @@ -1019,12 +1026,7 @@ fn codegen_( Rc::new(code.to_sexp()), ) }) - .and_then(|code| { - fail_if_present(defun.loc.clone(), &compiler.inlines, &defun.name, code) - }) - .and_then(|code| { - fail_if_present(defun.loc.clone(), &compiler.defuns, &defun.name, code) - }) + .and_then(check_already_present) .map(|code| { if let (Some(fc), Some(hash)) = (&mut context.funcache, cache_key) { fc.function_outputs.insert( From f8fec365022c1b4574e482841d6d7204cb03d183 Mon Sep 17 00:00:00 2001 From: arty Date: Mon, 9 Mar 2026 20:00:31 -0700 Subject: [PATCH 19/26] No need for dependency graph here --- src/tests/compiler/resolve.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/tests/compiler/resolve.rs b/src/tests/compiler/resolve.rs index 1c9249375..d43788de9 100644 --- a/src/tests/compiler/resolve.rs +++ b/src/tests/compiler/resolve.rs @@ -11,7 +11,6 @@ use crate::compiler::comptypes::{ BodyForm, CompileErr, CompileForm, CompilerOpts, DefunData, HelperForm, LetFormKind, }; use crate::compiler::frontend::frontend; -use crate::compiler::optimize::depgraph::{DepgraphOptions, FunctionDependencyGraph}; use crate::compiler::optimize::get_optimizer; use crate::compiler::resolve::resolve_namespaces; use crate::compiler::sexp::{decode_string, parse_sexp, SExp}; @@ -48,16 +47,10 @@ fn compile_program_get_result( HashMap::new(), get_optimizer(&loc, opts.clone())?, ); - let dependency_graph = FunctionDependencyGraph::new_with_options( - &desugared, - DepgraphOptions { - with_constants: true, - }, - ); let generated = codegen( &mut context, opts.clone(), - Some(&dependency_graph), + None, &desugared, )?; run( From 47d9f907524ef0fd3f4e870ed2c5267e6d9d3481 Mon Sep 17 00:00:00 2001 From: arty Date: Mon, 9 Mar 2026 20:00:39 -0700 Subject: [PATCH 20/26] fmt --- src/tests/compiler/resolve.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/tests/compiler/resolve.rs b/src/tests/compiler/resolve.rs index d43788de9..8c8ca6e91 100644 --- a/src/tests/compiler/resolve.rs +++ b/src/tests/compiler/resolve.rs @@ -47,12 +47,7 @@ fn compile_program_get_result( HashMap::new(), get_optimizer(&loc, opts.clone())?, ); - let generated = codegen( - &mut context, - opts.clone(), - None, - &desugared, - )?; + let generated = codegen(&mut context, opts.clone(), None, &desugared)?; run( &mut context.allocator, context.runner.clone(), From fc6d90f21ff3a599ad5a2b978b3b2ca6e7b3901f Mon Sep 17 00:00:00 2001 From: arty Date: Thu, 26 Mar 2026 16:51:35 -0700 Subject: [PATCH 21/26] fix --- src/tests/compiler/codegen_funcache.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tests/compiler/codegen_funcache.rs b/src/tests/compiler/codegen_funcache.rs index 196015c64..3a39ef452 100644 --- a/src/tests/compiler/codegen_funcache.rs +++ b/src/tests/compiler/codegen_funcache.rs @@ -131,6 +131,7 @@ fn test_codegen_function_cache() { stepping: Some(25), strict: true, int_fix: true, + extra_numeric_constants: false, }); let fs_opts = TestModuleCompilerOpts::new(orig_opts); let opts: Rc = Rc::new(fs_opts.clone()); From 45fc5993974ce7db20ebe1de86afb49d8a831519 Mon Sep 17 00:00:00 2001 From: Adam Kelly <338792+aqk@users.noreply.github.com> Date: Wed, 22 Apr 2026 21:19:13 -0400 Subject: [PATCH 22/26] some code to ponder --- resources/coverage/run_coverage.py | 9 ++++ src/compiler/diskcache.rs | 75 ++++++++++++++++++++++++++ src/compiler/optimize/cse.rs | 2 +- src/compiler/optimize/depgraph.rs | 6 +-- src/compiler/preprocessor/macros.rs | 10 ++-- src/compiler/rename.rs | 11 ++-- src/compiler/sexp.rs | 24 +++------ src/tests/compiler/codegen_funcache.rs | 49 ++++++++++++++--- src/tests/compiler/mod.rs | 1 + 9 files changed, 147 insertions(+), 40 deletions(-) diff --git a/resources/coverage/run_coverage.py b/resources/coverage/run_coverage.py index 34fc80ebb..bfd48fdca 100644 --- a/resources/coverage/run_coverage.py +++ b/resources/coverage/run_coverage.py @@ -1,3 +1,12 @@ +"""Coverage driver for CI and local runs. + +For ad-hoc reports on a subset of tests (e.g. module disk cache / funcache), you can run: + + LLVM_PROFILE_FILE='chialisp-%p-%m.profraw' RUSTFLAGS='-Cinstrument-coverage' \\ + cargo test --lib try_from_cache cache_key_stable funcache_without_dependency test_codegen_function_cache + +Then run grcov on the resulting ``*.profraw`` files (see the ``collect_coverage`` invocation below). +""" import os import json import shutil diff --git a/src/compiler/diskcache.rs b/src/compiler/diskcache.rs index 0b07c5b4a..8cb196429 100644 --- a/src/compiler/diskcache.rs +++ b/src/compiler/diskcache.rs @@ -60,3 +60,78 @@ pub fn set_cache_element( ) { set_cache_element_error(opts, cf, export_path, export_hex).ok(); } + +/// Exposes the cache-key segment used under `.chialisp//` (tests and tooling only). +#[cfg(test)] +pub fn module_cache_key_hex(cf: &CompileForm) -> String { + cache_key(cf) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::compiler::comptypes::{BodyForm, CompileForm, IncludeDesc, IncludeProcessType}; + use crate::compiler::sexp::SExp; + use crate::compiler::srcloc::Srcloc; + + fn empty_compileform(loc: Srcloc) -> CompileForm { + CompileForm { + loc: loc.clone(), + include_forms: Vec::new(), + args: Rc::new(SExp::Nil(loc.clone())), + helpers: Vec::new(), + exp: Rc::new(BodyForm::Quoted(SExp::Nil(loc.clone()))), + } + } + + #[test] + fn cache_key_stable_for_empty_includes() { + let loc = Srcloc::start(&"a.clsp".to_string()); + let cf = empty_compileform(loc); + let k = cache_key(&cf); + assert_eq!( + k, + "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a" + ); + } + + #[test] + fn cache_key_changes_with_concatenated_fingerprints() { + let loc = Srcloc::start(&"b.clsp".to_string()); + let mut cf = empty_compileform(loc.clone()); + let desc = |fp: Vec| IncludeDesc { + kw: loc.clone(), + nl: loc.clone(), + name: b"x".to_vec(), + kind: None, + fingerprint: fp, + }; + cf.include_forms.push(desc(vec![1, 2, 3])); + let k1 = cache_key(&cf); + cf.include_forms.push(desc(vec![4, 5])); + let k2 = cache_key(&cf); + assert_ne!(k1, k2); + cf.include_forms.truncate(1); + let k1_again = cache_key(&cf); + assert_eq!(k1, k1_again); + } + + #[test] + fn cache_key_main_fingerprint_style() { + let loc = Srcloc::start(&"c.clsp".to_string()); + let mut cf = empty_compileform(loc.clone()); + cf.include_forms.push(IncludeDesc { + kw: loc.clone(), + nl: loc.clone(), + name: b"main".to_vec(), + kind: Some(IncludeProcessType::Compiled), + fingerprint: vec![0xab, 0xcd], + }); + let k = cache_key(&cf); + assert!(!k.is_empty()); + assert_ne!( + k, + "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a" + ); + } +} diff --git a/src/compiler/optimize/cse.rs b/src/compiler/optimize/cse.rs index ed29ca1ab..cff0a221e 100644 --- a/src/compiler/optimize/cse.rs +++ b/src/compiler/optimize/cse.rs @@ -225,7 +225,7 @@ fn sorted_cse_detections_by_applicability( .iter() .map(|a| (number_of_overlaps(cse_detections, a), a.clone())) .collect(); - detections_with_dependencies.sort_by(|(a, _), (b, _)| a.cmp(b)); + detections_with_dependencies.sort_by_key(|(a, _)| *a); detections_with_dependencies } diff --git a/src/compiler/optimize/depgraph.rs b/src/compiler/optimize/depgraph.rs index 0a1d59833..71c90298f 100644 --- a/src/compiler/optimize/depgraph.rs +++ b/src/compiler/optimize/depgraph.rs @@ -209,10 +209,8 @@ impl FunctionDependencyGraph { HelperForm::Defun(_, defun) => { self.process_expr(&defun.name, defun.body.clone()); } - HelperForm::Defconstant(dc) => { - if self.options.with_constants { - self.process_expr(&dc.name, dc.body.clone()); - } + HelperForm::Defconstant(dc) if self.options.with_constants => { + self.process_expr(&dc.name, dc.body.clone()); } _ => {} } diff --git a/src/compiler/preprocessor/macros.rs b/src/compiler/preprocessor/macros.rs index f4f673374..38fa223c5 100644 --- a/src/compiler/preprocessor/macros.rs +++ b/src/compiler/preprocessor/macros.rs @@ -45,12 +45,10 @@ fn match_number(body: Rc) -> Result { SExp::QuotedString(ql, b'x', b) => { return Ok(MatchedNumber::MatchedHex(ql.clone(), b.clone())); } - SExp::Atom(al, b) => { - // An atom with unprintable characters is rendered as an integer. - if !printable(b, false) { - let to_integer = number_from_u8(b); - return Ok(MatchedNumber::MatchedInt(al.clone(), to_integer)); - } + // An atom with unprintable characters is rendered as an integer. + SExp::Atom(al, b) if !printable(b, false) => { + let to_integer = number_from_u8(b); + return Ok(MatchedNumber::MatchedInt(al.clone(), to_integer)); } SExp::Nil(il) => { return Ok(MatchedNumber::MatchedInt(il.clone(), bi_zero())); diff --git a/src/compiler/rename.rs b/src/compiler/rename.rs index 2770dcfa4..4069042d3 100644 --- a/src/compiler/rename.rs +++ b/src/compiler/rename.rs @@ -94,12 +94,11 @@ pub fn rename_in_cons( /* Returns a list of pairs containing the old and new atom names */ fn invent_new_names_sexp(body: Rc) -> Vec<(Vec, Vec)> { match body.borrow() { - SExp::Atom(_, name) => { - if name != b"@" { - vec![(name.to_vec(), gensym(name.to_vec()))] - } else { - vec![] - } + SExp::Atom(_, name) if name != b"@" => { + vec![(name.to_vec(), gensym(name.to_vec()))] + } + SExp::Atom(_, _) => { + vec![] } SExp::Cons(_, head, tail) => { let mut head_list = invent_new_names_sexp(head.clone()); diff --git a/src/compiler/sexp.rs b/src/compiler/sexp.rs index b173b8ccd..e0b5dddb8 100644 --- a/src/compiler/sexp.rs +++ b/src/compiler/sexp.rs @@ -1220,25 +1220,17 @@ impl SelectNode for AtomValue<&[u8; N] fn select_nodes(&self, s: Rc) -> Result { let AtomValue::Here(name) = self; match s.borrow() { - SExp::Nil(l) => { - if name.is_empty() { - return Ok(l.clone()); - } + SExp::Nil(l) if name.is_empty() => { + return Ok(l.clone()); } - SExp::Atom(l, n) => { - if n == name { - return Ok(l.clone()); - } + SExp::Atom(l, n) if n == name => { + return Ok(l.clone()); } - SExp::QuotedString(l, _, n) => { - if n == name { - return Ok(l.clone()); - } + SExp::QuotedString(l, _, n) if n == name => { + return Ok(l.clone()); } - SExp::Integer(l, i) => { - if &u8_from_number(i.clone()) == name { - return Ok(l.clone()); - } + SExp::Integer(l, i) if &u8_from_number(i.clone()) == name => { + return Ok(l.clone()); } _ => {} } diff --git a/src/tests/compiler/codegen_funcache.rs b/src/tests/compiler/codegen_funcache.rs index 3a39ef452..c2093878d 100644 --- a/src/tests/compiler/codegen_funcache.rs +++ b/src/tests/compiler/codegen_funcache.rs @@ -109,7 +109,6 @@ fn transform_helper Option>( ) { for h in compileform.helpers.iter_mut() { if let Some(res) = transform(&h) { - eprintln!("{} became {}", h.to_sexp(), res.to_sexp()); *h = res; } } @@ -191,8 +190,6 @@ fn test_codegen_function_cache() { None }); - eprintln!("compileform {}", desugared.to_sexp()); - let dependency_graph = FunctionDependencyGraph::new_with_options( &desugared, DepgraphOptions { @@ -209,12 +206,9 @@ fn test_codegen_function_cache() { ) .unwrap(), ); - eprintln!("generated code {base_generated}"); - let original_map = context.funcache.as_ref().unwrap().function_outputs.clone(); let mut allocator = Allocator::new(); let run_arg = parse_sexp(Srcloc::start("*args*"), b"(3 7)".iter().cloned()).unwrap()[0].clone(); - eprintln!("use args {}", run_arg); let expected_outcome = get_expected_outcome(opts.clone(), run_arg.clone()).unwrap(); let run_outcome_base = run( &mut allocator, @@ -405,9 +399,50 @@ fn test_codegen_function_cache() { let diffcc_ff = extract_helper_gen(&generated_diff_c_map_clean, "FF").unwrap(); let diffcc_gg = extract_helper_gen(&generated_diff_c_map_clean, "GG").unwrap(); let diffcc_h = extract_helper_gen(&generated_diff_c_map_clean, "H").unwrap(); - eprintln!("new H {diffcc_h}"); assert_eq!(diffcc_ff, old_ff); assert_eq!(diffcc_gg, old_gg); assert_ne!(diffcc_h, old_h); } + +/// `Funcache` is only consulted when a `FunctionDependencyGraph` is passed into `codegen`. +#[test] +fn funcache_without_dependency_graph_does_not_store_entries() { + let opts: Rc = Rc::new(DefaultCompilerOpts::new("*fc-no-graph*")).set_dialect( + AcceptedDialect { + stepping: Some(25), + strict: true, + int_fix: true, + extra_numeric_constants: false, + }, + ); + let loc = Srcloc::start("*fc-no-graph*"); + let parsed = parse_sexp( + loc.clone(), + "(mod (X) (defun F (Y) (+ Y 1)) (F X))".bytes(), + ) + .expect("parse"); + let FrontendOutput::CompileForm(cf) = frontend(opts.clone(), &parsed).expect("frontend") + else { + panic!("expected CompileForm"); + }; + let desugared = do_desugar(&cf).expect("desugar"); + let runner = Rc::new(DefaultProgramRunner::new()); + let mut context = BasicCompileContext::new( + Allocator::new(), + runner, + HashMap::new(), + Box::new(Strategy23 {}), + ); + context.funcache = Some(Funcache::default()); + codegen(&mut context, opts, None, &desugared).expect("codegen"); + assert!( + context + .funcache + .as_ref() + .expect("funcache") + .function_outputs + .is_empty(), + "without a dependency graph, per-function cache keys are not computed" + ); +} diff --git a/src/tests/compiler/mod.rs b/src/tests/compiler/mod.rs index e0dcd7f6a..36a7550cf 100644 --- a/src/tests/compiler/mod.rs +++ b/src/tests/compiler/mod.rs @@ -9,6 +9,7 @@ mod cldb; mod clvm; mod codegen_funcache; mod compiler; +mod module_cache; mod evaluate; mod fuzz; mod fuzz_assign; From 2140d5df13b87645d1e95624fe938ca52639e809 Mon Sep 17 00:00:00 2001 From: Adam Kelly <338792+aqk@users.noreply.github.com> Date: Wed, 22 Apr 2026 21:53:45 -0400 Subject: [PATCH 23/26] add missing file --- src/tests/compiler/module_cache.rs | 162 +++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 src/tests/compiler/module_cache.rs diff --git a/src/tests/compiler/module_cache.rs b/src/tests/compiler/module_cache.rs new file mode 100644 index 000000000..687c96f14 --- /dev/null +++ b/src/tests/compiler/module_cache.rs @@ -0,0 +1,162 @@ +//! Tests for module export disk cache (`try_from_cache`) and cache key wiring. + +use std::rc::Rc; + +use clvmr::Allocator; + +use crate::classic::clvm::__type_compatibility__::Stream; +use crate::classic::clvm::sexp::sexp_as_bin; +use crate::compiler::clvm::convert_to_clvm_rs; +use crate::compiler::compiler::{try_from_cache, DefaultCompilerOpts}; +use crate::compiler::comptypes::{ + BodyForm, CompileForm, CompilerOpts, CompilerOutput, Export, ExportFunctionDesc, + ExportProgramDesc, NameAndLoc, +}; +use crate::compiler::diskcache::module_cache_key_hex; +use crate::compiler::sexp::{parse_sexp, SExp}; +use crate::compiler::srcloc::Srcloc; +use crate::tests::compiler::modules::TestModuleCompilerOpts; + +const TEST_CLSP: &str = "module_cache_try.clsp"; + +fn sample_program_hex() -> String { + let mut allocator = Allocator::new(); + let loc = Srcloc::start(&"*hex*".to_string()); + let sexp = parse_sexp(loc.clone(), "(q . 99)".bytes()) + .expect("parse")[0] + .clone(); + let converted = convert_to_clvm_rs(&mut allocator, sexp).expect("convert"); + let mut stream = Stream::new(None); + stream.write(sexp_as_bin(&mut allocator, converted)); + stream.get_value().hex() +} + +fn minimal_module_compileform() -> CompileForm { + let loc = Srcloc::start(&TEST_CLSP.to_string()); + CompileForm { + loc: loc.clone(), + include_forms: Vec::new(), + args: Rc::new(SExp::Nil(loc.clone())), + helpers: Vec::new(), + exp: Rc::new(BodyForm::Quoted(SExp::Nil(loc.clone()))), + } +} + +fn main_export() -> Export { + let loc = Srcloc::start(&TEST_CLSP.to_string()); + Export::MainProgram(ExportProgramDesc { + loc: loc.clone(), + kw_loc: None, + args: Rc::new(SExp::Nil(loc.clone())), + expr: Rc::new(BodyForm::Quoted(SExp::Nil(loc))), + }) +} + +fn function_export(name: &[u8], as_name: Option>) -> Export { + let loc = Srcloc::start(&TEST_CLSP.to_string()); + Export::Function(Box::new(ExportFunctionDesc { + loc: loc.clone(), + kw_loc: None, + name: NameAndLoc { + value: name.to_vec(), + loc: None, + }, + as_loc: None, + as_name: as_name.map(|value| NameAndLoc { value, loc: None }), + })) +} + +fn cache_dir_for(cf: &CompileForm) -> String { + format!(".chialisp/{}/", module_cache_key_hex(cf)) +} + +#[test] +fn try_from_cache_returns_none_when_cache_missing() { + let cf = minimal_module_compileform(); + let orig_opts: Rc = Rc::new(DefaultCompilerOpts::new(TEST_CLSP)); + let wrapped = TestModuleCompilerOpts::new(orig_opts); + let opts: Rc = Rc::new(wrapped); + let out = try_from_cache(opts, &cf, &[main_export()]).expect("try_from_cache"); + assert!(out.is_none()); +} + +#[test] +fn try_from_cache_returns_none_when_second_export_missing() { + let cf = minimal_module_compileform(); + let hex = sample_program_hex(); + let orig_opts: Rc = Rc::new(DefaultCompilerOpts::new(TEST_CLSP)); + let wrapped = TestModuleCompilerOpts::new(orig_opts); + let prefix = cache_dir_for(&cf); + wrapped.set_file_content( + format!("{}{}", prefix, TEST_CLSP.replace(".clsp", ".hex")), + hex.as_bytes().to_vec(), + ); + let opts: Rc = Rc::new(wrapped); + let exports = vec![main_export(), function_export(b"F", None)]; + let out = try_from_cache(opts, &cf, &exports).expect("try_from_cache"); + assert!(out.is_none()); +} + +#[test] +fn try_from_cache_returns_none_on_invalid_cached_hex() { + let cf = minimal_module_compileform(); + let orig_opts: Rc = Rc::new(DefaultCompilerOpts::new(TEST_CLSP)); + let wrapped = TestModuleCompilerOpts::new(orig_opts); + let prefix = cache_dir_for(&cf); + wrapped.set_file_content( + format!("{}{}", prefix, TEST_CLSP.replace(".clsp", ".hex")), + b"not_valid_hex_clvm_zzzz".to_vec(), + ); + let opts: Rc = Rc::new(wrapped); + let out = try_from_cache(opts, &cf, &[main_export()]).expect("try_from_cache"); + assert!(out.is_none()); +} + +#[test] +fn try_from_cache_hits_when_main_program_hex_present() { + let cf = minimal_module_compileform(); + let hex = sample_program_hex(); + let orig_opts: Rc = Rc::new(DefaultCompilerOpts::new(TEST_CLSP)); + let wrapped = TestModuleCompilerOpts::new(orig_opts); + let prefix = cache_dir_for(&cf); + let main_hex_path = format!("{}{}", prefix, TEST_CLSP.replace(".clsp", ".hex")); + wrapped.set_file_content(main_hex_path.clone(), hex.as_bytes().to_vec()); + let opts: Rc = Rc::new(wrapped.clone()); + + let out = try_from_cache(opts, &cf, &[main_export()]).expect("try_from_cache"); + let Some(CompilerOutput::Module(mo)) = out else { + panic!("expected module output"); + }; + assert_eq!(mo.components.len(), 1); + assert_eq!(mo.components[0].shortname, b"program".to_vec()); + assert_eq!(mo.components[0].filename, TEST_CLSP.replace(".clsp", ".hex")); + + let hash_path = format!("{}_hash.hex", TEST_CLSP.trim_end_matches(".clsp")); + assert!( + wrapped.get_written_file(&hash_path).is_some(), + "expected treehash sidecar {hash_path}, got {:?}", + wrapped.list_written_files() + ); +} + +#[test] +fn try_from_cache_uses_as_name_for_hex_path() { + let cf = minimal_module_compileform(); + let hex = sample_program_hex(); + let orig_opts: Rc = Rc::new(DefaultCompilerOpts::new(TEST_CLSP)); + let wrapped = TestModuleCompilerOpts::new(orig_opts); + let prefix = cache_dir_for(&cf); + let export = function_export(b"F", Some(b"RenamedF".to_vec())); + // Matches `create_hex_output_path`: `_.hex` with func dotted with `hex`. + let rel_hex = format!("{}_{}.hex", TEST_CLSP.trim_end_matches(".clsp"), "RenamedF"); + let cache_path = format!("{prefix}{rel_hex}"); + wrapped.set_file_content(cache_path, hex.as_bytes().to_vec()); + let opts: Rc = Rc::new(wrapped); + + let out = try_from_cache(opts, &cf, &[export]).expect("try_from_cache"); + let Some(CompilerOutput::Module(mo)) = out else { + panic!("expected module output"); + }; + assert_eq!(mo.components.len(), 1); + assert_eq!(mo.components[0].shortname, b"F".to_vec()); +} From 9027df14523f323494468c2fbf2169053209eaad Mon Sep 17 00:00:00 2001 From: Adam Kelly <338792+aqk@users.noreply.github.com> Date: Wed, 22 Apr 2026 22:02:15 -0400 Subject: [PATCH 24/26] fix type mismatch --- src/compiler/diskcache.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/compiler/diskcache.rs b/src/compiler/diskcache.rs index 1a2caa0b1..ba189f450 100644 --- a/src/compiler/diskcache.rs +++ b/src/compiler/diskcache.rs @@ -99,16 +99,21 @@ mod tests { fn cache_key_changes_with_concatenated_fingerprints() { let loc = Srcloc::start(&"b.clsp".to_string()); let mut cf = empty_compileform(loc.clone()); - let desc = |fp: Vec| IncludeDesc { + let fp = |prefix: &[u8]| { + let mut a = [0u8; 32]; + a[..prefix.len()].copy_from_slice(prefix); + a + }; + let desc = |fp: [u8; 32]| IncludeDesc { kw: loc.clone(), nl: loc.clone(), name: b"x".to_vec(), kind: None, fingerprint: fp, }; - cf.include_forms.push(desc(vec![1, 2, 3])); + cf.include_forms.push(desc(fp(&[1, 2, 3]))); let k1 = cache_key(&cf); - cf.include_forms.push(desc(vec![4, 5])); + cf.include_forms.push(desc(fp(&[4, 5]))); let k2 = cache_key(&cf); assert_ne!(k1, k2); cf.include_forms.truncate(1); @@ -120,12 +125,15 @@ mod tests { fn cache_key_main_fingerprint_style() { let loc = Srcloc::start(&"c.clsp".to_string()); let mut cf = empty_compileform(loc.clone()); + let mut main_fp = [0u8; 32]; + main_fp[0] = 0xab; + main_fp[1] = 0xcd; cf.include_forms.push(IncludeDesc { kw: loc.clone(), nl: loc.clone(), name: b"main".to_vec(), kind: Some(IncludeProcessType::Compiled), - fingerprint: vec![0xab, 0xcd], + fingerprint: main_fp, }); let k = cache_key(&cf); assert!(!k.is_empty()); From 3285396b3f7c0646b29ee71fc0c040b10189addd Mon Sep 17 00:00:00 2001 From: Adam Kelly <338792+aqk@users.noreply.github.com> Date: Wed, 22 Apr 2026 23:38:03 -0400 Subject: [PATCH 25/26] fmt --- src/tests/compiler/codegen_funcache.rs | 17 ++++++----------- src/tests/compiler/mod.rs | 2 +- src/tests/compiler/module_cache.rs | 9 +++++---- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/tests/compiler/codegen_funcache.rs b/src/tests/compiler/codegen_funcache.rs index c2093878d..bd7238472 100644 --- a/src/tests/compiler/codegen_funcache.rs +++ b/src/tests/compiler/codegen_funcache.rs @@ -408,22 +408,17 @@ fn test_codegen_function_cache() { /// `Funcache` is only consulted when a `FunctionDependencyGraph` is passed into `codegen`. #[test] fn funcache_without_dependency_graph_does_not_store_entries() { - let opts: Rc = Rc::new(DefaultCompilerOpts::new("*fc-no-graph*")).set_dialect( - AcceptedDialect { + let opts: Rc = Rc::new(DefaultCompilerOpts::new("*fc-no-graph*")) + .set_dialect(AcceptedDialect { stepping: Some(25), strict: true, int_fix: true, extra_numeric_constants: false, - }, - ); + }); let loc = Srcloc::start("*fc-no-graph*"); - let parsed = parse_sexp( - loc.clone(), - "(mod (X) (defun F (Y) (+ Y 1)) (F X))".bytes(), - ) - .expect("parse"); - let FrontendOutput::CompileForm(cf) = frontend(opts.clone(), &parsed).expect("frontend") - else { + let parsed = + parse_sexp(loc.clone(), "(mod (X) (defun F (Y) (+ Y 1)) (F X))".bytes()).expect("parse"); + let FrontendOutput::CompileForm(cf) = frontend(opts.clone(), &parsed).expect("frontend") else { panic!("expected CompileForm"); }; let desugared = do_desugar(&cf).expect("desugar"); diff --git a/src/tests/compiler/mod.rs b/src/tests/compiler/mod.rs index 36a7550cf..2bf88fde0 100644 --- a/src/tests/compiler/mod.rs +++ b/src/tests/compiler/mod.rs @@ -9,10 +9,10 @@ mod cldb; mod clvm; mod codegen_funcache; mod compiler; -mod module_cache; mod evaluate; mod fuzz; mod fuzz_assign; +mod module_cache; mod modules; mod optimizer; mod preprocessor; diff --git a/src/tests/compiler/module_cache.rs b/src/tests/compiler/module_cache.rs index 687c96f14..77f511057 100644 --- a/src/tests/compiler/module_cache.rs +++ b/src/tests/compiler/module_cache.rs @@ -22,9 +22,7 @@ const TEST_CLSP: &str = "module_cache_try.clsp"; fn sample_program_hex() -> String { let mut allocator = Allocator::new(); let loc = Srcloc::start(&"*hex*".to_string()); - let sexp = parse_sexp(loc.clone(), "(q . 99)".bytes()) - .expect("parse")[0] - .clone(); + let sexp = parse_sexp(loc.clone(), "(q . 99)".bytes()).expect("parse")[0].clone(); let converted = convert_to_clvm_rs(&mut allocator, sexp).expect("convert"); let mut stream = Stream::new(None); stream.write(sexp_as_bin(&mut allocator, converted)); @@ -129,7 +127,10 @@ fn try_from_cache_hits_when_main_program_hex_present() { }; assert_eq!(mo.components.len(), 1); assert_eq!(mo.components[0].shortname, b"program".to_vec()); - assert_eq!(mo.components[0].filename, TEST_CLSP.replace(".clsp", ".hex")); + assert_eq!( + mo.components[0].filename, + TEST_CLSP.replace(".clsp", ".hex") + ); let hash_path = format!("{}_hash.hex", TEST_CLSP.trim_end_matches(".clsp")); assert!( From 085951142b64a06c5e878b155d256cba329f1f89 Mon Sep 17 00:00:00 2001 From: Adam Kelly <338792+aqk@users.noreply.github.com> Date: Wed, 22 Apr 2026 23:40:18 -0400 Subject: [PATCH 26/26] ignore *.profraw --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index d88dc51ca..2f823b2d1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +*.profraw target wasm/target .idea