mirror of
https://github.com/SerenityOS/serenity
synced 2024-10-02 22:24:26 +00:00
LibJS: Prepare yield object before re-routing it through finally
This commit is contained in:
parent
2447a25753
commit
1de475b404
|
@ -283,11 +283,13 @@ public:
|
||||||
// * Interpreter::run_bytecode::handle_ContinuePendingUnwind
|
// * Interpreter::run_bytecode::handle_ContinuePendingUnwind
|
||||||
// * Return::execute_impl
|
// * Return::execute_impl
|
||||||
// * Yield::execute_impl
|
// * Yield::execute_impl
|
||||||
emit<Bytecode::Op::Mov>(Operand(Register::saved_return_value()), value);
|
if constexpr (IsSame<OpType, Op::Yield>)
|
||||||
|
emit<Bytecode::Op::PrepareYield>(Operand(Register::saved_return_value()), value);
|
||||||
|
else
|
||||||
|
emit<Bytecode::Op::Mov>(Operand(Register::saved_return_value()), value);
|
||||||
emit<Bytecode::Op::Mov>(Operand(Register::exception()), add_constant(Value {}));
|
emit<Bytecode::Op::Mov>(Operand(Register::exception()), add_constant(Value {}));
|
||||||
// FIXME: Do we really need to clear the return value register here?
|
// FIXME: Do we really need to clear the return value register here?
|
||||||
emit<Bytecode::Op::Mov>(Operand(Register::return_value()), add_constant(Value {}));
|
emit<Bytecode::Op::Mov>(Operand(Register::return_value()), add_constant(Value {}));
|
||||||
|
|
||||||
emit<Bytecode::Op::Jump>(Label { *m_current_basic_block->finalizer() });
|
emit<Bytecode::Op::Jump>(Label { *m_current_basic_block->finalizer() });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,6 +110,7 @@
|
||||||
O(NewRegExp) \
|
O(NewRegExp) \
|
||||||
O(NewTypeError) \
|
O(NewTypeError) \
|
||||||
O(Not) \
|
O(Not) \
|
||||||
|
O(PrepareYield) \
|
||||||
O(PostfixDecrement) \
|
O(PostfixDecrement) \
|
||||||
O(PostfixIncrement) \
|
O(PostfixIncrement) \
|
||||||
O(PutById) \
|
O(PutById) \
|
||||||
|
|
|
@ -161,6 +161,22 @@ ALWAYS_INLINE void Interpreter::set(Operand op, Value value)
|
||||||
m_registers_and_constants_and_locals.data()[op.index()] = value;
|
m_registers_and_constants_and_locals.data()[op.index()] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ALWAYS_INLINE Value Interpreter::do_yield(Value value, Optional<Label> continuation)
|
||||||
|
{
|
||||||
|
auto object = Object::create(realm(), nullptr);
|
||||||
|
object->define_direct_property("result", value, JS::default_attributes);
|
||||||
|
|
||||||
|
if (continuation.has_value())
|
||||||
|
// FIXME: If we get a pointer, which is not accurately representable as a double
|
||||||
|
// will cause this to explode
|
||||||
|
object->define_direct_property("continuation", Value(continuation->address()), JS::default_attributes);
|
||||||
|
else
|
||||||
|
object->define_direct_property("continuation", js_null(), JS::default_attributes);
|
||||||
|
|
||||||
|
object->define_direct_property("isAwait", Value(false), JS::default_attributes);
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
// 16.1.6 ScriptEvaluation ( scriptRecord ), https://tc39.es/ecma262/#sec-runtime-semantics-scriptevaluation
|
// 16.1.6 ScriptEvaluation ( scriptRecord ), https://tc39.es/ecma262/#sec-runtime-semantics-scriptevaluation
|
||||||
ThrowCompletionOr<Value> Interpreter::run(Script& script_record, JS::GCPtr<Environment> lexical_environment_override)
|
ThrowCompletionOr<Value> Interpreter::run(Script& script_record, JS::GCPtr<Environment> lexical_environment_override)
|
||||||
{
|
{
|
||||||
|
@ -619,6 +635,7 @@ FLATTEN_ON_CLANG void Interpreter::run_bytecode(size_t entry_point)
|
||||||
HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(NewRegExp);
|
HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(NewRegExp);
|
||||||
HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(NewTypeError);
|
HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(NewTypeError);
|
||||||
HANDLE_INSTRUCTION(Not);
|
HANDLE_INSTRUCTION(Not);
|
||||||
|
HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(PrepareYield);
|
||||||
HANDLE_INSTRUCTION(PostfixDecrement);
|
HANDLE_INSTRUCTION(PostfixDecrement);
|
||||||
HANDLE_INSTRUCTION(PostfixIncrement);
|
HANDLE_INSTRUCTION(PostfixIncrement);
|
||||||
HANDLE_INSTRUCTION(PutById);
|
HANDLE_INSTRUCTION(PutById);
|
||||||
|
@ -1762,19 +1779,14 @@ void LeaveUnwindContext::execute_impl(Bytecode::Interpreter& interpreter) const
|
||||||
void Yield::execute_impl(Bytecode::Interpreter& interpreter) const
|
void Yield::execute_impl(Bytecode::Interpreter& interpreter) const
|
||||||
{
|
{
|
||||||
auto yielded_value = interpreter.get(m_value).value_or(js_undefined());
|
auto yielded_value = interpreter.get(m_value).value_or(js_undefined());
|
||||||
|
interpreter.do_return(
|
||||||
|
interpreter.do_yield(yielded_value, m_continuation_label));
|
||||||
|
}
|
||||||
|
|
||||||
auto object = Object::create(interpreter.realm(), nullptr);
|
void PrepareYield::execute_impl(Bytecode::Interpreter& interpreter) const
|
||||||
object->define_direct_property("result", yielded_value, JS::default_attributes);
|
{
|
||||||
|
auto value = interpreter.get(m_value).value_or(js_undefined());
|
||||||
if (m_continuation_label.has_value())
|
interpreter.set(m_dest, interpreter.do_yield(value, {}));
|
||||||
// FIXME: If we get a pointer, which is not accurately representable as a double
|
|
||||||
// will cause this to explode
|
|
||||||
object->define_direct_property("continuation", Value(m_continuation_label->address()), JS::default_attributes);
|
|
||||||
else
|
|
||||||
object->define_direct_property("continuation", js_null(), JS::default_attributes);
|
|
||||||
|
|
||||||
object->define_direct_property("isAwait", Value(false), JS::default_attributes);
|
|
||||||
interpreter.do_return(object);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Await::execute_impl(Bytecode::Interpreter& interpreter) const
|
void Await::execute_impl(Bytecode::Interpreter& interpreter) const
|
||||||
|
@ -2466,6 +2478,13 @@ ByteString Yield::to_byte_string_impl(Bytecode::Executable const& executable) co
|
||||||
format_operand("value"sv, m_value, executable));
|
format_operand("value"sv, m_value, executable));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ByteString PrepareYield::to_byte_string_impl(Bytecode::Executable const& executable) const
|
||||||
|
{
|
||||||
|
return ByteString::formatted("PrepareYield {}, {}",
|
||||||
|
format_operand("dst"sv, m_dest, executable),
|
||||||
|
format_operand("value"sv, m_value, executable));
|
||||||
|
}
|
||||||
|
|
||||||
ByteString Await::to_byte_string_impl(Bytecode::Executable const& executable) const
|
ByteString Await::to_byte_string_impl(Bytecode::Executable const& executable) const
|
||||||
{
|
{
|
||||||
return ByteString::formatted("Await {}, continuation:{}",
|
return ByteString::formatted("Await {}, continuation:{}",
|
||||||
|
|
|
@ -59,6 +59,7 @@ public:
|
||||||
[[nodiscard]] Value get(Operand) const;
|
[[nodiscard]] Value get(Operand) const;
|
||||||
void set(Operand, Value);
|
void set(Operand, Value);
|
||||||
|
|
||||||
|
Value do_yield(Value value, Optional<Label> continuation);
|
||||||
void do_return(Value value)
|
void do_return(Value value)
|
||||||
{
|
{
|
||||||
reg(Register::return_value()) = value;
|
reg(Register::return_value()) = value;
|
||||||
|
|
|
@ -2214,6 +2214,31 @@ private:
|
||||||
Operand m_value;
|
Operand m_value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class PrepareYield final : public Instruction {
|
||||||
|
public:
|
||||||
|
explicit PrepareYield(Operand dest, Operand value)
|
||||||
|
: Instruction(Type::PrepareYield)
|
||||||
|
, m_dest(dest)
|
||||||
|
, m_value(value)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void execute_impl(Bytecode::Interpreter&) const;
|
||||||
|
ByteString to_byte_string_impl(Bytecode::Executable const&) const;
|
||||||
|
void visit_operands_impl(Function<void(Operand&)> visitor)
|
||||||
|
{
|
||||||
|
visitor(m_dest);
|
||||||
|
visitor(m_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Operand destination() const { return m_dest; }
|
||||||
|
Operand value() const { return m_value; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
Operand m_dest;
|
||||||
|
Operand m_value;
|
||||||
|
};
|
||||||
|
|
||||||
class Await final : public Instruction {
|
class Await final : public Instruction {
|
||||||
public:
|
public:
|
||||||
constexpr static bool IsTerminator = true;
|
constexpr static bool IsTerminator = true;
|
||||||
|
|
|
@ -38,3 +38,43 @@ test("restore exception after generator yield in finally", () => {
|
||||||
expect(() => generator.next()).toThrowWithMessage(Error, "foo");
|
expect(() => generator.next()).toThrowWithMessage(Error, "foo");
|
||||||
expect(generator.next().done).toBe(true);
|
expect(generator.next().done).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("yield, then return from finally", () => {
|
||||||
|
let test = [];
|
||||||
|
let generator = (function* () {
|
||||||
|
try {
|
||||||
|
yield 1;
|
||||||
|
test.push(1);
|
||||||
|
} finally {
|
||||||
|
test.push(2);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
expect.fail("unreachable");
|
||||||
|
})();
|
||||||
|
|
||||||
|
let result = generator.next();
|
||||||
|
expect(result.value).toBe(1);
|
||||||
|
expect(result.done).toBe(false);
|
||||||
|
result = generator.next();
|
||||||
|
expect(result.value).toBe(2);
|
||||||
|
expect(result.done).toBe(true);
|
||||||
|
expect(test).toEqual([1, 2]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("return from async through finally", () => {
|
||||||
|
let test = 0;
|
||||||
|
let result = (async function () {
|
||||||
|
try {
|
||||||
|
return { y: 5 };
|
||||||
|
} finally {
|
||||||
|
test = 42;
|
||||||
|
}
|
||||||
|
expect.fail("unreachable");
|
||||||
|
})();
|
||||||
|
|
||||||
|
expect(result).toBeInstanceOf(Promise);
|
||||||
|
expect(test).toBe(42);
|
||||||
|
result.then(value => {
|
||||||
|
expect(value).toEqual({ y: 5 });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in a new issue