code-moniker-workspace 0.6.1

Workspace model, ports, snapshots, linkage, and change analysis for code-moniker.
Documentation
use std::collections::HashSet;
use std::hash::Hash;

use rayon::prelude::*;
use rustc_hash::FxHashMap;

use super::{LinkageMemoryMetrics, ReferenceLinkageDecision};
use crate::linkage::catalog::{
	LinkageQuery, ReferenceOrdinal, ReferenceSet, SymbolOrdinal, SymbolOrdinalCatalog, query_keys,
};
use crate::snapshot::{RecordTable, ReferenceId, ReferenceRecord, SourceId, SymbolId};
use crate::source::CodeIndexMaterial;

#[derive(Clone)]
pub(in crate::linkage) struct LinkageStoreIndexes {
	pub(in crate::linkage) reference_indexes: FxHashMap<ReferenceId, ReferenceOrdinal>,
	pub(in crate::linkage) references_by_source_root: FxHashMap<usize, ReferenceSet>,
	pub(in crate::linkage) references_by_name: FxHashMap<Vec<u8>, ReferenceSet>,
	pub(in crate::linkage) references_by_source_symbol: FxHashMap<SymbolId, ReferenceSet>,
	pub(in crate::linkage) references_by_call_name: FxHashMap<Vec<u8>, ReferenceSet>,
	pub(in crate::linkage) resolved_by_target_source: Option<ResolvedTargetSourceIndex>,
}

impl LinkageStoreIndexes {
	pub(super) fn new(
		references: &RecordTable<ReferenceRecord>,
		material: &CodeIndexMaterial,
	) -> Self {
		Self {
			reference_indexes: reference_indexes(references),
			references_by_source_root: parallel_reference_index(references, |reference| {
				reference_source_root(reference, material)
			}),
			references_by_name: parallel_reference_index(references, |reference| {
				LinkageQuery::new(reference, material)
					.map(|query| query_keys(&query))
					.unwrap_or_default()
			}),
			references_by_source_symbol: references_by_source_symbol(references),
			references_by_call_name: references_by_call_name(references),
			resolved_by_target_source: None,
		}
	}

	pub(super) fn add_reference(
		&mut self,
		reference: &ReferenceRecord,
		material: &CodeIndexMaterial,
	) {
		let Some(reference_ordinal) = self.reference_indexes.get(&reference.id).copied() else {
			return;
		};
		if let Some(source_root) = reference_source_root(reference, material) {
			self.references_by_source_root
				.entry(source_root)
				.or_default()
				.insert(reference_ordinal);
		}
		self.references_by_source_symbol
			.entry(reference.source_symbol)
			.or_default()
			.insert(reference_ordinal);
		if let Some(call_name) = reference.call_name.as_deref() {
			self.references_by_call_name
				.entry(call_name.as_bytes().to_vec())
				.or_default()
				.insert(reference_ordinal);
		}
		let Some(query) = LinkageQuery::new(reference, material) else {
			return;
		};
		for key in query_keys(&query) {
			self.references_by_name
				.entry(key)
				.or_default()
				.insert(reference_ordinal);
		}
	}

	pub(super) fn add_resolved_target(
		&mut self,
		decision: &ReferenceLinkageDecision,
		material: &CodeIndexMaterial,
		symbols: &SymbolOrdinalCatalog,
	) {
		let Some(index) = &mut self.resolved_by_target_source else {
			return;
		};
		index.add_decision(decision, ResolvedTargetContext { material, symbols });
	}

	pub(super) fn rebase_reference_ordinals(&mut self, rebase: &ReferenceOrdinalRebase) {
		rebase_reference_maps(&mut self.references_by_source_root, rebase);
		rebase_reference_maps(&mut self.references_by_name, rebase);
		rebase_reference_maps(&mut self.references_by_source_symbol, rebase);
		rebase_reference_maps(&mut self.references_by_call_name, rebase);
		if let Some(index) = &mut self.resolved_by_target_source {
			index.rebase_reference_ordinals(rebase);
		}
	}

	pub(super) fn remove_stale_references(&mut self, stale_references: &ReferenceSet) {
		remove_references(&mut self.references_by_source_root, stale_references);
		remove_references(&mut self.references_by_name, stale_references);
		remove_references(&mut self.references_by_source_symbol, stale_references);
		remove_references(&mut self.references_by_call_name, stale_references);
		self.remove_resolved_references(stale_references);
	}

