Skip to content
Closed
12 changes: 0 additions & 12 deletions .bifrost/suppressions.json
Original file line number Diff line number Diff line change
Expand Up @@ -133,18 +133,6 @@
"accepted_at": "2026-08-04",
"expires_at": null
},
{
"policy_id": "bifrost.performance.serialization-in-loop",
"finding_id": "2175df8014fc064443d2425243532bed8e2ad9ee917ef165c8cd7e9a31adbfcf",
"path": "crates/bifrost-analysis/src/searchtools/scan_usages.rs",
"identity_stability": "strong",
"status": "accepted",
"reason": "Each iteration serializes a distinct item; the serialization is inherent to the loop and cannot be hoisted.",
"policy_hash_at_acceptance": "da0b97c9fccd69df43320804e18b2325952a603f136ad01af388816b2a49a4b4",
"accepted_by": "dbakereffendi",
"accepted_at": "2026-08-04",
"expires_at": null
},
{
"policy_id": "bifrost.performance.sleep-in-loop",
"finding_id": "fe639e787c5ab14fa523237e759da98dab58a64b440d731bbf5eccdc97e91ae2",
Expand Down
167 changes: 86 additions & 81 deletions crates/bifrost-analysis/src/analyzer/analyzer_definition_lookup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -444,8 +444,8 @@ impl<'a> AnalyzerDefinitionLookup<'a> {
matches
}

/// Resolve many rendered names into the shared fqn memo with two batched
/// relational reads per language instead of one point batch per name.
/// Resolve rendered names in one language into the shared fqn memo with
/// two batched relational reads instead of one point batch per name.
///
/// The rounds are the same two questions [`Self::exact_for_language`]
/// asks per name -- an exact persisted-identity seek, then the identifier
Expand All @@ -454,95 +454,100 @@ impl<'a> AnalyzerDefinitionLookup<'a> {
/// what the point path would compute. A cancelled or failed batch
/// memoizes nothing: every name stays unmemoized and the point path
/// retries it with unchanged results.
pub(crate) fn prefetch_fqns(&self, fqns: &[String]) {
for language in self.query_languages() {
let missing: Vec<String> = {
let cache = self
.memo
.fqn_cache
.lock()
.expect("definition fqn cache poisoned");
let mut seen = HashSet::default();
fqns.iter()
.filter(|fqn| seen.insert(fqn.as_str()))
.filter(|fqn| !cache.contains_key(&(language, (*fqn).clone())))
.cloned()
.collect()
};
if missing.is_empty() {
continue;
}
pub(crate) fn prefetch_fqn_in_language(&self, language: Language, fqns: &[String]) {
let missing: Vec<String> = {
let cache = self
.memo
.fqn_cache
.lock()
.expect("definition fqn cache poisoned");
let mut seen = HashSet::default();
fqns.iter()
.filter(|fqn| seen.insert(fqn.as_str()))
.filter(|fqn| !cache.contains_key(&(language, (*fqn).clone())))
.cloned()
.collect()
};
if missing.is_empty() {
return;
}

let mut exact_owners = Vec::new();
let mut exact_questions = Vec::new();
for (index, fqn) in missing.iter().enumerate() {
if let Some(name) = Self::rendered_name(language, fqn) {
exact_owners.push(index);
exact_questions.push((name, RelationalDefinitionQuery::ExactName));
}
let mut exact_owners = Vec::new();
let mut exact_questions = Vec::new();
for (index, fqn) in missing.iter().enumerate() {
if let Some(name) = Self::rendered_name(language, fqn) {
exact_owners.push(index);
exact_questions.push((name, RelationalDefinitionQuery::ExactName));
}
// A name the language cannot even render as a path resolves to
// nothing without a fallback, exactly as the point path answers.
let mut parseable = vec![false; missing.len()];
let mut units_by_name: Vec<Vec<CodeUnit>> = vec![Vec::new(); missing.len()];
if !exact_questions.is_empty() {
let expected = exact_questions.len();
let values = self.query_values(language, exact_questions);
if values.len() != expected {
return;
}
for (owner, value) in exact_owners.into_iter().zip(values) {
parseable[owner] = true;
match value {
RelationalDefinitionValue::Definitions(units) => {
units_by_name[owner] = units;
}
_ => panic!("an exact-name query returned the wrong result shape"),
}
// A name the language cannot even render as a path resolves to
// nothing without a fallback, exactly as the point path answers.
let mut parseable = vec![false; missing.len()];
let mut units_by_name: Vec<Vec<CodeUnit>> = vec![Vec::new(); missing.len()];
if !exact_questions.is_empty() {
let expected = exact_questions.len();
let values = self.query_values(language, exact_questions);
if values.len() != expected {
return;
}
for (owner, value) in exact_owners.into_iter().zip(values) {
parseable[owner] = true;
match value {
RelationalDefinitionValue::Definitions(units) => {
units_by_name[owner] = units;
}
_ => panic!("an exact-name query returned the wrong result shape"),
}
}
}

let mut fallback: Vec<(usize, std::ops::Range<usize>, Vec<String>)> = Vec::new();
let mut fallback_questions = Vec::new();
for (index, fqn) in missing.iter().enumerate() {
units_by_name[index].retain(|unit| unit.fq_name() == *fqn);
if !parseable[index] || !units_by_name[index].is_empty() {
continue;
}
let identifiers = self.rendered_identifier_candidates(language, fqn);
let start = fallback_questions.len();
fallback_questions.extend(Self::identifier_queries(language, &identifiers, None));
fallback.push((index, start..fallback_questions.len(), identifiers));
let mut fallback: Vec<(usize, std::ops::Range<usize>, Vec<String>)> = Vec::new();
let mut fallback_questions = Vec::new();
for (index, fqn) in missing.iter().enumerate() {
units_by_name[index].retain(|unit| unit.fq_name() == *fqn);
if !parseable[index] || !units_by_name[index].is_empty() {
continue;
}
if !fallback_questions.is_empty() {
let expected = fallback_questions.len();
let values = self.query_values(language, fallback_questions);
if values.len() != expected {
return;
}
let mut values = values.into_iter().map(Some).collect::<Vec<_>>();
for (index, range, identifiers) in fallback {
let name_values = values[range]
.iter_mut()
.map(|value| value.take().expect("each fallback value is consumed once"))
.collect::<Vec<_>>();
units_by_name[index] =
Self::identifier_units_from_values(&identifiers, name_values);
units_by_name[index].retain(|unit| unit.fq_name() == missing[index]);
}
let identifiers = self.rendered_identifier_candidates(language, fqn);
let start = fallback_questions.len();
fallback_questions.extend(Self::identifier_queries(language, &identifiers, None));
fallback.push((index, start..fallback_questions.len(), identifiers));
}
if !fallback_questions.is_empty() {
let expected = fallback_questions.len();
let values = self.query_values(language, fallback_questions);
if values.len() != expected {
return;
}

let mut cache = self
.memo
.fqn_cache
.lock()
.expect("definition fqn cache poisoned");
for (fqn, mut units) in missing.into_iter().zip(units_by_name) {
sort_units(&mut units);
units.dedup();
cache.insert((language, fqn), units);
let mut values = values.into_iter().map(Some).collect::<Vec<_>>();
for (index, range, identifiers) in fallback {
let name_values = values[range]
.iter_mut()
.map(|value| value.take().expect("each fallback value is consumed once"))
.collect::<Vec<_>>();
units_by_name[index] =
Self::identifier_units_from_values(&identifiers, name_values);
units_by_name[index].retain(|unit| unit.fq_name() == missing[index]);
}
}

let mut cache = self
.memo
.fqn_cache
.lock()
.expect("definition fqn cache poisoned");
for (fqn, mut units) in missing.into_iter().zip(units_by_name) {
sort_units(&mut units);
units.dedup();
cache.insert((language, fqn), units);
}
}

/// Resolve many rendered names across every supported language.
pub(crate) fn prefetch_fqns(&self, fqns: &[String]) {
for language in self.query_languages() {
self.prefetch_fqn_in_language(language, fqns);
}
}
}

Expand Down
7 changes: 7 additions & 0 deletions crates/bifrost-analysis/src/analyzer/cpp/imports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,13 @@ pub enum HeaderLanguageAttribution {
impl TestDetectionProvider for CppAnalyzer {}

impl ImportAnalysisProvider for CppAnalyzer {
fn import_infos_for_files(
&self,
files: &[ProjectFile],
) -> Option<crate::hash::HashMap<ProjectFile, Vec<crate::analyzer::ImportInfo>>> {
Some(self.inner.bulk_import_infos(files.iter().cloned()))
}

fn file_dependency_facts_for_files(
&self,
files: &[ProjectFile],
Expand Down
40 changes: 40 additions & 0 deletions crates/bifrost-analysis/src/analyzer/cpp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1632,6 +1632,10 @@ impl IAnalyzer for CppAnalyzer {
self.inner.end_query(context);
}

fn prefetch_definitions(&self, fq_names: &[String]) {
self.inner.prefetch_definitions(fq_names);
}

fn record_query_failure(&self, error: crate::analyzer::store::StoreError) {
self.inner.record_query_failure(error);
}
Expand Down Expand Up @@ -1824,6 +1828,42 @@ impl IAnalyzer for CppAnalyzer {

#[cfg(any(test, feature = "test-support"))]
impl crate::analyzer::AnalyzerTestHooks for CppAnalyzer {
fn reset_relational_definition_batch_call_count_for_test(&self) {
self.inner
.test_hooks()
.reset_relational_definition_batch_call_count_for_test();
}

fn relational_definition_batch_call_count_for_test(&self) -> usize {
self.inner
.test_hooks()
.relational_definition_batch_call_count_for_test()
}

fn reset_definition_candidates_query_count_for_test(&self) {
self.inner
.test_hooks()
.reset_definition_candidates_query_count_for_test();
}

fn definition_candidates_query_count_for_test(&self) -> usize {
self.inner
.test_hooks()
.definition_candidates_query_count_for_test()
}

fn reset_definition_prefetch_batch_count_for_test(&self) {
self.inner
.test_hooks()
.reset_definition_prefetch_batch_count_for_test();
}

fn definition_prefetch_batch_count_for_test(&self) -> usize {
self.inner
.test_hooks()
.definition_prefetch_batch_count_for_test()
}

fn reset_full_declaration_scan_count_for_test(&self) {
self.inner
.test_hooks()
Expand Down
16 changes: 16 additions & 0 deletions crates/bifrost-analysis/src/analyzer/csharp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1108,6 +1108,10 @@ impl IAnalyzer for CSharpAnalyzer {
self.inner.end_query(context);
}

fn prefetch_definitions(&self, fq_names: &[String]) {
self.inner.prefetch_definitions(fq_names);
}

fn record_query_failure(&self, error: crate::analyzer::store::StoreError) {
self.inner.record_query_failure(error);
}
Expand Down Expand Up @@ -1318,6 +1322,18 @@ impl crate::analyzer::AnalyzerTestHooks for CSharpAnalyzer {
.definition_candidates_query_count_for_test()
}

fn reset_relational_definition_batch_call_count_for_test(&self) {
self.inner
.test_hooks()
.reset_relational_definition_batch_call_count_for_test();
}

fn relational_definition_batch_call_count_for_test(&self) -> usize {
self.inner
.test_hooks()
.relational_definition_batch_call_count_for_test()
}

fn reset_full_declaration_scan_count_for_test(&self) {
self.inner
.test_hooks()
Expand Down
7 changes: 7 additions & 0 deletions crates/bifrost-analysis/src/analyzer/go/imports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ use super::GoAnalyzer;
use crate::analyzer::{AnalyzerQueryScope, QueryScope};

impl ImportAnalysisProvider for GoAnalyzer {
fn import_infos_for_files(
&self,
files: &[ProjectFile],
) -> Option<crate::hash::HashMap<ProjectFile, Vec<crate::analyzer::ImportInfo>>> {
Some(self.inner.bulk_import_infos(files.iter().cloned()))
}

fn file_dependency_facts_for_files(
&self,
files: &[ProjectFile],
Expand Down
40 changes: 40 additions & 0 deletions crates/bifrost-analysis/src/analyzer/go/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -793,6 +793,10 @@ impl IAnalyzer for GoAnalyzer {
self.inner.end_query(context);
}

fn prefetch_definitions(&self, fq_names: &[String]) {
self.inner.prefetch_definitions(fq_names);
}

fn record_query_failure(&self, error: crate::analyzer::store::StoreError) {
self.inner.record_query_failure(error);
}
Expand Down Expand Up @@ -1015,6 +1019,42 @@ impl crate::analyzer::AnalyzerTestHooks for GoAnalyzer {
.evaluation_root_continuation_semantic_cache_revivals_for_test()
}

fn reset_relational_definition_batch_call_count_for_test(&self) {
self.inner
.test_hooks()
.reset_relational_definition_batch_call_count_for_test();
}

fn relational_definition_batch_call_count_for_test(&self) -> usize {
self.inner
.test_hooks()
.relational_definition_batch_call_count_for_test()
}

fn reset_definition_candidates_query_count_for_test(&self) {
self.inner
.test_hooks()
.reset_definition_candidates_query_count_for_test();
}

fn definition_candidates_query_count_for_test(&self) -> usize {
self.inner
.test_hooks()
.definition_candidates_query_count_for_test()
}

fn reset_definition_prefetch_batch_count_for_test(&self) {
self.inner
.test_hooks()
.reset_definition_prefetch_batch_count_for_test();
}

fn definition_prefetch_batch_count_for_test(&self) -> usize {
self.inner
.test_hooks()
.definition_prefetch_batch_count_for_test()
}

fn reset_full_declaration_scan_count_for_test(&self) {
self.inner
.test_hooks()
Expand Down
Loading
Loading