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
13 changes: 13 additions & 0 deletions changelog.d/11823-fresh-class-followups.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Fixed three conversion and reflection gaps in classes created per evaluation
(class expressions in functions, and declarations whose evaluation has its own
environment), and moved their shape memo out of a table.

`String(C)` on such a class now returns the class source after `delete
C.toString` removed a static `toString` the class declared, instead of
`[object Function]`. An own `toString` set to a non-callable value such as
`undefined` makes `String(C)` throw a `TypeError` again, as in node, instead of
returning the class source. `Object.getOwnPropertyNames(C)` lists integer keys
such as a static method named `0` before `prototype`, as in node.

What a class remembers to build later evaluations directly in its shapes now
lives in the class's own record instead of a table keyed by class.
20 changes: 20 additions & 0 deletions crates/perry-codegen/src/codegen/fresh_class_templates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,26 @@
use perry_hir::{Expr, Stmt};
use std::collections::HashSet;

/// The template cell of per-evaluation template `cid`: the template's own
/// record, an internal `[N x i64]` the module's string-pool initializer
/// defines and registers (`js_register_class_template_cell`) and the
/// evaluation site passes to `js_class_evaluation_object` and
/// `js_class_object_set_ctor_caps`. Codegen writes only its length in words
/// (word 0); the runtime owns the rest of its layout
/// (`class_object_template::TemplateCell`).
pub(crate) fn template_cell_global(cid: u32) -> String {
format!("perry_ctpl.{cid}")
}

/// How many words template cell of `class` gets: the runtime's fixed words
/// plus two per slot of the class object's and the prototype's final shapes
/// (`length`, `name`, one per static method and the pinned parent;
/// `constructor` and one per method). The runtime checks every record against
/// the length, so a short cell only declines to memoize.
pub(crate) fn template_cell_words(class: Option<&perry_hir::Class>) -> usize {
32 + 2 * (4 + class.map_or(16, |c| c.static_methods.len() + c.methods.len()))
}

