Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 16 additions & 17 deletions src/compiler/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -387,14 +387,7 @@ fn populate_export_map(
///
/// (export foo)
/// (export bar)
pub fn compile_module(
context: &mut BasicCompileContext,
mut opts: Rc<dyn CompilerOpts>,
standalone_constants: &HashSet<Vec<u8>>,
mut program: CompileForm,
exports: &[Export],
) -> Result<CompileModuleOutput, CompileErr> {
let loc = program.loc();
fn module_compile_opts(opts: Rc<dyn CompilerOpts>) -> Rc<dyn CompilerOpts> {
let mut dialect = opts.dialect();
// Module style is new, so there will never be a time when we don't want every
// bug fix that existed before it was released.
Expand All @@ -405,7 +398,19 @@ pub fn compile_module(
dialect.stepping = Some(26);
}
}
opts = opts.set_optimize(true).set_dialect(dialect);

opts.set_optimize(true).set_dialect(dialect)
}

pub fn compile_module(
context: &mut BasicCompileContext,
mut opts: Rc<dyn CompilerOpts>,
standalone_constants: &HashSet<Vec<u8>>,
mut program: CompileForm,
exports: &[Export],
) -> Result<CompileModuleOutput, CompileErr> {
let loc = program.loc();
opts = module_compile_opts(opts);

if exports.is_empty() {
return Err(CompileErr(
Expand Down Expand Up @@ -844,20 +849,14 @@ pub fn compile_pre_forms(
)),
FrontendOutput::Module(mut cf, exports) => {
add_main_fingerprint(&mut cf, pre_forms);
let opts = module_compile_opts(opts);

// If we can read from cache, use that. We'll use the actual form of the compileform
// and opts (the dialect) to determine a cache hit.
if let Some(result) = try_from_cache(opts.clone(), &cf, &exports)? {
return Ok(result);
}

// cl23 always reflects optimization.
let dialect = opts.dialect();
let opts = if let Some(stepping) = dialect.stepping.as_ref() {
opts.set_optimize(*stepping > 21)
} else {
opts
};

// We make a dependency graph of constants and functions. There must
// be a solveable hierarchy for constants, that is some must be top
// level constants that are not depended on. Thse will not be tabled
Expand Down
68 changes: 54 additions & 14 deletions src/compiler/diskcache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,25 @@ use crate::compiler::clvm::sha256tree_from_atom;
use crate::compiler::comptypes::{CompileErr, CompileForm, CompilerOpts};
use crate::compiler::sexp::decode_string;

fn cache_key(cf: &CompileForm) -> String {
let mut include_fingerprints = Vec::new();
fn cache_key(opts: Rc<dyn CompilerOpts>, cf: &CompileForm) -> String {
let dialect = opts.dialect();
let mut key_material = b"module-cache-v2".to_vec();

if let Some(stepping) = dialect.stepping {
key_material.push(1);
key_material.extend_from_slice(&stepping.to_le_bytes());
} else {
key_material.push(0);
}
key_material.push(u8::from(dialect.strict));
key_material.push(u8::from(dialect.int_fix));
key_material.push(u8::from(dialect.extra_numeric_constants));
key_material.push(u8::from(dialect.cse_dominance));

for include in cf.include_forms.iter() {
include_fingerprints.extend_from_slice(&include.fingerprint);
key_material.extend_from_slice(&include.fingerprint);
}
hex::encode(sha256tree_from_atom(&include_fingerprints))
hex::encode(sha256tree_from_atom(&key_material))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cache key dialect not normalized

Medium Severity

cache_key hashes opts.dialect() as passed into try_from_cache, but module compilation rewrites dialect (int_fix, stepping floor, cse_dominance) before set_cache_element. Lookup and store therefore use different key material, so incremental module disk cache hits fail even when entries exist.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9c21fd1. Configure here.

}

/// Try to get an element from the cache, exposing errors.
Expand All @@ -30,7 +43,7 @@ pub fn try_element_from_cache(
cf: &CompileForm,
export_path: &str,
) -> Option<String> {
let key = cache_key(cf);
let key = cache_key(opts.clone(), cf);
let hex_file_name = format!(".chialisp/{key}/{export_path}");
opts.read_new_file(cf.loc().file.to_string(), hex_file_name.clone())
.ok()
Expand All @@ -43,7 +56,7 @@ pub fn set_cache_element_error(
export_path: &str,
export_hex: &str,
) -> Result<(), CompileErr> {
let key = cache_key(cf);
let key = cache_key(opts.clone(), cf);
let hex_file_name = format!(".chialisp/{key}/{export_path}");
opts.write_new_file(&hex_file_name, export_hex.as_bytes())?;
Ok(())
Expand All @@ -63,14 +76,16 @@ pub fn set_cache_element(

/// Exposes the cache-key segment used under `.chialisp/<key>/` (tests and tooling only).
#[cfg(test)]
pub fn module_cache_key_hex(cf: &CompileForm) -> String {
cache_key(cf)
pub fn module_cache_key_hex(opts: Rc<dyn CompilerOpts>, cf: &CompileForm) -> String {
cache_key(opts, cf)
}

#[cfg(test)]
mod tests {
use super::*;
use crate::compiler::compiler::DefaultCompilerOpts;
use crate::compiler::comptypes::{BodyForm, CompileForm, IncludeDesc, IncludeProcessType};
use crate::compiler::dialect::AcceptedDialect;
use crate::compiler::sexp::SExp;
use crate::compiler::srcloc::Srcloc;

Expand All @@ -84,14 +99,18 @@ mod tests {
}
}

fn default_opts(filename: &str) -> Rc<dyn CompilerOpts> {
Rc::new(DefaultCompilerOpts::new(filename))
}

#[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);
let k = cache_key(default_opts("a.clsp"), &cf);
assert_eq!(
k,
"4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"
"bb1dfe276a7165264b23349a3d349c470f451aa964f89c172bfb5bc8fdf9d548"
);
}

Expand All @@ -112,15 +131,36 @@ mod tests {
fingerprint: fp,
};
cf.include_forms.push(desc(fp(&[1, 2, 3])));
let k1 = cache_key(&cf);
let k1 = cache_key(default_opts("b.clsp"), &cf);
cf.include_forms.push(desc(fp(&[4, 5])));
let k2 = cache_key(&cf);
let k2 = cache_key(default_opts("b.clsp"), &cf);
assert_ne!(k1, k2);
cf.include_forms.truncate(1);
let k1_again = cache_key(&cf);
let k1_again = cache_key(default_opts("b.clsp"), &cf);
assert_eq!(k1, k1_again);
}

#[test]
fn cache_key_changes_with_cse_dominance() {
let loc = Srcloc::start(&"cse.clsp".to_string());
let cf = empty_compileform(loc);
let dialect_before = AcceptedDialect {
stepping: Some(26),
strict: true,
int_fix: true,
extra_numeric_constants: false,
cse_dominance: false,
};
let dialect_after = AcceptedDialect {
cse_dominance: true,
..dialect_before.clone()
};
let before = default_opts("cse.clsp").set_dialect(dialect_before);
let after = default_opts("cse.clsp").set_dialect(dialect_after);

assert_ne!(cache_key(before, &cf), cache_key(after, &cf));
}

#[test]
fn cache_key_main_fingerprint_style() {
let loc = Srcloc::start(&"c.clsp".to_string());
Expand All @@ -135,7 +175,7 @@ mod tests {
kind: Some(IncludeProcessType::Compiled),
fingerprint: main_fp,
});
let k = cache_key(&cf);
let k = cache_key(default_opts("c.clsp"), &cf);
assert!(!k.is_empty());
assert_ne!(
k,
Expand Down
3 changes: 2 additions & 1 deletion src/tests/compiler/module_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ fn function_export(name: &[u8], as_name: Option<Vec<u8>>) -> Export {
}

fn cache_dir_for(cf: &CompileForm) -> String {
format!(".chialisp/{}/", module_cache_key_hex(cf))
let opts: Rc<dyn CompilerOpts> = Rc::new(DefaultCompilerOpts::new(TEST_CLSP));
format!(".chialisp/{}/", module_cache_key_hex(opts, cf))
}

#[test]
Expand Down
Loading