LibJS: Replace Array.fromAsync with a native JavaScript implementation

This allows us to use the bytecode implementation of await, which
correctly suspends execution contexts and handles completion
injections.

This gains us 4 test262 tests around mutating Array.fromAsync's
iterable whilst it's suspended as well.

This is also one step towards removing spin_until, which the
non-bytecode implementation of await uses.

```
Duration:
     -5.98s

Summary:
    Diff Tests:
        +4     -4 

Diff Tests:
    [...]/Array/fromAsync/asyncitems-array-add-to-singleton.js  -> 
    [...]/Array/fromAsync/asyncitems-array-add.js               -> 
    [...]/Array/fromAsync/asyncitems-array-mutate.js            -> 
    [...]/Array/fromAsync/asyncitems-array-remove.js            -> 
```
This commit is contained in:
Luke Wilde 2025-11-06 19:20:29 +00:00 committed by Andreas Kling
parent a63b0cfaba
commit 0eceee0a05
Notes: github-actions[bot] 2025-11-30 10:56:04 +00:00
15 changed files with 942 additions and 233 deletions

View file

@ -1472,7 +1472,7 @@ ScopedOperand Generator::add_constant(Value value)
return append_new_constant();
}
CodeGenerationErrorOr<void> Generator::generate_builtin_abstract_operation(Identifier const& builtin_identifier, ReadonlySpan<CallExpression::Argument> arguments, ScopedOperand const&)
CodeGenerationErrorOr<void> Generator::generate_builtin_abstract_operation(Identifier const& builtin_identifier, ReadonlySpan<CallExpression::Argument> arguments, ScopedOperand const& dst)
{
VERIFY(m_builtin_abstract_operations_enabled);
for (auto const& argument : arguments) {
@ -1486,6 +1486,249 @@ CodeGenerationErrorOr<void> Generator::generate_builtin_abstract_operation(Ident
auto const& operation_name = builtin_identifier.string();
if (operation_name == "IsCallable"sv) {
if (arguments.size() != 1) {
return CodeGenerationError {
&builtin_identifier,
"IsCallable only accepts one argument"sv,
};
}
auto source = TRY(arguments[0].value->generate_bytecode(*this)).value();
emit<Op::IsCallable>(dst, source);
return {};
}
if (operation_name == "IsConstructor"sv) {
if (arguments.size() != 1) {
return CodeGenerationError {
&builtin_identifier,
"IsConstructor only accepts one argument"sv,
};
}
auto source = TRY(arguments[0].value->generate_bytecode(*this)).value();
emit<Op::IsConstructor>(dst, source);
return {};
}
if (operation_name == "ToBoolean"sv) {
if (arguments.size() != 1) {
return CodeGenerationError {
&builtin_identifier,
"ToBoolean only accepts one argument"sv,
};
}
auto source = TRY(arguments[0].value->generate_bytecode(*this)).value();
emit<Op::ToBoolean>(dst, source);
return {};
}
if (operation_name == "ToObject"sv) {
if (arguments.size() != 1) {
return CodeGenerationError {
&builtin_identifier,
"ToObject only accepts one argument"sv,
};
}
auto source = TRY(arguments[0].value->generate_bytecode(*this)).value();
emit<Op::ToObject>(dst, source);
return {};
}
if (operation_name == "ThrowTypeError"sv) {
if (arguments.size() != 1) {
return CodeGenerationError {
&builtin_identifier,
"throw_type_error only accepts one argument"sv,
};
}
auto const* message = as_if<StringLiteral>(*arguments[0].value);
if (!message) {
return CodeGenerationError {
&builtin_identifier,
"ThrowTypeError's message must be a string literal"sv,
};
}
auto message_string = intern_string(message->value());
auto type_error_register = allocate_register();
emit<Op::NewTypeError>(type_error_register, message_string);
perform_needed_unwinds<Op::Throw>();
emit<Op::Throw>(type_error_register);
return {};
}
if (operation_name == "ThrowIfNotObject"sv) {
if (arguments.size() != 1) {
return CodeGenerationError {
&builtin_identifier,
"ThrowIfNotObject only accepts one argument"sv,
};
}
auto source = TRY(arguments[0].value->generate_bytecode(*this)).value();
emit<Op::ThrowIfNotObject>(source);
return {};
}
if (operation_name == "Call"sv) {
if (arguments.size() < 2) {
return CodeGenerationError {
&builtin_identifier,
"Call must have at least two arguments"sv,
};
}
auto const& callee_argument = arguments[0].value;
auto callee = TRY(callee_argument->generate_bytecode(*this)).value();
auto this_value = TRY(arguments[1].value->generate_bytecode(*this)).value();
auto arguments_to_call_with = arguments.slice(2);
Vector<ScopedOperand> argument_operands;
argument_operands.ensure_capacity(arguments_to_call_with.size());
for (auto const& argument : arguments_to_call_with) {
auto argument_value = TRY(argument.value->generate_bytecode(*this)).value();
argument_operands.unchecked_append(copy_if_needed_to_preserve_evaluation_order(argument_value));
}
auto expression_string = ([&callee_argument] -> Optional<Utf16String> {
if (auto const* identifier = as_if<Identifier>(*callee_argument))
return identifier->string().to_utf16_string();
if (auto const* member_expression = as_if<MemberExpression>(*callee_argument))
return member_expression->to_string_approximation();
return {};
})();
Optional<Bytecode::StringTableIndex> expression_string_index;
if (expression_string.has_value())
expression_string_index = intern_string(expression_string.release_value());
emit_with_extra_operand_slots<Bytecode::Op::Call>(
argument_operands.size(),
dst,
callee,
this_value,
expression_string_index,
argument_operands);
return {};
}
if (operation_name == "NewObjectWithNoPrototype"sv) {
if (!arguments.is_empty()) {
return CodeGenerationError {
&builtin_identifier,
"NewObjectWithNoPrototype does not take any arguments"sv,
};
}
emit<Op::NewObjectWithNoPrototype>(dst);
return {};
}
if (operation_name == "CreateAsyncFromSyncIterator"sv) {
if (arguments.size() != 3) {
return CodeGenerationError {
&builtin_identifier,
"CreateAsyncFromSyncIterator only accepts exactly three arguments"sv,
};
}
auto iterator = TRY(arguments[0].value->generate_bytecode(*this)).value();
auto next_method = TRY(arguments[1].value->generate_bytecode(*this)).value();
auto done = TRY(arguments[2].value->generate_bytecode(*this)).value();
emit<Op::CreateAsyncFromSyncIterator>(dst, iterator, next_method, done);
return {};
}
if (operation_name == "ToLength"sv) {
if (arguments.size() != 1) {
return CodeGenerationError {
&builtin_identifier,
"ToLength only accepts exactly one argument"sv,
};
}
auto value = TRY(arguments[0].value->generate_bytecode(*this)).value();
emit<Op::ToLength>(dst, value);
return {};
}
if (operation_name == "NewTypeError"sv) {
if (arguments.size() != 1) {
return CodeGenerationError {
&builtin_identifier,
"NewTypeError only accepts one argument"sv,
};
}
auto const* message = as_if<StringLiteral>(*arguments[0].value);
if (!message) {
return CodeGenerationError {
&builtin_identifier,
"new_type_error's message must be a string literal"sv,
};
}
auto message_string = intern_string(message->value());
emit<Op::NewTypeError>(dst, message_string);
return {};
}
if (operation_name == "NewArrayWithLength"sv) {
if (arguments.size() != 1) {
return CodeGenerationError {
&builtin_identifier,
"NewArrayWithLength only accepts one argument"sv,
};
}
auto length = TRY(arguments[0].value->generate_bytecode(*this)).value();
emit<Op::NewArrayWithLength>(dst, length);
return {};
}
if (operation_name == "CreateDataPropertyOrThrow"sv) {
if (arguments.size() != 3) {
return CodeGenerationError {
&builtin_identifier,
"CreateDataPropertyOrThrow only accepts three arguments"sv,
};
}
auto object = TRY(arguments[0].value->generate_bytecode(*this)).value();
auto property = TRY(arguments[1].value->generate_bytecode(*this)).value();
auto value = TRY(arguments[2].value->generate_bytecode(*this)).value();
emit<Op::CreateDataPropertyOrThrow>(object, property, value);
return {};
}
#define __JS_ENUMERATE(snake_name, functionName, length) \
if (operation_name == #functionName##sv) { \
Vector<ScopedOperand> argument_operands; \
argument_operands.ensure_capacity(arguments.size()); \
for (auto const& argument : arguments) { \
auto argument_value = TRY(argument.value->generate_bytecode(*this)).value(); \
argument_operands.unchecked_append(copy_if_needed_to_preserve_evaluation_order(argument_value)); \
} \
emit_with_extra_operand_slots<Bytecode::Op::Call>( \
argument_operands.size(), \
dst, \
add_constant(m_vm.current_realm()->intrinsics().snake_name##_abstract_operation_function()), \
add_constant(js_undefined()), \
intern_string(builtin_identifier.string().to_utf16_string()), \
argument_operands); \
return {}; \
}
JS_ENUMERATE_NATIVE_JAVASCRIPT_BACKED_ABSTRACT_OPERATIONS
#undef __JS_ENUMERATE
dbgln("Unknown builtin abstract operation: '{}'", operation_name);
return CodeGenerationError {
&builtin_identifier,
@ -1504,6 +1747,18 @@ CodeGenerationErrorOr<Optional<ScopedOperand>> Generator::maybe_generate_builtin
if (!m_builtin_abstract_operations_enabled)
return OptionalNone {};
if (constant_name == "SYMBOL_ITERATOR"sv) {
return add_constant(vm().well_known_symbol_iterator());
}
if (constant_name == "SYMBOL_ASYNC_ITERATOR"sv) {
return add_constant(vm().well_known_symbol_async_iterator());
}
if (constant_name == "MAX_ARRAY_LIKE_INDEX"sv) {
return add_constant(Value(MAX_ARRAY_LIKE_INDEX));
}
dbgln("Unknown builtin constant: '{}'", constant_name);
return CodeGenerationError {
&builtin_identifier,