Skip to content
Merged
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
40 changes: 40 additions & 0 deletions benchmarks/compiler_output/workloads.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2076,6 +2076,7 @@ allowed_hot_loop_runtime_calls = [
"js_for_in_keys_stable_value",
"js_gc_loop_safepoint",
"js_in_operator",
"js_value_typeof",
]

[workloads.for_in_stable_keys.vectorization]
Expand All @@ -2092,6 +2093,8 @@ allowed_missed_reason_kinds = [
"unsupported_reduction",
]

[workloads.for_in_stable_keys.runtime_budgets]

[[workloads.for_in_stable_keys.stdout_checks]]
name = "for_in_stable_keys_checksum"
equals = "for_in_stable_keys:200000\n"
Expand All @@ -2114,3 +2117,40 @@ regex_none = [
"call i64 @js_object_get_own_property_names",
]
detail = "the stable arm does not directly allocate or rebuild generic key lists"

[workloads.issue_8693_imported_this]
source = "test-files/fixtures/issue_8693_imported_this/main.js"
kind = "imported_class_method_specialization"
allow_hot_loop_conversions = true
allow_dynamic_property_runtime = true

[workloads.issue_8693_imported_this.vectorization]
min_vectorized_loops = 0
scalar_baseline = "allowed: this fixture gates cross-module method dispatch, not loop vectorization"
allowed_missed_reason_kinds = [
"call_instruction",
"control_flow",
"generic_not_vectorized",
"not_beneficial",
"uncountable_loop",
"unknown_trip_count",
"unsupported_instruction",
"unsupported_reduction",
]

[workloads.issue_8693_imported_this.runtime_budgets]

[workloads.issue_8693_imported_this.native_rep_checks]
allow_materialization_reasons = ["runtime_api"]

[[workloads.issue_8693_imported_this.native_rep_checks.require_records]]
name = "imported_registry_proven_this_selection"
consumer = "proven_this_method_direct_call"
notes_contains = "receiver_provenance=imported_class_metadata"
min = 2

[[workloads.issue_8693_imported_this.native_rep_checks.require_records]]
name = "imported_registry_generic_fallback_retained"
consumer = "proven_this_method_direct_call"
notes_contains = "generic_dispatch_fallback=js_native_call_method_by_id"
min = 2
9 changes: 9 additions & 0 deletions changelog.d/8693-imported-this-specialization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
category: Performance
title: Specialize imported methods that capture this
---

ESM import and re-export metadata now carries producer-proven method
eligibility, allowing guarded direct calls into stable class methods that use
`this`. Generic dispatch remains available for shadowed or mutated receivers,
including prototype replacement, deletion, and recreation.
12 changes: 12 additions & 0 deletions changelog.d/8729-symbol-identity-equality.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Made strict equality against a proven `Symbol()` or `Symbol.for()` value use
direct identity instead of the generic JavaScript equality helper. The proof
comes only from a stable constructor initializer, never from an erased
TypeScript `symbol` annotation; reassigned bindings and loose equality retain
their semantic runtime paths.

This removes two generic `js_eq` calls from codehz/ecs's 10k-entity
accumulation loop. On an Apple M1 Mac mini, 11 alternating process pairs
measured the row at 0.195845 ms versus 0.615553 ms on the parent change, a
68.193% median paired improvement with 11/11 wins. The full ECS suite remained
7/7 with checksum 50005000, and a forced verified-GC Symbol stress run recorded
87 copying minors and 11,470 copied objects with Node-identical output.
1 change: 1 addition & 0 deletions changelog.d/8735-dense-subclass-seqlock-fence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed a data race in the dense-array subclass layout cache. `cached_dense_layout` read two payload fields with `Relaxed` ordering and then rechecked the sequence counter with an `Acquire` load — but an acquire *load* only constrains operations that follow it, so on weakly-ordered hardware the preceding payload reads could sink past the recheck. A reader racing a colliding publisher could then combine one field from the old layout with one from the new and return a wrong element value rather than faulting. A standalone `fence(Acquire)` now sits between the payload reads and the recheck, which is the canonical seqlock-reader form.
19 changes: 19 additions & 0 deletions crates/perry-codegen/src/codegen/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,25 @@ pub(super) fn inline_hot_small_size_cap() -> usize {
})
}

/// Give the representation-specialized method body the ordinary small-body
/// inline bias. This lets producer-local chains such as `Registry.add ->
/// Group.pushEntity` optimize through the second method boundary while the
/// externally linked clone remains callable by importers.
pub(super) fn apply_pshape_inline_policy(
lf: &mut crate::function::LlFunction,
method: &perry_hir::Function,
is_pshape_clone: bool,
) {
if !is_pshape_clone || method.is_async || method.is_generator || method.was_plain_async {
return;
}
if method.body.len() <= 8 {
lf.force_inline = true;
} else if inline_hot_small_enabled() && method.body.len() <= inline_hot_small_size_cap() {
lf.inline_hint = true;
}
}

