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
7 changes: 7 additions & 0 deletions changelog.d/11859-regfix-accessor-arm-names.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
A static-key store site emits the class-setter arm (#10498) only for a property
name that some compiled class of the program declares as a setter. The runtime
admits an entry only for a declared accessor, so every other store carried an
arm it could never take and ran it on every miss. The driver collects the
names over all modules and passes them to codegen; they are part of the object
cache key. Key-add cells lose 13 instructions per operation, and the compiled
tsc workload drops 1.1 MB.
1 change: 1 addition & 0 deletions crates/perry-codegen/src/codegen/closure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1223,6 +1223,7 @@ pub(super) fn compile_closure(
imported_vars: &cross_module.imported_vars,
imported_object_literals: &cross_module.imported_object_literals,
short_spread_method_candidates: &cross_module.short_spread_method_candidates,
program_class_accessor_names: cross_module.program_class_accessor_names.as_deref(),
object_literal_method_candidates: &cross_module.object_literal_method_candidates,
compile_time_constants: native_facts.compile_time_constants(),
target_triple: &cross_module.target_triple,
Expand Down
1 change: 1 addition & 0 deletions crates/perry-codegen/src/codegen/emission_order_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ fn ir_opts() -> CompileOptions {
constructor_param_counts: Default::default(),
imported_classes: Vec::new(),
short_spread_method_candidates: std::sync::Arc::default(),
program_class_accessor_names: Default::default(),
object_literal_method_candidates: std::sync::Arc::default(),
imported_enums: Vec::new(),
imported_async_funcs: std::collections::HashSet::new(),
Expand Down
2 changes: 2 additions & 0 deletions crates/perry-codegen/src/codegen/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -841,6 +841,7 @@ pub(super) fn compile_module_entry(
imported_vars: &cross_module.imported_vars,
imported_object_literals: &cross_module.imported_object_literals,
short_spread_method_candidates: &cross_module.short_spread_method_candidates,
program_class_accessor_names: cross_module.program_class_accessor_names.as_deref(),
object_literal_method_candidates: &cross_module.object_literal_method_candidates,
compile_time_constants: main_native_facts.compile_time_constants(),
target_triple: &cross_module.target_triple,
Expand Down Expand Up @@ -1714,6 +1715,7 @@ pub(super) fn compile_module_entry(
imported_vars: &cross_module.imported_vars,
imported_object_literals: &cross_module.imported_object_literals,
short_spread_method_candidates: &cross_module.short_spread_method_candidates,
program_class_accessor_names: cross_module.program_class_accessor_names.as_deref(),
object_literal_method_candidates: &cross_module.object_literal_method_candidates,
compile_time_constants: init_native_facts.compile_time_constants(),
target_triple: &cross_module.target_triple,
Expand Down
1 change: 1 addition & 0 deletions crates/perry-codegen/src/codegen/entry/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ fn entry_opts(output_type: &str) -> CompileOptions {
constructor_param_counts: Default::default(),
imported_classes: Vec::new(),
short_spread_method_candidates: std::sync::Arc::default(),
program_class_accessor_names: Default::default(),
object_literal_method_candidates: std::sync::Arc::default(),
imported_enums: Vec::new(),
imported_async_funcs: std::collections::HashSet::new(),
Expand Down
1 change: 1 addition & 0 deletions crates/perry-codegen/src/codegen/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1363,6 +1363,7 @@ pub(super) fn compile_function(
imported_vars: &cross_module.imported_vars,
imported_object_literals: &cross_module.imported_object_literals,
short_spread_method_candidates: &cross_module.short_spread_method_candidates,
program_class_accessor_names: cross_module.program_class_accessor_names.as_deref(),
object_literal_method_candidates: &cross_module.object_literal_method_candidates,
compile_time_constants: native_facts.compile_time_constants(),
target_triple: &cross_module.target_triple,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ fn emit(classes: bool, objects: bool) -> String {
let opts = CompileOptions {
emit_ir_only: true,
short_spread_method_candidates: Arc::new(short),
program_class_accessor_names: Default::default(),
object_literal_method_candidates: Arc::new(object),
..Default::default()
};
Expand Down
1 change: 1 addition & 0 deletions crates/perry-codegen/src/codegen/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,7 @@ pub(super) fn compile_method(
imported_vars: &cross_module.imported_vars,
imported_object_literals: &cross_module.imported_object_literals,
short_spread_method_candidates: &cross_module.short_spread_method_candidates,
program_class_accessor_names: cross_module.program_class_accessor_names.as_deref(),
object_literal_method_candidates: &cross_module.object_literal_method_candidates,
compile_time_constants: native_facts.compile_time_constants(),
target_triple: &cross_module.target_triple,
Expand Down
1 change: 1 addition & 0 deletions crates/perry-codegen/src/codegen/method_static.rs
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,7 @@ pub(in crate::codegen) fn compile_static_method(
imported_vars: &cross_module.imported_vars,
imported_object_literals: &cross_module.imported_object_literals,
short_spread_method_candidates: &cross_module.short_spread_method_candidates,
program_class_accessor_names: cross_module.program_class_accessor_names.as_deref(),
object_literal_method_candidates: &cross_module.object_literal_method_candidates,
compile_time_constants: native_facts.compile_time_constants(),
target_triple: &cross_module.target_triple,
Expand Down
7 changes: 4 additions & 3 deletions crates/perry-codegen/src/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,9 +286,9 @@ pub(crate) use helpers::{
};
pub use opts::{
namespace_member_class_key, namespace_member_func_key, namespace_member_var_key, AppMetadata,
CompileOptions, ExportedObjectLiteralCapability, FpContractMode, ImportedClass,
ImportedObjectLiteral, ImportedObjectLiteralMethod, NamespaceEntry, NamespaceEntryKind,
ObjectLiteralMethodCandidate, ShortSpreadMethodCandidate,
ClassAccessorNames, CompileOptions, ExportedObjectLiteralCapability, FpContractMode,
ImportedClass, ImportedObjectLiteral, ImportedObjectLiteralMethod, NamespaceEntry,
NamespaceEntryKind, ObjectLiteralMethodCandidate, ShortSpreadMethodCandidate,
};
pub(crate) use opts::{CrossModuleCtx, ImportedCtor};
pub(crate) use param_guard::scalar_descriptor_rep;
Expand Down Expand Up @@ -2609,6 +2609,7 @@ fn compile_module_impl(
namespace_member_origin_names: opts.namespace_member_origin_names,
imported_async_funcs: opts.imported_async_funcs,
short_spread_method_candidates: Arc::clone(&opts.short_spread_method_candidates),
program_class_accessor_names: opts.program_class_accessor_names.clone(),
object_literal_method_candidates: Arc::clone(&opts.object_literal_method_candidates),
local_async_funcs,
local_generator_funcs,
Expand Down
1 change: 1 addition & 0 deletions crates/perry-codegen/src/codegen/number_exactness_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ fn ir_opts() -> CompileOptions {
constructor_param_counts: Default::default(),
imported_classes: Vec::new(),
short_spread_method_candidates: std::sync::Arc::default(),
program_class_accessor_names: Default::default(),
object_literal_method_candidates: std::sync::Arc::default(),
imported_enums: Vec::new(),
imported_async_funcs: std::collections::HashSet::new(),
Expand Down
72 changes: 72 additions & 0 deletions crates/perry-codegen/src/codegen/opts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,71 @@ pub fn namespace_member_func_key(namespace: &str, member: &str) -> String {
format!("\0perry_namespace_func\0{namespace}\0{member}")
}

/// The property names the program's compiled classes declare as accessors
/// (`get k()` / `set k(v)`), across every module (#10498).
///
/// A store site's class-setter arm can only ever take an entry for a name some
/// compiled class declares as a setter: the runtime admits an entry only when
/// the receiver's class chain declares the accessor
/// (`class_chain_has_instance_accessor`). A store whose name no class declares
/// therefore emits no arm; it misses to the runtime as before the arm existed,
/// which still asks the same entry first. The getters are collected alongside.
#[derive(Debug, Clone, Default)]
pub struct ClassAccessorNames {
getters: std::collections::HashSet<String>,
setters: std::collections::HashSet<String>,
}

impl ClassAccessorNames {
/// The accessor names declared by the classes of `modules` (instance and
/// static alike: a superset only costs an arm).
pub fn collect<'m>(modules: impl IntoIterator<Item = &'m perry_hir::Module>) -> Self {
let mut names = Self::default();
for module in modules {
for class in &module.classes {
names
.getters
.extend(class.getters.iter().map(|(name, _)| name.clone()));
names
.setters
.extend(class.setters.iter().map(|(name, _)| name.clone()));
}
}
names
}

/// The names given, for a caller that already knows them.
pub fn from_names<G, S>(getters: G, setters: S) -> Self
where
G: IntoIterator<Item = String>,
S: IntoIterator<Item = String>,
{
Self {
getters: getters.into_iter().collect(),
setters: setters.into_iter().collect(),
}
}

/// May a compiled class declare a getter named `name`?
pub fn may_get(&self, name: &str) -> bool {
self.getters.contains(name)
}

/// May a compiled class declare a setter named `name`?
pub fn may_set(&self, name: &str) -> bool {
self.setters.contains(name)
}

/// A stable rendering for the object-cache key.
pub fn cache_key(&self) -> String {
let mut getters: Vec<&str> = self.getters.iter().map(String::as_str).collect();
let mut setters: Vec<&str> = self.setters.iter().map(String::as_str).collect();
getters.sort_unstable();
setters.sort_unstable();
format!("get:{}|set:{}", getters.join(","), setters.join(","))
}
}

/// Options controlling code generation for a single module.
#[derive(Debug, Clone, Default)]
pub struct CompileOptions {
Expand Down Expand Up @@ -296,6 +361,11 @@ pub struct CompileOptions {
/// reverse-flow metadata: the calling module need not import the producer.
pub object_literal_method_candidates:
std::sync::Arc<std::collections::HashMap<String, Vec<ObjectLiteralMethodCandidate>>>,
/// The whole program's class accessor names ([`ClassAccessorNames`]),
/// which decide where the class-setter arms are emitted. `None` (a
/// standalone or test compile that did not collect them) emits the arm
/// at every store site.
pub program_class_accessor_names: Option<std::sync::Arc<ClassAccessorNames>>,
/// Imported enum member lists, keyed by the local name under which
/// the enum is visible in this module.
pub imported_enums: Vec<(String, Vec<(String, perry_hir::EnumValue)>)>,
Expand Down Expand Up @@ -859,6 +929,8 @@ pub(crate) struct CrossModuleCtx {
std::sync::Arc<std::collections::HashMap<String, Vec<ShortSpreadMethodCandidate>>>,
pub object_literal_method_candidates:
std::sync::Arc<std::collections::HashMap<String, Vec<ObjectLiteralMethodCandidate>>>,
/// See `CompileOptions::program_class_accessor_names`.
pub program_class_accessor_names: Option<std::sync::Arc<ClassAccessorNames>>,
/// FuncIds of locally-defined async functions in this module. Populated
/// from `hir.functions.is_async`. Used by `is_promise_expr` to refine
/// `let p = asyncFn();` to `Promise(_)` so subsequent `p.then(cb)`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ fn ir_opts(is_entry: bool) -> CompileOptions {
constructor_param_counts: Default::default(),
imported_classes: Vec::new(),
short_spread_method_candidates: std::sync::Arc::default(),
program_class_accessor_names: Default::default(),
object_literal_method_candidates: std::sync::Arc::default(),
imported_enums: Vec::new(),
imported_async_funcs: std::collections::HashSet::new(),
Expand Down
1 change: 1 addition & 0 deletions crates/perry-codegen/src/expr/array_push_guard_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ fn ir_opts() -> CompileOptions {
constructor_param_counts: Default::default(),
imported_classes: Vec::new(),
short_spread_method_candidates: std::sync::Arc::default(),
program_class_accessor_names: Default::default(),
object_literal_method_candidates: std::sync::Arc::default(),
imported_enums: Vec::new(),
imported_async_funcs: std::collections::HashSet::new(),
Expand Down
1 change: 1 addition & 0 deletions crates/perry-codegen/src/expr/call_spread_short_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ fn emit_reverse_dependency_consumer() -> String {
let opts = crate::CompileOptions {
emit_ir_only: true,
short_spread_method_candidates: std::sync::Arc::new(by_method),
program_class_accessor_names: Default::default(),
object_literal_method_candidates: std::sync::Arc::default(),
..Default::default()
};
Expand Down
1 change: 1 addition & 0 deletions crates/perry-codegen/src/expr/class_field_barrier_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ pub(super) fn ir_opts() -> CompileOptions {
constructor_param_counts: Default::default(),
imported_classes: Vec::new(),
short_spread_method_candidates: std::sync::Arc::default(),
program_class_accessor_names: Default::default(),
object_literal_method_candidates: std::sync::Arc::default(),
imported_enums: Vec::new(),
imported_async_funcs: std::collections::HashSet::new(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ fn ir_opts() -> CompileOptions {
constructor_param_counts: Default::default(),
imported_classes: Vec::new(),
short_spread_method_candidates: std::sync::Arc::default(),
program_class_accessor_names: Default::default(),
object_literal_method_candidates: std::sync::Arc::default(),
imported_enums: Vec::new(),
imported_async_funcs: std::collections::HashSet::new(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ fn ir_opts() -> CompileOptions {
constructor_param_counts: Default::default(),
imported_classes: Vec::new(),
short_spread_method_candidates: std::sync::Arc::default(),
program_class_accessor_names: Default::default(),
object_literal_method_candidates: std::sync::Arc::default(),
imported_enums: Vec::new(),
imported_async_funcs: std::collections::HashSet::new(),
Expand Down
10 changes: 10 additions & 0 deletions crates/perry-codegen/src/expr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -871,6 +871,9 @@ pub(crate) struct FnCtx<'a> {
/// calls. See `CompileOptions::object_literal_method_candidates`.
pub object_literal_method_candidates:
&'a std::collections::HashMap<String, Vec<crate::ObjectLiteralMethodCandidate>>,
/// The whole program's class accessor names. See
/// `CompileOptions::program_class_accessor_names`.
pub program_class_accessor_names: Option<&'a crate::ClassAccessorNames>,
/// FFI manifest: `name -> (params, return)` from `package.json`
/// `nativeLibrary.functions`. Descriptors use the shared native-library
/// ABI vocabulary. `lower_call` consults
Expand Down Expand Up @@ -2675,6 +2678,13 @@ mod inline_cache_name_tests {
}

impl<'a> FnCtx<'a> {
/// May some compiled class of the program declare a setter named `name`?
/// Where this is false a store site emits no class-setter arm.
pub(crate) fn program_may_declare_setter(&self, name: &str) -> bool {
self.program_class_accessor_names
.is_none_or(|names| names.may_set(name))
}

/// Is `e` the `this` of a STATIC class member — i.e. a receiver that holds
/// the class CONSTRUCTOR (an INT32 class ref) rather than an instance?
///
Expand Down
47 changes: 47 additions & 0 deletions crates/perry-codegen/src/expr/property_get/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ fn ir_opts(debug_locations: bool, module_source: Option<&str>) -> CompileOptions
constructor_param_counts: Default::default(),
imported_classes: Vec::new(),
short_spread_method_candidates: std::sync::Arc::default(),
program_class_accessor_names: Default::default(),
object_literal_method_candidates: std::sync::Arc::default(),
imported_enums: Vec::new(),
imported_async_funcs: std::collections::HashSet::new(),
Expand Down Expand Up @@ -1832,3 +1833,49 @@ fn the_generic_slow_read_is_called_only_after_the_front_declines() {

#[path = "array_length_tests.rs"]
mod array_length;

/// The #10498 class-setter arm only where a compiled class of the program
/// declares a setter of the store's name: the runtime admits an entry only for
/// a declared accessor (`class_chain_has_instance_accessor`), so any other
/// site's arm is code that can never be taken and work on every miss. The
/// read site's getter arm is not gated.
#[test]
fn class_setter_arms_are_emitted_only_for_declared_setter_names() {
use crate::ClassAccessorNames;
fn module_storing(property: &str) -> Module {
let mut m = module_reading(property);
m.init.push(Stmt::Expr(Expr::PropertySet {
object: Box::new(Expr::LocalGet(1)),
property: property.to_string(),
value: Box::new(Expr::Number(1.0)),
}));
m
}
let emit = |names: Option<ClassAccessorNames>| {
let mut opts = ir_opts(false, None);
opts.program_class_accessor_names = names.map(std::sync::Arc::new);
String::from_utf8(compile_module(&module_storing("price"), opts).unwrap())
.expect("LLVM IR should be UTF-8")
};
let read_arm = "pic.acc.empty";
let store_arm = "put.pic.acc";
// Names not collected (a standalone compile): the store keeps its arm.
let unknown = emit(None);
assert!(unknown.contains(store_arm), "{unknown}");
// No class declares a setter `price` (a getter alone does not count).
let getter_only = emit(Some(ClassAccessorNames::from_names(
["price".to_string()],
["total".to_string()],
)));
assert!(!getter_only.contains(store_arm), "{getter_only}");
assert!(
getter_only.contains(read_arm),
"the read arm is not gated:\n{getter_only}"
);
// A declared setter keeps the store arm.
let setter = emit(Some(ClassAccessorNames::from_names(
Vec::new(),
["price".to_string()],
)));
assert!(setter.contains(store_arm), "{setter}");
}
7 changes: 5 additions & 2 deletions crates/perry-codegen/src/expr/put_value_store_ic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,8 +313,11 @@ pub(crate) fn emit_static_store_ic(
// #10498: a store to a key the receiver inherits as a compiled class
// setter calls it inline (`setter_arm`), ahead of the key-add memo and the
// ways. 64-bit targets only: the entry is a record of 8-byte words.
let setter_entry = setter_arm_target(ctx.target_triple)
.then(|| ctx.new_block(&format!("{STORE_IC_STEM}.acc")));
// Only for a name some compiled class of the program declares as a setter:
// no other site can ever take an entry.
let setter_entry = (setter_arm_target(ctx.target_triple)
&& ctx.program_may_declare_setter(property))
.then(|| ctx.new_block(&format!("{STORE_IC_STEM}.acc")));
let shape_miss = setter_entry
.map(|idx| ctx.block_label(idx))
.unwrap_or_else(|| add_label.clone());
Expand Down
4 changes: 2 additions & 2 deletions crates/perry-codegen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@ pub use codegen::{
decode_static_seed, encode_static_seed, module_birth_shapes, namespace_member_class_key,
namespace_member_func_key, namespace_member_var_key, resolve_target_triple,
short_spread_method_capabilities, take_module_static_seeds, user_function_symbol, AppMetadata,
BirthProto, BirthShape, CompileOptions, ConstFnBirth, ConstructorContracts, CtorAbi,
DefinedClassShape, ExportedObjectLiteralCapability, FpContractMode, ImportedClass,
BirthProto, BirthShape, ClassAccessorNames, CompileOptions, ConstFnBirth, ConstructorContracts,
CtorAbi, DefinedClassShape, ExportedObjectLiteralCapability, FpContractMode, ImportedClass,
ImportedObjectLiteral, ImportedObjectLiteralMethod, ModuleBirth, NamespaceEntry,
NamespaceEntryKind, ObjectLiteralMethodCandidate, ProgramClassShapeIds,
ResolvedConstructorContracts, ShortSpreadMethodCandidate, TypedMasks, STATIC_SEED_FORMAT,
Expand Down
1 change: 1 addition & 0 deletions crates/perry-codegen/src/lower_call/alloc_hot_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ fn ir_opts() -> CompileOptions {
constructor_param_counts: Default::default(),
imported_classes: Vec::new(),
short_spread_method_candidates: std::sync::Arc::default(),
program_class_accessor_names: Default::default(),
object_literal_method_candidates: std::sync::Arc::default(),
imported_enums: Vec::new(),
imported_async_funcs: std::collections::HashSet::new(),
Expand Down
Loading
Loading