use std::{
sync::Arc,
time::{Duration, Instant},
};
use dashmap::{DashMap, DashSet};
use rayon::prelude::*;
use crate::{
analysis::{CallGraph, ConstValue, SsaFunction, SsaVarId, ValueRange},
compiler::{
events::EventLog,
summary::{CallSiteInfo, MethodSummary},
},
metadata::token::Token,
};
pub struct CompilerContext {
pub call_graph: Arc<CallGraph>,
pub ssa_functions: DashMap<Token, SsaFunction>,
pub summaries: DashMap<Token, MethodSummary>,
pub call_sites: DashMap<Token, boxcar::Vec<CallSiteInfo>>,
pub events: EventLog,
pub dead_methods: DashSet<Token>,
pub processed_methods: DashSet<Token>,
pub entry_points: DashSet<Token>,
pub no_inline: DashSet<Token>,
pub inlined_methods: DashSet<Token>,
known_values: DashMap<Token, DashMap<SsaVarId, ConstValue>>,
known_ranges: DashMap<Token, DashMap<SsaVarId, ValueRange>>,
local_remappings: DashMap<Token, Vec<Option<u16>>>,
start_time: Instant,
}
impl CompilerContext {
#[must_use]
pub fn new(call_graph: Arc<CallGraph>) -> Self {
Self {
call_graph,
ssa_functions: DashMap::new(),
summaries: DashMap::new(),
call_sites: DashMap::new(),
events: EventLog::new(),
dead_methods: DashSet::new(),
processed_methods: DashSet::new(),
entry_points: DashSet::new(),
no_inline: DashSet::new(),
inlined_methods: DashSet::new(),
known_values: DashMap::new(),
known_ranges: DashMap::new(),
local_remappings: DashMap::new(),
start_time: Instant::now(),
}
}
#[must_use]
pub fn elapsed(&self) -> Duration {
self.start_time.elapsed()
}
#[must_use]
pub fn is_dead(&self, token: Token) -> bool {
self.dead_methods.contains(&token)
}
pub fn mark_dead(&self, token: Token) {
self.dead_methods.insert(token);
}
#[must_use]
pub fn is_entry_point(&self, token: Token) -> bool {
self.entry_points.contains(&token)
}
pub fn add_entry_point(&self, token: Token) {
self.entry_points.insert(token);
}
pub fn mark_inlined(&self, token: Token) {
self.inlined_methods.insert(token);
}
#[must_use]
pub fn was_inlined(&self, token: Token) -> bool {
self.inlined_methods.contains(&token)
}
pub fn with_summary<R, F>(&self, token: Token, f: F) -> Option<R>
where
F: FnOnce(&MethodSummary) -> R,
{
self.summaries.get(&token).map(|r| f(&r))
}
#[must_use]
pub fn is_inline_candidate(&self, token: Token) -> bool {
self.summaries
.get(&token)
.is_some_and(|r| r.inline_candidate)
}
#[must_use]
pub fn instruction_count(&self, token: Token) -> Option<usize> {
self.summaries.get(&token).map(|r| r.instruction_count)
}
#[must_use]
pub fn is_string_decryptor(&self, token: Token) -> bool {
self.summaries
.get(&token)
.is_some_and(|r| r.is_string_decryptor)
}
pub fn modify_summary<F>(&self, token: Token, f: F) -> bool
where
F: FnOnce(&mut MethodSummary),
{
if let Some(mut entry) = self.summaries.get_mut(&token) {
f(entry.value_mut());
true
} else {
false
}
}
pub fn set_summary(&self, summary: MethodSummary) {
self.summaries.insert(summary.token, summary);
}
pub fn with_call_sites<R, F>(&self, callee: Token, f: F) -> Option<R>
where
F: FnOnce(&boxcar::Vec<CallSiteInfo>) -> R,
{
self.call_sites.get(&callee).map(|r| f(&r))
}
#[must_use]
pub fn call_site_count(&self, callee: Token) -> usize {
self.call_sites.get(&callee).map_or(0, |r| r.count())
}
#[must_use]
pub fn has_call_sites(&self, callee: Token) -> bool {
self.call_sites.get(&callee).is_some_and(|r| r.count() > 0)
}
pub fn for_each_call_site<F>(&self, callee: Token, mut f: F)
where
F: FnMut(&CallSiteInfo),
{
if let Some(sites) = self.call_sites.get(&callee) {
for (_, site) in sites.iter() {
f(site);
}
}
}
pub fn add_call_site(&self, callee: Token, call_site: CallSiteInfo) {
self.call_sites.entry(callee).or_default().push(call_site);
}
#[must_use]
pub fn parameter_constant(&self, method: Token, param_index: usize) -> Option<ConstValue> {
self.with_summary(method, |s| s.parameter_constant(param_index).cloned())
.flatten()
}
#[must_use]
pub fn returns_constant(&self, method: Token) -> Option<ConstValue> {
self.with_summary(method, |s| s.returns_constant().cloned())
.flatten()
}
pub fn with_ssa<R, F>(&self, token: Token, f: F) -> Option<R>
where
F: FnOnce(&SsaFunction) -> R,
{
self.ssa_functions.get(&token).map(|r| f(&r))
}
pub fn with_ssa_mut<R, F>(&self, token: Token, f: F) -> Option<R>
where
F: FnOnce(&mut SsaFunction) -> R,
{
self.ssa_functions.get_mut(&token).map(|mut r| f(&mut r))
}
#[must_use]
pub fn has_ssa(&self, token: Token) -> bool {
self.ssa_functions.contains_key(&token)
}
pub fn set_ssa(&self, token: Token, ssa: SsaFunction) {
self.ssa_functions.insert(token, ssa);
}
pub fn take_ssa(&self, token: Token) -> Option<SsaFunction> {
self.ssa_functions.remove(&token).map(|(_, v)| v)
}
#[must_use]
pub fn methods_reverse_topological(&self) -> Vec<Token> {
let mut order = self.call_graph.topological_order().to_vec();
order.reverse();
order
}
#[must_use]
pub fn methods_topological(&self) -> Vec<Token> {
self.call_graph.topological_order().to_vec()
}
pub fn all_methods(&self) -> impl Iterator<Item = Token> + '_ {
self.ssa_functions.iter().map(|r| *r.key())
}
#[must_use]
pub fn method_count(&self) -> usize {
self.ssa_functions.len()
}
pub fn with_known_value<R, F>(&self, method: Token, var: SsaVarId, f: F) -> Option<R>
where
F: FnOnce(&ConstValue) -> R,
{
self.known_values
.get(&method)
.and_then(|method_values| method_values.get(&var).map(|r| f(&r)))
}
#[must_use]
pub fn has_known_value(&self, method: Token, var: SsaVarId) -> bool {
self.known_values
.get(&method)
.is_some_and(|m| m.contains_key(&var))
}
#[must_use]
pub fn known_value_is<F>(&self, method: Token, var: SsaVarId, predicate: F) -> bool
where
F: FnOnce(&ConstValue) -> bool,
{
self.known_values
.get(&method)
.and_then(|m| m.get(&var).map(|r| predicate(&r)))
.unwrap_or(false)
}
pub fn for_each_known_value<F>(&self, method: Token, mut f: F)
where
F: FnMut(SsaVarId, &ConstValue),
{
if let Some(inner) = self.known_values.get(&method) {
for entry in inner.iter() {
f(*entry.key(), entry.value());
}
}
}
pub fn add_known_value(&self, method: Token, var: SsaVarId, value: ConstValue) {
self.known_values
.entry(method)
.or_default()
.insert(var, value);
}
pub fn clear_known_values(&self, method: Token) {
self.known_values.remove(&method);
}
#[must_use]
pub fn known_value_count(&self, method: Token) -> usize {
self.known_values
.get(&method)
.map_or(0, |inner| inner.len())
}
pub fn add_known_range(&self, method: Token, var: SsaVarId, range: ValueRange) {
self.known_ranges
.entry(method)
.or_default()
.insert(var, range);
}
pub fn with_known_range<R, F>(&self, method: Token, var: SsaVarId, f: F) -> Option<R>
where
F: FnOnce(&ValueRange) -> R,
{
self.known_ranges
.get(&method)
.and_then(|method_ranges| method_ranges.get(&var).map(|r| f(&r)))
}
#[must_use]
pub fn has_known_range(&self, method: Token, var: SsaVarId) -> bool {
self.known_ranges
.get(&method)
.is_some_and(|m| m.contains_key(&var))
}
pub fn for_each_known_range<F>(&self, method: Token, mut f: F)
where
F: FnMut(SsaVarId, &ValueRange),
{
if let Some(inner) = self.known_ranges.get(&method) {
for entry in inner.iter() {
f(*entry.key(), entry.value());
}
}
}
pub fn clear_known_ranges(&self, method: Token) {
self.known_ranges.remove(&method);
}
pub fn canonicalize_all_ssa(&self) {
let tokens: Vec<Token> = self.all_methods().collect();
tokens.par_iter().for_each(|&token| {
let Some((_, mut ssa)) = self.ssa_functions.remove(&token) else {
return;
};
ssa.canonicalize();
let remapping = ssa.optimize_locals();
self.ssa_functions.insert(token, ssa);
self.local_remappings.insert(token, remapping);
});
}
pub fn with_local_remapping<R, F>(&self, token: Token, f: F) -> Option<R>
where
F: FnOnce(&[Option<u16>]) -> R,
{
self.local_remappings.get(&token).map(|r| f(&r))
}
#[must_use]
pub fn has_local_remapping(&self, token: Token) -> bool {
self.local_remappings.contains_key(&token)
}
}