use std::sync::atomic::{AtomicU64, Ordering};
use std::time::Instant;
use llmcc_core::context::CompileUnit;
use llmcc_core::interner::InternPool;
use llmcc_core::ir::HirScope;
use llmcc_core::scope::{LookupOptions, Scope, ScopeStack};
use llmcc_core::symbol::{ScopeId, SymKind, SymKindSet, Symbol};
use llmcc_core::{CompileCtxt, LanguageTraitImpl};
use rayon::prelude::*;
use crate::ResolverOption;
#[derive(Debug)]
pub struct BinderScopes<'a> {
unit: CompileUnit<'a>,
scopes: ScopeStack<'a>,
}
impl<'a> BinderScopes<'a> {
pub fn new(unit: CompileUnit<'a>, globals: &'a Scope<'a>) -> Self {
let scopes = ScopeStack::new(&unit.cc.arena, &unit.cc.interner);
scopes.push(globals);
Self { unit, scopes }
}
#[inline]
pub fn top(&self) -> &'a Scope<'a> {
self.scopes.top().unwrap()
}
#[inline]
pub fn unit(&self) -> CompileUnit<'a> {
self.unit
}
#[inline]
pub fn interner(&self) -> &InternPool {
self.unit.interner()
}
#[inline]
pub fn scopes(&self) -> &ScopeStack<'a> {
&self.scopes
}
#[inline]
pub fn scopes_mut(&mut self) -> &mut ScopeStack<'a> {
&mut self.scopes
}
#[inline]
pub fn scope_depth(&self) -> usize {
self.scopes.depth()
}
pub fn push_scope(&mut self, id: ScopeId) {
tracing::trace!("push_scope: {:?}", id);
let scope = self.unit.get_scope(id);
self.scopes.push(scope);
}
pub fn push_scope_recursive(&mut self, id: ScopeId) {
tracing::trace!("push_scope_recursive: {:?}", id);
let scope = self.unit.get_scope(id);
self.scopes.push_recursive(scope);
}
pub fn push_scope_node(&mut self, sn: &'a HirScope<'a>) {
if sn.opt_ident().is_some() {
self.push_scope_recursive(sn.scope().id());
} else {
self.push_scope(sn.scope().id());
}
}
#[inline]
pub fn pop_scope(&mut self) {
tracing::trace!("pop_scope: depth {}", self.scopes.depth());
self.scopes.pop();
}
#[inline]
pub fn pop_until(&mut self, depth: usize) {
tracing::trace!("pop_until: {} -> {}", self.scopes.depth(), depth);
self.scopes.pop_until(depth);
}
#[inline]
pub fn globals(&self) -> &'a Scope<'a> {
self.scopes.globals()
}
#[inline]
pub fn lookup_globals(&self, name: &str, kind_filters: SymKindSet) -> Option<Vec<&'a Symbol>> {
tracing::trace!("lookup globals '{}' with filters {:?}", name, kind_filters);
let options = LookupOptions::current().with_kind_set(kind_filters);
let name_key = self.unit.cc.interner.intern(name);
self.scopes.globals().lookup_symbols(name_key, options)
}
#[inline]
pub fn lookup_global(&self, name: &str, kind_filters: SymKindSet) -> Option<&'a Symbol> {
let symbols = self.lookup_globals(name, kind_filters)?;
if symbols.len() > 1 {
tracing::warn!(
"multiple global symbols found for '{}', returning the last one",
name
);
}
symbols.last().copied()
}
#[inline]
pub fn lookup_symbols(&self, name: &str, kind_filters: SymKindSet) -> Option<Vec<&'a Symbol>> {
tracing::trace!("lookup symbols '{}' with filters {:?}", name, kind_filters);
let options = LookupOptions::current().with_kind_set(kind_filters);
self.scopes.lookup_symbols(name, options)
}
#[inline]
pub fn lookup_symbol(&self, name: &str, kind_filters: SymKindSet) -> Option<&'a Symbol> {
let symbols = self.lookup_symbols(name, kind_filters)?;
if symbols.len() > 1 {
let current_unit = self.unit.index;
let current_crate_index = self.unit.unit_meta().crate_index;
if let Some(local_sym) = symbols
.iter()
.find(|s| s.unit_index() == Some(current_unit))
{
tracing::trace!(
"lookup_symbol: multiple found for '{}', preferring local symbol {:?}",
name,
local_sym.id()
);
return Some(*local_sym);
}
let same_crate_symbols: Vec<_> = symbols
.iter()
.filter(|s| s.crate_index() == Some(current_crate_index))
.copied()
.collect();
if !same_crate_symbols.is_empty() && same_crate_symbols.len() < symbols.len() {
tracing::trace!(
"lookup_symbol: multiple found for '{}', preferring same-crate symbol {:?}",
name,
same_crate_symbols.last().map(|s| s.id())
);
return same_crate_symbols.last().copied();
}
tracing::warn!(
"multiple symbols found for '{}', returning the last one",
name
);
}
symbols.last().copied()
}
pub fn lookup_member_symbols(
&self,
obj_type_symbol: &'a Symbol,
member_name: &str,
kind_filters: SymKindSet,
) -> Option<&'a Symbol> {
if !kind_filters.is_empty() {
tracing::trace!(
"looking up member '{}' in type scope with filters {:?}",
member_name,
kind_filters
);
for kind in [
SymKind::Method,
SymKind::Function,
SymKind::Field,
SymKind::Variable,
SymKind::Const,
SymKind::Static,
] {
if kind_filters.contains(kind) {
tracing::trace!(" filter: {:?}", kind);
let sym = self.lookup_member_symbol(obj_type_symbol, member_name, Some(kind));
if sym.is_some() {
return sym;
}
}
}
} else {
tracing::trace!("looking up member '{}' in type scope", member_name);
}
None
}
pub fn lookup_member_symbol(
&self,
obj_type_symbol: &'a Symbol,
member_name: &str,
kind_filter: Option<SymKind>,
) -> Option<&'a Symbol> {
tracing::trace!(
"lookup_member_symbol: '{}' in {:?} (kind={:?})",
member_name,
obj_type_symbol.name,
obj_type_symbol.kind()
);
let effective_symbol = if obj_type_symbol.kind() == SymKind::TypeAlias {
if let Some(type_of_id) = obj_type_symbol.type_of() {
let resolved = self
.unit
.cc
.opt_get_symbol(type_of_id)
.unwrap_or(obj_type_symbol);
tracing::trace!(
" -> followed type_of to {:?} (kind={:?})",
resolved.name,
resolved.kind()
);
resolved
} else {
obj_type_symbol
}
} else {
obj_type_symbol
};
let scope_id = effective_symbol.opt_scope()?;
let scope = self.unit.get_scope(scope_id);
let scopes = ScopeStack::new(&self.unit.cc.arena, &self.unit.cc.interner);
scopes.push_recursive(scope);
let options = if let Some(filter) = kind_filter {
LookupOptions::current().with_kind_set(SymKindSet::from_kind(filter))
} else {
LookupOptions::current()
};
scopes
.lookup_symbols(member_name, options)?
.into_iter()
.last()
}
pub fn lookup_qualified(
&self,
qualified_name: &[&str],
kind_filters: SymKindSet,
) -> Option<Vec<&'a Symbol>> {
tracing::trace!(
"lookup qualified {:?} with kind_filters {:?}",
qualified_name,
kind_filters
);
let mut options = LookupOptions::default().with_shift_start(true);
if !kind_filters.is_empty() {
options = options.with_kind_set(kind_filters)
}
let symbols = self.scopes.lookup_qualified(qualified_name, options)?;
Some(symbols)
}
pub fn lookup_qualified_symbol(
&self,
qualified_name: &[&str],
kind_filters: SymKindSet,
) -> Option<&'a Symbol> {
let symbols = self.lookup_qualified(qualified_name, kind_filters)?;
let current_unit = self.unit.index;
tracing::trace!(
"lookup_qualified_symbol: '{:?}' found {} symbols, current_unit={}: {:?}",
qualified_name,
symbols.len(),
current_unit,
symbols
.iter()
.map(|s| (s.id(), s.unit_index()))
.collect::<Vec<_>>()
);
if symbols.len() > 1 {
if let Some(local_sym) = symbols
.iter()
.find(|s| s.unit_index() == Some(current_unit))
{
tracing::trace!(" -> preferring local symbol {:?}", local_sym.id());
return Some(*local_sym);
}
let current_crate_index = self.unit.unit_meta().crate_index;
if let Some(same_crate_sym) = symbols
.iter()
.find(|s| s.crate_index() == Some(current_crate_index))
{
tracing::trace!(
"preferring same-crate symbol for qualified '{:?}' crate_index={}",
qualified_name,
current_crate_index
);
return Some(*same_crate_sym);
}
tracing::warn!(
"multiple symbols found for qualified '{:?}', returning the last one",
qualified_name
);
}
symbols.last().copied()
}
}
pub fn bind_symbols_with<'a, L: LanguageTraitImpl>(
cc: &'a CompileCtxt<'a>,
globals: &'a Scope<'a>,
config: &ResolverOption,
) {
let total_start = Instant::now();
tracing::info!("starting symbol binding for total {} units", cc.files.len());
let bind_cpu_time_ns = AtomicU64::new(0);
let bind_unit = |unit_index: usize| {
let bind_start = Instant::now();
tracing::debug!("binding symbols for unit {}", unit_index);
let unit = cc.compile_unit(unit_index);
let id = unit.file_root_id().unwrap();
let node = unit.hir_node(id);
L::bind_symbols(unit, node, globals, config);
bind_cpu_time_ns.fetch_add(bind_start.elapsed().as_nanos() as u64, Ordering::Relaxed);
};
let parallel_start = Instant::now();
if config.sequential {
tracing::debug!("running symbol binding sequentially");
(0..cc.files.len()).for_each(bind_unit);
} else {
tracing::debug!("running symbol binding in parallel");
(0..cc.files.len()).into_par_iter().for_each(bind_unit);
}
let parallel_time = parallel_start.elapsed();
let total_time = total_start.elapsed();
let bind_cpu_ms = bind_cpu_time_ns.load(Ordering::Relaxed) as f64 / 1_000_000.0;
tracing::info!(
"binding breakdown: parallel={:.2}ms (bind_cpu={:.2}ms), total={:.2}ms",
parallel_time.as_secs_f64() * 1000.0,
bind_cpu_ms,
total_time.as_secs_f64() * 1000.0,
);
tracing::info!("symbol binding complete");
}