/// Every `ClassExprFresh` template named anywhere in `hir`: module init,
/// function bodies, class members and nested closures.
pub(crate) fn fresh_class_templates(hir: &perry_hir::Module) -> HashSet<String> {
Expand Down
2 changes: 1 addition & 1 deletion crates/perry-codegen/src/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ mod emission_order_tests;
mod entry;
pub mod entry_outline;
mod export_value_wrappers;
mod fresh_class_templates;
pub(crate) mod fresh_class_templates;
pub(crate) mod func_registry;
mod function;
mod function_source_header;
Expand Down
28 changes: 28 additions & 0 deletions crates/perry-codegen/src/codegen/string_pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1146,6 +1146,34 @@ pub(super) fn emit_string_pool(
.iter()
.filter_map(|name| class_ids.get(name).copied())
.collect();
// Each per-evaluation template's own record, its template cell
// (`fresh_class_templates::template_cell_global`), registered on the
// template's vtable entry for the runtime paths that start from one of its
// class objects.
let mut fresh_cells: Vec<(u32, usize)> = fresh_class_templates
.iter()
.filter_map(|name| {
let cid = *class_ids.get(name)?;
let words =
super::fresh_class_templates::template_cell_words(classes.get(name).copied());
Some((cid, words))
})
.collect();
fresh_cells.sort_unstable();
fresh_cells.dedup_by_key(|cell| cell.0);
for (cid, words) in fresh_cells {
let global = super::fresh_class_templates::template_cell_global(cid);
chunker.module().add_raw_global(format!(
"@{global} = internal global [{words} x i64] [i64 {words}{}]",
", i64 0".repeat(words - 1)
));
let blk = chunker.current_block();
let cell_i64 = blk.ptrtoint(&format!("@{global}"), I64);
blk.call_void(
"js_register_class_template_cell",
&[(I64, &cid.to_string()), (I64, &cell_i64)],
);
}
method_triples.sort_unstable();
for (
cid,
Expand Down
14 changes: 13 additions & 1 deletion crates/perry-codegen/src/expr/static_field_meta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,17 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {
} => {
let template_cid = ctx.class_ids.get(template).copied().unwrap_or(0);
let tcid_str = template_cid.to_string();
// The template's own record (its template cell), which the
// module's string-pool initializer defines for every template it
// evaluates.
let cell = if template_cid == 0 {
"null".to_string()
} else {
format!(
"@{}",
crate::codegen::fresh_class_templates::template_cell_global(template_cid)
)
};
// Room for the evaluation's own `length`, `name` and static
// methods, its pinned parent, its captured environment and its
// prototype object besides its static fields, so the template's
Expand Down Expand Up @@ -645,6 +656,7 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {
(I32, &tcid_str),
(I32, &nfields),
(I32, &field_mask.to_string()),
(PTR, &cell),
],
);
// #7154: the fresh class object is a raw SSA register while the
Expand Down Expand Up @@ -782,7 +794,7 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {
// the template's final shape (`class_object_template`).
ctx.block().call_void(
"js_class_object_set_ctor_caps",
&[(I64, &obj), (DOUBLE, &caps_box)],
&[(I64, &obj), (DOUBLE, &caps_box), (PTR, &cell)],
);
// A guarded class environment learns this evaluation; the
// first one publishes its captures into the slots.
Expand Down
2 changes: 2 additions & 0 deletions crates/perry-codegen/src/gc_effects/linux-x86_64.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -2755,6 +2755,7 @@ js_register_class_static_method_bind_length Leaf
js_register_class_static_method_entry Reenters
js_register_class_static_setter Reenters
js_register_class_string_member_order Leaf
js_register_class_template_cell Leaf
js_register_class_to_string_tag Leaf
js_register_embedded_asset Leaf
js_register_embedded_text_asset Leaf
Expand Down Expand Up @@ -2801,6 +2802,7 @@ js_register_path_init Reenters
js_register_path_module Reenters
js_register_path_module_partial Reenters
js_register_prototype_method Reenters
js_register_readable_handle_predicate Leaf
js_register_set_text_handler Reenters
js_register_show_toast_handler Reenters
js_register_stdin_flow_ops Leaf
Expand Down
7 changes: 6 additions & 1 deletion crates/perry-codegen/src/gc_effects/macos-aarch64.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -594,7 +594,7 @@ js_class_lexical_binding_get Reenters
js_class_lexical_binding_set Reenters
js_class_method_bind Reenters
js_class_method_bind_by_id Reenters
js_class_method_entry_enter_home Reenters
js_class_method_entry_enter_home ThrowOnly
js_class_method_entry_leave Leaf
js_class_method_snapshot_bind Reenters
js_class_object_refresh_capture_values Reenters
Expand Down Expand Up @@ -2110,6 +2110,8 @@ js_object_delete_field Reenters
js_object_delete_field_value Reenters
js_object_entries Reenters
js_object_entries_value Reenters
js_object_final_shape_id_for_class_keys_static_constfn Reenters
js_object_finalize_constfn_static Reenters
js_object_free Leaf
js_object_freeze Reenters
js_object_from_entries Reenters
Expand Down Expand Up @@ -2753,6 +2755,7 @@ js_register_class_static_method_bind_length Reenters
js_register_class_static_method_entry Reenters
js_register_class_static_setter Reenters
js_register_class_string_member_order Leaf
js_register_class_template_cell Leaf
js_register_class_to_string_tag Reenters
js_register_embedded_asset Leaf
js_register_embedded_text_asset Leaf
Expand Down Expand Up @@ -2799,6 +2802,7 @@ js_register_path_init Leaf
js_register_path_module ThrowOnly
js_register_path_module_partial ThrowOnly
js_register_prototype_method Reenters
js_register_readable_handle_predicate Leaf
js_register_set_text_handler Reenters
js_register_show_toast_handler Reenters
js_register_stdin_flow_ops Leaf
Expand Down Expand Up @@ -2996,6 +3000,7 @@ js_shape_ordinary_inline_slot_for_key Reenters
js_shape_register_static_seed Leaf
js_shape_run_static_seed Reenters
js_shape_seed_plain Reenters
js_shape_seed_plain_constfn Reenters
js_shared_array_buffer_new ThrowOnly
js_shared_array_buffer_new_value Reenters
js_sharp_blur Reenters
Expand Down
5 changes: 5 additions & 0 deletions crates/perry-codegen/src/gc_effects/windows-x86_64.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -2110,6 +2110,8 @@ js_object_delete_field Reenters
js_object_delete_field_value Reenters
js_object_entries Reenters
js_object_entries_value Reenters
js_object_final_shape_id_for_class_keys_static_constfn Reenters
js_object_finalize_constfn_static Reenters
js_object_free Leaf
js_object_freeze Reenters
js_object_from_entries Reenters
Expand Down Expand Up @@ -2753,6 +2755,7 @@ js_register_class_static_method_bind_length Leaf
js_register_class_static_method_entry Reenters
js_register_class_static_setter Reenters
js_register_class_string_member_order Leaf
js_register_class_template_cell Leaf
js_register_class_to_string_tag Leaf
js_register_embedded_asset Leaf
js_register_embedded_text_asset Leaf
Expand Down Expand Up @@ -2799,6 +2802,7 @@ js_register_path_init Leaf
js_register_path_module Reenters
js_register_path_module_partial Reenters
js_register_prototype_method Reenters
js_register_readable_handle_predicate Leaf
js_register_set_text_handler Reenters
js_register_show_toast_handler Reenters
js_register_stdin_flow_ops Leaf
Expand Down Expand Up @@ -2996,6 +3000,7 @@ js_shape_ordinary_inline_slot_for_key Reenters
js_shape_register_static_seed Leaf
js_shape_run_static_seed Reenters
js_shape_seed_plain Reenters
js_shape_seed_plain_constfn Reenters
js_shared_array_buffer_new Reenters
js_shared_array_buffer_new_value Reenters
js_sharp_blur Reenters
Expand Down
4 changes: 2 additions & 2 deletions crates/perry-codegen/src/runtime_decls/objects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,8 @@ pub fn declare_phase_b_objects(module: &mut LlModule) {
// new/instanceof read class_id from it.
module.declare_function("js_object_mark_class", VOID, &[I64]);
// #6438: pin a per-evaluation class object's own parent edge.
module.declare_function("js_class_evaluation_object", I64, &[I32, I32, I32]);
module.declare_function("js_class_object_set_ctor_caps", VOID, &[I64, DOUBLE]);
module.declare_function("js_class_evaluation_object", I64, &[I32, I32, I32, PTR]);
module.declare_function("js_class_object_set_ctor_caps", VOID, &[I64, DOUBLE, PTR]);
// Shape-cache-aware variant: pre-populates keys_array via SHAPE_INLINE_CACHE,
// so subsequent field stores can use index-based set_field (skipping the
// per-call linear key-search done by js_object_set_field_by_name).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,7 @@ pub(crate) fn declare_core(module: &mut LlModule) {
VOID,
&[I64, I64, I64, I64],
);
module.declare_function("js_register_class_template_cell", VOID, &[I64, I64]);
module.declare_function("js_static_method_entry_leave", VOID, &[]);
module.declare_function(
"js_class_static_call_guard",
Expand Down
5 changes: 3 additions & 2 deletions crates/perry-codegen/src/wasm32/runtime_abi.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,7 @@ js_class_env_register_slot void i32u,i32u,ptr
js_class_env_register_state void i32u,ptr
js_class_env_set void f64,i32u,i32u,f64
js_class_env_stamp f64 f64,i32u,f64
js_class_evaluation_object i64 i32u,i32u,i32u
js_class_evaluation_object i64 i32u,i32u,i32u,ptr
js_class_field_add f64 f64,f64,f64
js_class_field_get_ic f64 i64,f64,i32u,i32u,ptr,i32u,i32s,ptr
js_class_field_get_ic_fast f64 i64,f64,i32u,i32u,ptr,i32u,i32s,ptr
Expand All @@ -606,7 +606,7 @@ js_class_method_entry_enter_home i64 i32u,i64,i64
js_class_method_entry_leave void i64
js_class_method_snapshot_bind f64 f64,ptr,usize
js_class_object_refresh_capture_values void f64,i64,f64
js_class_object_set_ctor_caps void i64,f64
js_class_object_set_ctor_caps void i64,f64,ptr
js_class_prototype_method_value f64 f64,f64
js_class_register_capture_values void i32u,ptr,usize
js_class_register_static_field void i32u,ptr,usize,f64,ptr
Expand Down Expand Up @@ -3232,6 +3232,7 @@ js_register_class_static_method_bind_length void i64,ptr,i64,i64
js_register_class_static_method_entry void i64,ptr,i64,i64
js_register_class_static_setter void i64,ptr,i64,i64,i32s
js_register_class_string_member_order void i64,ptr,i64,i64,i64
js_register_class_template_cell void i64,i64
js_register_class_to_string_tag void i32u,i64
js_register_embedded_asset void ptr,usize,ptr,usize
js_register_embedded_text_asset void ptr,usize,ptr,usize
Expand Down
25 changes: 13 additions & 12 deletions crates/perry-runtime/src/object/class_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,11 @@ pub(crate) use state::{
class_prototype_method_value_cache_root_store, class_prototype_object_addr_index_contains,
class_prototype_object_addr_index_rekey, class_prototype_object_root_store,
class_ref_dynamic_prop_root_store, class_register_declared_static_global_slot,
class_static_alias_sync, class_static_clear_defined_attrs, class_static_defined_attrs,
class_static_key_deleted, class_static_prototype, class_static_prototype_is_nulled,
class_static_prototype_root_clear, class_static_prototype_root_store,
class_static_set_defined_attrs, decl_prototype_identity_id, global_object_prototype_bits,
class_set_template_cell, class_static_alias_sync, class_static_clear_defined_attrs,
class_static_defined_attrs, class_static_key_deleted, class_static_prototype,
class_static_prototype_is_nulled, class_static_prototype_root_clear,
class_static_prototype_root_store, class_static_set_defined_attrs, class_template_cell,
decl_prototype_identity_id, global_object_prototype_bits,
is_bound_native_constructor_closure_value, is_non_constructable_builtin_function_value,
parent_closure_in_chain, template_has_class_objects, throw_non_constructable_builtin_function,
CLASS_OBJECT_EVER,
Expand Down Expand Up @@ -230,14 +231,14 @@ pub(crate) use parent_static::{
class_dynamic_static_accessor_getter_value, class_has_instance_getter,
class_has_own_static_method, class_has_own_symbol_member, class_has_symbol_member_in_chain,
class_instance_setter_apply, class_method_bind_length, class_object_own_field_bytes,
class_object_pinned_parent, class_own_static_method_code, class_own_static_method_entry,
class_own_symbol_accessor_ptrs, class_own_symbol_member_keys, class_own_symbol_method,
class_private_instance_getter_value, class_private_instance_setter_apply,
class_static_accessor_getter_value, class_static_accessor_setter_apply,
class_symbol_getter_value, class_symbol_setter_apply, dynamic_value_class_id,
get_parent_class_id, instance_chain_parent_class_id, lookup_class_symbol_method_in_chain,
lookup_static_method_in_chain, lookup_static_method_owner, register_class,
register_class_dynamic_static_accessor, static_accessor_in_chain,
class_object_owns_key_bytes, class_object_pinned_parent, class_own_static_method_code,
class_own_static_method_entry, class_own_symbol_accessor_ptrs, class_own_symbol_member_keys,
class_own_symbol_method, class_private_instance_getter_value,
class_private_instance_setter_apply, class_static_accessor_getter_value,
class_static_accessor_setter_apply, class_symbol_getter_value, class_symbol_setter_apply,
dynamic_value_class_id, get_parent_class_id, instance_chain_parent_class_id,
lookup_class_symbol_method_in_chain, lookup_static_method_in_chain, lookup_static_method_owner,
register_class, register_class_dynamic_static_accessor, static_accessor_in_chain,
};
pub use parent_static::{
is_class_object_ptr, is_class_object_value, is_registered_class_prototype_object,
Expand Down
28 changes: 28 additions & 0 deletions crates/perry-runtime/src/object/class_registry/parent_static.rs
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,34 @@ pub(crate) fn class_object_own_field_bytes(
None
}

/// Does class object `obj` have an own property `want`, whatever it holds (a
/// data value, `undefined` included, or an accessor)?
pub(crate) fn class_object_owns_key_bytes(
obj: *const crate::object::ObjectHeader,
want: &[u8],
) -> bool {
if obj.is_null()
|| !crate::value::addr_class::is_above_handle_band(obj as usize)
|| !crate::object::is_valid_obj_ptr(obj as *const u8)
{
return false;
}
unsafe {
let keys_view = crate::object::object_keys(obj);
let keys = keys_view.arr();
if keys.is_null() {
return false;
}
let (slots, slot_len) = crate::object::keys_array_dense_slots(keys);
(0..(keys_view.count() as usize).min(slot_len)).any(|i| {
crate::string::js_string_key_matches_bytes(
crate::JSValue::from_bits((*slots.add(i)).to_bits()),
want,
)
})
}
}

/// Read back the parent constructor value stashed at class-definition time by
/// `js_register_class_parent_dynamic` (see `CLASS_DYNAMIC_PARENT_VALUE`).
/// `super()` in a `class X extends <runtime-value>` body uses this so the
Expand Down
23 changes: 23 additions & 0 deletions crates/perry-runtime/src/object/class_registry/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,10 @@ pub struct ClassVTable {
pub methods: HashMap<String, VTableMethodEntry>,
pub accessors: HashMap<String, AccessorDecl>,
pub private_accessors: HashMap<String, AccessorDecl>,
/// The address of the class's template cell (`@perry_ctpl.<cid>`) when
/// the class is a per-evaluation template (`class_object_template`); 0
/// otherwise.
pub template_cell: usize,
}

impl ClassVTable {
Expand Down Expand Up @@ -473,6 +477,25 @@ pub static CLASS_STRING_MEMBER_ORDERS: ImageTable<RwLock<Option<StringMemberOrde
pub static CLASS_METHOD_BIND_LENGTHS: ImageTable<RwLock<Option<HashMap<(u32, String), u32>>>> =
ImageTable::new(|image| &image.method_bind_lengths);

/// Record `cell` as per-evaluation template `class_id`'s template cell.
pub(crate) fn class_set_template_cell(class_id: u32, cell: usize) {
let Ok(mut guard) = CLASS_VTABLE_REGISTRY.write() else {
return;
};
guard
.get_or_insert_with(crate::fast_hash::new_ptr_hash_map)
.entry(class_id)
.or_default()
.template_cell = cell;
}

/// Per-evaluation template `class_id`'s template cell, if one was registered.
pub(crate) fn class_template_cell(class_id: u32) -> Option<usize> {
let guard = CLASS_VTABLE_REGISTRY.read().ok()?;
let cell = guard.as_ref()?.get(&class_id)?.template_cell;
(cell != 0).then_some(cell)
}

/// The closure-convention entry registered for method `name` of per-evaluation
/// class `class_id` (its vtable entry's `entry`), if any.
pub(crate) fn class_method_entry(class_id: u32, name: &str) -> Option<usize> {
Expand Down
9 changes: 5 additions & 4 deletions crates/perry-runtime/src/object/descriptors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1009,9 +1009,10 @@ pub extern "C" fn js_object_get_own_property_names(obj_value: f64) -> f64 {
js_object_get_own_property_names_shape(obj_value)
}

/// `names` (a class object's stored own keys, in creation order) with
/// `prototype` inserted where ClassDefinitionEvaluation created it: after the
/// leading `length` / `name` that are still the object's first keys.
/// `names` (a class object's stored own keys in property order: integer
/// indices first, then strings in creation order) with `prototype` inserted
/// where ClassDefinitionEvaluation created it among the strings: after the
/// leading `length` / `name` that are still the object's first string keys.
fn class_object_names_with_prototype(names: f64) -> f64 {
let scope = crate::gc::RuntimeHandleScope::new();
let names = scope.root_nanbox_f64(names);
Expand All @@ -1032,7 +1033,7 @@ fn class_object_names_with_prototype(names: f64) -> f64 {
}
let at = out
.iter()
.position(|n| n != "length" && n != "name")
.position(|n| n != "length" && n != "name" && property_name_array_index(n).is_none())
.unwrap_or(out.len());
out.insert(at, "prototype".to_string());
let result = crate::array::js_array_alloc(out.len() as u32);
Expand Down
Loading
Loading