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
25 changes: 22 additions & 3 deletions crates/perry-codegen/src/codegen/string_pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::strings::StringPool;
use crate::types::{DOUBLE, I32, I64, PTR, VOID};

use super::helpers::{sanitize, sanitize_member, scoped_static_method_name};
use super::spec_function_length;

/// Emits a long sequence of INDEPENDENT init operations (string allocation,
/// closure/class/function registration) into a series of small chunk functions
Expand Down Expand Up @@ -963,7 +964,15 @@ pub(super) fn emit_string_pool(
// getter-pairs loop above; emission mangling matches the
// setter-method-emission path at codegen.rs:2041 (renamed.name =
// "__set_<prop>" → LLVM symbol perry_method_<mp>__<class>____set_<f.name>).
let mut setter_pairs: Vec<(u32, String, String, bool)> = Vec::new();
// (cid, prop, llvm_name, is_static, spec_length). `spec_length` is the
// ECMAScript-visible `.length` of the setter function value read via
// `Object.getOwnPropertyDescriptor(proto, prop).set` — leading formal
// params before the first default/rest. A setter always has one formal
// param, but `set m(x = 42)` has `.length === 0` (test262
// class/setter-length-dflt): without a per-func-ptr length registration
// the runtime fell back to the setter's ABI arity (1), over-counting the
// defaulted param.
let mut setter_pairs: Vec<(u32, String, String, bool, u32)> = Vec::new();
for (class_name, class) in classes.iter() {
if *class_name != class.name {
continue;
Expand Down Expand Up @@ -999,11 +1008,12 @@ pub(super) fn emit_string_pool(
sanitize_member(&inner),
)
};
setter_pairs.push((cid, prop.clone(), llvm_name, is_static));
let spec_length = spec_function_length(&setter_fn.params) as u32;
setter_pairs.push((cid, prop.clone(), llvm_name, is_static, spec_length));
}
}
setter_pairs.sort_unstable();
for (cid, prop_name, llvm_name, is_static) in setter_pairs {
for (cid, prop_name, llvm_name, is_static, spec_length) in setter_pairs {
chunker.roll_if_full();
let blk = chunker.current_block();
let entry = match strings.iter().find(|e| e.value == prop_name) {
Expand All @@ -1015,6 +1025,15 @@ pub(super) fn emit_string_pool(
let func_ref = format!("@{}", llvm_name);
let func_i64 = blk.ptrtoint(&func_ref, I64);
let bytes_i64 = blk.ptrtoint(&bytes_global, I64);
// Register the setter's spec `.length` keyed by its func_ptr so
// `Object.getOwnPropertyDescriptor(proto, prop).set.length` reports
// the default-aware count instead of the raw ABI arity. Static
// accessors are emitted under a no-`this` `perry_static_…` symbol, so
// the same func_ptr is what a value-read of `.set` binds.
blk.call_void(
"js_register_closure_length",
&[(PTR, &func_ref), (I32, &spec_length.to_string())],
);
let register_fn = if is_static {
"js_register_class_static_setter"
} else {
Expand Down
14 changes: 10 additions & 4 deletions crates/perry-runtime/src/object/class_registry/registration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,10 +161,16 @@ pub(crate) fn class_accessor_function_value(
return f64::from_bits(crate::value::TAG_UNDEFINED);
}
unsafe { crate::closure::js_closure_set_capture_ptr(closure, 0, raw_ptr as i64) };
super::super::native_module::set_builtin_closure_length(
closure as usize,
if is_setter { 1 } else { 0 },
);
// Spec `.length`: params before the first default/rest. A getter takes no
// params (0); a setter takes exactly one formal param — but `set m(x = 42)`
// has `.length === 0` (defaults don't count). Codegen registers the raw
// accessor func_ptr's default-aware spec length via
// `js_register_closure_length`; consult it so a defaulted setter reports 0.
// Fall back to the fixed 1/0 when no registration exists (e.g. native or
// cross-module accessors whose length wasn't emitted).
let spec_length = crate::closure::lookup_closure_length(raw_ptr as *const u8)
.unwrap_or(if is_setter { 1 } else { 0 });
super::super::native_module::set_builtin_closure_length(closure as usize, spec_length);
super::super::native_module::set_builtin_closure_non_constructable(closure as usize);
// Spec `.name` = "get <key>" / "set <key>" with attributes
// { writable: false, enumerable: false, configurable: true } (mirrors the
Expand Down
Loading