From da107ec9fb580da58ff909717b01fad3bd12edec Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Tue, 20 Feb 2024 11:59:46 +0100 Subject: [PATCH] LibJS/Bytecode: Add fast paths for many binary expression instructions By handling common cases like Int32 arithmetic directly in the instruction handler, we can avoid the cost of calling the generic helper functions in Value.cpp. --- .../Libraries/LibJS/Bytecode/Interpreter.cpp | 204 +++++++++++++++++- Userland/Libraries/LibJS/Bytecode/Op.h | 49 +++-- 2 files changed, 221 insertions(+), 32 deletions(-) diff --git a/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp b/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp index fc8f8c47c7..de2cdbeff3 100644 --- a/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp @@ -608,7 +608,7 @@ static ThrowCompletionOr strict_equals(VM&, Value src1, Value src2) return Value(is_strictly_equal(src1, src2)); } -#define JS_DEFINE_COMMON_BINARY_OP(OpTitleCase, op_snake_case) \ +#define JS_DEFINE_EXECUTE_FOR_COMMON_BINARY_OP(OpTitleCase, op_snake_case) \ ThrowCompletionOr OpTitleCase::execute_impl(Bytecode::Interpreter& interpreter) const \ { \ auto& vm = interpreter.vm(); \ @@ -616,16 +616,202 @@ static ThrowCompletionOr strict_equals(VM&, Value src1, Value src2) auto rhs = interpreter.get(m_rhs); \ interpreter.set(m_dst, TRY(op_snake_case(vm, lhs, rhs))); \ return {}; \ - } \ - ByteString OpTitleCase::to_byte_string_impl(Bytecode::Executable const& executable) const \ - { \ - return ByteString::formatted(#OpTitleCase " {}, {}, {}", \ - format_operand("dst"sv, m_dst, executable), \ - format_operand("lhs"sv, m_lhs, executable), \ - format_operand("rhs"sv, m_rhs, executable)); \ } -JS_ENUMERATE_COMMON_BINARY_OPS(JS_DEFINE_COMMON_BINARY_OP) +#define JS_DEFINE_TO_BYTE_STRING_FOR_COMMON_BINARY_OP(OpTitleCase, op_snake_case) \ + ByteString OpTitleCase::to_byte_string_impl(Bytecode::Executable const& executable) const \ + { \ + return ByteString::formatted(#OpTitleCase " {}, {}, {}", \ + format_operand("dst"sv, m_dst, executable), \ + format_operand("lhs"sv, m_lhs, executable), \ + format_operand("rhs"sv, m_rhs, executable)); \ + } + +JS_ENUMERATE_COMMON_BINARY_OPS_WITHOUT_FAST_PATH(JS_DEFINE_EXECUTE_FOR_COMMON_BINARY_OP) +JS_ENUMERATE_COMMON_BINARY_OPS_WITHOUT_FAST_PATH(JS_DEFINE_TO_BYTE_STRING_FOR_COMMON_BINARY_OP) +JS_ENUMERATE_COMMON_BINARY_OPS_WITH_FAST_PATH(JS_DEFINE_TO_BYTE_STRING_FOR_COMMON_BINARY_OP) + +ThrowCompletionOr Add::execute_impl(Bytecode::Interpreter& interpreter) const +{ + auto& vm = interpreter.vm(); + auto const lhs = interpreter.get(m_lhs); + auto const rhs = interpreter.get(m_rhs); + + if (lhs.is_number() && rhs.is_number()) { + if (lhs.is_int32() && rhs.is_int32()) { + if (!Checked::addition_would_overflow(lhs.as_i32(), rhs.as_i32())) { + interpreter.set(m_dst, Value(lhs.as_i32() + rhs.as_i32())); + return {}; + } + } + interpreter.set(m_dst, Value(lhs.as_double() + rhs.as_double())); + return {}; + } + + interpreter.set(m_dst, TRY(add(vm, lhs, rhs))); + return {}; +} + +ThrowCompletionOr Mul::execute_impl(Bytecode::Interpreter& interpreter) const +{ + auto& vm = interpreter.vm(); + auto const lhs = interpreter.get(m_lhs); + auto const rhs = interpreter.get(m_rhs); + + if (lhs.is_number() && rhs.is_number()) { + if (lhs.is_int32() && rhs.is_int32()) { + if (!Checked::multiplication_would_overflow(lhs.as_i32(), rhs.as_i32())) { + interpreter.set(m_dst, Value(lhs.as_i32() * rhs.as_i32())); + return {}; + } + } + interpreter.set(m_dst, Value(lhs.as_double() * rhs.as_double())); + return {}; + } + + interpreter.set(m_dst, TRY(mul(vm, lhs, rhs))); + return {}; +} + +ThrowCompletionOr Sub::execute_impl(Bytecode::Interpreter& interpreter) const +{ + auto& vm = interpreter.vm(); + auto const lhs = interpreter.get(m_lhs); + auto const rhs = interpreter.get(m_rhs); + + if (lhs.is_number() && rhs.is_number()) { + if (lhs.is_int32() && rhs.is_int32()) { + if (!Checked::addition_would_overflow(lhs.as_i32(), -rhs.as_i32())) { + interpreter.set(m_dst, Value(lhs.as_i32() - rhs.as_i32())); + return {}; + } + } + interpreter.set(m_dst, Value(lhs.as_double() - rhs.as_double())); + return {}; + } + + interpreter.set(m_dst, TRY(sub(vm, lhs, rhs))); + return {}; +} + +ThrowCompletionOr BitwiseXor::execute_impl(Bytecode::Interpreter& interpreter) const +{ + auto& vm = interpreter.vm(); + auto const lhs = interpreter.get(m_lhs); + auto const rhs = interpreter.get(m_rhs); + if (lhs.is_int32() && rhs.is_int32()) { + interpreter.set(m_dst, Value(lhs.as_i32() ^ rhs.as_i32())); + return {}; + } + interpreter.set(m_dst, TRY(bitwise_xor(vm, lhs, rhs))); + return {}; +} + +ThrowCompletionOr BitwiseAnd::execute_impl(Bytecode::Interpreter& interpreter) const +{ + auto& vm = interpreter.vm(); + auto const lhs = interpreter.get(m_lhs); + auto const rhs = interpreter.get(m_rhs); + if (lhs.is_int32() && rhs.is_int32()) { + interpreter.set(m_dst, Value(lhs.as_i32() & rhs.as_i32())); + return {}; + } + interpreter.set(m_dst, TRY(bitwise_and(vm, lhs, rhs))); + return {}; +} + +ThrowCompletionOr BitwiseOr::execute_impl(Bytecode::Interpreter& interpreter) const +{ + auto& vm = interpreter.vm(); + auto const lhs = interpreter.get(m_lhs); + auto const rhs = interpreter.get(m_rhs); + if (lhs.is_int32() && rhs.is_int32()) { + interpreter.set(m_dst, Value(lhs.as_i32() | rhs.as_i32())); + return {}; + } + interpreter.set(m_dst, TRY(bitwise_or(vm, lhs, rhs))); + return {}; +} + +ThrowCompletionOr UnsignedRightShift::execute_impl(Bytecode::Interpreter& interpreter) const +{ + auto& vm = interpreter.vm(); + auto const lhs = interpreter.get(m_lhs); + auto const rhs = interpreter.get(m_rhs); + if (lhs.is_int32() && rhs.is_int32() && lhs.as_i32() >= 0 && rhs.as_i32() >= 0) { + auto const shift_count = static_cast(rhs.as_i32()) % 32; + interpreter.set(m_dst, Value(static_cast(lhs.as_i32()) >> shift_count)); + return {}; + } + interpreter.set(m_dst, TRY(unsigned_right_shift(vm, lhs, rhs))); + return {}; +} + +ThrowCompletionOr RightShift::execute_impl(Bytecode::Interpreter& interpreter) const +{ + auto& vm = interpreter.vm(); + auto const lhs = interpreter.get(m_lhs); + auto const rhs = interpreter.get(m_rhs); + if (lhs.is_int32() && rhs.is_int32() && rhs.as_i32() >= 0) { + auto const shift_count = static_cast(rhs.as_i32()) % 32; + interpreter.set(m_dst, Value(lhs.as_i32() >> shift_count)); + return {}; + } + interpreter.set(m_dst, TRY(right_shift(vm, lhs, rhs))); + return {}; +} + +ThrowCompletionOr LessThan::execute_impl(Bytecode::Interpreter& interpreter) const +{ + auto& vm = interpreter.vm(); + auto const lhs = interpreter.get(m_lhs); + auto const rhs = interpreter.get(m_rhs); + if (lhs.is_int32() && rhs.is_int32()) { + interpreter.set(m_dst, Value(lhs.as_i32() < rhs.as_i32())); + return {}; + } + interpreter.set(m_dst, TRY(less_than(vm, lhs, rhs))); + return {}; +} + +ThrowCompletionOr LessThanEquals::execute_impl(Bytecode::Interpreter& interpreter) const +{ + auto& vm = interpreter.vm(); + auto const lhs = interpreter.get(m_lhs); + auto const rhs = interpreter.get(m_rhs); + if (lhs.is_int32() && rhs.is_int32()) { + interpreter.set(m_dst, Value(lhs.as_i32() <= rhs.as_i32())); + return {}; + } + interpreter.set(m_dst, TRY(less_than_equals(vm, lhs, rhs))); + return {}; +} + +ThrowCompletionOr GreaterThan::execute_impl(Bytecode::Interpreter& interpreter) const +{ + auto& vm = interpreter.vm(); + auto const lhs = interpreter.get(m_lhs); + auto const rhs = interpreter.get(m_rhs); + if (lhs.is_int32() && rhs.is_int32()) { + interpreter.set(m_dst, Value(lhs.as_i32() > rhs.as_i32())); + return {}; + } + interpreter.set(m_dst, TRY(greater_than(vm, lhs, rhs))); + return {}; +} + +ThrowCompletionOr GreaterThanEquals::execute_impl(Bytecode::Interpreter& interpreter) const +{ + auto& vm = interpreter.vm(); + auto const lhs = interpreter.get(m_lhs); + auto const rhs = interpreter.get(m_rhs); + if (lhs.is_int32() && rhs.is_int32()) { + interpreter.set(m_dst, Value(lhs.as_i32() >= rhs.as_i32())); + return {}; + } + interpreter.set(m_dst, TRY(greater_than_equals(vm, lhs, rhs))); + return {}; +} static ThrowCompletionOr not_(VM&, Value value) { diff --git a/Userland/Libraries/LibJS/Bytecode/Op.h b/Userland/Libraries/LibJS/Bytecode/Op.h index f78690f5b0..89ceaaa474 100644 --- a/Userland/Libraries/LibJS/Bytecode/Op.h +++ b/Userland/Libraries/LibJS/Bytecode/Op.h @@ -51,30 +51,32 @@ private: Operand m_src; }; -#define JS_ENUMERATE_COMMON_BINARY_OPS(O) \ - O(Add, add) \ - O(Sub, sub) \ - O(Mul, mul) \ - O(Div, div) \ - O(Exp, exp) \ - O(Mod, mod) \ - O(In, in) \ - O(InstanceOf, instance_of) \ - O(GreaterThan, greater_than) \ - O(GreaterThanEquals, greater_than_equals) \ - O(LessThan, less_than) \ - O(LessThanEquals, less_than_equals) \ - O(LooselyInequals, loosely_inequals) \ - O(LooselyEquals, loosely_equals) \ - O(StrictlyInequals, strict_inequals) \ - O(StrictlyEquals, strict_equals) \ - O(BitwiseAnd, bitwise_and) \ - O(BitwiseOr, bitwise_or) \ - O(BitwiseXor, bitwise_xor) \ - O(LeftShift, left_shift) \ - O(RightShift, right_shift) \ +#define JS_ENUMERATE_COMMON_BINARY_OPS_WITH_FAST_PATH(O) \ + O(Add, add) \ + O(BitwiseAnd, bitwise_and) \ + O(BitwiseOr, bitwise_or) \ + O(BitwiseXor, bitwise_xor) \ + O(GreaterThan, greater_than) \ + O(GreaterThanEquals, greater_than_equals) \ + O(LessThan, less_than) \ + O(LessThanEquals, less_than_equals) \ + O(Mul, mul) \ + O(RightShift, right_shift) \ + O(Sub, sub) \ O(UnsignedRightShift, unsigned_right_shift) +#define JS_ENUMERATE_COMMON_BINARY_OPS_WITHOUT_FAST_PATH(O) \ + O(Div, div) \ + O(Exp, exp) \ + O(Mod, mod) \ + O(In, in) \ + O(InstanceOf, instance_of) \ + O(LooselyInequals, loosely_inequals) \ + O(LooselyEquals, loosely_equals) \ + O(StrictlyInequals, strict_inequals) \ + O(StrictlyEquals, strict_equals) \ + O(LeftShift, left_shift) + #define JS_DECLARE_COMMON_BINARY_OP(OpTitleCase, op_snake_case) \ class OpTitleCase final : public Instruction { \ public: \ @@ -99,7 +101,8 @@ private: Operand m_rhs; \ }; -JS_ENUMERATE_COMMON_BINARY_OPS(JS_DECLARE_COMMON_BINARY_OP) +JS_ENUMERATE_COMMON_BINARY_OPS_WITHOUT_FAST_PATH(JS_DECLARE_COMMON_BINARY_OP) +JS_ENUMERATE_COMMON_BINARY_OPS_WITH_FAST_PATH(JS_DECLARE_COMMON_BINARY_OP) #undef JS_DECLARE_COMMON_BINARY_OP #define JS_ENUMERATE_COMMON_UNARY_OPS(O) \