/// Maximum total (module-wide) direct call sites a function may have and still
/// be hinted. This is the anti-bloat backstop: the raised `-inlinehint-threshold`
/// lifts LLVM's ceiling for a hinted callee at *every* one of its call sites, so
Expand Down
10 changes: 9 additions & 1 deletion crates/perry-codegen/src/codegen/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,10 +306,18 @@ pub(super) fn compile_method(
let ic_base = llmod.ic_counter;
let buffer_alias_base = llmod.buffer_alias_counter;
let lf = llmod.define_function(&llvm_name, DOUBLE, params);
if is_pshape_clone || is_index_clone || typed_public_trampoline.is_some() || force_generic_body
// Plain `$pshape` clones are producer-published capabilities and need
// external linkage for guarded calls from importing modules. The stricter
// array-cache clone remains module-local: only containment-proven locals
// in this module may select it.
if ptr_array_cache_clone
|| is_index_clone
|| typed_public_trampoline.is_some()
|| force_generic_body
{
lf.linkage = "internal".to_string();
}
super::helpers::apply_pshape_inline_policy(lf, method, is_pshape_clone);
if is_index_clone {
lf.pre_statepoint_inline = true;
}
Expand Down
4 changes: 4 additions & 0 deletions crates/perry-codegen/src/codegen/method_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,10 @@ pub(crate) fn build_method_names(
let param_types: Vec<crate::types::LlvmType> =
std::iter::repeat_n(DOUBLE, arity).collect();
llmod.declare_function(&llvm_fn, DOUBLE, &param_types);
if ic.proven_this_method_names.contains(method_name) {
let clone = crate::collectors::pshape_method_name(&llvm_fn);
llmod.declare_function(&clone, DOUBLE, &param_types);
}
}

// Cross-module getters. The dispatch site at
Expand Down
65 changes: 54 additions & 11 deletions crates/perry-codegen/src/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1808,17 +1808,6 @@ pub fn compile_module(hir: &HirModule, opts: CompileOptions) -> Result<Vec<u8>>
receiver_class_table,
&module_dispatch_facts,
) {
// #7142: the tower routing site emits its own inline shape
// re-check, so it only takes the clone where the clone deletes
// strictly more guarded field sites than that check costs. The
// other two sites are guard-dominated and route unconditionally.
if crate::collectors::pshape_tower_route_profitable(
class,
method,
receiver_class_table,
) {
pshape_tower_routable.insert((class.name.clone(), method.name.clone()));
}
pshape_methods.insert((class.name.clone(), method.name.clone()), fact);
}
match typed_abi::typed_f64_method_rejection_reason(method) {
Expand Down Expand Up @@ -1933,6 +1922,60 @@ pub fn compile_module(hir: &HirModule, opts: CompileOptions) -> Result<Vec<u8>>
}
}
}
// #7142: the tower routing site emits its own inline shape re-check, so it
// only takes a clone where that clone deletes strictly more guarded work
// than the check costs. Price these routes after all local clone facts are
// known so nested `this.other()` calls can count an inherited clone too.
let local_pshape_methods: std::collections::HashSet<(String, String)> =
pshape_methods.keys().cloned().collect();
for class in &hir.classes {
for method in &class.methods {
let key = (class.name.clone(), method.name.clone());
if local_pshape_methods.contains(&key)
&& crate::collectors::pshape_tower_route_profitable(
class,
method,
receiver_class_table,
&local_pshape_methods,
)
{
pshape_tower_routable.insert(key);
}
}
}
// Imported classes publish only clone names the defining module proved and
// emitted. Installing those capabilities in the same registries lets both
// the ordinary exact-class/shape guarded arm and profitable adapter-field
// dispatch towers retain the receiver proof across ESM and npm boundaries.
// The tower subset is producer-authored because only the defining module
// can see enough of the body to price its additional keys-token check.
for imported in &opts.imported_classes {
let effective_name = imported
.local_alias
.as_deref()
.unwrap_or(&imported.name)
.to_string();
if hir.classes.iter().any(|class| class.name == effective_name) {
continue;
}
for method in &imported.proven_this_method_names {
if !imported.method_names.contains(method) {
continue;
}
pshape_methods.insert(
(effective_name.clone(), method.clone()),
crate::collectors::PtrShapeLocal {
class_name: effective_name.clone(),
numeric_fields: std::collections::HashSet::new(),
report_name: crate::opt_report::enabled()
.then(|| format!("imported:{}", imported.source_prefix)),
},
);
if imported.proven_this_tower_method_names.contains(method) {
pshape_tower_routable.insert((effective_name.clone(), method.clone()));
}
}
}
let mut compiler_private_async_i32_control_locals = std::collections::HashSet::new();
let mut compiler_private_async_i1_control_locals = std::collections::HashSet::new();
crate::boxed_vars::collect_compiler_private_async_control_locals_in_stmts(
Expand Down
34 changes: 34 additions & 0 deletions crates/perry-codegen/src/codegen/module_globals_emit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ fn module_global_runtime_type(
Expr::String(_) | Expr::WtfString(_) | Expr::I18nString { .. } | Expr::TypeOf(_) => {
Some(Type::String)
}
Expr::SymbolNew(_) | Expr::SymbolFor(_) => Some(Type::Symbol),
// Compiler-owned allocation HIR establishes these runtime classes
// independently of the erased binding annotation. Keep module-global
// facts aligned with `proven_type_from_init`; otherwise a value that
Expand Down Expand Up @@ -75,6 +76,39 @@ fn module_global_runtime_type(
}
}

#[cfg(test)]
mod runtime_type_tests {
use super::module_global_runtime_type;
use perry_hir::types::Type;
use perry_hir::Expr;

#[test]
fn symbol_constructors_are_module_global_runtime_proofs() {
assert_eq!(
module_global_runtime_type(&Expr::SymbolNew(None), true),
Some(Type::Symbol)
);
assert_eq!(
module_global_runtime_type(
&Expr::SymbolFor(Box::new(Expr::String("shared".to_string()))),
true,
),
Some(Type::Symbol)
);
}

#[test]
fn an_object_initializer_cannot_inherit_a_symbol_annotation_as_proof() {
assert_eq!(
module_global_runtime_type(
&Expr::Object(vec![("x".to_string(), Expr::Number(1.0))]),
true,
),
None
);
}
}

fn module_shadows_shared_array_buffer_intrinsic(
hir: &HirModule,
imported_classes: &[ImportedClass],
Expand Down
21 changes: 15 additions & 6 deletions crates/perry-codegen/src/codegen/opts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,16 @@ pub struct ImportedClass {
pub has_instance_fields: bool,
/// Method names defined on this class.
pub method_names: Vec<String>,
/// Own methods for which the defining module emitted an externally
/// callable, guarded proven-`this` clone. Consumers may reference only
/// names in this producer-authored capability set; absence is fail-closed
/// and keeps the public method body on the direct arm.
pub proven_this_method_names: Vec<String>,
/// Subset of `proven_this_method_names` for which the producer also proved
/// that the extra exact-keys check paid by a class-id dispatch-tower arm is
/// profitable. This keeps adapter-field calls fail-closed without asking
/// an importer that cannot see the method body to repeat the decision.
pub proven_this_tower_method_names: Vec<String>,
/// Declared return types parallel to `method_names`. Imported class stubs
/// retain these so a call such as `factory.make().run()` can recover the
/// returned receiver class without value-importing that class directly.
Expand Down Expand Up @@ -911,12 +921,11 @@ pub(crate) struct CrossModuleCtx {
/// exported.
pub nonnegative_index_methods: std::collections::HashMap<(String, String), Vec<u32>>,
/// Representation-selection Phase 5a: `(class, method)` pairs that have a
/// generated `internal` proven-`this` clone
/// (`collectors/proven_this.rs`). Keys are OWN declarations of
/// module-local classes only, which is precisely the condition the two
/// routing sites rely on: a hit means the receiver's proven exact class is
/// the class the clone was compiled for, so `this` cannot be a subclass
/// instance with a different chain.
/// generated proven-`this` clone (`collectors/proven_this.rs`). Local keys
/// come from body analysis; imported keys come from an explicit capability
/// published by the defining module. Both represent OWN declarations, so a
/// hit means the receiver's proven exact class is the class the clone was
/// compiled for and `this` cannot have a different subclass chain.
pub pshape_methods:
std::collections::HashMap<(String, String), crate::collectors::PtrShapeLocal>,
/// #7142: the subset of [`Self::pshape_methods`] whose clone the class-id
Expand Down
1 change: 1 addition & 0 deletions crates/perry-codegen/src/collectors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ pub(crate) use number_by_construction::collect_number_by_construction_locals;
pub(crate) use param_ranges::{collect_param_int_ranges, ParamIntRanges};
pub(crate) use pointer_locals::collect_pointer_typed_locals;
pub(crate) use proven_this::{
exportable_method_capabilities as exportable_proven_this_method_capabilities,
method_proven_this, prune_unregistered_clones, pshape_method_name, ptr_array_cache_fields,
ptr_array_cache_method_name, ptr_array_cached_method,
tower_route_profitable as pshape_tower_route_profitable,
Expand Down
Loading
Loading