1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 20:07:35 +00:00

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.
This commit is contained in:
Andreas Kling 2024-02-20 11:59:46 +01:00
parent 9d9b737a58
commit da107ec9fb
2 changed files with 221 additions and 32 deletions

View file

@ -608,7 +608,7 @@ static ThrowCompletionOr<Value> 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<void> OpTitleCase::execute_impl(Bytecode::Interpreter& interpreter) const \
{ \
auto& vm = interpreter.vm(); \
@ -616,7 +616,9 @@ static ThrowCompletionOr<Value> 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 {}; \
} \
}
#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 " {}, {}, {}", \
@ -625,7 +627,191 @@ static ThrowCompletionOr<Value> strict_equals(VM&, Value src1, Value src2)
format_operand("rhs"sv, m_rhs, executable)); \
}
JS_ENUMERATE_COMMON_BINARY_OPS(JS_DEFINE_COMMON_BINARY_OP)
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<void> 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<i32>::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<void> 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<i32>::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<void> 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<i32>::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<void> 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<void> 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<void> 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<void> 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<u32>(rhs.as_i32()) % 32;
interpreter.set(m_dst, Value(static_cast<u32>(lhs.as_i32()) >> shift_count));
return {};
}
interpreter.set(m_dst, TRY(unsigned_right_shift(vm, lhs, rhs)));
return {};
}
ThrowCompletionOr<void> 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<u32>(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<void> 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<void> 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<void> 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<void> 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<Value> not_(VM&, Value value)
{

View file

@ -51,29 +51,31 @@ private:
Operand m_src;
};
#define JS_ENUMERATE_COMMON_BINARY_OPS(O) \
#define JS_ENUMERATE_COMMON_BINARY_OPS_WITH_FAST_PATH(O) \
O(Add, add) \
O(Sub, sub) \
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(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) \
O(UnsignedRightShift, unsigned_right_shift)
O(LeftShift, left_shift)
#define JS_DECLARE_COMMON_BINARY_OP(OpTitleCase, op_snake_case) \
class OpTitleCase final : public Instruction { \
@ -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) \