From 563b402f04fadb3535d76992f388955544402a98 Mon Sep 17 00:00:00 2001 From: Ali Mohammad Pur Date: Mon, 9 Aug 2021 02:55:01 +0430 Subject: LibWasm: Replace the numeric operation macros with templated functions This should make debugging and profiling much better, at little to no runtime cost. Also moves off the operator definitions to a separate header, so it should also improve the editing experience quite a bit. --- .../AbstractMachine/BytecodeInterpreter.cpp | 554 ++++++++------------- .../LibWasm/AbstractMachine/BytecodeInterpreter.h | 6 + .../LibWasm/AbstractMachine/Configuration.cpp | 6 +- .../LibWasm/AbstractMachine/Configuration.h | 9 +- .../Libraries/LibWasm/AbstractMachine/Operators.h | 444 +++++++++++++++++ 5 files changed, 662 insertions(+), 357 deletions(-) create mode 100644 Userland/Libraries/LibWasm/AbstractMachine/Operators.h (limited to 'Userland') diff --git a/Userland/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.cpp b/Userland/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.cpp index dedde43160..4a08bf9866 100644 --- a/Userland/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.cpp +++ b/Userland/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.cpp @@ -8,10 +8,9 @@ #include #include #include +#include #include #include -#include -#include namespace Wasm { @@ -168,79 +167,58 @@ void BytecodeInterpreter::call_address(Configuration& configuration, FunctionAdd configuration.stack().entries().unchecked_append(move(entry)); } -#define BINARY_NUMERIC_OPERATION(type, operator, cast, ...) \ - do { \ - TRAP_IF_NOT(!configuration.stack().is_empty()); \ - auto rhs_entry = configuration.stack().pop(); \ - auto& lhs_entry = configuration.stack().peek(); \ - TRAP_IF_NOT(rhs_entry.has()); \ - TRAP_IF_NOT(lhs_entry.has()); \ - auto rhs = rhs_entry.get().to(); \ - auto lhs = lhs_entry.get().to(); \ - TRAP_IF_NOT(lhs.has_value()); \ - TRAP_IF_NOT(rhs.has_value()); \ - __VA_ARGS__; \ - auto result = lhs.value() operator rhs.value(); \ - dbgln_if(WASM_TRACE_DEBUG, "{} {} {} = {}", lhs.value(), #operator, rhs.value(), result); \ - configuration.stack().peek() = Value(cast(result)); \ - return; \ - } while (false) - -#define OVF_CHECKED_BINARY_NUMERIC_OPERATION(type, operator, cast, ...) \ - do { \ - TRAP_IF_NOT(!configuration.stack().is_empty()); \ - auto rhs_entry = configuration.stack().pop(); \ - auto& lhs_entry = configuration.stack().peek(); \ - TRAP_IF_NOT(rhs_entry.has()); \ - TRAP_IF_NOT(lhs_entry.has()); \ - auto rhs = rhs_entry.get().to(); \ - auto ulhs = lhs_entry.get().to(); \ - TRAP_IF_NOT(ulhs.has_value()); \ - TRAP_IF_NOT(rhs.has_value()); \ - dbgln_if(WASM_TRACE_DEBUG, "{} {} {} = ??", ulhs.value(), #operator, rhs.value()); \ - __VA_ARGS__; \ - Checked lhs = ulhs.value(); \ - lhs operator##= rhs.value(); \ - TRAP_IF_NOT(!lhs.has_overflow()); \ - auto result = lhs.value(); \ - dbgln_if(WASM_TRACE_DEBUG, "{} {} {} = {}", ulhs.value(), #operator, rhs.value(), result); \ - configuration.stack().peek() = Value(cast(result)); \ - return; \ - } while (false) - -#define BINARY_PREFIX_NUMERIC_OPERATION(type, operation, cast, ...) \ - do { \ - TRAP_IF_NOT(!configuration.stack().is_empty()); \ - auto rhs_entry = configuration.stack().pop(); \ - auto& lhs_entry = configuration.stack().peek(); \ - TRAP_IF_NOT(rhs_entry.has()); \ - TRAP_IF_NOT(lhs_entry.has()); \ - auto rhs = rhs_entry.get().to(); \ - auto lhs = lhs_entry.get().to(); \ - TRAP_IF_NOT(lhs.has_value()); \ - TRAP_IF_NOT(rhs.has_value()); \ - __VA_ARGS__; \ - auto result = operation(lhs.value(), rhs.value()); \ - dbgln_if(WASM_TRACE_DEBUG, "{}({} {}) = {}", #operation, lhs.value(), rhs.value(), result); \ - configuration.stack().peek() = Value(cast(result)); \ - return; \ - } while (false) - -#define UNARY_MAP(pop_type, operation, ...) \ - do { \ - TRAP_IF_NOT(!configuration.stack().is_empty()); \ - auto& entry = configuration.stack().peek(); \ - TRAP_IF_NOT(entry.has()); \ - auto value = entry.get().to(); \ - TRAP_IF_NOT(value.has_value()); \ - auto result = operation(value.value()); \ - dbgln_if(WASM_TRACE_DEBUG, "map({}) {} = {}", #operation, value.value(), result); \ - configuration.stack().peek() = Value(__VA_ARGS__(result)); \ - return; \ - } while (false) +template +void BytecodeInterpreter::binary_numeric_operation(Configuration& configuration) +{ + TRAP_IF_NOT(!configuration.stack().is_empty()); + auto rhs_entry = configuration.stack().pop(); + auto& lhs_entry = configuration.stack().peek(); + auto rhs_ptr = rhs_entry.get_pointer(); + auto lhs_ptr = lhs_entry.get_pointer(); + TRAP_IF_NOT(rhs_ptr); + TRAP_IF_NOT(lhs_ptr); + auto rhs = rhs_ptr->to(); + auto lhs = lhs_ptr->to(); + TRAP_IF_NOT(lhs.has_value()); + TRAP_IF_NOT(rhs.has_value()); + PushType result; + auto call_result = Operator {}(lhs.value(), rhs.value()); + if constexpr (IsSpecializationOf) { + if (call_result.is_error()) { + trap_if_not(false, call_result.error()); + return; + } + result = call_result.release_value(); + } else { + result = call_result; + } + dbgln_if(WASM_TRACE_DEBUG, "{} {} {} = {}", lhs.value(), Operator::name(), rhs.value(), result); + configuration.stack().peek() = Value(result); +} -#define UNARY_NUMERIC_OPERATION(type, operation) \ - UNARY_MAP(type, operation, type) +template +void BytecodeInterpreter::unary_operation(Configuration& configuration) +{ + TRAP_IF_NOT(!configuration.stack().is_empty()); + auto& entry = configuration.stack().peek(); + auto entry_ptr = entry.get_pointer(); + TRAP_IF_NOT(entry_ptr); + auto value = entry_ptr->to(); + TRAP_IF_NOT(value.has_value()); + auto call_result = Operator {}(*value); + PushType result; + if constexpr (IsSpecializationOf) { + if (call_result.is_error()) { + trap_if_not(false, call_result.error()); + return; + } + result = call_result.release_value(); + } else { + result = call_result; + } + dbgln_if(WASM_TRACE_DEBUG, "map({}) {} = {}", Operator::name(), *value, result); + configuration.stack().peek() = Value(result); +} #define LOAD_AND_PUSH(read_type, push_type) \ do { \ @@ -387,115 +365,6 @@ Vector BytecodeInterpreter::pop_values(Configuration& configuration, size return results; } -template -ALWAYS_INLINE static T rotl(T value, R shift) -{ - // generates a single 'rol' instruction if shift is positive - // otherwise generate a `ror` - auto const mask = CHAR_BIT * sizeof(T) - 1; - shift &= mask; - return (value << shift) | (value >> ((-shift) & mask)); -} - -template -ALWAYS_INLINE static T rotr(T value, R shift) -{ - // generates a single 'ror' instruction if shift is positive - // otherwise generate a `rol` - auto const mask = CHAR_BIT * sizeof(T) - 1; - shift &= mask; - return (value >> shift) | (value << ((-shift) & mask)); -} - -template -ALWAYS_INLINE static i32 clz(T value) -{ - if (value == 0) - return sizeof(T) * CHAR_BIT; - - if constexpr (sizeof(T) == 4) - return __builtin_clz(value); - else if constexpr (sizeof(T) == 8) - return __builtin_clzll(value); - else - VERIFY_NOT_REACHED(); -} - -template -ALWAYS_INLINE static i32 ctz(T value) -{ - if (value == 0) - return sizeof(T) * CHAR_BIT; - - if constexpr (sizeof(T) == 4) - return __builtin_ctz(value); - else if constexpr (sizeof(T) == 8) - return __builtin_ctzll(value); - else - VERIFY_NOT_REACHED(); -} - -template -ALWAYS_INLINE static OutputT extend_signed(InputT value) -{ - // Note: C++ will take care of sign extension. - return value; -} - -template -ALWAYS_INLINE static TruncT saturating_truncate(T value) -{ - if (isnan(value)) - return 0; - - if (isinf(value)) { - if (value < 0) - return NumericLimits::min(); - return NumericLimits::max(); - } - - constexpr auto convert = [](auto truncated_value) { - if (truncated_value < NumericLimits::min()) - return NumericLimits::min(); - if (static_cast(truncated_value) > static_cast(NumericLimits::max())) - return NumericLimits::max(); - return static_cast(truncated_value); - }; - - if constexpr (IsSame) - return convert(truncf(value)); - else - return convert(trunc(value)); -} - -template -ALWAYS_INLINE static T float_max(T lhs, T rhs) -{ - if (isnan(lhs)) - return lhs; - if (isnan(rhs)) - return rhs; - if (isinf(lhs)) - return lhs > 0 ? lhs : rhs; - if (isinf(rhs)) - return rhs > 0 ? rhs : lhs; - return max(lhs, rhs); -} - -template -ALWAYS_INLINE static T float_min(T lhs, T rhs) -{ - if (isnan(lhs)) - return lhs; - if (isnan(rhs)) - return rhs; - if (isinf(lhs)) - return lhs > 0 ? rhs : lhs; - if (isinf(rhs)) - return rhs > 0 ? lhs : rhs; - return min(lhs, rhs); -} - void BytecodeInterpreter::interpret(Configuration& configuration, InstructionPointer& ip, Instruction const& instruction) { dbgln_if(WASM_TRACE_DEBUG, "Executing instruction {} at ip {}", instruction_name(instruction.opcode()), ip.value()); @@ -570,20 +439,16 @@ void BytecodeInterpreter::interpret(Configuration& configuration, InstructionPoi } case Instructions::structured_end.value(): case Instructions::structured_else.value(): { - auto label = configuration.nth_label(0); - TRAP_IF_NOT(label.has_value()); - size_t end = configuration.stack().size() - label->arity() - 1; - size_t start = end; - while (start > 0 && start < configuration.stack().size() && !configuration.stack().entries()[start].has