| /* |
| * Copyright (C) 2019-2023 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "WasmBBQJIT.h" |
| |
| #include "B3Common.h" |
| #include "B3ValueRep.h" |
| #include "BinarySwitch.h" |
| #include "BytecodeStructs.h" |
| #include "CCallHelpers.h" |
| #include "CPU.h" |
| #include "CompilerTimingScope.h" |
| #include "GPRInfo.h" |
| #include "JSCast.h" |
| #include "JSWebAssemblyException.h" |
| #include "MacroAssembler.h" |
| #include "RegisterSet.h" |
| #include "WasmB3IRGenerator.h" |
| #include "WasmBBQDisassembler.h" |
| #include "WasmCallingConvention.h" |
| #include "WasmCompilationMode.h" |
| #include "WasmFormat.h" |
| #include "WasmFunctionParser.h" |
| #include "WasmIRGeneratorHelpers.h" |
| #include "WasmInstance.h" |
| #include "WasmMemoryInformation.h" |
| #include "WasmModule.h" |
| #include "WasmModuleInformation.h" |
| #include "WasmOperations.h" |
| #include "WasmOps.h" |
| #include "WasmThunks.h" |
| #include "WasmTypeDefinition.h" |
| #include <wtf/Assertions.h> |
| #include <wtf/Compiler.h> |
| #include <wtf/HashFunctions.h> |
| #include <wtf/HashMap.h> |
| #include <wtf/MathExtras.h> |
| #include <wtf/PlatformRegisters.h> |
| #include <wtf/SmallSet.h> |
| #include <wtf/StdLibExtras.h> |
| |
| #if ENABLE(WEBASSEMBLY_BBQJIT) |
| |
| namespace JSC { namespace Wasm { |
| |
| class BBQJIT { |
| public: |
| using ErrorType = String; |
| using PartialResult = Expected<void, ErrorType>; |
| using Address = MacroAssembler::Address; |
| using BaseIndex = MacroAssembler::BaseIndex; |
| using Imm32 = MacroAssembler::Imm32; |
| using Imm64 = MacroAssembler::Imm64; |
| using TrustedImm32 = MacroAssembler::TrustedImm32; |
| using TrustedImm64 = MacroAssembler::TrustedImm64; |
| using TrustedImmPtr = MacroAssembler::TrustedImmPtr; |
| using RelationalCondition = MacroAssembler::RelationalCondition; |
| using ResultCondition = MacroAssembler::ResultCondition; |
| using DoubleCondition = MacroAssembler::DoubleCondition; |
| using StatusCondition = MacroAssembler::StatusCondition; |
| using Jump = MacroAssembler::Jump; |
| using JumpList = MacroAssembler::JumpList; |
| using DataLabelPtr = MacroAssembler::DataLabelPtr; |
| |
| // Functions can have up to 1000000 instructions, so 32 bits is a sensible maximum number of stack items or locals. |
| using LocalOrTempIndex = uint32_t; |
| |
| static constexpr unsigned LocalIndexBits = 21; |
| static_assert(maxFunctionLocals < 1 << LocalIndexBits); |
| |
| static constexpr GPRReg wasmScratchGPR = GPRInfo::nonPreservedNonArgumentGPR0; // Scratch registers to hold temporaries in operations. |
| static constexpr FPRReg wasmScratchFPR = FPRInfo::nonPreservedNonArgumentFPR0; |
| |
| #if CPU(X86) || CPU(X86_64) |
| static constexpr GPRReg shiftRCX = X86Registers::ecx; |
| #else |
| static constexpr GPRReg shiftRCX = InvalidGPRReg; |
| #endif |
| |
| private: |
| struct Location { |
| enum Kind : uint8_t { |
| None = 0, |
| Stack = 1, |
| Gpr = 2, |
| Fpr = 3, |
| Global = 4, |
| StackArgument = 5 |
| }; |
| |
| Location() |
| : m_kind(None) |
| { } |
| |
| static Location none() |
| { |
| return Location(); |
| } |
| |
| static Location fromStack(int32_t stackOffset) |
| { |
| Location loc; |
| loc.m_kind = Stack; |
| loc.m_offset = stackOffset; |
| return loc; |
| } |
| |
| static Location fromStackArgument(int32_t stackOffset) |
| { |
| Location loc; |
| loc.m_kind = StackArgument; |
| loc.m_offset = stackOffset; |
| return loc; |
| } |
| |
| static Location fromGPR(GPRReg gpr) |
| { |
| Location loc; |
| loc.m_kind = Gpr; |
| loc.m_gpr = gpr; |
| return loc; |
| } |
| |
| static Location fromFPR(FPRReg fpr) |
| { |
| Location loc; |
| loc.m_kind = Fpr; |
| loc.m_fpr = fpr; |
| return loc; |
| } |
| |
| static Location fromGlobal(int32_t globalOffset) |
| { |
| Location loc; |
| loc.m_kind = Global; |
| loc.m_offset = globalOffset; |
| return loc; |
| } |
| |
| static Location fromArgumentLocation(ArgumentLocation argLocation) |
| { |
| switch (argLocation.location.kind()) { |
| case ValueLocation::Kind::GPRRegister: |
| return Location::fromGPR(argLocation.location.jsr().gpr()); |
| case ValueLocation::Kind::FPRRegister: |
| return Location::fromFPR(argLocation.location.fpr()); |
| case ValueLocation::Kind::StackArgument: |
| return Location::fromStackArgument(argLocation.location.offsetFromSP()); |
| case ValueLocation::Kind::Stack: |
| return Location::fromStack(argLocation.location.offsetFromFP()); |
| } |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| |
| bool isNone() const |
| { |
| return m_kind == None; |
| } |
| |
| bool isGPR() const |
| { |
| return m_kind == Gpr; |
| } |
| |
| bool isFPR() const |
| { |
| return m_kind == Fpr; |
| } |
| |
| bool isRegister() const |
| { |
| return isGPR() || isFPR(); |
| } |
| |
| bool isStack() const |
| { |
| return m_kind == Stack; |
| } |
| |
| bool isStackArgument() const |
| { |
| return m_kind == StackArgument; |
| } |
| |
| bool isGlobal() const |
| { |
| return m_kind == Global; |
| } |
| |
| bool isMemory() const |
| { |
| return isStack() || isStackArgument() || isGlobal(); |
| } |
| |
| int32_t asStackOffset() const |
| { |
| ASSERT(isStack()); |
| return m_offset; |
| } |
| |
| Address asStackAddress() const |
| { |
| ASSERT(isStack()); |
| return Address(GPRInfo::callFrameRegister, asStackOffset()); |
| } |
| |
| int32_t asGlobalOffset() const |
| { |
| ASSERT(isGlobal()); |
| return m_offset; |
| } |
| |
| Address asGlobalAddress() const |
| { |
| ASSERT(isGlobal()); |
| return Address(GPRInfo::wasmContextInstancePointer, asGlobalOffset()); |
| } |
| |
| int32_t asStackArgumentOffset() const |
| { |
| ASSERT(isStackArgument()); |
| return m_offset; |
| } |
| |
| Address asStackArgumentAddress() const |
| { |
| ASSERT(isStackArgument()); |
| return Address(MacroAssembler::stackPointerRegister, asStackArgumentOffset()); |
| } |
| |
| Address asAddress() const |
| { |
| switch (m_kind) { |
| case Stack: |
| return asStackAddress(); |
| case Global: |
| return asGlobalAddress(); |
| case StackArgument: |
| return asStackArgumentAddress(); |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| } |
| |
| GPRReg asGPR() const |
| { |
| ASSERT(isGPR()); |
| return m_gpr; |
| } |
| |
| FPRReg asFPR() const |
| { |
| ASSERT(isFPR()); |
| return m_fpr; |
| } |
| |
| void dump(PrintStream& out) const |
| { |
| switch (m_kind) { |
| case None: |
| out.print("None"); |
| break; |
| case Gpr: |
| out.print("GPR(", MacroAssembler::gprName(m_gpr), ")"); |
| break; |
| case Fpr: |
| out.print("FPR(", MacroAssembler::fprName(m_fpr), ")"); |
| break; |
| case Stack: |
| out.print("Stack(", m_offset, ")"); |
| break; |
| case Global: |
| out.print("Global(", m_offset, ")"); |
| break; |
| case StackArgument: |
| out.print("StackArgument(", m_offset, ")"); |
| break; |
| } |
| } |
| |
| bool operator==(Location other) const |
| { |
| if (m_kind != other.m_kind) |
| return false; |
| switch (m_kind) { |
| case Gpr: |
| return m_gpr == other.m_gpr; |
| case Fpr: |
| return m_fpr == other.m_fpr; |
| case Stack: |
| case StackArgument: |
| case Global: |
| return m_offset == other.m_offset; |
| case None: |
| return true; |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| } |
| |
| bool operator!=(Location other) const |
| { |
| return !(*this == other); |
| } |
| |
| Kind kind() const |
| { |
| return Kind(m_kind); |
| } |
| private: |
| union { |
| // It's useful to we be able to cram a location into a 4-byte space, so that |
| // we can store them efficiently in ControlData. |
| struct { |
| unsigned m_kind : 3; |
| int32_t m_offset : 29; |
| }; |
| struct { |
| Kind m_padGpr; |
| GPRReg m_gpr; |
| }; |
| struct { |
| Kind m_padFpr; |
| FPRReg m_fpr; |
| }; |
| }; |
| }; |
| |
| static_assert(sizeof(Location) == 4); |
| |
| static bool isFloatingPointType(TypeKind type) |
| { |
| return type == TypeKind::F32 || type == TypeKind::F64 || type == TypeKind::V128; |
| } |
| |
| public: |
| static ALWAYS_INLINE uint32_t sizeOfType(TypeKind type) |
| { |
| switch (type) { |
| case TypeKind::I32: |
| case TypeKind::F32: |
| case TypeKind::I31ref: |
| return 4; |
| case TypeKind::I64: |
| case TypeKind::F64: |
| return 8; |
| case TypeKind::V128: |
| return 16; |
| case TypeKind::Func: |
| case TypeKind::Funcref: |
| case TypeKind::Ref: |
| case TypeKind::RefNull: |
| case TypeKind::Rec: |
| case TypeKind::Sub: |
| case TypeKind::Struct: |
| case TypeKind::Structref: |
| case TypeKind::Externref: |
| case TypeKind::Array: |
| case TypeKind::Arrayref: |
| case TypeKind::Eqref: |
| case TypeKind::Anyref: |
| case TypeKind::Nullref: |
| return sizeof(EncodedJSValue); |
| case TypeKind::Void: |
| return 0; |
| } |
| return 0; |
| } |
| |
| class Value { |
| public: |
| // Represents the location in which this value is stored. |
| enum Kind : uint8_t { |
| None = 0, |
| Const = 1, |
| Temp = 2, |
| Local = 3, |
| Pinned = 4 // Used if we need to represent a Location as a Value, mostly in operation calls |
| }; |
| |
| ALWAYS_INLINE bool isNone() const |
| { |
| return m_kind == None; |
| } |
| |
| ALWAYS_INLINE bool isTemp() const |
| { |
| return m_kind == Temp; |
| } |
| |
| ALWAYS_INLINE bool isLocal() const |
| { |
| return m_kind == Local; |
| } |
| |
| ALWAYS_INLINE bool isConst() const |
| { |
| return m_kind == Const; |
| } |
| |
| ALWAYS_INLINE bool isPinned() const |
| { |
| return m_kind == Pinned; |
| } |
| |
| ALWAYS_INLINE Kind kind() const |
| { |
| return m_kind; |
| } |
| |
| ALWAYS_INLINE int32_t asI32() const |
| { |
| ASSERT(m_kind == Const); |
| return m_i32; |
| } |
| |
| ALWAYS_INLINE int64_t asI64() const |
| { |
| ASSERT(m_kind == Const); |
| return m_i64; |
| } |
| |
| ALWAYS_INLINE float asF32() const |
| { |
| ASSERT(m_kind == Const); |
| return m_f32; |
| } |
| |
| ALWAYS_INLINE double asF64() const |
| { |
| ASSERT(m_kind == Const); |
| return m_f64; |
| } |
| |
| ALWAYS_INLINE EncodedJSValue asRef() const |
| { |
| ASSERT(m_kind == Const); |
| return m_ref; |
| } |
| |
| ALWAYS_INLINE LocalOrTempIndex asTemp() const |
| { |
| ASSERT(m_kind == Temp); |
| return m_index; |
| } |
| |
| ALWAYS_INLINE LocalOrTempIndex asLocal() const |
| { |
| ASSERT(m_kind == Local); |
| return m_index; |
| } |
| |
| ALWAYS_INLINE Location asPinned() const |
| { |
| ASSERT(m_kind == Pinned); |
| return m_pinned; |
| } |
| |
| ALWAYS_INLINE static Value none() |
| { |
| Value val; |
| val.m_kind = None; |
| return val; |
| } |
| |
| ALWAYS_INLINE static Value fromI32(int32_t immediate) |
| { |
| Value val; |
| val.m_kind = Const; |
| val.m_type = TypeKind::I32; |
| val.m_i32 = immediate; |
| return val; |
| } |
| |
| ALWAYS_INLINE static Value fromI64(int64_t immediate) |
| { |
| Value val; |
| val.m_kind = Const; |
| val.m_type = TypeKind::I64; |
| val.m_i64 = immediate; |
| return val; |
| } |
| |
| ALWAYS_INLINE static Value fromF32(float immediate) |
| { |
| Value val; |
| val.m_kind = Const; |
| val.m_type = TypeKind::F32; |
| val.m_f32 = immediate; |
| return val; |
| } |
| |
| ALWAYS_INLINE static Value fromF64(double immediate) |
| { |
| Value val; |
| val.m_kind = Const; |
| val.m_type = TypeKind::F64; |
| val.m_f64 = immediate; |
| return val; |
| } |
| |
| ALWAYS_INLINE static Value fromRef(TypeKind refType, EncodedJSValue ref) |
| { |
| Value val; |
| val.m_kind = Const; |
| val.m_type = refType; |
| val.m_ref = ref; |
| return val; |
| } |
| |
| ALWAYS_INLINE static Value fromTemp(TypeKind type, LocalOrTempIndex temp) |
| { |
| Value val; |
| val.m_kind = Temp; |
| val.m_type = type; |
| val.m_index = temp; |
| return val; |
| } |
| |
| ALWAYS_INLINE static Value fromLocal(TypeKind type, LocalOrTempIndex local) |
| { |
| Value val; |
| val.m_kind = Local; |
| val.m_type = type; |
| val.m_index = local; |
| return val; |
| } |
| |
| ALWAYS_INLINE static Value pinned(TypeKind type, Location location) |
| { |
| Value val; |
| val.m_kind = Pinned; |
| val.m_type = type; |
| val.m_pinned = location; |
| return val; |
| } |
| |
| ALWAYS_INLINE Value() |
| : m_kind(None) |
| { } |
| |
| ALWAYS_INLINE uint32_t size() const |
| { |
| return sizeOfType(m_type); |
| } |
| |
| ALWAYS_INLINE bool isFloat() const |
| { |
| return isFloatingPointType(m_type); |
| } |
| |
| ALWAYS_INLINE TypeKind type() const |
| { |
| return m_type; |
| } |
| |
| void dump(PrintStream& out) const |
| { |
| switch (m_kind) { |
| case Const: |
| out.print("Const("); |
| if (m_type == TypeKind::I32) |
| out.print(m_i32); |
| else if (m_type == TypeKind::I64) |
| out.print(m_i64); |
| else if (m_type == TypeKind::F32) |
| out.print(m_f32); |
| else if (m_type == TypeKind::F64) |
| out.print(m_f64); |
| out.print(")"); |
| break; |
| case Local: |
| out.print("Local(", m_index, ")"); |
| break; |
| case Temp: |
| out.print("Temp(", m_index, ")"); |
| break; |
| case None: |
| out.print("None"); |
| break; |
| case Pinned: |
| out.print(m_pinned); |
| } |
| } |
| private: |
| union { |
| int32_t m_i32; |
| int64_t m_i64; |
| float m_f32; |
| double m_f64; |
| LocalOrTempIndex m_index; |
| Location m_pinned; |
| EncodedJSValue m_ref; |
| }; |
| |
| Kind m_kind; |
| TypeKind m_type; |
| }; |
| |
| private: |
| struct RegisterBinding { |
| enum Kind : uint8_t { |
| None = 0, |
| Local = 1, |
| Temp = 2, |
| Scratch = 3, // Denotes a register bound for use as a scratch, not as a local or temp's location. |
| }; |
| union { |
| uint32_t m_uintValue; |
| struct { |
| TypeKind m_type; |
| unsigned m_kind : 3; |
| unsigned m_index : LocalIndexBits; |
| }; |
| }; |
| |
| RegisterBinding() |
| : m_uintValue(0) |
| { } |
| |
| RegisterBinding(uint32_t uintValue) |
| : m_uintValue(uintValue) |
| { } |
| |
| static RegisterBinding fromValue(Value value) |
| { |
| ASSERT(value.isLocal() || value.isTemp()); |
| RegisterBinding binding; |
| binding.m_type = value.type(); |
| binding.m_kind = value.isLocal() ? Local : Temp; |
| binding.m_index = value.isLocal() ? value.asLocal() : value.asTemp(); |
| return binding; |
| } |
| |
| static RegisterBinding none() |
| { |
| return RegisterBinding(); |
| } |
| |
| static RegisterBinding scratch() |
| { |
| RegisterBinding binding; |
| binding.m_kind = Scratch; |
| return binding; |
| } |
| |
| Value toValue() const |
| { |
| switch (m_kind) { |
| case None: |
| case Scratch: |
| return Value::none(); |
| case Local: |
| return Value::fromLocal(m_type, m_index); |
| case Temp: |
| return Value::fromTemp(m_type, m_index); |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| } |
| |
| bool isNone() const |
| { |
| return m_kind == None; |
| } |
| |
| bool isValid() const |
| { |
| return m_kind != None; |
| } |
| |
| bool isScratch() const |
| { |
| return m_kind == Scratch; |
| } |
| |
| bool operator==(RegisterBinding other) const |
| { |
| if (m_kind != other.m_kind) |
| return false; |
| |
| return m_kind == None || (m_index == other.m_index && m_type == other.m_type); |
| } |
| |
| void dump(PrintStream& out) const |
| { |
| switch (m_kind) { |
| case None: |
| out.print("None"); |
| break; |
| case Scratch: |
| out.print("Scratch"); |
| break; |
| case Local: |
| out.print("Local(", m_index, ")"); |
| break; |
| case Temp: |
| out.print("Temp(", m_index, ")"); |
| break; |
| } |
| } |
| |
| unsigned hash() const |
| { |
| return pairIntHash(static_cast<unsigned>(m_kind), m_index); |
| } |
| |
| uint32_t encode() const |
| { |
| return m_uintValue; |
| } |
| }; |
| |
| public: |
| struct ControlData { |
| static bool isIf(const ControlData& control) { return control.blockType() == BlockType::If; } |
| static bool isTry(const ControlData& control) { return control.blockType() == BlockType::Try; } |
| static bool isAnyCatch(const ControlData& control) { return control.blockType() == BlockType::Catch; } |
| static bool isCatch(const ControlData& control) { return isAnyCatch(control) && control.catchKind() == CatchKind::Catch; } |
| static bool isTopLevel(const ControlData& control) { return control.blockType() == BlockType::TopLevel; } |
| static bool isLoop(const ControlData& control) { return control.blockType() == BlockType::Loop; } |
| static bool isBlock(const ControlData& control) { return control.blockType() == BlockType::Block; } |
| |
| ControlData() |
| : m_enclosedHeight(0) |
| { } |
| |
| ControlData(BBQJIT& generator, BlockType blockType, BlockSignature signature, LocalOrTempIndex enclosedHeight) |
| : m_signature(signature) |
| , m_blockType(blockType) |
| , m_enclosedHeight(enclosedHeight) |
| { |
| if (blockType == BlockType::TopLevel) { |
| // Abide by calling convention instead. |
| CallInformation wasmCallInfo = wasmCallingConvention().callInformationFor(*signature, CallRole::Callee); |
| for (unsigned i = 0; i < signature->as<FunctionSignature>()->argumentCount(); ++i) |
| m_argumentLocations.append(Location::fromArgumentLocation(wasmCallInfo.params[i])); |
| for (unsigned i = 0; i < signature->as<FunctionSignature>()->returnCount(); ++i) |
| m_resultLocations.append(Location::fromArgumentLocation(wasmCallInfo.results[i])); |
| return; |
| } |
| |
| // This function is intentionally not using implicitSlots since arguments and results should not include implicit slot. |
| auto allocateArgumentOrResult = [&](BBQJIT& generator, TypeKind type, unsigned i, RegisterSet& remainingGPRs, RegisterSet& remainingFPRs) -> Location { |
| switch (type) { |
| case TypeKind::V128: |
| case TypeKind::F32: |
| case TypeKind::F64: { |
| if (remainingFPRs.isEmpty()) |
| return generator.canonicalSlot(Value::fromTemp(type, this->enclosedHeight() + i)); |
| auto reg = *remainingFPRs.begin(); |
| remainingFPRs.remove(reg); |
| return Location::fromFPR(reg.fpr()); |
| } |
| default: |
| if (remainingGPRs.isEmpty()) |
| return generator.canonicalSlot(Value::fromTemp(type, this->enclosedHeight() + i)); |
| auto reg = *remainingGPRs.begin(); |
| remainingGPRs.remove(reg); |
| return Location::fromGPR(reg.gpr()); |
| } |
| }; |
| |
| const auto& functionSignature = signature->as<FunctionSignature>(); |
| auto gprSetCopy = generator.m_validGPRs; |
| auto fprSetCopy = generator.m_validFPRs; |
| if (!isAnyCatch(*this)) { |
| for (unsigned i = 0; i < functionSignature->argumentCount(); ++i) |
| m_argumentLocations.append(allocateArgumentOrResult(generator, functionSignature->argumentType(i).kind, i, gprSetCopy, fprSetCopy)); |
| } |
| |
| gprSetCopy = generator.m_validGPRs; |
| fprSetCopy = generator.m_validFPRs; |
| for (unsigned i = 0; i < functionSignature->returnCount(); ++i) |
| m_resultLocations.append(allocateArgumentOrResult(generator, functionSignature->returnType(i).kind, i, gprSetCopy, fprSetCopy)); |
| } |
| |
| template<typename Stack> |
| void flushAtBlockBoundary(BBQJIT& generator, unsigned targetArity, Stack& expressionStack, bool endOfWasmBlock) |
| { |
| // First, we flush all locals that were allocated outside of their designated slots in this block. |
| for (unsigned i = 0; i < expressionStack.size(); ++i) { |
| if (expressionStack[i].value().isLocal()) |
| m_touchedLocals.add(expressionStack[i].value().asLocal()); |
| } |
| for (LocalOrTempIndex touchedLocal : m_touchedLocals) { |
| Value value = Value::fromLocal(generator.m_localTypes[touchedLocal], touchedLocal); |
| if (generator.locationOf(value).isRegister()) |
| generator.flushValue(value); |
| } |
| |
| // If we are a catch block, we need to flush the exception value, since it's not represented on the expression stack. |
| if (isAnyCatch(*this)) { |
| Value value = generator.exception(*this); |
| if (!endOfWasmBlock) |
| generator.flushValue(value); |
| else |
| generator.consume(value); |
| } |
| |
| for (unsigned i = 0; i < expressionStack.size(); ++i) { |
| Value& value = expressionStack[i].value(); |
| int resultIndex = static_cast<int>(i) - static_cast<int>(expressionStack.size() - targetArity); |
| |
| // Next, we turn all constants into temporaries, so they can be given persistent slots on the stack. |
| // If this is the end of the enclosing wasm block, we know we won't need them again, so this can be skipped. |
| if (value.isConst() && (resultIndex < 0 || !endOfWasmBlock)) { |
| Value constant = value; |
| value = Value::fromTemp(value.type(), static_cast<LocalOrTempIndex>(enclosedHeight() + implicitSlots() + i)); |
| Location slot = generator.locationOf(value); |
| generator.emitMoveConst(constant, slot); |
| } |
| |
| // Next, we flush or consume all the temp values on the stack. |
| if (value.isTemp()) { |
| if (!endOfWasmBlock) |
| generator.flushValue(value); |
| else if (resultIndex < 0) |
| generator.consume(value); |
| } |
| } |
| } |
| |
| template<typename Stack, size_t N> |
| bool addExit(BBQJIT& generator, const Vector<Location, N>& targetLocations, Stack& expressionStack) |
| { |
| unsigned targetArity = targetLocations.size(); |
| |
| if (!targetArity) |
| return false; |
| |
| // We move all passed temporaries to the successor, in its argument slots. |
| unsigned offset = expressionStack.size() - targetArity; |
| |
| Vector<Value, 8> resultValues; |
| Vector<Location, 8> resultLocations; |
| for (unsigned i = 0; i < targetArity; ++i) { |
| resultValues.append(expressionStack[i + offset].value()); |
| resultLocations.append(targetLocations[i]); |
| } |
| generator.emitShuffle(resultValues, resultLocations); |
| return true; |
| } |
| |
| template<typename Stack> |
| void finalizeBlock(BBQJIT& generator, unsigned targetArity, Stack& expressionStack, bool preserveArguments) |
| { |
| // Finally, as we are leaving the block, we convert any constants into temporaries on the stack, so we don't blindly assume they have |
| // the same constant values in the successor. |
| unsigned offset = expressionStack.size() - targetArity; |
| for (unsigned i = 0; i < targetArity; ++i) { |
| Value& value = expressionStack[i + offset].value(); |
| if (value.isConst()) { |
| Value constant = value; |
| value = Value::fromTemp(value.type(), static_cast<LocalOrTempIndex>(enclosedHeight() + implicitSlots() + i + offset)); |
| if (preserveArguments) |
| generator.emitMoveConst(constant, generator.canonicalSlot(value)); |
| } else if (value.isTemp()) { |
| if (preserveArguments) |
| generator.flushValue(value); |
| else |
| generator.consume(value); |
| } |
| } |
| } |
| |
| template<typename Stack> |
| void flushAndSingleExit(BBQJIT& generator, ControlData& target, Stack& expressionStack, bool isChildBlock, bool endOfWasmBlock, bool unreachable = false) |
| { |
| // Helper to simplify the common case where we don't need to handle multiple exits. |
| const auto& targetLocations = isChildBlock ? target.argumentLocations() : target.targetLocations(); |
| flushAtBlockBoundary(generator, targetLocations.size(), expressionStack, endOfWasmBlock); |
| if (!unreachable) |
| addExit(generator, targetLocations, expressionStack); |
| finalizeBlock(generator, targetLocations.size(), expressionStack, false); |
| } |
| |
| template<typename Stack> |
| void startBlock(BBQJIT& generator, Stack& expressionStack) |
| { |
| ASSERT(expressionStack.size() >= m_argumentLocations.size()); |
| |
| for (unsigned i = 0; i < m_argumentLocations.size(); ++i) { |
| ASSERT(!expressionStack[i + expressionStack.size() - m_argumentLocations.size()].value().isConst()); |
| generator.bind(expressionStack[i].value(), m_argumentLocations[i]); |
| } |
| } |
| |
| template<typename Stack> |
| void resumeBlock(BBQJIT& generator, const ControlData& predecessor, Stack& expressionStack) |
| { |
| ASSERT(expressionStack.size() >= predecessor.resultLocations().size()); |
| |
| for (unsigned i = 0; i < predecessor.resultLocations().size(); ++i) { |
| unsigned offset = expressionStack.size() - predecessor.resultLocations().size(); |
| // Intentionally not using implicitSlots since results should not include implicit slot. |
| expressionStack[i + offset].value() = Value::fromTemp(expressionStack[i + offset].type().kind, predecessor.enclosedHeight() + i); |
| generator.bind(expressionStack[i + offset].value(), predecessor.resultLocations()[i]); |
| } |
| } |
| |
| void convertIfToBlock() |
| { |
| ASSERT(m_blockType == BlockType::If); |
| m_blockType = BlockType::Block; |
| } |
| |
| void convertLoopToBlock() |
| { |
| ASSERT(m_blockType == BlockType::Loop); |
| m_blockType = BlockType::Block; |
| } |
| |
| void addBranch(Jump jump) |
| { |
| m_branchList.append(jump); |
| } |
| |
| void addLabel(Box<CCallHelpers::Label>&& label) |
| { |
| m_labels.append(WTFMove(label)); |
| } |
| |
| void delegateJumpsTo(ControlData& delegateTarget) |
| { |
| delegateTarget.m_branchList.append(std::exchange(m_branchList, { })); |
| delegateTarget.m_labels.appendVector(std::exchange(m_labels, { })); |
| } |
| |
| void linkJumps(MacroAssembler::AbstractMacroAssemblerType* masm) |
| { |
| m_branchList.link(masm); |
| fillLabels(masm->label()); |
| } |
| |
| void linkJumpsTo(MacroAssembler::Label label, MacroAssembler::AbstractMacroAssemblerType* masm) |
| { |
| m_branchList.linkTo(label, masm); |
| fillLabels(label); |
| } |
| |
| void linkIfBranch(MacroAssembler::AbstractMacroAssemblerType* masm) |
| { |
| ASSERT(m_blockType == BlockType::If); |
| if (m_ifBranch.isSet()) |
| m_ifBranch.link(masm); |
| } |
| |
| void dump(PrintStream& out) const |
| { |
| UNUSED_PARAM(out); |
| } |
| |
| LocalOrTempIndex enclosedHeight() const |
| { |
| return m_enclosedHeight; |
| } |
| |
| unsigned implicitSlots() const { return isAnyCatch(*this) ? 1 : 0; } |
| |
| const Vector<Location, 2>& targetLocations() const |
| { |
| return blockType() == BlockType::Loop |
| ? argumentLocations() |
| : resultLocations(); |
| } |
| |
| const Vector<Location, 2>& argumentLocations() const |
| { |
| return m_argumentLocations; |
| } |
| |
| const Vector<Location, 2>& resultLocations() const |
| { |
| return m_resultLocations; |
| } |
| |
| BlockType blockType() const { return m_blockType; } |
| BlockSignature signature() const { return m_signature; } |
| |
| FunctionArgCount branchTargetArity() const |
| { |
| if (blockType() == BlockType::Loop) |
| return m_signature->as<FunctionSignature>()->argumentCount(); |
| return m_signature->as<FunctionSignature>()->returnCount(); |
| } |
| |
| Type branchTargetType(unsigned i) const |
| { |
| ASSERT(i < branchTargetArity()); |
| if (m_blockType == BlockType::Loop) |
| return m_signature->as<FunctionSignature>()->argumentType(i); |
| return m_signature->as<FunctionSignature>()->returnType(i); |
| } |
| |
| Type argumentType(unsigned i) const |
| { |
| ASSERT(i < m_signature->as<FunctionSignature>()->argumentCount()); |
| return m_signature->as<FunctionSignature>()->argumentType(i); |
| } |
| |
| CatchKind catchKind() const |
| { |
| ASSERT(m_blockType == BlockType::Catch); |
| return m_catchKind; |
| } |
| |
| void setCatchKind(CatchKind catchKind) |
| { |
| ASSERT(m_blockType == BlockType::Catch); |
| m_catchKind = catchKind; |
| } |
| |
| unsigned tryStart() const |
| { |
| return m_tryStart; |
| } |
| |
| unsigned tryEnd() const |
| { |
| return m_tryEnd; |
| } |
| |
| unsigned tryCatchDepth() const |
| { |
| return m_tryCatchDepth; |
| } |
| |
| void setTryInfo(unsigned tryStart, unsigned tryEnd, unsigned tryCatchDepth) |
| { |
| m_tryStart = tryStart; |
| m_tryEnd = tryEnd; |
| m_tryCatchDepth = tryCatchDepth; |
| } |
| |
| void setIfBranch(MacroAssembler::Jump branch) |
| { |
| ASSERT(m_blockType == BlockType::If); |
| m_ifBranch = branch; |
| } |
| |
| void setLoopLabel(MacroAssembler::Label label) |
| { |
| ASSERT(m_blockType == BlockType::Loop); |
| m_loopLabel = label; |
| } |
| |
| const MacroAssembler::Label& loopLabel() const |
| { |
| return m_loopLabel; |
| } |
| |
| void touch(LocalOrTempIndex local) |
| { |
| m_touchedLocals.add(local); |
| } |
| |
| private: |
| friend class BBQJIT; |
| |
| void fillLabels(CCallHelpers::Label label) |
| { |
| for (auto& box : m_labels) |
| *box = label; |
| } |
| |
| BlockSignature m_signature; |
| BlockType m_blockType; |
| CatchKind m_catchKind { CatchKind::Catch }; |
| Vector<Location, 2> m_argumentLocations; // List of input locations to write values into when entering this block. |
| Vector<Location, 2> m_resultLocations; // List of result locations to write values into when exiting this block. |
| JumpList m_branchList; // List of branch control info for branches targeting the end of this block. |
| Vector<Box<CCallHelpers::Label>> m_labels; // List of labels filled. |
| MacroAssembler::Label m_loopLabel; |
| MacroAssembler::Jump m_ifBranch; |
| LocalOrTempIndex m_enclosedHeight; // Height of enclosed expression stack, used as the base for all temporary locations. |
| BitVector m_touchedLocals; // Number of locals allocated to registers in this block. |
| unsigned m_tryStart { 0 }; |
| unsigned m_tryEnd { 0 }; |
| unsigned m_tryCatchDepth { 0 }; |
| }; |
| |
| friend struct ControlData; |
| |
| using ExpressionType = Value; |
| using ControlType = ControlData; |
| using CallType = CallLinkInfo::CallType; |
| using ResultList = Vector<ExpressionType, 8>; |
| using ControlEntry = typename FunctionParserTypes<ControlType, ExpressionType, CallType>::ControlEntry; |
| using TypedExpression = typename FunctionParserTypes<ControlType, ExpressionType, CallType>::TypedExpression; |
| using Stack = FunctionParser<BBQJIT>::Stack; |
| using ControlStack = FunctionParser<BBQJIT>::ControlStack; |
| |
| private: |
| unsigned m_loggingIndent = 0; |
| |
| template<typename... Args> |
| void logInstructionData(bool first, const Value& value, const Location& location, const Args&... args) |
| { |
| if (!first) |
| dataLog(", "); |
| |
| dataLog(value); |
| if (location.kind() != Location::None) |
| dataLog(":", location); |
| logInstructionData(false, args...); |
| } |
| |
| template<typename... Args> |
| void logInstructionData(bool first, const Value& value, const Args&... args) |
| { |
| if (!first) |
| dataLog(", "); |
| |
| dataLog(value); |
| if (!value.isConst() && !value.isPinned()) |
| dataLog(":", locationOf(value)); |
| logInstructionData(false, args...); |
| } |
| |
| template<size_t N, typename OverflowHandler, typename... Args> |
| void logInstructionData(bool first, const Vector<TypedExpression, N, OverflowHandler>& expressions, const Args&... args) |
| { |
| for (const TypedExpression& expression : expressions) { |
| if (!first) |
| dataLog(", "); |
| |
| const Value& value = expression.value(); |
| dataLog(value); |
| if (!value.isConst() && !value.isPinned()) |
| dataLog(":", locationOf(value)); |
| first = false; |
| } |
| logInstructionData(false, args...); |
| } |
| |
| template<size_t N, typename OverflowHandler, typename... Args> |
| void logInstructionData(bool first, const Vector<Value, N, OverflowHandler>& values, const Args&... args) |
| { |
| for (const Value& value : values) { |
| if (!first) |
| dataLog(", "); |
| |
| dataLog(value); |
| if (!value.isConst() && !value.isPinned()) |
| dataLog(":", locationOf(value)); |
| first = false; |
| } |
| logInstructionData(false, args...); |
| } |
| |
| template<size_t N, typename OverflowHandler, typename... Args> |
| void logInstructionData(bool first, const Vector<Location, N, OverflowHandler>& values, const Args&... args) |
| { |
| for (const Location& value : values) { |
| if (!first) |
| dataLog(", "); |
| dataLog(value); |
| first = false; |
| } |
| logInstructionData(false, args...); |
| } |
| |
| inline void logInstructionData(bool) |
| { |
| dataLogLn(); |
| } |
| |
| template<typename... Data> |
| ALWAYS_INLINE void logInstruction(const char* opcode, SIMDLaneOperation op, const Data&... data) |
| { |
| dataLog("BBQ\t"); |
| for (unsigned i = 0; i < m_loggingIndent; i ++) |
| dataLog(" "); |
| dataLog(opcode, SIMDLaneOperationDump(op), " "); |
| logInstructionData(true, data...); |
| } |
| |
| template<typename... Data> |
| ALWAYS_INLINE void logInstruction(const char* opcode, const Data&... data) |
| { |
| dataLog("BBQ\t"); |
| for (unsigned i = 0; i < m_loggingIndent; i ++) |
| dataLog(" "); |
| dataLog(opcode, " "); |
| logInstructionData(true, data...); |
| } |
| |
| template<typename T> |
| struct Result { |
| T value; |
| |
| Result(const T& value_in) |
| : value(value_in) |
| { } |
| }; |
| |
| template<typename... Args> |
| void logInstructionData(bool first, const char* const& literal, const Args&... args) |
| { |
| if (!first) |
| dataLog(" "); |
| |
| dataLog(literal); |
| if (!std::strcmp(literal, "=> ")) |
| logInstructionData(true, args...); |
| else |
| logInstructionData(false, args...); |
| } |
| |
| template<typename T, typename... Args> |
| void logInstructionData(bool first, const Result<T>& result, const Args&... args) |
| { |
| if (!first) |
| dataLog(" "); |
| |
| dataLog("=> "); |
| logInstructionData(true, result.value, args...); |
| } |
| |
| template<typename T, typename... Args> |
| void logInstructionData(bool first, const T& t, const Args&... args) |
| { |
| if (!first) |
| dataLog(", "); |
| dataLog(t); |
| logInstructionData(false, args...); |
| } |
| |
| #define RESULT(...) Result { __VA_ARGS__ } |
| #define LOG_INSTRUCTION(...) do { if (UNLIKELY(Options::verboseBBQJITInstructions())) { logInstruction(__VA_ARGS__); } } while (false) |
| #define LOG_INDENT() do { if (UNLIKELY(Options::verboseBBQJITInstructions())) { m_loggingIndent += 2; } } while (false); |
| #define LOG_DEDENT() do { if (UNLIKELY(Options::verboseBBQJITInstructions())) { m_loggingIndent -= 2; } } while (false); |
| |
| public: |
| static constexpr bool tierSupportsSIMD = true; |
| |
| BBQJIT(CCallHelpers& jit, const TypeDefinition& signature, BBQCallee& callee, const FunctionData& function, uint32_t functionIndex, const ModuleInformation& info, Vector<UnlinkedWasmToWasmCall>& unlinkedWasmToWasmCalls, MemoryMode mode, InternalFunction* compilation, std::optional<bool> hasExceptionHandlers, unsigned loopIndexForOSREntry, TierUpCount* tierUp) |
| : m_jit(jit) |
| , m_callee(callee) |
| , m_function(function) |
| , m_functionSignature(signature.as<FunctionSignature>()) |
| , m_functionIndex(functionIndex) |
| , m_info(info) |
| , m_mode(mode) |
| , m_unlinkedWasmToWasmCalls(unlinkedWasmToWasmCalls) |
| , m_hasExceptionHandlers(hasExceptionHandlers) |
| , m_tierUp(tierUp) |
| , m_loopIndexForOSREntry(loopIndexForOSREntry) |
| , m_gprBindings(jit.numberOfRegisters(), RegisterBinding::none()) |
| , m_fprBindings(jit.numberOfFPRegisters(), RegisterBinding::none()) |
| , m_gprLRU(jit.numberOfRegisters()) |
| , m_fprLRU(jit.numberOfFPRegisters()) |
| , m_lastUseTimestamp(0) |
| , m_compilation(compilation) |
| , m_pcToCodeOriginMapBuilder(Options::useSamplingProfiler()) |
| { |
| RegisterSetBuilder gprSetBuilder = RegisterSetBuilder::allGPRs(); |
| gprSetBuilder.exclude(RegisterSetBuilder::specialRegisters()); |
| gprSetBuilder.exclude(RegisterSetBuilder::macroClobberedGPRs()); |
| gprSetBuilder.exclude(RegisterSetBuilder::wasmPinnedRegisters()); |
| // TODO: handle callee-saved registers better. |
| gprSetBuilder.exclude(RegisterSetBuilder::vmCalleeSaveRegisters()); |
| |
| RegisterSetBuilder fprSetBuilder = RegisterSetBuilder::allFPRs(); |
| RegisterSetBuilder::macroClobberedFPRs().forEach([&](Reg reg) { |
| fprSetBuilder.remove(reg); |
| }); |
| // TODO: handle callee-saved registers better. |
| RegisterSetBuilder::vmCalleeSaveRegisters().forEach([&](Reg reg) { |
| fprSetBuilder.remove(reg); |
| }); |
| |
| RegisterSetBuilder callerSaveGprs = gprSetBuilder; |
| RegisterSetBuilder callerSaveFprs = fprSetBuilder; |
| |
| gprSetBuilder.remove(wasmScratchGPR); |
| fprSetBuilder.remove(wasmScratchFPR); |
| |
| m_gprSet = m_validGPRs = gprSetBuilder.buildAndValidate(); |
| m_fprSet = m_validFPRs = fprSetBuilder.buildAndValidate(); |
| m_callerSaveGPRs = callerSaveGprs.buildAndValidate(); |
| m_callerSaveFPRs = callerSaveFprs.buildAndValidate(); |
| m_callerSaves = callerSaveGprs.merge(callerSaveFprs).buildAndValidate(); |
| |
| m_gprLRU.add(m_gprSet); |
| m_fprLRU.add(m_fprSet); |
| |
| if ((Options::verboseBBQJITAllocation())) |
| dataLogLn("BBQ\tUsing GPR set: ", m_gprSet, "\n \tFPR set: ", m_fprSet); |
| |
| if (UNLIKELY(shouldDumpDisassemblyFor(CompilationMode::BBQMode))) { |
| m_disassembler = makeUnique<BBQDisassembler>(); |
| m_disassembler->setStartOfCode(m_jit.label()); |
| } |
| |
| CallInformation callInfo = wasmCallingConvention().callInformationFor(signature, CallRole::Callee); |
| ASSERT(callInfo.params.size() == m_functionSignature->argumentCount()); |
| for (unsigned i = 0; i < m_functionSignature->argumentCount(); i ++) { |
| const Type& type = m_functionSignature->argumentType(i); |
| m_localSlots.append(allocateStack(Value::fromLocal(type.kind, i))); |
| m_locals.append(Location::none()); |
| m_localTypes.append(type.kind); |
| |
| Value parameter = Value::fromLocal(type.kind, i); |
| bind(parameter, Location::fromArgumentLocation(callInfo.params[i])); |
| m_arguments.append(i); |
| } |
| m_localStorage = m_frameSize; // All stack slots allocated so far are locals. |
| } |
| |
| ALWAYS_INLINE static Value emptyExpression() |
| { |
| return Value::none(); |
| } |
| |
| void setParser(FunctionParser<BBQJIT>* parser) |
| { |
| m_parser = parser; |
| } |
| |
| bool addArguments(const TypeDefinition& signature) |
| { |
| RELEASE_ASSERT(m_arguments.size() == signature.as<FunctionSignature>()->argumentCount()); // We handle arguments in the prologue |
| return true; |
| } |
| |
| Value addConstant(Type type, uint64_t value) |
| { |
| Value result; |
| switch (type.kind) { |
| case TypeKind::I32: |
| result = Value::fromI32(value); |
| LOG_INSTRUCTION("I32Const", RESULT(result)); |
| break; |
| case TypeKind::I64: |
| result = Value::fromI64(value); |
| LOG_INSTRUCTION("I64Const", RESULT(result)); |
| break; |
| case TypeKind::F32: { |
| int32_t tmp = static_cast<int32_t>(value); |
| result = Value::fromF32(*reinterpret_cast<float*>(&tmp)); |
| LOG_INSTRUCTION("F32Const", RESULT(result)); |
| break; |
| } |
| case TypeKind::F64: |
| result = Value::fromF64(*reinterpret_cast<double*>(&value)); |
| LOG_INSTRUCTION("F64Const", RESULT(result)); |
| break; |
| case TypeKind::Ref: |
| case TypeKind::RefNull: |
| case TypeKind::Structref: |
| case TypeKind::Arrayref: |
| case TypeKind::Funcref: |
| case TypeKind::Externref: |
| case TypeKind::Eqref: |
| case TypeKind::Anyref: |
| case TypeKind::Nullref: |
| result = Value::fromRef(type.kind, static_cast<EncodedJSValue>(value)); |
| LOG_INSTRUCTION("RefConst", makeString(type.kind), RESULT(result)); |
| break; |
| default: |
| RELEASE_ASSERT_NOT_REACHED_WITH_MESSAGE("Unimplemented constant type.\n"); |
| return Value::none(); |
| } |
| return result; |
| } |
| |
| PartialResult addDrop(Value value) |
| { |
| LOG_INSTRUCTION("Drop", value); |
| consume(value); |
| return { }; |
| } |
| |
| PartialResult addLocal(Type type, uint32_t numberOfLocals) |
| { |
| for (uint32_t i = 0; i < numberOfLocals; i ++) { |
| uint32_t localIndex = m_locals.size(); |
| m_localSlots.append(allocateStack(Value::fromLocal(type.kind, localIndex))); |
| m_localStorage = m_frameSize; |
| m_locals.append(m_localSlots.last()); |
| m_localTypes.append(type.kind); |
| } |
| return { }; |
| } |
| |
| #define BBQ_STUB { RELEASE_ASSERT_NOT_REACHED_WITH_MESSAGE("Unimplemented instruction!"); return PartialResult(); } |
| #define BBQ_CONTROL_STUB { RELEASE_ASSERT_NOT_REACHED_WITH_MESSAGE("Unimplemented instruction!"); return ControlData(); } |
| |
| Value instanceValue() |
| { |
| return Value::pinned(TypeKind::I64, Location::fromGPR(GPRInfo::wasmContextInstancePointer)); |
| } |
| |
| // Tables |
| PartialResult WARN_UNUSED_RETURN addTableGet(unsigned tableIndex, Value index, Value& result) |
| { |
| // FIXME: Emit this inline <https://bugs.webkit.org/show_bug.cgi?id=198506>. |
| ASSERT(index.type() == TypeKind::I32); |
| TypeKind returnType = m_info.tables[tableIndex].wasmType().kind; |
| ASSERT(typeKindSizeInBytes(returnType) == 8); |
| |
| Vector<Value, 8> arguments = { |
| instanceValue(), |
| Value::fromI32(tableIndex), |
| index |
| }; |
| emitCCall(&operationGetWasmTableElement, arguments, returnType, result); |
| Location resultLocation = allocate(result); |
| |
| LOG_INSTRUCTION("TableGet", tableIndex, index, RESULT(result)); |
| |
| throwExceptionIf(ExceptionType::OutOfBoundsTableAccess, m_jit.branchTest64(ResultCondition::Zero, resultLocation.asGPR())); |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addTableSet(unsigned tableIndex, Value index, Value value) |
| { |
| // FIXME: Emit this inline <https://bugs.webkit.org/show_bug.cgi?id=198506>. |
| ASSERT(index.type() == TypeKind::I32); |
| |
| Vector<Value, 8> arguments = { |
| instanceValue(), |
| Value::fromI32(tableIndex), |
| index, |
| value |
| }; |
| |
| Value shouldThrow; |
| emitCCall(&operationSetWasmTableElement, arguments, TypeKind::I32, shouldThrow); |
| Location shouldThrowLocation = allocate(shouldThrow); |
| |
| LOG_INSTRUCTION("TableSet", tableIndex, index, value); |
| |
| throwExceptionIf(ExceptionType::OutOfBoundsTableAccess, m_jit.branchTest32(ResultCondition::Zero, shouldThrowLocation.asGPR())); |
| |
| consume(shouldThrow); |
| |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addTableInit(unsigned elementIndex, unsigned tableIndex, ExpressionType dstOffset, ExpressionType srcOffset, ExpressionType length) |
| { |
| ASSERT(dstOffset.type() == TypeKind::I32); |
| ASSERT(srcOffset.type() == TypeKind::I32); |
| ASSERT(length.type() == TypeKind::I32); |
| |
| Vector<Value, 8> arguments = { |
| instanceValue(), |
| Value::fromI32(elementIndex), |
| Value::fromI32(tableIndex), |
| dstOffset, |
| srcOffset, |
| length |
| }; |
| Value shouldThrow; |
| emitCCall(&operationWasmTableInit, arguments, TypeKind::I32, shouldThrow); |
| Location shouldThrowLocation = allocate(shouldThrow); |
| |
| LOG_INSTRUCTION("TableInit", tableIndex, dstOffset, srcOffset, length); |
| |
| throwExceptionIf(ExceptionType::OutOfBoundsTableAccess, m_jit.branchTest32(ResultCondition::Zero, shouldThrowLocation.asGPR())); |
| |
| consume(shouldThrow); |
| |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addElemDrop(unsigned elementIndex) |
| { |
| Vector<Value, 8> arguments = { |
| instanceValue(), |
| Value::fromI32(elementIndex) |
| }; |
| emitCCall(&operationWasmElemDrop, arguments); |
| |
| LOG_INSTRUCTION("ElemDrop", elementIndex); |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addTableSize(unsigned tableIndex, Value& result) |
| { |
| Vector<Value, 8> arguments = { |
| instanceValue(), |
| Value::fromI32(tableIndex) |
| }; |
| emitCCall(&operationGetWasmTableSize, arguments, TypeKind::I32, result); |
| |
| LOG_INSTRUCTION("TableSize", tableIndex, RESULT(result)); |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addTableGrow(unsigned tableIndex, Value fill, Value delta, Value& result) |
| { |
| ASSERT(delta.type() == TypeKind::I32); |
| |
| Vector<Value, 8> arguments = { |
| instanceValue(), |
| Value::fromI32(tableIndex), |
| fill, delta |
| }; |
| emitCCall(&operationWasmTableGrow, arguments, TypeKind::I32, result); |
| |
| LOG_INSTRUCTION("TableGrow", tableIndex, fill, delta, RESULT(result)); |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addTableFill(unsigned tableIndex, Value offset, Value fill, Value count) |
| { |
| ASSERT(offset.type() == TypeKind::I32); |
| ASSERT(count.type() == TypeKind::I32); |
| |
| Vector<Value, 8> arguments = { |
| instanceValue(), |
| Value::fromI32(tableIndex), |
| offset, fill, count |
| }; |
| Value shouldThrow; |
| emitCCall(&operationWasmTableFill, arguments, TypeKind::I32, shouldThrow); |
| Location shouldThrowLocation = allocate(shouldThrow); |
| |
| LOG_INSTRUCTION("TableFill", tableIndex, fill, offset, count); |
| |
| throwExceptionIf(ExceptionType::OutOfBoundsTableAccess, m_jit.branchTest32(ResultCondition::Zero, shouldThrowLocation.asGPR())); |
| |
| consume(shouldThrow); |
| |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addTableCopy(unsigned dstTableIndex, unsigned srcTableIndex, Value dstOffset, Value srcOffset, Value length) |
| { |
| ASSERT(dstOffset.type() == TypeKind::I32); |
| ASSERT(srcOffset.type() == TypeKind::I32); |
| ASSERT(length.type() == TypeKind::I32); |
| |
| Vector<Value, 8> arguments = { |
| instanceValue(), |
| Value::fromI32(dstTableIndex), |
| Value::fromI32(srcTableIndex), |
| dstOffset, srcOffset, length |
| }; |
| Value shouldThrow; |
| emitCCall(&operationWasmTableCopy, arguments, TypeKind::I32, shouldThrow); |
| Location shouldThrowLocation = allocate(shouldThrow); |
| |
| LOG_INSTRUCTION("TableCopy", dstTableIndex, srcTableIndex, dstOffset, srcOffset, length); |
| |
| throwExceptionIf(ExceptionType::OutOfBoundsTableAccess, m_jit.branchTest32(ResultCondition::Zero, shouldThrowLocation.asGPR())); |
| |
| consume(shouldThrow); |
| |
| return { }; |
| } |
| |
| // Locals |
| |
| PartialResult WARN_UNUSED_RETURN getLocal(uint32_t localIndex, Value& result) |
| { |
| // Currently, we load locals as temps, which basically prevents register allocation of locals. |
| // This is probably not ideal, we have infrastructure to support binding locals to registers, but |
| // we currently can't track local versioning (meaning we can get SSA-style issues where assigning |
| // to a local also updates all previous uses). |
| result = topValue(m_parser->typeOfLocal(localIndex).kind); |
| Location resultLocation = allocate(result); |
| emitLoad(Value::fromLocal(m_parser->typeOfLocal(localIndex).kind, localIndex), resultLocation); |
| LOG_INSTRUCTION("GetLocal", localIndex, RESULT(result)); |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN setLocal(uint32_t localIndex, Value value) |
| { |
| if (!value.isConst()) |
| loadIfNecessary(value); |
| Value local = Value::fromLocal(m_parser->typeOfLocal(localIndex).kind, localIndex); |
| Location localLocation = locationOf(local); |
| emitStore(value, localLocation); |
| consume(value); |
| LOG_INSTRUCTION("SetLocal", localIndex, value); |
| return { }; |
| } |
| |
| // Globals |
| |
| Value topValue(TypeKind type) |
| { |
| return Value::fromTemp(type, currentControlData().enclosedHeight() + currentControlData().implicitSlots() + m_parser->expressionStack().size()); |
| } |
| |
| Value exception(const ControlData& control) |
| { |
| ASSERT(ControlData::isAnyCatch(control)); |
| return Value::fromTemp(TypeKind::Externref, control.enclosedHeight()); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN getGlobal(uint32_t index, Value& result) |
| { |
| const Wasm::GlobalInformation& global = m_info.globals[index]; |
| Type type = global.type; |
| |
| int32_t offset = Instance::offsetOfGlobalPtr(m_info.importFunctionCount(), m_info.tableCount(), index); |
| Value globalValue = Value::pinned(type.kind, Location::fromGlobal(offset)); |
| |
| switch (global.bindingMode) { |
| case Wasm::GlobalInformation::BindingMode::EmbeddedInInstance: |
| result = topValue(type.kind); |
| emitLoad(globalValue, loadIfNecessary(result)); |
| break; |
| case Wasm::GlobalInformation::BindingMode::Portable: |
| ASSERT(global.mutability == Wasm::Mutability::Mutable); |
| m_jit.loadPtr(Address(GPRInfo::wasmContextInstancePointer, offset), wasmScratchGPR); |
| result = topValue(type.kind); |
| Location resultLocation = allocate(result); |
| switch (type.kind) { |
| case TypeKind::I32: |
| case TypeKind::I31ref: |
| m_jit.load32(Address(wasmScratchGPR), resultLocation.asGPR()); |
| break; |
| case TypeKind::I64: |
| m_jit.load64(Address(wasmScratchGPR), resultLocation.asGPR()); |
| break; |
| case TypeKind::F32: |
| m_jit.loadFloat(Address(wasmScratchGPR), resultLocation.asFPR()); |
| break; |
| case TypeKind::F64: |
| m_jit.loadDouble(Address(wasmScratchGPR), resultLocation.asFPR()); |
| break; |
| case TypeKind::V128: |
| m_jit.loadVector(Address(wasmScratchGPR), resultLocation.asFPR()); |
| break; |
| case TypeKind::Func: |
| case TypeKind::Funcref: |
| case TypeKind::Ref: |
| case TypeKind::RefNull: |
| case TypeKind::Rec: |
| case TypeKind::Sub: |
| case TypeKind::Struct: |
| case TypeKind::Structref: |
| case TypeKind::Externref: |
| case TypeKind::Array: |
| case TypeKind::Arrayref: |
| case TypeKind::Eqref: |
| case TypeKind::Anyref: |
| case TypeKind::Nullref: |
| m_jit.load64(Address(wasmScratchGPR), resultLocation.asGPR()); |
| break; |
| case TypeKind::Void: |
| break; |
| } |
| break; |
| } |
| |
| LOG_INSTRUCTION("GetGlobal", index, RESULT(result)); |
| |
| return { }; |
| } |
| |
| void emitWriteBarrier(GPRReg cellGPR) |
| { |
| GPRReg vmGPR; |
| GPRReg cellStateGPR; |
| { |
| ScratchScope<2, 0> scratches(*this); |
| vmGPR = scratches.gpr(0); |
| cellStateGPR = scratches.gpr(1); |
| } |
| |
| // We must flush everything first. Jumping over flush (emitCCall) is wrong since paths need to get merged. |
| flushRegisters(); |
| |
| m_jit.loadPtr(Address(GPRInfo::wasmContextInstancePointer, Instance::offsetOfVM()), vmGPR); |
| m_jit.load8(Address(cellGPR, JSCell::cellStateOffset()), cellStateGPR); |
| auto noFenceCheck = m_jit.branch32(RelationalCondition::Above, cellStateGPR, Address(vmGPR, VM::offsetOfHeapBarrierThreshold())); |
| |
| // Fence check path |
| auto toSlowPath = m_jit.branchTest8(ResultCondition::Zero, Address(vmGPR, VM::offsetOfHeapMutatorShouldBeFenced())); |
| |
| // Fence path |
| m_jit.memoryFence(); |
| Jump belowBlackThreshold = m_jit.branch8(RelationalCondition::Above, Address(cellGPR, JSCell::cellStateOffset()), TrustedImm32(blackThreshold)); |
| |
| // Slow path |
| toSlowPath.link(&m_jit); |
| m_jit.prepareWasmCallOperation(GPRInfo::wasmContextInstancePointer); |
| m_jit.setupArguments<decltype(operationWasmWriteBarrierSlowPath)>(cellGPR, vmGPR); |
| m_jit.callOperation(operationWasmWriteBarrierSlowPath); |
| |
| // Continuation |
| noFenceCheck.link(&m_jit); |
| belowBlackThreshold.link(&m_jit); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN setGlobal(uint32_t index, Value value) |
| { |
| const Wasm::GlobalInformation& global = m_info.globals[index]; |
| Type type = global.type; |
| |
| int32_t offset = Instance::offsetOfGlobalPtr(m_info.importFunctionCount(), m_info.tableCount(), index); |
| Location valueLocation = locationOf(value); |
| |
| switch (global.bindingMode) { |
| case Wasm::GlobalInformation::BindingMode::EmbeddedInInstance: { |
| emitMove(value, Location::fromGlobal(offset)); |
| consume(value); |
| if (isRefType(type)) { |
| m_jit.load64(Address(GPRInfo::wasmContextInstancePointer, Instance::offsetOfOwner()), wasmScratchGPR); |
| emitWriteBarrier(wasmScratchGPR); |
| } |
| break; |
| } |
| case Wasm::GlobalInformation::BindingMode::Portable: { |
| ASSERT(global.mutability == Wasm::Mutability::Mutable); |
| m_jit.loadPtr(Address(GPRInfo::wasmContextInstancePointer, offset), wasmScratchGPR); |
| |
| Location valueLocation; |
| if (value.isConst() && value.isFloat()) { |
| ScratchScope<0, 1> scratches(*this); |
| valueLocation = Location::fromFPR(scratches.fpr(0)); |
| emitMoveConst(value, valueLocation); |
| } else if (value.isConst()) { |
| ScratchScope<1, 0> scratches(*this); |
| valueLocation = Location::fromGPR(scratches.gpr(0)); |
| emitMoveConst(value, valueLocation); |
| } else |
| valueLocation = loadIfNecessary(value); |
| ASSERT(valueLocation.isRegister()); |
| consume(value); |
| |
| switch (type.kind) { |
| case TypeKind::I32: |
| case TypeKind::I31ref: |
| m_jit.store32(valueLocation.asGPR(), Address(wasmScratchGPR)); |
| break; |
| case TypeKind::I64: |
| m_jit.store64(valueLocation.asGPR(), Address(wasmScratchGPR)); |
| break; |
| case TypeKind::F32: |
| m_jit.storeFloat(valueLocation.asFPR(), Address(wasmScratchGPR)); |
| break; |
| case TypeKind::F64: |
| m_jit.storeDouble(valueLocation.asFPR(), Address(wasmScratchGPR)); |
| break; |
| case TypeKind::V128: |
| m_jit.storeVector(valueLocation.asFPR(), Address(wasmScratchGPR)); |
| break; |
| case TypeKind::Func: |
| case TypeKind::Funcref: |
| case TypeKind::Ref: |
| case TypeKind::RefNull: |
| case TypeKind::Rec: |
| case TypeKind::Sub: |
| case TypeKind::Struct: |
| case TypeKind::Structref: |
| case TypeKind::Externref: |
| case TypeKind::Array: |
| case TypeKind::Arrayref: |
| case TypeKind::Eqref: |
| case TypeKind::Anyref: |
| case TypeKind::Nullref: |
| m_jit.store64(valueLocation.asGPR(), Address(wasmScratchGPR)); |
| break; |
| case TypeKind::Void: |
| break; |
| } |
| if (isRefType(type)) { |
| m_jit.loadPtr(Address(wasmScratchGPR, Wasm::Global::offsetOfOwner() - Wasm::Global::offsetOfValue()), wasmScratchGPR); |
| emitWriteBarrier(wasmScratchGPR); |
| } |
| break; |
| } |
| } |
| |
| LOG_INSTRUCTION("SetGlobal", index, value, valueLocation); |
| |
| return { }; |
| } |
| |
| // Memory |
| inline Location emitCheckAndPreparePointer(Value pointer, uint32_t uoffset, uint32_t sizeOfOperation) |
| { |
| ScratchScope<1, 0> scratches(*this); |
| Location pointerLocation; |
| if (pointer.isConst()) { |
| pointerLocation = Location::fromGPR(scratches.gpr(0)); |
| emitMoveConst(pointer, pointerLocation); |
| } else |
| pointerLocation = loadIfNecessary(pointer); |
| ASSERT(pointerLocation.isGPR()); |
| |
| uint64_t boundary = static_cast<uint64_t>(sizeOfOperation) + uoffset - 1; |
| switch (m_mode) { |
| case MemoryMode::BoundsChecking: { |
| // We're not using signal handling only when the memory is not shared. |
| // Regardless of signaling, we must check that no memory access exceeds the current memory size. |
| m_jit.zeroExtend32ToWord(pointerLocation.asGPR(), wasmScratchGPR); |
| if (boundary) |
| m_jit.add64(TrustedImm64(boundary), wasmScratchGPR); |
| throwExceptionIf(ExceptionType::OutOfBoundsMemoryAccess, m_jit.branch64(RelationalCondition::AboveOrEqual, wasmScratchGPR, GPRInfo::wasmBoundsCheckingSizeRegister)); |
| break; |
| } |
| |
| case MemoryMode::Signaling: { |
| // We've virtually mapped 4GiB+redzone for this memory. Only the user-allocated pages are addressable, contiguously in range [0, current], |
| // and everything above is mapped PROT_NONE. We don't need to perform any explicit bounds check in the 4GiB range because WebAssembly register |
| // memory accesses are 32-bit. However WebAssembly register + offset accesses perform the addition in 64-bit which can push an access above |
| // the 32-bit limit (the offset is unsigned 32-bit). The redzone will catch most small offsets, and we'll explicitly bounds check any |
| // register + large offset access. We don't think this will be generated frequently. |
| // |
| // We could check that register + large offset doesn't exceed 4GiB+redzone since that's technically the limit we need to avoid overflowing the |
| // PROT_NONE region, but it's better if we use a smaller immediate because it can codegens better. We know that anything equal to or greater |
| // than the declared 'maximum' will trap, so we can compare against that number. If there was no declared 'maximum' then we still know that |
| // any access equal to or greater than 4GiB will trap, no need to add the redzone. |
| if (uoffset >= Memory::fastMappedRedzoneBytes()) { |
| uint64_t maximum = m_info.memory.maximum() ? m_info.memory.maximum().bytes() : std::numeric_limits<uint32_t>::max(); |
| m_jit.zeroExtend32ToWord(pointerLocation.asGPR(), wasmScratchGPR); |
| if (boundary) |
| m_jit.add64(TrustedImm64(boundary), wasmScratchGPR); |
| throwExceptionIf(ExceptionType::OutOfBoundsMemoryAccess, m_jit.branch64(RelationalCondition::AboveOrEqual, wasmScratchGPR, TrustedImm64(static_cast<int64_t>(maximum)))); |
| } |
| break; |
| } |
| } |
| |
| #if CPU(ARM64) |
| m_jit.addZeroExtend64(GPRInfo::wasmBaseMemoryPointer, pointerLocation.asGPR(), wasmScratchGPR); |
| #else |
| m_jit.zeroExtend32ToWord(pointerLocation.asGPR(), wasmScratchGPR); |
| m_jit.addPtr(GPRInfo::wasmBaseMemoryPointer, wasmScratchGPR); |
| #endif |
| |
| consume(pointer); |
| return Location::fromGPR(wasmScratchGPR); |
| } |
| |
| template<typename Functor> |
| auto emitCheckAndPrepareAndMaterializePointerApply(Value pointer, uint32_t uoffset, uint32_t sizeOfOperation, Functor&& functor) -> decltype(auto) |
| { |
| uint64_t boundary = static_cast<uint64_t>(sizeOfOperation) + uoffset - 1; |
| |
| ScratchScope<1, 0> scratches(*this); |
| Location pointerLocation; |
| if (pointer.isConst()) { |
| uint64_t constantPointer = static_cast<uint64_t>(static_cast<uint32_t>(pointer.asI32())); |
| uint64_t finalOffset = constantPointer + uoffset; |
| if (!(finalOffset > static_cast<uint64_t>(std::numeric_limits<int32_t>::max()) || !B3::Air::Arg::isValidAddrForm(B3::Air::Move, finalOffset, Width::Width128))) { |
| switch (m_mode) { |
| case MemoryMode::BoundsChecking: { |
| m_jit.move(TrustedImm64(constantPointer + boundary), wasmScratchGPR); |
| throwExceptionIf(ExceptionType::OutOfBoundsMemoryAccess, m_jit.branch64(RelationalCondition::AboveOrEqual, wasmScratchGPR, GPRInfo::wasmBoundsCheckingSizeRegister)); |
| break; |
| } |
| case MemoryMode::Signaling: { |
| if (uoffset >= Memory::fastMappedRedzoneBytes()) { |
| uint64_t maximum = m_info.memory.maximum() ? m_info.memory.maximum().bytes() : std::numeric_limits<uint32_t>::max(); |
| if ((constantPointer + boundary) >= maximum) |
| throwExceptionIf(ExceptionType::OutOfBoundsMemoryAccess, m_jit.jump()); |
| } |
| break; |
| } |
| } |
| return functor(CCallHelpers::Address(GPRInfo::wasmBaseMemoryPointer, static_cast<int32_t>(finalOffset))); |
| } |
| pointerLocation = Location::fromGPR(scratches.gpr(0)); |
| emitMoveConst(pointer, pointerLocation); |
| } else |
| pointerLocation = loadIfNecessary(pointer); |
| ASSERT(pointerLocation.isGPR()); |
| |
| switch (m_mode) { |
| case MemoryMode::BoundsChecking: { |
| // We're not using signal handling only when the memory is not shared. |
| // Regardless of signaling, we must check that no memory access exceeds the current memory size. |
| m_jit.zeroExtend32ToWord(pointerLocation.asGPR(), wasmScratchGPR); |
| if (boundary) |
| m_jit.add64(TrustedImm64(boundary), wasmScratchGPR); |
| throwExceptionIf(ExceptionType::OutOfBoundsMemoryAccess, m_jit.branch64(RelationalCondition::AboveOrEqual, wasmScratchGPR, GPRInfo::wasmBoundsCheckingSizeRegister)); |
| break; |
| } |
| |
| case MemoryMode::Signaling: { |
| // We've virtually mapped 4GiB+redzone for this memory. Only the user-allocated pages are addressable, contiguously in range [0, current], |
| // and everything above is mapped PROT_NONE. We don't need to perform any explicit bounds check in the 4GiB range because WebAssembly register |
| // memory accesses are 32-bit. However WebAssembly register + offset accesses perform the addition in 64-bit which can push an access above |
| // the 32-bit limit (the offset is unsigned 32-bit). The redzone will catch most small offsets, and we'll explicitly bounds check any |
| // register + large offset access. We don't think this will be generated frequently. |
| // |
| // We could check that register + large offset doesn't exceed 4GiB+redzone since that's technically the limit we need to avoid overflowing the |
| // PROT_NONE region, but it's better if we use a smaller immediate because it can codegens better. We know that anything equal to or greater |
| // than the declared 'maximum' will trap, so we can compare against that number. If there was no declared 'maximum' then we still know that |
| // any access equal to or greater than 4GiB will trap, no need to add the redzone. |
| if (uoffset >= Memory::fastMappedRedzoneBytes()) { |
| uint64_t maximum = m_info.memory.maximum() ? m_info.memory.maximum().bytes() : std::numeric_limits<uint32_t>::max(); |
| m_jit.zeroExtend32ToWord(pointerLocation.asGPR(), wasmScratchGPR); |
| if (boundary) |
| m_jit.add64(TrustedImm64(boundary), wasmScratchGPR); |
| throwExceptionIf(ExceptionType::OutOfBoundsMemoryAccess, m_jit.branch64(RelationalCondition::AboveOrEqual, wasmScratchGPR, TrustedImm64(static_cast<int64_t>(maximum)))); |
| } |
| break; |
| } |
| } |
| |
| #if CPU(ARM64) |
| if (!(static_cast<uint64_t>(uoffset) > static_cast<uint64_t>(std::numeric_limits<int32_t>::max()) || !B3::Air::Arg::isValidAddrForm(B3::Air::Move, uoffset, Width::Width128))) |
| return functor(CCallHelpers::BaseIndex(GPRInfo::wasmBaseMemoryPointer, pointerLocation.asGPR(), CCallHelpers::TimesOne, static_cast<int32_t>(uoffset), CCallHelpers::Extend::ZExt32)); |
| |
| m_jit.addZeroExtend64(GPRInfo::wasmBaseMemoryPointer, pointerLocation.asGPR(), wasmScratchGPR); |
| #else |
| m_jit.zeroExtend32ToWord(pointerLocation.asGPR(), wasmScratchGPR); |
| m_jit.addPtr(GPRInfo::wasmBaseMemoryPointer, wasmScratchGPR); |
| #endif |
| |
| if (static_cast<uint64_t>(uoffset) > static_cast<uint64_t>(std::numeric_limits<int32_t>::max()) || !B3::Air::Arg::isValidAddrForm(B3::Air::Move, uoffset, Width::Width128)) { |
| m_jit.add64(TrustedImm64(static_cast<int64_t>(uoffset)), wasmScratchGPR); |
| return functor(Address(wasmScratchGPR)); |
| } |
| return functor(Address(wasmScratchGPR, static_cast<int32_t>(uoffset))); |
| } |
| |
| static inline uint32_t sizeOfLoadOp(LoadOpType op) |
| { |
| switch (op) { |
| case LoadOpType::I32Load8S: |
| case LoadOpType::I32Load8U: |
| case LoadOpType::I64Load8S: |
| case LoadOpType::I64Load8U: |
| return 1; |
| case LoadOpType::I32Load16S: |
| case LoadOpType::I64Load16S: |
| case LoadOpType::I32Load16U: |
| case LoadOpType::I64Load16U: |
| return 2; |
| case LoadOpType::I32Load: |
| case LoadOpType::I64Load32S: |
| case LoadOpType::I64Load32U: |
| case LoadOpType::F32Load: |
| return 4; |
| case LoadOpType::I64Load: |
| case LoadOpType::F64Load: |
| return 8; |
| } |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| |
| static inline TypeKind typeOfLoadOp(LoadOpType op) |
| { |
| switch (op) { |
| case LoadOpType::I32Load8S: |
| case LoadOpType::I32Load8U: |
| case LoadOpType::I32Load16S: |
| case LoadOpType::I32Load16U: |
| case LoadOpType::I32Load: |
| return TypeKind::I32; |
| case LoadOpType::I64Load8S: |
| case LoadOpType::I64Load8U: |
| case LoadOpType::I64Load16S: |
| case LoadOpType::I64Load16U: |
| case LoadOpType::I64Load32S: |
| case LoadOpType::I64Load32U: |
| case LoadOpType::I64Load: |
| return TypeKind::I64; |
| case LoadOpType::F32Load: |
| return TypeKind::F32; |
| case LoadOpType::F64Load: |
| return TypeKind::F64; |
| } |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| |
| Address materializePointer(Location pointerLocation, uint32_t uoffset) |
| { |
| if (static_cast<uint64_t>(uoffset) > static_cast<uint64_t>(std::numeric_limits<int32_t>::max()) || !B3::Air::Arg::isValidAddrForm(B3::Air::Move, uoffset, Width::Width128)) { |
| m_jit.add64(TrustedImm64(static_cast<int64_t>(uoffset)), pointerLocation.asGPR()); |
| return Address(pointerLocation.asGPR()); |
| } |
| return Address(pointerLocation.asGPR(), static_cast<int32_t>(uoffset)); |
| } |
| |
| #define CREATE_OP_STRING(name, ...) #name, |
| |
| constexpr static const char* LOAD_OP_NAMES[14] = { |
| "I32Load", "I64Load", "F32Load", "F64Load", |
| "I32Load8S", "I32Load8U", "I32Load16S", "I32Load16U", |
| "I64Load8S", "I64Load8U", "I64Load16S", "I64Load16U", "I64Load32S", "I64Load32U" |
| }; |
| |
| PartialResult WARN_UNUSED_RETURN load(LoadOpType loadOp, Value pointer, Value& result, uint32_t uoffset) |
| { |
| if (UNLIKELY(sumOverflows<uint32_t>(uoffset, sizeOfLoadOp(loadOp)))) { |
| // FIXME: Same issue as in AirIRGenerator::load(): https://bugs.webkit.org/show_bug.cgi?id=166435 |
| emitThrowException(ExceptionType::OutOfBoundsMemoryAccess); |
| consume(pointer); |
| |
| // Unreachable at runtime, so we just add a constant that makes the types work out. |
| switch (loadOp) { |
| case LoadOpType::I32Load8S: |
| case LoadOpType::I32Load16S: |
| case LoadOpType::I32Load: |
| case LoadOpType::I32Load16U: |
| case LoadOpType::I32Load8U: |
| result = Value::fromI32(0); |
| break; |
| case LoadOpType::I64Load8S: |
| case LoadOpType::I64Load8U: |
| case LoadOpType::I64Load16S: |
| case LoadOpType::I64Load32U: |
| case LoadOpType::I64Load32S: |
| case LoadOpType::I64Load: |
| case LoadOpType::I64Load16U: |
| result = Value::fromI64(0); |
| break; |
| case LoadOpType::F32Load: |
| result = Value::fromF32(0); |
| break; |
| case LoadOpType::F64Load: |
| result = Value::fromF64(0); |
| break; |
| } |
| } else { |
| result = emitCheckAndPrepareAndMaterializePointerApply(pointer, uoffset, sizeOfLoadOp(loadOp), [&](auto location) -> Value { |
| consume(pointer); |
| Value result = topValue(typeOfLoadOp(loadOp)); |
| Location resultLocation = allocate(result); |
| |
| switch (loadOp) { |
| case LoadOpType::I32Load8S: |
| m_jit.load8SignedExtendTo32(location, resultLocation.asGPR()); |
| break; |
| case LoadOpType::I64Load8S: |
| m_jit.load8SignedExtendTo32(location, resultLocation.asGPR()); |
| m_jit.signExtend32To64(resultLocation.asGPR(), resultLocation.asGPR()); |
| break; |
| case LoadOpType::I32Load8U: |
| m_jit.load8(location, resultLocation.asGPR()); |
| break; |
| case LoadOpType::I64Load8U: |
| m_jit.load8(location, resultLocation.asGPR()); |
| break; |
| case LoadOpType::I32Load16S: |
| m_jit.load16SignedExtendTo32(location, resultLocation.asGPR()); |
| break; |
| case LoadOpType::I64Load16S: |
| m_jit.load16SignedExtendTo32(location, resultLocation.asGPR()); |
| m_jit.signExtend32To64(resultLocation.asGPR(), resultLocation.asGPR()); |
| break; |
| case LoadOpType::I32Load16U: |
| m_jit.load16(location, resultLocation.asGPR()); |
| break; |
| case LoadOpType::I64Load16U: |
| m_jit.load16(location, resultLocation.asGPR()); |
| break; |
| case LoadOpType::I32Load: |
| m_jit.load32(location, resultLocation.asGPR()); |
| break; |
| case LoadOpType::I64Load32U: |
| m_jit.load32(location, resultLocation.asGPR()); |
| break; |
| case LoadOpType::I64Load32S: |
| m_jit.load32(location, resultLocation.asGPR()); |
| m_jit.signExtend32To64(resultLocation.asGPR(), resultLocation.asGPR()); |
| break; |
| case LoadOpType::I64Load: |
| m_jit.load64(location, resultLocation.asGPR()); |
| break; |
| case LoadOpType::F32Load: |
| m_jit.loadFloat(location, resultLocation.asFPR()); |
| break; |
| case LoadOpType::F64Load: |
| m_jit.loadDouble(location, resultLocation.asFPR()); |
| break; |
| } |
| |
| return result; |
| }); |
| } |
| |
| LOG_INSTRUCTION(LOAD_OP_NAMES[(unsigned)loadOp - (unsigned)I32Load], pointer, uoffset, RESULT(result)); |
| |
| return { }; |
| } |
| |
| inline uint32_t sizeOfStoreOp(StoreOpType op) |
| { |
| switch (op) { |
| case StoreOpType::I32Store8: |
| case StoreOpType::I64Store8: |
| return 1; |
| case StoreOpType::I32Store16: |
| case StoreOpType::I64Store16: |
| return 2; |
| case StoreOpType::I32Store: |
| case StoreOpType::I64Store32: |
| case StoreOpType::F32Store: |
| return 4; |
| case StoreOpType::I64Store: |
| case StoreOpType::F64Store: |
| return 8; |
| } |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| |
| constexpr static const char* STORE_OP_NAMES[9] = { |
| "I32Store", "I64Store", "F32Store", "F64Store", |
| "I32Store8", "I32Store16", |
| "I64Store8", "I64Store16", "I64Store32", |
| }; |
| |
| PartialResult WARN_UNUSED_RETURN store(StoreOpType storeOp, Value pointer, Value value, uint32_t uoffset) |
| { |
| Location valueLocation = locationOf(value); |
| if (UNLIKELY(sumOverflows<uint32_t>(uoffset, sizeOfStoreOp(storeOp)))) { |
| // FIXME: Same issue as in AirIRGenerator::load(): https://bugs.webkit.org/show_bug.cgi?id=166435 |
| emitThrowException(ExceptionType::OutOfBoundsMemoryAccess); |
| consume(pointer); |
| consume(value); |
| } else { |
| emitCheckAndPrepareAndMaterializePointerApply(pointer, uoffset, sizeOfStoreOp(storeOp), [&](auto location) -> void { |
| Location valueLocation; |
| if (value.isConst() && value.isFloat()) { |
| ScratchScope<0, 1> scratches(*this); |
| valueLocation = Location::fromFPR(scratches.fpr(0)); |
| emitMoveConst(value, valueLocation); |
| } else if (value.isConst()) { |
| ScratchScope<1, 0> scratches(*this); |
| valueLocation = Location::fromGPR(scratches.gpr(0)); |
| emitMoveConst(value, valueLocation); |
| } else |
| valueLocation = loadIfNecessary(value); |
| ASSERT(valueLocation.isRegister()); |
| |
| consume(value); |
| consume(pointer); |
| |
| switch (storeOp) { |
| case StoreOpType::I64Store8: |
| case StoreOpType::I32Store8: |
| m_jit.store8(valueLocation.asGPR(), location); |
| return; |
| case StoreOpType::I64Store16: |
| case StoreOpType::I32Store16: |
| m_jit.store16(valueLocation.asGPR(), location); |
| return; |
| case StoreOpType::I64Store32: |
| case StoreOpType::I32Store: |
| m_jit.store32(valueLocation.asGPR(), location); |
| return; |
| case StoreOpType::I64Store: |
| m_jit.store64(valueLocation.asGPR(), location); |
| return; |
| case StoreOpType::F32Store: |
| m_jit.storeFloat(valueLocation.asFPR(), location); |
| return; |
| case StoreOpType::F64Store: |
| m_jit.storeDouble(valueLocation.asFPR(), location); |
| return; |
| } |
| }); |
| } |
| |
| LOG_INSTRUCTION(STORE_OP_NAMES[(unsigned)storeOp - (unsigned)I32Store], pointer, uoffset, value, valueLocation); |
| |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addGrowMemory(Value delta, Value& result) |
| { |
| Vector<Value, 8> arguments = { instanceValue(), delta }; |
| emitCCall(&operationGrowMemory, arguments, TypeKind::I32, result); |
| restoreWebAssemblyGlobalState(); |
| |
| LOG_INSTRUCTION("GrowMemory", delta, RESULT(result)); |
| |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addCurrentMemory(Value& result) |
| { |
| result = topValue(TypeKind::I32); |
| Location resultLocation = allocate(result); |
| m_jit.loadPtr(Address(GPRInfo::wasmContextInstancePointer, Instance::offsetOfMemory()), wasmScratchGPR); |
| m_jit.loadPtr(Address(wasmScratchGPR, Memory::offsetOfHandle()), wasmScratchGPR); |
| m_jit.loadPtr(Address(wasmScratchGPR, BufferMemoryHandle::offsetOfSize()), wasmScratchGPR); |
| |
| constexpr uint32_t shiftValue = 16; |
| static_assert(PageCount::pageSize == 1ull << shiftValue, "This must hold for the code below to be correct."); |
| m_jit.urshiftPtr(Imm32(shiftValue), wasmScratchGPR); |
| m_jit.zeroExtend32ToWord(wasmScratchGPR, resultLocation.asGPR()); |
| |
| LOG_INSTRUCTION("CurrentMemory", RESULT(result)); |
| |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addMemoryFill(Value dstAddress, Value targetValue, Value count) |
| { |
| ASSERT(dstAddress.type() == TypeKind::I32); |
| ASSERT(targetValue.type() == TypeKind::I32); |
| ASSERT(count.type() == TypeKind::I32); |
| |
| Vector<Value, 8> arguments = { |
| instanceValue(), |
| dstAddress, targetValue, count |
| }; |
| Value shouldThrow; |
| emitCCall(&operationWasmMemoryFill, arguments, TypeKind::I32, shouldThrow); |
| Location shouldThrowLocation = allocate(shouldThrow); |
| |
| throwExceptionIf(ExceptionType::OutOfBoundsMemoryAccess, m_jit.branchTest32(ResultCondition::Zero, shouldThrowLocation.asGPR())); |
| |
| LOG_INSTRUCTION("MemoryFill", dstAddress, targetValue, count); |
| |
| consume(shouldThrow); |
| |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addMemoryCopy(Value dstAddress, Value srcAddress, Value count) |
| { |
| ASSERT(dstAddress.type() == TypeKind::I32); |
| ASSERT(srcAddress.type() == TypeKind::I32); |
| ASSERT(count.type() == TypeKind::I32); |
| |
| Vector<Value, 8> arguments = { |
| instanceValue(), |
| dstAddress, srcAddress, count |
| }; |
| Value shouldThrow; |
| emitCCall(&operationWasmMemoryCopy, arguments, TypeKind::I32, shouldThrow); |
| Location shouldThrowLocation = allocate(shouldThrow); |
| |
| throwExceptionIf(ExceptionType::OutOfBoundsMemoryAccess, m_jit.branchTest32(ResultCondition::Zero, shouldThrowLocation.asGPR())); |
| |
| LOG_INSTRUCTION("MemoryCopy", dstAddress, srcAddress, count); |
| |
| consume(shouldThrow); |
| |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addMemoryInit(unsigned dataSegmentIndex, Value dstAddress, Value srcAddress, Value length) |
| { |
| ASSERT(dstAddress.type() == TypeKind::I32); |
| ASSERT(srcAddress.type() == TypeKind::I32); |
| ASSERT(length.type() == TypeKind::I32); |
| |
| Vector<Value, 8> arguments = { |
| instanceValue(), |
| Value::fromI32(dataSegmentIndex), |
| dstAddress, srcAddress, length |
| }; |
| Value shouldThrow; |
| emitCCall(&operationWasmMemoryInit, arguments, TypeKind::I32, shouldThrow); |
| Location shouldThrowLocation = allocate(shouldThrow); |
| |
| throwExceptionIf(ExceptionType::OutOfBoundsMemoryAccess, m_jit.branchTest32(ResultCondition::Zero, shouldThrowLocation.asGPR())); |
| |
| LOG_INSTRUCTION("MemoryInit", dataSegmentIndex, dstAddress, srcAddress, length); |
| |
| consume(shouldThrow); |
| |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addDataDrop(unsigned dataSegmentIndex) |
| { |
| Vector<Value, 8> arguments = { instanceValue(), Value::fromI32(dataSegmentIndex) }; |
| emitCCall(&operationWasmDataDrop, arguments); |
| |
| LOG_INSTRUCTION("DataDrop", dataSegmentIndex); |
| return { }; |
| } |
| |
| // Atomics |
| |
| static inline Width accessWidth(ExtAtomicOpType op) |
| { |
| return static_cast<Width>(memoryLog2Alignment(op)); |
| } |
| |
| static inline uint32_t sizeOfAtomicOpMemoryAccess(ExtAtomicOpType op) |
| { |
| return bytesForWidth(accessWidth(op)); |
| } |
| |
| void emitSanitizeAtomicResult(ExtAtomicOpType op, TypeKind resultType, GPRReg source, GPRReg dest) |
| { |
| switch (resultType) { |
| case TypeKind::I64: { |
| switch (accessWidth(op)) { |
| case Width8: |
| m_jit.zeroExtend8To32(source, dest); |
| return; |
| case Width16: |
| m_jit.zeroExtend16To32(source, dest); |
| return; |
| case Width32: |
| m_jit.zeroExtend32ToWord(source, dest); |
| return; |
| case Width64: |
| m_jit.move(source, dest); |
| return; |
| case Width128: |
| RELEASE_ASSERT_NOT_REACHED(); |
| return; |
| } |
| return; |
| } |
| case TypeKind::I32: |
| switch (accessWidth(op)) { |
| case Width8: |
| m_jit.zeroExtend8To32(source, dest); |
| return; |
| case Width16: |
| m_jit.zeroExtend16To32(source, dest); |
| return; |
| case Width32: |
| case Width64: |
| m_jit.move(source, dest); |
| return; |
| case Width128: |
| RELEASE_ASSERT_NOT_REACHED(); |
| return; |
| } |
| return; |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| return; |
| } |
| } |
| |
| void emitSanitizeAtomicResult(ExtAtomicOpType op, TypeKind resultType, GPRReg result) |
| { |
| emitSanitizeAtomicResult(op, resultType, result, result); |
| } |
| |
| template<typename Functor> |
| void emitAtomicOpGeneric(ExtAtomicOpType op, Address address, GPRReg oldGPR, GPRReg scratchGPR, const Functor& functor) |
| { |
| Width accessWidth = this->accessWidth(op); |
| |
| // We need a CAS loop or a LL/SC loop. Using prepare/attempt jargon, we want: |
| // |
| // Block #reloop: |
| // Prepare |
| // Operation |
| // Attempt |
| // Successors: Then:#done, Else:#reloop |
| // Block #done: |
| // Move oldValue, result |
| |
| // Prepare |
| auto reloopLabel = m_jit.label(); |
| switch (accessWidth) { |
| case Width8: |
| #if CPU(ARM64) |
| m_jit.loadLinkAcq8(address, oldGPR); |
| #else |
| m_jit.load8SignedExtendTo32(address, oldGPR); |
| #endif |
| break; |
| case Width16: |
| #if CPU(ARM64) |
| m_jit.loadLinkAcq16(address, oldGPR); |
| #else |
| m_jit.load16SignedExtendTo32(address, oldGPR); |
| #endif |
| break; |
| case Width32: |
| #if CPU(ARM64) |
| m_jit.loadLinkAcq32(address, oldGPR); |
| #else |
| m_jit.load32(address, oldGPR); |
| #endif |
| break; |
| case Width64: |
| #if CPU(ARM64) |
| m_jit.loadLinkAcq64(address, oldGPR); |
| #else |
| m_jit.load64(address, oldGPR); |
| #endif |
| break; |
| case Width128: |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| |
| // Operation |
| functor(oldGPR, scratchGPR); |
| |
| #if CPU(X86_64) |
| switch (accessWidth) { |
| case Width8: |
| m_jit.branchAtomicStrongCAS8(StatusCondition::Failure, oldGPR, scratchGPR, address).linkTo(reloopLabel, &m_jit); |
| break; |
| case Width16: |
| m_jit.branchAtomicStrongCAS16(StatusCondition::Failure, oldGPR, scratchGPR, address).linkTo(reloopLabel, &m_jit); |
| break; |
| case Width32: |
| m_jit.branchAtomicStrongCAS32(StatusCondition::Failure, oldGPR, scratchGPR, address).linkTo(reloopLabel, &m_jit); |
| break; |
| case Width64: |
| m_jit.branchAtomicStrongCAS64(StatusCondition::Failure, oldGPR, scratchGPR, address).linkTo(reloopLabel, &m_jit); |
| break; |
| case Width128: |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| #elif CPU(ARM64) |
| switch (accessWidth) { |
| case Width8: |
| m_jit.storeCondRel8(scratchGPR, address, scratchGPR); |
| break; |
| case Width16: |
| m_jit.storeCondRel16(scratchGPR, address, scratchGPR); |
| break; |
| case Width32: |
| m_jit.storeCondRel32(scratchGPR, address, scratchGPR); |
| break; |
| case Width64: |
| m_jit.storeCondRel64(scratchGPR, address, scratchGPR); |
| break; |
| case Width128: |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| m_jit.branchTest32(ResultCondition::NonZero, scratchGPR).linkTo(reloopLabel, &m_jit); |
| #endif |
| } |
| |
| Value WARN_UNUSED_RETURN emitAtomicLoadOp(ExtAtomicOpType loadOp, Type valueType, Location pointer, uint32_t uoffset) |
| { |
| ASSERT(pointer.isGPR()); |
| |
| // For Atomic access, we need SimpleAddress (uoffset = 0). |
| if (uoffset) |
| m_jit.add64(TrustedImm64(static_cast<int64_t>(uoffset)), pointer.asGPR()); |
| Address address = Address(pointer.asGPR()); |
| |
| if (accessWidth(loadOp) != Width8) |
| throwExceptionIf(ExceptionType::OutOfBoundsMemoryAccess, m_jit.branchTest64(ResultCondition::NonZero, pointer.asGPR(), TrustedImm64(sizeOfAtomicOpMemoryAccess(loadOp) - 1))); |
| |
| Value result = topValue(valueType.kind); |
| Location resultLocation = allocate(result); |
| |
| if (!(isARM64_LSE() || isX86_64())) { |
| ScratchScope<1, 0> scratches(*this); |
| emitAtomicOpGeneric(loadOp, address, resultLocation.asGPR(), scratches.gpr(0), [&](GPRReg oldGPR, GPRReg newGPR) { |
| emitSanitizeAtomicResult(loadOp, canonicalWidth(accessWidth(loadOp)) == Width64 ? TypeKind::I64 : TypeKind::I32, oldGPR, newGPR); |
| }); |
| emitSanitizeAtomicResult(loadOp, valueType.kind, resultLocation.asGPR()); |
| return result; |
| } |
| |
| m_jit.move(TrustedImm32(0), resultLocation.asGPR()); |
| switch (loadOp) { |
| case ExtAtomicOpType::I32AtomicLoad: { |
| #if CPU(ARM64) |
| m_jit.atomicXchgAdd32(resultLocation.asGPR(), address, resultLocation.asGPR()); |
| #else |
| m_jit.atomicXchgAdd32(resultLocation.asGPR(), address); |
| #endif |
| break; |
| } |
| case ExtAtomicOpType::I64AtomicLoad: { |
| #if CPU(ARM64) |
| m_jit.atomicXchgAdd64(resultLocation.asGPR(), address, resultLocation.asGPR()); |
| #else |
| m_jit.atomicXchgAdd64(resultLocation.asGPR(), address); |
| #endif |
| break; |
| } |
| case ExtAtomicOpType::I32AtomicLoad8U: { |
| #if CPU(ARM64) |
| m_jit.atomicXchgAdd8(resultLocation.asGPR(), address, resultLocation.asGPR()); |
| #else |
| m_jit.atomicXchgAdd8(resultLocation.asGPR(), address); |
| #endif |
| break; |
| } |
| case ExtAtomicOpType::I32AtomicLoad16U: { |
| #if CPU(ARM64) |
| m_jit.atomicXchgAdd16(resultLocation.asGPR(), address, resultLocation.asGPR()); |
| #else |
| m_jit.atomicXchgAdd16(resultLocation.asGPR(), address); |
| #endif |
| break; |
| } |
| case ExtAtomicOpType::I64AtomicLoad8U: { |
| #if CPU(ARM64) |
| m_jit.atomicXchgAdd8(resultLocation.asGPR(), address, resultLocation.asGPR()); |
| #else |
| m_jit.atomicXchgAdd8(resultLocation.asGPR(), address); |
| #endif |
| break; |
| } |
| case ExtAtomicOpType::I64AtomicLoad16U: { |
| #if CPU(ARM64) |
| m_jit.atomicXchgAdd16(resultLocation.asGPR(), address, resultLocation.asGPR()); |
| #else |
| m_jit.atomicXchgAdd16(resultLocation.asGPR(), address); |
| #endif |
| break; |
| } |
| case ExtAtomicOpType::I64AtomicLoad32U: { |
| #if CPU(ARM64) |
| m_jit.atomicXchgAdd32(resultLocation.asGPR(), address, resultLocation.asGPR()); |
| #else |
| m_jit.atomicXchgAdd32(resultLocation.asGPR(), address); |
| #endif |
| break; |
| } |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| break; |
| } |
| |
| emitSanitizeAtomicResult(loadOp, valueType.kind, resultLocation.asGPR()); |
| |
| return result; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN atomicLoad(ExtAtomicOpType loadOp, Type valueType, ExpressionType pointer, ExpressionType& result, uint32_t uoffset) |
| { |
| if (UNLIKELY(sumOverflows<uint32_t>(uoffset, sizeOfAtomicOpMemoryAccess(loadOp)))) { |
| // FIXME: Same issue as in AirIRGenerator::load(): https://bugs.webkit.org/show_bug.cgi?id=166435 |
| emitThrowException(ExceptionType::OutOfBoundsMemoryAccess); |
| consume(pointer); |
| result = valueType.isI64() ? Value::fromI64(0) : Value::fromI32(0); |
| } else |
| result = emitAtomicLoadOp(loadOp, valueType, emitCheckAndPreparePointer(pointer, uoffset, sizeOfAtomicOpMemoryAccess(loadOp)), uoffset); |
| |
| LOG_INSTRUCTION(makeString(loadOp), pointer, uoffset, RESULT(result)); |
| |
| return { }; |
| } |
| |
| void emitAtomicStoreOp(ExtAtomicOpType storeOp, Type, Location pointer, Value value, uint32_t uoffset) |
| { |
| ASSERT(pointer.isGPR()); |
| |
| // For Atomic access, we need SimpleAddress (uoffset = 0). |
| if (uoffset) |
| m_jit.add64(TrustedImm64(static_cast<int64_t>(uoffset)), pointer.asGPR()); |
| Address address = Address(pointer.asGPR()); |
| |
| if (accessWidth(storeOp) != Width8) |
| throwExceptionIf(ExceptionType::OutOfBoundsMemoryAccess, m_jit.branchTest64(ResultCondition::NonZero, pointer.asGPR(), TrustedImm64(sizeOfAtomicOpMemoryAccess(storeOp) - 1))); |
| |
| GPRReg scratch1GPR = InvalidGPRReg; |
| GPRReg scratch2GPR = InvalidGPRReg; |
| Location valueLocation; |
| if (value.isConst()) { |
| ScratchScope<3, 0> scratches(*this); |
| valueLocation = Location::fromGPR(scratches.gpr(0)); |
| emitMoveConst(value, valueLocation); |
| scratch1GPR = scratches.gpr(1); |
| scratch2GPR = scratches.gpr(2); |
| } else { |
| ScratchScope<2, 0> scratches(*this); |
| valueLocation = loadIfNecessary(value); |
| scratch1GPR = scratches.gpr(0); |
| scratch2GPR = scratches.gpr(1); |
| } |
| ASSERT(valueLocation.isRegister()); |
| |
| consume(value); |
| |
| if (!(isARM64_LSE() || isX86_64())) { |
| emitAtomicOpGeneric(storeOp, address, scratch1GPR, scratch2GPR, [&](GPRReg, GPRReg newGPR) { |
| m_jit.move(valueLocation.asGPR(), newGPR); |
| }); |
| return; |
| } |
| |
| switch (storeOp) { |
| case ExtAtomicOpType::I32AtomicStore: { |
| #if CPU(ARM64) |
| m_jit.atomicXchg32(valueLocation.asGPR(), address, scratch1GPR); |
| #else |
| m_jit.store32(valueLocation.asGPR(), address); |
| #endif |
| break; |
| } |
| case ExtAtomicOpType::I64AtomicStore: { |
| #if CPU(ARM64) |
| m_jit.atomicXchg64(valueLocation.asGPR(), address, scratch1GPR); |
| #else |
| m_jit.store64(valueLocation.asGPR(), address); |
| #endif |
| break; |
| } |
| case ExtAtomicOpType::I32AtomicStore8U: { |
| #if CPU(ARM64) |
| m_jit.atomicXchg8(valueLocation.asGPR(), address, scratch1GPR); |
| #else |
| m_jit.store8(valueLocation.asGPR(), address); |
| #endif |
| break; |
| } |
| case ExtAtomicOpType::I32AtomicStore16U: { |
| #if CPU(ARM64) |
| m_jit.atomicXchg16(valueLocation.asGPR(), address, scratch1GPR); |
| #else |
| m_jit.store16(valueLocation.asGPR(), address); |
| #endif |
| break; |
| } |
| case ExtAtomicOpType::I64AtomicStore8U: { |
| #if CPU(ARM64) |
| m_jit.atomicXchg8(valueLocation.asGPR(), address, scratch1GPR); |
| #else |
| m_jit.store8(valueLocation.asGPR(), address); |
| #endif |
| break; |
| } |
| case ExtAtomicOpType::I64AtomicStore16U: { |
| #if CPU(ARM64) |
| m_jit.atomicXchg16(valueLocation.asGPR(), address, scratch1GPR); |
| #else |
| m_jit.store16(valueLocation.asGPR(), address); |
| #endif |
| break; |
| } |
| case ExtAtomicOpType::I64AtomicStore32U: { |
| #if CPU(ARM64) |
| m_jit.atomicXchg32(valueLocation.asGPR(), address, scratch1GPR); |
| #else |
| m_jit.store32(valueLocation.asGPR(), address); |
| #endif |
| break; |
| } |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| break; |
| } |
| } |
| |
| PartialResult WARN_UNUSED_RETURN atomicStore(ExtAtomicOpType storeOp, Type valueType, ExpressionType pointer, ExpressionType value, uint32_t uoffset) |
| { |
| Location valueLocation = locationOf(value); |
| if (UNLIKELY(sumOverflows<uint32_t>(uoffset, sizeOfAtomicOpMemoryAccess(storeOp)))) { |
| // FIXME: Same issue as in AirIRGenerator::load(): https://bugs.webkit.org/show_bug.cgi?id=166435 |
| emitThrowException(ExceptionType::OutOfBoundsMemoryAccess); |
| consume(pointer); |
| consume(value); |
| } else |
| emitAtomicStoreOp(storeOp, valueType, emitCheckAndPreparePointer(pointer, uoffset, sizeOfAtomicOpMemoryAccess(storeOp)), value, uoffset); |
| |
| LOG_INSTRUCTION(makeString(storeOp), pointer, uoffset, value, valueLocation); |
| |
| return { }; |
| } |
| |
| Value emitAtomicBinaryRMWOp(ExtAtomicOpType op, Type valueType, Location pointer, Value value, uint32_t uoffset) |
| { |
| ASSERT(pointer.isGPR()); |
| |
| // For Atomic access, we need SimpleAddress (uoffset = 0). |
| if (uoffset) |
| m_jit.add64(TrustedImm64(static_cast<int64_t>(uoffset)), pointer.asGPR()); |
| Address address = Address(pointer.asGPR()); |
| |
| if (accessWidth(op) != Width8) |
| throwExceptionIf(ExceptionType::OutOfBoundsMemoryAccess, m_jit.branchTest64(ResultCondition::NonZero, pointer.asGPR(), TrustedImm64(sizeOfAtomicOpMemoryAccess(op) - 1))); |
| |
| Value result = topValue(valueType.kind); |
| Location resultLocation = allocate(result); |
| |
| GPRReg scratchGPR = InvalidGPRReg; |
| Location valueLocation; |
| if (value.isConst()) { |
| ScratchScope<2, 0> scratches(*this); |
| valueLocation = Location::fromGPR(scratches.gpr(0)); |
| emitMoveConst(value, valueLocation); |
| scratchGPR = scratches.gpr(1); |
| } else { |
| ScratchScope<1, 0> scratches(*this); |
| valueLocation = loadIfNecessary(value); |
| scratchGPR = scratches.gpr(0); |
| } |
| ASSERT(valueLocation.isRegister()); |
| consume(value); |
| |
| switch (op) { |
| case ExtAtomicOpType::I32AtomicRmw8AddU: |
| case ExtAtomicOpType::I32AtomicRmw16AddU: |
| case ExtAtomicOpType::I32AtomicRmwAdd: |
| case ExtAtomicOpType::I64AtomicRmw8AddU: |
| case ExtAtomicOpType::I64AtomicRmw16AddU: |
| case ExtAtomicOpType::I64AtomicRmw32AddU: |
| case ExtAtomicOpType::I64AtomicRmwAdd: |
| if (isX86() || isARM64_LSE()) { |
| switch (accessWidth(op)) { |
| case Width8: |
| #if CPU(ARM64) |
| m_jit.atomicXchgAdd8(valueLocation.asGPR(), address, resultLocation.asGPR()); |
| #else |
| m_jit.move(valueLocation.asGPR(), resultLocation.asGPR()); |
| m_jit.atomicXchgAdd8(resultLocation.asGPR(), address); |
| #endif |
| break; |
| case Width16: |
| #if CPU(ARM64) |
| m_jit.atomicXchgAdd16(valueLocation.asGPR(), address, resultLocation.asGPR()); |
| #else |
| m_jit.move(valueLocation.asGPR(), resultLocation.asGPR()); |
| m_jit.atomicXchgAdd16(resultLocation.asGPR(), address); |
| #endif |
| break; |
| case Width32: |
| #if CPU(ARM64) |
| m_jit.atomicXchgAdd32(valueLocation.asGPR(), address, resultLocation.asGPR()); |
| #else |
| m_jit.move(valueLocation.asGPR(), resultLocation.asGPR()); |
| m_jit.atomicXchgAdd32(resultLocation.asGPR(), address); |
| #endif |
| break; |
| case Width64: |
| #if CPU(ARM64) |
| m_jit.atomicXchgAdd64(valueLocation.asGPR(), address, resultLocation.asGPR()); |
| #else |
| m_jit.move(valueLocation.asGPR(), resultLocation.asGPR()); |
| m_jit.atomicXchgAdd64(resultLocation.asGPR(), address); |
| #endif |
| break; |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| break; |
| } |
| emitSanitizeAtomicResult(op, valueType.kind, resultLocation.asGPR()); |
| return result; |
| } |
| break; |
| case ExtAtomicOpType::I32AtomicRmw8SubU: |
| case ExtAtomicOpType::I32AtomicRmw16SubU: |
| case ExtAtomicOpType::I32AtomicRmwSub: |
| case ExtAtomicOpType::I64AtomicRmw8SubU: |
| case ExtAtomicOpType::I64AtomicRmw16SubU: |
| case ExtAtomicOpType::I64AtomicRmw32SubU: |
| case ExtAtomicOpType::I64AtomicRmwSub: |
| if (isX86() || isARM64_LSE()) { |
| m_jit.move(valueLocation.asGPR(), scratchGPR); |
| if (valueType.isI64()) |
| m_jit.neg64(scratchGPR); |
| else |
| m_jit.neg32(scratchGPR); |
| |
| switch (accessWidth(op)) { |
| case Width8: |
| #if CPU(ARM64) |
| m_jit.atomicXchgAdd8(scratchGPR, address, resultLocation.asGPR()); |
| #else |
| m_jit.move(scratchGPR, resultLocation.asGPR()); |
| m_jit.atomicXchgAdd8(resultLocation.asGPR(), address); |
| #endif |
| break; |
| case Width16: |
| #if CPU(ARM64) |
| m_jit.atomicXchgAdd16(scratchGPR, address, resultLocation.asGPR()); |
| #else |
| m_jit.move(scratchGPR, resultLocation.asGPR()); |
| m_jit.atomicXchgAdd16(resultLocation.asGPR(), address); |
| #endif |
| break; |
| case Width32: |
| #if CPU(ARM64) |
| m_jit.atomicXchgAdd32(scratchGPR, address, resultLocation.asGPR()); |
| #else |
| m_jit.move(scratchGPR, resultLocation.asGPR()); |
| m_jit.atomicXchgAdd32(resultLocation.asGPR(), address); |
| #endif |
| break; |
| case Width64: |
| #if CPU(ARM64) |
| m_jit.atomicXchgAdd64(scratchGPR, address, resultLocation.asGPR()); |
| #else |
| m_jit.move(scratchGPR, resultLocation.asGPR()); |
| m_jit.atomicXchgAdd64(resultLocation.asGPR(), address); |
| #endif |
| break; |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| break; |
| } |
| emitSanitizeAtomicResult(op, valueType.kind, resultLocation.asGPR()); |
| return result; |
| } |
| break; |
| case ExtAtomicOpType::I32AtomicRmw8AndU: |
| case ExtAtomicOpType::I32AtomicRmw16AndU: |
| case ExtAtomicOpType::I32AtomicRmwAnd: |
| case ExtAtomicOpType::I64AtomicRmw8AndU: |
| case ExtAtomicOpType::I64AtomicRmw16AndU: |
| case ExtAtomicOpType::I64AtomicRmw32AndU: |
| case ExtAtomicOpType::I64AtomicRmwAnd: |
| #if CPU(ARM64) |
| if (isARM64_LSE()) { |
| m_jit.move(valueLocation.asGPR(), scratchGPR); |
| if (valueType.isI64()) |
| m_jit.not64(scratchGPR); |
| else |
| m_jit.not32(scratchGPR); |
| |
| switch (accessWidth(op)) { |
| case Width8: |
| m_jit.atomicXchgClear8(scratchGPR, address, resultLocation.asGPR()); |
| break; |
| case Width16: |
| m_jit.atomicXchgClear16(scratchGPR, address, resultLocation.asGPR()); |
| break; |
| case Width32: |
| m_jit.atomicXchgClear32(scratchGPR, address, resultLocation.asGPR()); |
| break; |
| case Width64: |
| m_jit.atomicXchgClear64(scratchGPR, address, resultLocation.asGPR()); |
| break; |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| break; |
| } |
| emitSanitizeAtomicResult(op, valueType.kind, resultLocation.asGPR()); |
| return result; |
| } |
| #endif |
| break; |
| case ExtAtomicOpType::I32AtomicRmw8OrU: |
| case ExtAtomicOpType::I32AtomicRmw16OrU: |
| case ExtAtomicOpType::I32AtomicRmwOr: |
| case ExtAtomicOpType::I64AtomicRmw8OrU: |
| case ExtAtomicOpType::I64AtomicRmw16OrU: |
| case ExtAtomicOpType::I64AtomicRmw32OrU: |
| case ExtAtomicOpType::I64AtomicRmwOr: |
| #if CPU(ARM64) |
| if (isARM64_LSE()) { |
| switch (accessWidth(op)) { |
| case Width8: |
| m_jit.atomicXchgOr8(valueLocation.asGPR(), address, resultLocation.asGPR()); |
| break; |
| case Width16: |
| m_jit.atomicXchgOr16(valueLocation.asGPR(), address, resultLocation.asGPR()); |
| break; |
| case Width32: |
| m_jit.atomicXchgOr32(valueLocation.asGPR(), address, resultLocation.asGPR()); |
| break; |
| case Width64: |
| m_jit.atomicXchgOr64(valueLocation.asGPR(), address, resultLocation.asGPR()); |
| break; |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| break; |
| } |
| emitSanitizeAtomicResult(op, valueType.kind, resultLocation.asGPR()); |
| return result; |
| } |
| #endif |
| break; |
| case ExtAtomicOpType::I32AtomicRmw8XorU: |
| case ExtAtomicOpType::I32AtomicRmw16XorU: |
| case ExtAtomicOpType::I32AtomicRmwXor: |
| case ExtAtomicOpType::I64AtomicRmw8XorU: |
| case ExtAtomicOpType::I64AtomicRmw16XorU: |
| case ExtAtomicOpType::I64AtomicRmw32XorU: |
| case ExtAtomicOpType::I64AtomicRmwXor: |
| #if CPU(ARM64) |
| if (isARM64_LSE()) { |
| switch (accessWidth(op)) { |
| case Width8: |
| m_jit.atomicXchgXor8(valueLocation.asGPR(), address, resultLocation.asGPR()); |
| break; |
| case Width16: |
| m_jit.atomicXchgXor16(valueLocation.asGPR(), address, resultLocation.asGPR()); |
| break; |
| case Width32: |
| m_jit.atomicXchgXor32(valueLocation.asGPR(), address, resultLocation.asGPR()); |
| break; |
| case Width64: |
| m_jit.atomicXchgXor64(valueLocation.asGPR(), address, resultLocation.asGPR()); |
| break; |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| break; |
| } |
| emitSanitizeAtomicResult(op, valueType.kind, resultLocation.asGPR()); |
| return result; |
| } |
| #endif |
| break; |
| case ExtAtomicOpType::I32AtomicRmw8XchgU: |
| case ExtAtomicOpType::I32AtomicRmw16XchgU: |
| case ExtAtomicOpType::I32AtomicRmwXchg: |
| case ExtAtomicOpType::I64AtomicRmw8XchgU: |
| case ExtAtomicOpType::I64AtomicRmw16XchgU: |
| case ExtAtomicOpType::I64AtomicRmw32XchgU: |
| case ExtAtomicOpType::I64AtomicRmwXchg: |
| if (isX86() || isARM64_LSE()) { |
| switch (accessWidth(op)) { |
| case Width8: |
| #if CPU(ARM64) |
| m_jit.atomicXchg8(valueLocation.asGPR(), address, resultLocation.asGPR()); |
| #else |
| m_jit.move(valueLocation.asGPR(), resultLocation.asGPR()); |
| m_jit.atomicXchg8(resultLocation.asGPR(), address); |
| #endif |
| break; |
| case Width16: |
| #if CPU(ARM64) |
| m_jit.atomicXchg16(valueLocation.asGPR(), address, resultLocation.asGPR()); |
| #else |
| m_jit.move(valueLocation.asGPR(), resultLocation.asGPR()); |
| m_jit.atomicXchg16(resultLocation.asGPR(), address); |
| #endif |
| break; |
| case Width32: |
| #if CPU(ARM64) |
| m_jit.atomicXchg32(valueLocation.asGPR(), address, resultLocation.asGPR()); |
| #else |
| m_jit.move(valueLocation.asGPR(), resultLocation.asGPR()); |
| m_jit.atomicXchg32(resultLocation.asGPR(), address); |
| #endif |
| break; |
| case Width64: |
| #if CPU(ARM64) |
| m_jit.atomicXchg64(valueLocation.asGPR(), address, resultLocation.asGPR()); |
| #else |
| m_jit.move(valueLocation.asGPR(), resultLocation.asGPR()); |
| m_jit.atomicXchg64(resultLocation.asGPR(), address); |
| #endif |
| break; |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| break; |
| } |
| emitSanitizeAtomicResult(op, valueType.kind, resultLocation.asGPR()); |
| return result; |
| } |
| break; |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| break; |
| } |
| |
| emitAtomicOpGeneric(op, address, resultLocation.asGPR(), scratchGPR, [&](GPRReg oldGPR, GPRReg newGPR) { |
| switch (op) { |
| case ExtAtomicOpType::I32AtomicRmw16AddU: |
| case ExtAtomicOpType::I32AtomicRmw8AddU: |
| case ExtAtomicOpType::I32AtomicRmwAdd: |
| m_jit.add32(oldGPR, valueLocation.asGPR(), newGPR); |
| break; |
| case ExtAtomicOpType::I64AtomicRmw8AddU: |
| case ExtAtomicOpType::I64AtomicRmw16AddU: |
| case ExtAtomicOpType::I64AtomicRmw32AddU: |
| case ExtAtomicOpType::I64AtomicRmwAdd: |
| m_jit.add64(oldGPR, valueLocation.asGPR(), newGPR); |
| break; |
| case ExtAtomicOpType::I32AtomicRmw8SubU: |
| case ExtAtomicOpType::I32AtomicRmw16SubU: |
| case ExtAtomicOpType::I32AtomicRmwSub: |
| m_jit.sub32(oldGPR, valueLocation.asGPR(), newGPR); |
| break; |
| case ExtAtomicOpType::I64AtomicRmw8SubU: |
| case ExtAtomicOpType::I64AtomicRmw16SubU: |
| case ExtAtomicOpType::I64AtomicRmw32SubU: |
| case ExtAtomicOpType::I64AtomicRmwSub: |
| m_jit.sub64(oldGPR, valueLocation.asGPR(), newGPR); |
| break; |
| case ExtAtomicOpType::I32AtomicRmw8AndU: |
| case ExtAtomicOpType::I32AtomicRmw16AndU: |
| case ExtAtomicOpType::I32AtomicRmwAnd: |
| m_jit.and32(oldGPR, valueLocation.asGPR(), newGPR); |
| break; |
| case ExtAtomicOpType::I64AtomicRmw8AndU: |
| case ExtAtomicOpType::I64AtomicRmw16AndU: |
| case ExtAtomicOpType::I64AtomicRmw32AndU: |
| case ExtAtomicOpType::I64AtomicRmwAnd: |
| m_jit.and64(oldGPR, valueLocation.asGPR(), newGPR); |
| break; |
| case ExtAtomicOpType::I32AtomicRmw8OrU: |
| case ExtAtomicOpType::I32AtomicRmw16OrU: |
| case ExtAtomicOpType::I32AtomicRmwOr: |
| m_jit.or32(oldGPR, valueLocation.asGPR(), newGPR); |
| break; |
| case ExtAtomicOpType::I64AtomicRmw8OrU: |
| case ExtAtomicOpType::I64AtomicRmw16OrU: |
| case ExtAtomicOpType::I64AtomicRmw32OrU: |
| case ExtAtomicOpType::I64AtomicRmwOr: |
| m_jit.or64(oldGPR, valueLocation.asGPR(), newGPR); |
| break; |
| case ExtAtomicOpType::I32AtomicRmw8XorU: |
| case ExtAtomicOpType::I32AtomicRmw16XorU: |
| case ExtAtomicOpType::I32AtomicRmwXor: |
| m_jit.xor32(oldGPR, valueLocation.asGPR(), newGPR); |
| break; |
| case ExtAtomicOpType::I64AtomicRmw8XorU: |
| case ExtAtomicOpType::I64AtomicRmw16XorU: |
| case ExtAtomicOpType::I64AtomicRmw32XorU: |
| case ExtAtomicOpType::I64AtomicRmwXor: |
| m_jit.xor64(oldGPR, valueLocation.asGPR(), newGPR); |
| break; |
| case ExtAtomicOpType::I32AtomicRmw8XchgU: |
| case ExtAtomicOpType::I32AtomicRmw16XchgU: |
| case ExtAtomicOpType::I32AtomicRmwXchg: |
| case ExtAtomicOpType::I64AtomicRmw8XchgU: |
| case ExtAtomicOpType::I64AtomicRmw16XchgU: |
| case ExtAtomicOpType::I64AtomicRmw32XchgU: |
| case ExtAtomicOpType::I64AtomicRmwXchg: |
| emitSanitizeAtomicResult(op, valueType.kind, valueLocation.asGPR(), newGPR); |
| break; |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| break; |
| } |
| }); |
| emitSanitizeAtomicResult(op, valueType.kind, resultLocation.asGPR()); |
| return result; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN atomicBinaryRMW(ExtAtomicOpType op, Type valueType, ExpressionType pointer, ExpressionType value, ExpressionType& result, uint32_t uoffset) |
| { |
| Location valueLocation = locationOf(value); |
| if (UNLIKELY(sumOverflows<uint32_t>(uoffset, sizeOfAtomicOpMemoryAccess(op)))) { |
| // FIXME: Even though this is provably out of bounds, it's not a validation error, so we have to handle it |
| // as a runtime exception. However, this may change: https://bugs.webkit.org/show_bug.cgi?id=166435 |
| emitThrowException(ExceptionType::OutOfBoundsMemoryAccess); |
| consume(pointer); |
| consume(value); |
| result = valueType.isI64() ? Value::fromI64(0) : Value::fromI32(0); |
| } else |
| result = emitAtomicBinaryRMWOp(op, valueType, emitCheckAndPreparePointer(pointer, uoffset, sizeOfAtomicOpMemoryAccess(op)), value, uoffset); |
| |
| LOG_INSTRUCTION(makeString(op), pointer, uoffset, value, valueLocation, RESULT(result)); |
| |
| return { }; |
| } |
| |
| Value WARN_UNUSED_RETURN emitAtomicCompareExchange(ExtAtomicOpType op, Type valueType, Location pointer, Value expected, Value value, uint32_t uoffset) |
| { |
| ASSERT(pointer.isGPR()); |
| |
| // For Atomic access, we need SimpleAddress (uoffset = 0). |
| if (uoffset) |
| m_jit.add64(TrustedImm64(static_cast<int64_t>(uoffset)), pointer.asGPR()); |
| Address address = Address(pointer.asGPR()); |
| Width valueWidth = widthForType(toB3Type(valueType)); |
| Width accessWidth = this->accessWidth(op); |
| |
| if (accessWidth != Width8) |
| throwExceptionIf(ExceptionType::OutOfBoundsMemoryAccess, m_jit.branchTest64(ResultCondition::NonZero, pointer.asGPR(), TrustedImm64(sizeOfAtomicOpMemoryAccess(op) - 1))); |
| |
| Value result = topValue(expected.type()); |
| Location resultLocation = allocate(result); |
| |
| ScratchScope<1, 0> scratches(*this); |
| GPRReg scratchGPR = scratches.gpr(0); |
| |
| // FIXME: We should have a better way to write this. |
| Location valueLocation; |
| Location expectedLocation; |
| if (value.isConst()) { |
| if (expected.isConst()) { |
| ScratchScope<2, 0> scratches(*this); |
| valueLocation = Location::fromGPR(scratches.gpr(0)); |
| expectedLocation = Location::fromGPR(scratches.gpr(1)); |
| emitMoveConst(value, valueLocation); |
| emitMoveConst(expected, expectedLocation); |
| } else { |
| ScratchScope<1, 0> scratches(*this); |
| valueLocation = Location::fromGPR(scratches.gpr(0)); |
| emitMoveConst(value, valueLocation); |
| expectedLocation = loadIfNecessary(expected); |
| } |
| } else { |
| valueLocation = loadIfNecessary(value); |
| if (expected.isConst()) { |
| ScratchScope<1, 0> scratches(*this); |
| expectedLocation = Location::fromGPR(scratches.gpr(0)); |
| emitMoveConst(expected, expectedLocation); |
| } else |
| expectedLocation = loadIfNecessary(expected); |
| } |
| |
| ASSERT(valueLocation.isRegister()); |
| ASSERT(expectedLocation.isRegister()); |
| |
| consume(value); |
| consume(expected); |
| |
| auto emitStrongCAS = [&](GPRReg expectedGPR, GPRReg valueGPR, GPRReg resultGPR) { |
| if (isX86_64() || isARM64_LSE()) { |
| m_jit.move(expectedGPR, resultGPR); |
| switch (accessWidth) { |
| case Width8: |
| m_jit.atomicStrongCAS8(resultGPR, valueGPR, address); |
| break; |
| case Width16: |
| m_jit.atomicStrongCAS16(resultGPR, valueGPR, address); |
| break; |
| case Width32: |
| m_jit.atomicStrongCAS32(resultGPR, valueGPR, address); |
| break; |
| case Width64: |
| m_jit.atomicStrongCAS64(resultGPR, valueGPR, address); |
| break; |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| break; |
| } |
| return; |
| } |
| |
| m_jit.move(expectedGPR, resultGPR); |
| switch (accessWidth) { |
| case Width8: |
| m_jit.atomicStrongCAS8(StatusCondition::Success, resultGPR, valueGPR, address, scratchGPR); |
| break; |
| case Width16: |
| m_jit.atomicStrongCAS16(StatusCondition::Success, resultGPR, valueGPR, address, scratchGPR); |
| break; |
| case Width32: |
| m_jit.atomicStrongCAS32(StatusCondition::Success, resultGPR, valueGPR, address, scratchGPR); |
| break; |
| case Width64: |
| m_jit.atomicStrongCAS64(StatusCondition::Success, resultGPR, valueGPR, address, scratchGPR); |
| break; |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| break; |
| } |
| }; |
| |
| if (valueWidth == accessWidth) { |
| emitStrongCAS(expectedLocation.asGPR(), valueLocation.asGPR(), resultLocation.asGPR()); |
| emitSanitizeAtomicResult(op, expected.type(), resultLocation.asGPR()); |
| return result; |
| } |
| |
| emitSanitizeAtomicResult(op, expected.type(), expectedLocation.asGPR(), scratchGPR); |
| |
| Jump failure; |
| switch (valueWidth) { |
| case Width8: |
| case Width16: |
| case Width32: |
| failure = m_jit.branch32(RelationalCondition::NotEqual, expectedLocation.asGPR(), scratchGPR); |
| break; |
| case Width64: |
| failure = m_jit.branch64(RelationalCondition::NotEqual, expectedLocation.asGPR(), scratchGPR); |
| break; |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| break; |
| } |
| |
| emitStrongCAS(expectedLocation.asGPR(), valueLocation.asGPR(), resultLocation.asGPR()); |
| auto done = m_jit.jump(); |
| |
| failure.link(&m_jit); |
| if (isARM64_LSE() || isX86_64()) { |
| m_jit.move(TrustedImm32(0), resultLocation.asGPR()); |
| switch (accessWidth) { |
| case Width8: |
| #if CPU(ARM64) |
| m_jit.atomicXchgAdd8(resultLocation.asGPR(), address, resultLocation.asGPR()); |
| #else |
| m_jit.atomicXchgAdd8(resultLocation.asGPR(), address); |
| #endif |
| break; |
| case Width16: |
| #if CPU(ARM64) |
| m_jit.atomicXchgAdd32(resultLocation.asGPR(), address, resultLocation.asGPR()); |
| #else |
| m_jit.atomicXchgAdd32(resultLocation.asGPR(), address); |
| #endif |
| break; |
| case Width32: |
| #if CPU(ARM64) |
| m_jit.atomicXchgAdd32(resultLocation.asGPR(), address, resultLocation.asGPR()); |
| #else |
| m_jit.atomicXchgAdd32(resultLocation.asGPR(), address); |
| #endif |
| break; |
| case Width64: |
| #if CPU(ARM64) |
| m_jit.atomicXchgAdd64(resultLocation.asGPR(), address, resultLocation.asGPR()); |
| #else |
| m_jit.atomicXchgAdd64(resultLocation.asGPR(), address); |
| #endif |
| break; |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| break; |
| } |
| } else { |
| emitAtomicOpGeneric(op, address, resultLocation.asGPR(), scratchGPR, [&](GPRReg oldGPR, GPRReg newGPR) { |
| emitSanitizeAtomicResult(op, canonicalWidth(accessWidth) == Width64 ? TypeKind::I64 : TypeKind::I32, oldGPR, newGPR); |
| }); |
| } |
| |
| done.link(&m_jit); |
| emitSanitizeAtomicResult(op, expected.type(), resultLocation.asGPR()); |
| |
| return result; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN atomicCompareExchange(ExtAtomicOpType op, Type valueType, ExpressionType pointer, ExpressionType expected, ExpressionType value, ExpressionType& result, uint32_t uoffset) |
| { |
| Location valueLocation = locationOf(value); |
| if (UNLIKELY(sumOverflows<uint32_t>(uoffset, sizeOfAtomicOpMemoryAccess(op)))) { |
| // FIXME: Even though this is provably out of bounds, it's not a validation error, so we have to handle it |
| // as a runtime exception. However, this may change: https://bugs.webkit.org/show_bug.cgi?id=166435 |
| emitThrowException(ExceptionType::OutOfBoundsMemoryAccess); |
| consume(pointer); |
| consume(expected); |
| consume(value); |
| result = valueType.isI64() ? Value::fromI64(0) : Value::fromI32(0); |
| } else |
| result = emitAtomicCompareExchange(op, valueType, emitCheckAndPreparePointer(pointer, uoffset, sizeOfAtomicOpMemoryAccess(op)), expected, value, uoffset); |
| |
| LOG_INSTRUCTION(makeString(op), pointer, expected, value, valueLocation, uoffset, RESULT(result)); |
| |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN atomicWait(ExtAtomicOpType op, ExpressionType pointer, ExpressionType value, ExpressionType timeout, ExpressionType& result, uint32_t uoffset) |
| { |
| Vector<Value, 8> arguments = { |
| instanceValue(), |
| pointer, |
| Value::fromI32(uoffset), |
| value, |
| timeout |
| }; |
| |
| if (op == ExtAtomicOpType::MemoryAtomicWait32) |
| emitCCall(&operationMemoryAtomicWait32, arguments, TypeKind::I32, result); |
| else |
| emitCCall(&operationMemoryAtomicWait64, arguments, TypeKind::I32, result); |
| Location resultLocation = allocate(result); |
| |
| LOG_INSTRUCTION(makeString(op), pointer, value, timeout, uoffset, RESULT(result)); |
| |
| throwExceptionIf(ExceptionType::OutOfBoundsMemoryAccess, m_jit.branch32(RelationalCondition::LessThan, resultLocation.asGPR(), TrustedImm32(0))); |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN atomicNotify(ExtAtomicOpType op, ExpressionType pointer, ExpressionType count, ExpressionType& result, uint32_t uoffset) |
| { |
| Vector<Value, 8> arguments = { |
| instanceValue(), |
| pointer, |
| Value::fromI32(uoffset), |
| count |
| }; |
| emitCCall(&operationMemoryAtomicNotify, arguments, TypeKind::I32, result); |
| Location resultLocation = allocate(result); |
| |
| LOG_INSTRUCTION(makeString(op), pointer, count, uoffset, RESULT(result)); |
| |
| throwExceptionIf(ExceptionType::OutOfBoundsMemoryAccess, m_jit.branch32(RelationalCondition::LessThan, resultLocation.asGPR(), TrustedImm32(0))); |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN atomicFence(ExtAtomicOpType, uint8_t) |
| { |
| m_jit.memoryFence(); |
| return { }; |
| } |
| |
| // Saturated truncation. |
| |
| struct FloatingPointRange { |
| Value min, max; |
| bool closedLowerEndpoint; |
| }; |
| |
| enum class TruncationKind { |
| I32TruncF32S, |
| I32TruncF32U, |
| I64TruncF32S, |
| I64TruncF32U, |
| I32TruncF64S, |
| I32TruncF64U, |
| I64TruncF64S, |
| I64TruncF64U |
| }; |
| |
| TruncationKind truncationKind(OpType truncationOp) |
| { |
| switch (truncationOp) { |
| case OpType::I32TruncSF32: |
| return TruncationKind::I32TruncF32S; |
| case OpType::I32TruncUF32: |
| return TruncationKind::I32TruncF32U; |
| case OpType::I64TruncSF32: |
| return TruncationKind::I64TruncF32S; |
| case OpType::I64TruncUF32: |
| return TruncationKind::I64TruncF32U; |
| case OpType::I32TruncSF64: |
| return TruncationKind::I32TruncF64S; |
| case OpType::I32TruncUF64: |
| return TruncationKind::I32TruncF64U; |
| case OpType::I64TruncSF64: |
| return TruncationKind::I64TruncF64S; |
| case OpType::I64TruncUF64: |
| return TruncationKind::I64TruncF64U; |
| default: |
| RELEASE_ASSERT_NOT_REACHED_WITH_MESSAGE("Not a truncation op"); |
| } |
| } |
| |
| TruncationKind truncationKind(Ext1OpType truncationOp) |
| { |
| switch (truncationOp) { |
| case Ext1OpType::I32TruncSatF32S: |
| return TruncationKind::I32TruncF32S; |
| case Ext1OpType::I32TruncSatF32U: |
| return TruncationKind::I32TruncF32U; |
| case Ext1OpType::I64TruncSatF32S: |
| return TruncationKind::I64TruncF32S; |
| case Ext1OpType::I64TruncSatF32U: |
| return TruncationKind::I64TruncF32U; |
| case Ext1OpType::I32TruncSatF64S: |
| return TruncationKind::I32TruncF64S; |
| case Ext1OpType::I32TruncSatF64U: |
| return TruncationKind::I32TruncF64U; |
| case Ext1OpType::I64TruncSatF64S: |
| return TruncationKind::I64TruncF64S; |
| case Ext1OpType::I64TruncSatF64U: |
| return TruncationKind::I64TruncF64U; |
| default: |
| RELEASE_ASSERT_NOT_REACHED_WITH_MESSAGE("Not a truncation op"); |
| } |
| } |
| |
| FloatingPointRange lookupTruncationRange(TruncationKind truncationKind) |
| { |
| Value min; |
| Value max; |
| bool closedLowerEndpoint = false; |
| |
| switch (truncationKind) { |
| case TruncationKind::I32TruncF32S: |
| closedLowerEndpoint = true; |
| max = Value::fromF32(-static_cast<float>(std::numeric_limits<int32_t>::min())); |
| min = Value::fromF32(static_cast<float>(std::numeric_limits<int32_t>::min())); |
| break; |
| case TruncationKind::I32TruncF32U: |
| max = Value::fromF32(static_cast<float>(std::numeric_limits<int32_t>::min()) * static_cast<float>(-2.0)); |
| min = Value::fromF32(static_cast<float>(-1.0)); |
| break; |
| case TruncationKind::I32TruncF64S: |
| max = Value::fromF64(-static_cast<double>(std::numeric_limits<int32_t>::min())); |
| min = Value::fromF64(static_cast<double>(std::numeric_limits<int32_t>::min()) - 1.0); |
| break; |
| case TruncationKind::I32TruncF64U: |
| max = Value::fromF64(static_cast<double>(std::numeric_limits<int32_t>::min()) * -2.0); |
| min = Value::fromF64(-1.0); |
| break; |
| case TruncationKind::I64TruncF32S: |
| closedLowerEndpoint = true; |
| max = Value::fromF32(-static_cast<float>(std::numeric_limits<int64_t>::min())); |
| min = Value::fromF32(static_cast<float>(std::numeric_limits<int64_t>::min())); |
| break; |
| case TruncationKind::I64TruncF32U: |
| max = Value::fromF32(static_cast<float>(std::numeric_limits<int64_t>::min()) * static_cast<float>(-2.0)); |
| min = Value::fromF32(static_cast<float>(-1.0)); |
| break; |
| case TruncationKind::I64TruncF64S: |
| closedLowerEndpoint = true; |
| max = Value::fromF64(-static_cast<double>(std::numeric_limits<int64_t>::min())); |
| min = Value::fromF64(static_cast<double>(std::numeric_limits<int64_t>::min())); |
| break; |
| case TruncationKind::I64TruncF64U: |
| max = Value::fromF64(static_cast<double>(std::numeric_limits<int64_t>::min()) * -2.0); |
| min = Value::fromF64(-1.0); |
| break; |
| } |
| |
| return FloatingPointRange { min, max, closedLowerEndpoint }; |
| } |
| |
| void truncInBounds(TruncationKind truncationKind, Location operandLocation, Location resultLocation, FPRReg scratch1FPR, FPRReg scratch2FPR) |
| { |
| switch (truncationKind) { |
| case TruncationKind::I32TruncF32S: |
| m_jit.truncateFloatToInt32(operandLocation.asFPR(), resultLocation.asGPR()); |
| break; |
| case TruncationKind::I32TruncF64S: |
| m_jit.truncateDoubleToInt32(operandLocation.asFPR(), resultLocation.asGPR()); |
| break; |
| case TruncationKind::I32TruncF32U: |
| m_jit.truncateFloatToUint32(operandLocation.asFPR(), resultLocation.asGPR()); |
| break; |
| case TruncationKind::I32TruncF64U: |
| m_jit.truncateDoubleToUint32(operandLocation.asFPR(), resultLocation.asGPR()); |
| break; |
| case TruncationKind::I64TruncF32S: |
| m_jit.truncateFloatToInt64(operandLocation.asFPR(), resultLocation.asGPR()); |
| break; |
| case TruncationKind::I64TruncF64S: |
| m_jit.truncateDoubleToInt64(operandLocation.asFPR(), resultLocation.asGPR()); |
| break; |
| case TruncationKind::I64TruncF32U: { |
| if constexpr (isX86()) |
| emitMoveConst(Value::fromF32(static_cast<float>(std::numeric_limits<uint64_t>::max() - std::numeric_limits<int64_t>::max())), Location::fromFPR(scratch2FPR)); |
| m_jit.truncateFloatToUint64(operandLocation.asFPR(), resultLocation.asGPR(), scratch1FPR, scratch2FPR); |
| break; |
| } |
| case TruncationKind::I64TruncF64U: { |
| if constexpr (isX86()) |
| emitMoveConst(Value::fromF64(static_cast<double>(std::numeric_limits<uint64_t>::max() - std::numeric_limits<int64_t>::max())), Location::fromFPR(scratch2FPR)); |
| m_jit.truncateDoubleToUint64(operandLocation.asFPR(), resultLocation.asGPR(), scratch1FPR, scratch2FPR); |
| break; |
| } |
| } |
| } |
| |
| PartialResult WARN_UNUSED_RETURN truncTrapping(OpType truncationOp, Value operand, Value& result, Type returnType, Type operandType) |
| { |
| ScratchScope<0, 2> scratches(*this); |
| |
| Location operandLocation; |
| if (operand.isConst()) { |
| operandLocation = Location::fromFPR(wasmScratchFPR); |
| emitMoveConst(operand, operandLocation); |
| } else |
| operandLocation = loadIfNecessary(operand); |
| ASSERT(operandLocation.isRegister()); |
| |
| consume(operand); // Allow temp operand location to be reused |
| |
| result = topValue(returnType.kind); |
| Location resultLocation = allocate(result); |
| TruncationKind kind = truncationKind(truncationOp); |
| auto range = lookupTruncationRange(kind); |
| auto minFloatConst = range.min; |
| auto maxFloatConst = range.max; |
| Location minFloat = Location::fromFPR(scratches.fpr(0)); |
| Location maxFloat = Location::fromFPR(scratches.fpr(1)); |
| |
| // FIXME: Can we do better isel here? Two floating-point constant materializations for every |
| // trunc seems costly. |
| emitMoveConst(minFloatConst, minFloat); |
| emitMoveConst(maxFloatConst, maxFloat); |
| |
| LOG_INSTRUCTION("TruncSaturated", operand, operandLocation, RESULT(result)); |
| |
| DoubleCondition minCondition = range.closedLowerEndpoint ? DoubleCondition::DoubleLessThanOrUnordered : DoubleCondition::DoubleLessThanOrEqualOrUnordered; |
| Jump belowMin = operandType == Types::F32 |
| ? m_jit.branchFloat(minCondition, operandLocation.asFPR(), minFloat.asFPR()) |
| : m_jit.branchDouble(minCondition, operandLocation.asFPR(), minFloat.asFPR()); |
| throwExceptionIf(ExceptionType::OutOfBoundsTrunc, belowMin); |
| |
| Jump aboveMax = operandType == Types::F32 |
| ? m_jit.branchFloat(DoubleCondition::DoubleGreaterThanOrEqualOrUnordered, operandLocation.asFPR(), maxFloat.asFPR()) |
| : m_jit.branchDouble(DoubleCondition::DoubleGreaterThanOrEqualOrUnordered, operandLocation.asFPR(), maxFloat.asFPR()); |
| throwExceptionIf(ExceptionType::OutOfBoundsTrunc, aboveMax); |
| |
| truncInBounds(kind, operandLocation, resultLocation, scratches.fpr(0), scratches.fpr(1)); |
| |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN truncSaturated(Ext1OpType truncationOp, Value operand, Value& result, Type returnType, Type operandType) |
| { |
| ScratchScope<0, 2> scratches(*this); |
| |
| TruncationKind kind = truncationKind(truncationOp); |
| auto range = lookupTruncationRange(kind); |
| auto minFloatConst = range.min; |
| auto maxFloatConst = range.max; |
| Location minFloat = Location::fromFPR(scratches.fpr(0)); |
| Location maxFloat = Location::fromFPR(scratches.fpr(1)); |
| |
| // FIXME: Can we do better isel here? Two floating-point constant materializations for every |
| // trunc seems costly. |
| emitMoveConst(minFloatConst, minFloat); |
| emitMoveConst(maxFloatConst, maxFloat); |
| |
| // FIXME: Lots of this is duplicated from AirIRGeneratorBase. Might be nice to unify it? |
| uint64_t minResult = 0; |
| uint64_t maxResult = 0; |
| switch (kind) { |
| case TruncationKind::I32TruncF32S: |
| case TruncationKind::I32TruncF64S: |
| maxResult = bitwise_cast<uint32_t>(INT32_MAX); |
| minResult = bitwise_cast<uint32_t>(INT32_MIN); |
| break; |
| case TruncationKind::I32TruncF32U: |
| case TruncationKind::I32TruncF64U: |
| maxResult = bitwise_cast<uint32_t>(UINT32_MAX); |
| minResult = bitwise_cast<uint32_t>(0U); |
| break; |
| case TruncationKind::I64TruncF32S: |
| case TruncationKind::I64TruncF64S: |
| maxResult = bitwise_cast<uint64_t>(INT64_MAX); |
| minResult = bitwise_cast<uint64_t>(INT64_MIN); |
| break; |
| case TruncationKind::I64TruncF32U: |
| case TruncationKind::I64TruncF64U: |
| maxResult = bitwise_cast<uint64_t>(UINT64_MAX); |
| minResult = bitwise_cast<uint64_t>(0ULL); |
| break; |
| } |
| |
| Location operandLocation; |
| if (operand.isConst()) { |
| operandLocation = Location::fromFPR(wasmScratchFPR); |
| emitMoveConst(operand, operandLocation); |
| } else |
| operandLocation = loadIfNecessary(operand); |
| ASSERT(operandLocation.isRegister()); |
| |
| consume(operand); // Allow temp operand location to be reused |
| |
| result = topValue(returnType.kind); |
| Location resultLocation = allocate(result); |
| |
| LOG_INSTRUCTION("TruncSaturated", operand, operandLocation, RESULT(result)); |
| |
| Jump lowerThanMin = operandType == Types::F32 |
| ? m_jit.branchFloat(DoubleCondition::DoubleLessThanOrEqualOrUnordered, operandLocation.asFPR(), minFloat.asFPR()) |
| : m_jit.branchDouble(DoubleCondition::DoubleLessThanOrEqualOrUnordered, operandLocation.asFPR(), minFloat.asFPR()); |
| Jump higherThanMax = operandType == Types::F32 |
| ? m_jit.branchFloat(DoubleCondition::DoubleGreaterThanOrEqualOrUnordered, operandLocation.asFPR(), maxFloat.asFPR()) |
| : m_jit.branchDouble(DoubleCondition::DoubleGreaterThanOrEqualOrUnordered, operandLocation.asFPR(), maxFloat.asFPR()); |
| |
| // In-bounds case. Emit normal truncation instructions. |
| truncInBounds(kind, operandLocation, resultLocation, scratches.fpr(0), scratches.fpr(1)); |
| |
| Jump afterInBounds = m_jit.jump(); |
| |
| // Below-minimum case. |
| lowerThanMin.link(&m_jit); |
| |
| // As an optimization, if the min result is 0; we can unconditionally return |
| // that if the above-minimum-range check fails; otherwise, we need to check |
| // for NaN since it also will fail the above-minimum-range-check |
| if (!minResult) { |
| if (returnType == Types::I32) |
| m_jit.move(TrustedImm32(0), resultLocation.asGPR()); |
| else |
| m_jit.move(TrustedImm64(0), resultLocation.asGPR()); |
| } else { |
| Jump isNotNaN = operandType == Types::F32 |
| ? m_jit.branchFloat(DoubleCondition::DoubleEqualAndOrdered, operandLocation.asFPR(), operandLocation.asFPR()) |
| : m_jit.branchDouble(DoubleCondition::DoubleEqualAndOrdered, operandLocation.asFPR(), operandLocation.asFPR()); |
| |
| // NaN case. Set result to zero. |
| if (returnType == Types::I32) |
| m_jit.move(TrustedImm32(0), resultLocation.asGPR()); |
| else |
| m_jit.move(TrustedImm64(0), resultLocation.asGPR()); |
| Jump afterNaN = m_jit.jump(); |
| |
| // Non-NaN case. Set result to the minimum value. |
| isNotNaN.link(&m_jit); |
| emitMoveConst(returnType == Types::I32 ? Value::fromI32(static_cast<int32_t>(minResult)) : Value::fromI64(static_cast<int64_t>(minResult)), resultLocation); |
| afterNaN.link(&m_jit); |
| } |
| Jump afterMin = m_jit.jump(); |
| |
| // Above maximum case. |
| higherThanMax.link(&m_jit); |
| emitMoveConst(returnType == Types::I32 ? Value::fromI32(static_cast<int32_t>(maxResult)) : Value::fromI64(static_cast<int64_t>(maxResult)), resultLocation); |
| |
| afterInBounds.link(&m_jit); |
| afterMin.link(&m_jit); |
| |
| return { }; |
| } |
| |
| // GC |
| PartialResult WARN_UNUSED_RETURN addI31New(ExpressionType, ExpressionType&) BBQ_STUB |
| PartialResult WARN_UNUSED_RETURN addI31GetS(ExpressionType, ExpressionType&) BBQ_STUB |
| PartialResult WARN_UNUSED_RETURN addI31GetU(ExpressionType, ExpressionType&) BBQ_STUB |
| PartialResult WARN_UNUSED_RETURN addArrayNew(uint32_t, ExpressionType, ExpressionType, ExpressionType&) BBQ_STUB |
| PartialResult WARN_UNUSED_RETURN addArrayNewDefault(uint32_t, ExpressionType, ExpressionType&) BBQ_STUB |
| PartialResult WARN_UNUSED_RETURN addArrayNewData(uint32_t, uint32_t, ExpressionType, ExpressionType, ExpressionType&) BBQ_STUB |
| PartialResult WARN_UNUSED_RETURN addArrayNewElem(uint32_t, uint32_t, ExpressionType, ExpressionType, ExpressionType&) BBQ_STUB |
| PartialResult WARN_UNUSED_RETURN addArrayNewFixed(uint32_t, Vector<ExpressionType>&, ExpressionType&) BBQ_STUB |
| PartialResult WARN_UNUSED_RETURN addArrayGet(ExtGCOpType, uint32_t, ExpressionType, ExpressionType, ExpressionType&) BBQ_STUB |
| PartialResult WARN_UNUSED_RETURN addArraySet(uint32_t, ExpressionType, ExpressionType, ExpressionType) BBQ_STUB |
| PartialResult WARN_UNUSED_RETURN addArrayLen(ExpressionType, ExpressionType&) BBQ_STUB |
| PartialResult WARN_UNUSED_RETURN addStructNewDefault(uint32_t, ExpressionType&) BBQ_STUB |
| PartialResult WARN_UNUSED_RETURN addRefCast(ExpressionType, bool, int32_t, ExpressionType&) BBQ_STUB |
| PartialResult WARN_UNUSED_RETURN addRefTest(ExpressionType, bool, int32_t, ExpressionType&) BBQ_STUB |
| |
| PartialResult WARN_UNUSED_RETURN addExternInternalize(ExpressionType reference, ExpressionType& result) |
| { |
| Vector<Value, 8> arguments = { |
| reference |
| }; |
| emitCCall(&operationWasmExternInternalize, arguments, TypeKind::Anyref, result); |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addExternExternalize(ExpressionType reference, ExpressionType& result) |
| { |
| result = reference; |
| return { }; |
| } |
| |
| // Basic operators |
| PartialResult WARN_UNUSED_RETURN addSelect(Value condition, Value lhs, Value rhs, Value& result) |
| { |
| if (condition.isConst()) { |
| Value src = condition.asI32() ? lhs : rhs; |
| Location srcLocation; |
| if (src.isConst()) |
| result = src; |
| else { |
| result = topValue(lhs.type()); |
| srcLocation = loadIfNecessary(src); |
| } |
| |
| LOG_INSTRUCTION("Select", condition, lhs, rhs, RESULT(result)); |
| consume(condition); |
| consume(lhs); |
| consume(rhs); |
| if (!result.isConst()) { |
| Location resultLocation = allocate(result); |
| emitMove(lhs.type(), srcLocation, resultLocation); |
| } |
| } else { |
| Location conditionLocation = loadIfNecessary(condition); |
| Location lhsLocation = Location::none(), rhsLocation = Location::none(); |
| |
| // Ensure all non-constant parameters are loaded into registers. |
| if (!lhs.isConst()) |
| lhsLocation = loadIfNecessary(lhs); |
| if (!rhs.isConst()) |
| rhsLocation = loadIfNecessary(rhs); |
| |
| ASSERT(lhs.isConst() || lhsLocation.isRegister()); |
| ASSERT(rhs.isConst() || rhsLocation.isRegister()); |
| consume(lhs); |
| consume(rhs); |
| |
| result = topValue(lhs.type()); |
| Location resultLocation = allocate(result); |
| LOG_INSTRUCTION("Select", condition, lhs, lhsLocation, rhs, rhsLocation, RESULT(result)); |
| LOG_INDENT(); |
| |
| bool inverted = false; |
| |
| // If the operands or the result alias, we want the matching one to be on top. |
| if (rhsLocation == resultLocation) { |
| std::swap(lhs, rhs); |
| std::swap(lhsLocation, rhsLocation); |
| inverted = true; |
| } |
| |
| // If the condition location and the result alias, we want to make sure the condition is |
| // preserved no matter what. |
| if (conditionLocation == resultLocation) { |
| m_jit.move(conditionLocation.asGPR(), wasmScratchGPR); |
| conditionLocation = Location::fromGPR(wasmScratchGPR); |
| } |
| |
| // Kind of gross isel, but it should handle all use/def aliasing cases correctly. |
| if (lhs.isConst()) |
| emitMoveConst(lhs, resultLocation); |
| else |
| emitMove(lhs.type(), lhsLocation, resultLocation); |
| Jump ifZero = m_jit.branchTest32(inverted ? ResultCondition::Zero : ResultCondition::NonZero, conditionLocation.asGPR(), conditionLocation.asGPR()); |
| consume(condition); |
| if (rhs.isConst()) |
| emitMoveConst(rhs, resultLocation); |
| else |
| emitMove(rhs.type(), rhsLocation, resultLocation); |
| ifZero.link(&m_jit); |
| |
| LOG_DEDENT(); |
| } |
| return { }; |
| } |
| |
| template<typename Fold, typename RegReg, typename RegImm> |
| inline PartialResult binary(const char* opcode, TypeKind resultType, Value& lhs, Value& rhs, Value& result, Fold fold, RegReg regReg, RegImm regImm) |
| { |
| if (lhs.isConst() && rhs.isConst()) { |
| result = fold(lhs, rhs); |
| LOG_INSTRUCTION(opcode, lhs, rhs, RESULT(result)); |
| return { }; |
| } |
| |
| Location lhsLocation = Location::none(), rhsLocation = Location::none(); |
| |
| // Ensure all non-constant parameters are loaded into registers. |
| if (!lhs.isConst()) |
| lhsLocation = loadIfNecessary(lhs); |
| if (!rhs.isConst()) |
| rhsLocation = loadIfNecessary(rhs); |
| |
| ASSERT(lhs.isConst() || lhsLocation.isRegister()); |
| ASSERT(rhs.isConst() || rhsLocation.isRegister()); |
| |
| consume(lhs); // If either of our operands are temps, consume them and liberate any bound |
| consume(rhs); // registers. This lets us reuse one of the registers for the output. |
| |
| Location toReuse = lhs.isConst() ? rhsLocation : lhsLocation; // Select the location to reuse, preferring lhs. |
| |
| result = topValue(resultType); // Result will be the new top of the stack. |
| Location resultLocation = allocateWithHint(result, toReuse); |
| ASSERT(resultLocation.isRegister()); |
| |
| LOG_INSTRUCTION(opcode, lhs, lhsLocation, rhs, rhsLocation, RESULT(result)); |
| |
| if (lhs.isConst() || rhs.isConst()) |
| regImm(lhs, lhsLocation, rhs, rhsLocation, resultLocation); |
| else |
| regReg(lhs, lhsLocation, rhs, rhsLocation, resultLocation); |
| |
| return { }; |
| } |
| |
| template<typename Fold, typename Reg> |
| inline PartialResult unary(const char* opcode, TypeKind resultType, Value& operand, Value& result, Fold fold, Reg reg) |
| { |
| if (operand.isConst()) { |
| result = fold(operand); |
| LOG_INSTRUCTION(opcode, operand, RESULT(result)); |
| return { }; |
| } |
| |
| Location operandLocation = loadIfNecessary(operand); |
| ASSERT(operandLocation.isRegister()); |
| |
| consume(operand); // If our operand is a temp, consume it and liberate its register if it has one. |
| |
| result = topValue(resultType); // Result will be the new top of the stack. |
| Location resultLocation = allocateWithHint(result, operandLocation); // Try to reuse the operand location. |
| ASSERT(resultLocation.isRegister()); |
| |
| LOG_INSTRUCTION(opcode, operand, operandLocation, RESULT(result)); |
| |
| reg(operand, operandLocation, resultLocation); |
| return { }; |
| } |
| |
| struct ImmHelpers { |
| ALWAYS_INLINE static Value& imm(Value& lhs, Value& rhs) |
| { |
| return lhs.isConst() ? lhs : rhs; |
| } |
| |
| ALWAYS_INLINE static Location& immLocation(Location& lhsLocation, Location& rhsLocation) |
| { |
| return lhsLocation.isRegister() ? rhsLocation : lhsLocation; |
| } |
| |
| ALWAYS_INLINE static Value& reg(Value& lhs, Value& rhs) |
| { |
| return lhs.isConst() ? rhs : lhs; |
| } |
| |
| ALWAYS_INLINE static Location& regLocation(Location& lhsLocation, Location& rhsLocation) |
| { |
| return lhsLocation.isRegister() ? lhsLocation : rhsLocation; |
| } |
| }; |
| |
| #define BLOCK(...) __VA_ARGS__ |
| |
| #define EMIT_BINARY(opcode, resultType, foldExpr, regRegStatement, regImmStatement) \ |
| return binary(opcode, resultType, lhs, rhs, result, \ |
| [&](Value& lhs, Value& rhs) -> Value { \ |
| UNUSED_PARAM(lhs); \ |
| UNUSED_PARAM(rhs); \ |
| return foldExpr; /* Lambda to be called for constant folding, i.e. when both operands are constants. */ \ |
| }, \ |
| [&](Value& lhs, Location lhsLocation, Value& rhs, Location rhsLocation, Location resultLocation) -> void { \ |
| UNUSED_PARAM(lhs); \ |
| UNUSED_PARAM(rhs); \ |
| UNUSED_PARAM(lhsLocation); \ |
| UNUSED_PARAM(rhsLocation); \ |
| UNUSED_PARAM(resultLocation); \ |
| regRegStatement /* Lambda to be called when both operands are registers. */ \ |
| }, \ |
| [&](Value& lhs, Location lhsLocation, Value& rhs, Location rhsLocation, Location resultLocation) -> void { \ |
| UNUSED_PARAM(lhs); \ |
| UNUSED_PARAM(rhs); \ |
| UNUSED_PARAM(lhsLocation); \ |
| UNUSED_PARAM(rhsLocation); \ |
| UNUSED_PARAM(resultLocation); \ |
| regImmStatement /* Lambda to be when one operand is a register and the other is a constant. */ \ |
| }); |
| |
| #define EMIT_UNARY(opcode, resultType, foldExpr, regStatement) \ |
| return unary(opcode, resultType, operand, result, \ |
| [&](Value& operand) -> Value { \ |
| UNUSED_PARAM(operand); \ |
| return foldExpr; /* Lambda to be called for constant folding, i.e. when both operands are constants. */ \ |
| }, \ |
| [&](Value& operand, Location operandLocation, Location resultLocation) -> void { \ |
| UNUSED_PARAM(operand); \ |
| UNUSED_PARAM(operandLocation); \ |
| UNUSED_PARAM(resultLocation); \ |
| regStatement /* Lambda to be called when both operands are registers. */ \ |
| }); |
| |
| PartialResult WARN_UNUSED_RETURN addI32Add(Value lhs, Value rhs, Value& result) |
| { |
| EMIT_BINARY( |
| "I32Add", TypeKind::I32, |
| BLOCK(Value::fromI32(lhs.asI32() + rhs.asI32())), |
| BLOCK( |
| m_jit.add32(lhsLocation.asGPR(), rhsLocation.asGPR(), resultLocation.asGPR()); |
| ), |
| BLOCK( |
| m_jit.move(ImmHelpers::regLocation(lhsLocation, rhsLocation).asGPR(), resultLocation.asGPR()); |
| m_jit.add32(Imm32(ImmHelpers::imm(lhs, rhs).asI32()), resultLocation.asGPR()); |
| ) |
| ); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI64Add(Value lhs, Value rhs, Value& result) |
| { |
| EMIT_BINARY( |
| "I64Add", TypeKind::I64, |
| BLOCK(Value::fromI64(lhs.asI64() + rhs.asI64())), |
| BLOCK( |
| m_jit.add64(lhsLocation.asGPR(), rhsLocation.asGPR(), resultLocation.asGPR()); |
| ), |
| BLOCK( |
| m_jit.move(ImmHelpers::regLocation(lhsLocation, rhsLocation).asGPR(), resultLocation.asGPR()); |
| m_jit.add64(TrustedImm64(ImmHelpers::imm(lhs, rhs).asI64()), resultLocation.asGPR()); |
| ) |
| ); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF32Add(Value lhs, Value rhs, Value& result) |
| { |
| EMIT_BINARY( |
| "F32Add", TypeKind::F32, |
| BLOCK(Value::fromF32(lhs.asF32() + rhs.asF32())), |
| BLOCK( |
| m_jit.addFloat(lhsLocation.asFPR(), rhsLocation.asFPR(), resultLocation.asFPR()); |
| ), |
| BLOCK( |
| ImmHelpers::immLocation(lhsLocation, rhsLocation) = Location::fromFPR(wasmScratchFPR); |
| emitMoveConst(ImmHelpers::imm(lhs, rhs), Location::fromFPR(wasmScratchFPR)); |
| m_jit.addFloat(lhsLocation.asFPR(), rhsLocation.asFPR(), resultLocation.asFPR()); |
| ) |
| ); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF64Add(Value lhs, Value rhs, Value& result) |
| { |
| EMIT_BINARY( |
| "F64Add", TypeKind::F64, |
| BLOCK(Value::fromF64(lhs.asF64() + rhs.asF64())), |
| BLOCK( |
| m_jit.addDouble(lhsLocation.asFPR(), rhsLocation.asFPR(), resultLocation.asFPR()); |
| ), |
| BLOCK( |
| ImmHelpers::immLocation(lhsLocation, rhsLocation) = Location::fromFPR(wasmScratchFPR); |
| emitMoveConst(ImmHelpers::imm(lhs, rhs), Location::fromFPR(wasmScratchFPR)); |
| m_jit.addDouble(lhsLocation.asFPR(), rhsLocation.asFPR(), resultLocation.asFPR()); |
| ) |
| ); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI32Sub(Value lhs, Value rhs, Value& result) |
| { |
| EMIT_BINARY( |
| "I32Sub", TypeKind::I32, |
| BLOCK(Value::fromI32(lhs.asI32() - rhs.asI32())), |
| BLOCK( |
| m_jit.sub32(lhsLocation.asGPR(), rhsLocation.asGPR(), resultLocation.asGPR()); |
| ), |
| BLOCK( |
| if (rhs.isConst()) { |
| // Add a negative if rhs is a constant. |
| m_jit.move(lhsLocation.asGPR(), resultLocation.asGPR()); |
| m_jit.add32(Imm32(-rhs.asI32()), resultLocation.asGPR()); |
| } else { |
| emitMoveConst(lhs, Location::fromGPR(wasmScratchGPR)); |
| m_jit.sub32(wasmScratchGPR, rhsLocation.asGPR(), resultLocation.asGPR()); |
| } |
| ) |
| ); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI64Sub(Value lhs, Value rhs, Value& result) |
| { |
| EMIT_BINARY( |
| "I64Sub", TypeKind::I64, |
| BLOCK(Value::fromI64(lhs.asI64() - rhs.asI64())), |
| BLOCK( |
| m_jit.sub64(lhsLocation.asGPR(), rhsLocation.asGPR(), resultLocation.asGPR()); |
| ), |
| BLOCK( |
| if (rhs.isConst()) { |
| // Add a negative if rhs is a constant. |
| m_jit.move(lhsLocation.asGPR(), resultLocation.asGPR()); |
| m_jit.add64(TrustedImm64(-rhs.asI64()), resultLocation.asGPR()); |
| } else { |
| emitMoveConst(lhs, Location::fromGPR(wasmScratchGPR)); |
| m_jit.sub64(wasmScratchGPR, rhsLocation.asGPR(), resultLocation.asGPR()); |
| } |
| ) |
| ); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF32Sub(Value lhs, Value rhs, Value& result) |
| { |
| EMIT_BINARY( |
| "F32Sub", TypeKind::F32, |
| BLOCK(Value::fromF32(lhs.asF32() - rhs.asF32())), |
| BLOCK( |
| m_jit.subFloat(lhsLocation.asFPR(), rhsLocation.asFPR(), resultLocation.asFPR()); |
| ), |
| BLOCK( |
| if (rhs.isConst()) { |
| // Add a negative if rhs is a constant. |
| emitMoveConst(Value::fromF32(-rhs.asF32()), Location::fromFPR(wasmScratchFPR)); |
| m_jit.addFloat(lhsLocation.asFPR(), wasmScratchFPR, resultLocation.asFPR()); |
| } else { |
| emitMoveConst(lhs, Location::fromFPR(wasmScratchFPR)); |
| m_jit.subFloat(wasmScratchFPR, rhsLocation.asFPR(), resultLocation.asFPR()); |
| } |
| ) |
| ); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF64Sub(Value lhs, Value rhs, Value& result) |
| { |
| EMIT_BINARY( |
| "F64Sub", TypeKind::F64, |
| BLOCK(Value::fromF64(lhs.asF64() - rhs.asF64())), |
| BLOCK( |
| m_jit.subDouble(lhsLocation.asFPR(), rhsLocation.asFPR(), resultLocation.asFPR()); |
| ), |
| BLOCK( |
| if (rhs.isConst()) { |
| // Add a negative if rhs is a constant. |
| emitMoveConst(Value::fromF64(-rhs.asF64()), Location::fromFPR(wasmScratchFPR)); |
| m_jit.addDouble(lhsLocation.asFPR(), wasmScratchFPR, resultLocation.asFPR()); |
| } else { |
| emitMoveConst(lhs, Location::fromFPR(wasmScratchFPR)); |
| m_jit.subDouble(wasmScratchFPR, rhsLocation.asFPR(), resultLocation.asFPR()); |
| } |
| ) |
| ); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI32Mul(Value lhs, Value rhs, Value& result) |
| { |
| EMIT_BINARY( |
| "I32Mul", TypeKind::I32, |
| BLOCK(Value::fromI32(lhs.asI32() * rhs.asI32())), |
| BLOCK( |
| m_jit.mul32(lhsLocation.asGPR(), rhsLocation.asGPR(), resultLocation.asGPR()); |
| ), |
| BLOCK( |
| m_jit.mul32( |
| Imm32(ImmHelpers::imm(lhs, rhs).asI32()), |
| ImmHelpers::regLocation(lhsLocation, rhsLocation).asGPR(), |
| resultLocation.asGPR() |
| ); |
| ) |
| ); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI64Mul(Value lhs, Value rhs, Value& result) |
| { |
| EMIT_BINARY( |
| "I64Mul", TypeKind::I64, |
| BLOCK(Value::fromI64(lhs.asI64() * rhs.asI64())), |
| BLOCK( |
| m_jit.mul64(lhsLocation.asGPR(), rhsLocation.asGPR(), resultLocation.asGPR()); |
| ), |
| BLOCK( |
| ImmHelpers::immLocation(lhsLocation, rhsLocation) = Location::fromGPR(wasmScratchGPR); |
| emitMoveConst(ImmHelpers::imm(lhs, rhs), Location::fromGPR(wasmScratchGPR)); |
| m_jit.mul64(lhsLocation.asGPR(), rhsLocation.asGPR(), resultLocation.asGPR()); |
| ) |
| ); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF32Mul(Value lhs, Value rhs, Value& result) |
| { |
| EMIT_BINARY( |
| "F32Mul", TypeKind::F32, |
| BLOCK(Value::fromF32(lhs.asF32() * rhs.asF32())), |
| BLOCK( |
| m_jit.mulFloat(lhsLocation.asFPR(), rhsLocation.asFPR(), resultLocation.asFPR()); |
| ), |
| BLOCK( |
| ImmHelpers::immLocation(lhsLocation, rhsLocation) = Location::fromFPR(wasmScratchFPR); |
| emitMoveConst(ImmHelpers::imm(lhs, rhs), Location::fromFPR(wasmScratchFPR)); |
| m_jit.mulFloat(lhsLocation.asFPR(), rhsLocation.asFPR(), resultLocation.asFPR()); |
| ) |
| ); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF64Mul(Value lhs, Value rhs, Value& result) |
| { |
| EMIT_BINARY( |
| "F64Mul", TypeKind::F64, |
| BLOCK(Value::fromF64(lhs.asF64() * rhs.asF64())), |
| BLOCK( |
| m_jit.mulDouble(lhsLocation.asFPR(), rhsLocation.asFPR(), resultLocation.asFPR()); |
| ), |
| BLOCK( |
| ImmHelpers::immLocation(lhsLocation, rhsLocation) = Location::fromFPR(wasmScratchFPR); |
| emitMoveConst(ImmHelpers::imm(lhs, rhs), Location::fromFPR(wasmScratchFPR)); |
| m_jit.mulDouble(lhsLocation.asFPR(), rhsLocation.asFPR(), resultLocation.asFPR()); |
| ) |
| ); |
| } |
| |
| template<typename Func> |
| void addLatePath(Func func) |
| { |
| m_latePaths.append(createSharedTask<void(BBQJIT&, CCallHelpers&)>(WTFMove(func))); |
| } |
| |
| void emitThrowException(ExceptionType type) |
| { |
| m_jit.move(CCallHelpers::TrustedImm32(static_cast<uint32_t>(type)), GPRInfo::argumentGPR1); |
| auto jumpToExceptionStub = m_jit.jump(); |
| |
| m_jit.addLinkTask([jumpToExceptionStub] (LinkBuffer& linkBuffer) { |
| linkBuffer.link(jumpToExceptionStub, CodeLocationLabel<JITThunkPtrTag>(Thunks::singleton().stub(throwExceptionFromWasmThunkGenerator).code())); |
| }); |
| } |
| |
| void throwExceptionIf(ExceptionType type, Jump jump) |
| { |
| m_exceptions[static_cast<unsigned>(type)].append(jump); |
| } |
| |
| #if CPU(X86_64) |
| RegisterSet clobbersForDivX86() |
| { |
| static RegisterSet x86DivClobbers; |
| static std::once_flag flag; |
| std::call_once( |
| flag, |
| []() { |
| RegisterSetBuilder builder; |
| builder.add(X86Registers::eax, IgnoreVectors); |
| builder.add(X86Registers::edx, IgnoreVectors); |
| x86DivClobbers = builder.buildAndValidate(); |
| }); |
| return x86DivClobbers; |
| } |
| |
| #define PREPARE_FOR_MOD_OR_DIV \ |
| do { \ |
| for (JSC::Reg reg : clobbersForDivX86()) \ |
| clobber(reg); \ |
| } while (false); \ |
| ScratchScope<0, 0> scratches(*this, clobbersForDivX86()) |
| |
| template<typename IntType, bool IsMod> |
| void emitModOrDiv(const Value& lhs, Location lhsLocation, const Value& rhs, Location rhsLocation, Location resultLocation) |
| { |
| // FIXME: We currently don't do nearly as sophisticated instruction selection on Intel as we do on other platforms, |
| // but there's no good reason we can't. We should probably port over the isel in the future if it seems to yield |
| // dividends. |
| |
| constexpr bool isSigned = std::is_signed<IntType>(); |
| constexpr bool is32 = sizeof(IntType) == 4; |
| |
| ASSERT(lhsLocation.isRegister() || rhsLocation.isRegister()); |
| if (lhs.isConst()) |
| emitMoveConst(lhs, lhsLocation = Location::fromGPR(wasmScratchGPR)); |
| else if (rhs.isConst()) |
| emitMoveConst(rhs, rhsLocation = Location::fromGPR(wasmScratchGPR)); |
| ASSERT(lhsLocation.isRegister() && rhsLocation.isRegister()); |
| |
| ASSERT(resultLocation.isRegister()); |
| ASSERT(lhsLocation.asGPR() != X86Registers::eax && lhsLocation.asGPR() != X86Registers::edx); |
| ASSERT(rhsLocation.asGPR() != X86Registers::eax && lhsLocation.asGPR() != X86Registers::edx); |
| |
| ScratchScope<2, 0> scratches(*this, lhsLocation, rhsLocation, resultLocation); |
| |
| Jump toDiv, toEnd; |
| |
| Jump isZero = is32 |
| ? m_jit.branchTest32(ResultCondition::Zero, rhsLocation.asGPR()) |
| : m_jit.branchTest64(ResultCondition::Zero, rhsLocation.asGPR()); |
| throwExceptionIf(ExceptionType::DivisionByZero, isZero); |
| if constexpr (isSigned) { |
| if constexpr (is32) |
| m_jit.compare32(RelationalCondition::Equal, rhsLocation.asGPR(), TrustedImm32(-1), scratches.gpr(0)); |
| else |
| m_jit.compare64(RelationalCondition::Equal, rhsLocation.asGPR(), TrustedImm32(-1), scratches.gpr(0)); |
| if constexpr (is32) |
| m_jit.compare32(RelationalCondition::Equal, lhsLocation.asGPR(), TrustedImm32(std::numeric_limits<int32_t>::min()), scratches.gpr(1)); |
| else { |
| m_jit.move(TrustedImm64(std::numeric_limits<int64_t>::min()), scratches.gpr(1)); |
| m_jit.compare64(RelationalCondition::Equal, lhsLocation.asGPR(), scratches.gpr(1), scratches.gpr(1)); |
| } |
| m_jit.and64(scratches.gpr(0), scratches.gpr(1)); |
| if constexpr (IsMod) { |
| toDiv = m_jit.branchTest64(ResultCondition::Zero, scratches.gpr(1)); |
| // In this case, WASM doesn't want us to fault, but x86 will. So we set the result ourselves. |
| if constexpr (is32) |
| m_jit.xor32(resultLocation.asGPR(), resultLocation.asGPR()); |
| else |
| m_jit.xor64(resultLocation.asGPR(), resultLocation.asGPR()); |
| toEnd = m_jit.jump(); |
| } else { |
| Jump isNegativeOne = m_jit.branchTest64(ResultCondition::NonZero, scratches.gpr(1)); |
| throwExceptionIf(ExceptionType::IntegerOverflow, isNegativeOne); |
| } |
| } |
| |
| if (toDiv.isSet()) |
| toDiv.link(&m_jit); |
| |
| m_jit.move(lhsLocation.asGPR(), X86Registers::eax); |
| |
| if constexpr (is32 && isSigned) { |
| m_jit.x86ConvertToDoubleWord32(); |
| m_jit.x86Div32(rhsLocation.asGPR()); |
| } else if constexpr (is32) { |
| m_jit.xor32(X86Registers::edx, X86Registers::edx); |
| m_jit.x86UDiv32(rhsLocation.asGPR()); |
| } else if constexpr (isSigned) { |
| m_jit.x86ConvertToQuadWord64(); |
| m_jit.x86Div64(rhsLocation.asGPR()); |
| } else { |
| m_jit.xor64(X86Registers::edx, X86Registers::edx); |
| m_jit.x86UDiv64(rhsLocation.asGPR()); |
| } |
| |
| if constexpr (IsMod) |
| m_jit.move(X86Registers::edx, resultLocation.asGPR()); |
| else |
| m_jit.move(X86Registers::eax, resultLocation.asGPR()); |
| |
| if (toEnd.isSet()) |
| toEnd.link(&m_jit); |
| } |
| #else |
| #define PREPARE_FOR_MOD_OR_DIV |
| |
| template<typename IntType, bool IsMod> |
| void emitModOrDiv(const Value& lhs, Location lhsLocation, const Value& rhs, Location rhsLocation, Location resultLocation) |
| { |
| constexpr bool isSigned = std::is_signed<IntType>(); |
| constexpr bool is32 = sizeof(IntType) == 4; |
| |
| ASSERT(lhsLocation.isRegister() || rhsLocation.isRegister()); |
| ASSERT(resultLocation.isRegister()); |
| |
| bool checkedForZero = false, checkedForNegativeOne = false; |
| if (rhs.isConst()) { |
| int64_t divisor = is32 ? rhs.asI32() : rhs.asI64(); |
| if (!divisor) { |
| emitThrowException(ExceptionType::DivisionByZero); |
| return; |
| } |
| if (divisor == 1) { |
| if constexpr (IsMod) { |
| // N % 1 == 0 |
| if constexpr (is32) |
| m_jit.xor32(resultLocation.asGPR(), resultLocation.asGPR()); |
| else |
| m_jit.xor64(resultLocation.asGPR(), resultLocation.asGPR()); |
| } else |
| m_jit.move(lhsLocation.asGPR(), resultLocation.asGPR()); |
| return; |
| } |
| if (divisor == -1) { |
| // Check for INT_MIN / -1 case, and throw an IntegerOverflow exception if it occurs |
| if (!IsMod && isSigned) { |
| Jump jump = is32 |
| ? m_jit.branch32(RelationalCondition::Equal, lhsLocation.asGPR(), TrustedImm32(std::numeric_limits<int32_t>::min())) |
| : m_jit.branch64(RelationalCondition::Equal, lhsLocation.asGPR(), TrustedImm64(std::numeric_limits<int64_t>::min())); |
| throwExceptionIf(ExceptionType::IntegerOverflow, jump); |
| } |
| |
| if constexpr (IsMod) { |
| // N % 1 == 0 |
| if constexpr (is32) |
| m_jit.xor32(resultLocation.asGPR(), resultLocation.asGPR()); |
| else |
| m_jit.xor64(resultLocation.asGPR(), resultLocation.asGPR()); |
| return; |
| } |
| if constexpr (isSigned) { |
| if constexpr (is32) |
| m_jit.neg32(lhsLocation.asGPR(), resultLocation.asGPR()); |
| else |
| m_jit.neg64(lhsLocation.asGPR(), resultLocation.asGPR()); |
| return; |
| } |
| |
| // Fall through to general case. |
| } else if (isPowerOfTwo(divisor)) { |
| if constexpr (IsMod) { |
| Location originalResult = resultLocation; |
| if constexpr (isSigned) |
| resultLocation = Location::fromGPR(wasmScratchGPR); |
| |
| if constexpr (is32) |
| m_jit.and32(Imm32(static_cast<uint32_t>(divisor) - 1), lhsLocation.asGPR(), resultLocation.asGPR()); |
| else |
| m_jit.and64(TrustedImm64(static_cast<uint64_t>(divisor) - 1), lhsLocation.asGPR(), resultLocation.asGPR()); |
| |
| if constexpr (isSigned) { |
| Jump isNonNegative = is32 |
| ? m_jit.branch32(RelationalCondition::GreaterThanOrEqual, lhsLocation.asGPR(), TrustedImm32(0)) |
| : m_jit.branch64(RelationalCondition::GreaterThanOrEqual, lhsLocation.asGPR(), TrustedImm64(0)); |
| if constexpr (is32) |
| m_jit.neg32(wasmScratchGPR, wasmScratchGPR); |
| else |
| m_jit.neg64(wasmScratchGPR, wasmScratchGPR); |
| isNonNegative.link(&m_jit); |
| m_jit.move(wasmScratchGPR, originalResult.asGPR()); |
| } |
| return; |
| } |
| |
| if constexpr (isSigned) { |
| Jump isNonNegative = is32 |
| ? m_jit.branch32(RelationalCondition::GreaterThanOrEqual, lhsLocation.asGPR(), TrustedImm32(0)) |
| : m_jit.branch64(RelationalCondition::GreaterThanOrEqual, lhsLocation.asGPR(), TrustedImm64(0)); |
| if constexpr (is32) |
| m_jit.add32(Imm32(1), lhsLocation.asGPR(), lhsLocation.asGPR()); |
| else |
| m_jit.add64(TrustedImm32(1), lhsLocation.asGPR(), lhsLocation.asGPR()); |
| isNonNegative.link(&m_jit); |
| } |
| if constexpr (is32) |
| m_jit.rshift32(lhsLocation.asGPR(), m_jit.trustedImm32ForShift(Imm32(WTF::fastLog2(static_cast<unsigned>(divisor)))), resultLocation.asGPR()); |
| else |
| m_jit.rshift64(lhsLocation.asGPR(), TrustedImm32(WTF::fastLog2(static_cast<unsigned>(divisor))), resultLocation.asGPR()); |
| return; |
| } |
| // TODO: try generating integer reciprocal instead. |
| checkedForNegativeOne = true; |
| checkedForZero = true; |
| rhsLocation = Location::fromGPR(wasmScratchGPR); |
| emitMoveConst(rhs, rhsLocation); |
| // Fall through to register/register div. |
| } else if (lhs.isConst()) { |
| int64_t dividend = is32 ? lhs.asI32() : lhs.asI64(); |
| |
| Jump isZero = is32 |
| ? m_jit.branchTest32(ResultCondition::Zero, rhsLocation.asGPR()) |
| : m_jit.branchTest64(ResultCondition::Zero, rhsLocation.asGPR()); |
| throwExceptionIf(ExceptionType::DivisionByZero, isZero); |
| checkedForZero = true; |
| |
| if (!dividend) { |
| if constexpr (is32) |
| m_jit.xor32(resultLocation.asGPR(), resultLocation.asGPR()); |
| else |
| m_jit.xor64(resultLocation.asGPR(), resultLocation.asGPR()); |
| return; |
| } |
| if (isSigned && !IsMod && dividend == std::numeric_limits<IntType>::min()) { |
| Jump isNegativeOne = is32 |
| ? m_jit.branch32(RelationalCondition::Equal, rhsLocation.asGPR(), TrustedImm32(-1)) |
| : m_jit.branch64(RelationalCondition::Equal, rhsLocation.asGPR(), TrustedImm64(-1)); |
| throwExceptionIf(ExceptionType::IntegerOverflow, isNegativeOne); |
| } |
| checkedForNegativeOne = true; |
| |
| lhsLocation = Location::fromGPR(wasmScratchGPR); |
| emitMoveConst(lhs, lhsLocation); |
| // Fall through to register/register div. |
| } |
| |
| if (!checkedForZero) { |
| Jump isZero = is32 |
| ? m_jit.branchTest32(ResultCondition::Zero, rhsLocation.asGPR()) |
| : m_jit.branchTest64(ResultCondition::Zero, rhsLocation.asGPR()); |
| throwExceptionIf(ExceptionType::DivisionByZero, isZero); |
| } |
| |
| ScratchScope<1, 0> scratches(*this, lhsLocation, rhsLocation, resultLocation); |
| if (isSigned && !IsMod && !checkedForNegativeOne) { |
| // The following code freely clobbers wasmScratchGPR. This would be a bug if either of our operands were |
| // stored in wasmScratchGPR, which is the case if one of our operands is a constant - but in that case, |
| // we should be able to rule out this check based on the value of that constant above. |
| ASSERT(!lhs.isConst()); |
| ASSERT(!rhs.isConst()); |
| ASSERT(lhsLocation.asGPR() != wasmScratchGPR); |
| ASSERT(rhsLocation.asGPR() != wasmScratchGPR); |
| |
| if constexpr (is32) |
| m_jit.compare32(RelationalCondition::Equal, rhsLocation.asGPR(), TrustedImm32(-1), wasmScratchGPR); |
| else |
| m_jit.compare64(RelationalCondition::Equal, rhsLocation.asGPR(), TrustedImm32(-1), wasmScratchGPR); |
| if constexpr (is32) |
| m_jit.compare32(RelationalCondition::Equal, lhsLocation.asGPR(), TrustedImm32(std::numeric_limits<int32_t>::min()), scratches.gpr(0)); |
| else { |
| m_jit.move(TrustedImm64(std::numeric_limits<int64_t>::min()), scratches.gpr(0)); |
| m_jit.compare64(RelationalCondition::Equal, lhsLocation.asGPR(), scratches.gpr(0), scratches.gpr(0)); |
| } |
| m_jit.and64(wasmScratchGPR, scratches.gpr(0), wasmScratchGPR); |
| Jump isNegativeOne = m_jit.branchTest64(ResultCondition::NonZero, wasmScratchGPR); |
| throwExceptionIf(ExceptionType::IntegerOverflow, isNegativeOne); |
| } |
| |
| GPRReg divResult = IsMod ? scratches.gpr(0) : resultLocation.asGPR(); |
| if (is32 && isSigned) |
| m_jit.div32(lhsLocation.asGPR(), rhsLocation.asGPR(), divResult); |
| else if (is32) |
| m_jit.uDiv32(lhsLocation.asGPR(), rhsLocation.asGPR(), divResult); |
| else if (isSigned) |
| m_jit.div64(lhsLocation.asGPR(), rhsLocation.asGPR(), divResult); |
| else |
| m_jit.uDiv64(lhsLocation.asGPR(), rhsLocation.asGPR(), divResult); |
| |
| if (IsMod) { |
| if (is32) |
| m_jit.multiplySub32(divResult, rhsLocation.asGPR(), lhsLocation.asGPR(), resultLocation.asGPR()); |
| else |
| m_jit.multiplySub64(divResult, rhsLocation.asGPR(), lhsLocation.asGPR(), resultLocation.asGPR()); |
| } |
| } |
| #endif |
| |
| template<typename IntType> |
| Value checkConstantDivision(const Value& lhs, const Value& rhs) |
| { |
| constexpr bool is32 = sizeof(IntType) == 4; |
| if (!(is32 ? int64_t(rhs.asI32()) : rhs.asI64())) { |
| emitThrowException(ExceptionType::DivisionByZero); |
| return is32 ? Value::fromI32(1) : Value::fromI64(1); |
| } |
| if ((is32 ? int64_t(rhs.asI32()) : rhs.asI64()) == -1 |
| && (is32 ? int64_t(lhs.asI32()) : lhs.asI64()) == std::numeric_limits<IntType>::min() |
| && std::is_signed<IntType>()) { |
| emitThrowException(ExceptionType::IntegerOverflow); |
| return is32 ? Value::fromI32(1) : Value::fromI64(1); |
| } |
| return rhs; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI32DivS(Value lhs, Value rhs, Value& result) |
| { |
| PREPARE_FOR_MOD_OR_DIV; |
| EMIT_BINARY( |
| "I32DivS", TypeKind::I32, |
| BLOCK( |
| Value::fromI32(lhs.asI32() / checkConstantDivision<int32_t>(lhs, rhs).asI32()) |
| ), |
| BLOCK( |
| emitModOrDiv<int32_t, false>(lhs, lhsLocation, rhs, rhsLocation, resultLocation); |
| ), |
| BLOCK( |
| emitModOrDiv<int32_t, false>(lhs, lhsLocation, rhs, rhsLocation, resultLocation); |
| ) |
| ); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI64DivS(Value lhs, Value rhs, Value& result) |
| { |
| PREPARE_FOR_MOD_OR_DIV; |
| EMIT_BINARY( |
| "I64DivS", TypeKind::I64, |
| BLOCK( |
| Value::fromI64(lhs.asI64() / checkConstantDivision<int64_t>(lhs, rhs).asI64()) |
| ), |
| BLOCK( |
| emitModOrDiv<int64_t, false>(lhs, lhsLocation, rhs, rhsLocation, resultLocation); |
| ), |
| BLOCK( |
| emitModOrDiv<int64_t, false>(lhs, lhsLocation, rhs, rhsLocation, resultLocation); |
| ) |
| ); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI32DivU(Value lhs, Value rhs, Value& result) |
| { |
| PREPARE_FOR_MOD_OR_DIV; |
| EMIT_BINARY( |
| "I32DivU", TypeKind::I32, |
| BLOCK( |
| Value::fromI32(static_cast<uint32_t>(lhs.asI32()) / static_cast<uint32_t>(checkConstantDivision<int32_t>(lhs, rhs).asI32())) |
| ), |
| BLOCK( |
| emitModOrDiv<uint32_t, false>(lhs, lhsLocation, rhs, rhsLocation, resultLocation); |
| ), |
| BLOCK( |
| emitModOrDiv<uint32_t, false>(lhs, lhsLocation, rhs, rhsLocation, resultLocation); |
| ) |
| ); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI64DivU(Value lhs, Value rhs, Value& result) |
| { |
| PREPARE_FOR_MOD_OR_DIV; |
| EMIT_BINARY( |
| "I64DivU", TypeKind::I64, |
| BLOCK( |
| Value::fromI64(static_cast<uint64_t>(lhs.asI64()) / static_cast<uint64_t>(checkConstantDivision<int64_t>(lhs, rhs).asI64())) |
| ), |
| BLOCK( |
| emitModOrDiv<uint64_t, false>(lhs, lhsLocation, rhs, rhsLocation, resultLocation); |
| ), |
| BLOCK( |
| emitModOrDiv<uint64_t, false>(lhs, lhsLocation, rhs, rhsLocation, resultLocation); |
| ) |
| ); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI32RemS(Value lhs, Value rhs, Value& result) |
| { |
| PREPARE_FOR_MOD_OR_DIV; |
| EMIT_BINARY( |
| "I32RemS", TypeKind::I32, |
| BLOCK( |
| Value::fromI32(lhs.asI32() % checkConstantDivision<int32_t>(lhs, rhs).asI32()) |
| ), |
| BLOCK( |
| emitModOrDiv<int32_t, true>(lhs, lhsLocation, rhs, rhsLocation, resultLocation); |
| ), |
| BLOCK( |
| emitModOrDiv<int32_t, true>(lhs, lhsLocation, rhs, rhsLocation, resultLocation); |
| ) |
| ); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI64RemS(Value lhs, Value rhs, Value& result) |
| { |
| PREPARE_FOR_MOD_OR_DIV; |
| EMIT_BINARY( |
| "I64RemS", TypeKind::I64, |
| BLOCK( |
| Value::fromI64(lhs.asI64() % checkConstantDivision<int64_t>(lhs, rhs).asI64()) |
| ), |
| BLOCK( |
| emitModOrDiv<int64_t, true>(lhs, lhsLocation, rhs, rhsLocation, resultLocation); |
| ), |
| BLOCK( |
| emitModOrDiv<int64_t, true>(lhs, lhsLocation, rhs, rhsLocation, resultLocation); |
| ) |
| ); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI32RemU(Value lhs, Value rhs, Value& result) |
| { |
| PREPARE_FOR_MOD_OR_DIV; |
| EMIT_BINARY( |
| "I32RemU", TypeKind::I32, |
| BLOCK( |
| Value::fromI32(static_cast<uint32_t>(lhs.asI32()) % static_cast<uint32_t>(checkConstantDivision<int32_t>(lhs, rhs).asI32())) |
| ), |
| BLOCK( |
| emitModOrDiv<uint32_t, true>(lhs, lhsLocation, rhs, rhsLocation, resultLocation); |
| ), |
| BLOCK( |
| emitModOrDiv<uint32_t, true>(lhs, lhsLocation, rhs, rhsLocation, resultLocation); |
| ) |
| ); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI64RemU(Value lhs, Value rhs, Value& result) |
| { |
| PREPARE_FOR_MOD_OR_DIV; |
| EMIT_BINARY( |
| "I64RemU", TypeKind::I64, |
| BLOCK( |
| Value::fromI64(static_cast<uint64_t>(lhs.asI64()) % static_cast<uint64_t>(checkConstantDivision<int64_t>(lhs, rhs).asI64())) |
| ), |
| BLOCK( |
| emitModOrDiv<uint64_t, true>(lhs, lhsLocation, rhs, rhsLocation, resultLocation); |
| ), |
| BLOCK( |
| emitModOrDiv<uint64_t, true>(lhs, lhsLocation, rhs, rhsLocation, resultLocation); |
| ) |
| ); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF32Div(Value lhs, Value rhs, Value& result) |
| { |
| EMIT_BINARY( |
| "F32Div", TypeKind::F32, |
| BLOCK(Value::fromF32(lhs.asF32() / rhs.asF32())), |
| BLOCK( |
| m_jit.divFloat(lhsLocation.asFPR(), rhsLocation.asFPR(), resultLocation.asFPR()); |
| ), |
| BLOCK( |
| ImmHelpers::immLocation(lhsLocation, rhsLocation) = Location::fromFPR(wasmScratchFPR); |
| emitMoveConst(ImmHelpers::imm(lhs, rhs), Location::fromFPR(wasmScratchFPR)); |
| m_jit.divFloat(lhsLocation.asFPR(), rhsLocation.asFPR(), resultLocation.asFPR()); |
| ) |
| ); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF64Div(Value lhs, Value rhs, Value& result) |
| { |
| EMIT_BINARY( |
| "F64Div", TypeKind::F64, |
| BLOCK(Value::fromF64(lhs.asF64() / rhs.asF64())), |
| BLOCK( |
| m_jit.divDouble(lhsLocation.asFPR(), rhsLocation.asFPR(), resultLocation.asFPR()); |
| ), |
| BLOCK( |
| ImmHelpers::immLocation(lhsLocation, rhsLocation) = Location::fromFPR(wasmScratchFPR); |
| emitMoveConst(ImmHelpers::imm(lhs, rhs), Location::fromFPR(wasmScratchFPR)); |
| m_jit.divDouble(lhsLocation.asFPR(), rhsLocation.asFPR(), resultLocation.asFPR()); |
| ) |
| ); |
| } |
| |
| enum class MinOrMax { Min, Max }; |
| |
| template<typename FloatType, MinOrMax IsMinOrMax> |
| void emitFloatingPointMinOrMax(FPRReg left, FPRReg right, FPRReg result) |
| { |
| constexpr bool is32 = sizeof(FloatType) == 4; |
| |
| #if CPU(ARM64) |
| if constexpr (is32 && IsMinOrMax == MinOrMax::Min) |
| m_jit.floatMin(left, right, result); |
| else if constexpr (is32) |
| m_jit.floatMax(left, right, result); |
| else if constexpr (IsMinOrMax == MinOrMax::Min) |
| m_jit.doubleMin(left, right, result); |
| else |
| m_jit.doubleMax(left, right, result); |
| return; |
| #else |
| // Entry |
| Jump isEqual = is32 |
| ? m_jit.branchFloat(MacroAssembler::DoubleEqualAndOrdered, left, right) |
| : m_jit.branchDouble(MacroAssembler::DoubleEqualAndOrdered, left, right); |
| |
| // Left is not equal to right |
| Jump isLessThan = is32 |
| ? m_jit.branchFloat(MacroAssembler::DoubleLessThanAndOrdered, left, right) |
| : m_jit.branchDouble(MacroAssembler::DoubleLessThanAndOrdered, left, right); |
| |
| // Left is not less than right |
| Jump isGreaterThan = is32 |
| ? m_jit.branchFloat(MacroAssembler::DoubleGreaterThanAndOrdered, left, right) |
| : m_jit.branchDouble(MacroAssembler::DoubleGreaterThanAndOrdered, left, right); |
| |
| // NaN |
| if constexpr (is32) |
| m_jit.addFloat(left, right, result); |
| else |
| m_jit.addDouble(left, right, result); |
| Jump afterNaN = m_jit.jump(); |
| |
| // Left is strictly greater than right |
| isGreaterThan.link(&m_jit); |
| auto isGreaterThanResult = IsMinOrMax == MinOrMax::Max ? left : right; |
| m_jit.moveDouble(isGreaterThanResult, result); |
| Jump afterGreaterThan = m_jit.jump(); |
| |
| // Left is strictly less than right |
| isLessThan.link(&m_jit); |
| auto isLessThanResult = IsMinOrMax == MinOrMax::Max ? right : left; |
| m_jit.moveDouble(isLessThanResult, result); |
| Jump afterLessThan = m_jit.jump(); |
| |
| // Left is equal to right |
| isEqual.link(&m_jit); |
| if constexpr (is32 && IsMinOrMax == MinOrMax::Max) |
| m_jit.andFloat(left, right, result); |
| else if constexpr (is32) |
| m_jit.orFloat(left, right, result); |
| else if constexpr (IsMinOrMax == MinOrMax::Max) |
| m_jit.andDouble(left, right, result); |
| else |
| m_jit.orDouble(left, right, result); |
| |
| // Continuation |
| afterNaN.link(&m_jit); |
| afterGreaterThan.link(&m_jit); |
| afterLessThan.link(&m_jit); |
| #endif |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF32Min(Value lhs, Value rhs, Value& result) |
| { |
| EMIT_BINARY( |
| "F32Min", TypeKind::F32, |
| BLOCK(Value::fromF32(std::min(lhs.asF32(), rhs.asF32()))), |
| BLOCK( |
| emitFloatingPointMinOrMax<float, MinOrMax::Min>(lhsLocation.asFPR(), rhsLocation.asFPR(), resultLocation.asFPR()); |
| ), |
| BLOCK( |
| ImmHelpers::immLocation(lhsLocation, rhsLocation) = Location::fromFPR(wasmScratchFPR); |
| emitMoveConst(ImmHelpers::imm(lhs, rhs), Location::fromFPR(wasmScratchFPR)); |
| emitFloatingPointMinOrMax<float, MinOrMax::Min>(lhsLocation.asFPR(), rhsLocation.asFPR(), resultLocation.asFPR()); |
| ) |
| ); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF64Min(Value lhs, Value rhs, Value& result) |
| { |
| EMIT_BINARY( |
| "F64Min", TypeKind::F64, |
| BLOCK(Value::fromF64(std::min(lhs.asF64(), rhs.asF64()))), |
| BLOCK( |
| emitFloatingPointMinOrMax<double, MinOrMax::Min>(lhsLocation.asFPR(), rhsLocation.asFPR(), resultLocation.asFPR()); |
| ), |
| BLOCK( |
| ImmHelpers::immLocation(lhsLocation, rhsLocation) = Location::fromFPR(wasmScratchFPR); |
| emitMoveConst(ImmHelpers::imm(lhs, rhs), Location::fromFPR(wasmScratchFPR)); |
| emitFloatingPointMinOrMax<double, MinOrMax::Min>(lhsLocation.asFPR(), rhsLocation.asFPR(), resultLocation.asFPR()); |
| ) |
| ); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF32Max(Value lhs, Value rhs, Value& result) |
| { |
| EMIT_BINARY( |
| "F32Max", TypeKind::F32, |
| BLOCK(Value::fromF32(std::max(lhs.asF32(), rhs.asF32()))), |
| BLOCK( |
| emitFloatingPointMinOrMax<float, MinOrMax::Max>(lhsLocation.asFPR(), rhsLocation.asFPR(), resultLocation.asFPR()); |
| ), |
| BLOCK( |
| ImmHelpers::immLocation(lhsLocation, rhsLocation) = Location::fromFPR(wasmScratchFPR); |
| emitMoveConst(ImmHelpers::imm(lhs, rhs), Location::fromFPR(wasmScratchFPR)); |
| emitFloatingPointMinOrMax<float, MinOrMax::Max>(lhsLocation.asFPR(), rhsLocation.asFPR(), resultLocation.asFPR()); |
| ) |
| ); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF64Max(Value lhs, Value rhs, Value& result) |
| { |
| EMIT_BINARY( |
| "F64Max", TypeKind::F64, |
| BLOCK(Value::fromF64(std::max(lhs.asF64(), rhs.asF64()))), |
| BLOCK( |
| emitFloatingPointMinOrMax<double, MinOrMax::Max>(lhsLocation.asFPR(), rhsLocation.asFPR(), resultLocation.asFPR()); |
| ), |
| BLOCK( |
| ImmHelpers::immLocation(lhsLocation, rhsLocation) = Location::fromFPR(wasmScratchFPR); |
| emitMoveConst(ImmHelpers::imm(lhs, rhs), Location::fromFPR(wasmScratchFPR)); |
| emitFloatingPointMinOrMax<double, MinOrMax::Max>(lhsLocation.asFPR(), rhsLocation.asFPR(), resultLocation.asFPR()); |
| ) |
| ); |
| } |
| |
| inline float floatCopySign(float lhs, float rhs) |
| { |
| uint32_t lhsAsInt32 = bitwise_cast<uint32_t>(lhs); |
| uint32_t rhsAsInt32 = bitwise_cast<uint32_t>(rhs); |
| lhsAsInt32 &= 0x7fffffffu; |
| rhsAsInt32 &= 0x80000000u; |
| lhsAsInt32 |= rhsAsInt32; |
| return bitwise_cast<float>(lhsAsInt32); |
| } |
| |
| inline double doubleCopySign(double lhs, double rhs) |
| { |
| uint64_t lhsAsInt64 = bitwise_cast<uint64_t>(lhs); |
| uint64_t rhsAsInt64 = bitwise_cast<uint64_t>(rhs); |
| lhsAsInt64 &= 0x7fffffffffffffffu; |
| rhsAsInt64 &= 0x8000000000000000u; |
| lhsAsInt64 |= rhsAsInt64; |
| return bitwise_cast<double>(lhsAsInt64); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI32And(Value lhs, Value rhs, Value& result) |
| { |
| EMIT_BINARY( |
| "I32And", TypeKind::I32, |
| BLOCK(Value::fromI32(lhs.asI32() & rhs.asI32())), |
| BLOCK( |
| m_jit.and32(lhsLocation.asGPR(), rhsLocation.asGPR(), resultLocation.asGPR()); |
| ), |
| BLOCK( |
| m_jit.move(ImmHelpers::regLocation(lhsLocation, rhsLocation).asGPR(), resultLocation.asGPR()); |
| m_jit.and32(Imm32(ImmHelpers::imm(lhs, rhs).asI32()), resultLocation.asGPR()); |
| ) |
| ); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI64And(Value lhs, Value rhs, Value& result) |
| { |
| EMIT_BINARY( |
| "I64And", TypeKind::I64, |
| BLOCK(Value::fromI64(lhs.asI64() & rhs.asI64())), |
| BLOCK( |
| m_jit.and64(lhsLocation.asGPR(), rhsLocation.asGPR(), resultLocation.asGPR()); |
| ), |
| BLOCK( |
| m_jit.move(ImmHelpers::regLocation(lhsLocation, rhsLocation).asGPR(), resultLocation.asGPR()); |
| m_jit.and64(TrustedImm64(ImmHelpers::imm(lhs, rhs).asI64()), resultLocation.asGPR()); |
| ) |
| ); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI32Xor(Value lhs, Value rhs, Value& result) |
| { |
| EMIT_BINARY( |
| "I32Xor", TypeKind::I32, |
| BLOCK(Value::fromI32(lhs.asI32() ^ rhs.asI32())), |
| BLOCK( |
| m_jit.xor32(lhsLocation.asGPR(), rhsLocation.asGPR(), resultLocation.asGPR()); |
| ), |
| BLOCK( |
| m_jit.move(ImmHelpers::regLocation(lhsLocation, rhsLocation).asGPR(), resultLocation.asGPR()); |
| m_jit.xor32(Imm32(ImmHelpers::imm(lhs, rhs).asI32()), resultLocation.asGPR()); |
| ) |
| ); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI64Xor(Value lhs, Value rhs, Value& result) |
| { |
| EMIT_BINARY( |
| "I64Xor", TypeKind::I64, |
| BLOCK(Value::fromI64(lhs.asI64() ^ rhs.asI64())), |
| BLOCK( |
| m_jit.xor64(lhsLocation.asGPR(), rhsLocation.asGPR(), resultLocation.asGPR()); |
| ), |
| BLOCK( |
| m_jit.move(ImmHelpers::regLocation(lhsLocation, rhsLocation).asGPR(), resultLocation.asGPR()); |
| m_jit.xor64(TrustedImm64(ImmHelpers::imm(lhs, rhs).asI64()), resultLocation.asGPR()); |
| ) |
| ); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI32Or(Value lhs, Value rhs, Value& result) |
| { |
| EMIT_BINARY( |
| "I32Or", TypeKind::I32, |
| BLOCK(Value::fromI32(lhs.asI32() | rhs.asI32())), |
| BLOCK( |
| m_jit.or32(lhsLocation.asGPR(), rhsLocation.asGPR(), resultLocation.asGPR()); |
| ), |
| BLOCK( |
| m_jit.move(ImmHelpers::regLocation(lhsLocation, rhsLocation).asGPR(), resultLocation.asGPR()); |
| m_jit.or32(Imm32(ImmHelpers::imm(lhs, rhs).asI32()), resultLocation.asGPR()); |
| ) |
| ); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI64Or(Value lhs, Value rhs, Value& result) |
| { |
| EMIT_BINARY( |
| "I64Or", TypeKind::I64, |
| BLOCK(Value::fromI64(lhs.asI64() | rhs.asI64())), |
| BLOCK( |
| m_jit.or64(lhsLocation.asGPR(), rhsLocation.asGPR(), resultLocation.asGPR()); |
| ), |
| BLOCK( |
| m_jit.move(ImmHelpers::regLocation(lhsLocation, rhsLocation).asGPR(), resultLocation.asGPR()); |
| m_jit.or64(TrustedImm64(ImmHelpers::imm(lhs, rhs).asI64()), resultLocation.asGPR()); |
| ) |
| ); |
| } |
| |
| #if CPU(X86_64) |
| #define PREPARE_FOR_SHIFT \ |
| do { \ |
| clobber(shiftRCX); \ |
| } while (false); \ |
| ScratchScope<0, 0> scratches(*this, Location::fromGPR(shiftRCX)) |
| #else |
| #define PREPARE_FOR_SHIFT |
| #endif |
| |
| void moveShiftAmountIfNecessary(Location& rhsLocation) |
| { |
| if constexpr (isX86()) { |
| m_jit.move(rhsLocation.asGPR(), shiftRCX); |
| rhsLocation = Location::fromGPR(shiftRCX); |
| } |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI32Shl(Value lhs, Value rhs, Value& result) |
| { |
| PREPARE_FOR_SHIFT; |
| EMIT_BINARY( |
| "I32Shl", TypeKind::I32, |
| BLOCK(Value::fromI32(lhs.asI32() << rhs.asI32())), |
| BLOCK( |
| moveShiftAmountIfNecessary(rhsLocation); |
| m_jit.lshift32(lhsLocation.asGPR(), rhsLocation.asGPR(), resultLocation.asGPR()); |
| ), |
| BLOCK( |
| if (rhs.isConst()) |
| m_jit.lshift32(lhsLocation.asGPR(), m_jit.trustedImm32ForShift(Imm32(rhs.asI32())), resultLocation.asGPR()); |
| else { |
| moveShiftAmountIfNecessary(rhsLocation); |
| emitMoveConst(lhs, lhsLocation = Location::fromGPR(wasmScratchGPR)); |
| m_jit.lshift32(lhsLocation.asGPR(), rhsLocation.asGPR(), resultLocation.asGPR()); |
| } |
| ) |
| ); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI64Shl(Value lhs, Value rhs, Value& result) |
| { |
| PREPARE_FOR_SHIFT; |
| EMIT_BINARY( |
| "I64Shl", TypeKind::I64, |
| BLOCK(Value::fromI64(lhs.asI64() << rhs.asI64())), |
| BLOCK( |
| moveShiftAmountIfNecessary(rhsLocation); |
| m_jit.lshift64(lhsLocation.asGPR(), rhsLocation.asGPR(), resultLocation.asGPR()); |
| ), |
| BLOCK( |
| if (rhs.isConst()) |
| m_jit.lshift64(lhsLocation.asGPR(), TrustedImm32(rhs.asI64()), resultLocation.asGPR()); |
| else { |
| moveShiftAmountIfNecessary(rhsLocation); |
| emitMoveConst(lhs, lhsLocation = Location::fromGPR(wasmScratchGPR)); |
| m_jit.lshift64(lhsLocation.asGPR(), rhsLocation.asGPR(), resultLocation.asGPR()); |
| } |
| ) |
| ); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI32ShrS(Value lhs, Value rhs, Value& result) |
| { |
| PREPARE_FOR_SHIFT; |
| EMIT_BINARY( |
| "I32ShrS", TypeKind::I32, |
| BLOCK(Value::fromI32(lhs.asI32() >> rhs.asI32())), |
| BLOCK( |
| moveShiftAmountIfNecessary(rhsLocation); |
| m_jit.rshift32(lhsLocation.asGPR(), rhsLocation.asGPR(), resultLocation.asGPR()); |
| ), |
| BLOCK( |
| if (rhs.isConst()) |
| m_jit.rshift32(lhsLocation.asGPR(), m_jit.trustedImm32ForShift(Imm32(rhs.asI32())), resultLocation.asGPR()); |
| else { |
| moveShiftAmountIfNecessary(rhsLocation); |
| emitMoveConst(lhs, lhsLocation = Location::fromGPR(wasmScratchGPR)); |
| m_jit.rshift32(lhsLocation.asGPR(), rhsLocation.asGPR(), resultLocation.asGPR()); |
| } |
| ) |
| ); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI64ShrS(Value lhs, Value rhs, Value& result) |
| { |
| PREPARE_FOR_SHIFT; |
| EMIT_BINARY( |
| "I64ShrS", TypeKind::I64, |
| BLOCK(Value::fromI64(lhs.asI64() >> rhs.asI64())), |
| BLOCK( |
| moveShiftAmountIfNecessary(rhsLocation); |
| m_jit.rshift64(lhsLocation.asGPR(), rhsLocation.asGPR(), resultLocation.asGPR()); |
| ), |
| BLOCK( |
| if (rhs.isConst()) |
| m_jit.rshift64(lhsLocation.asGPR(), TrustedImm32(rhs.asI64()), resultLocation.asGPR()); |
| else { |
| moveShiftAmountIfNecessary(rhsLocation); |
| emitMoveConst(lhs, lhsLocation = Location::fromGPR(wasmScratchGPR)); |
| m_jit.rshift64(lhsLocation.asGPR(), rhsLocation.asGPR(), resultLocation.asGPR()); |
| } |
| ) |
| ); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI32ShrU(Value lhs, Value rhs, Value& result) |
| { |
| PREPARE_FOR_SHIFT; |
| EMIT_BINARY( |
| "I32ShrU", TypeKind::I32, |
| BLOCK(Value::fromI32(static_cast<uint32_t>(lhs.asI32()) >> static_cast<uint32_t>(rhs.asI32()))), |
| BLOCK( |
| moveShiftAmountIfNecessary(rhsLocation); |
| m_jit.urshift32(lhsLocation.asGPR(), rhsLocation.asGPR(), resultLocation.asGPR()); |
| ), |
| BLOCK( |
| if (rhs.isConst()) |
| m_jit.urshift32(lhsLocation.asGPR(), m_jit.trustedImm32ForShift(Imm32(rhs.asI32())), resultLocation.asGPR()); |
| else { |
| moveShiftAmountIfNecessary(rhsLocation); |
| emitMoveConst(lhs, lhsLocation = Location::fromGPR(wasmScratchGPR)); |
| m_jit.urshift32(lhsLocation.asGPR(), rhsLocation.asGPR(), resultLocation.asGPR()); |
| } |
| ) |
| ); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI64ShrU(Value lhs, Value rhs, Value& result) |
| { |
| PREPARE_FOR_SHIFT; |
| EMIT_BINARY( |
| "I64ShrU", TypeKind::I64, |
| BLOCK(Value::fromI64(static_cast<uint64_t>(lhs.asI64()) >> static_cast<uint64_t>(rhs.asI64()))), |
| BLOCK( |
| moveShiftAmountIfNecessary(rhsLocation); |
| m_jit.urshift64(lhsLocation.asGPR(), rhsLocation.asGPR(), resultLocation.asGPR()); |
| ), |
| BLOCK( |
| if (rhs.isConst()) |
| m_jit.urshift64(lhsLocation.asGPR(), TrustedImm32(rhs.asI64()), resultLocation.asGPR()); |
| else { |
| moveShiftAmountIfNecessary(rhsLocation); |
| emitMoveConst(lhs, lhsLocation = Location::fromGPR(wasmScratchGPR)); |
| m_jit.urshift64(lhsLocation.asGPR(), rhsLocation.asGPR(), resultLocation.asGPR()); |
| } |
| ) |
| ); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI32Rotl(Value lhs, Value rhs, Value& result) |
| { |
| PREPARE_FOR_SHIFT; |
| EMIT_BINARY( |
| "I32Rotl", TypeKind::I32, |
| BLOCK(Value::fromI32(B3::rotateLeft(lhs.asI32(), rhs.asI32()))), |
| #if CPU(X86_64) |
| BLOCK( |
| moveShiftAmountIfNecessary(rhsLocation); |
| m_jit.rotateLeft32(lhsLocation.asGPR(), rhsLocation.asGPR(), resultLocation.asGPR()); |
| ), |
| BLOCK( |
| if (rhs.isConst()) |
| m_jit.rotateLeft32(lhsLocation.asGPR(), m_jit.trustedImm32ForShift(Imm32(rhs.asI32())), resultLocation.asGPR()); |
| else { |
| moveShiftAmountIfNecessary(rhsLocation); |
| emitMoveConst(lhs, resultLocation); |
| m_jit.rotateLeft32(resultLocation.asGPR(), rhsLocation.asGPR(), resultLocation.asGPR()); |
| } |
| ) |
| #else |
| BLOCK( |
| moveShiftAmountIfNecessary(rhsLocation); |
| m_jit.neg32(rhsLocation.asGPR(), wasmScratchGPR); |
| m_jit.rotateRight32(lhsLocation.asGPR(), wasmScratchGPR, resultLocation.asGPR()); |
| ), |
| BLOCK( |
| if (rhs.isConst()) |
| m_jit.rotateRight32(lhsLocation.asGPR(), m_jit.trustedImm32ForShift(Imm32(-rhs.asI32())), resultLocation.asGPR()); |
| else { |
| moveShiftAmountIfNecessary(rhsLocation); |
| m_jit.neg32(rhsLocation.asGPR(), wasmScratchGPR); |
| emitMoveConst(lhs, resultLocation); |
| m_jit.rotateRight32(resultLocation.asGPR(), wasmScratchGPR, resultLocation.asGPR()); |
| } |
| ) |
| #endif |
| ); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI64Rotl(Value lhs, Value rhs, Value& result) |
| { |
| PREPARE_FOR_SHIFT; |
| EMIT_BINARY( |
| "I64Rotl", TypeKind::I64, |
| BLOCK(Value::fromI64(B3::rotateLeft(lhs.asI64(), rhs.asI64()))), |
| #if CPU(X86_64) |
| BLOCK( |
| moveShiftAmountIfNecessary(rhsLocation); |
| m_jit.rotateLeft64(lhsLocation.asGPR(), rhsLocation.asGPR(), resultLocation.asGPR()); |
| ), |
| BLOCK( |
| if (rhs.isConst()) |
| m_jit.rotateLeft64(lhsLocation.asGPR(), TrustedImm32(rhs.asI32()), resultLocation.asGPR()); |
| else { |
| moveShiftAmountIfNecessary(rhsLocation); |
| emitMoveConst(lhs, resultLocation); |
| m_jit.rotateLeft64(resultLocation.asGPR(), rhsLocation.asGPR(), resultLocation.asGPR()); |
| } |
| ) |
| #else |
| BLOCK( |
| moveShiftAmountIfNecessary(rhsLocation); |
| m_jit.neg64(rhsLocation.asGPR(), wasmScratchGPR); |
| m_jit.rotateRight64(lhsLocation.asGPR(), wasmScratchGPR, resultLocation.asGPR()); |
| ), |
| BLOCK( |
| if (rhs.isConst()) |
| m_jit.rotateRight64(lhsLocation.asGPR(), TrustedImm32(-rhs.asI64()), resultLocation.asGPR()); |
| else { |
| moveShiftAmountIfNecessary(rhsLocation); |
| m_jit.neg64(rhsLocation.asGPR(), wasmScratchGPR); |
| emitMoveConst(lhs, resultLocation); |
| m_jit.rotateRight64(resultLocation.asGPR(), wasmScratchGPR, resultLocation.asGPR()); |
| } |
| ) |
| #endif |
| ); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI32Rotr(Value lhs, Value rhs, Value& result) |
| { |
| PREPARE_FOR_SHIFT; |
| EMIT_BINARY( |
| "I32Rotr", TypeKind::I32, |
| BLOCK(Value::fromI32(B3::rotateRight(lhs.asI32(), rhs.asI32()))), |
| BLOCK( |
| moveShiftAmountIfNecessary(rhsLocation); |
| m_jit.rotateRight32(lhsLocation.asGPR(), rhsLocation.asGPR(), resultLocation.asGPR()); |
| ), |
| BLOCK( |
| if (rhs.isConst()) |
| m_jit.rotateRight32(lhsLocation.asGPR(), m_jit.trustedImm32ForShift(Imm32(rhs.asI32())), resultLocation.asGPR()); |
| else { |
| moveShiftAmountIfNecessary(rhsLocation); |
| emitMoveConst(lhs, Location::fromGPR(wasmScratchGPR)); |
| m_jit.rotateRight32(wasmScratchGPR, rhsLocation.asGPR(), resultLocation.asGPR()); |
| } |
| ) |
| ); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI64Rotr(Value lhs, Value rhs, Value& result) |
| { |
| PREPARE_FOR_SHIFT; |
| EMIT_BINARY( |
| "I64Rotr", TypeKind::I64, |
| BLOCK(Value::fromI64(B3::rotateRight(lhs.asI64(), rhs.asI64()))), |
| BLOCK( |
| moveShiftAmountIfNecessary(rhsLocation); |
| m_jit.rotateRight64(lhsLocation.asGPR(), rhsLocation.asGPR(), resultLocation.asGPR()); |
| ), |
| BLOCK( |
| if (rhs.isConst()) |
| m_jit.rotateRight64(lhsLocation.asGPR(), TrustedImm32(rhs.asI64()), resultLocation.asGPR()); |
| else { |
| moveShiftAmountIfNecessary(rhsLocation); |
| emitMoveConst(lhs, Location::fromGPR(wasmScratchGPR)); |
| m_jit.rotateRight64(wasmScratchGPR, rhsLocation.asGPR(), resultLocation.asGPR()); |
| } |
| ) |
| ); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI32Clz(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "I32Clz", TypeKind::I32, |
| BLOCK(Value::fromI32(WTF::clz(operand.asI32()))), |
| BLOCK( |
| m_jit.countLeadingZeros32(operandLocation.asGPR(), resultLocation.asGPR()); |
| ) |
| ); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI64Clz(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "I64Clz", TypeKind::I64, |
| BLOCK(Value::fromI64(WTF::clz(operand.asI64()))), |
| BLOCK( |
| m_jit.countLeadingZeros64(operandLocation.asGPR(), resultLocation.asGPR()); |
| ) |
| ); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI32Ctz(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "I32Ctz", TypeKind::I32, |
| BLOCK(Value::fromI32(WTF::ctz(operand.asI32()))), |
| BLOCK( |
| m_jit.countTrailingZeros32(operandLocation.asGPR(), resultLocation.asGPR()); |
| ) |
| ); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI64Ctz(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "I64Ctz", TypeKind::I64, |
| BLOCK(Value::fromI64(WTF::ctz(operand.asI64()))), |
| BLOCK( |
| m_jit.countTrailingZeros64(operandLocation.asGPR(), resultLocation.asGPR()); |
| ) |
| ); |
| } |
| |
| PartialResult emitCompareI32(const char* opcode, Value& lhs, Value& rhs, Value& result, RelationalCondition condition, bool (*comparator)(int32_t lhs, int32_t rhs)) |
| { |
| EMIT_BINARY( |
| opcode, TypeKind::I32, |
| BLOCK(Value::fromI32(static_cast<int32_t>(comparator(lhs.asI32(), rhs.asI32())))), |
| BLOCK( |
| m_jit.compare32(condition, lhsLocation.asGPR(), rhsLocation.asGPR(), resultLocation.asGPR()); |
| ), |
| BLOCK( |
| if (rhs.isConst()) |
| m_jit.compare32(condition, lhsLocation.asGPR(), Imm32(rhs.asI32()), resultLocation.asGPR()); |
| else |
| m_jit.compare32(condition, Imm32(lhs.asI32()), rhsLocation.asGPR(), resultLocation.asGPR()); |
| ) |
| ) |
| } |
| |
| PartialResult emitCompareI64(const char* opcode, Value& lhs, Value& rhs, Value& result, RelationalCondition condition, bool (*comparator)(int64_t lhs, int64_t rhs)) |
| { |
| EMIT_BINARY( |
| opcode, TypeKind::I32, |
| BLOCK(Value::fromI32(static_cast<int32_t>(comparator(lhs.asI64(), rhs.asI64())))), |
| BLOCK( |
| m_jit.compare64(condition, lhsLocation.asGPR(), rhsLocation.asGPR(), resultLocation.asGPR()); |
| ), |
| BLOCK( |
| ImmHelpers::immLocation(lhsLocation, rhsLocation) = Location::fromGPR(wasmScratchGPR); |
| emitMoveConst(ImmHelpers::imm(lhs, rhs), Location::fromGPR(wasmScratchGPR)); |
| m_jit.compare64(condition, lhsLocation.asGPR(), rhsLocation.asGPR(), resultLocation.asGPR()); |
| ) |
| ) |
| } |
| |
| #define RELOP_AS_LAMBDA(op) [](auto lhs, auto rhs) -> auto { return lhs op rhs; } |
| #define TYPED_RELOP_AS_LAMBDA(type, op) [](auto lhs, auto rhs) -> auto { return static_cast<type>(lhs) op static_cast<type>(rhs); } |
| |
| PartialResult WARN_UNUSED_RETURN addI32Eq(Value lhs, Value rhs, Value& result) |
| { |
| return emitCompareI32("I32Eq", lhs, rhs, result, RelationalCondition::Equal, RELOP_AS_LAMBDA( == )); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI64Eq(Value lhs, Value rhs, Value& result) |
| { |
| return emitCompareI64("I64Eq", lhs, rhs, result, RelationalCondition::Equal, RELOP_AS_LAMBDA( == )); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI32Ne(Value lhs, Value rhs, Value& result) |
| { |
| return emitCompareI32("I32Ne", lhs, rhs, result, RelationalCondition::NotEqual, RELOP_AS_LAMBDA( != )); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI64Ne(Value lhs, Value rhs, Value& result) |
| { |
| return emitCompareI64("I64Ne", lhs, rhs, result, RelationalCondition::NotEqual, RELOP_AS_LAMBDA( != )); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI32LtS(Value lhs, Value rhs, Value& result) |
| { |
| return emitCompareI32("I32LtS", lhs, rhs, result, RelationalCondition::LessThan, RELOP_AS_LAMBDA( < )); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI64LtS(Value lhs, Value rhs, Value& result) |
| { |
| return emitCompareI64("I64LtS", lhs, rhs, result, RelationalCondition::LessThan, RELOP_AS_LAMBDA( < )); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI32LeS(Value lhs, Value rhs, Value& result) |
| { |
| return emitCompareI32("I32LeS", lhs, rhs, result, RelationalCondition::LessThanOrEqual, RELOP_AS_LAMBDA( <= )); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI64LeS(Value lhs, Value rhs, Value& result) |
| { |
| return emitCompareI64("I64LeS", lhs, rhs, result, RelationalCondition::LessThanOrEqual, RELOP_AS_LAMBDA( <= )); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI32GtS(Value lhs, Value rhs, Value& result) |
| { |
| return emitCompareI32("I32GtS", lhs, rhs, result, RelationalCondition::GreaterThan, RELOP_AS_LAMBDA( > )); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI64GtS(Value lhs, Value rhs, Value& result) |
| { |
| return emitCompareI64("I64GtS", lhs, rhs, result, RelationalCondition::GreaterThan, RELOP_AS_LAMBDA( > )); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI32GeS(Value lhs, Value rhs, Value& result) |
| { |
| return emitCompareI32("I32GeS", lhs, rhs, result, RelationalCondition::GreaterThanOrEqual, RELOP_AS_LAMBDA( >= )); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI64GeS(Value lhs, Value rhs, Value& result) |
| { |
| return emitCompareI64("I64GeS", lhs, rhs, result, RelationalCondition::GreaterThanOrEqual, RELOP_AS_LAMBDA( >= )); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI32LtU(Value lhs, Value rhs, Value& result) |
| { |
| return emitCompareI32("I32LtU", lhs, rhs, result, RelationalCondition::Below, TYPED_RELOP_AS_LAMBDA(uint32_t, <)); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI64LtU(Value lhs, Value rhs, Value& result) |
| { |
| return emitCompareI64("I64LtU", lhs, rhs, result, RelationalCondition::Below, TYPED_RELOP_AS_LAMBDA(uint64_t, <)); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI32LeU(Value lhs, Value rhs, Value& result) |
| { |
| return emitCompareI32("I32LeU", lhs, rhs, result, RelationalCondition::BelowOrEqual, TYPED_RELOP_AS_LAMBDA(uint32_t, <=)); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI64LeU(Value lhs, Value rhs, Value& result) |
| { |
| return emitCompareI64("I64LeU", lhs, rhs, result, RelationalCondition::BelowOrEqual, TYPED_RELOP_AS_LAMBDA(uint64_t, <=)); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI32GtU(Value lhs, Value rhs, Value& result) |
| { |
| return emitCompareI32("I32GtU", lhs, rhs, result, RelationalCondition::Above, TYPED_RELOP_AS_LAMBDA(uint32_t, >)); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI64GtU(Value lhs, Value rhs, Value& result) |
| { |
| return emitCompareI64("I64GtU", lhs, rhs, result, RelationalCondition::Above, TYPED_RELOP_AS_LAMBDA(uint64_t, >)); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI32GeU(Value lhs, Value rhs, Value& result) |
| { |
| return emitCompareI32("I32GeU", lhs, rhs, result, RelationalCondition::AboveOrEqual, TYPED_RELOP_AS_LAMBDA(uint32_t, >=)); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI64GeU(Value lhs, Value rhs, Value& result) |
| { |
| return emitCompareI64("I64GeU", lhs, rhs, result, RelationalCondition::AboveOrEqual, TYPED_RELOP_AS_LAMBDA(uint64_t, >=)); |
| } |
| |
| PartialResult emitCompareF32(const char* opcode, Value& lhs, Value& rhs, Value& result, DoubleCondition condition, bool (*comparator)(float lhs, float rhs)) |
| { |
| EMIT_BINARY( |
| opcode, TypeKind::I32, |
| BLOCK(Value::fromI32(static_cast<int32_t>(comparator(lhs.asF32(), rhs.asF32())))), |
| BLOCK( |
| m_jit.compareFloat(condition, lhsLocation.asFPR(), rhsLocation.asFPR(), resultLocation.asGPR()); |
| ), |
| BLOCK( |
| ImmHelpers::immLocation(lhsLocation, rhsLocation) = Location::fromFPR(wasmScratchFPR); |
| emitMoveConst(ImmHelpers::imm(lhs, rhs), Location::fromFPR(wasmScratchFPR)); |
| m_jit.compareFloat(condition, lhsLocation.asFPR(), rhsLocation.asFPR(), resultLocation.asGPR()); |
| ) |
| ) |
| } |
| |
| PartialResult emitCompareF64(const char* opcode, Value& lhs, Value& rhs, Value& result, DoubleCondition condition, bool (*comparator)(double lhs, double rhs)) |
| { |
| EMIT_BINARY( |
| opcode, TypeKind::I32, |
| BLOCK(Value::fromI32(static_cast<int32_t>(comparator(lhs.asF64(), rhs.asF64())))), |
| BLOCK( |
| m_jit.compareDouble(condition, lhsLocation.asFPR(), rhsLocation.asFPR(), resultLocation.asGPR()); |
| ), |
| BLOCK( |
| ImmHelpers::immLocation(lhsLocation, rhsLocation) = Location::fromFPR(wasmScratchFPR); |
| emitMoveConst(ImmHelpers::imm(lhs, rhs), Location::fromFPR(wasmScratchFPR)); |
| m_jit.compareDouble(condition, lhsLocation.asFPR(), rhsLocation.asFPR(), resultLocation.asGPR()); |
| ) |
| ) |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF32Eq(Value lhs, Value rhs, Value& result) |
| { |
| return emitCompareF32("F32Eq", lhs, rhs, result, DoubleCondition::DoubleEqualAndOrdered, RELOP_AS_LAMBDA( == )); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF64Eq(Value lhs, Value rhs, Value& result) |
| { |
| return emitCompareF64("F64Eq", lhs, rhs, result, DoubleCondition::DoubleEqualAndOrdered, RELOP_AS_LAMBDA( == )); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF32Ne(Value lhs, Value rhs, Value& result) |
| { |
| return emitCompareF32("F32Ne", lhs, rhs, result, DoubleCondition::DoubleNotEqualOrUnordered, RELOP_AS_LAMBDA( != )); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF64Ne(Value lhs, Value rhs, Value& result) |
| { |
| return emitCompareF64("F64Ne", lhs, rhs, result, DoubleCondition::DoubleNotEqualOrUnordered, RELOP_AS_LAMBDA( != )); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF32Lt(Value lhs, Value rhs, Value& result) |
| { |
| return emitCompareF32("F32Lt", lhs, rhs, result, DoubleCondition::DoubleLessThanAndOrdered, RELOP_AS_LAMBDA( < )); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF64Lt(Value lhs, Value rhs, Value& result) |
| { |
| return emitCompareF64("F64Lt", lhs, rhs, result, DoubleCondition::DoubleLessThanAndOrdered, RELOP_AS_LAMBDA( < )); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF32Le(Value lhs, Value rhs, Value& result) |
| { |
| return emitCompareF32("F32Le", lhs, rhs, result, DoubleCondition::DoubleLessThanOrEqualAndOrdered, RELOP_AS_LAMBDA( <= )); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF64Le(Value lhs, Value rhs, Value& result) |
| { |
| return emitCompareF64("F64Le", lhs, rhs, result, DoubleCondition::DoubleLessThanOrEqualAndOrdered, RELOP_AS_LAMBDA( <= )); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF32Gt(Value lhs, Value rhs, Value& result) |
| { |
| return emitCompareF32("F32Gt", lhs, rhs, result, DoubleCondition::DoubleGreaterThanAndOrdered, RELOP_AS_LAMBDA( > )); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF64Gt(Value lhs, Value rhs, Value& result) |
| { |
| return emitCompareF64("F64Gt", lhs, rhs, result, DoubleCondition::DoubleGreaterThanAndOrdered, RELOP_AS_LAMBDA( > )); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF32Ge(Value lhs, Value rhs, Value& result) |
| { |
| return emitCompareF32("F32Ge", lhs, rhs, result, DoubleCondition::DoubleGreaterThanOrEqualAndOrdered, RELOP_AS_LAMBDA( >= )); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF64Ge(Value lhs, Value rhs, Value& result) |
| { |
| return emitCompareF64("F64Ge", lhs, rhs, result, DoubleCondition::DoubleGreaterThanOrEqualAndOrdered, RELOP_AS_LAMBDA( >= )); |
| } |
| |
| #undef RELOP_AS_LAMBDA |
| #undef TYPED_RELOP_AS_LAMBDA |
| |
| PartialResult addI32WrapI64(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "I32WrapI64", TypeKind::I32, |
| BLOCK(Value::fromI32(static_cast<int32_t>(operand.asI64()))), |
| BLOCK( |
| m_jit.move(operandLocation.asGPR(), resultLocation.asGPR()); |
| ) |
| ) |
| } |
| |
| PartialResult addI32Extend8S(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "I32Extend8S", TypeKind::I32, |
| BLOCK(Value::fromI32(static_cast<int32_t>(static_cast<int8_t>(operand.asI32())))), |
| BLOCK( |
| m_jit.signExtend8To32(operandLocation.asGPR(), resultLocation.asGPR()); |
| ) |
| ) |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI32Extend16S(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "I32Extend16S", TypeKind::I32, |
| BLOCK(Value::fromI32(static_cast<int32_t>(static_cast<int16_t>(operand.asI32())))), |
| BLOCK( |
| m_jit.signExtend16To32(operandLocation.asGPR(), resultLocation.asGPR()); |
| ) |
| ) |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI64Extend8S(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "I64Extend8S", TypeKind::I64, |
| BLOCK(Value::fromI64(static_cast<int64_t>(static_cast<int8_t>(operand.asI64())))), |
| BLOCK( |
| m_jit.signExtend8To64(operandLocation.asGPR(), resultLocation.asGPR()); |
| ) |
| ) |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI64Extend16S(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "I64Extend16S", TypeKind::I64, |
| BLOCK(Value::fromI64(static_cast<int64_t>(static_cast<int16_t>(operand.asI64())))), |
| BLOCK( |
| m_jit.signExtend16To64(operandLocation.asGPR(), resultLocation.asGPR()); |
| ) |
| ) |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI64Extend32S(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "I64Extend32S", TypeKind::I64, |
| BLOCK(Value::fromI64(static_cast<int64_t>(static_cast<int32_t>(operand.asI64())))), |
| BLOCK( |
| m_jit.signExtend32To64(operandLocation.asGPR(), resultLocation.asGPR()); |
| ) |
| ) |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI64ExtendSI32(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "I64ExtendSI32", TypeKind::I64, |
| BLOCK(Value::fromI64(static_cast<int64_t>(operand.asI32()))), |
| BLOCK( |
| m_jit.signExtend32To64(operandLocation.asGPR(), resultLocation.asGPR()); |
| ) |
| ) |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI64ExtendUI32(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "I64ExtendUI32", TypeKind::I64, |
| BLOCK(Value::fromI64(static_cast<uint64_t>(static_cast<uint32_t>(operand.asI32())))), |
| BLOCK( |
| m_jit.zeroExtend32ToWord(operandLocation.asGPR(), resultLocation.asGPR()); |
| ) |
| ) |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI32Eqz(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "I32Eqz", TypeKind::I32, |
| BLOCK(Value::fromI32(!operand.asI32())), |
| BLOCK( |
| m_jit.test32(ResultCondition::Zero, operandLocation.asGPR(), operandLocation.asGPR(), resultLocation.asGPR()); |
| ) |
| ) |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI64Eqz(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "I64Eqz", TypeKind::I32, |
| BLOCK(Value::fromI32(!operand.asI64())), |
| BLOCK( |
| m_jit.test64(ResultCondition::Zero, operandLocation.asGPR(), operandLocation.asGPR(), resultLocation.asGPR()); |
| ) |
| ) |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI32Popcnt(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "I32Popcnt", TypeKind::I32, |
| BLOCK(Value::fromI32(__builtin_popcount(operand.asI32()))), |
| BLOCK( |
| #if CPU(X86_64) |
| if (m_jit.supportsCountPopulation()) |
| m_jit.countPopulation32(operandLocation.asGPR(), resultLocation.asGPR()); |
| else |
| #endif |
| emitCCall(&operationPopcount32, Vector<Value> { operand }, TypeKind::I32, result); |
| ) |
| ) |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI64Popcnt(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "I64Popcnt", TypeKind::I64, |
| BLOCK(Value::fromI64(__builtin_popcountll(operand.asI64()))), |
| BLOCK( |
| #if CPU(X86_64) |
| if (m_jit.supportsCountPopulation()) |
| m_jit.countPopulation64(operandLocation.asGPR(), resultLocation.asGPR()); |
| else |
| #endif |
| emitCCall(&operationPopcount64, Vector<Value> { operand }, TypeKind::I32, result); |
| ) |
| ) |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI32ReinterpretF32(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "I32ReinterpretF32", TypeKind::I32, |
| BLOCK(Value::fromI32(bitwise_cast<int32_t>(operand.asF32()))), |
| BLOCK( |
| m_jit.moveFloatTo32(operandLocation.asFPR(), resultLocation.asGPR()); |
| ) |
| ) |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI64ReinterpretF64(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "I64ReinterpretF64", TypeKind::I64, |
| BLOCK(Value::fromI64(bitwise_cast<int64_t>(operand.asF64()))), |
| BLOCK( |
| m_jit.moveDoubleTo64(operandLocation.asFPR(), resultLocation.asGPR()); |
| ) |
| ) |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF32ReinterpretI32(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "F32ReinterpretI32", TypeKind::F32, |
| BLOCK(Value::fromF32(bitwise_cast<float>(operand.asI32()))), |
| BLOCK( |
| m_jit.move32ToFloat(operandLocation.asGPR(), resultLocation.asFPR()); |
| ) |
| ) |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF64ReinterpretI64(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "F64ReinterpretI64", TypeKind::F64, |
| BLOCK(Value::fromF64(bitwise_cast<double>(operand.asI64()))), |
| BLOCK( |
| m_jit.move64ToDouble(operandLocation.asGPR(), resultLocation.asFPR()); |
| ) |
| ) |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF32DemoteF64(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "F32DemoteF64", TypeKind::F32, |
| BLOCK(Value::fromF32(operand.asF64())), |
| BLOCK( |
| m_jit.convertDoubleToFloat(operandLocation.asFPR(), resultLocation.asFPR()); |
| ) |
| ) |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF64PromoteF32(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "F64PromoteF32", TypeKind::F64, |
| BLOCK(Value::fromF64(operand.asF32())), |
| BLOCK( |
| m_jit.convertFloatToDouble(operandLocation.asFPR(), resultLocation.asFPR()); |
| ) |
| ) |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF32ConvertSI32(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "F32ConvertSI32", TypeKind::F32, |
| BLOCK(Value::fromF32(operand.asI32())), |
| BLOCK( |
| m_jit.convertInt32ToFloat(operandLocation.asGPR(), resultLocation.asFPR()); |
| ) |
| ) |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF32ConvertUI32(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "F32ConvertUI32", TypeKind::F32, |
| BLOCK(Value::fromF32(static_cast<uint32_t>(operand.asI32()))), |
| BLOCK( |
| #if CPU(X86_64) |
| ScratchScope<1, 0> scratches(*this); |
| m_jit.zeroExtend32ToWord(operandLocation.asGPR(), wasmScratchGPR); |
| m_jit.convertUInt64ToFloat(wasmScratchGPR, resultLocation.asFPR(), scratches.gpr(0)); |
| #else |
| m_jit.zeroExtend32ToWord(operandLocation.asGPR(), wasmScratchGPR); |
| m_jit.convertUInt64ToFloat(wasmScratchGPR, resultLocation.asFPR()); |
| #endif |
| ) |
| ) |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF32ConvertSI64(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "F32ConvertSI64", TypeKind::F32, |
| BLOCK(Value::fromF32(operand.asI64())), |
| BLOCK( |
| m_jit.convertInt64ToFloat(operandLocation.asGPR(), resultLocation.asFPR()); |
| ) |
| ) |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF32ConvertUI64(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "F32ConvertUI64", TypeKind::F32, |
| BLOCK(Value::fromF32(static_cast<uint64_t>(operand.asI64()))), |
| BLOCK( |
| #if CPU(X86_64) |
| m_jit.convertUInt64ToFloat(operandLocation.asGPR(), resultLocation.asFPR(), wasmScratchGPR); |
| #else |
| m_jit.convertUInt64ToFloat(operandLocation.asGPR(), resultLocation.asFPR()); |
| #endif |
| ) |
| ) |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF64ConvertSI32(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "F64ConvertSI32", TypeKind::F64, |
| BLOCK(Value::fromF64(operand.asI32())), |
| BLOCK( |
| m_jit.convertInt32ToDouble(operandLocation.asGPR(), resultLocation.asFPR()); |
| ) |
| ) |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF64ConvertUI32(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "F64ConvertUI32", TypeKind::F64, |
| BLOCK(Value::fromF64(static_cast<uint32_t>(operand.asI32()))), |
| BLOCK( |
| #if CPU(X86_64) |
| ScratchScope<1, 0> scratches(*this); |
| m_jit.zeroExtend32ToWord(operandLocation.asGPR(), wasmScratchGPR); |
| m_jit.convertUInt64ToDouble(wasmScratchGPR, resultLocation.asFPR(), scratches.gpr(0)); |
| #else |
| m_jit.zeroExtend32ToWord(operandLocation.asGPR(), wasmScratchGPR); |
| m_jit.convertUInt64ToDouble(wasmScratchGPR, resultLocation.asFPR()); |
| #endif |
| ) |
| ) |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF64ConvertSI64(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "F64ConvertSI64", TypeKind::F64, |
| BLOCK(Value::fromF64(operand.asI64())), |
| BLOCK( |
| m_jit.convertInt64ToDouble(operandLocation.asGPR(), resultLocation.asFPR()); |
| ) |
| ) |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF64ConvertUI64(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "F64ConvertUI64", TypeKind::F64, |
| BLOCK(Value::fromF64(static_cast<uint64_t>(operand.asI64()))), |
| BLOCK( |
| #if CPU(X86_64) |
| m_jit.convertUInt64ToDouble(operandLocation.asGPR(), resultLocation.asFPR(), wasmScratchGPR); |
| #else |
| m_jit.convertUInt64ToDouble(operandLocation.asGPR(), resultLocation.asFPR()); |
| #endif |
| ) |
| ) |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF32Copysign(Value lhs, Value rhs, Value& result) |
| { |
| EMIT_BINARY( |
| "F32Copysign", TypeKind::F32, |
| BLOCK(Value::fromF32(floatCopySign(lhs.asF32(), rhs.asF32()))), |
| BLOCK( |
| // FIXME: Better than what we have in the Air backend, but still not great. I think |
| // there's some vector instruction we can use to do this much quicker. |
| |
| #if CPU(X86_64) |
| m_jit.moveFloatTo32(lhsLocation.asFPR(), wasmScratchGPR); |
| m_jit.and32(TrustedImm32(0x7fffffff), wasmScratchGPR); |
| m_jit.move32ToFloat(wasmScratchGPR, wasmScratchFPR); |
| m_jit.moveFloatTo32(rhsLocation.asFPR(), wasmScratchGPR); |
| m_jit.and32(TrustedImm32(static_cast<int32_t>(0x80000000u)), wasmScratchGPR, wasmScratchGPR); |
| m_jit.move32ToFloat(wasmScratchGPR, resultLocation.asFPR()); |
| m_jit.orFloat(resultLocation.asFPR(), wasmScratchFPR, resultLocation.asFPR()); |
| #else |
| m_jit.moveFloatTo32(rhsLocation.asFPR(), wasmScratchGPR); |
| m_jit.and32(TrustedImm32(static_cast<int32_t>(0x80000000u)), wasmScratchGPR, wasmScratchGPR); |
| m_jit.move32ToFloat(wasmScratchGPR, wasmScratchFPR); |
| m_jit.absFloat(lhsLocation.asFPR(), lhsLocation.asFPR()); |
| m_jit.orFloat(lhsLocation.asFPR(), wasmScratchFPR, resultLocation.asFPR()); |
| #endif |
| ), |
| BLOCK( |
| if (lhs.isConst()) { |
| m_jit.moveFloatTo32(rhsLocation.asFPR(), wasmScratchGPR); |
| m_jit.and32(TrustedImm32(static_cast<int32_t>(0x80000000u)), wasmScratchGPR, wasmScratchGPR); |
| m_jit.move32ToFloat(wasmScratchGPR, wasmScratchFPR); |
| |
| emitMoveConst(Value::fromF32(std::abs(lhs.asF32())), resultLocation); |
| m_jit.orFloat(resultLocation.asFPR(), wasmScratchFPR, resultLocation.asFPR()); |
| } else { |
| bool signBit = bitwise_cast<uint32_t>(rhs.asF32()) & 0x80000000u; |
| #if CPU(X86_64) |
| m_jit.moveDouble(lhsLocation.asFPR(), resultLocation.asFPR()); |
| m_jit.move32ToFloat(TrustedImm32(0x7fffffff), wasmScratchFPR); |
| m_jit.andFloat(wasmScratchFPR, resultLocation.asFPR()); |
| if (signBit) { |
| m_jit.xorFloat(wasmScratchFPR, wasmScratchFPR); |
| m_jit.subFloat(wasmScratchFPR, resultLocation.asFPR(), resultLocation.asFPR()); |
| } |
| #else |
| m_jit.absFloat(lhsLocation.asFPR(), resultLocation.asFPR()); |
| if (signBit) |
| m_jit.negateFloat(resultLocation.asFPR(), resultLocation.asFPR()); |
| #endif |
| } |
| ) |
| ) |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF64Copysign(Value lhs, Value rhs, Value& result) |
| { |
| if constexpr (isX86()) |
| clobber(shiftRCX); |
| |
| EMIT_BINARY( |
| "F64Copysign", TypeKind::F64, |
| BLOCK(Value::fromF64(doubleCopySign(lhs.asF64(), rhs.asF64()))), |
| BLOCK( |
| // FIXME: Better than what we have in the Air backend, but still not great. I think |
| // there's some vector instruction we can use to do this much quicker. |
| |
| #if CPU(X86_64) |
| m_jit.moveDoubleTo64(lhsLocation.asFPR(), wasmScratchGPR); |
| m_jit.and64(TrustedImm64(0x7fffffffffffffffll), wasmScratchGPR); |
| m_jit.move64ToDouble(wasmScratchGPR, wasmScratchFPR); |
| m_jit.moveDoubleTo64(rhsLocation.asFPR(), wasmScratchGPR); |
| m_jit.urshift64(wasmScratchGPR, TrustedImm32(63), wasmScratchGPR); |
| m_jit.lshift64(wasmScratchGPR, TrustedImm32(63), wasmScratchGPR); |
| m_jit.move64ToDouble(wasmScratchGPR, resultLocation.asFPR()); |
| m_jit.orDouble(resultLocation.asFPR(), wasmScratchFPR, resultLocation.asFPR()); |
| #else |
| m_jit.moveDoubleTo64(rhsLocation.asFPR(), wasmScratchGPR); |
| |
| // Probably saves us a bit of space compared to reserving another register and |
| // materializing a 64-bit constant. |
| m_jit.urshift64(wasmScratchGPR, TrustedImm32(63), wasmScratchGPR); |
| m_jit.lshift64(wasmScratchGPR, TrustedImm32(63), wasmScratchGPR); |
| m_jit.move64ToDouble(wasmScratchGPR, wasmScratchFPR); |
| |
| m_jit.absDouble(lhsLocation.asFPR(), lhsLocation.asFPR()); |
| m_jit.orDouble(lhsLocation.asFPR(), wasmScratchFPR, resultLocation.asFPR()); |
| #endif |
| ), |
| BLOCK( |
| if (lhs.isConst()) { |
| m_jit.moveDoubleTo64(rhsLocation.asFPR(), wasmScratchGPR); |
| m_jit.urshift64(wasmScratchGPR, TrustedImm32(63), wasmScratchGPR); |
| m_jit.lshift64(wasmScratchGPR, TrustedImm32(63), wasmScratchGPR); |
| m_jit.move64ToDouble(wasmScratchGPR, wasmScratchFPR); |
| |
| // Moving this constant clobbers wasmScratchGPR, but not wasmScratchFPR |
| emitMoveConst(Value::fromF64(std::abs(lhs.asF64())), resultLocation); |
| m_jit.orDouble(resultLocation.asFPR(), wasmScratchFPR, resultLocation.asFPR()); |
| } else { |
| bool signBit = bitwise_cast<uint64_t>(rhs.asF64()) & 0x8000000000000000ull; |
| #if CPU(X86_64) |
| m_jit.moveDouble(lhsLocation.asFPR(), resultLocation.asFPR()); |
| m_jit.move64ToDouble(TrustedImm64(0x7fffffffffffffffll), wasmScratchFPR); |
| m_jit.andDouble(wasmScratchFPR, resultLocation.asFPR()); |
| if (signBit) { |
| m_jit.xorDouble(wasmScratchFPR, wasmScratchFPR); |
| m_jit.subDouble(wasmScratchFPR, resultLocation.asFPR(), resultLocation.asFPR()); |
| } |
| #else |
| m_jit.absDouble(lhsLocation.asFPR(), resultLocation.asFPR()); |
| if (signBit) |
| m_jit.negateDouble(resultLocation.asFPR(), resultLocation.asFPR()); |
| #endif |
| } |
| ) |
| ) |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF32Floor(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "F32Floor", TypeKind::F32, |
| BLOCK(Value::fromF32(Math::floorFloat(operand.asF32()))), |
| BLOCK( |
| m_jit.floorFloat(operandLocation.asFPR(), resultLocation.asFPR()); |
| ) |
| ) |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF64Floor(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "F64Floor", TypeKind::F64, |
| BLOCK(Value::fromF64(Math::floorDouble(operand.asF64()))), |
| BLOCK( |
| m_jit.floorDouble(operandLocation.asFPR(), resultLocation.asFPR()); |
| ) |
| ) |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF32Ceil(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "F32Ceil", TypeKind::F32, |
| BLOCK(Value::fromF32(Math::ceilFloat(operand.asF32()))), |
| BLOCK( |
| m_jit.ceilFloat(operandLocation.asFPR(), resultLocation.asFPR()); |
| ) |
| ) |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF64Ceil(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "F64Ceil", TypeKind::F64, |
| BLOCK(Value::fromF64(Math::ceilDouble(operand.asF64()))), |
| BLOCK( |
| m_jit.ceilDouble(operandLocation.asFPR(), resultLocation.asFPR()); |
| ) |
| ) |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF32Abs(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "F32Abs", TypeKind::F32, |
| BLOCK(Value::fromF32(std::abs(operand.asF32()))), |
| BLOCK( |
| #if CPU(X86_64) |
| m_jit.move32ToFloat(TrustedImm32(0x7fffffffll), wasmScratchFPR); |
| m_jit.andFloat(operandLocation.asFPR(), wasmScratchFPR, resultLocation.asFPR()); |
| #else |
| m_jit.absFloat(operandLocation.asFPR(), resultLocation.asFPR()); |
| #endif |
| ) |
| ) |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF64Abs(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "F64Abs", TypeKind::F64, |
| BLOCK(Value::fromF64(std::abs(operand.asF64()))), |
| BLOCK( |
| #if CPU(X86_64) |
| m_jit.move64ToDouble(TrustedImm64(0x7fffffffffffffffll), wasmScratchFPR); |
| m_jit.andDouble(operandLocation.asFPR(), wasmScratchFPR, resultLocation.asFPR()); |
| #else |
| m_jit.absDouble(operandLocation.asFPR(), resultLocation.asFPR()); |
| #endif |
| ) |
| ) |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF32Sqrt(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "F32Sqrt", TypeKind::F32, |
| BLOCK(Value::fromF32(Math::sqrtFloat(operand.asF32()))), |
| BLOCK( |
| m_jit.sqrtFloat(operandLocation.asFPR(), resultLocation.asFPR()); |
| ) |
| ) |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF64Sqrt(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "F64Sqrt", TypeKind::F64, |
| BLOCK(Value::fromF64(Math::sqrtDouble(operand.asF64()))), |
| BLOCK( |
| m_jit.sqrtDouble(operandLocation.asFPR(), resultLocation.asFPR()); |
| ) |
| ) |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF32Neg(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "F32Neg", TypeKind::F32, |
| BLOCK(Value::fromF32(-operand.asF32())), |
| BLOCK( |
| #if CPU(X86_64) |
| m_jit.moveFloatTo32(operandLocation.asFPR(), wasmScratchGPR); |
| m_jit.xor32(TrustedImm32(bitwise_cast<uint32_t>(static_cast<float>(-0.0))), wasmScratchGPR); |
| m_jit.move32ToFloat(wasmScratchGPR, resultLocation.asFPR()); |
| #else |
| m_jit.negateFloat(operandLocation.asFPR(), resultLocation.asFPR()); |
| #endif |
| ) |
| ) |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF64Neg(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "F64Neg", TypeKind::F64, |
| BLOCK(Value::fromF64(-operand.asF64())), |
| BLOCK( |
| #if CPU(X86_64) |
| m_jit.moveDoubleTo64(operandLocation.asFPR(), wasmScratchGPR); |
| m_jit.xor64(TrustedImm64(bitwise_cast<uint64_t>(static_cast<double>(-0.0))), wasmScratchGPR); |
| m_jit.move64ToDouble(wasmScratchGPR, resultLocation.asFPR()); |
| #else |
| m_jit.negateDouble(operandLocation.asFPR(), resultLocation.asFPR()); |
| #endif |
| ) |
| ) |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF32Nearest(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "F32Nearest", TypeKind::F32, |
| BLOCK(Value::fromF32(std::nearbyintf(operand.asF32()))), |
| BLOCK( |
| m_jit.roundTowardNearestIntFloat(operandLocation.asFPR(), resultLocation.asFPR()); |
| ) |
| ) |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF64Nearest(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "F64Nearest", TypeKind::F64, |
| BLOCK(Value::fromF64(std::nearbyint(operand.asF64()))), |
| BLOCK( |
| m_jit.roundTowardNearestIntDouble(operandLocation.asFPR(), resultLocation.asFPR()); |
| ) |
| ) |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF32Trunc(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "F32Trunc", TypeKind::F32, |
| BLOCK(Value::fromF32(Math::truncFloat(operand.asF32()))), |
| BLOCK( |
| m_jit.roundTowardZeroFloat(operandLocation.asFPR(), resultLocation.asFPR()); |
| ) |
| ) |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addF64Trunc(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "F64Trunc", TypeKind::F64, |
| BLOCK(Value::fromF64(Math::truncDouble(operand.asF64()))), |
| BLOCK( |
| m_jit.roundTowardZeroDouble(operandLocation.asFPR(), resultLocation.asFPR()); |
| ) |
| ) |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI32TruncSF32(Value operand, Value& result) |
| { |
| return truncTrapping(OpType::I32TruncSF32, operand, result, Types::I32, Types::F32); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI32TruncSF64(Value operand, Value& result) |
| { |
| return truncTrapping(OpType::I32TruncSF64, operand, result, Types::I32, Types::F64); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI32TruncUF32(Value operand, Value& result) |
| { |
| return truncTrapping(OpType::I32TruncUF32, operand, result, Types::I32, Types::F32); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI32TruncUF64(Value operand, Value& result) |
| { |
| return truncTrapping(OpType::I32TruncUF64, operand, result, Types::I32, Types::F64); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI64TruncSF32(Value operand, Value& result) |
| { |
| return truncTrapping(OpType::I64TruncSF32, operand, result, Types::I64, Types::F32); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI64TruncSF64(Value operand, Value& result) |
| { |
| return truncTrapping(OpType::I64TruncSF64, operand, result, Types::I64, Types::F64); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI64TruncUF32(Value operand, Value& result) |
| { |
| return truncTrapping(OpType::I64TruncUF32, operand, result, Types::I64, Types::F32); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addI64TruncUF64(Value operand, Value& result) |
| { |
| return truncTrapping(OpType::I64TruncUF64, operand, result, Types::I64, Types::F64); |
| } |
| |
| // References |
| |
| PartialResult WARN_UNUSED_RETURN addRefIsNull(Value operand, Value& result) |
| { |
| EMIT_UNARY( |
| "RefIsNull", TypeKind::I32, |
| BLOCK(Value::fromI32(operand.asRef() == JSValue::encode(jsNull()))), |
| BLOCK( |
| ASSERT(JSValue::encode(jsNull()) >= 0 && JSValue::encode(jsNull()) <= INT32_MAX); |
| m_jit.compare64(RelationalCondition::Equal, operandLocation.asGPR(), TrustedImm32(static_cast<int32_t>(JSValue::encode(jsNull()))), resultLocation.asGPR()); |
| ) |
| ); |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addRefAsNonNull(Value value, Value& result) |
| { |
| Location valueLocation; |
| if (value.isConst()) { |
| valueLocation = Location::fromGPR(wasmScratchGPR); |
| emitMoveConst(value, valueLocation); |
| } else |
| valueLocation = loadIfNecessary(value); |
| ASSERT(valueLocation.isGPR()); |
| consume(value); |
| |
| result = topValue(TypeKind::Ref); |
| Location resultLocation = allocate(result); |
| ASSERT(JSValue::encode(jsNull()) >= 0 && JSValue::encode(jsNull()) <= INT32_MAX); |
| throwExceptionIf(ExceptionType::NullRefAsNonNull, m_jit.branch64(RelationalCondition::Equal, valueLocation.asGPR(), TrustedImm32(static_cast<int32_t>(JSValue::encode(jsNull()))))); |
| m_jit.move(valueLocation.asGPR(), resultLocation.asGPR()); |
| |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addRefEq(Value ref0, Value ref1, Value& result) |
| { |
| return addI64Eq(ref0, ref1, result); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addRefFunc(uint32_t index, Value& result) |
| { |
| // FIXME: Emit this inline <https://bugs.webkit.org/show_bug.cgi?id=198506>. |
| TypeKind returnType = Options::useWebAssemblyTypedFunctionReferences() ? TypeKind::Ref : TypeKind::Funcref; |
| |
| Vector<Value, 8> arguments = { |
| instanceValue(), |
| Value::fromI32(index) |
| }; |
| emitCCall(&operationWasmRefFunc, arguments, returnType, result); |
| |
| return { }; |
| } |
| |
| #undef BLOCK |
| #undef EMIT_BINARY |
| #undef EMIT_UNARY |
| |
| void emitEntryTierUpCheck() |
| { |
| if (!m_tierUp) |
| return; |
| |
| static_assert(GPRInfo::nonPreservedNonArgumentGPR0 == wasmScratchGPR); |
| m_jit.move(TrustedImmPtr(bitwise_cast<uintptr_t>(&m_tierUp->m_counter)), wasmScratchGPR); |
| Jump tierUp = m_jit.branchAdd32(CCallHelpers::PositiveOrZero, TrustedImm32(TierUpCount::functionEntryIncrement()), Address(wasmScratchGPR)); |
| MacroAssembler::Label tierUpResume = m_jit.label(); |
| auto functionIndex = m_functionIndex; |
| addLatePath([tierUp, tierUpResume, functionIndex](BBQJIT& generator, CCallHelpers& jit) { |
| tierUp.link(&jit); |
| jit.move(TrustedImm32(functionIndex), GPRInfo::nonPreservedNonArgumentGPR0); |
| MacroAssembler::Call call = jit.nearCall(); |
| jit.jump(tierUpResume); |
| |
| bool usesSIMD = generator.m_usesSIMD; |
| jit.addLinkTask([=] (LinkBuffer& linkBuffer) { |
| MacroAssembler::repatchNearCall(linkBuffer.locationOfNearCall<NoPtrTag>(call), CodeLocationLabel<JITThunkPtrTag>(Thunks::singleton().stub(triggerOMGEntryTierUpThunkGenerator(usesSIMD)).code())); |
| }); |
| }); |
| } |
| |
| // Control flow |
| ControlData WARN_UNUSED_RETURN addTopLevel(BlockSignature signature) |
| { |
| if (UNLIKELY(Options::verboseBBQJITInstructions())) { |
| auto nameSection = m_info.nameSection; |
| auto functionIndexSpace = m_info.isImportedFunctionFromFunctionIndexSpace(m_functionIndex) ? m_functionIndex : m_functionIndex + m_info.importFunctionCount(); |
| std::pair<const Name*, RefPtr<NameSection>> name = nameSection->get(functionIndexSpace); |
| dataLog("BBQ\tFunction "); |
| if (name.first) |
| dataLog(makeString(*name.first)); |
| else |
| dataLog(m_functionIndex); |
| dataLogLn(" ", *m_functionSignature); |
| LOG_INDENT(); |
| } |
| |
| m_pcToCodeOriginMapBuilder.appendItem(m_jit.label(), PCToCodeOriginMapBuilder::defaultCodeOrigin()); |
| m_jit.emitFunctionPrologue(); |
| m_topLevel = ControlData(*this, BlockType::TopLevel, signature, 0); |
| |
| m_jit.move(CCallHelpers::TrustedImmPtr(CalleeBits::boxWasm(&m_callee)), wasmScratchGPR); |
| static_assert(CallFrameSlot::codeBlock + 1 == CallFrameSlot::callee); |
| if constexpr (is32Bit()) { |
| CCallHelpers::Address calleeSlot { GPRInfo::callFrameRegister, CallFrameSlot::callee * sizeof(Register) }; |
| m_jit.storePtr(wasmScratchGPR, calleeSlot.withOffset(PayloadOffset)); |
| m_jit.store32(CCallHelpers::TrustedImm32(JSValue::WasmTag), calleeSlot.withOffset(TagOffset)); |
| m_jit.storePtr(GPRInfo::wasmContextInstancePointer, CCallHelpers::addressFor(CallFrameSlot::codeBlock)); |
| } else |
| m_jit.storePairPtr(GPRInfo::wasmContextInstancePointer, wasmScratchGPR, GPRInfo::callFrameRegister, CCallHelpers::TrustedImm32(CallFrameSlot::codeBlock * sizeof(Register))); |
| |
| m_frameSizeLabels.append(m_jit.moveWithPatch(TrustedImmPtr(nullptr), wasmScratchGPR)); |
| |
| bool mayHaveExceptionHandlers = !m_hasExceptionHandlers || m_hasExceptionHandlers.value(); |
| if (mayHaveExceptionHandlers) |
| m_jit.store32(CCallHelpers::TrustedImm32(PatchpointExceptionHandle::s_invalidCallSiteIndex), CCallHelpers::tagFor(CallFrameSlot::argumentCountIncludingThis)); |
| |
| // Because we compile in a single pass, we always need to pessimistically check for stack underflow/overflow. |
| static_assert(wasmScratchGPR == GPRInfo::nonPreservedNonArgumentGPR0); |
| m_jit.subPtr(GPRInfo::callFrameRegister, wasmScratchGPR, wasmScratchGPR); |
| MacroAssembler::JumpList underflow; |
| underflow.append(m_jit.branchPtr(CCallHelpers::Above, wasmScratchGPR, GPRInfo::callFrameRegister)); |
| m_jit.addLinkTask([underflow] (LinkBuffer& linkBuffer) { |
| linkBuffer.link(underflow, CodeLocationLabel<JITThunkPtrTag>(Thunks::singleton().stub(throwStackOverflowFromWasmThunkGenerator).code())); |
| }); |
| |
| MacroAssembler::JumpList overflow; |
| m_jit.loadPtr(CCallHelpers::Address(GPRInfo::wasmContextInstancePointer, Instance::offsetOfVM()), GPRInfo::nonPreservedNonArgumentGPR1); |
| overflow.append(m_jit.branchPtr(CCallHelpers::Below, wasmScratchGPR, CCallHelpers::Address(GPRInfo::nonPreservedNonArgumentGPR1, VM::offsetOfSoftStackLimit()))); |
| m_jit.addLinkTask([overflow] (LinkBuffer& linkBuffer) { |
| linkBuffer.link(overflow, CodeLocationLabel<JITThunkPtrTag>(Thunks::singleton().stub(throwStackOverflowFromWasmThunkGenerator).code())); |
| }); |
| m_jit.move(wasmScratchGPR, MacroAssembler::stackPointerRegister); |
| |
| LocalOrTempIndex i = 0; |
| for (; i < m_arguments.size(); ++i) |
| flushValue(Value::fromLocal(m_parser->typeOfLocal(i).kind, i)); |
| |
| // Zero all locals that aren't initialized by arguments. |
| enum class ClearMode { Zero, JSNull }; |
| std::optional<int32_t> highest; |
| std::optional<int32_t> lowest; |
| auto flushZeroClear = [&]() { |
| if (!lowest) |
| return; |
| size_t size = highest.value() - lowest.value(); |
| int32_t pointer = lowest.value(); |
| |
| // Adjust pointer offset to be efficient for paired-stores. |
| if (pointer & 4 && size >= 4) { |
| m_jit.store32(TrustedImm32(0), Address(GPRInfo::callFrameRegister, pointer)); |
| pointer += 4; |
| size -= 4; |
| } |
| |
| #if CPU(ARM64) |
| if (pointer & 8 && size >= 8) { |
| m_jit.store64(TrustedImm64(0), Address(GPRInfo::callFrameRegister, pointer)); |
| pointer += 8; |
| size -= 8; |
| } |
| |
| unsigned count = size / 16; |
| for (unsigned i = 0; i < count; ++i) { |
| m_jit.storePair64(ARM64Registers::zr, ARM64Registers::zr, GPRInfo::callFrameRegister, TrustedImm32(pointer)); |
| pointer += 16; |
| size -= 16; |
| } |
| if (size & 8) { |
| m_jit.store64(TrustedImm64(0), Address(GPRInfo::callFrameRegister, pointer)); |
| pointer += 8; |
| size -= 8; |
| } |
| #else |
| unsigned count = size / 8; |
| for (unsigned i = 0; i < count; ++i) { |
| m_jit.store64(TrustedImm64(0), Address(GPRInfo::callFrameRegister, pointer)); |
| pointer += 8; |
| size -= 8; |
| } |
| #endif |
| |
| if (size & 4) { |
| m_jit.store32(TrustedImm32(0), Address(GPRInfo::callFrameRegister, pointer)); |
| pointer += 4; |
| size -= 4; |
| } |
| ASSERT(size == 0); |
| |
| highest = std::nullopt; |
| lowest = std::nullopt; |
| }; |
| |
| auto clear = [&](ClearMode mode, TypeKind type, Location location) { |
| if (mode == ClearMode::JSNull) { |
| flushZeroClear(); |
| emitStoreConst(Value::fromI64(bitwise_cast<uint64_t>(JSValue::encode(jsNull()))), location); |
| return; |
| } |
| if (!highest) |
| highest = location.asStackOffset() + sizeOfType(type); |
| lowest = location.asStackOffset(); |
| }; |
| |
| JIT_COMMENT(m_jit, "initialize locals"); |
| for (; i < m_locals.size(); ++i) { |
| TypeKind type = m_parser->typeOfLocal(i).kind; |
| switch (type) { |
| case TypeKind::I32: |
| case TypeKind::I31ref: |
| case TypeKind::F32: |
| case TypeKind::F64: |
| case TypeKind::I64: |
| case TypeKind::Struct: |
| case TypeKind::Rec: |
| case TypeKind::Func: |
| case TypeKind::Array: |
| case TypeKind::Sub: |
| case TypeKind::V128: |
| clear(ClearMode::Zero, type, m_locals[i]); |
| break; |
| case TypeKind::Externref: |
| case TypeKind::Funcref: |
| case TypeKind::Ref: |
| case TypeKind::RefNull: |
| case TypeKind::Structref: |
| case TypeKind::Arrayref: |
| case TypeKind::Eqref: |
| case TypeKind::Anyref: |
| case TypeKind::Nullref: |
| clear(ClearMode::JSNull, type, m_locals[i]); |
| break; |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| } |
| flushZeroClear(); |
| JIT_COMMENT(m_jit, "initialize locals done"); |
| |
| for (size_t i = 0; i < m_functionSignature->argumentCount(); i ++) |
| m_topLevel.touch(i); // Ensure arguments are flushed to persistent locations when this block ends. |
| |
| emitEntryTierUpCheck(); |
| |
| return m_topLevel; |
| } |
| |
| bool hasLoops() const |
| { |
| return m_compilation->bbqLoopEntrypoints.size(); |
| } |
| |
| MacroAssembler::Label addLoopOSREntrypoint() |
| { |
| // To handle OSR entry into loops, we emit a second entrypoint, which sets up our call frame then calls an |
| // operation to get the address of the loop we're trying to enter. Unlike the normal prologue, by the time |
| // we emit this entry point, we: |
| // - Know the frame size, so we don't need to patch the constant. |
| // - Can omit the entry tier-up check, since this entry point is only reached when we initially tier up into a loop. |
| // - Don't need to zero our locals, since they are restored from the OSR entry scratch buffer anyway. |
| auto label = m_jit.label(); |
| m_jit.emitFunctionPrologue(); |
| |
| m_jit.move(CCallHelpers::TrustedImmPtr(CalleeBits::boxWasm(&m_callee)), wasmScratchGPR); |
| static_assert(CallFrameSlot::codeBlock + 1 == CallFrameSlot::callee); |
| if constexpr (is32Bit()) { |
| CCallHelpers::Address calleeSlot { GPRInfo::callFrameRegister, CallFrameSlot::callee * sizeof(Register) }; |
| m_jit.storePtr(wasmScratchGPR, calleeSlot.withOffset(PayloadOffset)); |
| m_jit.store32(CCallHelpers::TrustedImm32(JSValue::WasmTag), calleeSlot.withOffset(TagOffset)); |
| m_jit.storePtr(GPRInfo::wasmContextInstancePointer, CCallHelpers::addressFor(CallFrameSlot::codeBlock)); |
| } else |
| m_jit.storePairPtr(GPRInfo::wasmContextInstancePointer, wasmScratchGPR, GPRInfo::callFrameRegister, CCallHelpers::TrustedImm32(CallFrameSlot::codeBlock * sizeof(Register))); |
| |
| int frameSize = m_frameSize + m_maxCalleeStackSize; |
| int roundedFrameSize = WTF::roundUpToMultipleOf(stackAlignmentBytes(), frameSize); |
| m_jit.subPtr(GPRInfo::callFrameRegister, TrustedImm32(roundedFrameSize), MacroAssembler::stackPointerRegister); |
| |
| MacroAssembler::JumpList underflow; |
| underflow.append(m_jit.branchPtr(CCallHelpers::Above, MacroAssembler::stackPointerRegister, GPRInfo::callFrameRegister)); |
| m_jit.addLinkTask([underflow] (LinkBuffer& linkBuffer) { |
| linkBuffer.link(underflow, CodeLocationLabel<JITThunkPtrTag>(Thunks::singleton().stub(throwStackOverflowFromWasmThunkGenerator).code())); |
| }); |
| |
| MacroAssembler::JumpList overflow; |
| m_jit.loadPtr(CCallHelpers::Address(GPRInfo::wasmContextInstancePointer, Instance::offsetOfVM()), GPRInfo::nonPreservedNonArgumentGPR0); |
| overflow.append(m_jit.branchPtr(CCallHelpers::Below, MacroAssembler::stackPointerRegister, CCallHelpers::Address(GPRInfo::nonPreservedNonArgumentGPR0, VM::offsetOfSoftStackLimit()))); |
| m_jit.addLinkTask([overflow] (LinkBuffer& linkBuffer) { |
| linkBuffer.link(overflow, CodeLocationLabel<JITThunkPtrTag>(Thunks::singleton().stub(throwStackOverflowFromWasmThunkGenerator).code())); |
| }); |
| |
| // This operation shuffles around values on the stack, until everything is in the right place. Then, |
| // it returns the address of the loop we're jumping to in wasmScratchGPR (so we don't interfere with |
| // anything we just loaded from the scratch buffer into a register) |
| m_jit.probe(tagCFunction<JITProbePtrTag>(operationWasmLoopOSREnterBBQJIT), m_tierUp, m_usesSIMD ? SavedFPWidth::SaveVectors : SavedFPWidth::DontSaveVectors); |
| |
| // We expect the loop address to be populated by the probe operation. |
| static_assert(wasmScratchGPR == GPRInfo::nonPreservedNonArgumentGPR0); |
| m_jit.farJump(wasmScratchGPR, WasmEntryPtrTag); |
| return label; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addBlock(BlockSignature signature, Stack& enclosingStack, ControlType& result, Stack& newStack) |
| { |
| result = ControlData(*this, BlockType::Block, signature, currentControlData().enclosedHeight() + currentControlData().implicitSlots() + enclosingStack.size() - signature->as<FunctionSignature>()->argumentCount()); |
| currentControlData().flushAndSingleExit(*this, result, enclosingStack, true, false); |
| |
| LOG_INSTRUCTION("Block", *signature); |
| LOG_INDENT(); |
| splitStack(signature, enclosingStack, newStack); |
| result.startBlock(*this, newStack); |
| return { }; |
| } |
| |
| B3::Type toB3Type(Type type) |
| { |
| return Wasm::toB3Type(type); |
| } |
| |
| B3::Type toB3Type(TypeKind kind) |
| { |
| switch (kind) { |
| case TypeKind::I31ref: |
| case TypeKind::I32: |
| return B3::Type(B3::Int32); |
| case TypeKind::I64: |
| return B3::Type(B3::Int64); |
| case TypeKind::Ref: |
| case TypeKind::RefNull: |
| case TypeKind::Structref: |
| case TypeKind::Arrayref: |
| case TypeKind::Funcref: |
| case TypeKind::Externref: |
| case TypeKind::Eqref: |
| case TypeKind::Anyref: |
| case TypeKind::Nullref: |
| return B3::Type(is32Bit() ? B3::Int32 : B3::Int64); |
| case TypeKind::F32: |
| return B3::Type(B3::Float); |
| case TypeKind::F64: |
| return B3::Type(B3::Double); |
| case TypeKind::V128: |
| return B3::Type(B3::V128); |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| return B3::Void; |
| } |
| } |
| |
| B3::ValueRep toB3Rep(Location location) |
| { |
| if (location.isRegister()) |
| return B3::ValueRep(location.isGPR() ? Reg(location.asGPR()) : Reg(location.asFPR())); |
| if (location.isStack()) |
| return B3::ValueRep(ValueLocation::stack(location.asStackOffset())); |
| |
| RELEASE_ASSERT_NOT_REACHED(); |
| return B3::ValueRep(); |
| } |
| |
| StackMap makeStackMap(const ControlData& data, Stack& enclosingStack) |
| { |
| unsigned numElements = m_locals.size() + data.enclosedHeight() + data.argumentLocations().size(); |
| |
| StackMap stackMap(numElements); |
| unsigned stackMapIndex = 0; |
| for (unsigned i = 0; i < m_locals.size(); i ++) |
| stackMap[stackMapIndex ++] = OSREntryValue(toB3Rep(m_locals[i]), toB3Type(m_localTypes[i])); |
| for (const ControlEntry& entry : m_parser->controlStack()) { |
| for (const TypedExpression& expr : entry.enclosedExpressionStack) |
| stackMap[stackMapIndex ++] = OSREntryValue(toB3Rep(locationOf(expr.value())), toB3Type(expr.type().kind)); |
| if (ControlData::isAnyCatch(entry.controlData)) { |
| for (unsigned i = 0; i < entry.controlData.implicitSlots(); i ++) { |
| Value exception = this->exception(entry.controlData); |
| stackMap[stackMapIndex ++] = OSREntryValue(toB3Rep(locationOf(exception)), B3::Int64); // Exceptions are EncodedJSValues, so they are always Int64 |
| } |
| } |
| } |
| for (const TypedExpression& expr : enclosingStack) |
| stackMap[stackMapIndex ++] = OSREntryValue(toB3Rep(locationOf(expr.value())), toB3Type(expr.type().kind)); |
| for (unsigned i = 0; i < data.argumentLocations().size(); i ++) |
| stackMap[stackMapIndex ++] = OSREntryValue(toB3Rep(data.argumentLocations()[i]), toB3Type(data.argumentType(i).kind)); |
| RELEASE_ASSERT(stackMapIndex == numElements); |
| |
| m_osrEntryScratchBufferSize = std::max(m_osrEntryScratchBufferSize, numElements + BBQCallee::extraOSRValuesForLoopIndex); |
| return stackMap; |
| } |
| |
| void emitLoopTierUpCheck(const ControlData& data, Stack& enclosingStack, unsigned loopIndex) |
| { |
| ASSERT(m_tierUp->osrEntryTriggers().size() == loopIndex); |
| m_tierUp->osrEntryTriggers().append(TierUpCount::TriggerReason::DontTrigger); |
| |
| unsigned outerLoops = m_outerLoops.isEmpty() ? UINT32_MAX : m_outerLoops.last(); |
| m_tierUp->outerLoops().append(outerLoops); |
| m_outerLoops.append(loopIndex); |
| |
| static_assert(GPRInfo::nonPreservedNonArgumentGPR0 == wasmScratchGPR); |
| m_jit.move(TrustedImm64(bitwise_cast<uintptr_t>(&m_tierUp->m_counter)), wasmScratchGPR); |
| |
| TierUpCount::TriggerReason* forceEntryTrigger = &(m_tierUp->osrEntryTriggers().last()); |
| static_assert(!static_cast<uint8_t>(TierUpCount::TriggerReason::DontTrigger), "the JIT code assumes non-zero means 'enter'"); |
| static_assert(sizeof(TierUpCount::TriggerReason) == 1, "branchTest8 assumes this size"); |
| |
| Jump forceOSREntry = m_jit.branchTest8(ResultCondition::NonZero, CCallHelpers::AbsoluteAddress(forceEntryTrigger)); |
| Jump tierUp = m_jit.branchAdd32(ResultCondition::PositiveOrZero, TrustedImm32(TierUpCount::loopIncrement()), CCallHelpers::Address(wasmScratchGPR)); |
| MacroAssembler::Label tierUpResume = m_jit.label(); |
| |
| OSREntryData& osrEntryData = m_tierUp->addOSREntryData(m_functionIndex, loopIndex, makeStackMap(data, enclosingStack)); |
| OSREntryData* osrEntryDataPtr = &osrEntryData; |
| |
| addLatePath([forceOSREntry, tierUp, tierUpResume, osrEntryDataPtr](BBQJIT& generator, CCallHelpers& jit) { |
| forceOSREntry.link(&jit); |
| tierUp.link(&jit); |
| |
| Probe::SavedFPWidth savedFPWidth = generator.m_usesSIMD ? Probe::SavedFPWidth::SaveVectors : Probe::SavedFPWidth::DontSaveVectors; // By the time we reach the late path, we should know whether or not the function uses SIMD. |
| jit.probe(tagCFunction<JITProbePtrTag>(operationWasmTriggerOSREntryNow), osrEntryDataPtr, savedFPWidth); |
| jit.branchTestPtr(CCallHelpers::Zero, GPRInfo::nonPreservedNonArgumentGPR0).linkTo(tierUpResume, &jit); |
| jit.farJump(GPRInfo::nonPreservedNonArgumentGPR0, WasmEntryPtrTag); |
| }); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addLoop(BlockSignature signature, Stack& enclosingStack, ControlType& result, Stack& newStack, uint32_t loopIndex) |
| { |
| result = ControlData(*this, BlockType::Loop, signature, currentControlData().enclosedHeight() + currentControlData().implicitSlots() + enclosingStack.size() - signature->as<FunctionSignature>()->argumentCount()); |
| currentControlData().flushAndSingleExit(*this, result, enclosingStack, true, false); |
| |
| LOG_INSTRUCTION("Loop", *signature); |
| LOG_INDENT(); |
| splitStack(signature, enclosingStack, newStack); |
| result.startBlock(*this, newStack); |
| result.setLoopLabel(m_jit.label()); |
| |
| RELEASE_ASSERT(m_compilation->bbqLoopEntrypoints.size() == loopIndex); |
| m_compilation->bbqLoopEntrypoints.append(result.loopLabel()); |
| |
| emitLoopTierUpCheck(result, enclosingStack, loopIndex); |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addIf(Value condition, BlockSignature signature, Stack& enclosingStack, ControlData& result, Stack& newStack) |
| { |
| Location conditionLocation = Location::fromGPR(wasmScratchGPR); |
| if (!condition.isConst()) |
| emitMove(condition, conditionLocation); |
| consume(condition); |
| |
| result = ControlData(*this, BlockType::If, signature, currentControlData().enclosedHeight() + currentControlData().implicitSlots() + enclosingStack.size() - signature->as<FunctionSignature>()->argumentCount()); |
| |
| // Despite being conditional, if doesn't need to worry about diverging expression stacks at block boundaries, so it doesn't need multiple exits. |
| currentControlData().flushAndSingleExit(*this, result, enclosingStack, true, false); |
| |
| LOG_INSTRUCTION("If", *signature, condition, conditionLocation); |
| LOG_INDENT(); |
| splitStack(signature, enclosingStack, newStack); |
| |
| result.startBlock(*this, newStack); |
| if (condition.isConst() && !condition.asI32()) |
| result.setIfBranch(m_jit.jump()); // Emit direct branch if we know the condition is false. |
| else if (!condition.isConst()) // Otherwise, we only emit a branch at all if we don't know the condition statically. |
| result.setIfBranch(m_jit.branchTest32(ResultCondition::Zero, wasmScratchGPR, wasmScratchGPR)); |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addElse(ControlData& data, Stack& expressionStack) |
| { |
| data.flushAndSingleExit(*this, data, expressionStack, false, true); |
| ControlData dataElse(*this, BlockType::Block, data.signature(), data.enclosedHeight()); |
| data.linkJumps(&m_jit); |
| dataElse.addBranch(m_jit.jump()); |
| data.linkIfBranch(&m_jit); // Link specifically the conditional branch of the preceding If |
| LOG_DEDENT(); |
| LOG_INSTRUCTION("Else"); |
| LOG_INDENT(); |
| |
| // We don't care at this point about the values live at the end of the previous control block, |
| // we just need the right number of temps for our arguments on the top of the stack. |
| expressionStack.clear(); |
| while (expressionStack.size() < data.signature()->as<FunctionSignature>()->argumentCount()) { |
| Type type = data.signature()->as<FunctionSignature>()->argumentType(expressionStack.size()); |
| expressionStack.constructAndAppend(type, Value::fromTemp(type.kind, dataElse.enclosedHeight() + dataElse.implicitSlots() + expressionStack.size())); |
| } |
| |
| dataElse.startBlock(*this, expressionStack); |
| data = dataElse; |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addElseToUnreachable(ControlData& data) |
| { |
| // We want to flush or consume all values on the stack to reset the allocator |
| // state entering the else block. |
| data.flushAtBlockBoundary(*this, 0, m_parser->expressionStack(), true); |
| |
| ControlData dataElse(*this, BlockType::Block, data.signature(), data.enclosedHeight()); |
| data.linkJumps(&m_jit); |
| dataElse.addBranch(m_jit.jump()); // Still needed even when the parent was unreachable to avoid running code within the else block. |
| data.linkIfBranch(&m_jit); // Link specifically the conditional branch of the preceding If |
| LOG_DEDENT(); |
| LOG_INSTRUCTION("Else"); |
| LOG_INDENT(); |
| |
| // We don't have easy access to the original expression stack we had entering the if block, |
| // so we construct a local stack just to set up temp bindings as we enter the else. |
| Stack expressionStack; |
| auto functionSignature = dataElse.signature()->as<FunctionSignature>(); |
| for (unsigned i = 0; i < functionSignature->argumentCount(); i ++) |
| expressionStack.constructAndAppend(functionSignature->argumentType(i), Value::fromTemp(functionSignature->argumentType(i).kind, dataElse.enclosedHeight() + dataElse.implicitSlots() + i)); |
| dataElse.startBlock(*this, expressionStack); |
| data = dataElse; |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addTry(BlockSignature signature, Stack& enclosingStack, ControlType& result, Stack& newStack) |
| { |
| m_usesExceptions = true; |
| ++m_tryCatchDepth; |
| ++m_callSiteIndex; |
| result = ControlData(*this, BlockType::Try, signature, currentControlData().enclosedHeight() + currentControlData().implicitSlots() + enclosingStack.size() - signature->as<FunctionSignature>()->argumentCount()); |
| result.setTryInfo(m_callSiteIndex, m_callSiteIndex, m_tryCatchDepth); |
| currentControlData().flushAndSingleExit(*this, result, enclosingStack, true, false); |
| |
| LOG_INSTRUCTION("Try", *signature); |
| LOG_INDENT(); |
| splitStack(signature, enclosingStack, newStack); |
| result.startBlock(*this, newStack); |
| return { }; |
| } |
| |
| void emitCatchPrologue() |
| { |
| m_frameSizeLabels.append(m_jit.moveWithPatch(TrustedImmPtr(nullptr), GPRInfo::nonPreservedNonArgumentGPR0)); |
| m_jit.subPtr(GPRInfo::callFrameRegister, GPRInfo::nonPreservedNonArgumentGPR0, MacroAssembler::stackPointerRegister); |
| if (!!m_info.memory) { |
| m_jit.loadPairPtr(GPRInfo::wasmContextInstancePointer, TrustedImm32(Instance::offsetOfCachedMemory()), GPRInfo::wasmBaseMemoryPointer, GPRInfo::wasmBoundsCheckingSizeRegister); |
| m_jit.cageConditionallyAndUntag(Gigacage::Primitive, GPRInfo::wasmBaseMemoryPointer, GPRInfo::wasmBoundsCheckingSizeRegister, wasmScratchGPR, /* validateAuth */ true, /* mayBeNull */ false); |
| } |
| static_assert(noOverlap(GPRInfo::nonPreservedNonArgumentGPR0, GPRInfo::returnValueGPR, GPRInfo::returnValueGPR2)); |
| } |
| |
| void emitCatchAllImpl(ControlData& dataCatch) |
| { |
| m_catchEntrypoints.append(m_jit.label()); |
| emitCatchPrologue(); |
| bind(this->exception(dataCatch), Location::fromGPR(GPRInfo::returnValueGPR)); |
| Stack emptyStack { }; |
| dataCatch.startBlock(*this, emptyStack); |
| } |
| |
| void emitCatchImpl(ControlData& dataCatch, const TypeDefinition& exceptionSignature, ResultList& results) |
| { |
| m_catchEntrypoints.append(m_jit.label()); |
| emitCatchPrologue(); |
| bind(this->exception(dataCatch), Location::fromGPR(GPRInfo::returnValueGPR)); |
| Stack emptyStack { }; |
| dataCatch.startBlock(*this, emptyStack); |
| |
| if (exceptionSignature.as<FunctionSignature>()->argumentCount()) { |
| ScratchScope<1, 0> scratches(*this); |
| GPRReg bufferGPR = scratches.gpr(0); |
| m_jit.loadPtr(Address(GPRInfo::returnValueGPR, JSWebAssemblyException::offsetOfPayload() + JSWebAssemblyException::Payload::offsetOfStorage()), bufferGPR); |
| for (unsigned i = 0; i < exceptionSignature.as<FunctionSignature>()->argumentCount(); ++i) { |
| Type type = exceptionSignature.as<FunctionSignature>()->argumentType(i); |
| Value result = Value::fromTemp(type.kind, dataCatch.enclosedHeight() + dataCatch.implicitSlots() + i); |
| Location slot = canonicalSlot(result); |
| switch (type.kind) { |
| case TypeKind::I32: |
| m_jit.load32(Address(bufferGPR, JSWebAssemblyException::Payload::Storage::offsetOfData() + i * sizeof(uint64_t)), wasmScratchGPR); |
| m_jit.store32(wasmScratchGPR, slot.asAddress()); |
| break; |
| case TypeKind::I31ref: |
| case TypeKind::I64: |
| case TypeKind::Ref: |
| case TypeKind::RefNull: |
| case TypeKind::Arrayref: |
| case TypeKind::Structref: |
| case TypeKind::Funcref: |
| case TypeKind::Externref: |
| case TypeKind::Eqref: |
| case TypeKind::Anyref: |
| case TypeKind::Nullref: |
| case TypeKind::Rec: |
| case TypeKind::Sub: |
| case TypeKind::Array: |
| case TypeKind::Struct: |
| case TypeKind::Func: { |
| m_jit.load64(Address(bufferGPR, JSWebAssemblyException::Payload::Storage::offsetOfData() + i * sizeof(uint64_t)), wasmScratchGPR); |
| m_jit.store64(wasmScratchGPR, slot.asAddress()); |
| break; |
| } |
| case TypeKind::F32: |
| m_jit.loadFloat(Address(bufferGPR, JSWebAssemblyException::Payload::Storage::offsetOfData() + i * sizeof(uint64_t)), wasmScratchFPR); |
| m_jit.storeFloat(wasmScratchFPR, slot.asAddress()); |
| break; |
| case TypeKind::F64: |
| m_jit.loadDouble(Address(bufferGPR, JSWebAssemblyException::Payload::Storage::offsetOfData() + i * sizeof(uint64_t)), wasmScratchFPR); |
| m_jit.storeDouble(wasmScratchFPR, slot.asAddress()); |
| break; |
| case TypeKind::V128: |
| materializeVectorConstant(v128_t { }, Location::fromFPR(wasmScratchFPR)); |
| m_jit.storeVector(wasmScratchFPR, slot.asAddress()); |
| break; |
| case TypeKind::Void: |
| RELEASE_ASSERT_NOT_REACHED(); |
| break; |
| } |
| bind(result, slot); |
| results.append(result); |
| } |
| } |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addCatch(unsigned exceptionIndex, const TypeDefinition& exceptionSignature, Stack& expressionStack, ControlType& data, ResultList& results) |
| { |
| m_usesExceptions = true; |
| data.flushAndSingleExit(*this, data, expressionStack, false, true); |
| ControlData dataCatch(*this, BlockType::Catch, data.signature(), data.enclosedHeight()); |
| dataCatch.setCatchKind(CatchKind::Catch); |
| if (ControlData::isTry(data)) { |
| ++m_callSiteIndex; |
| data.setTryInfo(data.tryStart(), m_callSiteIndex, data.tryCatchDepth()); |
| } |
| dataCatch.setTryInfo(data.tryStart(), data.tryEnd(), data.tryCatchDepth()); |
| |
| data.delegateJumpsTo(dataCatch); |
| dataCatch.addBranch(m_jit.jump()); |
| LOG_DEDENT(); |
| LOG_INSTRUCTION("Catch"); |
| LOG_INDENT(); |
| emitCatchImpl(dataCatch, exceptionSignature, results); |
| data = WTFMove(dataCatch); |
| m_exceptionHandlers.append({ HandlerType::Catch, data.tryStart(), data.tryEnd(), 0, m_tryCatchDepth, exceptionIndex }); |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addCatchToUnreachable(unsigned exceptionIndex, const TypeDefinition& exceptionSignature, ControlType& data, ResultList& results) |
| { |
| m_usesExceptions = true; |
| ControlData dataCatch(*this, BlockType::Catch, data.signature(), data.enclosedHeight()); |
| dataCatch.setCatchKind(CatchKind::Catch); |
| if (ControlData::isTry(data)) { |
| ++m_callSiteIndex; |
| data.setTryInfo(data.tryStart(), m_callSiteIndex, data.tryCatchDepth()); |
| } |
| dataCatch.setTryInfo(data.tryStart(), data.tryEnd(), data.tryCatchDepth()); |
| |
| data.delegateJumpsTo(dataCatch); |
| LOG_DEDENT(); |
| LOG_INSTRUCTION("Catch"); |
| LOG_INDENT(); |
| emitCatchImpl(dataCatch, exceptionSignature, results); |
| data = WTFMove(dataCatch); |
| m_exceptionHandlers.append({ HandlerType::Catch, data.tryStart(), data.tryEnd(), 0, m_tryCatchDepth, exceptionIndex }); |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addCatchAll(Stack& expressionStack, ControlType& data) |
| { |
| m_usesExceptions = true; |
| data.flushAndSingleExit(*this, data, expressionStack, false, true); |
| ControlData dataCatch(*this, BlockType::Catch, data.signature(), data.enclosedHeight()); |
| dataCatch.setCatchKind(CatchKind::CatchAll); |
| if (ControlData::isTry(data)) { |
| ++m_callSiteIndex; |
| data.setTryInfo(data.tryStart(), m_callSiteIndex, data.tryCatchDepth()); |
| } |
| dataCatch.setTryInfo(data.tryStart(), data.tryEnd(), data.tryCatchDepth()); |
| |
| data.delegateJumpsTo(dataCatch); |
| dataCatch.addBranch(m_jit.jump()); |
| LOG_DEDENT(); |
| LOG_INSTRUCTION("CatchAll"); |
| LOG_INDENT(); |
| emitCatchAllImpl(dataCatch); |
| data = WTFMove(dataCatch); |
| m_exceptionHandlers.append({ HandlerType::CatchAll, data.tryStart(), data.tryEnd(), 0, m_tryCatchDepth, 0 }); |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addCatchAllToUnreachable(ControlType& data) |
| { |
| m_usesExceptions = true; |
| ControlData dataCatch(*this, BlockType::Catch, data.signature(), data.enclosedHeight()); |
| dataCatch.setCatchKind(CatchKind::CatchAll); |
| if (ControlData::isTry(data)) { |
| ++m_callSiteIndex; |
| data.setTryInfo(data.tryStart(), m_callSiteIndex, data.tryCatchDepth()); |
| } |
| dataCatch.setTryInfo(data.tryStart(), data.tryEnd(), data.tryCatchDepth()); |
| |
| data.delegateJumpsTo(dataCatch); |
| LOG_DEDENT(); |
| LOG_INSTRUCTION("CatchAll"); |
| LOG_INDENT(); |
| emitCatchAllImpl(dataCatch); |
| data = WTFMove(dataCatch); |
| m_exceptionHandlers.append({ HandlerType::CatchAll, data.tryStart(), data.tryEnd(), 0, m_tryCatchDepth, 0 }); |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addDelegate(ControlType& target, ControlType& data) |
| { |
| return addDelegateToUnreachable(target, data); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addDelegateToUnreachable(ControlType& target, ControlType& data) |
| { |
| unsigned depth = 0; |
| if (ControlType::isTry(target)) |
| depth = target.tryCatchDepth(); |
| |
| if (ControlData::isTry(data)) { |
| ++m_callSiteIndex; |
| data.setTryInfo(data.tryStart(), m_callSiteIndex, data.tryCatchDepth()); |
| } |
| m_exceptionHandlers.append({ HandlerType::Delegate, data.tryStart(), m_callSiteIndex, 0, m_tryCatchDepth, depth }); |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addThrow(unsigned exceptionIndex, Vector<ExpressionType>& arguments, Stack&) |
| { |
| Checked<int32_t> calleeStackSize = WTF::roundUpToMultipleOf(stackAlignmentBytes(), arguments.size() * sizeof(uint64_t)); |
| m_maxCalleeStackSize = std::max<int>(calleeStackSize, m_maxCalleeStackSize); |
| |
| LOG_INSTRUCTION("Throw", arguments); |
| |
| for (unsigned i = 0; i < arguments.size(); i++) { |
| Location stackLocation = Location::fromStackArgument(i * sizeof(uint64_t)); |
| Value argument = arguments[i]; |
| if (argument.type() == TypeKind::V128) |
| emitMove(Value::fromF64(0), stackLocation); |
| else |
| emitMove(argument, stackLocation); |
| consume(argument); |
| } |
| |
| ++m_callSiteIndex; |
| bool mayHaveExceptionHandlers = !m_hasExceptionHandlers || m_hasExceptionHandlers.value(); |
| if (mayHaveExceptionHandlers) { |
| m_jit.store32(CCallHelpers::TrustedImm32(m_callSiteIndex), CCallHelpers::tagFor(CallFrameSlot::argumentCountIncludingThis)); |
| flushRegisters(); |
| } |
| m_jit.move(GPRInfo::wasmContextInstancePointer, GPRInfo::argumentGPR0); |
| emitThrowImpl(m_jit, exceptionIndex); |
| |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addRethrow(unsigned, ControlType& data) |
| { |
| LOG_INSTRUCTION("Rethrow", exception(data)); |
| |
| ++m_callSiteIndex; |
| bool mayHaveExceptionHandlers = !m_hasExceptionHandlers || m_hasExceptionHandlers.value(); |
| if (mayHaveExceptionHandlers) { |
| m_jit.store32(CCallHelpers::TrustedImm32(m_callSiteIndex), CCallHelpers::tagFor(CallFrameSlot::argumentCountIncludingThis)); |
| flushRegisters(); |
| } |
| emitMove(this->exception(data), Location::fromGPR(GPRInfo::argumentGPR1)); |
| m_jit.move(GPRInfo::wasmContextInstancePointer, GPRInfo::argumentGPR0); |
| emitRethrowImpl(m_jit); |
| return { }; |
| } |
| |
| void prepareForExceptions() |
| { |
| ++m_callSiteIndex; |
| bool mayHaveExceptionHandlers = !m_hasExceptionHandlers || m_hasExceptionHandlers.value(); |
| if (mayHaveExceptionHandlers) { |
| m_jit.store32(CCallHelpers::TrustedImm32(m_callSiteIndex), CCallHelpers::tagFor(CallFrameSlot::argumentCountIncludingThis)); |
| flushRegistersForException(); |
| } |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addReturn(const ControlData& data, const Stack& returnValues) |
| { |
| CallInformation wasmCallInfo = wasmCallingConvention().callInformationFor(*data.signature(), CallRole::Callee); |
| |
| if (!wasmCallInfo.results.isEmpty()) { |
| ASSERT(returnValues.size() >= wasmCallInfo.results.size()); |
| unsigned offset = returnValues.size() - wasmCallInfo.results.size(); |
| Vector<Value, 8> returnValuesForShuffle; |
| Vector<Location, 8> returnLocationsForShuffle; |
| for (unsigned i = 0; i < wasmCallInfo.results.size(); ++i) { |
| returnValuesForShuffle.append(returnValues[offset + i]); |
| returnLocationsForShuffle.append(Location::fromArgumentLocation(wasmCallInfo.results[i])); |
| } |
| emitShuffle(returnValuesForShuffle, returnLocationsForShuffle); |
| LOG_INSTRUCTION("Return", returnLocationsForShuffle); |
| } else |
| LOG_INSTRUCTION("Return"); |
| |
| for (const auto& value : returnValues) |
| consume(value); |
| |
| const ControlData& enclosingBlock = !m_parser->controlStack().size() ? data : currentControlData(); |
| for (LocalOrTempIndex localIndex : enclosingBlock.m_touchedLocals) { |
| Value local = Value::fromLocal(m_localTypes[localIndex], localIndex); |
| if (locationOf(local).isRegister()) { |
| // Flush all locals without emitting stores (since we're leaving anyway) |
| unbind(local, locationOf(local)); |
| bind(local, canonicalSlot(local)); |
| } |
| } |
| |
| m_jit.emitFunctionEpilogue(); |
| m_jit.ret(); |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addBranch(ControlData& target, Value condition, Stack& results) |
| { |
| if (condition.isConst() && !condition.asI32()) // If condition is known to be false, this is a no-op. |
| return { }; |
| |
| Location conditionLocation = Location::fromGPR(wasmScratchGPR); |
| if (!condition.isNone() && !condition.isConst()) |
| emitMove(condition, conditionLocation); |
| consume(condition); |
| |
| if (condition.isNone()) |
| LOG_INSTRUCTION("Branch"); |
| else |
| LOG_INSTRUCTION("Branch", condition, conditionLocation); |
| |
| if (condition.isConst() || condition.isNone()) { |
| currentControlData().flushAndSingleExit(*this, target, results, false, condition.isNone()); |
| target.addBranch(m_jit.jump()); // We know condition is true, since if it was false we would have returned early. |
| } else { |
| currentControlData().flushAtBlockBoundary(*this, 0, results, condition.isNone()); |
| Jump ifNotTaken = m_jit.branchTest32(ResultCondition::Zero, wasmScratchGPR); |
| currentControlData().addExit(*this, target.targetLocations(), results); |
| target.addBranch(m_jit.jump()); |
| ifNotTaken.link(&m_jit); |
| currentControlData().finalizeBlock(*this, target.targetLocations().size(), results, true); |
| } |
| |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addSwitch(Value condition, const Vector<ControlData*>& targets, ControlData& defaultTarget, Stack& results) |
| { |
| ASSERT(condition.type() == TypeKind::I32); |
| |
| LOG_INSTRUCTION("BrTable", condition); |
| |
| if (!condition.isConst()) |
| emitMove(condition, Location::fromGPR(wasmScratchGPR)); |
| consume(condition); |
| |
| if (condition.isConst()) { |
| // If we know the condition statically, we emit one direct branch to the known target. |
| int targetIndex = condition.asI32(); |
| if (targetIndex >= 0 && targetIndex < static_cast<int>(targets.size())) { |
| currentControlData().flushAndSingleExit(*this, *targets[targetIndex], results, false, true); |
| targets[targetIndex]->addBranch(m_jit.jump()); |
| } else { |
| currentControlData().flushAndSingleExit(*this, defaultTarget, results, false, true); |
| defaultTarget.addBranch(m_jit.jump()); |
| } |
| return { }; |
| } |
| |
| // Flush everything below the top N values. |
| currentControlData().flushAtBlockBoundary(*this, defaultTarget.targetLocations().size(), results, true); |
| |
| constexpr unsigned minCasesForTable = 7; |
| if (minCasesForTable <= targets.size()) { |
| Vector<Box<CCallHelpers::Label>> labels; |
| labels.reserveInitialCapacity(targets.size()); |
| auto* jumpTable = m_callee.addJumpTable(targets.size()); |
| auto fallThrough = m_jit.branch32(RelationalCondition::AboveOrEqual, wasmScratchGPR, TrustedImm32(targets.size())); |
| m_jit.zeroExtend32ToWord(wasmScratchGPR, wasmScratchGPR); |
| m_jit.lshiftPtr(TrustedImm32(3), wasmScratchGPR); |
| m_jit.addPtr(TrustedImmPtr(jumpTable->data()), wasmScratchGPR); |
| m_jit.farJump(Address(wasmScratchGPR), JSSwitchPtrTag); |
| |
| for (unsigned index = 0; index < targets.size(); ++index) { |
| Box<CCallHelpers::Label> label = Box<CCallHelpers::Label>::create(m_jit.label()); |
| labels.uncheckedAppend(label); |
| bool isCodeEmitted = currentControlData().addExit(*this, targets[index]->targetLocations(), results); |
| if (isCodeEmitted) |
| targets[index]->addBranch(m_jit.jump()); |
| else { |
| // It is common that we do not need to emit anything before jumping to the target block. |
| // In that case, we put Box<Label> which will be filled later when the end of the block is linked. |
| // We put direct jump to that block in the link task. |
| targets[index]->addLabel(WTFMove(label)); |
| } |
| } |
| |
| m_jit.addLinkTask([labels = WTFMove(labels), jumpTable](LinkBuffer& linkBuffer) { |
| for (unsigned index = 0; index < labels.size(); ++index) |
| jumpTable->at(index) = linkBuffer.locationOf<JSSwitchPtrTag>(*labels[index]); |
| }); |
| |
| fallThrough.link(&m_jit); |
| } else { |
| Vector<int64_t> cases; |
| cases.reserveInitialCapacity(targets.size()); |
| for (size_t i = 0; i < targets.size(); ++i) |
| cases.uncheckedAppend(i); |
| |
| BinarySwitch binarySwitch(wasmScratchGPR, cases, BinarySwitch::Int32); |
| while (binarySwitch.advance(m_jit)) { |
| unsigned value = binarySwitch.caseValue(); |
| unsigned index = binarySwitch.caseIndex(); |
| ASSERT_UNUSED(value, value == index); |
| ASSERT(index < targets.size()); |
| currentControlData().addExit(*this, targets[index]->targetLocations(), results); |
| targets[index]->addBranch(m_jit.jump()); |
| } |
| |
| binarySwitch.fallThrough().link(&m_jit); |
| } |
| currentControlData().addExit(*this, defaultTarget.targetLocations(), results); |
| defaultTarget.addBranch(m_jit.jump()); |
| |
| currentControlData().finalizeBlock(*this, defaultTarget.targetLocations().size(), results, false); |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN endBlock(ControlEntry& entry, Stack& stack) |
| { |
| return addEndToUnreachable(entry, stack, false); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addEndToUnreachable(ControlEntry& entry, Stack& stack, bool unreachable = true) |
| { |
| ControlData& entryData = entry.controlData; |
| |
| unsigned returnCount = entryData.signature()->as<FunctionSignature>()->returnCount(); |
| if (unreachable) { |
| for (unsigned i = 0; i < returnCount; ++i) { |
| Type type = entryData.signature()->as<FunctionSignature>()->returnType(i); |
| entry.enclosedExpressionStack.constructAndAppend(type, Value::fromTemp(type.kind, entryData.enclosedHeight() + entryData.implicitSlots() + i)); |
| } |
| for (const auto& binding : m_gprBindings) { |
| if (!binding.isNone()) |
| consume(binding.toValue()); |
| } |
| for (const auto& binding : m_fprBindings) { |
| if (!binding.isNone()) |
| consume(binding.toValue()); |
| } |
| } else { |
| unsigned offset = stack.size() - returnCount; |
| for (unsigned i = 0; i < returnCount; ++i) |
| entry.enclosedExpressionStack.append(stack[i + offset]); |
| } |
| |
| switch (entryData.blockType()) { |
| case BlockType::TopLevel: |
| entryData.flushAndSingleExit(*this, entryData, entry.enclosedExpressionStack, false, true, unreachable); |
| entryData.linkJumps(&m_jit); |
| for (unsigned i = 0; i < returnCount; ++i) { |
| // Make sure we expect the stack values in the correct locations. |
| if (!entry.enclosedExpressionStack[i].value().isConst()) { |
| Value& value = entry.enclosedExpressionStack[i].value(); |
| value = Value::fromTemp(value.type(), i); |
| Location valueLocation = locationOf(value); |
| if (valueLocation.isRegister()) |
| RELEASE_ASSERT(valueLocation == entryData.resultLocations()[i]); |
| else |
| bind(value, entryData.resultLocations()[i]); |
| } |
| } |
| return addReturn(entryData, entry.enclosedExpressionStack); |
| case BlockType::Loop: |
| entryData.convertLoopToBlock(); |
| entryData.flushAndSingleExit(*this, entryData, entry.enclosedExpressionStack, false, true, unreachable); |
| entryData.linkJumpsTo(entryData.loopLabel(), &m_jit); |
| m_outerLoops.takeLast(); |
| break; |
| case BlockType::Try: |
| case BlockType::Catch: |
| --m_tryCatchDepth; |
| entryData.flushAndSingleExit(*this, entryData, entry.enclosedExpressionStack, false, true, unreachable); |
| entryData.linkJumps(&m_jit); |
| break; |
| default: |
| entryData.flushAndSingleExit(*this, entryData, entry.enclosedExpressionStack, false, true, unreachable); |
| entryData.linkJumps(&m_jit); |
| break; |
| } |
| |
| LOG_DEDENT(); |
| LOG_INSTRUCTION("End"); |
| |
| currentControlData().resumeBlock(*this, entryData, entry.enclosedExpressionStack); |
| |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN endTopLevel(BlockSignature, const Stack&) |
| { |
| int frameSize = m_frameSize + m_maxCalleeStackSize; |
| CCallHelpers& jit = m_jit; |
| m_jit.addLinkTask([frameSize, labels = WTFMove(m_frameSizeLabels), &jit](LinkBuffer& linkBuffer) { |
| int roundedFrameSize = WTF::roundUpToMultipleOf(stackAlignmentBytes(), frameSize); |
| for (auto label : labels) |
| jit.repatchPointer(linkBuffer.locationOf<NoPtrTag>(label), bitwise_cast<void*>(static_cast<uintptr_t>(roundedFrameSize))); |
| }); |
| |
| LOG_DEDENT(); |
| LOG_INSTRUCTION("End"); |
| |
| if (UNLIKELY(m_disassembler)) |
| m_disassembler->setEndOfOpcode(m_jit.label()); |
| |
| for (const auto& latePath : m_latePaths) |
| latePath->run(*this, m_jit); |
| |
| for (unsigned i = 0; i < numberOfExceptionTypes; ++i) { |
| auto& jumps = m_exceptions[i]; |
| if (!jumps.empty()) { |
| jumps.link(&jit); |
| emitThrowException(static_cast<ExceptionType>(i)); |
| } |
| } |
| |
| m_compilation->osrEntryScratchBufferSize = m_osrEntryScratchBufferSize; |
| return { }; |
| } |
| |
| // Flush a value to its canonical slot. |
| void flushValue(Value value) |
| { |
| if (value.isConst() || value.isPinned()) |
| return; |
| Location currentLocation = locationOf(value); |
| Location slot = canonicalSlot(value); |
| |
| emitMove(value, slot); |
| unbind(value, currentLocation); |
| bind(value, slot); |
| } |
| |
| void restoreWebAssemblyContextInstance() |
| { |
| m_jit.loadPtr(Address(GPRInfo::callFrameRegister, CallFrameSlot::codeBlock * sizeof(Register)), GPRInfo::wasmContextInstancePointer); |
| } |
| |
| void restoreWebAssemblyGlobalState() |
| { |
| restoreWebAssemblyContextInstance(); |
| // FIXME: We should just store these registers on stack and load them. |
| if (!!m_info.memory) { |
| m_jit.loadPairPtr(GPRInfo::wasmContextInstancePointer, TrustedImm32(Instance::offsetOfCachedMemory()), GPRInfo::wasmBaseMemoryPointer, GPRInfo::wasmBoundsCheckingSizeRegister); |
| m_jit.cageConditionallyAndUntag(Gigacage::Primitive, GPRInfo::wasmBaseMemoryPointer, GPRInfo::wasmBoundsCheckingSizeRegister, wasmScratchGPR, /* validateAuth */ true, /* mayBeNull */ false); |
| } |
| } |
| |
| void flushRegistersForException() |
| { |
| // Flush all locals. |
| for (RegisterBinding& binding : m_gprBindings) { |
| if (binding.toValue().isLocal()) |
| flushValue(binding.toValue()); |
| } |
| |
| for (RegisterBinding& binding : m_fprBindings) { |
| if (binding.toValue().isLocal()) |
| flushValue(binding.toValue()); |
| } |
| } |
| |
| void flushRegisters() |
| { |
| // Just flush everything. |
| for (RegisterBinding& binding : m_gprBindings) { |
| if (!binding.toValue().isNone()) |
| flushValue(binding.toValue()); |
| } |
| |
| for (RegisterBinding& binding : m_fprBindings) { |
| if (!binding.toValue().isNone()) |
| flushValue(binding.toValue()); |
| } |
| } |
| |
| void saveValuesAcrossCall(const CallInformation& callInfo) |
| { |
| // Flush any values in caller-saved registers. |
| for (Reg reg : m_callerSaves) { |
| RegisterBinding binding = reg.isGPR() ? m_gprBindings[reg.gpr()] : m_fprBindings[reg.fpr()]; |
| if (!binding.toValue().isNone()) |
| flushValue(binding.toValue()); |
| } |
| |
| // Flush any values that overlap with parameter locations. |
| // FIXME: This is kind of a quick and dirty approach to what really should be a parallel move |
| // from argument to parameter locations, we may be able to avoid some stores. |
| for (size_t i = 0; i < callInfo.params.size(); ++i) { |
| Location paramLocation = Location::fromArgumentLocation(callInfo.params[i]); |
| if (paramLocation.isRegister()) { |
| RegisterBinding binding = paramLocation.isGPR() ? m_gprBindings[paramLocation.asGPR()] : m_fprBindings[paramLocation.asFPR()]; |
| if (!binding.toValue().isNone()) |
| flushValue(binding.toValue()); |
| } |
| } |
| } |
| |
| void restoreValuesAfterCall(const CallInformation& callInfo) |
| { |
| UNUSED_PARAM(callInfo); |
| // Caller-saved values shouldn't actually need to be restored here, the register allocator will restore them lazily |
| // whenever they are next used. |
| } |
| |
| template<size_t N> |
| void passParametersToCall(const Vector<Value, N>& arguments, const CallInformation& callInfo) |
| { |
| // Move arguments to parameter locations. |
| for (size_t i = 0; i < callInfo.params.size(); i ++) { |
| Location paramLocation = Location::fromArgumentLocation(callInfo.params[i]); |
| Value argument = arguments[i]; |
| emitMove(argument, paramLocation); |
| consume(argument); |
| } |
| } |
| |
| template<size_t N> |
| void returnValuesFromCall(Vector<Value, N>& results, const FunctionSignature& functionType, const CallInformation& callInfo) |
| { |
| for (size_t i = 0; i < callInfo.results.size(); i ++) { |
| Value result = Value::fromTemp(functionType.returnType(i).kind, currentControlData().enclosedHeight() + currentControlData().implicitSlots() + m_parser->expressionStack().size() + i); |
| Location returnLocation = Location::fromArgumentLocation(callInfo.results[i]); |
| if (returnLocation.isRegister()) { |
| RegisterBinding& currentBinding = returnLocation.isGPR() ? m_gprBindings[returnLocation.asGPR()] : m_fprBindings[returnLocation.asFPR()]; |
| if (currentBinding.isScratch()) { |
| // FIXME: This is a total hack and could cause problems. We assume scratch registers (allocated by a ScratchScope) |
| // will never be live across a call. So far, this is probably true, but it's fragile. Probably the fix here is to |
| // exclude all possible return value registers from ScratchScope so we can guarantee there's never any interference. |
| currentBinding = RegisterBinding::none(); |
| if (returnLocation.isGPR()) { |
| ASSERT(m_validGPRs.contains(returnLocation.asGPR(), IgnoreVectors)); |
| m_gprSet.add(returnLocation.asGPR(), IgnoreVectors); |
| } else { |
| ASSERT(m_validFPRs.contains(returnLocation.asFPR(), Width::Width128)); |
| m_fprSet.add(returnLocation.asFPR(), Width::Width128); |
| } |
| } |
| } |
| bind(result, returnLocation); |
| results.append(result); |
| } |
| } |
| |
| template<typename Func, size_t N> |
| void emitCCall(Func function, const Vector<Value, N>& arguments) |
| { |
| // Currently, we assume the Wasm calling convention is the same as the C calling convention |
| Vector<Type, 1> resultTypes; |
| Vector<Type> argumentTypes; |
| for (const Value& value : arguments) |
| argumentTypes.append(Type { value.type(), 0u }); |
| RefPtr<TypeDefinition> functionType = TypeInformation::typeDefinitionForFunction(resultTypes, argumentTypes); |
| CallInformation callInfo = wasmCallingConvention().callInformationFor(*functionType, CallRole::Caller); |
| Checked<int32_t> calleeStackSize = WTF::roundUpToMultipleOf(stackAlignmentBytes(), callInfo.headerAndArgumentStackSizeInBytes); |
| m_maxCalleeStackSize = std::max<int>(calleeStackSize, m_maxCalleeStackSize); |
| |
| // Prepare wasm operation calls. |
| m_jit.prepareWasmCallOperation(GPRInfo::wasmContextInstancePointer); |
| |
| // Preserve caller-saved registers and other info |
| prepareForExceptions(); |
| saveValuesAcrossCall(callInfo); |
| |
| // Move argument values to parameter locations |
| passParametersToCall(arguments, callInfo); |
| |
| // Materialize address of native function and call register |
| void* taggedFunctionPtr = tagCFunctionPtr<void*, OperationPtrTag>(function); |
| m_jit.move(TrustedImmPtr(bitwise_cast<uintptr_t>(taggedFunctionPtr)), wasmScratchGPR); |
| m_jit.call(wasmScratchGPR, OperationPtrTag); |
| } |
| |
| template<typename Func, size_t N> |
| void emitCCall(Func function, const Vector<Value, N>& arguments, TypeKind returnType, Value& result) |
| { |
| // Currently, we assume the Wasm calling convention is the same as the C calling convention |
| Vector<Type, 1> resultTypes = { Type { returnType, 0u } }; |
| Vector<Type> argumentTypes; |
| for (const Value& value : arguments) |
| argumentTypes.append(Type { value.type(), 0u }); |
| RefPtr<TypeDefinition> functionType = TypeInformation::typeDefinitionForFunction(resultTypes, argumentTypes); |
| CallInformation callInfo = wasmCallingConvention().callInformationFor(*functionType, CallRole::Caller); |
| Checked<int32_t> calleeStackSize = WTF::roundUpToMultipleOf(stackAlignmentBytes(), callInfo.headerAndArgumentStackSizeInBytes); |
| m_maxCalleeStackSize = std::max<int>(calleeStackSize, m_maxCalleeStackSize); |
| |
| // Prepare wasm operation calls. |
| m_jit.prepareWasmCallOperation(GPRInfo::wasmContextInstancePointer); |
| |
| // Preserve caller-saved registers and other info |
| prepareForExceptions(); |
| saveValuesAcrossCall(callInfo); |
| |
| // Move argument values to parameter locations |
| passParametersToCall(arguments, callInfo); |
| |
| // Materialize address of native function and call register |
| void* taggedFunctionPtr = tagCFunctionPtr<void*, OperationPtrTag>(function); |
| m_jit.move(TrustedImmPtr(bitwise_cast<uintptr_t>(taggedFunctionPtr)), wasmScratchGPR); |
| m_jit.call(wasmScratchGPR, OperationPtrTag); |
| |
| // FIXME: Probably we should make CCall more lower level, and we should bind the result to Value separately. |
| result = Value::fromTemp(returnType, currentControlData().enclosedHeight() + currentControlData().implicitSlots() + m_parser->expressionStack().size()); |
| Location resultLocation; |
| switch (returnType) { |
| case TypeKind::I32: |
| case TypeKind::I31ref: |
| case TypeKind::I64: |
| case TypeKind::Ref: |
| case TypeKind::RefNull: |
| case TypeKind::Arrayref: |
| case TypeKind::Structref: |
| case TypeKind::Funcref: |
| case TypeKind::Externref: |
| case TypeKind::Eqref: |
| case TypeKind::Anyref: |
| case TypeKind::Nullref: |
| case TypeKind::Rec: |
| case TypeKind::Sub: |
| case TypeKind::Array: |
| case TypeKind::Struct: |
| case TypeKind::Func: { |
| resultLocation = Location::fromGPR(GPRInfo::returnValueGPR); |
| ASSERT(m_validGPRs.contains(GPRInfo::returnValueGPR, IgnoreVectors)); |
| break; |
| } |
| case TypeKind::F32: |
| case TypeKind::F64: { |
| resultLocation = Location::fromFPR(FPRInfo::returnValueFPR); |
| ASSERT(m_validFPRs.contains(FPRInfo::returnValueFPR, Width::Width128)); |
| break; |
| } |
| case TypeKind::V128: { |
| resultLocation = Location::fromFPR(FPRInfo::returnValueFPR); |
| ASSERT(m_validFPRs.contains(FPRInfo::returnValueFPR, Width::Width128)); |
| break; |
| } |
| case TypeKind::Void: |
| RELEASE_ASSERT_NOT_REACHED(); |
| break; |
| } |
| |
| RegisterBinding& currentBinding = resultLocation.isGPR() ? m_gprBindings[resultLocation.asGPR()] : m_fprBindings[resultLocation.asFPR()]; |
| RELEASE_ASSERT(!currentBinding.isScratch()); |
| |
| bind(result, resultLocation); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addCall(unsigned functionIndex, const TypeDefinition& signature, Vector<Value>& arguments, ResultList& results, CallType callType = CallType::Call) |
| { |
| UNUSED_PARAM(callType); // TODO: handle tail calls |
| const FunctionSignature& functionType = *signature.as<FunctionSignature>(); |
| CallInformation callInfo = wasmCallingConvention().callInformationFor(signature, CallRole::Caller); |
| Checked<int32_t> calleeStackSize = WTF::roundUpToMultipleOf(stackAlignmentBytes(), callInfo.headerAndArgumentStackSizeInBytes); |
| m_maxCalleeStackSize = std::max<int>(calleeStackSize, m_maxCalleeStackSize); |
| |
| // Preserve caller-saved registers and other info |
| prepareForExceptions(); |
| saveValuesAcrossCall(callInfo); |
| |
| // Move argument values to parameter locations |
| passParametersToCall(arguments, callInfo); |
| |
| if (m_info.isImportedFunctionFromFunctionIndexSpace(functionIndex)) { |
| m_jit.move(TrustedImmPtr(Instance::offsetOfImportFunctionStub(functionIndex)), wasmScratchGPR); |
| m_jit.addPtr(GPRInfo::wasmContextInstancePointer, wasmScratchGPR); |
| m_jit.loadPtr(Address(wasmScratchGPR), wasmScratchGPR); |
| |
| m_jit.call(wasmScratchGPR, WasmEntryPtrTag); |
| } else { |
| // Emit the call. |
| Vector<UnlinkedWasmToWasmCall>* unlinkedWasmToWasmCalls = &m_unlinkedWasmToWasmCalls; |
| CCallHelpers::Call call = m_jit.threadSafePatchableNearCall(); |
| m_jit.addLinkTask([unlinkedWasmToWasmCalls, call, functionIndex] (LinkBuffer& linkBuffer) { |
| unlinkedWasmToWasmCalls->append({ linkBuffer.locationOfNearCall<WasmEntryPtrTag>(call), functionIndex }); |
| }); |
| } |
| |
| // Push return value(s) onto the expression stack |
| returnValuesFromCall(results, functionType, callInfo); |
| |
| if (m_info.callCanClobberInstance(functionIndex) || m_info.isImportedFunctionFromFunctionIndexSpace(functionIndex)) |
| restoreWebAssemblyGlobalState(); |
| |
| LOG_INSTRUCTION("Call", functionIndex, arguments, "=> ", results); |
| |
| return { }; |
| } |
| |
| void emitIndirectCall(const char* opcode, const Value& calleeIndex, GPRReg calleeInstance, GPRReg calleeCode, GPRReg jsCalleeAnchor, const TypeDefinition& signature, Vector<Value>& arguments, ResultList& results, CallType callType = CallType::Call) |
| { |
| // TODO: Support tail calls |
| UNUSED_PARAM(jsCalleeAnchor); |
| RELEASE_ASSERT(callType == CallType::Call); |
| |
| const auto& callingConvention = wasmCallingConvention(); |
| CallInformation wasmCalleeInfo = callingConvention.callInformationFor(signature, CallRole::Caller); |
| Checked<int32_t> calleeStackSize = WTF::roundUpToMultipleOf(stackAlignmentBytes(), wasmCalleeInfo.headerAndArgumentStackSizeInBytes); |
| m_maxCalleeStackSize = std::max<int>(calleeStackSize, m_maxCalleeStackSize); |
| |
| // Do a context switch if needed. |
| Jump isSameInstance = m_jit.branchPtr(RelationalCondition::Equal, calleeInstance, GPRInfo::wasmContextInstancePointer); |
| m_jit.move(calleeInstance, GPRInfo::wasmContextInstancePointer); |
| m_jit.loadPairPtr(GPRInfo::wasmContextInstancePointer, TrustedImm32(Instance::offsetOfCachedMemory()), GPRInfo::wasmBaseMemoryPointer, GPRInfo::wasmBoundsCheckingSizeRegister); |
| m_jit.cageConditionallyAndUntag(Gigacage::Primitive, GPRInfo::wasmBaseMemoryPointer, GPRInfo::wasmBoundsCheckingSizeRegister, wasmScratchGPR, /* validateAuth */ true, /* mayBeNull */ false); |
| isSameInstance.link(&m_jit); |
| |
| // Since this can switch instance, we need to keep JSWebAssemblyInstance anchored in the stack. |
| m_jit.storePtr(jsCalleeAnchor, Location::fromArgumentLocation(wasmCalleeInfo.thisArgument).asAddress()); |
| |
| // Safe to use across saveValues/passParameters since neither clobber the scratch GPR. |
| m_jit.loadPtr(Address(calleeCode), wasmScratchGPR); |
| prepareForExceptions(); |
| saveValuesAcrossCall(wasmCalleeInfo); |
| passParametersToCall(arguments, wasmCalleeInfo); |
| m_jit.call(wasmScratchGPR, WasmEntryPtrTag); |
| returnValuesFromCall(results, *signature.as<FunctionSignature>(), wasmCalleeInfo); |
| |
| // The call could have been to another WebAssembly instance, and / or could have modified our Memory. |
| restoreWebAssemblyGlobalState(); |
| |
| LOG_INSTRUCTION(opcode, calleeIndex, arguments, "=> ", results); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addCallIndirect(unsigned tableIndex, const TypeDefinition& originalSignature, Vector<Value>& args, ResultList& results, CallType callType = CallType::Call) |
| { |
| Value calleeIndex = args.takeLast(); |
| const TypeDefinition& signature = originalSignature.expand(); |
| ASSERT(signature.as<FunctionSignature>()->argumentCount() == args.size()); |
| ASSERT(m_info.tableCount() > tableIndex); |
| ASSERT(m_info.tables[tableIndex].type() == TableElementType::Funcref); |
| |
| Location calleeIndexLocation; |
| GPRReg calleeInstance, calleeCode, jsCalleeAnchor; |
| |
| { |
| ScratchScope<3, 0> scratches(*this); |
| |
| if (calleeIndex.isConst()) |
| emitMoveConst(calleeIndex, calleeIndexLocation = Location::fromGPR(scratches.gpr(2))); |
| else |
| calleeIndexLocation = loadIfNecessary(calleeIndex); |
| |
| GPRReg callableFunctionBufferLength = scratches.gpr(0); |
| GPRReg callableFunctionBuffer = scratches.gpr(1); |
| |
| ASSERT(tableIndex < m_info.tableCount()); |
| |
| int numImportFunctions = m_info.importFunctionCount(); |
| m_jit.loadPtr(Address(GPRInfo::wasmContextInstancePointer, Instance::offsetOfTablePtr(numImportFunctions, tableIndex)), callableFunctionBufferLength); |
| auto& tableInformation = m_info.table(tableIndex); |
| if (tableInformation.maximum() && tableInformation.maximum().value() == tableInformation.initial()) { |
| if (!tableInformation.isImport()) |
| m_jit.addPtr(TrustedImm32(FuncRefTable::offsetOfFunctionsForFixedSizedTable()), callableFunctionBufferLength, callableFunctionBuffer); |
| else |
| m_jit.loadPtr(Address(callableFunctionBufferLength, FuncRefTable::offsetOfFunctions()), callableFunctionBuffer); |
| m_jit.move(TrustedImm32(tableInformation.initial()), callableFunctionBufferLength); |
| } else { |
| m_jit.loadPtr(Address(callableFunctionBufferLength, FuncRefTable::offsetOfFunctions()), callableFunctionBuffer); |
| m_jit.load32(Address(callableFunctionBufferLength, FuncRefTable::offsetOfLength()), callableFunctionBufferLength); |
| } |
| ASSERT(calleeIndexLocation.isGPR()); |
| m_jit.zeroExtend32ToWord(calleeIndexLocation.asGPR(), calleeIndexLocation.asGPR()); |
| |
| consume(calleeIndex); |
| |
| // Check the index we are looking for is valid. |
| throwExceptionIf(ExceptionType::OutOfBoundsCallIndirect, m_jit.branch32(RelationalCondition::AboveOrEqual, calleeIndexLocation.asGPR(), callableFunctionBufferLength)); |
| |
| // Neither callableFunctionBuffer nor callableFunctionBufferLength are used before any of these |
| // are def'd below, so we can reuse the registers and save some pressure. |
| calleeCode = scratches.gpr(0); |
| calleeInstance = scratches.gpr(1); |
| jsCalleeAnchor = scratches.gpr(2); |
| |
| static_assert(sizeof(TypeIndex) == sizeof(void*)); |
| GPRReg calleeSignatureIndex = wasmScratchGPR; |
| |
| // Compute the offset in the table index space we are looking for. |
| m_jit.move(TrustedImmPtr(sizeof(FuncRefTable::Function)), calleeSignatureIndex); |
| m_jit.mul64(calleeIndexLocation.asGPR(), calleeSignatureIndex); |
| m_jit.addPtr(callableFunctionBuffer, calleeSignatureIndex); |
| |
| // FIXME: This seems wasteful to do two checks just for a nicer error message. |
| // We should move just to use a single branch and then figure out what |
| // error to use in the exception handler. |
| |
| m_jit.loadPtr(Address(calleeSignatureIndex, FuncRefTable::Function::offsetOfFunction() + WasmToWasmImportableFunction::offsetOfEntrypointLoadLocation()), calleeCode); // Pointer to callee code. |
| m_jit.loadPtr(Address(calleeSignatureIndex, FuncRefTable::Function::offsetOfInstance()), calleeInstance); |
| m_jit.loadPtr(Address(calleeSignatureIndex, FuncRefTable::Function::offsetOfValue()), jsCalleeAnchor); |
| m_jit.loadPtr(Address(calleeSignatureIndex, FuncRefTable::Function::offsetOfFunction() + WasmToWasmImportableFunction::offsetOfSignatureIndex()), calleeSignatureIndex); |
| |
| throwExceptionIf(ExceptionType::NullTableEntry, m_jit.branchTestPtr(ResultCondition::Zero, calleeSignatureIndex, calleeSignatureIndex)); |
| throwExceptionIf(ExceptionType::BadSignature, m_jit.branchPtr(RelationalCondition::NotEqual, calleeSignatureIndex, TrustedImmPtr(TypeInformation::get(originalSignature)))); |
| } |
| |
| emitIndirectCall("CallIndirect", calleeIndex, calleeInstance, calleeCode, jsCalleeAnchor, signature, args, results, callType); |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addCallRef(const TypeDefinition&, Vector<Value>&, ResultList&) BBQ_STUB |
| |
| PartialResult WARN_UNUSED_RETURN addUnreachable() |
| { |
| LOG_INSTRUCTION("Unreachable"); |
| emitThrowException(ExceptionType::Unreachable); |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addStructNew(uint32_t, Vector<Value>&, Value&) BBQ_STUB |
| PartialResult WARN_UNUSED_RETURN addStructGet(Value, const StructType&, uint32_t, Value&) BBQ_STUB |
| PartialResult WARN_UNUSED_RETURN addStructSet(Value, const StructType&, uint32_t, Value) BBQ_STUB |
| |
| PartialResult WARN_UNUSED_RETURN addCrash() |
| { |
| m_jit.breakpoint(); |
| return { }; |
| } |
| |
| ALWAYS_INLINE void willParseOpcode() |
| { |
| m_pcToCodeOriginMapBuilder.appendItem(m_jit.label(), CodeOrigin(BytecodeIndex(m_parser->currentOpcodeStartingOffset()))); |
| if (UNLIKELY(m_disassembler)) |
| m_disassembler->setOpcode(m_jit.label(), m_parser->currentOpcode(), m_parser->currentOpcodeStartingOffset()); |
| } |
| |
| ALWAYS_INLINE void didParseOpcode() |
| { |
| } |
| |
| // SIMD |
| |
| void notifyFunctionUsesSIMD() |
| { |
| m_usesSIMD = true; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addSIMDLoad(ExpressionType pointer, uint32_t uoffset, ExpressionType& result) |
| { |
| result = emitCheckAndPrepareAndMaterializePointerApply(pointer, uoffset, bytesForWidth(Width::Width128), [&](auto location) -> Value { |
| consume(pointer); |
| Value result = topValue(TypeKind::V128); |
| Location resultLocation = allocate(result); |
| m_jit.loadVector(location, resultLocation.asFPR()); |
| LOG_INSTRUCTION("V128Load", pointer, uoffset, RESULT(result)); |
| return result; |
| }); |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addSIMDStore(ExpressionType value, ExpressionType pointer, uint32_t uoffset) |
| { |
| emitCheckAndPrepareAndMaterializePointerApply(pointer, uoffset, bytesForWidth(Width::Width128), [&](auto location) -> void { |
| Location valueLocation = loadIfNecessary(value); |
| consume(pointer); |
| consume(value); |
| m_jit.storeVector(valueLocation.asFPR(), location); |
| LOG_INSTRUCTION("V128Store", pointer, uoffset, value, valueLocation); |
| }); |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addSIMDSplat(SIMDLane lane, ExpressionType value, ExpressionType& result) |
| { |
| Location valueLocation; |
| if (value.isConst()) { |
| if (value.isFloat()) { |
| ScratchScope<0, 1> scratches(*this); |
| valueLocation = Location::fromFPR(scratches.fpr(0)); |
| } else { |
| ScratchScope<1, 0> scratches(*this); |
| valueLocation = Location::fromGPR(scratches.gpr(0)); |
| } |
| emitMoveConst(value, valueLocation); |
| } else |
| valueLocation = loadIfNecessary(value); |
| consume(value); |
| |
| result = topValue(TypeKind::V128); |
| Location resultLocation = allocate(result); |
| if (valueLocation.isGPR()) |
| m_jit.vectorSplat(lane, valueLocation.asGPR(), resultLocation.asFPR()); |
| else |
| m_jit.vectorSplat(lane, valueLocation.asFPR(), resultLocation.asFPR()); |
| |
| LOG_INSTRUCTION("VectorSplat", lane, value, valueLocation, RESULT(result)); |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addSIMDShuffle(v128_t imm, ExpressionType a, ExpressionType b, ExpressionType& result) |
| { |
| #if CPU(X86_64) |
| ScratchScope<0, 1> scratches(*this); |
| #elif CPU(ARM64) |
| // We need these adjacent registers for the tbl instruction, so we clobber and preserve them in this scope here. |
| clobber(ARM64Registers::q28); |
| clobber(ARM64Registers::q29); |
| ScratchScope<0, 0> scratches(*this, Location::fromFPR(ARM64Registers::q28), Location::fromFPR(ARM64Registers::q29)); |
| #endif |
| Location aLocation = loadIfNecessary(a); |
| Location bLocation = loadIfNecessary(b); |
| consume(a); |
| consume(b); |
| |
| result = topValue(TypeKind::V128); |
| Location resultLocation = allocate(result); |
| |
| LOG_INSTRUCTION("VectorShuffle", a, aLocation, b, bLocation, RESULT(result)); |
| |
| if constexpr (isX86()) { |
| v128_t leftImm = imm; |
| v128_t rightImm = imm; |
| for (unsigned i = 0; i < 16; ++i) { |
| if (leftImm.u8x16[i] > 15) |
| leftImm.u8x16[i] = 0xFF; // Force OOB |
| if (rightImm.u8x16[i] < 16 || rightImm.u8x16[i] > 31) |
| rightImm.u8x16[i] = 0xFF; // Force OOB |
| } |
| // Store each byte (w/ index < 16) of `a` to result |
| // and zero clear each byte (w/ index > 15) in result. |
| materializeVectorConstant(leftImm, Location::fromFPR(scratches.fpr(0))); |
| m_jit.vectorSwizzle(aLocation.asFPR(), scratches.fpr(0), scratches.fpr(0)); |
| |
| // Store each byte (w/ index - 16 >= 0) of `b` to result2 |
| // and zero clear each byte (w/ index - 16 < 0) in result2. |
| materializeVectorConstant(rightImm, Location::fromFPR(wasmScratchFPR)); |
| m_jit.vectorSwizzle(bLocation.asFPR(), wasmScratchFPR, wasmScratchFPR); |
| m_jit.vectorOr(SIMDInfo { SIMDLane::v128, SIMDSignMode::None }, scratches.fpr(0), wasmScratchFPR, resultLocation.asFPR()); |
| return { }; |
| } |
| |
| #if CPU(ARM64) |
| materializeVectorConstant(imm, Location::fromFPR(wasmScratchFPR)); |
| if (unsigned(aLocation.asFPR()) + 1 != unsigned(bLocation.asFPR())) { |
| m_jit.moveVector(aLocation.asFPR(), ARM64Registers::q28); |
| m_jit.moveVector(bLocation.asFPR(), ARM64Registers::q29); |
| aLocation = Location::fromFPR(ARM64Registers::q28); |
| bLocation = Location::fromFPR(ARM64Registers::q29); |
| } |
| m_jit.vectorSwizzle2(aLocation.asFPR(), bLocation.asFPR(), wasmScratchFPR, resultLocation.asFPR()); |
| #else |
| UNREACHABLE_FOR_PLATFORM(); |
| #endif |
| |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addSIMDShift(SIMDLaneOperation op, SIMDInfo info, ExpressionType src, ExpressionType shift, ExpressionType& result) |
| { |
| #if CPU(X86_64) |
| // Clobber and preserve RCX on x86, since we need it to do shifts. |
| clobber(shiftRCX); |
| ScratchScope<2, 2> scratches(*this, Location::fromGPR(shiftRCX)); |
| #endif |
| Location srcLocation = loadIfNecessary(src); |
| Location shiftLocation; |
| if (shift.isConst()) { |
| shiftLocation = Location::fromGPR(wasmScratchGPR); |
| emitMoveConst(shift, shiftLocation); |
| } else |
| shiftLocation = loadIfNecessary(shift); |
| consume(src); |
| consume(shift); |
| |
| result = topValue(TypeKind::V128); |
| Location resultLocation = allocate(result); |
| |
| int32_t mask = (elementByteSize(info.lane) * CHAR_BIT) - 1; |
| |
| LOG_INSTRUCTION("Vector", op, src, srcLocation, shift, shiftLocation, RESULT(result)); |
| |
| #if CPU(ARM64) |
| m_jit.and32(Imm32(mask), shiftLocation.asGPR(), wasmScratchGPR); |
| if (op == SIMDLaneOperation::Shr) { |
| // ARM64 doesn't have a version of this instruction for right shift. Instead, if the input to |
| // left shift is negative, it's a right shift by the absolute value of that amount. |
| m_jit.neg32(wasmScratchGPR); |
| } |
| m_jit.vectorSplatInt8(wasmScratchGPR, wasmScratchFPR); |
| if (info.signMode == SIMDSignMode::Signed) |
| m_jit.vectorSshl(info, srcLocation.asFPR(), wasmScratchFPR, resultLocation.asFPR()); |
| else |
| m_jit.vectorUshl(info, srcLocation.asFPR(), wasmScratchFPR, resultLocation.asFPR()); |
| #else |
| ASSERT(isX86()); |
| m_jit.move(shiftLocation.asGPR(), wasmScratchGPR); |
| m_jit.and32(Imm32(mask), wasmScratchGPR); |
| |
| if (op == SIMDLaneOperation::Shr && info.signMode == SIMDSignMode::Signed && info.lane == SIMDLane::i64x2) { |
| // x86 has no SIMD 64-bit signed right shift instruction, so we scalarize it here. |
| m_jit.move(wasmScratchGPR, shiftRCX); |
| m_jit.vectorExtractLaneInt64(TrustedImm32(0), srcLocation.asFPR(), scratches.gpr(0)); |
| m_jit.vectorExtractLaneInt64(TrustedImm32(1), srcLocation.asFPR(), scratches.gpr(1)); |
| m_jit.rshift64(shiftRCX, scratches.gpr(0)); |
| m_jit.rshift64(shiftRCX, scratches.gpr(1)); |
| m_jit.vectorReplaceLaneInt64(TrustedImm32(0), scratches.gpr(0), resultLocation.asFPR()); |
| m_jit.vectorReplaceLaneInt64(TrustedImm32(1), scratches.gpr(1), resultLocation.asFPR()); |
| return { }; |
| } |
| |
| // Unlike ARM, x86 expects the shift provided as a *scalar*, stored in the lower 64 bits of a vector register. |
| // So, we don't need to splat the shift amount like we do on ARM. |
| m_jit.move64ToDouble(wasmScratchGPR, wasmScratchFPR); |
| |
| // 8-bit shifts are pretty involved to implement on Intel, so they get their own instruction type with extra temps. |
| if (op == SIMDLaneOperation::Shl && info.lane == SIMDLane::i8x16) { |
| m_jit.vectorUshl8(srcLocation.asFPR(), wasmScratchFPR, resultLocation.asFPR(), scratches.fpr(0), scratches.fpr(1)); |
| return { }; |
| } |
| if (op == SIMDLaneOperation::Shr && info.lane == SIMDLane::i8x16) { |
| if (info.signMode == SIMDSignMode::Signed) |
| m_jit.vectorSshr8(srcLocation.asFPR(), wasmScratchFPR, resultLocation.asFPR(), scratches.fpr(0), scratches.fpr(1)); |
| else |
| m_jit.vectorUshr8(srcLocation.asFPR(), wasmScratchFPR, resultLocation.asFPR(), scratches.fpr(0), scratches.fpr(1)); |
| return { }; |
| } |
| |
| if (op == SIMDLaneOperation::Shl) |
| m_jit.vectorUshl(info, srcLocation.asFPR(), wasmScratchFPR, resultLocation.asFPR()); |
| else if (info.signMode == SIMDSignMode::Signed) |
| m_jit.vectorSshr(info, srcLocation.asFPR(), wasmScratchFPR, resultLocation.asFPR()); |
| else |
| m_jit.vectorUshr(info, srcLocation.asFPR(), wasmScratchFPR, resultLocation.asFPR()); |
| #endif |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addSIMDExtmul(SIMDLaneOperation op, SIMDInfo info, ExpressionType left, ExpressionType right, ExpressionType& result) |
| { |
| ASSERT(info.signMode != SIMDSignMode::None); |
| |
| Location leftLocation = loadIfNecessary(left); |
| Location rightLocation = loadIfNecessary(right); |
| consume(left); |
| consume(right); |
| |
| result = topValue(TypeKind::V128); |
| Location resultLocation = allocate(result); |
| |
| LOG_INSTRUCTION("Vector", op, left, leftLocation, right, rightLocation, RESULT(result)); |
| |
| if (op == SIMDLaneOperation::ExtmulLow) { |
| m_jit.vectorExtendLow(info, leftLocation.asFPR(), wasmScratchFPR); |
| m_jit.vectorExtendLow(info, rightLocation.asFPR(), resultLocation.asFPR()); |
| } else { |
| ASSERT(op == SIMDLaneOperation::ExtmulHigh); |
| m_jit.vectorExtendHigh(info, leftLocation.asFPR(), wasmScratchFPR); |
| m_jit.vectorExtendHigh(info, rightLocation.asFPR(), resultLocation.asFPR()); |
| } |
| emitVectorMul(info, Location::fromFPR(wasmScratchFPR), resultLocation, resultLocation); |
| |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addSIMDLoadSplat(SIMDLaneOperation op, ExpressionType pointer, uint32_t uoffset, ExpressionType& result) |
| { |
| Location pointerLocation = emitCheckAndPreparePointer(pointer, uoffset, bytesForWidth(Width::Width128)); |
| Address address = materializePointer(pointerLocation, uoffset); |
| |
| result = topValue(TypeKind::V128); |
| Location resultLocation = allocate(result); |
| |
| LOG_INSTRUCTION("Vector", op, pointer, pointerLocation, uoffset, RESULT(result)); |
| |
| switch (op) { |
| #if CPU(X86_64) |
| case SIMDLaneOperation::LoadSplat8: |
| m_jit.vectorLoad8Splat(address, resultLocation.asFPR(), wasmScratchFPR); |
| break; |
| #else |
| case SIMDLaneOperation::LoadSplat8: |
| m_jit.vectorLoad8Splat(address, resultLocation.asFPR()); |
| break; |
| #endif |
| case SIMDLaneOperation::LoadSplat16: |
| m_jit.vectorLoad16Splat(address, resultLocation.asFPR()); |
| break; |
| case SIMDLaneOperation::LoadSplat32: |
| m_jit.vectorLoad32Splat(address, resultLocation.asFPR()); |
| break; |
| case SIMDLaneOperation::LoadSplat64: |
| m_jit.vectorLoad64Splat(address, resultLocation.asFPR()); |
| break; |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addSIMDLoadLane(SIMDLaneOperation op, ExpressionType pointer, ExpressionType vector, uint32_t uoffset, uint8_t lane, ExpressionType& result) |
| { |
| Location pointerLocation = emitCheckAndPreparePointer(pointer, uoffset, bytesForWidth(Width::Width128)); |
| Address address = materializePointer(pointerLocation, uoffset); |
| |
| Location vectorLocation = loadIfNecessary(vector); |
| consume(vector); |
| |
| result = topValue(TypeKind::V128); |
| Location resultLocation = allocate(result); |
| |
| LOG_INSTRUCTION("Vector", op, pointer, pointerLocation, uoffset, RESULT(result)); |
| |
| m_jit.moveVector(vectorLocation.asFPR(), resultLocation.asFPR()); |
| switch (op) { |
| case SIMDLaneOperation::LoadLane8: |
| m_jit.vectorLoad8Lane(address, TrustedImm32(lane), resultLocation.asFPR()); |
| break; |
| case SIMDLaneOperation::LoadLane16: |
| m_jit.vectorLoad16Lane(address, TrustedImm32(lane), resultLocation.asFPR()); |
| break; |
| case SIMDLaneOperation::LoadLane32: |
| m_jit.vectorLoad32Lane(address, TrustedImm32(lane), resultLocation.asFPR()); |
| break; |
| case SIMDLaneOperation::LoadLane64: |
| m_jit.vectorLoad64Lane(address, TrustedImm32(lane), resultLocation.asFPR()); |
| break; |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addSIMDStoreLane(SIMDLaneOperation op, ExpressionType pointer, ExpressionType vector, uint32_t uoffset, uint8_t lane) |
| { |
| Location pointerLocation = emitCheckAndPreparePointer(pointer, uoffset, bytesForWidth(Width::Width128)); |
| Address address = materializePointer(pointerLocation, uoffset); |
| |
| Location vectorLocation = loadIfNecessary(vector); |
| consume(vector); |
| |
| LOG_INSTRUCTION("Vector", op, vector, vectorLocation, pointer, pointerLocation, uoffset); |
| |
| switch (op) { |
| case SIMDLaneOperation::StoreLane8: |
| m_jit.vectorStore8Lane(vectorLocation.asFPR(), address, TrustedImm32(lane)); |
| break; |
| case SIMDLaneOperation::StoreLane16: |
| m_jit.vectorStore16Lane(vectorLocation.asFPR(), address, TrustedImm32(lane)); |
| break; |
| case SIMDLaneOperation::StoreLane32: |
| m_jit.vectorStore32Lane(vectorLocation.asFPR(), address, TrustedImm32(lane)); |
| break; |
| case SIMDLaneOperation::StoreLane64: |
| m_jit.vectorStore64Lane(vectorLocation.asFPR(), address, TrustedImm32(lane)); |
| break; |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addSIMDLoadExtend(SIMDLaneOperation op, ExpressionType pointer, uint32_t uoffset, ExpressionType& result) |
| { |
| SIMDLane lane; |
| SIMDSignMode signMode; |
| |
| switch (op) { |
| case SIMDLaneOperation::LoadExtend8U: |
| lane = SIMDLane::i16x8; |
| signMode = SIMDSignMode::Unsigned; |
| break; |
| case SIMDLaneOperation::LoadExtend8S: |
| lane = SIMDLane::i16x8; |
| signMode = SIMDSignMode::Signed; |
| break; |
| case SIMDLaneOperation::LoadExtend16U: |
| lane = SIMDLane::i32x4; |
| signMode = SIMDSignMode::Unsigned; |
| break; |
| case SIMDLaneOperation::LoadExtend16S: |
| lane = SIMDLane::i32x4; |
| signMode = SIMDSignMode::Signed; |
| break; |
| case SIMDLaneOperation::LoadExtend32U: |
| lane = SIMDLane::i64x2; |
| signMode = SIMDSignMode::Unsigned; |
| break; |
| case SIMDLaneOperation::LoadExtend32S: |
| lane = SIMDLane::i64x2; |
| signMode = SIMDSignMode::Signed; |
| break; |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| |
| result = emitCheckAndPrepareAndMaterializePointerApply(pointer, uoffset, sizeof(double), [&](auto location) -> Value { |
| consume(pointer); |
| Value result = topValue(TypeKind::V128); |
| Location resultLocation = allocate(result); |
| |
| LOG_INSTRUCTION("Vector", op, pointer, uoffset, RESULT(result)); |
| |
| m_jit.loadDouble(location, resultLocation.asFPR()); |
| m_jit.vectorExtendLow(SIMDInfo { lane, signMode }, resultLocation.asFPR(), resultLocation.asFPR()); |
| |
| return result; |
| }); |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addSIMDLoadPad(SIMDLaneOperation op, ExpressionType pointer, uint32_t uoffset, ExpressionType& result) |
| { |
| result = emitCheckAndPrepareAndMaterializePointerApply(pointer, uoffset, op == SIMDLaneOperation::LoadPad32 ? sizeof(float) : sizeof(double), [&](auto location) -> Value { |
| consume(pointer); |
| Value result = topValue(TypeKind::V128); |
| Location resultLocation = allocate(result); |
| |
| LOG_INSTRUCTION("Vector", op, pointer, uoffset, RESULT(result)); |
| |
| if (op == SIMDLaneOperation::LoadPad32) |
| m_jit.loadFloat(location, resultLocation.asFPR()); |
| else { |
| ASSERT(op == SIMDLaneOperation::LoadPad64); |
| m_jit.loadDouble(location, resultLocation.asFPR()); |
| } |
| return result; |
| }); |
| return { }; |
| } |
| |
| void materializeVectorConstant(v128_t value, Location result) |
| { |
| if (!value.u64x2[0] && !value.u64x2[1]) |
| m_jit.moveZeroToVector(result.asFPR()); |
| else if (value.u64x2[0] == 0xffffffffffffffffull && value.u64x2[1] == 0xffffffffffffffffull) |
| #if CPU(X86_64) |
| m_jit.compareIntegerVector(RelationalCondition::Equal, SIMDInfo { SIMDLane::i32x4, SIMDSignMode::Unsigned }, result.asFPR(), result.asFPR(), result.asFPR(), wasmScratchFPR); |
| #else |
| m_jit.compareIntegerVector(RelationalCondition::Equal, SIMDInfo { SIMDLane::i32x4, SIMDSignMode::Unsigned }, result.asFPR(), result.asFPR(), result.asFPR()); |
| #endif |
| else |
| m_jit.materializeVector(value, result.asFPR()); |
| } |
| |
| ExpressionType WARN_UNUSED_RETURN addConstant(v128_t value) |
| { |
| // We currently don't track constant Values for V128s, since folding them seems like a lot of work that might not be worth it. |
| // Maybe we can look into this eventually? |
| Value temp = topValue(TypeKind::V128); |
| Location tempLocation = allocate(temp); |
| materializeVectorConstant(value, tempLocation); |
| LOG_INSTRUCTION("V128Const", value, RESULT(temp)); |
| return temp; |
| } |
| |
| // SIMD generated |
| |
| PartialResult WARN_UNUSED_RETURN addExtractLane(SIMDInfo info, uint8_t lane, Value value, Value& result) |
| { |
| Location valueLocation = loadIfNecessary(value); |
| consume(value); |
| |
| result = topValue(simdScalarType(info.lane).kind); |
| Location resultLocation = allocate(result); |
| LOG_INSTRUCTION("VectorExtractLane", info.lane, lane, value, valueLocation, RESULT(result)); |
| |
| if (scalarTypeIsFloatingPoint(info.lane)) |
| m_jit.vectorExtractLane(info.lane, TrustedImm32(lane), valueLocation.asFPR(), resultLocation.asFPR()); |
| else |
| m_jit.vectorExtractLane(info.lane, info.signMode, TrustedImm32(lane), valueLocation.asFPR(), resultLocation.asGPR()); |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addReplaceLane(SIMDInfo info, uint8_t lane, ExpressionType vector, ExpressionType scalar, ExpressionType& result) |
| { |
| Location vectorLocation = loadIfNecessary(vector); |
| Location scalarLocation; |
| if (scalar.isConst()) { |
| scalarLocation = scalar.isFloat() ? Location::fromFPR(wasmScratchFPR) : Location::fromGPR(wasmScratchGPR); |
| emitMoveConst(scalar, scalarLocation); |
| } else |
| scalarLocation = loadIfNecessary(scalar); |
| consume(vector); |
| consume(scalar); |
| |
| result = topValue(TypeKind::V128); |
| Location resultLocation = allocate(result); |
| |
| if (scalarLocation == resultLocation) { |
| m_jit.moveVector(scalarLocation.asFPR(), wasmScratchFPR); |
| scalarLocation = Location::fromFPR(wasmScratchFPR); |
| } |
| |
| LOG_INSTRUCTION("VectorReplaceLane", info.lane, lane, vector, vectorLocation, scalar, scalarLocation, RESULT(result)); |
| |
| m_jit.moveVector(vectorLocation.asFPR(), resultLocation.asFPR()); |
| if (scalarLocation.isFPR()) |
| m_jit.vectorReplaceLane(info.lane, TrustedImm32(lane), scalarLocation.asFPR(), resultLocation.asFPR()); |
| else |
| m_jit.vectorReplaceLane(info.lane, TrustedImm32(lane), scalarLocation.asGPR(), resultLocation.asFPR()); |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addSIMDI_V(SIMDLaneOperation op, SIMDInfo info, ExpressionType value, ExpressionType& result) |
| { |
| Location valueLocation = loadIfNecessary(value); |
| consume(value); |
| |
| result = topValue(TypeKind::I32); |
| Location resultLocation = allocate(result); |
| |
| LOG_INSTRUCTION("Vector", op, value, valueLocation, RESULT(result)); |
| |
| switch (op) { |
| case SIMDLaneOperation::Bitmask: |
| #if CPU(ARM64) |
| if (info.lane == SIMDLane::i64x2) { |
| // This might look bad, but remember: every bit of information we destroy contributes to the heat death of the universe. |
| m_jit.vectorSshr8(SIMDInfo { SIMDLane::i64x2, SIMDSignMode::None }, valueLocation.asFPR(), TrustedImm32(63), wasmScratchFPR); |
| m_jit.vectorUnzipEven(SIMDInfo { SIMDLane::i8x16, SIMDSignMode::None }, wasmScratchFPR, wasmScratchFPR, wasmScratchFPR); |
| m_jit.moveDoubleTo64(wasmScratchFPR, wasmScratchGPR); |
| m_jit.rshift64(wasmScratchGPR, TrustedImm32(31), wasmScratchGPR); |
| m_jit.and32(Imm32(0b11), wasmScratchGPR, resultLocation.asGPR()); |
| return { }; |
| } |
| |
| { |
| v128_t towerOfPower { }; |
| switch (info.lane) { |
| case SIMDLane::i32x4: |
| for (unsigned i = 0; i < 4; ++i) |
| towerOfPower.u32x4[i] = 1 << i; |
| break; |
| case SIMDLane::i16x8: |
| for (unsigned i = 0; i < 8; ++i) |
| towerOfPower.u16x8[i] = 1 << i; |
| break; |
| case SIMDLane::i8x16: |
| for (unsigned i = 0; i < 8; ++i) |
| towerOfPower.u8x16[i] = 1 << i; |
| for (unsigned i = 0; i < 8; ++i) |
| towerOfPower.u8x16[i + 8] = 1 << i; |
| break; |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| |
| // FIXME: this is bad, we should load |
| materializeVectorConstant(towerOfPower, Location::fromFPR(wasmScratchFPR)); |
| } |
| |
| { |
| ScratchScope<0, 1> scratches(*this, valueLocation, resultLocation); |
| |
| m_jit.vectorSshr8(info, valueLocation.asFPR(), TrustedImm32(elementByteSize(info.lane) * 8 - 1), scratches.fpr(0)); |
| m_jit.vectorAnd(SIMDInfo { SIMDLane::v128, SIMDSignMode::None }, scratches.fpr(0), wasmScratchFPR, scratches.fpr(0)); |
| |
| if (info.lane == SIMDLane::i8x16) { |
| m_jit.vectorExtractPair(SIMDInfo { SIMDLane::i8x16, SIMDSignMode::None }, TrustedImm32(8), scratches.fpr(0), scratches.fpr(0), wasmScratchFPR); |
| m_jit.vectorZipUpper(SIMDInfo { SIMDLane::i8x16, SIMDSignMode::None }, scratches.fpr(0), wasmScratchFPR, scratches.fpr(0)); |
| info.lane = SIMDLane::i16x8; |
| } |
| |
| m_jit.vectorHorizontalAdd(info, scratches.fpr(0), scratches.fpr(0)); |
| m_jit.moveFloatTo32(scratches.fpr(0), resultLocation.asGPR()); |
| } |
| #else |
| ASSERT(isX86()); |
| m_jit.vectorBitmask(info, valueLocation.asFPR(), resultLocation.asGPR(), wasmScratchFPR); |
| #endif |
| return { }; |
| case JSC::SIMDLaneOperation::AnyTrue: |
| #if CPU(ARM64) |
| m_jit.vectorUnsignedMax(SIMDInfo { SIMDLane::i32x4, SIMDSignMode::None }, valueLocation.asFPR(), wasmScratchFPR); |
| m_jit.moveFloatTo32(wasmScratchFPR, resultLocation.asGPR()); |
| m_jit.test32(ResultCondition::NonZero, resultLocation.asGPR(), resultLocation.asGPR(), resultLocation.asGPR()); |
| #else |
| m_jit.vectorAnyTrue(valueLocation.asFPR(), resultLocation.asGPR()); |
| #endif |
| return { }; |
| case JSC::SIMDLaneOperation::AllTrue: |
| #if CPU(ARM64) |
| ASSERT(scalarTypeIsIntegral(info.lane)); |
| switch (info.lane) { |
| case SIMDLane::i64x2: |
| m_jit.compareIntegerVectorWithZero(RelationalCondition::NotEqual, info, valueLocation.asFPR(), wasmScratchFPR); |
| m_jit.vectorUnsignedMin(SIMDInfo { SIMDLane::i32x4, SIMDSignMode::None }, wasmScratchFPR, wasmScratchFPR); |
| break; |
| case SIMDLane::i32x4: |
| case SIMDLane::i16x8: |
| case SIMDLane::i8x16: |
| m_jit.vectorUnsignedMin(info, valueLocation.asFPR(), wasmScratchFPR); |
| break; |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| |
| m_jit.moveFloatTo32(wasmScratchFPR, wasmScratchGPR); |
| m_jit.test32(ResultCondition::NonZero, wasmScratchGPR, wasmScratchGPR, resultLocation.asGPR()); |
| #else |
| ASSERT(isX86()); |
| m_jit.vectorAllTrue(info, valueLocation.asFPR(), resultLocation.asGPR(), wasmScratchFPR); |
| #endif |
| return { }; |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| return { }; |
| } |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addSIMDV_V(SIMDLaneOperation op, SIMDInfo info, ExpressionType value, ExpressionType& result) |
| { |
| Location valueLocation = loadIfNecessary(value); |
| consume(value); |
| |
| result = topValue(TypeKind::V128); |
| Location resultLocation = allocate(result); |
| |
| LOG_INSTRUCTION("Vector", op, value, valueLocation, RESULT(result)); |
| |
| switch (op) { |
| case JSC::SIMDLaneOperation::Demote: |
| m_jit.vectorDemote(info, valueLocation.asFPR(), resultLocation.asFPR()); |
| return { }; |
| case JSC::SIMDLaneOperation::Promote: |
| m_jit.vectorPromote(info, valueLocation.asFPR(), resultLocation.asFPR()); |
| return { }; |
| case JSC::SIMDLaneOperation::Abs: |
| #if CPU(X86_64) |
| if (info.lane == SIMDLane::i64x2) { |
| m_jit.vectorAbsInt64(valueLocation.asFPR(), resultLocation.asFPR(), wasmScratchFPR); |
| return { }; |
| } |
| if (scalarTypeIsFloatingPoint(info.lane)) { |
| if (info.lane == SIMDLane::f32x4) { |
| m_jit.move32ToFloat(TrustedImm32(0x7fffffff), wasmScratchFPR); |
| m_jit.vectorSplatFloat32(wasmScratchFPR, wasmScratchFPR); |
| } else { |
| m_jit.move64ToDouble(TrustedImm64(0x7fffffffffffffffll), wasmScratchFPR); |
| m_jit.vectorSplatFloat64(wasmScratchFPR, wasmScratchFPR); |
| } |
| m_jit.vectorAnd(SIMDInfo { SIMDLane::v128, SIMDSignMode::None }, valueLocation.asFPR(), wasmScratchFPR, resultLocation.asFPR()); |
| return { }; |
| } |
| #endif |
| m_jit.vectorAbs(info, valueLocation.asFPR(), resultLocation.asFPR()); |
| return { }; |
| case JSC::SIMDLaneOperation::Popcnt: |
| #if CPU(X86_64) |
| { |
| ScratchScope<0, 1> scratches(*this, valueLocation, resultLocation); |
| ASSERT(info.lane == SIMDLane::i8x16); |
| |
| // x86_64 does not natively support vector lanewise popcount, so we emulate it using multiple |
| // masks. |
| |
| v128_t bottomNibbleConst; |
| v128_t popcntConst; |
| bottomNibbleConst.u64x2[0] = 0x0f0f0f0f0f0f0f0f; |
| bottomNibbleConst.u64x2[1] = 0x0f0f0f0f0f0f0f0f; |
| popcntConst.u64x2[0] = 0x0302020102010100; |
| popcntConst.u64x2[1] = 0x0403030203020201; |
| |
| materializeVectorConstant(bottomNibbleConst, Location::fromFPR(scratches.fpr(0))); |
| m_jit.vectorAndnot(SIMDInfo { SIMDLane::v128, SIMDSignMode::None }, valueLocation.asFPR(), scratches.fpr(0), wasmScratchFPR); |
| m_jit.vectorAnd(SIMDInfo { SIMDLane::v128, SIMDSignMode::None }, valueLocation.asFPR(), scratches.fpr(0), resultLocation.asFPR()); |
| m_jit.vectorUshr8(SIMDInfo { SIMDLane::i16x8, SIMDSignMode::None }, wasmScratchFPR, TrustedImm32(4), wasmScratchFPR); |
| |
| materializeVectorConstant(popcntConst, Location::fromFPR(scratches.fpr(0))); |
| m_jit.vectorSwizzle(scratches.fpr(0), resultLocation.asFPR(), resultLocation.asFPR()); |
| m_jit.vectorSwizzle(scratches.fpr(0), wasmScratchFPR, wasmScratchFPR); |
| m_jit.vectorAdd(SIMDInfo { SIMDLane::i8x16, SIMDSignMode::None }, resultLocation.asFPR(), wasmScratchFPR, resultLocation.asFPR()); |
| } |
| #else |
| m_jit.vectorPopcnt(info, valueLocation.asFPR(), resultLocation.asFPR()); |
| #endif |
| return { }; |
| case JSC::SIMDLaneOperation::Ceil: |
| m_jit.vectorCeil(info, valueLocation.asFPR(), resultLocation.asFPR()); |
| return { }; |
| case JSC::SIMDLaneOperation::Floor: |
| m_jit.vectorFloor(info, valueLocation.asFPR(), resultLocation.asFPR()); |
| return { }; |
| case JSC::SIMDLaneOperation::Trunc: |
| m_jit.vectorTrunc(info, valueLocation.asFPR(), resultLocation.asFPR()); |
| return { }; |
| case JSC::SIMDLaneOperation::Nearest: |
| m_jit.vectorNearest(info, valueLocation.asFPR(), resultLocation.asFPR()); |
| return { }; |
| case JSC::SIMDLaneOperation::Sqrt: |
| m_jit.vectorSqrt(info, valueLocation.asFPR(), resultLocation.asFPR()); |
| return { }; |
| case JSC::SIMDLaneOperation::ExtaddPairwise: |
| #if CPU(X86_64) |
| if (info.lane == SIMDLane::i16x8 && info.signMode == SIMDSignMode::Unsigned) { |
| m_jit.vectorExtaddPairwiseUnsignedInt16(valueLocation.asFPR(), resultLocation.asFPR(), wasmScratchFPR); |
| return { }; |
| } |
| m_jit.vectorExtaddPairwise(info, valueLocation.asFPR(), resultLocation.asFPR(), wasmScratchGPR, wasmScratchFPR); |
| #else |
| m_jit.vectorExtaddPairwise(info, valueLocation.asFPR(), resultLocation.asFPR()); |
| #endif |
| return { }; |
| case JSC::SIMDLaneOperation::Convert: |
| #if CPU(X86_64) |
| if (info.signMode == SIMDSignMode::Unsigned) { |
| m_jit.vectorConvertUnsigned(valueLocation.asFPR(), resultLocation.asFPR(), wasmScratchFPR); |
| return { }; |
| } |
| #endif |
| m_jit.vectorConvert(info, valueLocation.asFPR(), resultLocation.asFPR()); |
| return { }; |
| case JSC::SIMDLaneOperation::ConvertLow: |
| #if CPU(X86_64) |
| if (info.signMode == SIMDSignMode::Signed) |
| m_jit.vectorConvertLowSignedInt32(valueLocation.asFPR(), resultLocation.asFPR()); |
| else |
| m_jit.vectorConvertLowUnsignedInt32(valueLocation.asFPR(), resultLocation.asFPR(), wasmScratchGPR, wasmScratchFPR); |
| #else |
| m_jit.vectorConvertLow(info, valueLocation.asFPR(), resultLocation.asFPR()); |
| #endif |
| return { }; |
| case JSC::SIMDLaneOperation::ExtendHigh: |
| m_jit.vectorExtendHigh(info, valueLocation.asFPR(), resultLocation.asFPR()); |
| return { }; |
| case JSC::SIMDLaneOperation::ExtendLow: |
| m_jit.vectorExtendLow(info, valueLocation.asFPR(), resultLocation.asFPR()); |
| return { }; |
| case JSC::SIMDLaneOperation::TruncSat: |
| #if CPU(X86_64) |
| switch (info.lane) { |
| case SIMDLane::f64x2: |
| if (info.signMode == SIMDSignMode::Signed) |
| m_jit.vectorTruncSatSignedFloat64(valueLocation.asFPR(), resultLocation.asFPR(), wasmScratchGPR, wasmScratchFPR); |
| else |
| m_jit.vectorTruncSatUnsignedFloat64(valueLocation.asFPR(), resultLocation.asFPR(), wasmScratchGPR, wasmScratchFPR); |
| break; |
| case SIMDLane::f32x4: { |
| ScratchScope<0, 1> scratches(*this, valueLocation, resultLocation); |
| if (info.signMode == SIMDSignMode::Signed) |
| m_jit.vectorTruncSat(info, valueLocation.asFPR(), resultLocation.asFPR(), wasmScratchGPR, wasmScratchFPR, scratches.fpr(0)); |
| else |
| m_jit.vectorTruncSatUnsignedFloat32(valueLocation.asFPR(), resultLocation.asFPR(), wasmScratchGPR, wasmScratchFPR, scratches.fpr(0)); |
| break; |
| } |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| #else |
| m_jit.vectorTruncSat(info, valueLocation.asFPR(), resultLocation.asFPR()); |
| #endif |
| return { }; |
| case JSC::SIMDLaneOperation::Not: { |
| #if CPU(X86_64) |
| ScratchScope<0, 1> scratches(*this, valueLocation, resultLocation); |
| m_jit.compareIntegerVector(RelationalCondition::Equal, SIMDInfo { SIMDLane::i32x4, SIMDSignMode::None }, wasmScratchFPR, wasmScratchFPR, wasmScratchFPR, scratches.fpr(0)); |
| m_jit.vectorXor(info, valueLocation.asFPR(), wasmScratchFPR, resultLocation.asFPR()); |
| #else |
| m_jit.vectorNot(info, valueLocation.asFPR(), resultLocation.asFPR()); |
| #endif |
| return { }; |
| } |
| case JSC::SIMDLaneOperation::Neg: |
| #if CPU(X86_64) |
| switch (info.lane) { |
| case SIMDLane::i8x16: |
| case SIMDLane::i16x8: |
| case SIMDLane::i32x4: |
| case SIMDLane::i64x2: |
| // For integers, we can negate by subtracting our input from zero. |
| m_jit.moveZeroToVector(wasmScratchFPR); |
| m_jit.vectorSub(info, wasmScratchFPR, valueLocation.asFPR(), resultLocation.asFPR()); |
| break; |
| case SIMDLane::f32x4: |
| // For floats, we unfortunately have to flip the sign bit using XOR. |
| m_jit.move32ToFloat(TrustedImm32(-0x80000000), wasmScratchFPR); |
| m_jit.vectorSplatFloat32(wasmScratchFPR, wasmScratchFPR); |
| m_jit.vectorXor(SIMDInfo { SIMDLane::v128, SIMDSignMode::None }, valueLocation.asFPR(), wasmScratchFPR, resultLocation.asFPR()); |
| break; |
| case SIMDLane::f64x2: |
| m_jit.move64ToDouble(TrustedImm64(-0x8000000000000000ll), wasmScratchFPR); |
| m_jit.vectorSplatFloat64(wasmScratchFPR, wasmScratchFPR); |
| m_jit.vectorXor(SIMDInfo { SIMDLane::v128, SIMDSignMode::None }, valueLocation.asFPR(), wasmScratchFPR, resultLocation.asFPR()); |
| break; |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| #else |
| m_jit.vectorNeg(info, valueLocation.asFPR(), resultLocation.asFPR()); |
| #endif |
| return { }; |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| return { }; |
| } |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addSIMDBitwiseSelect(ExpressionType left, ExpressionType right, ExpressionType selector, ExpressionType& result) |
| { |
| Location leftLocation = loadIfNecessary(left); |
| Location rightLocation = loadIfNecessary(right); |
| Location selectorLocation = loadIfNecessary(selector); |
| consume(left); |
| consume(right); |
| consume(selector); |
| |
| result = topValue(TypeKind::V128); |
| Location resultLocation = allocate(result); |
| |
| LOG_INSTRUCTION("VectorBitwiseSelect", left, leftLocation, right, rightLocation, selector, selectorLocation, RESULT(result)); |
| |
| #if CPU(X86_64) |
| m_jit.vectorAnd(SIMDInfo { SIMDLane::v128, SIMDSignMode::None }, leftLocation.asFPR(), selectorLocation.asFPR(), wasmScratchFPR); |
| m_jit.vectorAndnot(SIMDInfo { SIMDLane::v128, SIMDSignMode::None }, rightLocation.asFPR(), selectorLocation.asFPR(), resultLocation.asFPR()); |
| m_jit.vectorOr(SIMDInfo { SIMDLane::v128, SIMDSignMode::None }, resultLocation.asFPR(), wasmScratchFPR, resultLocation.asFPR()); |
| #else |
| m_jit.moveVector(selectorLocation.asFPR(), wasmScratchFPR); |
| m_jit.vectorBitwiseSelect(leftLocation.asFPR(), rightLocation.asFPR(), wasmScratchFPR); |
| m_jit.moveVector(wasmScratchFPR, resultLocation.asFPR()); |
| #endif |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addSIMDRelOp(SIMDLaneOperation op, SIMDInfo info, ExpressionType left, ExpressionType right, B3::Air::Arg relOp, ExpressionType& result) |
| { |
| Location leftLocation = loadIfNecessary(left); |
| Location rightLocation = loadIfNecessary(right); |
| consume(left); |
| consume(right); |
| |
| result = topValue(TypeKind::V128); |
| Location resultLocation = allocate(result); |
| |
| LOG_INSTRUCTION("Vector", op, left, leftLocation, right, rightLocation, RESULT(result)); |
| |
| if (scalarTypeIsFloatingPoint(info.lane)) { |
| m_jit.compareFloatingPointVector(relOp.asDoubleCondition(), info, leftLocation.asFPR(), rightLocation.asFPR(), resultLocation.asFPR()); |
| return { }; |
| } |
| |
| #if CPU(X86_64) |
| // On Intel, the best codegen for a bitwise-complement of an integer vector is to |
| // XOR with a vector of all ones. This is necessary here since Intel also doesn't |
| // directly implement most relational conditions between vectors: the cases below |
| // are best emitted as inversions of conditions that are supported. |
| switch (relOp.asRelationalCondition()) { |
| case MacroAssembler::NotEqual: { |
| ScratchScope<0, 1> scratches(*this, leftLocation, rightLocation, resultLocation); |
| m_jit.compareIntegerVector(RelationalCondition::Equal, info, leftLocation.asFPR(), rightLocation.asFPR(), resultLocation.asFPR(), wasmScratchFPR); |
| m_jit.compareIntegerVector(RelationalCondition::Equal, SIMDInfo { SIMDLane::i32x4, SIMDSignMode::None }, wasmScratchFPR, wasmScratchFPR, wasmScratchFPR, scratches.fpr(0)); |
| m_jit.vectorXor(SIMDInfo { SIMDLane::v128, SIMDSignMode::None }, resultLocation.asFPR(), wasmScratchFPR, resultLocation.asFPR()); |
| break; |
| } |
| case MacroAssembler::Above: { |
| ScratchScope<0, 1> scratches(*this, leftLocation, rightLocation, resultLocation); |
| m_jit.compareIntegerVector(RelationalCondition::BelowOrEqual, info, leftLocation.asFPR(), rightLocation.asFPR(), resultLocation.asFPR(), wasmScratchFPR); |
| m_jit.compareIntegerVector(RelationalCondition::Equal, SIMDInfo { SIMDLane::i32x4, SIMDSignMode::None }, wasmScratchFPR, wasmScratchFPR, wasmScratchFPR, scratches.fpr(0)); |
| m_jit.vectorXor(SIMDInfo { SIMDLane::v128, SIMDSignMode::None }, resultLocation.asFPR(), wasmScratchFPR, resultLocation.asFPR()); |
| break; |
| } |
| case MacroAssembler::Below: { |
| ScratchScope<0, 1> scratches(*this, leftLocation, rightLocation, resultLocation); |
| m_jit.compareIntegerVector(RelationalCondition::AboveOrEqual, info, leftLocation.asFPR(), rightLocation.asFPR(), resultLocation.asFPR(), wasmScratchFPR); |
| m_jit.compareIntegerVector(RelationalCondition::Equal, SIMDInfo { SIMDLane::i32x4, SIMDSignMode::None }, wasmScratchFPR, wasmScratchFPR, wasmScratchFPR, scratches.fpr(0)); |
| m_jit.vectorXor(SIMDInfo { SIMDLane::v128, SIMDSignMode::None }, resultLocation.asFPR(), wasmScratchFPR, resultLocation.asFPR()); |
| break; |
| } |
| case MacroAssembler::GreaterThanOrEqual: |
| if (info.lane == SIMDLane::i64x2) { |
| // Note: rhs and lhs are reversed here, we are semantically negating LessThan. GreaterThan is |
| // just better supported on AVX. |
| ScratchScope<0, 1> scratches(*this, leftLocation, rightLocation, resultLocation); |
| m_jit.compareIntegerVector(RelationalCondition::GreaterThan, info, rightLocation.asFPR(), leftLocation.asFPR(), resultLocation.asFPR(), wasmScratchFPR); |
| m_jit.compareIntegerVector(RelationalCondition::Equal, SIMDInfo { SIMDLane::i32x4, SIMDSignMode::None }, wasmScratchFPR, wasmScratchFPR, wasmScratchFPR, scratches.fpr(0)); |
| m_jit.vectorXor(SIMDInfo { SIMDLane::v128, SIMDSignMode::None }, resultLocation.asFPR(), wasmScratchFPR, resultLocation.asFPR()); |
| } else |
| m_jit.compareIntegerVector(relOp.asRelationalCondition(), info, leftLocation.asFPR(), rightLocation.asFPR(), resultLocation.asFPR(), wasmScratchFPR); |
| break; |
| case MacroAssembler::LessThanOrEqual: |
| if (info.lane == SIMDLane::i64x2) { |
| ScratchScope<0, 1> scratches(*this, leftLocation, rightLocation, resultLocation); |
| m_jit.compareIntegerVector(RelationalCondition::GreaterThan, info, leftLocation.asFPR(), rightLocation.asFPR(), resultLocation.asFPR(), wasmScratchFPR); |
| m_jit.compareIntegerVector(RelationalCondition::Equal, SIMDInfo { SIMDLane::i32x4, SIMDSignMode::None }, wasmScratchFPR, wasmScratchFPR, wasmScratchFPR, scratches.fpr(0)); |
| m_jit.vectorXor(SIMDInfo { SIMDLane::v128, SIMDSignMode::None }, resultLocation.asFPR(), wasmScratchFPR, resultLocation.asFPR()); |
| } else |
| m_jit.compareIntegerVector(relOp.asRelationalCondition(), info, leftLocation.asFPR(), rightLocation.asFPR(), resultLocation.asFPR(), wasmScratchFPR); |
| break; |
| default: |
| m_jit.compareIntegerVector(relOp.asRelationalCondition(), info, leftLocation.asFPR(), rightLocation.asFPR(), resultLocation.asFPR(), wasmScratchFPR); |
| } |
| #else |
| m_jit.compareIntegerVector(relOp.asRelationalCondition(), info, leftLocation.asFPR(), rightLocation.asFPR(), resultLocation.asFPR()); |
| #endif |
| return { }; |
| } |
| |
| void emitVectorMul(SIMDInfo info, Location left, Location right, Location result) |
| { |
| if (info.lane == SIMDLane::i64x2) { |
| // Multiplication of 64-bit ints isn't natively supported on ARM or Intel (at least the ones we're targeting) |
| // so we scalarize it instead. |
| ScratchScope<1, 0> scratches(*this); |
| GPRReg dataScratchGPR = scratches.gpr(0); |
| m_jit.vectorExtractLaneInt64(TrustedImm32(0), left.asFPR(), wasmScratchGPR); |
| m_jit.vectorExtractLaneInt64(TrustedImm32(0), right.asFPR(), dataScratchGPR); |
| m_jit.mul64(wasmScratchGPR, dataScratchGPR, wasmScratchGPR); |
| m_jit.vectorReplaceLane(SIMDLane::i64x2, TrustedImm32(0), wasmScratchGPR, wasmScratchFPR); |
| m_jit.vectorExtractLaneInt64(TrustedImm32(1), left.asFPR(), wasmScratchGPR); |
| m_jit.vectorExtractLaneInt64(TrustedImm32(1), right.asFPR(), dataScratchGPR); |
| m_jit.mul64(wasmScratchGPR, dataScratchGPR, wasmScratchGPR); |
| m_jit.vectorReplaceLane(SIMDLane::i64x2, TrustedImm32(1), wasmScratchGPR, wasmScratchFPR); |
| m_jit.moveVector(wasmScratchFPR, result.asFPR()); |
| } else |
| m_jit.vectorMul(info, left.asFPR(), right.asFPR(), result.asFPR()); |
| } |
| |
| PartialResult WARN_UNUSED_RETURN fixupOutOfBoundsIndicesForSwizzle(Location a, Location b, Location result) |
| { |
| ASSERT(isX86()); |
| // Let each byte mask be 112 (0x70) then after VectorAddSat |
| // each index > 15 would set the saturated index's bit 7 to 1, |
| // whose corresponding byte will be zero cleared in VectorSwizzle. |
| // https://github.com/WebAssembly/simd/issues/93 |
| v128_t mask; |
| mask.u64x2[0] = 0x7070707070707070; |
| mask.u64x2[1] = 0x7070707070707070; |
| materializeVectorConstant(mask, Location::fromFPR(wasmScratchFPR)); |
| m_jit.vectorAddSat(SIMDInfo { SIMDLane::i8x16, SIMDSignMode::Unsigned }, wasmScratchFPR, b.asFPR(), wasmScratchFPR); |
| m_jit.vectorSwizzle(a.asFPR(), wasmScratchFPR, result.asFPR()); |
| return { }; |
| } |
| |
| PartialResult WARN_UNUSED_RETURN addSIMDV_VV(SIMDLaneOperation op, SIMDInfo info, ExpressionType left, ExpressionType right, ExpressionType& result) |
| { |
| Location leftLocation = loadIfNecessary(left); |
| Location rightLocation = loadIfNecessary(right); |
| consume(left); |
| consume(right); |
| |
| result = topValue(TypeKind::V128); |
| Location resultLocation = allocate(result); |
| |
| LOG_INSTRUCTION("Vector", op, left, leftLocation, right, rightLocation, RESULT(result)); |
| |
| switch (op) { |
| case SIMDLaneOperation::And: |
| m_jit.vectorAnd(info, leftLocation.asFPR(), rightLocation.asFPR(), resultLocation.asFPR()); |
| return { }; |
| case SIMDLaneOperation::Andnot: |
| m_jit.vectorAndnot(info, leftLocation.asFPR(), rightLocation.asFPR(), resultLocation.asFPR()); |
| return { }; |
| case SIMDLaneOperation::AvgRound: |
| m_jit.vectorAvgRound(info, leftLocation.asFPR(), rightLocation.asFPR(), resultLocation.asFPR()); |
| return { }; |
| case SIMDLaneOperation::DotProduct: |
| #if CPU(ARM64) |
| m_jit.vectorDotProduct(leftLocation.asFPR(), rightLocation.asFPR(), resultLocation.asFPR(), wasmScratchFPR); |
| #else |
| m_jit.vectorDotProduct(leftLocation.asFPR(), rightLocation.asFPR(), resultLocation.asFPR()); |
| #endif |
| return { }; |
| case SIMDLaneOperation::Add: |
| m_jit.vectorAdd(info, leftLocation.asFPR(), rightLocation.asFPR(), resultLocation.asFPR()); |
| return { }; |
| case SIMDLaneOperation::Mul: |
| emitVectorMul(info, leftLocation, rightLocation, resultLocation); |
| return { }; |
| case SIMDLaneOperation::MulSat: |
| #if CPU(X86_64) |
| m_jit.vectorMulSat(leftLocation.asFPR(), rightLocation.asFPR(), resultLocation.asFPR(), wasmScratchGPR, wasmScratchFPR); |
| #else |
| m_jit.vectorMulSat(leftLocation.asFPR(), rightLocation.asFPR(), resultLocation.asFPR()); |
| #endif |
| return { }; |
| case SIMDLaneOperation::Sub: |
| m_jit.vectorSub(info, leftLocation.asFPR(), rightLocation.asFPR(), resultLocation.asFPR()); |
| return { }; |
| case SIMDLaneOperation::Div: |
| m_jit.vectorDiv(info, leftLocation.asFPR(), rightLocation.asFPR(), resultLocation.asFPR()); |
| return { }; |
| case SIMDLaneOperation::Pmax: |
| #if CPU(ARM64) |
| m_jit.vectorPmax(info, leftLocation.asFPR(), rightLocation.asFPR(), resultLocation.asFPR(), wasmScratchFPR); |
| #else |
| m_jit.vectorPmax(info, leftLocation.asFPR(), rightLocation.asFPR(), resultLocation.asFPR()); |
| #endif |
| return { }; |
| case SIMDLaneOperation::Pmin: |
| #if CPU(ARM64) |
| m_jit.vectorPmin(info, leftLocation.asFPR(), rightLocation.asFPR(), resultLocation.asFPR(), wasmScratchFPR); |
| #else |
| m_jit.vectorPmin(info, leftLocation.asFPR(), rightLocation.asFPR(), resultLocation.asFPR()); |
| #endif |
| return { }; |
| case SIMDLaneOperation::Or: |
| m_jit.vectorOr(info, leftLocation.asFPR(), rightLocation.asFPR(), resultLocation.asFPR()); |
| return { }; |
| case SIMDLaneOperation::Swizzle: |
| if constexpr (isX86()) |
| return fixupOutOfBoundsIndicesForSwizzle(leftLocation, rightLocation, resultLocation); |
| m_jit.vectorSwizzle(leftLocation.asFPR(), rightLocation.asFPR(), resultLocation.asFPR()); |
| return { }; |
| case SIMDLaneOperation::Xor: |
| m_jit.vectorXor(info, leftLocation.asFPR(), rightLocation.asFPR(), resultLocation.asFPR()); |
| return { }; |
| case SIMDLaneOperation::Narrow: |
| m_jit.vectorNarrow(info, leftLocation.asFPR(), rightLocation.asFPR(), resultLocation.asFPR(), wasmScratchFPR); |
| return { }; |
| case SIMDLaneOperation::AddSat: |
| m_jit.vectorAddSat(info, leftLocation.asFPR(), rightLocation.asFPR(), resultLocation.asFPR()); |
| return { }; |
| case SIMDLaneOperation::SubSat: |
| m_jit.vectorSubSat(info, leftLocation.asFPR(), rightLocation.asFPR(), resultLocation.asFPR()); |
| return { }; |
| case SIMDLaneOperation::Max: |
| #if CPU(X86_64) |
| if (scalarTypeIsFloatingPoint(info.lane)) { |
| // Intel's vectorized maximum instruction has slightly different semantics to the WebAssembly vectorized |
| // minimum instruction, namely in terms of signed zero values and propagating NaNs. VectorPmax implements |
| // a fast version of this instruction that compiles down to a single op, without conforming to the exact |
| // semantics. In order to precisely implement VectorMax, we need to do extra work on Intel to check for |
| // the necessary edge cases. |
| |
| // Compute result in both directions. |
| m_jit.vectorPmax(info, rightLocation.asFPR(), leftLocation.asFPR(), wasmScratchFPR); |
| m_jit.vectorPmax(info, leftLocation.asFPR(), rightLocation.asFPR(), resultLocation.asFPR()); |
| |
| // Check for discrepancies by XORing the two results together. |
| m_jit.vectorXor(SIMDInfo { SIMDLane::v128, SIMDSignMode::None }, wasmScratchFPR, resultLocation.asFPR(), resultLocation.asFPR()); |
| |
| // OR results, propagating the sign bit for negative zeroes, and NaNs. |
| m_jit.vectorOr(SIMDInfo { SIMDLane::v128, SIMDSignMode::None }, wasmScratchFPR, resultLocation.asFPR(), wasmScratchFPR); |
| |
| // Propagate discrepancies in the sign bit. |
| m_jit.vectorSub(info, wasmScratchFPR, resultLocation.asFPR(), wasmScratchFPR); |
| |
| // Canonicalize NaNs by checking for unordered values and clearing payload if necessary. |
| m_jit.compareFloatingPointVectorUnordered(info, resultLocation.asFPR(), wasmScratchFPR, resultLocation.asFPR()); |
| m_jit.vectorUshr8(SIMDInfo { info.lane == SIMDLane::f32x4 ? SIMDLane::i32x4 : SIMDLane::i64x2, SIMDSignMode::None }, resultLocation.asFPR(), TrustedImm32(info.lane == SIMDLane::f32x4 ? 10 : 13), resultLocation.asFPR()); |
| m_jit.vectorAndnot(SIMDInfo { SIMDLane::v128, SIMDSignMode::None }, wasmScratchFPR, resultLocation.asFPR(), resultLocation.asFPR()); |
| } else |
| m_jit.vectorMax(info, leftLocation.asFPR(), rightLocation.asFPR(), resultLocation.asFPR()); |
| #else |
| m_jit.vectorMax(info, leftLocation.asFPR(), rightLocation.asFPR(), resultLocation.asFPR()); |
| #endif |
| return { }; |
| case SIMDLaneOperation::Min: |
| #if CPU(X86_64) |
| if (scalarTypeIsFloatingPoint(info.lane)) { |
| // Intel's vectorized minimum instruction has slightly different semantics to the WebAssembly vectorized |
| // minimum instruction, namely in terms of signed zero values and propagating NaNs. VectorPmin implements |
| // a fast version of this instruction that compiles down to a single op, without conforming to the exact |
| // semantics. In order to precisely implement VectorMin, we need to do extra work on Intel to check for |
| // the necessary edge cases. |
| |
| // Compute result in both directions. |
| m_jit.vectorPmin(info, rightLocation.asFPR(), leftLocation.asFPR(), wasmScratchFPR); |
| m_jit.vectorPmin(info, leftLocation.asFPR(), rightLocation.asFPR(), resultLocation.asFPR()); |
| |
| // OR results, propagating the sign bit for negative zeroes, and NaNs. |
| m_jit.vectorOr(SIMDInfo { SIMDLane::v128, SIMDSignMode::None }, wasmScratchFPR, resultLocation.asFPR(), wasmScratchFPR); |
| |
| // Canonicalize NaNs by checking for unordered values and clearing payload if necessary. |
| m_jit.compareFloatingPointVectorUnordered(info, resultLocation.asFPR(), wasmScratchFPR, resultLocation.asFPR()); |
| m_jit.vectorOr(SIMDInfo { SIMDLane::v128, SIMDSignMode::None }, wasmScratchFPR, resultLocation.asFPR(), wasmScratchFPR); |
| m_jit.vectorUshr8(SIMDInfo { info.lane == SIMDLane::f32x4 ? SIMDLane::i32x4 : SIMDLane::i64x2, SIMDSignMode::None }, resultLocation.asFPR(), TrustedImm32(info.lane == SIMDLane::f32x4 ? 10 : 13), resultLocation.asFPR()); |
| m_jit.vectorAndnot(SIMDInfo { SIMDLane::v128, SIMDSignMode::None }, wasmScratchFPR, resultLocation.asFPR(), resultLocation.asFPR()); |
| } else |
| m_jit.vectorMin(info, leftLocation.asFPR(), rightLocation.asFPR(), resultLocation.asFPR()); |
| #else |
| m_jit.vectorMin(info, leftLocation.asFPR(), rightLocation.asFPR(), resultLocation.asFPR()); |
| #endif |
| return { }; |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| return { }; |
| } |
| } |
| |
| void dump(const ControlStack&, const Stack*) { } |
| void didFinishParsingLocals() { } |
| void didPopValueFromStack(ExpressionType, String) { } |
| |
| void finalize() |
| { |
| if (UNLIKELY(m_disassembler)) |
| m_disassembler->setEndOfCode(m_jit.label()); |
| } |
| #undef BBQ_STUB |
| #undef BBQ_CONTROL_STUB |
| |
| Vector<UnlinkedHandlerInfo>&& takeExceptionHandlers() |
| { |
| return WTFMove(m_exceptionHandlers); |
| } |
| |
| Vector<CCallHelpers::Label>&& takeCatchEntrypoints() |
| { |
| return WTFMove(m_catchEntrypoints); |
| } |
| |
| Box<PCToCodeOriginMapBuilder> takePCToCodeOriginMapBuilder() |
| { |
| if (m_pcToCodeOriginMapBuilder.didBuildMapping()) |
| return Box<PCToCodeOriginMapBuilder>::create(WTFMove(m_pcToCodeOriginMapBuilder)); |
| return nullptr; |
| } |
| |
| std::unique_ptr<BBQDisassembler> takeDisassembler() |
| { |
| return WTFMove(m_disassembler); |
| } |
| |
| private: |
| static bool isScratch(Location loc) |
| { |
| return (loc.isGPR() && loc.asGPR() == wasmScratchGPR) || (loc.isFPR() && loc.asFPR() == wasmScratchFPR); |
| } |
| |
| void emitStoreConst(Value constant, Location loc) |
| { |
| LOG_INSTRUCTION("Store", constant, RESULT(loc)); |
| |
| ASSERT(constant.isConst()); |
| ASSERT(loc.isMemory()); |
| |
| switch (constant.type()) { |
| case TypeKind::I32: |
| case TypeKind::F32: |
| m_jit.store32(Imm32(constant.asI32()), loc.asAddress()); |
| break; |
| case TypeKind::Ref: |
| case TypeKind::Funcref: |
| case TypeKind::Arrayref: |
| case TypeKind::Structref: |
| case TypeKind::RefNull: |
| case TypeKind::Externref: |
| case TypeKind::Eqref: |
| case TypeKind::Anyref: |
| case TypeKind::Nullref: |
| m_jit.store64(TrustedImm64(constant.asRef()), loc.asAddress()); |
| break; |
| case TypeKind::I64: |
| case TypeKind::F64: |
| m_jit.store64(Imm64(constant.asI64()), loc.asAddress()); |
| break; |
| default: |
| RELEASE_ASSERT_NOT_REACHED_WITH_MESSAGE("Unimplemented constant typekind."); |
| break; |
| } |
| } |
| |
| void emitMoveConst(Value constant, Location loc) |
| { |
| ASSERT(constant.isConst()); |
| |
| if (loc.isMemory()) |
| return emitStoreConst(constant, loc); |
| |
| ASSERT(loc.isRegister()); |
| ASSERT(loc.isFPR() == constant.isFloat()); |
| |
| if (!isScratch(loc)) |
| LOG_INSTRUCTION("Move", constant, RESULT(loc)); |
| |
| switch (constant.type()) { |
| case TypeKind::I32: |
| m_jit.move(Imm32(constant.asI32()), loc.asGPR()); |
| break; |
| case TypeKind::I64: |
| m_jit.move(Imm64(constant.asI64()), loc.asGPR()); |
| break; |
| case TypeKind::Ref: |
| case TypeKind::Funcref: |
| case TypeKind::Arrayref: |
| case TypeKind::Structref: |
| case TypeKind::RefNull: |
| case TypeKind::Externref: |
| case TypeKind::Eqref: |
| case TypeKind::Anyref: |
| case TypeKind::Nullref: |
| m_jit.move(TrustedImm64(constant.asRef()), loc.asGPR()); |
| break; |
| case TypeKind::F32: |
| m_jit.moveFloat(Imm32(constant.asI32()), loc.asFPR()); |
| break; |
| case TypeKind::F64: |
| m_jit.moveDouble(Imm64(constant.asI64()), loc.asFPR()); |
| break; |
| default: |
| RELEASE_ASSERT_NOT_REACHED_WITH_MESSAGE("Unimplemented constant typekind."); |
| break; |
| } |
| } |
| |
| void emitStore(TypeKind type, Location src, Location dst) |
| { |
| ASSERT(dst.isMemory()); |
| ASSERT(src.isRegister()); |
| |
| switch (type) { |
| case TypeKind::I32: |
| case TypeKind::I31ref: |
| m_jit.store32(src.asGPR(), dst.asAddress()); |
| break; |
| case TypeKind::I64: |
| m_jit.store64(src.asGPR(), dst.asAddress()); |
| break; |
| case TypeKind::F32: |
| m_jit.storeFloat(src.asFPR(), dst.asAddress()); |
| break; |
| case TypeKind::F64: |
| m_jit.storeDouble(src.asFPR(), dst.asAddress()); |
| break; |
| case TypeKind::Externref: |
| case TypeKind::Ref: |
| case TypeKind::RefNull: |
| case TypeKind::Funcref: |
| case TypeKind::Arrayref: |
| case TypeKind::Structref: |
| case TypeKind::Eqref: |
| case TypeKind::Anyref: |
| case TypeKind::Nullref: |
| m_jit.store64(src.asGPR(), dst.asAddress()); |
| break; |
| case TypeKind::V128: |
| m_jit.storeVector(src.asFPR(), dst.asAddress()); |
| break; |
| default: |
| RELEASE_ASSERT_NOT_REACHED_WITH_MESSAGE("Unimplemented type kind store."); |
| } |
| } |
| |
| void emitStore(Value src, Location dst) |
| { |
| if (src.isConst()) |
| return emitStoreConst(src, dst); |
| |
| LOG_INSTRUCTION("Store", src, RESULT(dst)); |
| emitStore(src.type(), locationOf(src), dst); |
| } |
| |
| void emitMoveMemory(TypeKind type, Location src, Location dst) |
| { |
| ASSERT(dst.isMemory()); |
| ASSERT(src.isMemory()); |
| |
| if (src == dst) |
| return; |
| |
| switch (type) { |
| case TypeKind::I32: |
| case TypeKind::I31ref: |
| case TypeKind::F32: |
| m_jit.transfer32(src.asAddress(), dst.asAddress()); |
| break; |
| case TypeKind::I64: |
| m_jit.transfer64(src.asAddress(), dst.asAddress()); |
| break; |
| case TypeKind::F64: |
| m_jit.loadDouble(src.asAddress(), wasmScratchFPR); |
| m_jit.storeDouble(wasmScratchFPR, dst.asAddress()); |
| break; |
| case TypeKind::Externref: |
| case TypeKind::Ref: |
| case TypeKind::RefNull: |
| case TypeKind::Funcref: |
| case TypeKind::Structref: |
| case TypeKind::Arrayref: |
| case TypeKind::Eqref: |
| case TypeKind::Anyref: |
| case TypeKind::Nullref: |
| m_jit.transfer64(src.asAddress(), dst.asAddress()); |
| break; |
| case TypeKind::V128: { |
| Address srcAddress = src.asAddress(); |
| Address dstAddress = dst.asAddress(); |
| m_jit.loadVector(srcAddress, wasmScratchFPR); |
| m_jit.storeVector(wasmScratchFPR, dstAddress); |
| break; |
| } |
| default: |
| RELEASE_ASSERT_NOT_REACHED_WITH_MESSAGE("Unimplemented type kind move."); |
| } |
| } |
| |
| void emitMoveMemory(Value src, Location dst) |
| { |
| LOG_INSTRUCTION("Move", src, RESULT(dst)); |
| emitMoveMemory(src.type(), locationOf(src), dst); |
| } |
| |
| void emitMoveRegister(TypeKind type, Location src, Location dst) |
| { |
| ASSERT(dst.isRegister()); |
| ASSERT(src.isRegister()); |
| |
| if (src == dst) |
| return; |
| |
| switch (type) { |
| case TypeKind::I32: |
| case TypeKind::I31ref: |
| case TypeKind::I64: |
| case TypeKind::Externref: |
| case TypeKind::Ref: |
| case TypeKind::RefNull: |
| case TypeKind::Funcref: |
| case TypeKind::Arrayref: |
| case TypeKind::Structref: |
| case TypeKind::Eqref: |
| case TypeKind::Anyref: |
| case TypeKind::Nullref: |
| m_jit.move(src.asGPR(), dst.asGPR()); |
| break; |
| case TypeKind::F32: |
| case TypeKind::F64: |
| m_jit.moveDouble(src.asFPR(), dst.asFPR()); |
| break; |
| case TypeKind::V128: |
| m_jit.moveVector(src.asFPR(), dst.asFPR()); |
| break; |
| default: |
| RELEASE_ASSERT_NOT_REACHED_WITH_MESSAGE("Unimplemented type kind move."); |
| } |
| } |
| |
| void emitMoveRegister(Value src, Location dst) |
| { |
| if (!isScratch(locationOf(src)) && !isScratch(dst)) |
| LOG_INSTRUCTION("Move", src, RESULT(dst)); |
| |
| emitMoveRegister(src.type(), locationOf(src), dst); |
| } |
| |
| void emitLoad(TypeKind type, Location src, Location dst) |
| { |
| ASSERT(dst.isRegister()); |
| ASSERT(src.isMemory()); |
| |
| switch (type) { |
| case TypeKind::I32: |
| case TypeKind::I31ref: |
| m_jit.load32(src.asAddress(), dst.asGPR()); |
| break; |
| case TypeKind::I64: |
| m_jit.load64(src.asAddress(), dst.asGPR()); |
| break; |
| case TypeKind::F32: |
| m_jit.loadFloat(src.asAddress(), dst.asFPR()); |
| break; |
| case TypeKind::F64: |
| m_jit.loadDouble(src.asAddress(), dst.asFPR()); |
| break; |
| case TypeKind::Ref: |
| case TypeKind::RefNull: |
| case TypeKind::Externref: |
| case TypeKind::Funcref: |
| case TypeKind::Arrayref: |
| case TypeKind::Structref: |
| case TypeKind::Eqref: |
| case TypeKind::Anyref: |
| case TypeKind::Nullref: |
| m_jit.load64(src.asAddress(), dst.asGPR()); |
| break; |
| case TypeKind::V128: |
| m_jit.loadVector(src.asAddress(), dst.asFPR()); |
| break; |
| default: |
| RELEASE_ASSERT_NOT_REACHED_WITH_MESSAGE("Unimplemented type kind load."); |
| } |
| } |
| |
| void emitLoad(Value src, Location dst) |
| { |
| if (!isScratch(dst)) |
| LOG_INSTRUCTION("Load", src, RESULT(dst)); |
| |
| emitLoad(src.type(), locationOf(src), dst); |
| } |
| |
| void emitMove(TypeKind type, Location src, Location dst) |
| { |
| if (src.isRegister()) { |
| if (dst.isMemory()) |
| emitStore(type, src, dst); |
| else |
| emitMoveRegister(type, src, dst); |
| } else { |
| if (dst.isMemory()) |
| emitMoveMemory(type, src, dst); |
| else |
| emitLoad(type, src, dst); |
| } |
| } |
| |
| void emitMove(Value src, Location dst) |
| { |
| if (src.isConst()) { |
| if (dst.isMemory()) |
| emitStoreConst(src, dst); |
| else |
| emitMoveConst(src, dst); |
| } else { |
| Location srcLocation = locationOf(src); |
| emitMove(src.type(), srcLocation, dst); |
| } |
| } |
| |
| enum class ShuffleStatus { |
| ToMove, |
| BeingMoved, |
| Moved |
| }; |
| |
| template<size_t N, typename OverflowHandler> |
| void emitShuffleMove(Vector<Value, N, OverflowHandler>& srcVector, Vector<Location, N, OverflowHandler>& dstVector, Vector<ShuffleStatus, N, OverflowHandler>& statusVector, unsigned index, Location gpTemp, Location fpTemp) |
| { |
| Location srcLocation = locationOf(srcVector[index]); |
| Location dst = dstVector[index]; |
| if (srcLocation == dst) |
| return; // Easily eliminate redundant moves here. |
| |
| statusVector[index] = ShuffleStatus::BeingMoved; |
| for (unsigned i = 0; i < srcVector.size(); i ++) { |
| // This check should handle constants too - constants always have location None, and no |
| // dst should ever be a constant. But we assume that's asserted in the caller. |
| if (locationOf(srcVector[i]) == dst) { |
| switch (statusVector[i]) { |
| case ShuffleStatus::ToMove: |
| emitShuffleMove(srcVector, dstVector, statusVector, i, gpTemp, fpTemp); |
| break; |
| case ShuffleStatus::BeingMoved: { |
| Location temp = srcVector[i].isFloat() ? fpTemp : gpTemp; |
| emitMove(srcVector[i], temp); |
| srcVector[i] = Value::pinned(srcVector[i].type(), temp); |
| break; |
| } |
| case ShuffleStatus::Moved: |
| break; |
| } |
| } |
| } |
| emitMove(srcVector[index], dst); |
| statusVector[index] = ShuffleStatus::Moved; |
| } |
| |
| template<size_t N, typename OverflowHandler> |
| void emitShuffle(Vector<Value, N, OverflowHandler>& srcVector, Vector<Location, N, OverflowHandler>& dstVector) |
| { |
| ASSERT(srcVector.size() == dstVector.size()); |
| |
| if (srcVector.size() == 1) { |
| emitMove(srcVector[0], dstVector[0]); |
| return; |
| } |
| |
| // For multi-value return, a parallel move might be necessary. This is comparatively complex |
| // and slow, so we limit it to this slow path. |
| ScratchScope<1, 1> scratches(*this); |
| Location gpTemp = Location::fromGPR(scratches.gpr(0)); |
| Location fpTemp = Location::fromFPR(scratches.fpr(0)); |
| Vector<ShuffleStatus, N, OverflowHandler> statusVector(srcVector.size(), ShuffleStatus::ToMove); |
| |
| for (unsigned i = 0; i < srcVector.size(); i ++) { |
| if (statusVector[i] == ShuffleStatus::ToMove) |
| emitShuffleMove(srcVector, dstVector, statusVector, i, gpTemp, fpTemp); |
| } |
| } |
| |
| ControlData& currentControlData() |
| { |
| return m_parser->controlStack().last().controlData; |
| } |
| |
| void setLRUKey(Location location, LocalOrTempIndex key) |
| { |
| if (location.isGPR()) |
| m_gprLRU.increaseKey(location.asGPR(), key); |
| else if (location.isFPR()) |
| m_fprLRU.increaseKey(location.asFPR(), key); |
| } |
| |
| void increaseKey(Location location) |
| { |
| setLRUKey(location, m_lastUseTimestamp ++); |
| } |
| |
| Location bind(Value value) |
| { |
| if (value.isPinned()) |
| return value.asPinned(); |
| |
| Location reg = allocateRegister(value); |
| increaseKey(reg); |
| return bind(value, reg); |
| } |
| |
| Location allocate(Value value) |
| { |
| return allocateWithHint(value, Location::none()); |
| } |
| |
| Location allocateWithHint(Value value, Location hint) |
| { |
| if (value.isPinned()) |
| return value.asPinned(); |
| |
| // It's possible, in some circumstances, that the value in question was already assigned to a register. |
| // The most common example of this is returned values (that use registers from the calling convention) or |
| // values passed between control blocks. In these cases, we just leave it in its register unmoved. |
| Location existingLocation = locationOf(value); |
| if (existingLocation.isRegister()) { |
| ASSERT(value.isFloat() == existingLocation.isFPR()); |
| return existingLocation; |
| } |
| |
| Location reg = hint; |
| if (reg.kind() == Location::None |
| || value.isFloat() != reg.isFPR() |
| || (reg.isGPR() && !m_gprSet.contains(reg.asGPR(), IgnoreVectors)) |
| || (reg.isFPR() && !m_fprSet.contains(reg.asFPR(), Width::Width128))) |
| reg = allocateRegister(value); |
| increaseKey(reg); |
| if (value.isLocal()) |
| currentControlData().touch(value.asLocal()); |
| if (UNLIKELY(Options::verboseBBQJITAllocation())) |
| dataLogLn("BBQ\tAllocated ", value, " with type ", makeString(value.type()), " to ", reg); |
| return bind(value, reg); |
| } |
| |
| Location locationOfWithoutBinding(Value value) |
| { |
| // Used internally in bind() to avoid infinite recursion. |
| if (value.isPinned()) |
| return value.asPinned(); |
| if (value.isLocal()) { |
| ASSERT(value.asLocal() < m_locals.size()); |
| return m_locals[value.asLocal()]; |
| } |
| if (value.isTemp()) { |
| if (value.asTemp() >= m_temps.size()) |
| return Location::none(); |
| return m_temps[value.asTemp()]; |
| } |
| return Location::none(); |
| } |
| |
| Location locationOf(Value value) |
| { |
| if (value.isTemp()) { |
| if (value.asTemp() >= m_temps.size() || m_temps[value.asTemp()].isNone()) |
| bind(value, canonicalSlot(value)); |
| return m_temps[value.asTemp()]; |
| } |
| return locationOfWithoutBinding(value); |
| } |
| |
| Location loadIfNecessary(Value value) |
| { |
| ASSERT(!value.isPinned()); // We should not load or move pinned values. |
| ASSERT(!value.isConst()); // We should not be materializing things we know are constants. |
| if (UNLIKELY(Options::verboseBBQJITAllocation())) |
| dataLogLn("BBQ\tLoading value ", value, " if necessary"); |
| Location loc = locationOf(value); |
| if (loc.isMemory()) { |
| if (UNLIKELY(Options::verboseBBQJITAllocation())) |
| dataLogLn("BBQ\tLoading local ", value, " to ", loc); |
| loc = allocateRegister(value); // Find a register to store this value. Might spill older values if we run out. |
| emitLoad(value, loc); // Generate the load instructions to move the value into the register. |
| increaseKey(loc); |
| if (value.isLocal()) |
| currentControlData().touch(value.asLocal()); |
| bind(value, loc); // Bind the value to the register in the register/local/temp bindings. |
| } else |
| increaseKey(loc); |
| ASSERT(loc.isRegister()); |
| return loc; |
| } |
| |
| void consume(Value value) |
| { |
| // Called whenever a value is popped from the expression stack. Mostly, we |
| // use this to release the registers temporaries are bound to. |
| Location location = locationOf(value); |
| if (value.isTemp() && location != canonicalSlot(value)) |
| unbind(value, location); |
| } |
| |
| Location allocateRegister(TypeKind type) |
| { |
| if (isFloatingPointType(type)) |
| return Location::fromFPR(m_fprSet.isEmpty() ? evictFPR() : nextFPR()); |
| return Location::fromGPR(m_gprSet.isEmpty() ? evictGPR() : nextGPR()); |
| } |
| |
| Location allocateRegister(Value value) |
| { |
| return allocateRegister(value.type()); |
| } |
| |
| Location bind(Value value, Location loc) |
| { |
| // Bind a value to a location. Doesn't touch the LRU, but updates the register set |
| // and local/temp tables accordingly. |
| |
| // Return early if we are already bound to the chosen location. Mostly this avoids |
| // spurious assertion failures. |
| Location currentLocation = locationOfWithoutBinding(value); |
| if (currentLocation == loc) |
| return currentLocation; |
| |
| // Technically, it could be possible to bind from a register to another register. But |
| // this risks (and is usually caused by) messing up the allocator state. So we check |
| // for it here. |
| ASSERT(!currentLocation.isRegister()); |
| |
| if (loc.isRegister()) { |
| if (value.isFloat()) { |
| ASSERT(m_fprSet.contains(loc.asFPR(), Width::Width128)); |
| m_fprSet.remove(loc.asFPR()); |
| m_fprBindings[loc.asFPR()] = RegisterBinding::fromValue(value); |
| } else { |
| ASSERT(m_gprSet.contains(loc.asGPR(), IgnoreVectors)); |
| m_gprSet.remove(loc.asGPR()); |
| m_gprBindings[loc.asGPR()] = RegisterBinding::fromValue(value); |
| } |
| } |
| if (value.isLocal()) |
| m_locals[value.asLocal()] = loc; |
| else if (value.isTemp()) { |
| if (m_temps.size() <= value.asTemp()) |
| m_temps.resize(value.asTemp() + 1); |
| m_temps[value.asTemp()] = loc; |
| } |
| |
| if (UNLIKELY(Options::verboseBBQJITAllocation())) |
| dataLogLn("BBQ\tBound value ", value, " to ", loc); |
| |
| return loc; |
| } |
| |
| void unbind(Value value, Location loc) |
| { |
| // Unbind a value from a location. Doesn't touch the LRU, but updates the register set |
| // and local/temp tables accordingly. |
| if (loc.isFPR()) { |
| ASSERT(m_validFPRs.contains(loc.asFPR(), Width::Width128)); |
| m_fprSet.add(loc.asFPR(), Width::Width128); |
| m_fprBindings[loc.asFPR()] = RegisterBinding::none(); |
| } else if (loc.isGPR()) { |
| ASSERT(m_validGPRs.contains(loc.asGPR(), IgnoreVectors)); |
| m_gprSet.add(loc.asGPR(), IgnoreVectors); |
| m_gprBindings[loc.asGPR()] = RegisterBinding::none(); |
| } |
| if (value.isLocal()) |
| m_locals[value.asLocal()] = m_localSlots[value.asLocal()]; |
| else if (value.isTemp()) |
| m_temps[value.asTemp()] = Location::none(); |
| |
| if (UNLIKELY(Options::verboseBBQJITAllocation())) |
| dataLogLn("BBQ\tUnbound value ", value, " from ", loc); |
| } |
| |
| template<typename Register> |
| static Register fromJSCReg(Reg reg) |
| { |
| // This pattern avoids an explicit template specialization in class scope, which GCC does not support. |
| if constexpr (std::is_same_v<Register, GPRReg>) { |
| ASSERT(reg.isGPR()); |
| return reg.gpr(); |
| } else if constexpr (std::is_same_v<Register, FPRReg>) { |
| ASSERT(reg.isFPR()); |
| return reg.fpr(); |
| } |
| ASSERT_NOT_REACHED(); |
| } |
| |
| template<typename Register> |
| class LRU { |
| public: |
| ALWAYS_INLINE LRU(uint32_t numRegisters) |
| : m_keys(numRegisters, -1) // We use -1 to signify registers that can never be allocated or used. |
| { } |
| |
| void add(RegisterSet registers) |
| { |
| registers.forEach([&] (JSC::Reg r) { |
| m_keys[fromJSCReg<Register>(r)] = 0; |
| }); |
| } |
| |
| Register findMin() |
| { |
| int32_t minIndex = -1; |
| int32_t minKey = -1; |
| for (unsigned i = 0; i < m_keys.size(); i ++) { |
| Register reg = static_cast<Register>(i); |
| if (m_locked.contains(reg, conservativeWidth(reg))) |
| continue; |
| if (m_keys[i] < 0) |
| continue; |
| if (minKey < 0 || m_keys[i] < minKey) { |
| minKey = m_keys[i]; |
| minIndex = i; |
| } |
| } |
| ASSERT(minIndex >= 0, "No allocatable registers in LRU"); |
| return static_cast<Register>(minIndex); |
| } |
| |
| void increaseKey(Register reg, uint32_t newKey) |
| { |
| if (m_keys[reg] >= 0) // Leave untracked registers alone. |
| m_keys[reg] = newKey; |
| } |
| |
| void lock(Register reg) |
| { |
| m_locked.add(reg, conservativeWidth(reg)); |
| } |
| |
| void unlock(Register reg) |
| { |
| m_locked.remove(reg); |
| } |
| |
| private: |
| Vector<int32_t, 32> m_keys; |
| RegisterSet m_locked; |
| }; |
| |
| GPRReg nextGPR() |
| { |
| auto next = m_gprSet.begin(); |
| ASSERT(next != m_gprSet.end()); |
| GPRReg reg = (*next).gpr(); |
| ASSERT(m_gprBindings[reg].m_kind == RegisterBinding::None); |
| return reg; |
| } |
| |
| FPRReg nextFPR() |
| { |
| auto next = m_fprSet.begin(); |
| ASSERT(next != m_fprSet.end()); |
| FPRReg reg = (*next).fpr(); |
| ASSERT(m_fprBindings[reg].m_kind == RegisterBinding::None); |
| return reg; |
| } |
| |
| GPRReg evictGPR() |
| { |
| auto lruGPR = m_gprLRU.findMin(); |
| auto lruBinding = m_gprBindings[lruGPR]; |
| |
| if (UNLIKELY(Options::verboseBBQJITAllocation())) |
| dataLogLn("BBQ\tEvicting GPR ", MacroAssembler::gprName(lruGPR), " currently bound to ", lruBinding); |
| flushValue(lruBinding.toValue()); |
| |
| ASSERT(m_gprSet.contains(lruGPR, IgnoreVectors)); |
| ASSERT(m_gprBindings[lruGPR].m_kind == RegisterBinding::None); |
| return lruGPR; |
| } |
| |
| FPRReg evictFPR() |
| { |
| auto lruFPR = m_fprLRU.findMin(); |
| auto lruBinding = m_fprBindings[lruFPR]; |
| |
| if (UNLIKELY(Options::verboseBBQJITAllocation())) |
| dataLogLn("BBQ\tEvicting FPR ", MacroAssembler::fprName(lruFPR), " currently bound to ", lruBinding); |
| flushValue(lruBinding.toValue()); |
| |
| ASSERT(m_fprSet.contains(lruFPR, Width::Width128)); |
| ASSERT(m_fprBindings[lruFPR].m_kind == RegisterBinding::None); |
| return lruFPR; |
| } |
| |
| // We use this to free up specific registers that might get clobbered by an instruction. |
| |
| void clobber(GPRReg gpr) |
| { |
| if (m_validGPRs.contains(gpr, IgnoreVectors) && !m_gprSet.contains(gpr, IgnoreVectors)) { |
| RegisterBinding& binding = m_gprBindings[gpr]; |
| if (UNLIKELY(Options::verboseBBQJITAllocation())) |
| dataLogLn("BBQ\tClobbering GPR ", MacroAssembler::gprName(gpr), " currently bound to ", binding); |
| RELEASE_ASSERT(!binding.isNone() && !binding.isScratch()); // We could probably figure out how to handle this, but let's just crash if it happens for now. |
| flushValue(binding.toValue()); |
| } |
| } |
| |
| void clobber(FPRReg fpr) |
| { |
| if (m_validFPRs.contains(fpr, Width::Width128) && !m_fprSet.contains(fpr, Width::Width128)) { |
| RegisterBinding& binding = m_fprBindings[fpr]; |
| if (UNLIKELY(Options::verboseBBQJITAllocation())) |
| dataLogLn("BBQ\tClobbering FPR ", MacroAssembler::fprName(fpr), " currently bound to ", binding); |
| RELEASE_ASSERT(!binding.isNone() && !binding.isScratch()); // We could probably figure out how to handle this, but let's just crash if it happens for now. |
| flushValue(binding.toValue()); |
| } |
| } |
| |
| void clobber(JSC::Reg reg) |
| { |
| reg.isGPR() ? clobber(reg.gpr()) : clobber(reg.fpr()); |
| } |
| |
| template<int GPRs, int FPRs> |
| class ScratchScope { |
| WTF_MAKE_NONCOPYABLE(ScratchScope); |
| public: |
| template<typename... Args> |
| ScratchScope(BBQJIT& generator, Args... locationsToPreserve) |
| : m_generator(generator) |
| , m_endedEarly(false) |
| { |
| initializedPreservedSet(locationsToPreserve...); |
| for (JSC::Reg reg : m_preserved) { |
| if (reg.isGPR()) |
| bindGPRToScratch(reg.gpr()); |
| else |
| bindFPRToScratch(reg.fpr()); |
| } |
| for (int i = 0; i < GPRs; i ++) |
| m_tempGPRs[i] = bindGPRToScratch(m_generator.allocateRegister(TypeKind::I64).asGPR()); |
| for (int i = 0; i < FPRs; i ++) |
| m_tempFPRs[i] = bindFPRToScratch(m_generator.allocateRegister(TypeKind::F64).asFPR()); |
| } |
| |
| ~ScratchScope() |
| { |
| if (!m_endedEarly) |
| unbind(); |
| } |
| |
| void unbindEarly() |
| { |
| m_endedEarly = true; |
| unbind(); |
| } |
| |
| inline GPRReg gpr(unsigned i) const |
| { |
| ASSERT(i < GPRs); |
| ASSERT(!m_endedEarly); |
| return m_tempGPRs[i]; |
| } |
| |
| inline FPRReg fpr(unsigned i) const |
| { |
| ASSERT(i < FPRs); |
| ASSERT(!m_endedEarly); |
| return m_tempFPRs[i]; |
| } |
| |
| private: |
| GPRReg bindGPRToScratch(GPRReg reg) |
| { |
| if (!m_generator.m_validGPRs.contains(reg, IgnoreVectors)) |
| return reg; |
| RegisterBinding& binding = m_generator.m_gprBindings[reg]; |
| if (m_preserved.contains(reg, IgnoreVectors) && !binding.isNone()) { |
| if (UNLIKELY(Options::verboseBBQJITAllocation())) |
| dataLogLn("BBQ\tPreserving GPR ", MacroAssembler::gprName(reg), " currently bound to ", binding); |
| return reg; // If the register is already bound, we don't need to preserve it ourselves. |
| } |
| ASSERT(binding.isNone()); |
| binding = RegisterBinding::scratch(); |
| m_generator.m_gprSet.remove(reg); |
| m_generator.m_gprLRU.lock(reg); |
| if (UNLIKELY(Options::verboseBBQJITAllocation())) |
| dataLogLn("BBQ\tReserving scratch GPR ", MacroAssembler::gprName(reg)); |
| return reg; |
| } |
| |
| FPRReg bindFPRToScratch(FPRReg reg) |
| { |
| if (!m_generator.m_validFPRs.contains(reg, Width::Width128)) |
| return reg; |
| RegisterBinding& binding = m_generator.m_fprBindings[reg]; |
| if (m_preserved.contains(reg, Width::Width128) && !binding.isNone()) { |
| if (UNLIKELY(Options::verboseBBQJITAllocation())) |
| dataLogLn("BBQ\tPreserving FPR ", MacroAssembler::fprName(reg), " currently bound to ", binding); |
| return reg; // If the register is already bound, we don't need to preserve it ourselves. |
| } |
| ASSERT(binding.isNone()); |
| binding = RegisterBinding::scratch(); |
| m_generator.m_fprSet.remove(reg); |
| m_generator.m_fprLRU.lock(reg); |
| if (UNLIKELY(Options::verboseBBQJITAllocation())) |
| dataLogLn("BBQ\tReserving scratch FPR ", MacroAssembler::fprName(reg)); |
| return reg; |
| } |
| |
| void unbindGPRFromScratch(GPRReg reg) |
| { |
| if (!m_generator.m_validGPRs.contains(reg, IgnoreVectors)) |
| return; |
| RegisterBinding& binding = m_generator.m_gprBindings[reg]; |
| if (UNLIKELY(Options::verboseBBQJITAllocation())) |
| dataLogLn("BBQ\tReleasing GPR ", MacroAssembler::gprName(reg)); |
| if (m_preserved.contains(reg, IgnoreVectors) && !binding.isScratch()) |
| return; // It's okay if the register isn't bound to a scratch if we meant to preserve it - maybe it was just already bound to something. |
| ASSERT(binding.isScratch()); |
| binding = RegisterBinding::none(); |
| m_generator.m_gprSet.add(reg, IgnoreVectors); |
| m_generator.m_gprLRU.unlock(reg); |
| } |
| |
| void unbindFPRFromScratch(FPRReg reg) |
| { |
| if (!m_generator.m_validFPRs.contains(reg, Width::Width128)) |
| return; |
| RegisterBinding& binding = m_generator.m_fprBindings[reg]; |
| if (UNLIKELY(Options::verboseBBQJITAllocation())) |
| dataLogLn("BBQ\tReleasing FPR ", MacroAssembler::fprName(reg)); |
| if (m_preserved.contains(reg, Width::Width128) && !binding.isScratch()) |
| return; // It's okay if the register isn't bound to a scratch if we meant to preserve it - maybe it was just already bound to something. |
| ASSERT(binding.isScratch()); |
| binding = RegisterBinding::none(); |
| m_generator.m_fprSet.add(reg, Width::Width128); |
| m_generator.m_fprLRU.unlock(reg); |
| } |
| |
| template<typename... Args> |
| void initializedPreservedSet(Location location, Args... args) |
| { |
| if (location.isGPR()) |
| m_preserved.add(location.asGPR(), IgnoreVectors); |
| else if (location.isFPR()) |
| m_preserved.add(location.asFPR(), Width::Width128); |
| initializedPreservedSet(args...); |
| } |
| |
| template<typename... Args> |
| void initializedPreservedSet(RegisterSet registers, Args... args) |
| { |
| for (JSC::Reg reg : registers) { |
| if (reg.isGPR()) |
| m_preserved.add(reg.gpr(), IgnoreVectors); |
| else |
| m_preserved.add(reg.fpr(), Width::Width128); |
| } |
| initializedPreservedSet(args...); |
| } |
| |
| inline void initializedPreservedSet() |
| { } |
| |
| void unbind() |
| { |
| for (int i = 0; i < GPRs; i ++) |
| unbindGPRFromScratch(m_tempGPRs[i]); |
| for (int i = 0; i < FPRs; i ++) |
| unbindFPRFromScratch(m_tempFPRs[i]); |
| |
| for (JSC::Reg reg : m_preserved) { |
| if (reg.isGPR()) |
| unbindGPRFromScratch(reg.gpr()); |
| else |
| unbindFPRFromScratch(reg.fpr()); |
| } |
| } |
| |
| BBQJIT& m_generator; |
| GPRReg m_tempGPRs[GPRs]; |
| FPRReg m_tempFPRs[FPRs]; |
| RegisterSet m_preserved; |
| bool m_endedEarly; |
| }; |
| |
| Location canonicalSlot(Value value) |
| { |
| ASSERT(value.isLocal() || value.isTemp()); |
| if (value.isLocal()) |
| return m_localSlots[value.asLocal()]; |
| |
| LocalOrTempIndex tempIndex = value.asTemp(); |
| int slotOffset = WTF::roundUpToMultipleOf<tempSlotSize>(m_localStorage) + (tempIndex + 1) * tempSlotSize; |
| if (m_frameSize < slotOffset) |
| m_frameSize = slotOffset; |
| return Location::fromStack(-slotOffset); |
| } |
| |
| Location allocateStack(Value value) |
| { |
| // Align stack for value size. |
| m_frameSize = WTF::roundUpToMultipleOf(value.size(), m_frameSize); |
| m_frameSize += value.size(); |
| return Location::fromStack(-m_frameSize); |
| } |
| |
| constexpr static int tempSlotSize = 16; // Size of the stack slot for a stack temporary. Currently the size of the largest possible temporary (a v128). |
| |
| #undef LOG_INSTRUCTION |
| #undef RESULT |
| |
| CCallHelpers& m_jit; |
| BBQCallee& m_callee; |
| const FunctionData& m_function; |
| const FunctionSignature* m_functionSignature; |
| uint32_t m_functionIndex; |
| const ModuleInformation& m_info; |
| MemoryMode m_mode; |
| Vector<UnlinkedWasmToWasmCall>& m_unlinkedWasmToWasmCalls; |
| std::optional<bool> m_hasExceptionHandlers; |
| TierUpCount* m_tierUp; |
| FunctionParser<BBQJIT>* m_parser; |
| Vector<uint32_t, 4> m_arguments; |
| ControlData m_topLevel; |
| unsigned m_loopIndexForOSREntry; |
| Vector<unsigned> m_outerLoops; |
| unsigned m_osrEntryScratchBufferSize { 1 }; |
| |
| Vector<RegisterBinding, 32> m_gprBindings; // Tables mapping from each register to the current value bound to it. |
| Vector<RegisterBinding, 32> m_fprBindings; |
| RegisterSet m_gprSet, m_fprSet; // Sets tracking whether registers are bound or free. |
| RegisterSet m_validGPRs, m_validFPRs; // These contain the original register sets used in m_gprSet and m_fprSet. |
| Vector<Location, 8> m_locals; // Vectors mapping local and temp indices to binding indices. |
| Vector<Location, 8> m_temps; |
| Vector<Location, 8> m_localSlots; // Persistent stack slots for local variables. |
| Vector<TypeKind, 8> m_localTypes; // Types of all non-argument locals in this function. |
| LRU<GPRReg> m_gprLRU; // LRU cache tracking when general-purpose registers were last used. |
| LRU<FPRReg> m_fprLRU; // LRU cache tracking when floating-point registers were last used. |
| uint32_t m_lastUseTimestamp; // Monotonically increasing integer incrementing with each register use. |
| Vector<RefPtr<SharedTask<void(BBQJIT&, CCallHelpers&)>>, 8> m_latePaths; // Late paths to emit after the rest of the function body. |
| |
| Vector<DataLabelPtr, 1> m_frameSizeLabels; |
| int m_frameSize { 0 }; |
| int m_maxCalleeStackSize { 0 }; |
| int m_localStorage { 0 }; // Stack offset pointing to the local with the lowest address. |
| bool m_usesSIMD { false }; // Whether the function we are compiling uses SIMD instructions or not. |
| bool m_usesExceptions { false }; |
| Checked<unsigned> m_tryCatchDepth { 0 }; |
| Checked<unsigned> m_callSiteIndex { 0 }; |
| |
| RegisterSet m_callerSaveGPRs; |
| RegisterSet m_callerSaveFPRs; |
| RegisterSet m_callerSaves; |
| |
| InternalFunction* m_compilation; |
| |
| std::array<JumpList, numberOfExceptionTypes> m_exceptions { }; |
| Vector<UnlinkedHandlerInfo> m_exceptionHandlers; |
| Vector<CCallHelpers::Label> m_catchEntrypoints; |
| |
| PCToCodeOriginMapBuilder m_pcToCodeOriginMapBuilder; |
| std::unique_ptr<BBQDisassembler> m_disassembler; |
| }; |
| |
| Expected<std::unique_ptr<InternalFunction>, String> parseAndCompileBBQ(CompilationContext& compilationContext, BBQCallee& callee, const FunctionData& function, const TypeDefinition& signature, Vector<UnlinkedWasmToWasmCall>& unlinkedWasmToWasmCalls, const ModuleInformation& info, MemoryMode mode, uint32_t functionIndex, std::optional<bool> hasExceptionHandlers, unsigned loopIndexForOSREntry, TierUpCount* tierUp) |
| { |
| CompilerTimingScope totalTime("BBQ", "Total BBQ"); |
| |
| Thunks::singleton().stub(catchInWasmThunkGenerator); |
| |
| auto result = makeUnique<InternalFunction>(); |
| |
| compilationContext.wasmEntrypointJIT = makeUnique<CCallHelpers>(); |
| |
| BBQJIT irGenerator(*compilationContext.wasmEntrypointJIT, signature, callee, function, functionIndex, info, unlinkedWasmToWasmCalls, mode, result.get(), hasExceptionHandlers, loopIndexForOSREntry, tierUp); |
| FunctionParser<BBQJIT> parser(irGenerator, function.data.data(), function.data.size(), signature, info); |
| WASM_FAIL_IF_HELPER_FAILS(parser.parse()); |
| |
| if (irGenerator.hasLoops()) |
| result->bbqSharedLoopEntrypoint = irGenerator.addLoopOSREntrypoint(); |
| |
| irGenerator.finalize(); |
| |
| result->exceptionHandlers = irGenerator.takeExceptionHandlers(); |
| compilationContext.catchEntrypoints = irGenerator.takeCatchEntrypoints(); |
| compilationContext.pcToCodeOriginMapBuilder = irGenerator.takePCToCodeOriginMapBuilder(); |
| compilationContext.bbqDisassembler = irGenerator.takeDisassembler(); |
| |
| return result; |
| } |
| |
| } } // namespace JSC::Wasm |
| |
| #endif // ENABLE(WEBASSEMBLY_B3JIT) |