From 7323fc52586a4c396e00fba5ba8a00f7fe4ab390 Mon Sep 17 00:00:00 2001 From: _Kerman Date: Tue, 16 Dec 2025 21:12:44 +0800 Subject: [PATCH 1/4] feat: call effect --- .../nodes/expr/arrow_function_expression.rs | 8 +- crates/jsshaker/src/nodes/misc/class.rs | 19 +--- crates/jsshaker/src/nodes/misc/function.rs | 11 +- crates/jsshaker/src/scope/mod.rs | 14 +-- crates/jsshaker/src/value/function/bound.rs | 7 +- crates/jsshaker/src/value/function/cache.rs | 100 +++++++++--------- crates/jsshaker/src/value/function/mod.rs | 11 +- 7 files changed, 79 insertions(+), 91 deletions(-) diff --git a/crates/jsshaker/src/nodes/expr/arrow_function_expression.rs b/crates/jsshaker/src/nodes/expr/arrow_function_expression.rs index a38d30f2..dcc6813b 100644 --- a/crates/jsshaker/src/nodes/expr/arrow_function_expression.rs +++ b/crates/jsshaker/src/nodes/expr/arrow_function_expression.rs @@ -10,8 +10,8 @@ use crate::{ entity::Entity, scope::VariableScopeId, transformer::Transformer, - utils::{CalleeInfo, CalleeNode}, - value::{ArgumentsValue, cache::FnCacheTrackingData}, + utils::CalleeNode, + value::{ArgumentsValue, FunctionValue, cache::FnCacheTrackingData}, }; impl<'a> Analyzer<'a> { @@ -24,7 +24,7 @@ impl<'a> Analyzer<'a> { pub fn call_arrow_function_expression( &mut self, - callee: CalleeInfo<'a>, + func: &'a FunctionValue<'a>, call_dep: Dep<'a>, node: &'a ArrowFunctionExpression<'a>, variable_scopes: &'a [VariableScopeId], @@ -33,7 +33,7 @@ impl<'a> Analyzer<'a> { ) -> (Entity<'a>, FnCacheTrackingData<'a>) { let runner = move |analyzer: &mut Analyzer<'a>| { analyzer.push_call_scope( - callee, + func, call_dep, variable_scopes.to_vec(), node.r#async, diff --git a/crates/jsshaker/src/nodes/misc/class.rs b/crates/jsshaker/src/nodes/misc/class.rs index a9492d6b..983f1ff0 100644 --- a/crates/jsshaker/src/nodes/misc/class.rs +++ b/crates/jsshaker/src/nodes/misc/class.rs @@ -17,8 +17,8 @@ use crate::{ entity::Entity, scope::VariableScopeId, transformer::Transformer, - utils::{CalleeInfo, CalleeNode}, - value::{ArgumentsValue, ObjectPrototype, ValueTrait, cache::FnCacheTrackingData}, + utils::CalleeNode, + value::{ArgumentsValue, FunctionValue, ObjectPrototype, ValueTrait, cache::FnCacheTrackingData}, }; #[derive(Default)] @@ -54,15 +54,6 @@ impl<'a> Analyzer<'a> { }; // Enter class statics scope - let variable_scope_stack = self.scoping.variable.stack.clone(); - self.push_call_scope( - self.new_callee_info(CalleeNode::ClassStatics(node)), - self.factory.no_dep, - variable_scope_stack, - false, - false, - false, - ); self.variable_scope_mut().super_class = Some(data.super_class.unwrap_or(self.factory.undefined)); @@ -118,8 +109,6 @@ impl<'a> Analyzer<'a> { } } - self.pop_call_scope(); - let class = class.into(); data.value = Some(class); class @@ -139,7 +128,7 @@ impl<'a> Analyzer<'a> { pub fn call_class_constructor( &mut self, - callee: CalleeInfo<'a>, + func: &'a FunctionValue<'a>, call_dep: Dep<'a>, node: &'a Class<'a>, variable_scopes: &'a [VariableScopeId], @@ -150,7 +139,7 @@ impl<'a> Analyzer<'a> { let factory = self.factory; let data = self.load_data::(AstKind2::Class(node)); - self.push_call_scope(callee, call_dep, variable_scopes.to_vec(), false, false, consume); + self.push_call_scope(func, call_dep, variable_scopes.to_vec(), false, false, consume); let super_class = data.super_class.unwrap_or(self.factory.undefined); let variable_scope = self.variable_scope_mut(); variable_scope.this = Some(this); diff --git a/crates/jsshaker/src/nodes/misc/function.rs b/crates/jsshaker/src/nodes/misc/function.rs index d728dae2..c781789f 100644 --- a/crates/jsshaker/src/nodes/misc/function.rs +++ b/crates/jsshaker/src/nodes/misc/function.rs @@ -13,8 +13,8 @@ use crate::{ entity::Entity, scope::VariableScopeId, transformer::Transformer, - utils::{CalleeInfo, CalleeNode}, - value::{ArgumentsValue, cache::FnCacheTrackingData}, + utils::CalleeNode, + value::{ArgumentsValue, FunctionValue, cache::FnCacheTrackingData}, }; impl<'a> Analyzer<'a> { @@ -40,8 +40,7 @@ impl<'a> Analyzer<'a> { pub fn call_function( &mut self, - fn_entity: Entity<'a>, - callee: CalleeInfo<'a>, + func: &'a FunctionValue<'a>, call_dep: Dep<'a>, node: &'a Function<'a>, variable_scopes: &'a [VariableScopeId], @@ -51,7 +50,7 @@ impl<'a> Analyzer<'a> { ) -> (Entity<'a>, FnCacheTrackingData<'a>) { let runner = move |analyzer: &mut Analyzer<'a>| { analyzer.push_call_scope( - callee, + func, call_dep, variable_scopes.to_vec(), node.r#async, @@ -72,7 +71,7 @@ impl<'a> Analyzer<'a> { AstKind2::BindingIdentifier(id), false, DeclarationKind::NamedFunctionInBody, - Some(analyzer.factory.computed(fn_entity, AstKind2::BindingIdentifier(id))), + Some(analyzer.factory.computed(func.into(), AstKind2::BindingIdentifier(id))), ); } diff --git a/crates/jsshaker/src/scope/mod.rs b/crates/jsshaker/src/scope/mod.rs index 9b04e0ac..c8581a6c 100644 --- a/crates/jsshaker/src/scope/mod.rs +++ b/crates/jsshaker/src/scope/mod.rs @@ -20,7 +20,7 @@ use crate::{ entity::Entity, module::ModuleId, utils::{CalleeInfo, CalleeNode}, - value::{ObjectId, cache::FnCacheTrackingData}, + value::{FunctionValue, ObjectId, cache::FnCacheTrackingData}, }; pub struct Scoping<'a> { @@ -106,7 +106,7 @@ impl<'a> Analyzer<'a> { pub fn push_call_scope( &mut self, - callee: CalleeInfo<'a>, + func: &'a FunctionValue<'a>, call_dep: Dep<'a>, variable_scope_stack: Vec, is_async: bool, @@ -118,18 +118,20 @@ impl<'a> Analyzer<'a> { self.refer_dep(dep_id); } - self.module_stack.push(callee.module_id); + self.module_stack.push(func.callee.module_id); let old_variable_scope_stack = self.replace_variable_scope_stack(variable_scope_stack); let body_variable_scope = self.push_variable_scope(); let cf_scope_depth = self.push_cf_scope_with_deps( - CfScopeKind::Function(self.allocator.alloc(FnCacheTrackingData::new_in(self.allocator))), + CfScopeKind::Function( + self.allocator.alloc(FnCacheTrackingData::new_in(self.allocator, func)), + ), self.factory.vec1(self.dep((call_dep, dep_id))), false, ); self.scoping.call.push(CallScope::new_in( dep_id, - callee, + func.callee, old_variable_scope_stack, cf_scope_depth, body_variable_scope, @@ -146,7 +148,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_stack(old_variable_scope_stack); diff --git a/crates/jsshaker/src/value/function/bound.rs b/crates/jsshaker/src/value/function/bound.rs index e62a4d01..c04ed434 100644 --- a/crates/jsshaker/src/value/function/bound.rs +++ b/crates/jsshaker/src/value/function/bound.rs @@ -5,8 +5,7 @@ use crate::{ dep::Dep, entity::Entity, scope::VariableScopeId, - utils::CalleeInfo, - value::{ArgumentsValue, cache::FnCacheTrackingData}, + value::{ArgumentsValue, FunctionValue, cache::FnCacheTrackingData}, }; #[derive(Debug)] @@ -20,7 +19,7 @@ pub struct BoundFunction<'a> { impl<'a> Analyzer<'a> { pub fn call_bound_function( &mut self, - callee: CalleeInfo<'a>, + func: &'a FunctionValue<'a>, call_dep: Dep<'a>, bound_fn: &'a BoundFunction<'a>, variable_scopes: &'a [VariableScopeId], @@ -28,7 +27,7 @@ impl<'a> Analyzer<'a> { args: ArgumentsValue<'a>, consume: bool, ) -> (Entity<'a>, FnCacheTrackingData<'a>) { - self.push_call_scope(callee, call_dep, variable_scopes.to_vec(), false, false, consume); + self.push_call_scope(func, call_dep, variable_scopes.to_vec(), false, false, consume); // self.exec_formal_parameters(&node.params, args, DeclarationKind::ArrowFunctionParameter); // if node.expression { diff --git a/crates/jsshaker/src/value/function/cache.rs b/crates/jsshaker/src/value/function/cache.rs index 6a22f102..dda844b9 100644 --- a/crates/jsshaker/src/value/function/cache.rs +++ b/crates/jsshaker/src/value/function/cache.rs @@ -1,36 +1,50 @@ -use oxc::allocator; +use oxc::allocator::{self, Allocator}; use crate::{ Analyzer, analyzer::rw_tracking::{ReadWriteTarget, TrackReadCachable}, entity::Entity, scope::variable_scope::EntityOrTDZ, - value::{ArgumentsValue, cacheable::Cacheable}, + value::{ArgumentsValue, FunctionValue, cacheable::Cacheable}, }; -type FnReadDeps<'a> = allocator::HashMap<'a, ReadWriteTarget<'a>, EntityOrTDZ<'a>>; -type FnWriteEffects<'a> = allocator::HashMap<'a, ReadWriteTarget<'a>, (bool, Entity<'a>)>; +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct FnCachedInput<'a> { + pub is_ctor: bool, + pub this: Cacheable<'a>, + pub args: &'a [Cacheable<'a>], +} + +#[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, (Entity<'a>, FnCachedInput<'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 { - read_deps: FnReadDeps<'a>, - write_effects: FnWriteEffects<'a>, - }, + Tracked { callee: &'a FunctionValue<'a>, effects: FnCachedEffects<'a> }, } impl<'a> FnCacheTrackingData<'a> { pub fn worst_case() -> Self { - Self::default() + FnCacheTrackingData::UnTrackable } - pub fn new_in(alloc: &'a allocator::Allocator) -> Self { - Self::Tracked { - read_deps: allocator::HashMap::new_in(alloc), - write_effects: allocator::HashMap::new_in(alloc), - } + pub fn new_in(allocator: &'a allocator::Allocator, callee: &'a FunctionValue<'a>) -> Self { + Self::Tracked { callee, effects: FnCachedEffects::new_in(allocator) } } pub fn track_read( @@ -38,7 +52,7 @@ impl<'a> FnCacheTrackingData<'a> { target: ReadWriteTarget<'a>, cacheable: Option>, ) { - let Self::Tracked { read_deps, .. } = self else { + let Self::Tracked { effects, .. } = self else { return; }; let Some(cacheable) = cacheable else { @@ -48,11 +62,11 @@ impl<'a> FnCacheTrackingData<'a> { let TrackReadCachable::Mutable(current_value) = cacheable else { return; }; - if read_deps.len() > 8 { + if effects.reads.len() > 8 { *self = Self::UnTrackable; return; } - match read_deps.entry(target) { + match effects.reads.entry(target) { allocator::hash_map::Entry::Occupied(v) => { // TODO: Remove these? if match (*v.get(), current_value) { @@ -74,34 +88,20 @@ impl<'a> FnCacheTrackingData<'a> { target: ReadWriteTarget<'a>, cacheable: Option<(bool, Entity<'a>)>, ) { - let Self::Tracked { write_effects, .. } = self else { + let Self::Tracked { effects, .. } = self else { return; }; let Some(cacheable) = cacheable else { *self = Self::UnTrackable; return; }; - write_effects.insert(target, cacheable); + effects.writes.insert(target, cacheable); } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct FnCacheEntryKey<'a> { - pub is_ctor: bool, - pub this: Cacheable<'a>, - pub args: &'a [Cacheable<'a>], -} - -#[derive(Debug)] -pub struct FnCacheEntryValue<'a> { - pub read_deps: FnReadDeps<'a>, - pub write_effects: FnWriteEffects<'a>, - pub ret: Cacheable<'a>, -} - #[derive(Debug)] pub struct FnCache<'a> { - table: allocator::HashMap<'a, FnCacheEntryKey<'a>, FnCacheEntryValue<'a>>, + table: allocator::HashMap<'a, FnCachedInput<'a>, (FnCachedEffects<'a>, Cacheable<'a>)>, } impl<'a> FnCache<'a> { @@ -113,29 +113,29 @@ impl<'a> FnCache<'a> { analyzer: &Analyzer<'a>, this: Entity<'a>, args: ArgumentsValue<'a>, - ) -> Option> { + ) -> Option> { if !analyzer.config.enable_fn_cache { return None; } - let this = this.as_cachable()?; + let this = this.as_cacheable()?; if args.rest.is_some() { return None; // TODO: Support this case } let mut cargs = analyzer.factory.vec(); for arg in args.elements { - cargs.push(arg.as_cachable()?); + cargs.push(arg.as_cacheable()?); } - Some(FnCacheEntryKey { is_ctor: IS_CTOR, this, args: cargs.into_bump_slice() }) + Some(FnCachedInput { is_ctor: IS_CTOR, this, args: cargs.into_bump_slice() }) } - pub fn retrive( + pub fn retrieve( &self, analyzer: &mut Analyzer<'a>, - key: &FnCacheEntryKey<'a>, + key: &FnCachedInput<'a>, ) -> Option> { - if let Some(cached) = self.table.get(key) { - for (&target, &last_value) in &cached.read_deps { + 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), @@ -146,11 +146,11 @@ impl<'a> FnCache<'a> { } } - for (&target, &(indeterminate, cacheable)) in &cached.write_effects { + for (&target, &(indeterminate, cacheable)) in &cached.writes { analyzer.set_rw_target_current_value(target, cacheable, indeterminate); } - Some(cached.ret.into_entity(analyzer)) + Some(ret.into_entity(analyzer)) } else { None } @@ -158,16 +158,16 @@ impl<'a> FnCache<'a> { pub fn update_cache( &mut self, - key: FnCacheEntryKey<'a>, + key: FnCachedInput<'a>, ret: Entity<'a>, tracking: FnCacheTrackingData<'a>, ) { - let FnCacheTrackingData::Tracked { read_deps, write_effects } = tracking else { + let FnCacheTrackingData::Tracked { effects, .. } = tracking else { return; }; - let Some(ret) = ret.as_cachable() else { + let Some(ret) = ret.as_cacheable() else { return; }; - self.table.insert(key, FnCacheEntryValue { read_deps, write_effects, ret }); + self.table.insert(key, (effects, ret)); } } diff --git a/crates/jsshaker/src/value/function/mod.rs b/crates/jsshaker/src/value/function/mod.rs index e2b3db4b..0949b7b9 100644 --- a/crates/jsshaker/src/value/function/mod.rs +++ b/crates/jsshaker/src/value/function/mod.rs @@ -222,7 +222,7 @@ impl<'a> FunctionValue<'a> { let cache_key = FnCache::get_key::(analyzer, this, args); if !consume && let Some(cache_key) = cache_key - && let Some(cached_ret) = self.cache.borrow().retrive(analyzer, &cache_key) + && let Some(cached_ret) = self.cache.borrow().retrieve(analyzer, &cache_key) { analyzer.global_effect(); analyzer.consume((call_dep, this, args)); @@ -231,8 +231,7 @@ impl<'a> FunctionValue<'a> { let (ret_val, cache_tracking) = match self.callee.node { CalleeNode::Function(node) => analyzer.call_function( - self.into(), - self.callee, + self, call_dep, node, &self.variable_scope_stack, @@ -241,7 +240,7 @@ impl<'a> FunctionValue<'a> { consume, ), CalleeNode::ArrowFunctionExpression(node) => analyzer.call_arrow_function_expression( - self.callee, + self, call_dep, node, &self.variable_scope_stack, @@ -251,7 +250,7 @@ impl<'a> FunctionValue<'a> { CalleeNode::ClassConstructor(node) => { // if !CTOR { analyzer.call_class_constructor( - self.callee, + self, call_dep, node, &self.variable_scope_stack, @@ -265,7 +264,7 @@ impl<'a> FunctionValue<'a> { // } } CalleeNode::BoundFunction(bound_fn) => analyzer.call_bound_function( - self.callee, + self, call_dep, bound_fn, &self.variable_scope_stack, From d438ebdd083cba43ab6e59900dca53f5b8cc5670 Mon Sep 17 00:00:00 2001 From: _Kerman Date: Wed, 17 Dec 2025 14:04:03 +0800 Subject: [PATCH 2/4] wip --- crates/jsshaker/src/analyzer/rw_tracking.rs | 4 +- .../nodes/expr/arrow_function_expression.rs | 29 ++--- crates/jsshaker/src/nodes/misc/class.rs | 26 ++--- crates/jsshaker/src/nodes/misc/function.rs | 33 ++---- crates/jsshaker/src/scope/mod.rs | 20 ++-- crates/jsshaker/src/scope/variable_scope.rs | 6 +- crates/jsshaker/src/value/function/bound.rs | 26 ++--- crates/jsshaker/src/value/function/cache.rs | 24 ++-- crates/jsshaker/src/value/function/call.rs | 101 +++++++++++++++++ crates/jsshaker/src/value/function/mod.rs | 105 +----------------- 10 files changed, 175 insertions(+), 199 deletions(-) create mode 100644 crates/jsshaker/src/value/function/call.rs diff --git a/crates/jsshaker/src/analyzer/rw_tracking.rs b/crates/jsshaker/src/analyzer/rw_tracking.rs index 9f43b381..b021953b 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,7 +43,7 @@ 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; diff --git a/crates/jsshaker/src/nodes/expr/arrow_function_expression.rs b/crates/jsshaker/src/nodes/expr/arrow_function_expression.rs index dcc6813b..d0e857f3 100644 --- a/crates/jsshaker/src/nodes/expr/arrow_function_expression.rs +++ b/crates/jsshaker/src/nodes/expr/arrow_function_expression.rs @@ -6,12 +6,10 @@ use oxc::ast::{ use crate::{ analyzer::Analyzer, ast::{AstKind2, DeclarationKind}, - dep::Dep, entity::Entity, - scope::VariableScopeId, transformer::Transformer, utils::CalleeNode, - value::{ArgumentsValue, FunctionValue, cache::FnCacheTrackingData}, + value::{cache::FnCacheTrackingData, call::FunctionCallInfo}, }; impl<'a> Analyzer<'a> { @@ -24,38 +22,31 @@ impl<'a> Analyzer<'a> { pub fn call_arrow_function_expression( &mut self, - func: &'a FunctionValue<'a>, - call_dep: Dep<'a>, node: &'a ArrowFunctionExpression<'a>, - variable_scopes: &'a [VariableScopeId], - args: ArgumentsValue<'a>, - consume: bool, + info: FunctionCallInfo<'a>, ) -> (Entity<'a>, FnCacheTrackingData<'a>) { let runner = move |analyzer: &mut Analyzer<'a>| { - analyzer.push_call_scope( - func, - call_dep, - variable_scopes.to_vec(), - node.r#async, - false, - consume, - ); + analyzer.push_call_scope(info, node.r#async, false); - analyzer.exec_formal_parameters(&node.params, args, DeclarationKind::ArrowFunctionParameter); + analyzer.exec_formal_parameters( + &node.params, + info.args, + DeclarationKind::ArrowFunctionParameter, + ); if node.expression { analyzer.exec_function_expression_body(&node.body); } else { analyzer.exec_function_body(&node.body); } - if consume { + if info.consume { analyzer.consume_return_values(); } analyzer.pop_call_scope() }; - if !consume && node.r#async { + if !info.consume && node.r#async { // Too complex to analyze the control flow, thus run exhaustively self.exec_async_or_generator_fn(move |analyzer| { runner(analyzer).0.consume(analyzer); diff --git a/crates/jsshaker/src/nodes/misc/class.rs b/crates/jsshaker/src/nodes/misc/class.rs index 983f1ff0..c21140fb 100644 --- a/crates/jsshaker/src/nodes/misc/class.rs +++ b/crates/jsshaker/src/nodes/misc/class.rs @@ -13,12 +13,10 @@ use oxc::{ use crate::{ analyzer::Analyzer, ast::{AstKind2, DeclarationKind}, - dep::Dep, entity::Entity, - scope::VariableScopeId, transformer::Transformer, utils::CalleeNode, - value::{ArgumentsValue, FunctionValue, ObjectPrototype, ValueTrait, cache::FnCacheTrackingData}, + value::{ObjectPrototype, ValueTrait, cache::FnCacheTrackingData, call::FunctionCallInfo}, }; #[derive(Default)] @@ -128,22 +126,18 @@ impl<'a> Analyzer<'a> { pub fn call_class_constructor( &mut self, - func: &'a FunctionValue<'a>, - call_dep: Dep<'a>, node: &'a Class<'a>, - variable_scopes: &'a [VariableScopeId], - this: Entity<'a>, - args: ArgumentsValue<'a>, - consume: bool, + info: FunctionCallInfo<'a>, ) -> (Entity<'a>, FnCacheTrackingData<'a>) { let factory = self.factory; let data = self.load_data::(AstKind2::Class(node)); - self.push_call_scope(func, call_dep, variable_scopes.to_vec(), false, false, consume); + self.push_call_scope(info, false, false); let super_class = data.super_class.unwrap_or(self.factory.undefined); let variable_scope = self.variable_scope_mut(); - variable_scope.this = Some(this); - variable_scope.arguments = Some((args, factory.vec(/* later filled by formal parameters */))); + variable_scope.this = Some(info.this); + variable_scope.arguments = + Some((info.args, factory.vec(/* later filled by formal parameters */))); variable_scope.super_class = Some(super_class); if let Some(id) = &node.id { @@ -157,7 +151,7 @@ impl<'a> Analyzer<'a> { && !node.r#static { let value = self.exec_property_definition(node); - this.set_property(self, self.factory.no_dep, key.unwrap(), value); + info.this.set_property(self, self.factory.no_dep, key.unwrap(), value); } } @@ -166,15 +160,15 @@ impl<'a> Analyzer<'a> { let function = constructor.value.as_ref(); let dep = self.factory.dep(AstKind2::Function(function)); self.cf_scope_mut().push_dep(dep); - self.exec_formal_parameters(&function.params, args, DeclarationKind::FunctionParameter); + self.exec_formal_parameters(&function.params, info.args, DeclarationKind::FunctionParameter); self.exec_function_body(function.body.as_ref().unwrap()); - if consume { + if info.consume { self.consume_return_values(); } self.pop_call_scope() } else if let Some(super_class) = &data.super_class { self.pop_call_scope(); - let ret_val = super_class.call(self, self.factory.no_dep, this, args); + let ret_val = super_class.call(self, self.factory.no_dep, info.this, info.args); (ret_val, FnCacheTrackingData::worst_case()) } else { let (_, cache_tracking) = self.pop_call_scope(); diff --git a/crates/jsshaker/src/nodes/misc/function.rs b/crates/jsshaker/src/nodes/misc/function.rs index c781789f..449af579 100644 --- a/crates/jsshaker/src/nodes/misc/function.rs +++ b/crates/jsshaker/src/nodes/misc/function.rs @@ -9,12 +9,10 @@ use oxc::{ use crate::{ analyzer::Analyzer, ast::{AstKind2, DeclarationKind}, - dep::Dep, entity::Entity, - scope::VariableScopeId, transformer::Transformer, utils::CalleeNode, - value::{ArgumentsValue, FunctionValue, cache::FnCacheTrackingData}, + value::{cache::FnCacheTrackingData, call::FunctionCallInfo}, }; impl<'a> Analyzer<'a> { @@ -40,28 +38,17 @@ impl<'a> Analyzer<'a> { pub fn call_function( &mut self, - func: &'a FunctionValue<'a>, - call_dep: Dep<'a>, node: &'a Function<'a>, - variable_scopes: &'a [VariableScopeId], - this: Entity<'a>, - args: ArgumentsValue<'a>, - consume: bool, + info: FunctionCallInfo<'a>, ) -> (Entity<'a>, FnCacheTrackingData<'a>) { let runner = move |analyzer: &mut Analyzer<'a>| { - analyzer.push_call_scope( - func, - call_dep, - variable_scopes.to_vec(), - node.r#async, - node.generator, - consume, - ); + analyzer.push_call_scope(info, node.r#async, node.generator); let factory = analyzer.factory; let variable_scope = analyzer.variable_scope_mut(); - variable_scope.this = Some(this); - variable_scope.arguments = Some((args, factory.vec(/* later filled by formal parameters */))); + variable_scope.this = Some(info.this); + variable_scope.arguments = + Some((info.args, factory.vec(/* later filled by formal parameters */))); let declare_in_body = node.r#type == FunctionType::FunctionExpression && node.id.is_some(); if declare_in_body { @@ -71,21 +58,21 @@ impl<'a> Analyzer<'a> { AstKind2::BindingIdentifier(id), false, DeclarationKind::NamedFunctionInBody, - Some(analyzer.factory.computed(func.into(), AstKind2::BindingIdentifier(id))), + Some(analyzer.factory.computed(info.func.into(), AstKind2::BindingIdentifier(id))), ); } - analyzer.exec_formal_parameters(&node.params, args, DeclarationKind::FunctionParameter); + analyzer.exec_formal_parameters(&node.params, info.args, DeclarationKind::FunctionParameter); analyzer.exec_function_body(node.body.as_ref().unwrap()); - if consume { + if info.consume { analyzer.consume_return_values(); } analyzer.pop_call_scope() }; - if !consume && (node.r#async || node.generator) { + if !info.consume && (node.r#async || node.generator) { // Too complex to analyze the control flow, thus run exhaustively self.exec_async_or_generator_fn(move |analyzer| { runner(analyzer).0.consume(analyzer); diff --git a/crates/jsshaker/src/scope/mod.rs b/crates/jsshaker/src/scope/mod.rs index c8581a6c..30f476b9 100644 --- a/crates/jsshaker/src/scope/mod.rs +++ b/crates/jsshaker/src/scope/mod.rs @@ -20,7 +20,7 @@ use crate::{ entity::Entity, module::ModuleId, utils::{CalleeInfo, CalleeNode}, - value::{FunctionValue, ObjectId, cache::FnCacheTrackingData}, + value::{ObjectId, cache::FnCacheTrackingData, call::FunctionCallInfo}, }; pub struct Scoping<'a> { @@ -106,32 +106,30 @@ impl<'a> Analyzer<'a> { pub fn push_call_scope( &mut self, - func: &'a FunctionValue<'a>, - call_dep: Dep<'a>, - variable_scope_stack: Vec, + info: FunctionCallInfo<'a>, is_async: bool, is_generator: bool, - consume: bool, ) { let dep_id = DepAtom::from_counter(); - if consume { + if info.consume { self.refer_dep(dep_id); } - self.module_stack.push(func.callee.module_id); - let old_variable_scope_stack = self.replace_variable_scope_stack(variable_scope_stack); + self.module_stack.push(info.func.callee.module_id); + let old_variable_scope_stack = + self.replace_variable_scope_stack(info.func.variable_scope_stack.to_vec()); let body_variable_scope = self.push_variable_scope(); let cf_scope_depth = self.push_cf_scope_with_deps( CfScopeKind::Function( - self.allocator.alloc(FnCacheTrackingData::new_in(self.allocator, func)), + self.allocator.alloc(FnCacheTrackingData::new_in(self.allocator, info)), ), - self.factory.vec1(self.dep((call_dep, dep_id))), + self.factory.vec1(self.dep((info.call_dep, dep_id))), false, ); self.scoping.call.push(CallScope::new_in( dep_id, - func.callee, + info.func.callee, old_variable_scope_stack, cf_scope_depth, body_variable_scope, diff --git a/crates/jsshaker/src/scope/variable_scope.rs b/crates/jsshaker/src/scope/variable_scope.rs index fc2af042..6c15b0ed 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/value/function/bound.rs b/crates/jsshaker/src/value/function/bound.rs index c04ed434..64445891 100644 --- a/crates/jsshaker/src/value/function/bound.rs +++ b/crates/jsshaker/src/value/function/bound.rs @@ -2,10 +2,8 @@ use oxc::span::Span; use crate::{ Analyzer, - dep::Dep, entity::Entity, - scope::VariableScopeId, - value::{ArgumentsValue, FunctionValue, cache::FnCacheTrackingData}, + value::{ArgumentsValue, cache::FnCacheTrackingData, call::FunctionCallInfo}, }; #[derive(Debug)] @@ -17,17 +15,12 @@ pub struct BoundFunction<'a> { } impl<'a> Analyzer<'a> { - pub fn call_bound_function( + pub fn call_bound_function( &mut self, - func: &'a FunctionValue<'a>, - call_dep: Dep<'a>, bound_fn: &'a BoundFunction<'a>, - variable_scopes: &'a [VariableScopeId], - ctor_this: Option>, - args: ArgumentsValue<'a>, - consume: bool, + info: FunctionCallInfo<'a>, ) -> (Entity<'a>, FnCacheTrackingData<'a>) { - self.push_call_scope(func, call_dep, variable_scopes.to_vec(), false, false, consume); + self.push_call_scope(info, false, false); // self.exec_formal_parameters(&node.params, args, DeclarationKind::ArrowFunctionParameter); // if node.expression { @@ -36,11 +29,16 @@ impl<'a> Analyzer<'a> { // self.exec_function_body(&node.body); // } - let args = ArgumentsValue::from_concatenate(self, bound_fn.bound_args, args); - let ret = bound_fn.target.call(self, call_dep, ctor_this.unwrap_or(bound_fn.bound_this), args); + let args = ArgumentsValue::from_concatenate(self, bound_fn.bound_args, info.args); + let ret = bound_fn.target.call( + self, + info.call_dep, + if IS_CTOR { info.this } else { bound_fn.bound_this }, + args, + ); self.return_value(ret, self.factory.no_dep); - if consume { + if info.consume { self.consume_return_values(); } diff --git a/crates/jsshaker/src/value/function/cache.rs b/crates/jsshaker/src/value/function/cache.rs index dda844b9..dfd6dee3 100644 --- a/crates/jsshaker/src/value/function/cache.rs +++ b/crates/jsshaker/src/value/function/cache.rs @@ -2,10 +2,10 @@ 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, FunctionValue, cacheable::Cacheable}, + value::{ArgumentsValue, FunctionValue, cacheable::Cacheable, call::FunctionCallInfo}, }; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -35,7 +35,7 @@ impl<'a> FnCachedEffects<'a> { #[derive(Debug)] pub enum FnCacheTrackingData<'a> { UnTrackable, - Tracked { callee: &'a FunctionValue<'a>, effects: FnCachedEffects<'a> }, + Tracked { func: &'a FunctionValue<'a>, input: FnCachedInput<'a>, effects: FnCachedEffects<'a> }, } impl<'a> FnCacheTrackingData<'a> { @@ -43,14 +43,22 @@ impl<'a> FnCacheTrackingData<'a> { FnCacheTrackingData::UnTrackable } - pub fn new_in(allocator: &'a allocator::Allocator, callee: &'a FunctionValue<'a>) -> Self { - Self::Tracked { callee, effects: FnCachedEffects::new_in(allocator) } + pub fn new_in(allocator: &'a allocator::Allocator, info: FunctionCallInfo<'a>) -> Self { + if let Some(cache_key) = info.cache_key { + Self::Tracked { + func: info.func, + input: cache_key, + effects: FnCachedEffects::new_in(allocator), + } + } else { + FnCacheTrackingData::UnTrackable + } } pub fn track_read( &mut self, target: ReadWriteTarget<'a>, - cacheable: Option>, + cacheable: Option>, ) { let Self::Tracked { effects, .. } = self else { return; @@ -59,7 +67,7 @@ impl<'a> FnCacheTrackingData<'a> { *self = Self::UnTrackable; return; }; - let TrackReadCachable::Mutable(current_value) = cacheable else { + let TrackReadCacheable::Mutable(current_value) = cacheable else { return; }; if effects.reads.len() > 8 { @@ -97,6 +105,8 @@ impl<'a> FnCacheTrackingData<'a> { }; effects.writes.insert(target, cacheable); } + + pub fn track_call(&mut self, input: FnCachedInput<'a>) {} } #[derive(Debug)] diff --git a/crates/jsshaker/src/value/function/call.rs b/crates/jsshaker/src/value/function/call.rs new file mode 100644 index 00000000..3c18cea6 --- /dev/null +++ b/crates/jsshaker/src/value/function/call.rs @@ -0,0 +1,101 @@ +use crate::{ + Analyzer, + dep::Dep, + entity::Entity, + utils::CalleeNode, + value::{ + ArgumentsValue, FunctionValue, ObjectPrototype, TypeofResult, + cache::{FnCache, FnCachedInput}, + }, +}; + +#[derive(Debug, Clone, Copy)] +pub struct FunctionCallInfo<'a> { + pub func: &'a FunctionValue<'a>, + pub call_dep: Dep<'a>, + pub cache_key: Option>, + pub this: Entity<'a>, + pub args: ArgumentsValue<'a>, + pub consume: bool, +} + +impl<'a> FunctionValue<'a> { + pub fn call_impl( + &'a self, + analyzer: &mut Analyzer<'a>, + dep: Dep<'a>, + this: Entity<'a>, + args: ArgumentsValue<'a>, + consume: bool, + ) -> Entity<'a> { + let call_dep = analyzer.dep((self.callee.into_node(), dep)); + + let cache_key = FnCache::get_key::(analyzer, this, args); + if !consume + && let Some(cache_key) = cache_key + && let Some(cached_ret) = self.cache.borrow().retrieve(analyzer, &cache_key) + { + analyzer.global_effect(); + analyzer.consume((call_dep, this, args)); + return cached_ret; + } + + let info = FunctionCallInfo { func: self, call_dep, cache_key, this, args, consume }; + + let (ret_val, cache_tracking) = match self.callee.node { + CalleeNode::Function(node) => analyzer.call_function(node, info), + CalleeNode::ArrowFunctionExpression(node) => { + analyzer.call_arrow_function_expression(node, info) + } + CalleeNode::ClassConstructor(node) => { + // if !CTOR { + analyzer.call_class_constructor(node, info) + // } else { + // analyzer.throw_builtin_error("Cannot invoke class constructor without 'new'"); + // analyzer.factory.unknown + // } + } + CalleeNode::BoundFunction(bound_fn) => { + analyzer.call_bound_function::(bound_fn, info) + } + _ => unreachable!(), + }; + let ret_val = if IS_CTOR { + let typeof_ret = ret_val.test_typeof(); + match ( + typeof_ret.intersects(TypeofResult::Object), + typeof_ret.intersects(TypeofResult::_Primitive), + ) { + (true, true) => analyzer.factory.union((ret_val, this)), + (true, false) => ret_val, + (false, true) => this, + (false, false) => analyzer.factory.never, + } + } else { + ret_val + }; + + if let Some(cache_key) = cache_key { + // if cache_tracking.has_outer_deps { + // println!("Has outer deps: {}", self.callee.debug_name); + // } + self.cache.borrow_mut().update_cache(cache_key, ret_val, cache_tracking); + } + + analyzer.factory.computed(ret_val, call_dep) + } + + pub fn construct_impl( + &'a self, + analyzer: &mut Analyzer<'a>, + dep: Dep<'a>, + args: ArgumentsValue<'a>, + consume: bool, + ) -> Entity<'a> { + let target = analyzer.new_empty_object( + ObjectPrototype::Custom(self.prototype), + self.prototype.mangling_group.get(), + ); + self.call_impl::(analyzer, dep, target.into(), args, consume) + } +} diff --git a/crates/jsshaker/src/value/function/mod.rs b/crates/jsshaker/src/value/function/mod.rs index 0949b7b9..6e8a5f46 100644 --- a/crates/jsshaker/src/value/function/mod.rs +++ b/crates/jsshaker/src/value/function/mod.rs @@ -2,6 +2,7 @@ mod arguments; pub mod bound; mod builtin; pub mod cache; +pub mod call; use std::cell::{Cell, RefCell}; @@ -209,110 +210,6 @@ impl<'a> FunctionValue<'a> { false } - pub fn call_impl( - &'a self, - analyzer: &mut Analyzer<'a>, - dep: Dep<'a>, - this: Entity<'a>, - args: ArgumentsValue<'a>, - consume: bool, - ) -> Entity<'a> { - let call_dep = analyzer.dep((self.callee.into_node(), dep)); - - let cache_key = FnCache::get_key::(analyzer, this, args); - if !consume - && let Some(cache_key) = cache_key - && let Some(cached_ret) = self.cache.borrow().retrieve(analyzer, &cache_key) - { - analyzer.global_effect(); - analyzer.consume((call_dep, this, args)); - return cached_ret; - } - - let (ret_val, cache_tracking) = match self.callee.node { - CalleeNode::Function(node) => analyzer.call_function( - self, - call_dep, - node, - &self.variable_scope_stack, - this, - args, - consume, - ), - CalleeNode::ArrowFunctionExpression(node) => analyzer.call_arrow_function_expression( - self, - call_dep, - node, - &self.variable_scope_stack, - args, - consume, - ), - CalleeNode::ClassConstructor(node) => { - // if !CTOR { - analyzer.call_class_constructor( - self, - call_dep, - node, - &self.variable_scope_stack, - this, - args, - consume, - ) - // } else { - // analyzer.throw_builtin_error("Cannot invoke class constructor without 'new'"); - // analyzer.factory.unknown - // } - } - CalleeNode::BoundFunction(bound_fn) => analyzer.call_bound_function( - self, - call_dep, - bound_fn, - &self.variable_scope_stack, - IS_CTOR.then_some(this), - args, - consume, - ), - _ => unreachable!(), - }; - let ret_val = if IS_CTOR { - let typeof_ret = ret_val.test_typeof(); - match ( - typeof_ret.intersects(TypeofResult::Object), - typeof_ret.intersects(TypeofResult::_Primitive), - ) { - (true, true) => analyzer.factory.union((ret_val, this)), - (true, false) => ret_val, - (false, true) => this, - (false, false) => analyzer.factory.never, - } - } else { - ret_val - }; - - if let Some(cache_key) = cache_key { - // if cache_tracking.has_outer_deps { - // println!("Has outer deps: {}", self.callee.debug_name); - // } - self.cache.borrow_mut().update_cache(cache_key, ret_val, cache_tracking); - } - - analyzer.factory.computed(ret_val, call_dep) - } - - pub fn construct_impl( - &'a self, - analyzer: &mut Analyzer<'a>, - dep: Dep<'a>, - args: ArgumentsValue<'a>, - consume: bool, - ) -> Entity<'a> { - let target = analyzer.new_empty_object( - ObjectPrototype::Custom(self.prototype), - self.prototype.mangling_group.get(), - ); - self.call_impl::(analyzer, dep, target.into(), args, consume) - } - pub fn consume_body(&'a self, analyzer: &mut Analyzer<'a>, this: Entity<'a>) { if self.body_consumed.get().is_some() { return; From 4ea991f1051f51a2d891dbd748ef7cf5d6e06a9d Mon Sep 17 00:00:00 2001 From: _Kerman Date: Wed, 17 Dec 2025 14:21:23 +0800 Subject: [PATCH 3/4] wip --- .../nodes/expr/arrow_function_expression.rs | 4 +-- crates/jsshaker/src/nodes/misc/class.rs | 4 +-- crates/jsshaker/src/nodes/misc/function.rs | 4 +-- crates/jsshaker/src/scope/mod.rs | 9 ++---- crates/jsshaker/src/utils/callee_info.rs | 12 ++++---- crates/jsshaker/src/value/function/bound.rs | 11 ++----- crates/jsshaker/src/value/function/cache.rs | 30 ++++++++++++++++--- crates/jsshaker/src/value/function/call.rs | 4 +-- 8 files changed, 44 insertions(+), 34 deletions(-) diff --git a/crates/jsshaker/src/nodes/expr/arrow_function_expression.rs b/crates/jsshaker/src/nodes/expr/arrow_function_expression.rs index d0e857f3..519cecfb 100644 --- a/crates/jsshaker/src/nodes/expr/arrow_function_expression.rs +++ b/crates/jsshaker/src/nodes/expr/arrow_function_expression.rs @@ -9,7 +9,7 @@ use crate::{ entity::Entity, transformer::Transformer, utils::CalleeNode, - value::{cache::FnCacheTrackingData, call::FunctionCallInfo}, + value::{cache::FnCacheTrackingData, call::FnCallInfo}, }; impl<'a> Analyzer<'a> { @@ -23,7 +23,7 @@ impl<'a> Analyzer<'a> { pub fn call_arrow_function_expression( &mut self, node: &'a ArrowFunctionExpression<'a>, - info: FunctionCallInfo<'a>, + info: FnCallInfo<'a>, ) -> (Entity<'a>, FnCacheTrackingData<'a>) { let runner = move |analyzer: &mut Analyzer<'a>| { analyzer.push_call_scope(info, node.r#async, false); diff --git a/crates/jsshaker/src/nodes/misc/class.rs b/crates/jsshaker/src/nodes/misc/class.rs index c21140fb..016b7860 100644 --- a/crates/jsshaker/src/nodes/misc/class.rs +++ b/crates/jsshaker/src/nodes/misc/class.rs @@ -16,7 +16,7 @@ use crate::{ entity::Entity, transformer::Transformer, utils::CalleeNode, - value::{ObjectPrototype, ValueTrait, cache::FnCacheTrackingData, call::FunctionCallInfo}, + value::{ObjectPrototype, ValueTrait, cache::FnCacheTrackingData, call::FnCallInfo}, }; #[derive(Default)] @@ -127,7 +127,7 @@ impl<'a> Analyzer<'a> { pub fn call_class_constructor( &mut self, node: &'a Class<'a>, - info: FunctionCallInfo<'a>, + info: FnCallInfo<'a>, ) -> (Entity<'a>, FnCacheTrackingData<'a>) { let factory = self.factory; let data = self.load_data::(AstKind2::Class(node)); diff --git a/crates/jsshaker/src/nodes/misc/function.rs b/crates/jsshaker/src/nodes/misc/function.rs index 449af579..0dd5a6dc 100644 --- a/crates/jsshaker/src/nodes/misc/function.rs +++ b/crates/jsshaker/src/nodes/misc/function.rs @@ -12,7 +12,7 @@ use crate::{ entity::Entity, transformer::Transformer, utils::CalleeNode, - value::{cache::FnCacheTrackingData, call::FunctionCallInfo}, + value::{cache::FnCacheTrackingData, call::FnCallInfo}, }; impl<'a> Analyzer<'a> { @@ -39,7 +39,7 @@ impl<'a> Analyzer<'a> { pub fn call_function( &mut self, node: &'a Function<'a>, - info: FunctionCallInfo<'a>, + info: FnCallInfo<'a>, ) -> (Entity<'a>, FnCacheTrackingData<'a>) { let runner = move |analyzer: &mut Analyzer<'a>| { analyzer.push_call_scope(info, node.r#async, node.generator); diff --git a/crates/jsshaker/src/scope/mod.rs b/crates/jsshaker/src/scope/mod.rs index 30f476b9..a4bbf096 100644 --- a/crates/jsshaker/src/scope/mod.rs +++ b/crates/jsshaker/src/scope/mod.rs @@ -20,7 +20,7 @@ use crate::{ entity::Entity, module::ModuleId, utils::{CalleeInfo, CalleeNode}, - value::{ObjectId, cache::FnCacheTrackingData, call::FunctionCallInfo}, + value::{ObjectId, cache::FnCacheTrackingData, call::FnCallInfo}, }; pub struct Scoping<'a> { @@ -104,12 +104,7 @@ impl<'a> Analyzer<'a> { self.scoping.variable.replace_stack(new_stack) } - pub fn push_call_scope( - &mut self, - info: FunctionCallInfo<'a>, - is_async: bool, - is_generator: bool, - ) { + pub fn push_call_scope(&mut self, info: FnCallInfo<'a>, is_async: bool, is_generator: bool) { let dep_id = DepAtom::from_counter(); if info.consume { self.refer_dep(dep_id); 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/bound.rs b/crates/jsshaker/src/value/function/bound.rs index 64445891..f367a675 100644 --- a/crates/jsshaker/src/value/function/bound.rs +++ b/crates/jsshaker/src/value/function/bound.rs @@ -3,7 +3,7 @@ use oxc::span::Span; use crate::{ Analyzer, entity::Entity, - value::{ArgumentsValue, cache::FnCacheTrackingData, call::FunctionCallInfo}, + value::{ArgumentsValue, cache::FnCacheTrackingData, call::FnCallInfo}, }; #[derive(Debug)] @@ -18,17 +18,10 @@ impl<'a> Analyzer<'a> { pub fn call_bound_function( &mut self, bound_fn: &'a BoundFunction<'a>, - info: FunctionCallInfo<'a>, + info: FnCallInfo<'a>, ) -> (Entity<'a>, FnCacheTrackingData<'a>) { self.push_call_scope(info, false, false); - // self.exec_formal_parameters(&node.params, args, DeclarationKind::ArrowFunctionParameter); - // if node.expression { - // self.exec_function_expression_body(&node.body); - // } else { - // self.exec_function_body(&node.body); - // } - let args = ArgumentsValue::from_concatenate(self, bound_fn.bound_args, info.args); let ret = bound_fn.target.call( self, diff --git a/crates/jsshaker/src/value/function/cache.rs b/crates/jsshaker/src/value/function/cache.rs index dfd6dee3..cddd9422 100644 --- a/crates/jsshaker/src/value/function/cache.rs +++ b/crates/jsshaker/src/value/function/cache.rs @@ -5,7 +5,7 @@ use crate::{ analyzer::rw_tracking::{ReadWriteTarget, TrackReadCacheable}, entity::Entity, scope::variable_scope::EntityOrTDZ, - value::{ArgumentsValue, FunctionValue, cacheable::Cacheable, call::FunctionCallInfo}, + value::{ArgumentsValue, FunctionValue, cacheable::Cacheable, call::FnCallInfo}, }; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -15,11 +15,28 @@ 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, (Entity<'a>, FnCachedInput<'a>)>, + pub calls: allocator::HashSet<'a, FnCallEffect<'a>>, } impl<'a> FnCachedEffects<'a> { @@ -43,7 +60,7 @@ impl<'a> FnCacheTrackingData<'a> { FnCacheTrackingData::UnTrackable } - pub fn new_in(allocator: &'a allocator::Allocator, info: FunctionCallInfo<'a>) -> Self { + pub fn new_in(allocator: &'a allocator::Allocator, info: FnCallInfo<'a>) -> Self { if let Some(cache_key) = info.cache_key { Self::Tracked { func: info.func, @@ -106,7 +123,12 @@ impl<'a> FnCacheTrackingData<'a> { effects.writes.insert(target, cacheable); } - pub fn track_call(&mut self, input: FnCachedInput<'a>) {} + pub fn track_call(&mut self, effect: FnCallEffect<'a>) { + let Self::Tracked { effects, .. } = self else { + return; + }; + effects.calls.insert(effect); + } } #[derive(Debug)] diff --git a/crates/jsshaker/src/value/function/call.rs b/crates/jsshaker/src/value/function/call.rs index 3c18cea6..0f4e81aa 100644 --- a/crates/jsshaker/src/value/function/call.rs +++ b/crates/jsshaker/src/value/function/call.rs @@ -10,7 +10,7 @@ use crate::{ }; #[derive(Debug, Clone, Copy)] -pub struct FunctionCallInfo<'a> { +pub struct FnCallInfo<'a> { pub func: &'a FunctionValue<'a>, pub call_dep: Dep<'a>, pub cache_key: Option>, @@ -40,7 +40,7 @@ impl<'a> FunctionValue<'a> { return cached_ret; } - let info = FunctionCallInfo { func: self, call_dep, cache_key, this, args, consume }; + let info = FnCallInfo { func: self, call_dep, cache_key, this, args, consume }; let (ret_val, cache_tracking) = match self.callee.node { CalleeNode::Function(node) => analyzer.call_function(node, info), From 18d8820734966b90af8a86eb06006bee66e9537d Mon Sep 17 00:00:00 2001 From: _Kerman Date: Wed, 17 Dec 2025 16:06:32 +0800 Subject: [PATCH 4/4] wip --- crates/jsshaker/src/analyzer/rw_tracking.rs | 7 +++- crates/jsshaker/src/value/function/cache.rs | 41 +++++++++++++++------ 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/crates/jsshaker/src/analyzer/rw_tracking.rs b/crates/jsshaker/src/analyzer/rw_tracking.rs index b021953b..ce969d27 100644 --- a/crates/jsshaker/src/analyzer/rw_tracking.rs +++ b/crates/jsshaker/src/analyzer/rw_tracking.rs @@ -47,6 +47,7 @@ impl<'a> Analyzer<'a> { ) { 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/value/function/cache.rs b/crates/jsshaker/src/value/function/cache.rs index cddd9422..6e1e18b0 100644 --- a/crates/jsshaker/src/value/function/cache.rs +++ b/crates/jsshaker/src/value/function/cache.rs @@ -52,7 +52,8 @@ impl<'a> FnCachedEffects<'a> { #[derive(Debug)] pub enum FnCacheTrackingData<'a> { UnTrackable, - Tracked { func: &'a FunctionValue<'a>, input: FnCachedInput<'a>, effects: FnCachedEffects<'a> }, + Tracked { self_call_effect: FnCallEffect<'a>, effects: FnCachedEffects<'a> }, + Failed { self_call_effect: FnCallEffect<'a> }, } impl<'a> FnCacheTrackingData<'a> { @@ -63,8 +64,7 @@ impl<'a> FnCacheTrackingData<'a> { pub fn new_in(allocator: &'a allocator::Allocator, info: FnCallInfo<'a>) -> Self { if let Some(cache_key) = info.cache_key { Self::Tracked { - func: info.func, - input: cache_key, + self_call_effect: FnCallEffect { func: info.func, input: cache_key }, effects: FnCachedEffects::new_in(allocator), } } else { @@ -72,24 +72,40 @@ impl<'a> FnCacheTrackingData<'a> { } } + 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; + ) -> 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 TrackReadCacheable::Mutable(current_value) = cacheable else { - return; + return None; }; if effects.reads.len() > 8 { - *self = Self::UnTrackable; - return; + self.failed(); + return Some(self_call_effect); } match effects.reads.entry(target) { allocator::hash_map::Entry::Occupied(v) => { @@ -106,6 +122,7 @@ impl<'a> FnCacheTrackingData<'a> { v.insert(current_value); } } + Some(self_call_effect) } pub fn track_write( @@ -117,7 +134,7 @@ impl<'a> FnCacheTrackingData<'a> { return; }; let Some(cacheable) = cacheable else { - *self = Self::UnTrackable; + self.failed(); return; }; effects.writes.insert(target, cacheable);