diff --git a/codegraph-kernel/src/resolve/mod.rs b/codegraph-kernel/src/resolve/mod.rs index 4fe542b1a..2df678837 100644 --- a/codegraph-kernel/src/resolve/mod.rs +++ b/codegraph-kernel/src/resolve/mod.rs @@ -776,6 +776,13 @@ pub struct KernelResolver { /// is_lexically_reachable's `bare` regex, by reference_name — refs are /// evaluated in per-ref loops, so one entry covers a whole loop. bare_name_memo: Option<(String, bool)>, + /// bare_call_receiver's answer per ref (the same question is asked once + /// per same-file candidate). Keyed on a content hash of the ref's + /// fields — identical key means identical input, so no clearing needed. + bare_receiver_memo: HashMap, String)>>, + /// is_generated_file per path — the >256-pool scans run its ~25 + /// regexes once per candidate; pools share few unique paths. + gen_path_memo: HashMap, language_type_scope_memo: HashMap>, scala_package_object_membership_memo: HashMap>, cpp_default_declaration_memo: HashMap, @@ -987,6 +994,8 @@ impl KernelResolver { lexical_scope_memo: HashMap::new(), js_decl_scope_memo: HashMap::new(), bare_name_memo: None, + bare_receiver_memo: HashMap::new(), + gen_path_memo: HashMap::new(), language_type_scope_memo: HashMap::new(), scala_package_object_membership_memo: HashMap::new(), cpp_default_declaration_memo: HashMap::new(), @@ -1179,6 +1188,18 @@ impl KernelResolver { Ok(out) } + /// is_generated_file, memoized per path — the >256-pool generated-share + /// scans run the ~25-regex check once per candidate; pools share few + /// unique file paths. + pub(super) fn is_gen_path(&mut self, path: &str) -> bool { + if let Some(&b) = self.gen_path_memo.get(path) { + return b; + } + let b = crate::textutil::is_generated_file(path); + self.gen_path_memo.insert(path.to_string(), b); + b + } + /// `SELECT generated FROM files WHERE path = ?` — the join's fallback /// when a ref's stored file_path was empty (see read_pending_batch). fn file_generated(&mut self, path: &str) -> Result> { @@ -1196,6 +1217,10 @@ impl KernelResolver { /// The `settle` stage — one verdict per ref in input order. #[cfg_attr(feature = "addon", napi)] pub fn resolve_chunk(&mut self, refs: Vec) -> Result> { + // Memos carry only intra-batch value; bounding them per entry point + // also drops the Rcs bare_receiver_memo pins. + self.bare_receiver_memo.clear(); + self.gen_path_memo.clear(); let mut out = Vec::with_capacity(refs.len()); for r in refs { let t0 = PROF_ON.then(std::time::Instant::now); @@ -1256,6 +1281,8 @@ impl KernelResolver { /// run after the main pass wrote every implements/extends edge. #[cfg_attr(feature = "addon", napi)] pub fn resolve_deferred_this_members(&mut self, refs: Vec) -> Result> { + self.bare_receiver_memo.clear(); + self.gen_path_memo.clear(); let mut out = Vec::with_capacity(refs.len()); for r in refs { if !is_migrated_language(&r.language) { @@ -1277,6 +1304,8 @@ impl KernelResolver { /// every other chain through the dotted arm, then the language gate. #[cfg_attr(feature = "addon", napi)] pub fn resolve_deferred_chains(&mut self, refs: Vec) -> Result> { + self.bare_receiver_memo.clear(); + self.gen_path_memo.clear(); let mut out = Vec::with_capacity(refs.len()); for r in refs { if !is_migrated_language(&r.language) { diff --git a/codegraph-kernel/src/resolve/names.rs b/codegraph-kernel/src/resolve/names.rs index cc96a7091..46e0c77b6 100644 --- a/codegraph-kernel/src/resolve/names.rs +++ b/codegraph-kernel/src/resolve/names.rs @@ -723,7 +723,7 @@ impl KernelResolver { // pools (Migration, __init__) proceed untouched. if named.len() > GENERATED_NAME_POOL_CAP && (r.generated == Some(true) - || named.iter().filter(|n| crate::textutil::is_generated_file(&n.file_path)).count() * 4 + || named.iter().filter(|n| self.is_gen_path(&n.file_path)).count() * 4 >= named.len() * 3) { return Ok(None); @@ -1019,7 +1019,7 @@ impl KernelResolver { // at least as wide, so decline before the retain chain. if candidates.len() > GENERATED_NAME_POOL_CAP && (r.generated == Some(true) - || candidates.iter().filter(|n| crate::textutil::is_generated_file(&n.file_path)).count() * 4 + || candidates.iter().filter(|n| self.is_gen_path(&n.file_path)).count() * 4 >= candidates.len() * 3) { return Ok(None); diff --git a/codegraph-kernel/src/resolve/overloads_upstream.rs b/codegraph-kernel/src/resolve/overloads_upstream.rs index 54837b25b..7f5284746 100644 --- a/codegraph-kernel/src/resolve/overloads_upstream.rs +++ b/codegraph-kernel/src/resolve/overloads_upstream.rs @@ -917,6 +917,26 @@ impl KernelResolver { /// `this.container.classList.toggle()`); None for a call written bare or /// not found. pub(super) fn bare_call_receiver(&mut self, r: &ResolveRefIn) -> Res, String)>> { + // The answer depends only on the ref's fields, but is asked once per + // same-file candidate — memoize on a content hash of them (no alloc, + // no pointer aliasing, dedupes identical refs across a chunk too). + use std::hash::{Hash, Hasher}; + let mut h = std::collections::hash_map::DefaultHasher::new(); + r.file_path.hash(&mut h); + r.from_node_id.hash(&mut h); + r.reference_name.hash(&mut h); + r.line.hash(&mut h); + r.column.hash(&mut h); + let key = h.finish(); + if let Some(hit) = self.bare_receiver_memo.get(&key) { + return Ok(hit.clone()); + } + let out = self.bare_call_receiver_uncached(r)?; + self.bare_receiver_memo.insert(key, out.clone()); + Ok(out) + } + + fn bare_call_receiver_uncached(&mut self, r: &ResolveRefIn) -> Res, String)>> { if !re!(r"^[A-Za-z_$][\w$]*$").is_match(&r.reference_name) { return Ok(None); }