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
26 changes: 10 additions & 16 deletions crates/perry-codegen/src/expr/property_get.rs
Original file line number Diff line number Diff line change
Expand Up @@ -452,23 +452,17 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {
let i32_v = blk.call(I32, "js_url_search_params_size", &[(I64, &recv_handle)]);
Ok(blk.sitofp(I32, &i32_v, DOUBLE))
}

// `arr[i] = v` — typed-Number array element write.
//
// INLINE FAST PATH:
//
// load length from arr_ptr+0
// if idx < length: inline store, done
// else if idx < capacity: inline store + bump length, done
// else: call js_array_set_f64_extend (slow realloc path)
//
// The ArrayHeader layout is `{ length: u32, capacity: u32, ... }`
// (8 bytes), followed by `[f64; N]` elements at offset 8.
//
// For non-LocalGet receivers we still use bounds-checked
// `js_array_set_f64` (no return value, no realloc) since there's
// no local to write a possibly-realloc'd pointer back to.
Expr::PropertyGet { object, property } => {
if property == "prototype"
&& matches!(object.as_ref(), Expr::FuncRef(_) | Expr::Closure { .. })
{
let func_value = lower_expr(ctx, object)?;
return Ok(ctx.block().call(
DOUBLE,
"js_function_prototype_value_for_read",
&[(DOUBLE, &func_value)],
));
}
if let Some((builtin_name, method_name)) =
builtin_prototype_method_read(object, property)
{
Expand Down
1 change: 1 addition & 0 deletions crates/perry-codegen/src/runtime_decls/strings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1101,6 +1101,7 @@ pub fn declare_phase_b_strings(module: &mut LlModule) {
DOUBLE,
&[DOUBLE, PTR, I64],
);
module.declare_function("js_function_prototype_value_for_read", DOUBLE, &[DOUBLE]);
module.declare_function("js_typeerror_new", I64, &[I64]);
module.declare_function("js_rangeerror_new", I64, &[I64]);
module.declare_function("js_syntaxerror_new", I64, &[I64]);
Expand Down
31 changes: 31 additions & 0 deletions crates/perry-runtime/src/object/class_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1906,6 +1906,37 @@ pub(crate) fn ordinary_function_prototype_value_for_read(func_value: f64) -> Opt
Some(crate::value::js_nanbox_pointer(proto as i64))
}

#[no_mangle]
pub extern "C" fn js_function_prototype_value_for_read(func_value: f64) -> f64 {
let undef = f64::from_bits(crate::value::TAG_UNDEFINED);
let jv = crate::value::JSValue::from_bits(func_value.to_bits());
if !jv.is_pointer() {
return undef;
}
let ptr = jv.as_pointer() as *const crate::closure::ClosureHeader;
if ptr.is_null() || !is_valid_obj_ptr(ptr as *const u8) {
return undef;
}
unsafe {
if (*ptr).type_tag != crate::closure::CLOSURE_MAGIC {
return undef;
}
}

let closure_addr = ptr as usize;
if crate::closure::closure_is_key_deleted(closure_addr, "prototype") {
return undef;
}
let dynamic = crate::closure::closure_get_dynamic_prop(closure_addr, "prototype");
if dynamic.to_bits() != crate::value::TAG_UNDEFINED {
return dynamic;
}
if let Some(proto) = generator_function_prototype_of(closure_addr) {
return proto;
}
ordinary_function_prototype_value_for_read(func_value).unwrap_or(undef)
}

/// Lookup helper: returns the registered prototype-method value for
/// `(class_id, name)`, or None if no assignment matched. Walks the
/// parent-class chain so methods registered on a base class are found
Expand Down
33 changes: 33 additions & 0 deletions test-parity/node-suite/object/function-prototype-value.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
function show(label: string, value: unknown) {
console.log(label, JSON.stringify(value));
}

function Foo() {}

const fooProto: any = (Foo as any).prototype;
show("function prototype object", [
typeof fooProto,
fooProto !== null,
fooProto.constructor === Foo,
(Foo as any).prototype === fooProto,
]);

const child: any = Object.create((Foo as any).prototype);
show("object create accepts function prototype", [
typeof child,
child !== null,
]);

function Bar() {}
(Bar as any).prototype = Object.create((Foo as any).prototype);
(Bar as any).prototype.constructor = Bar;

const barProto: any = (Bar as any).prototype;
show("assigned function prototype object", [
typeof barProto,
barProto !== null,
(Bar as any).prototype === barProto,
]);

const arrow = () => {};
show("arrow prototype missing", (arrow as any).prototype === undefined);