diff --git a/src/optimizer.rs b/src/optimizer.rs index fa7a688..6255aeb 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -7447,7 +7447,7 @@ enum EscapeNode { AggregateField(u32), } -fn aggregate_field_slot_ids<'src>( +pub(crate) fn aggregate_field_slot_ids<'src>( module: &ControlFlowModule<'src>, ) -> AHashMap<(&'src str, usize), u32> { let layouts = module @@ -15217,6 +15217,51 @@ mod tests { assert!(!output.contains("===\"dark\""), "{output}"); } + #[test] + fn inherited_field_summaries_preserve_subclass_hooks_and_integer_overflow() { + for inlining in [false, true] { + let arena = Bump::new(); + let program = parse_source(&arena, r#" + class Base { + (func()->float)? hook; + int count; + string label; + init() { this.hook = null; this.count = 1; this.label = "base"; } + float sample() { + (func()->float)? callback = this.hook; + if (callback != null) { return callback(); } + return -1.0; + } + int next() { return this.count + 1; } + string name() { return this.label; } + } + class Child extends Base { + init() { super(); this.hook = () => 42.0; this.count = 2147483647; this.label = "child"; } + } + class Grandchild extends Child { init() { super(); } } + Base plain = new Base(); + Grandchild derived = new Grandchild(); + print(plain.sample()); print(derived.sample()); + print(plain.next()); print(derived.next()); + print(plain.name()); print(derived.name()); + "#).unwrap(); + let semantics = analyze(&program).unwrap(); + let mut ir = lower_to_control_flow(&program, &semantics).unwrap(); + let options = OptimizationOptions { + inlining, + scalar_replacement: false, + ..OptimizationOptions::default() + }; + optimize_control_flow_with_options(&mut ir, &options, false).unwrap(); + let output = crate::codegen_ir_js::emit_optimized_ir_js(&ir).unwrap(); + assert_eq!( + run_javascript(&output), + "-1\n42\n2\n-2147483648\nbase\nchild\n", + "{output}" + ); + } + } + #[test] fn folds_optional_access_guard_for_a_proven_non_null_receiver() { let arena = Bump::new(); diff --git a/src/value_analysis.rs b/src/value_analysis.rs index d5ecfe1..00e99a2 100644 --- a/src/value_analysis.rs +++ b/src/value_analysis.rs @@ -6,6 +6,73 @@ use crate::ir::{ }; use crate::semantic::{EscapeState, Type}; +type InheritedFieldAliases<'src> = Vec>; + +// A field inherited by a derived class is the same storage slot when read +// through either nominal type. Join writes/defaults and untyped boundaries +// before using these summaries to fold branches or remove integer coercions. +fn inherited_field_aliases<'src>(module: &ControlFlowModule<'src>) -> InheritedFieldAliases<'src> { + let mut groups = AHashMap::>::default(); + for (field, slot) in crate::optimizer::aggregate_field_slot_ids(module) { + groups.entry(slot).or_default().push(field); + } + groups + .into_values() + .filter(|fields| fields.len() > 1) + .collect() +} + +fn share_inherited_finite_values( + fields: &mut AHashMap>, + aliases: &InheritedFieldAliases<'_>, +) { + for group in aliases { + let mut shared = FiniteSummary::Bottom; + for (owner, index) in group { + if let Some(value) = fields.get(*owner).and_then(|fields| fields.get(index)) { + shared = shared.join(value); + } + } + for (owner, index) in group { + join_finite_field(fields, owner, *index, shared.clone()); + } + } +} + +fn share_inherited_integer_ranges( + fields: &mut AHashMap>, + aliases: &InheritedFieldAliases<'_>, + unsafe_owners: &AHashSet, +) { + for group in aliases { + if group + .iter() + .any(|(owner, _)| unsafe_owners.contains(*owner)) + { + for (owner, index) in group { + if let Some(owner_fields) = fields.get_mut(*owner) { + owner_fields.remove(index); + } + } + continue; + } + let shared = group + .iter() + .filter_map(|(owner, index)| { + fields + .get(*owner) + .and_then(|fields| fields.get(index)) + .copied() + }) + .reduce(I32Range::join); + if let Some(shared) = shared { + for (owner, index) in group { + join_field(fields, owner, *index, shared); + } + } + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct I32Range { pub min: i64, @@ -286,7 +353,9 @@ pub fn analyze_integer_values(module: &ControlFlowModule<'_>) -> IntegerValueAna }) .collect::>(); let mut return_ranges = vec![None; module.functions.len()]; + let aliases = inherited_field_aliases(module); let mut field_ranges = default_class_field_ranges(module, &unsafe_fields); + share_inherited_integer_ranges(&mut field_ranges, &aliases, &unsafe_fields); loop { let next_facts = module .functions @@ -332,6 +401,7 @@ pub fn analyze_integer_values(module: &ControlFlowModule<'_>) -> IntegerValueAna ); } + share_inherited_integer_ranges(&mut proposed_fields, &aliases, &unsafe_fields); let mut changed = false; for (current, proposed) in parameter_ranges.iter_mut().zip(proposed_parameters) { for (current, proposed) in current.iter_mut().zip(proposed) { @@ -413,7 +483,9 @@ pub fn analyze_finite_values(module: &ControlFlowModule<'_>) -> FiniteValueAnaly } }) .collect::>(); + let aliases = inherited_field_aliases(module); let mut field_values = default_class_field_values(module, &unsafe_fields); + share_inherited_finite_values(&mut field_values, &aliases); loop { let next_facts = module @@ -473,6 +545,7 @@ pub fn analyze_finite_values(module: &ControlFlowModule<'_>) -> FiniteValueAnaly ); } } + share_inherited_finite_values(&mut proposed_fields, &aliases); changed |= join_finite_field_summaries(&mut field_values, proposed_fields); if !changed { break;