	pub(super) fn remove_resolved_references(&mut self, stale_references: &ReferenceSet) {
		if let Some(index) = &mut self.resolved_by_target_source {
			index.remove_references(stale_references);
		}
	}

	pub(super) fn rebuild_resolved_targets(
		&mut self,
		decisions: &[ReferenceLinkageDecision],
		material: &CodeIndexMaterial,
		symbols: &SymbolOrdinalCatalog,
	) {
		self.resolved_by_target_source = Some(ResolvedTargetSourceIndex::from_decisions(
			decisions, material, symbols,
		));
	}

	pub(super) fn record_memory(&self, metrics: &mut LinkageMemoryMetrics) {
		record_reference_sets(self.references_by_source_root.values(), metrics);
		record_reference_sets(self.references_by_name.values(), metrics);
		record_reference_sets(self.references_by_source_symbol.values(), metrics);
		record_reference_sets(self.references_by_call_name.values(), metrics);
		if let Some(index) = &self.resolved_by_target_source {
			record_reference_sets(index.references_by_source.values(), metrics);
			record_reference_sets(index.references_by_symbol.values(), metrics);
		}
	}
}

#[derive(Clone, Default)]
pub(in crate::linkage) struct ResolvedTargetSourceIndex {
	references_by_source: FxHashMap<SourceId, ReferenceSet>,
	references_by_symbol: FxHashMap<SymbolOrdinal, ReferenceSet>,
}

impl ResolvedTargetSourceIndex {
	pub(in crate::linkage) fn get(&self, source: &SourceId) -> Option<&ReferenceSet> {
		self.references_by_source.get(source)
	}

	pub(in crate::linkage) fn get_symbol(&self, symbol: SymbolOrdinal) -> Option<&ReferenceSet> {
		self.references_by_symbol.get(&symbol)
	}

	fn from_decisions(
		decisions: &[ReferenceLinkageDecision],
		material: &CodeIndexMaterial,
		symbols: &SymbolOrdinalCatalog,
	) -> Self {
		let mut index = Self::default();
		let context = ResolvedTargetContext { material, symbols };
		for decision in decisions {
			index.add_decision(decision, context);
		}
		index
	}

	fn add_decision(
		&mut self,
		decision: &ReferenceLinkageDecision,
		context: ResolvedTargetContext<'_>,
	) {
		let Some(targets) = decision.linkage_targets() else {
			return;
		};
		for target in targets.iter() {
			self.references_by_symbol
				.entry(target)
				.or_default()
				.insert(ReferenceOrdinal::from_index(decision.reference_idx()));
			let Some(symbol_id) = context.symbols.id(target) else {
				continue;
			};
			let Some(source) = context.material.symbol_source(symbol_id) else {
				continue;
			};
			self.references_by_source
				.entry(source)
				.or_default()
				.insert(ReferenceOrdinal::from_index(decision.reference_idx()));
		}
	}

	fn remove_references(&mut self, stale_references: &ReferenceSet) {
		remove_references(&mut self.references_by_source, stale_references);
		remove_references(&mut self.references_by_symbol, stale_references);
	}

	fn rebase_reference_ordinals(&mut self, rebase: &ReferenceOrdinalRebase) {
		rebase_reference_maps(&mut self.references_by_source, rebase);
		rebase_reference_maps(&mut self.references_by_symbol, rebase);
	}
}

#[derive(Clone, Copy)]
struct ResolvedTargetContext<'a> {
	material: &'a CodeIndexMaterial,
	symbols: &'a SymbolOrdinalCatalog,
}

pub(super) struct ReferenceOrdinalRebase {
	next_by_old: Vec<Option<ReferenceOrdinal>>,
}

