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
41 changes: 39 additions & 2 deletions crates/perry-runtime/src/array/iterator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -453,8 +453,36 @@ pub(crate) fn async_from_sync_wrap_iterator(iter: f64) -> f64 {

#[no_mangle]
pub extern "C" fn js_get_async_iterator(value: f64) -> f64 {
if let Some(iter) = call_symbol_async_iterator(value) {
return iter;
// GetIterator(value, async) — ECMA-262 §7.4.3.
//
// Spec ordering matters (test262 yield-star-getiter-async-*): consult
// @@asyncIterator with GetMethod semantics FIRST. A method that is present
// but not callable is a TypeError; a callable method whose result is not an
// Object is a TypeError. Only an ABSENT (undefined/null) @@asyncIterator
// falls back to the sync iterator wrapped via CreateAsyncFromSyncIterator —
// so e.g. `yield* { [Symbol.asyncIterator]() { return undefined } }` throws
// instead of (wrongly) reaching the object's `[Symbol.iterator]`.
let sym = crate::symbol::well_known_symbol("asyncIterator");
if !sym.is_null() {
let sym_f64 = f64::from_bits(crate::value::JSValue::pointer(sym as *const u8).bits());
let method = unsafe { crate::symbol::js_object_get_symbol_property(value, sym_f64) };
let mb = method.to_bits();
if mb != crate::value::TAG_UNDEFINED && mb != crate::value::TAG_NULL {
// @@asyncIterator is present: GetMethod requires it be callable.
if !is_callable_value(method) {
throw_iterator_method_not_callable();
}
let prev_this = crate::object::js_implicit_this_set(value);
let iterator =
unsafe { crate::closure::js_native_call_value(method, std::ptr::null(), 0) };
crate::object::js_implicit_this_set(prev_this);
// GetIterator step 5: the result must be an Object.
if !is_async_iterator_object(iterator) {
throw_iterator_result_not_object();
}
return iterator;
}
// @@asyncIterator absent → fall through to the sync-iterator path.
}

let iter = crate::symbol::js_get_iterator(value);
Expand All @@ -469,6 +497,15 @@ pub extern "C" fn js_get_async_iterator(value: f64) -> f64 {
async_from_sync_wrap_iterator(iter)
}

/// `Type(x) is Object` for the GetIterator(async) result check: heap
/// pointer-tagged values that are not registered Symbols (strings, numbers,
/// booleans, null/undefined, symbols are all NOT objects).
fn is_async_iterator_object(value: f64) -> bool {
let jv = crate::value::JSValue::from_bits(value.to_bits());
jv.is_pointer()
&& !crate::symbol::is_registered_symbol(crate::value::js_nanbox_get_pointer(value) as usize)
}

#[cold]
fn throw_not_iterable(value: f64) -> ! {
let label = if value.to_bits() == crate::value::TAG_NULL {
Expand Down
126 changes: 67 additions & 59 deletions crates/perry-runtime/src/object/class_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3341,8 +3341,14 @@ pub(crate) unsafe fn call_vtable_method(
has_synthetic_arguments: bool,
has_rest: bool,
) -> f64 {
// A missing trailing argument is `undefined` per spec (NOT NaN): default
// parameters lower to a `param === undefined ? <default> : param` check in
// the method prologue, so padding a hole with NaN left the default
// un-applied (`async method(a, b, c = 99)` called via the dynamic vtable
// path — e.g. a detached `C.prototype.method` value — saw `c = NaN`). Pad
// with TAG_UNDEFINED so the prologue's default-check fires.
#[inline(always)]
unsafe fn arg_or_nan(args_ptr: *const f64, args_len: usize, idx: usize) -> f64 {
unsafe fn arg_or_undefined(args_ptr: *const f64, args_len: usize, idx: usize) -> f64 {
if idx < args_len {
*args_ptr.add(idx)
} else {
Expand Down Expand Up @@ -3406,13 +3412,13 @@ pub(crate) unsafe fn call_vtable_method(
crate::array::js_array_set_f64(
raw_args,
slot as u32,
arg_or_nan(args_ptr, args_len, i),
arg_or_undefined(args_ptr, args_len, i),
);
}
let raw_args_value = crate::value::js_nanbox_pointer(raw_args as i64);
let mut args = Vec::with_capacity(param_count as usize);
for i in 0..visible_params {
args.push(arg_or_nan(args_ptr, args_len, i));
args.push(arg_or_undefined(args_ptr, args_len, i));
}
args.push(raw_args_value);
adjusted_args_storage = Some(args);
Expand All @@ -3429,120 +3435,120 @@ pub(crate) unsafe fn call_vtable_method(
}
1 => {
let f: extern "C" fn(f64, f64) -> f64 = std::mem::transmute(func_ptr);
f(this_f64, arg_or_nan(call_args_ptr, call_args_len, 0))
f(this_f64, arg_or_undefined(call_args_ptr, call_args_len, 0))
}
2 => {
let f: extern "C" fn(f64, f64, f64) -> f64 = std::mem::transmute(func_ptr);
f(
this_f64,
arg_or_nan(call_args_ptr, call_args_len, 0),
arg_or_nan(call_args_ptr, call_args_len, 1),
arg_or_undefined(call_args_ptr, call_args_len, 0),
arg_or_undefined(call_args_ptr, call_args_len, 1),
)
}
3 => {
let f: extern "C" fn(f64, f64, f64, f64) -> f64 = std::mem::transmute(func_ptr);
f(
this_f64,
arg_or_nan(call_args_ptr, call_args_len, 0),
arg_or_nan(call_args_ptr, call_args_len, 1),
arg_or_nan(call_args_ptr, call_args_len, 2),
arg_or_undefined(call_args_ptr, call_args_len, 0),
arg_or_undefined(call_args_ptr, call_args_len, 1),
arg_or_undefined(call_args_ptr, call_args_len, 2),
)
}
4 => {
let f: extern "C" fn(f64, f64, f64, f64, f64) -> f64 = std::mem::transmute(func_ptr);
f(
this_f64,
arg_or_nan(call_args_ptr, call_args_len, 0),
arg_or_nan(call_args_ptr, call_args_len, 1),
arg_or_nan(call_args_ptr, call_args_len, 2),
arg_or_nan(call_args_ptr, call_args_len, 3),
arg_or_undefined(call_args_ptr, call_args_len, 0),
arg_or_undefined(call_args_ptr, call_args_len, 1),
arg_or_undefined(call_args_ptr, call_args_len, 2),
arg_or_undefined(call_args_ptr, call_args_len, 3),
)
}
5 => {
let f: extern "C" fn(f64, f64, f64, f64, f64, f64) -> f64 =
std::mem::transmute(func_ptr);
f(
this_f64,
arg_or_nan(call_args_ptr, call_args_len, 0),
arg_or_nan(call_args_ptr, call_args_len, 1),
arg_or_nan(call_args_ptr, call_args_len, 2),
arg_or_nan(call_args_ptr, call_args_len, 3),
arg_or_nan(call_args_ptr, call_args_len, 4),
arg_or_undefined(call_args_ptr, call_args_len, 0),
arg_or_undefined(call_args_ptr, call_args_len, 1),
arg_or_undefined(call_args_ptr, call_args_len, 2),
arg_or_undefined(call_args_ptr, call_args_len, 3),
arg_or_undefined(call_args_ptr, call_args_len, 4),
)
}
6 => {
let f: extern "C" fn(f64, f64, f64, f64, f64, f64, f64) -> f64 =
std::mem::transmute(func_ptr);
f(
this_f64,
arg_or_nan(call_args_ptr, call_args_len, 0),
arg_or_nan(call_args_ptr, call_args_len, 1),
arg_or_nan(call_args_ptr, call_args_len, 2),
arg_or_nan(call_args_ptr, call_args_len, 3),
arg_or_nan(call_args_ptr, call_args_len, 4),
arg_or_nan(call_args_ptr, call_args_len, 5),
arg_or_undefined(call_args_ptr, call_args_len, 0),
arg_or_undefined(call_args_ptr, call_args_len, 1),
arg_or_undefined(call_args_ptr, call_args_len, 2),
arg_or_undefined(call_args_ptr, call_args_len, 3),
arg_or_undefined(call_args_ptr, call_args_len, 4),
arg_or_undefined(call_args_ptr, call_args_len, 5),
)
}
7 => {
let f: extern "C" fn(f64, f64, f64, f64, f64, f64, f64, f64) -> f64 =
std::mem::transmute(func_ptr);
f(
this_f64,
arg_or_nan(call_args_ptr, call_args_len, 0),
arg_or_nan(call_args_ptr, call_args_len, 1),
arg_or_nan(call_args_ptr, call_args_len, 2),
arg_or_nan(call_args_ptr, call_args_len, 3),
arg_or_nan(call_args_ptr, call_args_len, 4),
arg_or_nan(call_args_ptr, call_args_len, 5),
arg_or_nan(call_args_ptr, call_args_len, 6),
arg_or_undefined(call_args_ptr, call_args_len, 0),
arg_or_undefined(call_args_ptr, call_args_len, 1),
arg_or_undefined(call_args_ptr, call_args_len, 2),
arg_or_undefined(call_args_ptr, call_args_len, 3),
arg_or_undefined(call_args_ptr, call_args_len, 4),
arg_or_undefined(call_args_ptr, call_args_len, 5),
arg_or_undefined(call_args_ptr, call_args_len, 6),
)
}
8 => {
let f: extern "C" fn(f64, f64, f64, f64, f64, f64, f64, f64, f64) -> f64 =
std::mem::transmute(func_ptr);
f(
this_f64,
arg_or_nan(call_args_ptr, call_args_len, 0),
arg_or_nan(call_args_ptr, call_args_len, 1),
arg_or_nan(call_args_ptr, call_args_len, 2),
arg_or_nan(call_args_ptr, call_args_len, 3),
arg_or_nan(call_args_ptr, call_args_len, 4),
arg_or_nan(call_args_ptr, call_args_len, 5),
arg_or_nan(call_args_ptr, call_args_len, 6),
arg_or_nan(call_args_ptr, call_args_len, 7),
arg_or_undefined(call_args_ptr, call_args_len, 0),
arg_or_undefined(call_args_ptr, call_args_len, 1),
arg_or_undefined(call_args_ptr, call_args_len, 2),
arg_or_undefined(call_args_ptr, call_args_len, 3),
arg_or_undefined(call_args_ptr, call_args_len, 4),
arg_or_undefined(call_args_ptr, call_args_len, 5),
arg_or_undefined(call_args_ptr, call_args_len, 6),
arg_or_undefined(call_args_ptr, call_args_len, 7),
)
}
9 => {
let f: extern "C" fn(f64, f64, f64, f64, f64, f64, f64, f64, f64, f64) -> f64 =
std::mem::transmute(func_ptr);
f(
this_f64,
arg_or_nan(call_args_ptr, call_args_len, 0),
arg_or_nan(call_args_ptr, call_args_len, 1),
arg_or_nan(call_args_ptr, call_args_len, 2),
arg_or_nan(call_args_ptr, call_args_len, 3),
arg_or_nan(call_args_ptr, call_args_len, 4),
arg_or_nan(call_args_ptr, call_args_len, 5),
arg_or_nan(call_args_ptr, call_args_len, 6),
arg_or_nan(call_args_ptr, call_args_len, 7),
arg_or_nan(call_args_ptr, call_args_len, 8),
arg_or_undefined(call_args_ptr, call_args_len, 0),
arg_or_undefined(call_args_ptr, call_args_len, 1),
arg_or_undefined(call_args_ptr, call_args_len, 2),
arg_or_undefined(call_args_ptr, call_args_len, 3),
arg_or_undefined(call_args_ptr, call_args_len, 4),
arg_or_undefined(call_args_ptr, call_args_len, 5),
arg_or_undefined(call_args_ptr, call_args_len, 6),
arg_or_undefined(call_args_ptr, call_args_len, 7),
arg_or_undefined(call_args_ptr, call_args_len, 8),
)
}
_ => {
let f: extern "C" fn(f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64) -> f64 =
std::mem::transmute(func_ptr);
f(
this_f64,
arg_or_nan(call_args_ptr, call_args_len, 0),
arg_or_nan(call_args_ptr, call_args_len, 1),
arg_or_nan(call_args_ptr, call_args_len, 2),
arg_or_nan(call_args_ptr, call_args_len, 3),
arg_or_nan(call_args_ptr, call_args_len, 4),
arg_or_nan(call_args_ptr, call_args_len, 5),
arg_or_nan(call_args_ptr, call_args_len, 6),
arg_or_nan(call_args_ptr, call_args_len, 7),
arg_or_nan(call_args_ptr, call_args_len, 8),
arg_or_nan(call_args_ptr, call_args_len, 9),
arg_or_undefined(call_args_ptr, call_args_len, 0),
arg_or_undefined(call_args_ptr, call_args_len, 1),
arg_or_undefined(call_args_ptr, call_args_len, 2),
arg_or_undefined(call_args_ptr, call_args_len, 3),
arg_or_undefined(call_args_ptr, call_args_len, 4),
arg_or_undefined(call_args_ptr, call_args_len, 5),
arg_or_undefined(call_args_ptr, call_args_len, 6),
arg_or_undefined(call_args_ptr, call_args_len, 7),
arg_or_undefined(call_args_ptr, call_args_len, 8),
arg_or_undefined(call_args_ptr, call_args_len, 9),
)
}
}
Expand Down Expand Up @@ -4348,12 +4354,14 @@ pub(crate) unsafe fn call_static_method(
args_len: usize,
param_count: u32,
) -> f64 {
// Missing trailing args pad with `undefined` (NOT NaN) so default
// parameters fire — see `call_vtable_method::arg_or_undefined`.
#[inline(always)]
unsafe fn a(args_ptr: *const f64, args_len: usize, idx: usize) -> f64 {
if idx < args_len {
*args_ptr.add(idx)
} else {
f64::NAN
f64::from_bits(crate::value::TAG_UNDEFINED)
}
}
match param_count {
Expand Down
65 changes: 65 additions & 0 deletions crates/perry-runtime/src/object/global_this.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2263,6 +2263,49 @@ extern "C" fn async_generator_proto_throw_thunk(
generator_proto_method(b"throw", arg, true)
}

/// `%AsyncGenerator.prototype%[Symbol.asyncIterator]()` returns `this` (spec
/// inherits this from `%AsyncIteratorPrototype%`). Without it, `for await` /
/// `GetIterator(obj, async)` over a generator instance can't obtain the async
/// iterator and either throws or silently produces nothing.
extern "C" fn async_generator_proto_async_iterator_thunk(
_c: *const crate::closure::ClosureHeader,
_arg: f64,
) -> f64 {
crate::object::js_implicit_this_get()
}

/// Install a well-known-symbol-keyed method (returning `this`) on a
/// generator/async-generator prototype, with the spec descriptor shape
/// (`name`/`length` own props, non-enumerable value).
fn install_proto_symbol_self_method(
proto: *mut ObjectHeader,
symbol_name: &str,
display_name: &str,
func_ptr: *const u8,
) {
let closure = crate::closure::js_closure_alloc(func_ptr, 0);
if closure.is_null() {
return;
}
crate::closure::js_register_closure_arity(func_ptr, 0);
super::native_module::set_bound_native_closure_name(closure, display_name);
super::native_module::set_builtin_closure_length(closure as usize, 0);
let configurable = super::PropertyAttrs::new(false, false, true);
super::set_builtin_property_attrs(closure as usize, "name".to_string(), configurable);
super::set_builtin_property_attrs(closure as usize, "length".to_string(), configurable);
let sym = crate::symbol::well_known_symbol(symbol_name);
if sym.is_null() {
return;
}
unsafe {
crate::symbol::js_object_set_symbol_property(
crate::value::js_nanbox_pointer(proto as i64),
f64::from_bits(JSValue::pointer(sym as *const u8).bits()),
crate::value::js_nanbox_pointer(closure as i64),
);
}
}

/// #4141: link a freshly-built generator/async-generator instance object into
/// the spec `[[Prototype]]` chain. Perry lowers `gen()` to a `{next,return,
/// throw}` object literal; this interposes a fresh intermediate object (the
Expand Down Expand Up @@ -2449,6 +2492,28 @@ fn build_generator_tower(
install_proto_method(gen_proto, "next", next_thunk, 1);
install_proto_method(gen_proto, "return", return_thunk, 1);
install_proto_method(gen_proto, "throw", throw_thunk, 1);
// Spec: `%AsyncGenerator.prototype%` inherits `[Symbol.asyncIterator]` from
// `%AsyncIteratorPrototype%` (returning `this`). Without it, `for await (x of
// gen())` over an async-generator *method instance* can't resolve the async
// iterator and hangs/yields nothing (the instance carries no own iterator
// symbol). The async-iterator-acquisition path (`js_get_async_iterator`)
// sets the implicit-this before invoking this thunk, so it returns the
// generator instance.
//
// Note: the SYNC `%Generator.prototype%` deliberately gets NO own
// `[Symbol.iterator]` here — the sync `for-of` iterator-acquisition path
// (`js_get_iterator`) does NOT bind implicit-this before invoking a
// `[Symbol.iterator]` method, so a `this`-returning thunk would resolve to
// `undefined` and break `for (x of gen())`. Sync generators already iterate
// through their own `next` via the builtin-iterator recognizers.
if is_async {
install_proto_symbol_self_method(
gen_proto,
"asyncIterator",
"[Symbol.asyncIterator]",
async_generator_proto_async_iterator_thunk as *const u8,
);
}
set_intrinsic_to_string_tag(gen_proto, inst_tag);

ctor_slot.store(ctor as i64, Ordering::Release);
Expand Down
16 changes: 16 additions & 0 deletions crates/perry-runtime/src/object/object_ops_frozen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,22 @@ pub extern "C" fn js_object_is_extensible(obj_value: f64) -> f64 {
if obj.is_null() || (obj as usize) <= 0x10000 {
return f64::from_bits(TAG_FALSE); // non-objects are not extensible
}
// Typed arrays and ArrayBuffers use a non-standard allocation that does
// not carry the 8-byte object `GcHeader` the freeze/seal/extend flags
// live in (small typed arrays are raw-`alloc`'d with the
// `TypedArrayHeader` at offset 0 — only the large-object path interposes
// a `GcHeader`). Reading `_reserved` for them dereferences whatever
// precedes the allocation, so `isExtensible` would non-deterministically
// report `false` depending on heap layout. Integer-indexed exotic
// objects are extensible by default; report that instead of reading a
// header that may not exist.
let raw = crate::value::js_nanbox_get_pointer(obj_value) as usize;
if raw > 0x10000
&& (crate::typedarray::lookup_typed_array_kind(raw).is_some()
|| crate::buffer::is_registered_buffer(raw))
{
return f64::from_bits(TAG_TRUE);
}
let gc = gc_header_for(obj);
if (*gc)._reserved & crate::gc::OBJ_FLAG_NO_EXTEND != 0 {
f64::from_bits(TAG_FALSE)
Expand Down
Loading