Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 46 additions & 1 deletion src/optimizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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();
Expand Down
73 changes: 73 additions & 0 deletions src/value_analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,73 @@ use crate::ir::{
};
use crate::semantic::{EscapeState, Type};

type InheritedFieldAliases<'src> = Vec<Vec<(&'src str, usize)>>;

// 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::<u32, Vec<(&str, usize)>>::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<String, AHashMap<usize, FiniteSummary>>,
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<String, AHashMap<usize, I32Range>>,
aliases: &InheritedFieldAliases<'_>,
unsafe_owners: &AHashSet<String>,
) {
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,
Expand Down Expand Up @@ -286,7 +353,9 @@ pub fn analyze_integer_values(module: &ControlFlowModule<'_>) -> IntegerValueAna
})
.collect::<Vec<_>>();
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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -413,7 +483,9 @@ pub fn analyze_finite_values(module: &ControlFlowModule<'_>) -> FiniteValueAnaly
}
})
.collect::<Vec<_>>();
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
Expand Down Expand Up @@ -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;
Expand Down
Loading