impl ReferenceOrdinalRebase {
	pub(super) fn new(
		previous: &FxHashMap<ReferenceId, ReferenceOrdinal>,
		next: &FxHashMap<ReferenceId, ReferenceOrdinal>,
		reference_id_remaps: &[(ReferenceId, ReferenceId)],
		removed_references: &HashSet<&ReferenceId>,
	) -> Self {
		let max_old = previous
			.values()
			.map(|reference| reference.index())
			.max()
			.unwrap_or(0);
		let mut next_by_old = vec![None; max_old + 1];
		let reference_id_remaps = reference_id_remaps
			.iter()
			.cloned()
			.collect::<FxHashMap<ReferenceId, ReferenceId>>();
		for (reference, previous_ordinal) in previous {
			if removed_references.contains(reference) {
				continue;
			}
			let next_reference = reference_id_remaps.get(reference).unwrap_or(reference);
			if let Some(next_ordinal) = next.get(next_reference) {
				next_by_old[previous_ordinal.index()] = Some(*next_ordinal);
			}
		}
		Self { next_by_old }
	}

	fn map(&self, previous: ReferenceOrdinal) -> Option<ReferenceOrdinal> {
		self.next_by_old.get(previous.index()).copied().flatten()
	}
}

pub(in crate::linkage) fn reference_indexes(
	references: &RecordTable<ReferenceRecord>,
) -> FxHashMap<ReferenceId, ReferenceOrdinal> {
	references
		.iter()
		.enumerate()
		.map(|(idx, reference)| (reference.id, ReferenceOrdinal::from_index(idx)))
		.collect()
}

fn parallel_reference_index<K, I>(
	references: &RecordTable<ReferenceRecord>,
	keys: impl Fn(&ReferenceRecord) -> I + Sync,
) -> FxHashMap<K, ReferenceSet>
where
	K: Eq + Hash + Send,
	I: IntoIterator<Item = K>,
{
	(0..references.len())
		.into_par_iter()
		.fold(
			FxHashMap::<K, ReferenceSet>::default,
			|mut index, reference_idx| {
				for key in keys(&references[reference_idx]) {
					index
						.entry(key)
						.or_default()
						.insert(ReferenceOrdinal::from_index(reference_idx));
				}
				index
			},
		)
		.reduce(FxHashMap::default, merge_reference_set_maps)
}

fn references_by_source_symbol(
	references: &RecordTable<ReferenceRecord>,
) -> FxHashMap<SymbolId, ReferenceSet> {
	let mut index = FxHashMap::default();
	for (reference_idx, reference) in references.iter().enumerate() {
		index
			.entry(reference.source_symbol)
			.or_insert_with(ReferenceSet::new)
			.insert(ReferenceOrdinal::from_index(reference_idx));
	}
	index
}

fn references_by_call_name(
	references: &RecordTable<ReferenceRecord>,
) -> FxHashMap<Vec<u8>, ReferenceSet> {
	let mut index = FxHashMap::default();
	for (reference_idx, reference) in references.iter().enumerate() {
		let Some(call_name) = reference.call_name.as_deref() else {
			continue;
		};
		index
			.entry(call_name.as_bytes().to_vec())
			.or_insert_with(ReferenceSet::new)
			.insert(ReferenceOrdinal::from_index(reference_idx));
	}
	index
}

fn remove_references<K: Eq + Hash>(
	index: &mut FxHashMap<K, ReferenceSet>,
	references: &ReferenceSet,
) {
	index.retain(|_, indexed_references| {
		indexed_references.remove_all(references);
		!indexed_references.is_empty()
	});
}

fn rebase_reference_maps<K: Eq + Hash>(
	index: &mut FxHashMap<K, ReferenceSet>,
	rebase: &ReferenceOrdinalRebase,
) {
	index.retain(|_, references| {
		*references = references
			.iter()
			.filter_map(|reference| rebase.map(reference))
			.collect();
		!references.is_empty()
	});
}

fn merge_reference_set_maps<K: Eq + Hash>(
	mut left: FxHashMap<K, ReferenceSet>,
	right: FxHashMap<K, ReferenceSet>,
) -> FxHashMap<K, ReferenceSet> {
	for (key, references) in right {
		left.entry(key).or_default().union_with(&references);
	}
	left
}

fn reference_source_root(
	reference: &ReferenceRecord,
	material: &CodeIndexMaterial,
) -> Option<usize> {
	let (file_idx, _) = material.identity.reference_location(&reference.id)?;
	material.files.get(file_idx).map(|file| file.source_root)
}

fn record_reference_sets<'a>(
	sets: impl IntoIterator<Item = &'a ReferenceSet>,
	metrics: &mut LinkageMemoryMetrics,
) {
	for set in sets {
		metrics.add_reference_set(set.len(), set.serialized_size());
	}
}