diff --git a/Userland/Libraries/LibJS/Bytecode/Instruction.h b/Userland/Libraries/LibJS/Bytecode/Instruction.h index 38ed1dc8fa..af25fbe784 100644 --- a/Userland/Libraries/LibJS/Bytecode/Instruction.h +++ b/Userland/Libraries/LibJS/Bytecode/Instruction.h @@ -69,6 +69,7 @@ O(IteratorToArray) \ O(Jump) \ O(JumpIf) \ + O(JumpIfNot) \ O(JumpGreaterThan) \ O(JumpGreaterThanEquals) \ O(JumpLessThan) \ diff --git a/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp b/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp index fb6d86ee41..cd34b86b1d 100644 --- a/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp @@ -344,6 +344,12 @@ void Interpreter::run_bytecode() else m_current_block = &static_cast(instruction).false_target()->block(); goto start; + case Instruction::Type::JumpIfNot: + if (!get(static_cast(instruction).condition()).to_boolean()) + m_current_block = &static_cast(instruction).true_target()->block(); + else + m_current_block = &static_cast(instruction).false_target()->block(); + goto start; #define JS_HANDLE_FUSABLE_BINARY_JUMP(PreOp, int32_operator, slow_case) \ case Instruction::Type::Jump##PreOp: { \ @@ -1252,6 +1258,12 @@ ThrowCompletionOr JumpIf::execute_impl(Bytecode::Interpreter&) const __builtin_unreachable(); } +ThrowCompletionOr JumpIfNot::execute_impl(Bytecode::Interpreter&) const +{ + // Handled in the interpreter loop. + __builtin_unreachable(); +} + #define JS_DEFINE_FUSABLE_BINARY_OP(PreOp, ...) \ ThrowCompletionOr Jump##PreOp::execute_impl(Bytecode::Interpreter&) const { __builtin_unreachable(); } \ \ @@ -1932,6 +1944,14 @@ ByteString JumpIf::to_byte_string_impl(Bytecode::Executable const& executable) c true_string, false_string); } +ByteString JumpIfNot::to_byte_string_impl(Bytecode::Executable const& executable) const +{ + return ByteString::formatted("JumpIfNot {}, \033[32mtrue\033[0m:{} \033[32mfalse\033[0m:{}", + format_operand("condition"sv, m_condition, executable), + *m_true_target, + *m_false_target); +} + ByteString JumpNullish::to_byte_string_impl(Bytecode::Executable const& executable) const { auto true_string = m_true_target.has_value() ? ByteString::formatted("{}", *m_true_target) : ""; diff --git a/Userland/Libraries/LibJS/Bytecode/Op.h b/Userland/Libraries/LibJS/Bytecode/Op.h index 27f206c3dc..035010f14a 100644 --- a/Userland/Libraries/LibJS/Bytecode/Op.h +++ b/Userland/Libraries/LibJS/Bytecode/Op.h @@ -1123,6 +1123,23 @@ private: Operand m_condition; }; +class JumpIfNot final : public Jump { +public: + explicit JumpIfNot(Operand condition, Label true_target, Label false_target) + : Jump(Type::JumpIfNot, move(true_target), move(false_target), sizeof(*this)) + , m_condition(condition) + { + } + + ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; + ByteString to_byte_string_impl(Bytecode::Executable const&) const; + + Operand condition() const { return m_condition; } + +private: + Operand m_condition; +}; + // NOTE: The raw operator is used for comparing two Int32 values. #define JS_ENUMERATE_FUSABLE_BINARY_OPS(X) \ X(GreaterThan, >, greater_than) \ diff --git a/Userland/Libraries/LibJS/Bytecode/Pass/GenerateCFG.cpp b/Userland/Libraries/LibJS/Bytecode/Pass/GenerateCFG.cpp index 4c311d2096..83fc01bf94 100644 --- a/Userland/Libraries/LibJS/Bytecode/Pass/GenerateCFG.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Pass/GenerateCFG.cpp @@ -71,6 +71,7 @@ static void generate_cfg_for_block(BasicBlock const& current_block, PassPipeline #undef JS_ENUMERATE_FUSABLE_BINARY_OP case JumpIf: + case JumpIfNot: case JumpNullish: case JumpUndefined: { // FIXME: It would be nice if we could avoid this copy, if we know that the unwind context stays the same in both paths diff --git a/Userland/Libraries/LibJS/Bytecode/Pass/Peephole.cpp b/Userland/Libraries/LibJS/Bytecode/Pass/Peephole.cpp index cfc285ad21..2c5b53693c 100644 --- a/Userland/Libraries/LibJS/Bytecode/Pass/Peephole.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Pass/Peephole.cpp @@ -38,6 +38,20 @@ void Peephole::perform(PassPipelineExecutable& executable) if (next_instruction.type() == Instruction::Type::JumpIf) { auto const& jump = static_cast(next_instruction); + if (instruction.type() == Instruction::Type::Not) { + auto const& not_ = static_cast(instruction); + VERIFY(jump.condition() == not_.dst()); + new_block->append( + not_.source_record().source_start_offset, + not_.source_record().source_end_offset, + not_.src(), + *jump.true_target(), + *jump.false_target()); + ++it; + VERIFY(it.at_end()); + continue; + } + #define DO_FUSE_JUMP(PreOp, ...) \ if (instruction.type() == Instruction::Type::PreOp) { \ auto const& compare = static_cast(instruction); \