Skip to content

Commit 0eceee0

Browse files
Lubrsiawesomekling
authored andcommitted
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 ❌ -> ✅ ```
1 parent a63b0cf commit 0eceee0

File tree

15 files changed

+942
-233
lines changed

15 files changed

+942
-233
lines changed

Libraries/LibJS/Bytecode/Bytecode.def

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,20 @@ op CreateArguments < Instruction
153153
m_is_immutable: bool
154154
endop
155155

156+
op CreateAsyncFromSyncIterator < Instruction
157+
@nothrow
158+
m_dst: Operand
159+
m_iterator: Operand
160+
m_next_method: Operand
161+
m_done: Operand
162+
endop
163+
164+
op CreateDataPropertyOrThrow < Instruction
165+
m_object: Operand
166+
m_property: Operand
167+
m_value: Operand
168+
endop
169+
156170
op CreateLexicalEnvironment < Instruction
157171
@nothrow
158172
m_dst: Optional<Operand>
@@ -422,6 +436,18 @@ op InstanceOf < Instruction
422436
m_rhs: Operand
423437
endop
424438

439+
op IsCallable < Instruction
440+
@nothrow
441+
m_dst: Operand
442+
m_value: Operand
443+
endop
444+
445+
op IsConstructor < Instruction
446+
@nothrow
447+
m_dst: Operand
448+
m_value: Operand
449+
endop
450+
425451
op IteratorClose < Instruction
426452
m_iterator_object: Operand
427453
m_iterator_next: Operand
@@ -640,6 +666,11 @@ op NewArray < Instruction
640666
m_elements: Operand[]
641667
endop
642668

669+
op NewArrayWithLength < Instruction
670+
m_dst: Operand
671+
m_array_length: Operand
672+
endop
673+
643674
op NewClass < Instruction
644675
m_length: u32
645676
m_dst: Operand
@@ -663,6 +694,11 @@ op NewObject < Instruction
663694
m_dst: Operand
664695
endop
665696

697+
op NewObjectWithNoPrototype < Instruction
698+
@nothrow
699+
m_dst: Operand
700+
endop
701+
666702
op NewPrimitiveArray < Instruction
667703
@nothrow
668704
m_length: u32
@@ -1041,6 +1077,22 @@ op ThrowIfTDZ < Instruction
10411077
m_src: Operand
10421078
endop
10431079

1080+
op ToBoolean < Instruction
1081+
@nothrow
1082+
m_dst: Operand
1083+
m_value: Operand
1084+
endop
1085+
1086+
op ToLength < Instruction
1087+
m_dst: Operand
1088+
m_value: Operand
1089+
endop
1090+
1091+
op ToObject < Instruction
1092+
m_dst: Operand
1093+
m_value: Operand
1094+
endop
1095+
10441096
op Typeof < Instruction
10451097
m_dst: Operand
10461098
m_src: Operand

Libraries/LibJS/Bytecode/Generator.cpp

Lines changed: 256 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1472,7 +1472,7 @@ ScopedOperand Generator::add_constant(Value value)
14721472
return append_new_constant();
14731473
}
14741474

1475-
CodeGenerationErrorOr<void> Generator::generate_builtin_abstract_operation(Identifier const& builtin_identifier, ReadonlySpan<CallExpression::Argument> arguments, ScopedOperand const&)
1475+
CodeGenerationErrorOr<void> Generator::generate_builtin_abstract_operation(Identifier const& builtin_identifier, ReadonlySpan<CallExpression::Argument> arguments, ScopedOperand const& dst)
14761476
{
14771477
VERIFY(m_builtin_abstract_operations_enabled);
14781478
for (auto const& argument : arguments) {
@@ -1486,6 +1486,249 @@ CodeGenerationErrorOr<void> Generator::generate_builtin_abstract_operation(Ident
14861486

14871487
auto const& operation_name = builtin_identifier.string();
14881488

1489+
if (operation_name == "IsCallable"sv) {
1490+
if (arguments.size() != 1) {
1491+
return CodeGenerationError {
1492+
&builtin_identifier,
1493+
"IsCallable only accepts one argument"sv,
1494+
};
1495+
}
1496+
1497+
auto source = TRY(arguments[0].value->generate_bytecode(*this)).value();
1498+
emit<Op::IsCallable>(dst, source);
1499+
return {};
1500+
}
1501+
1502+
if (operation_name == "IsConstructor"sv) {
1503+
if (arguments.size() != 1) {
1504+
return CodeGenerationError {
1505+
&builtin_identifier,
1506+
"IsConstructor only accepts one argument"sv,
1507+
};
1508+
}
1509+
1510+
auto source = TRY(arguments[0].value->generate_bytecode(*this)).value();
1511+
emit<Op::IsConstructor>(dst, source);
1512+
return {};
1513+
}
1514+
1515+
if (operation_name == "ToBoolean"sv) {
1516+
if (arguments.size() != 1) {
1517+
return CodeGenerationError {
1518+
&builtin_identifier,
1519+
"ToBoolean only accepts one argument"sv,
1520+
};
1521+
}
1522+
1523+
auto source = TRY(arguments[0].value->generate_bytecode(*this)).value();
1524+
emit<Op::ToBoolean>(dst, source);
1525+
return {};
1526+
}
1527+
1528+
if (operation_name == "ToObject"sv) {
1529+
if (arguments.size() != 1) {
1530+
return CodeGenerationError {
1531+
&builtin_identifier,
1532+
"ToObject only accepts one argument"sv,
1533+
};
1534+
}
1535+
1536+
auto source = TRY(arguments[0].value->generate_bytecode(*this)).value();
1537+
emit<Op::ToObject>(dst, source);
1538+
return {};
1539+
}
1540+
1541+
if (operation_name == "ThrowTypeError"sv) {
1542+
if (arguments.size() != 1) {
1543+
return CodeGenerationError {
1544+
&builtin_identifier,
1545+
"throw_type_error only accepts one argument"sv,
1546+
};
1547+
}
1548+
1549+
auto const* message = as_if<StringLiteral>(*arguments[0].value);
1550+
if (!message) {
1551+
return CodeGenerationError {
1552+
&builtin_identifier,
1553+
"ThrowTypeError's message must be a string literal"sv,
1554+
};
1555+
}
1556+
1557+
auto message_string = intern_string(message->value());
1558+
auto type_error_register = allocate_register();
1559+
emit<Op::NewTypeError>(type_error_register, message_string);
1560+
perform_needed_unwinds<Op::Throw>();
1561+
emit<Op::Throw>(type_error_register);
1562+
return {};
1563+
}
1564+
1565+
if (operation_name == "ThrowIfNotObject"sv) {
1566+
if (arguments.size() != 1) {
1567+
return CodeGenerationError {
1568+
&builtin_identifier,
1569+
"ThrowIfNotObject only accepts one argument"sv,
1570+
};
1571+
}
1572+
1573+
auto source = TRY(arguments[0].value->generate_bytecode(*this)).value();
1574+
emit<Op::ThrowIfNotObject>(source);
1575+
return {};
1576+
}
1577+
1578+
if (operation_name == "Call"sv) {
1579+
if (arguments.size() < 2) {
1580+
return CodeGenerationError {
1581+
&builtin_identifier,
1582+
"Call must have at least two arguments"sv,
1583+
};
1584+
}
1585+
1586+
auto const& callee_argument = arguments[0].value;
1587+
auto callee = TRY(callee_argument->generate_bytecode(*this)).value();
1588+
auto this_value = TRY(arguments[1].value->generate_bytecode(*this)).value();
1589+
auto arguments_to_call_with = arguments.slice(2);
1590+
1591+
Vector<ScopedOperand> argument_operands;
1592+
argument_operands.ensure_capacity(arguments_to_call_with.size());
1593+
for (auto const& argument : arguments_to_call_with) {
1594+
auto argument_value = TRY(argument.value->generate_bytecode(*this)).value();
1595+
argument_operands.unchecked_append(copy_if_needed_to_preserve_evaluation_order(argument_value));
1596+
}
1597+
1598+
auto expression_string = ([&callee_argument] -> Optional<Utf16String> {
1599+
if (auto const* identifier = as_if<Identifier>(*callee_argument))
1600+
return identifier->string().to_utf16_string();
1601+
1602+
if (auto const* member_expression = as_if<MemberExpression>(*callee_argument))
1603+
return member_expression->to_string_approximation();
1604+
1605+
return {};
1606+
})();
1607+
1608+
Optional<Bytecode::StringTableIndex> expression_string_index;
1609+
if (expression_string.has_value())
1610+
expression_string_index = intern_string(expression_string.release_value());
1611+
1612+
emit_with_extra_operand_slots<Bytecode::Op::Call>(
1613+
argument_operands.size(),
1614+
dst,
1615+
callee,
1616+
this_value,
1617+
expression_string_index,
1618+
argument_operands);
1619+
return {};
1620+
}
1621+
1622+
if (operation_name == "NewObjectWithNoPrototype"sv) {
1623+
if (!arguments.is_empty()) {
1624+
return CodeGenerationError {
1625+
&builtin_identifier,
1626+
"NewObjectWithNoPrototype does not take any arguments"sv,
1627+
};
1628+
}
1629+
1630+
emit<Op::NewObjectWithNoPrototype>(dst);
1631+
return {};
1632+
}
1633+
1634+
if (operation_name == "CreateAsyncFromSyncIterator"sv) {
1635+
if (arguments.size() != 3) {
1636+
return CodeGenerationError {
1637+
&builtin_identifier,
1638+
"CreateAsyncFromSyncIterator only accepts exactly three arguments"sv,
1639+
};
1640+
}
1641+
1642+
auto iterator = TRY(arguments[0].value->generate_bytecode(*this)).value();
1643+
auto next_method = TRY(arguments[1].value->generate_bytecode(*this)).value();
1644+
auto done = TRY(arguments[2].value->generate_bytecode(*this)).value();
1645+
1646+
emit<Op::CreateAsyncFromSyncIterator>(dst, iterator, next_method, done);
1647+
return {};
1648+
}
1649+
1650+
if (operation_name == "ToLength"sv) {
1651+
if (arguments.size() != 1) {
1652+
return CodeGenerationError {
1653+
&builtin_identifier,
1654+
"ToLength only accepts exactly one argument"sv,
1655+
};
1656+
}
1657+
1658+
auto value = TRY(arguments[0].value->generate_bytecode(*this)).value();
1659+
emit<Op::ToLength>(dst, value);
1660+
return {};
1661+
}
1662+
1663+
if (operation_name == "NewTypeError"sv) {
1664+
if (arguments.size() != 1) {
1665+
return CodeGenerationError {
1666+
&builtin_identifier,
1667+
"NewTypeError only accepts one argument"sv,
1668+
};
1669+
}
1670+
1671+
auto const* message = as_if<StringLiteral>(*arguments[0].value);
1672+
if (!message) {
1673+
return CodeGenerationError {
1674+
&builtin_identifier,
1675+
"new_type_error's message must be a string literal"sv,
1676+
};
1677+
}
1678+
1679+
auto message_string = intern_string(message->value());
1680+
emit<Op::NewTypeError>(dst, message_string);
1681+
return {};
1682+
}
1683+
1684+
if (operation_name == "NewArrayWithLength"sv) {
1685+
if (arguments.size() != 1) {
1686+
return CodeGenerationError {
1687+
&builtin_identifier,
1688+
"NewArrayWithLength only accepts one argument"sv,
1689+
};
1690+
}
1691+
1692+
auto length = TRY(arguments[0].value->generate_bytecode(*this)).value();
1693+
emit<Op::NewArrayWithLength>(dst, length);
1694+
return {};
1695+
}
1696+
1697+
if (operation_name == "CreateDataPropertyOrThrow"sv) {
1698+
if (arguments.size() != 3) {
1699+
return CodeGenerationError {
1700+
&builtin_identifier,
1701+
"CreateDataPropertyOrThrow only accepts three arguments"sv,
1702+
};
1703+
}
1704+
1705+
auto object = TRY(arguments[0].value->generate_bytecode(*this)).value();
1706+
auto property = TRY(arguments[1].value->generate_bytecode(*this)).value();
1707+
auto value = TRY(arguments[2].value->generate_bytecode(*this)).value();
1708+
emit<Op::CreateDataPropertyOrThrow>(object, property, value);
1709+
return {};
1710+
}
1711+
1712+
#define __JS_ENUMERATE(snake_name, functionName, length) \
1713+
if (operation_name == #functionName##sv) { \
1714+
Vector<ScopedOperand> argument_operands; \
1715+
argument_operands.ensure_capacity(arguments.size()); \
1716+
for (auto const& argument : arguments) { \
1717+
auto argument_value = TRY(argument.value->generate_bytecode(*this)).value(); \
1718+
argument_operands.unchecked_append(copy_if_needed_to_preserve_evaluation_order(argument_value)); \
1719+
} \
1720+
emit_with_extra_operand_slots<Bytecode::Op::Call>( \
1721+
argument_operands.size(), \
1722+
dst, \
1723+
add_constant(m_vm.current_realm()->intrinsics().snake_name##_abstract_operation_function()), \
1724+
add_constant(js_undefined()), \
1725+
intern_string(builtin_identifier.string().to_utf16_string()), \
1726+
argument_operands); \
1727+
return {}; \
1728+
}
1729+
JS_ENUMERATE_NATIVE_JAVASCRIPT_BACKED_ABSTRACT_OPERATIONS
1730+
#undef __JS_ENUMERATE
1731+
14891732
dbgln("Unknown builtin abstract operation: '{}'", operation_name);
14901733
return CodeGenerationError {
14911734
&builtin_identifier,
@@ -1504,6 +1747,18 @@ CodeGenerationErrorOr<Optional<ScopedOperand>> Generator::maybe_generate_builtin
15041747
if (!m_builtin_abstract_operations_enabled)
15051748
return OptionalNone {};
15061749

1750+
if (constant_name == "SYMBOL_ITERATOR"sv) {
1751+
return add_constant(vm().well_known_symbol_iterator());
1752+
}
1753+
1754+
if (constant_name == "SYMBOL_ASYNC_ITERATOR"sv) {
1755+
return add_constant(vm().well_known_symbol_async_iterator());
1756+
}
1757+
1758+
if (constant_name == "MAX_ARRAY_LIKE_INDEX"sv) {
1759+
return add_constant(Value(MAX_ARRAY_LIKE_INDEX));
1760+
}
1761+
15071762
dbgln("Unknown builtin constant: '{}'", constant_name);
15081763
return CodeGenerationError {
15091764
&builtin_identifier,

0 commit comments

Comments
 (0)