diff --git a/crates/jsshaker/src/analyzer/rw_tracking.rs b/crates/jsshaker/src/analyzer/rw_tracking.rs index 9f43b381..ce969d27 100644 --- a/crates/jsshaker/src/analyzer/rw_tracking.rs +++ b/crates/jsshaker/src/analyzer/rw_tracking.rs @@ -33,7 +33,7 @@ impl<'a> ReadWriteTarget<'a> { } #[derive(Debug, Clone, Copy)] -pub enum TrackReadCachable<'a> { +pub enum TrackReadCacheable<'a> { Immutable, Mutable(EntityOrTDZ<'a>), } @@ -43,10 +43,11 @@ impl<'a> Analyzer<'a> { &mut self, scope: CfScopeId, target: ReadWriteTarget<'a>, - cacheable: Option>, + cacheable: Option>, ) { let target_depth = self.find_first_different_cf_scope(scope); let mut registered = false; + let mut call_effect = None; for depth in (target_depth..self.scoping.cf.stack.len()).rev() { let scope = self.scoping.cf.get_mut_from_depth(depth); if let Some(data) = scope.exhaustive_data_mut() { @@ -63,7 +64,11 @@ impl<'a> Analyzer<'a> { } } if let Some(data) = scope.fn_cache_tracking_data_mut() { - data.track_read(target, cacheable); + if let Some(call_effect) = call_effect { + data.track_call(call_effect); + } else { + call_effect = data.track_read(target, cacheable); + } } } } diff --git a/crates/jsshaker/src/scope/mod.rs b/crates/jsshaker/src/scope/mod.rs index 3fd17733..776fffe3 100644 --- a/crates/jsshaker/src/scope/mod.rs +++ b/crates/jsshaker/src/scope/mod.rs @@ -142,7 +142,7 @@ impl<'a> Analyzer<'a> { let CfScopeKind::Function(tracking_data) = &mut cf_scope.kind else { unreachable!(); }; - let tracking_data = mem::take(*tracking_data); + let tracking_data = mem::replace(*tracking_data, FnCacheTrackingData::UnTrackable); self.pop_variable_scope(); self.replace_variable_scope(old_variable_scope); diff --git a/crates/jsshaker/src/scope/variable_scope.rs b/crates/jsshaker/src/scope/variable_scope.rs index 95f2b6e5..53a85332 100644 --- a/crates/jsshaker/src/scope/variable_scope.rs +++ b/crates/jsshaker/src/scope/variable_scope.rs @@ -9,7 +9,7 @@ use oxc::{ use super::cf_scope::CfScopeId; use crate::{ analyzer::Analyzer, - analyzer::rw_tracking::{ReadWriteTarget, TrackReadCachable}, + analyzer::rw_tracking::{ReadWriteTarget, TrackReadCacheable}, ast::DeclarationKind, define_box_bump_idx, dep::{Dep, LazyDep}, @@ -206,9 +206,9 @@ impl<'a> Analyzer<'a> { cf_scope, ReadWriteTarget::Variable(scope, symbol), Some(if may_change { - TrackReadCachable::Mutable(value) + TrackReadCacheable::Mutable(value) } else { - TrackReadCachable::Immutable + TrackReadCacheable::Immutable }), ); value diff --git a/crates/jsshaker/src/utils/callee_info.rs b/crates/jsshaker/src/utils/callee_info.rs index a0522cec..5fc6f70d 100644 --- a/crates/jsshaker/src/utils/callee_info.rs +++ b/crates/jsshaker/src/utils/callee_info.rs @@ -1,4 +1,4 @@ -use std::hash; +use std::{hash, ptr}; use oxc::{ ast::{ @@ -51,14 +51,14 @@ impl GetSpan for CalleeNode<'_> { impl PartialEq for CalleeNode<'_> { fn eq(&self, other: &Self) -> bool { - match (self, other) { + match (*self, *other) { (CalleeNode::Module, CalleeNode::Module) => true, - (CalleeNode::Function(a), CalleeNode::Function(b)) => a.span() == b.span(), + (CalleeNode::Function(a), CalleeNode::Function(b)) => ptr::eq(a, b), (CalleeNode::ArrowFunctionExpression(a), CalleeNode::ArrowFunctionExpression(b)) => { - a.span() == b.span() + ptr::eq(a, b) } - (CalleeNode::ClassStatics(a), CalleeNode::ClassStatics(b)) => a.span() == b.span(), - (CalleeNode::ClassConstructor(a), CalleeNode::ClassConstructor(b)) => a.span() == b.span(), + (CalleeNode::ClassStatics(a), CalleeNode::ClassStatics(b)) => ptr::eq(a, b), + (CalleeNode::ClassConstructor(a), CalleeNode::ClassConstructor(b)) => ptr::eq(a, b), (CalleeNode::BoundFunction(a), CalleeNode::BoundFunction(b)) => a.span == b.span, _ => false, } diff --git a/crates/jsshaker/src/value/function/cache.rs b/crates/jsshaker/src/value/function/cache.rs index 7a996e9e..a9875de9 100644 --- a/crates/jsshaker/src/value/function/cache.rs +++ b/crates/jsshaker/src/value/function/cache.rs @@ -1,11 +1,11 @@ -use oxc::allocator; +use oxc::allocator::{self, Allocator}; use crate::{ Analyzer, - analyzer::rw_tracking::{ReadWriteTarget, TrackReadCachable}, + analyzer::rw_tracking::{ReadWriteTarget, TrackReadCacheable}, entity::Entity, scope::variable_scope::EntityOrTDZ, - value::{ArgumentsValue, cacheable::Cacheable, call::FnCallInfo}, + value::{ArgumentsValue, FunctionValue, cacheable::Cacheable, call::FnCallInfo}, }; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -15,19 +15,45 @@ pub struct FnCachedInput<'a> { pub args: &'a [Cacheable<'a>], } +#[derive(Debug, Clone, Copy)] +pub struct FnCallEffect<'a> { + pub func: &'a FunctionValue<'a>, + pub input: FnCachedInput<'a>, +} +impl<'a> PartialEq for FnCallEffect<'a> { + fn eq(&self, other: &Self) -> bool { + std::ptr::eq(self.func, other.func) && self.input == other.input + } +} +impl<'a> Eq for FnCallEffect<'a> {} +impl<'a> std::hash::Hash for FnCallEffect<'a> { + fn hash(&self, state: &mut H) { + (self.func as *const _, &self.input).hash(state); + } +} + #[derive(Debug)] pub struct FnCachedEffects<'a> { pub reads: allocator::HashMap<'a, ReadWriteTarget<'a>, EntityOrTDZ<'a>>, pub writes: allocator::HashMap<'a, ReadWriteTarget<'a>, (bool, Entity<'a>)>, + pub calls: allocator::HashSet<'a, FnCallEffect<'a>>, +} + +impl<'a> FnCachedEffects<'a> { + pub fn new_in(allocator: &'a Allocator) -> Self { + Self { + reads: allocator::HashMap::new_in(allocator), + writes: allocator::HashMap::new_in(allocator), + calls: allocator::HashSet::new_in(allocator), + } + } } -#[derive(Debug, Default)] +#[derive(Debug)] pub enum FnCacheTrackingData<'a> { - #[default] UnTrackable, - Tracked { - effects: FnCachedEffects<'a>, - }, + Tracked { self_call_effect: FnCallEffect<'a>, effects: FnCachedEffects<'a> }, + Failed { self_call_effect: FnCallEffect<'a> }, } impl<'a> FnCachedEffects<'a> { @@ -41,32 +67,55 @@ impl<'a> FnCachedEffects<'a> { impl<'a> FnCacheTrackingData<'a> { pub fn worst_case() -> Self { - Self::default() + FnCacheTrackingData::UnTrackable } pub fn new_in(allocator: &'a allocator::Allocator, info: FnCallInfo<'a>) -> Self { - if let Some(_cache_key) = info.cache_key { - Self::Tracked { effects: FnCachedEffects::new_in(allocator) } + if let Some(cache_key) = info.cache_key { + Self::Tracked { + self_call_effect: FnCallEffect { func: info.func, input: cache_key }, + effects: FnCachedEffects::new_in(allocator), + } } else { FnCacheTrackingData::UnTrackable } } + pub fn failed(&mut self) { + let Self::Tracked { self_call_effect, .. } = self else { + unreachable!(); + }; + *self = Self::Failed { self_call_effect: *self_call_effect }; + } + + pub fn call_effect(&self) -> Option> { + match self { + Self::Tracked { self_call_effect, .. } => Some(*self_call_effect), + Self::Failed { self_call_effect } => Some(*self_call_effect), + Self::UnTrackable => None, + } + } + pub fn track_read( &mut self, target: ReadWriteTarget<'a>, - cacheable: Option>, - ) { - let Self::Tracked { effects, .. } = self else { - return; + cacheable: Option>, + ) -> Option> { + let Self::Tracked { self_call_effect, effects } = self else { + return None; }; + let self_call_effect = *self_call_effect; let Some(cacheable) = cacheable else { - *self = Self::UnTrackable; - return; + self.failed(); + return Some(self_call_effect); }; - let TrackReadCachable::Mutable(current_value) = cacheable else { - return; + let TrackReadCacheable::Mutable(current_value) = cacheable else { + return None; }; + if effects.reads.len() > 8 { + self.failed(); + return Some(self_call_effect); + } match effects.reads.entry(target) { allocator::hash_map::Entry::Occupied(v) => { // TODO: Remove these? @@ -82,6 +131,7 @@ impl<'a> FnCacheTrackingData<'a> { v.insert(current_value); } } + Some(self_call_effect) } pub fn track_write( @@ -93,11 +143,18 @@ impl<'a> FnCacheTrackingData<'a> { return; }; let Some(cacheable) = cacheable else { - *self = Self::UnTrackable; + self.failed(); return; }; effects.writes.insert(target, cacheable); } + + pub fn track_call(&mut self, effect: FnCallEffect<'a>) { + let Self::Tracked { effects, .. } = self else { + return; + }; + effects.calls.insert(effect); + } } #[derive(Debug)] @@ -135,8 +192,8 @@ impl<'a> FnCache<'a> { analyzer: &mut Analyzer<'a>, key: &FnCachedInput<'a>, ) -> Option> { - if let Some((effects, ret)) = self.table.get(key) { - for (&target, &last_value) in &effects.reads { + if let Some((cached, ret)) = self.table.get(key) { + for (&target, &last_value) in &cached.reads { let current_value = analyzer.get_rw_target_current_value(target); if match (last_value, current_value) { (Some(e1), Some(e2)) => !e1.exactly_same(e2), @@ -147,7 +204,7 @@ impl<'a> FnCache<'a> { } } - for (&target, &(indeterminate, cacheable)) in &effects.writes { + for (&target, &(indeterminate, cacheable)) in &cached.writes { analyzer.set_rw_target_current_value(target, cacheable, indeterminate); } @@ -163,7 +220,7 @@ impl<'a> FnCache<'a> { ret: Entity<'a>, tracking: FnCacheTrackingData<'a>, ) { - let FnCacheTrackingData::Tracked { effects } = tracking else { + let FnCacheTrackingData::Tracked { effects, .. } = tracking else { return; }; let Some(ret) = ret.as_cacheable() else {