mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-22 23:50:19 +00:00
LibJS/Bytecode: Do basic compare-and-jump peephole optimization
We now fuse sequences like [LessThan, JumpIf] to JumpLessThan. This is only allowed for temporaries (i.e VM registers) with no other references to them.
This commit is contained in:
parent
9bcb0feb4d
commit
7654da3851
Notes:
sideshowbarker
2024-07-18 00:41:35 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/SerenityOS/serenity/commit/7654da3851 Pull-request: https://github.com/SerenityOS/serenity/pull/24276 Reviewed-by: https://github.com/Hendiadyoin1 Reviewed-by: https://github.com/shannonbooth
8 changed files with 166 additions and 28 deletions
|
@ -186,13 +186,13 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> LogicalExpression::gene
|
|||
|
||||
switch (m_op) {
|
||||
case LogicalOp::And:
|
||||
generator.emit<Bytecode::Op::JumpIf>(
|
||||
generator.emit_jump_if(
|
||||
lhs,
|
||||
Bytecode::Label { rhs_block },
|
||||
Bytecode::Label { end_block });
|
||||
break;
|
||||
case LogicalOp::Or:
|
||||
generator.emit<Bytecode::Op::JumpIf>(
|
||||
generator.emit_jump_if(
|
||||
lhs,
|
||||
Bytecode::Label { end_block },
|
||||
Bytecode::Label { rhs_block });
|
||||
|
@ -549,7 +549,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> AssignmentExpression::g
|
|||
lhs_block_ptr = &generator.make_block();
|
||||
end_block_ptr = &generator.make_block();
|
||||
|
||||
generator.emit<Bytecode::Op::JumpIf>(
|
||||
generator.emit_jump_if(
|
||||
lhs,
|
||||
Bytecode::Label { *rhs_block_ptr },
|
||||
Bytecode::Label { *lhs_block_ptr });
|
||||
|
@ -558,7 +558,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> AssignmentExpression::g
|
|||
lhs_block_ptr = &generator.make_block();
|
||||
end_block_ptr = &generator.make_block();
|
||||
|
||||
generator.emit<Bytecode::Op::JumpIf>(
|
||||
generator.emit_jump_if(
|
||||
lhs,
|
||||
Bytecode::Label { *lhs_block_ptr },
|
||||
Bytecode::Label { *rhs_block_ptr });
|
||||
|
@ -748,7 +748,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> WhileStatement::generat
|
|||
|
||||
generator.switch_to_basic_block(test_block);
|
||||
auto test = TRY(m_test->generate_bytecode(generator)).value();
|
||||
generator.emit<Bytecode::Op::JumpIf>(
|
||||
generator.emit_jump_if(
|
||||
test,
|
||||
Bytecode::Label { body_block },
|
||||
Bytecode::Label { end_block });
|
||||
|
@ -798,7 +798,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> DoWhileStatement::gener
|
|||
|
||||
generator.switch_to_basic_block(test_block);
|
||||
auto test = TRY(m_test->generate_bytecode(generator)).value();
|
||||
generator.emit<Bytecode::Op::JumpIf>(
|
||||
generator.emit_jump_if(
|
||||
test,
|
||||
Bytecode::Label { body_block },
|
||||
Bytecode::Label { load_result_and_jump_to_end_block });
|
||||
|
@ -901,10 +901,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> ForStatement::generate_
|
|||
generator.switch_to_basic_block(*test_block_ptr);
|
||||
|
||||
auto test = TRY(m_test->generate_bytecode(generator)).value();
|
||||
generator.emit<Bytecode::Op::JumpIf>(
|
||||
test,
|
||||
Bytecode::Label { *body_block_ptr },
|
||||
Bytecode::Label { end_block });
|
||||
generator.emit_jump_if(test, Bytecode::Label { *body_block_ptr }, Bytecode::Label { end_block });
|
||||
}
|
||||
|
||||
if (m_update) {
|
||||
|
@ -1298,7 +1295,7 @@ static Bytecode::CodeGenerationErrorOr<void> generate_array_binding_pattern_byte
|
|||
auto& if_not_exhausted_block = generator.make_block();
|
||||
auto& continuation_block = generator.make_block();
|
||||
|
||||
generator.emit<Bytecode::Op::JumpIf>(
|
||||
generator.emit_jump_if(
|
||||
is_iterator_exhausted,
|
||||
Bytecode::Label { if_exhausted_block },
|
||||
Bytecode::Label { if_not_exhausted_block });
|
||||
|
@ -1324,7 +1321,7 @@ static Bytecode::CodeGenerationErrorOr<void> generate_array_binding_pattern_byte
|
|||
if (!first) {
|
||||
auto& iterator_is_not_exhausted_block = generator.make_block();
|
||||
|
||||
generator.emit<Bytecode::Op::JumpIf>(
|
||||
generator.emit_jump_if(
|
||||
is_iterator_exhausted,
|
||||
Bytecode::Label { iterator_is_exhausted_block },
|
||||
Bytecode::Label { iterator_is_not_exhausted_block });
|
||||
|
@ -1338,7 +1335,7 @@ static Bytecode::CodeGenerationErrorOr<void> generate_array_binding_pattern_byte
|
|||
// We still have to check for exhaustion here. If the iterator is exhausted,
|
||||
// we need to bail before trying to get the value
|
||||
auto& no_bail_block = generator.make_block();
|
||||
generator.emit<Bytecode::Op::JumpIf>(
|
||||
generator.emit_jump_if(
|
||||
is_iterator_exhausted,
|
||||
Bytecode::Label { iterator_is_exhausted_block },
|
||||
Bytecode::Label { no_bail_block });
|
||||
|
@ -1392,7 +1389,7 @@ static Bytecode::CodeGenerationErrorOr<void> generate_array_binding_pattern_byte
|
|||
auto& done_block = generator.make_block();
|
||||
auto& not_done_block = generator.make_block();
|
||||
|
||||
generator.emit<Bytecode::Op::JumpIf>(
|
||||
generator.emit_jump_if(
|
||||
is_iterator_exhausted,
|
||||
Bytecode::Label { done_block },
|
||||
Bytecode::Label { not_done_block });
|
||||
|
@ -1740,7 +1737,7 @@ static void generate_yield(Bytecode::Generator& generator,
|
|||
resumption_value_type_is_not_return_result,
|
||||
received_completion_type,
|
||||
generator.add_constant(Value(to_underlying(Completion::Type::Return))));
|
||||
generator.emit<Bytecode::Op::JumpIf>(
|
||||
generator.emit_jump_if(
|
||||
resumption_value_type_is_not_return_result,
|
||||
Bytecode::Label { continuation_label },
|
||||
Bytecode::Label { resumption_value_type_is_return_block });
|
||||
|
@ -1757,7 +1754,7 @@ static void generate_yield(Bytecode::Generator& generator,
|
|||
awaited_type_is_throw_result,
|
||||
received_completion_type,
|
||||
generator.add_constant(Value(to_underlying(Completion::Type::Throw))));
|
||||
generator.emit<Bytecode::Op::JumpIf>(
|
||||
generator.emit_jump_if(
|
||||
awaited_type_is_throw_result,
|
||||
Bytecode::Label { continuation_label },
|
||||
Bytecode::Label { awaited_type_is_normal_block });
|
||||
|
@ -1839,7 +1836,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> YieldExpression::genera
|
|||
received_completion_type_register_is_normal,
|
||||
received_completion_type,
|
||||
generator.add_constant(Value(to_underlying(Completion::Type::Normal))));
|
||||
generator.emit<Bytecode::Op::JumpIf>(
|
||||
generator.emit_jump_if(
|
||||
received_completion_type_register_is_normal,
|
||||
Bytecode::Label { type_is_normal_block },
|
||||
Bytecode::Label { is_type_throw_block });
|
||||
|
@ -1868,7 +1865,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> YieldExpression::genera
|
|||
// v. If done is true, then
|
||||
auto& type_is_normal_done_block = generator.make_block();
|
||||
auto& type_is_normal_not_done_block = generator.make_block();
|
||||
generator.emit<Bytecode::Op::JumpIf>(
|
||||
generator.emit_jump_if(
|
||||
done,
|
||||
Bytecode::Label { type_is_normal_done_block },
|
||||
Bytecode::Label { type_is_normal_not_done_block });
|
||||
|
@ -1917,7 +1914,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> YieldExpression::genera
|
|||
received_completion_type_register_is_throw,
|
||||
received_completion_type,
|
||||
generator.add_constant(Value(to_underlying(Completion::Type::Throw))));
|
||||
generator.emit<Bytecode::Op::JumpIf>(
|
||||
generator.emit_jump_if(
|
||||
received_completion_type_register_is_throw,
|
||||
Bytecode::Label { type_is_throw_block },
|
||||
Bytecode::Label { type_is_return_block });
|
||||
|
@ -1959,7 +1956,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> YieldExpression::genera
|
|||
// 6. If done is true, then
|
||||
auto& type_is_throw_done_block = generator.make_block();
|
||||
auto& type_is_throw_not_done_block = generator.make_block();
|
||||
generator.emit<Bytecode::Op::JumpIf>(
|
||||
generator.emit_jump_if(
|
||||
done,
|
||||
Bytecode::Label { type_is_throw_done_block },
|
||||
Bytecode::Label { type_is_throw_not_done_block });
|
||||
|
@ -2056,7 +2053,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> YieldExpression::genera
|
|||
// viii. If done is true, then
|
||||
auto& type_is_return_done_block = generator.make_block();
|
||||
auto& type_is_return_not_done_block = generator.make_block();
|
||||
generator.emit<Bytecode::Op::JumpIf>(
|
||||
generator.emit_jump_if(
|
||||
done,
|
||||
Bytecode::Label { type_is_return_done_block },
|
||||
Bytecode::Label { type_is_return_not_done_block });
|
||||
|
@ -2126,7 +2123,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> YieldExpression::genera
|
|||
received_completion_type_is_normal,
|
||||
received_completion_type,
|
||||
generator.add_constant(Value(to_underlying(Completion::Type::Normal))));
|
||||
generator.emit<Bytecode::Op::JumpIf>(
|
||||
generator.emit_jump_if(
|
||||
received_completion_type_is_normal,
|
||||
Bytecode::Label { normal_completion_continuation_block },
|
||||
Bytecode::Label { throw_completion_continuation_block });
|
||||
|
@ -2142,7 +2139,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> YieldExpression::genera
|
|||
generator.add_constant(Value(to_underlying(Completion::Type::Throw))));
|
||||
|
||||
// If type is not equal to "throw" or "normal", assume it's "return".
|
||||
generator.emit<Bytecode::Op::JumpIf>(
|
||||
generator.emit_jump_if(
|
||||
received_completion_type_is_throw,
|
||||
Bytecode::Label { throw_value_block },
|
||||
Bytecode::Label { return_value_block });
|
||||
|
@ -2178,7 +2175,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> IfStatement::generate_b
|
|||
generator.emit<Bytecode::Op::Mov>(dst, generator.add_constant(js_undefined()));
|
||||
|
||||
auto predicate = TRY(m_predicate->generate_bytecode(generator)).value();
|
||||
generator.emit<Bytecode::Op::JumpIf>(
|
||||
generator.emit_jump_if(
|
||||
predicate,
|
||||
Bytecode::Label { true_block },
|
||||
Bytecode::Label { false_block });
|
||||
|
@ -2241,7 +2238,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> ConditionalExpression::
|
|||
auto& end_block = generator.make_block();
|
||||
|
||||
auto test = TRY(m_test->generate_bytecode(generator)).value();
|
||||
generator.emit<Bytecode::Op::JumpIf>(
|
||||
generator.emit_jump_if(
|
||||
test,
|
||||
Bytecode::Label { true_block },
|
||||
Bytecode::Label { false_block });
|
||||
|
@ -2601,7 +2598,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> SwitchStatement::genera
|
|||
auto result = generator.allocate_register();
|
||||
generator.emit<Bytecode::Op::StrictlyEquals>(result, test_value, discriminant);
|
||||
next_test_block = test_blocks.dequeue();
|
||||
generator.emit<Bytecode::Op::JumpIf>(
|
||||
generator.emit_jump_if(
|
||||
result,
|
||||
Bytecode::Label { case_block },
|
||||
Bytecode::Label { *next_test_block });
|
||||
|
@ -2738,7 +2735,7 @@ static ScopedOperand generate_await(
|
|||
received_completion_type_is_normal,
|
||||
received_completion_type,
|
||||
generator.add_constant(Value(to_underlying(Completion::Type::Normal))));
|
||||
generator.emit<Bytecode::Op::JumpIf>(
|
||||
generator.emit_jump_if(
|
||||
received_completion_type_is_normal,
|
||||
Bytecode::Label { normal_completion_continuation_block },
|
||||
Bytecode::Label { throw_value_block });
|
||||
|
@ -2975,7 +2972,7 @@ static Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> for_in_of_body_e
|
|||
|
||||
// e. If done is true, return V.
|
||||
auto& loop_continue = generator.make_block();
|
||||
generator.emit<Bytecode::Op::JumpIf>(
|
||||
generator.emit_jump_if(
|
||||
done,
|
||||
Bytecode::Label { loop_end },
|
||||
Bytecode::Label { loop_continue });
|
||||
|
|
|
@ -36,6 +36,12 @@ public:
|
|||
u8 const* data() const { return m_buffer.data(); }
|
||||
size_t size() const { return m_buffer.size(); }
|
||||
|
||||
void rewind()
|
||||
{
|
||||
m_buffer.resize_and_keep_capacity(m_last_instruction_start_offset);
|
||||
m_terminated = false;
|
||||
}
|
||||
|
||||
void grow(size_t additional_size);
|
||||
|
||||
void terminate(Badge<Generator>) { m_terminated = true; }
|
||||
|
@ -55,6 +61,9 @@ public:
|
|||
auto const& this_() const { return m_this; }
|
||||
void set_this(ScopedOperand operand) { m_this = operand; }
|
||||
|
||||
[[nodiscard]] size_t last_instruction_start_offset() const { return m_last_instruction_start_offset; }
|
||||
void set_last_instruction_start_offset(size_t offset) { m_last_instruction_start_offset = offset; }
|
||||
|
||||
private:
|
||||
explicit BasicBlock(u32 index, String name);
|
||||
|
||||
|
@ -68,6 +77,8 @@ private:
|
|||
HashMap<size_t, SourceRecord> m_source_map;
|
||||
|
||||
Optional<ScopedOperand> m_this;
|
||||
|
||||
size_t m_last_instruction_start_offset { 0 };
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -833,4 +833,38 @@ ScopedOperand Generator::accumulator()
|
|||
return m_accumulator;
|
||||
}
|
||||
|
||||
bool Generator::fuse_compare_and_jump(ScopedOperand const& condition, Label true_target, Label false_target)
|
||||
{
|
||||
auto& last_instruction = *reinterpret_cast<Instruction const*>(m_current_basic_block->data() + m_current_basic_block->last_instruction_start_offset());
|
||||
|
||||
#define HANDLE_COMPARISON_OP(op_TitleCase, op_snake_case) \
|
||||
if (last_instruction.type() == Instruction::Type::op_TitleCase) { \
|
||||
auto& comparison = static_cast<Op::op_TitleCase const&>(last_instruction); \
|
||||
VERIFY(comparison.dst() == condition); \
|
||||
auto lhs = comparison.lhs(); \
|
||||
auto rhs = comparison.rhs(); \
|
||||
m_current_basic_block->rewind(); \
|
||||
emit<Op::Jump##op_TitleCase>(lhs, rhs, true_target, false_target); \
|
||||
return true; \
|
||||
}
|
||||
|
||||
JS_ENUMERATE_COMPARISON_OPS(HANDLE_COMPARISON_OP);
|
||||
#undef HANDLE_COMPARISON_OP
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Generator::emit_jump_if(ScopedOperand const& condition, Label true_target, Label false_target)
|
||||
{
|
||||
// NOTE: It's only safe to fuse compare-and-jump if the condition is a temporary with no other dependents.
|
||||
if (condition.operand().is_register()
|
||||
&& condition.ref_count() == 1
|
||||
&& m_current_basic_block->size() > 0) {
|
||||
if (fuse_compare_and_jump(condition, true_target, false_target))
|
||||
return;
|
||||
}
|
||||
|
||||
emit<Op::JumpIf>(condition, true_target, false_target);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -77,6 +77,7 @@ public:
|
|||
{
|
||||
VERIFY(!is_current_block_terminated());
|
||||
size_t slot_offset = m_current_basic_block->size();
|
||||
m_current_basic_block->set_last_instruction_start_offset(slot_offset);
|
||||
grow(sizeof(OpType));
|
||||
void* slot = m_current_basic_block->data() + slot_offset;
|
||||
new (slot) OpType(forward<Args>(args)...);
|
||||
|
@ -93,6 +94,7 @@ public:
|
|||
|
||||
size_t size_to_allocate = round_up_to_power_of_two(sizeof(OpType) + extra_slot_count * sizeof(ExtraSlotType), alignof(void*));
|
||||
size_t slot_offset = m_current_basic_block->size();
|
||||
m_current_basic_block->set_last_instruction_start_offset(slot_offset);
|
||||
grow(size_to_allocate);
|
||||
void* slot = m_current_basic_block->data() + slot_offset;
|
||||
new (slot) OpType(forward<Args>(args)...);
|
||||
|
@ -115,6 +117,8 @@ public:
|
|||
emit_with_extra_slots<OpType, Value>(extra_operand_slots, forward<Args>(args)...);
|
||||
}
|
||||
|
||||
void emit_jump_if(ScopedOperand const& condition, Label true_target, Label false_target);
|
||||
|
||||
struct ReferenceOperands {
|
||||
Optional<ScopedOperand> base {}; // [[Base]]
|
||||
Optional<ScopedOperand> referenced_name {}; // [[ReferencedName]] as an operand
|
||||
|
@ -309,6 +313,9 @@ private:
|
|||
|
||||
void grow(size_t);
|
||||
|
||||
// Returns true if a fused instruction was emitted.
|
||||
[[nodiscard]] bool fuse_compare_and_jump(ScopedOperand const& condition, Label true_target, Label false_target);
|
||||
|
||||
struct LabelableScope {
|
||||
Label bytecode_target;
|
||||
Vector<DeprecatedFlyString> language_label_set;
|
||||
|
|
|
@ -70,8 +70,16 @@
|
|||
O(IteratorToArray) \
|
||||
O(Jump) \
|
||||
O(JumpFalse) \
|
||||
O(JumpGreaterThan) \
|
||||
O(JumpGreaterThanEquals) \
|
||||
O(JumpIf) \
|
||||
O(JumpLessThan) \
|
||||
O(JumpLessThanEquals) \
|
||||
O(JumpLooselyEquals) \
|
||||
O(JumpLooselyInequals) \
|
||||
O(JumpNullish) \
|
||||
O(JumpStrictlyEquals) \
|
||||
O(JumpStrictlyInequals) \
|
||||
O(JumpTrue) \
|
||||
O(JumpUndefined) \
|
||||
O(LeaveFinally) \
|
||||
|
|
|
@ -362,6 +362,7 @@ FLATTEN_ON_CLANG void Interpreter::run_bytecode(size_t entry_point)
|
|||
#define SET_UP_LABEL(name) &&handle_##name,
|
||||
ENUMERATE_BYTECODE_OPS(SET_UP_LABEL)
|
||||
};
|
||||
#undef SET_UP_LABEL
|
||||
|
||||
#define DISPATCH_NEXT(name) \
|
||||
do { \
|
||||
|
@ -441,6 +442,26 @@ FLATTEN_ON_CLANG void Interpreter::run_bytecode(size_t entry_point)
|
|||
goto start;
|
||||
}
|
||||
|
||||
#define HANDLE_COMPARISON_OP(op_TitleCase, op_snake_case) \
|
||||
handle_Jump##op_TitleCase: \
|
||||
{ \
|
||||
auto& instruction = *reinterpret_cast<Op::Jump##op_TitleCase const*>(&bytecode[program_counter]); \
|
||||
auto result = op_snake_case(vm(), get(instruction.lhs()), get(instruction.rhs())); \
|
||||
if (result.is_error()) { \
|
||||
if (handle_exception(program_counter, *result.throw_completion().value()) == HandleExceptionResponse::ExitFromExecutable) \
|
||||
return; \
|
||||
goto start; \
|
||||
} \
|
||||
if (result.value().to_boolean()) \
|
||||
program_counter = instruction.true_target().address(); \
|
||||
else \
|
||||
program_counter = instruction.false_target().address(); \
|
||||
goto start; \
|
||||
}
|
||||
|
||||
JS_ENUMERATE_COMPARISON_OPS(HANDLE_COMPARISON_OP)
|
||||
#undef HANDLE_COMPARISON_OP
|
||||
|
||||
handle_JumpUndefined: {
|
||||
auto& instruction = *reinterpret_cast<Op::JumpUndefined const*>(&bytecode[program_counter]);
|
||||
if (get(instruction.condition()).is_undefined())
|
||||
|
@ -2142,6 +2163,18 @@ ByteString JumpNullish::to_byte_string_impl(Bytecode::Executable const& executab
|
|||
m_false_target);
|
||||
}
|
||||
|
||||
#define HANDLE_COMPARISON_OP(op_TitleCase, op_snake_case) \
|
||||
ByteString Jump##op_TitleCase::to_byte_string_impl(Bytecode::Executable const& executable) const \
|
||||
{ \
|
||||
return ByteString::formatted("Jump" #op_TitleCase " {}, {}, true:{}, false:{}", \
|
||||
format_operand("lhs"sv, m_lhs, executable), \
|
||||
format_operand("rhs"sv, m_rhs, executable), \
|
||||
m_true_target, \
|
||||
m_false_target); \
|
||||
}
|
||||
|
||||
JS_ENUMERATE_COMPARISON_OPS(HANDLE_COMPARISON_OP)
|
||||
|
||||
ByteString JumpUndefined::to_byte_string_impl(Bytecode::Executable const& executable) const
|
||||
{
|
||||
return ByteString::formatted("JumpUndefined {}, undefined:{} defined:{}",
|
||||
|
|
|
@ -1157,6 +1157,52 @@ private:
|
|||
Label m_target;
|
||||
};
|
||||
|
||||
#define JS_ENUMERATE_COMPARISON_OPS(X) \
|
||||
X(LessThan, less_than) \
|
||||
X(LessThanEquals, less_than_equals) \
|
||||
X(GreaterThan, greater_than) \
|
||||
X(GreaterThanEquals, greater_than_equals) \
|
||||
X(LooselyEquals, loosely_equals) \
|
||||
X(LooselyInequals, loosely_inequals) \
|
||||
X(StrictlyEquals, strict_equals) \
|
||||
X(StrictlyInequals, strict_inequals)
|
||||
|
||||
#define DECLARE_COMPARISON_OP(op_TitleCase, op_snake_case) \
|
||||
class Jump##op_TitleCase final : public Instruction { \
|
||||
public: \
|
||||
constexpr static bool IsTerminator = true; \
|
||||
\
|
||||
explicit Jump##op_TitleCase(Operand lhs, Operand rhs, Label true_target, Label false_target) \
|
||||
: Instruction(Type::Jump##op_TitleCase) \
|
||||
, m_lhs(lhs) \
|
||||
, m_rhs(rhs) \
|
||||
, m_true_target(true_target) \
|
||||
, m_false_target(false_target) \
|
||||
{ \
|
||||
} \
|
||||
\
|
||||
ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const; \
|
||||
ByteString to_byte_string_impl(Bytecode::Executable const&) const; \
|
||||
void visit_labels_impl(Function<void(Label&)> visitor) \
|
||||
{ \
|
||||
visitor(m_true_target); \
|
||||
visitor(m_false_target); \
|
||||
} \
|
||||
\
|
||||
Operand lhs() const { return m_lhs; } \
|
||||
Operand rhs() const { return m_rhs; } \
|
||||
auto& true_target() const { return m_true_target; } \
|
||||
auto& false_target() const { return m_false_target; } \
|
||||
\
|
||||
private: \
|
||||
Operand m_lhs; \
|
||||
Operand m_rhs; \
|
||||
Label m_true_target; \
|
||||
Label m_false_target; \
|
||||
};
|
||||
|
||||
JS_ENUMERATE_COMPARISON_OPS(DECLARE_COMPARISON_OP)
|
||||
|
||||
class JumpNullish final : public Instruction {
|
||||
public:
|
||||
constexpr static bool IsTerminator = true;
|
||||
|
|
|
@ -40,6 +40,8 @@ public:
|
|||
|
||||
[[nodiscard]] bool operator==(ScopedOperand const& other) const { return operand() == other.operand(); }
|
||||
|
||||
[[nodiscard]] size_t ref_count() const { return m_impl->ref_count(); }
|
||||
|
||||
private:
|
||||
NonnullRefPtr<ScopedOperandImpl> m_impl;
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue