/* * Copyright (C) 2017-2020 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 "CCallHelpers.h" #include "CPU.h" #include "FPRInfo.h" #include "GPRInfo.h" #include "InitializeThreading.h" #include "LinkBuffer.h" #include "ProbeContext.h" #include "StackAlignment.h" #include #include #include #include #include #include #include #include #include // We don't have a NO_RETURN_DUE_TO_EXIT, nor should we. That's ridiculous. static bool hiddenTruthBecauseNoReturnIsStupid() { return true; } static void usage() { dataLog("Usage: testmasm []\n"); if (hiddenTruthBecauseNoReturnIsStupid()) exit(1); } #if ENABLE(JIT) static Vector doubleOperands() { return Vector { 0, -0, 1, -1, 42, -42, std::numeric_limits::max(), std::numeric_limits::min(), std::numeric_limits::lowest(), std::numeric_limits::quiet_NaN(), std::numeric_limits::infinity(), -std::numeric_limits::infinity(), }; } #if CPU(X86) || CPU(X86_64) || CPU(ARM64) static Vector floatOperands() { return Vector { 0, -0, 1, -1, 42, -42, std::numeric_limits::max(), std::numeric_limits::min(), std::numeric_limits::lowest(), std::numeric_limits::quiet_NaN(), std::numeric_limits::infinity(), -std::numeric_limits::infinity(), }; } #endif static Vector int32Operands() { return Vector { 0, 1, -1, 2, -2, 42, -42, 64, std::numeric_limits::max(), std::numeric_limits::min(), }; } #if CPU(X86_64) || CPU(ARM64) static Vector int64Operands() { return Vector { 0, 1, -1, 2, -2, 42, -42, 64, std::numeric_limits::max(), std::numeric_limits::min(), std::numeric_limits::max(), std::numeric_limits::min(), }; } #endif #if ENABLE(MASM_PROBE) namespace WTF { static void printInternal(PrintStream& out, void* value) { out.printf("%p", value); } } // namespace WTF #endif // ENABLE(MASM_PROBE) namespace JSC { namespace Probe { JS_EXPORT_PRIVATE void* probeStateForContext(Probe::Context&); } // namespace Probe } // namespace JSC using namespace JSC; namespace { #if ENABLE(MASM_PROBE) using CPUState = Probe::CPUState; #endif Lock crashLock; typedef WTF::Function Generator; template T nextID(T id) { return static_cast(id + 1); } #define TESTWORD64 0x0c0defefebeef000 #define TESTWORD32 0x0beef000 #define testWord32(x) (TESTWORD32 + static_cast(x)) #define testWord64(x) (TESTWORD64 + static_cast(x)) #if USE(JSVALUE64) #define testWord(x) testWord64(x) #else #define testWord(x) testWord32(x) #endif // Nothing fancy for now; we just use the existing WTF assertion machinery. #define CHECK_EQ(_actual, _expected) do { \ if ((_actual) == (_expected)) \ break; \ crashLock.lock(); \ dataLog("FAILED while testing " #_actual ": expected: ", _expected, ", actual: ", _actual, "\n"); \ WTFReportAssertionFailure(__FILE__, __LINE__, WTF_PRETTY_FUNCTION, "CHECK_EQ("#_actual ", " #_expected ")"); \ CRASH(); \ } while (false) #define CHECK_NOT_EQ(_actual, _expected) do { \ if ((_actual) != (_expected)) \ break; \ crashLock.lock(); \ dataLog("FAILED while testing " #_actual ": expected not: ", _expected, ", actual: ", _actual, "\n"); \ WTFReportAssertionFailure(__FILE__, __LINE__, WTF_PRETTY_FUNCTION, "CHECK_NOT_EQ("#_actual ", " #_expected ")"); \ CRASH(); \ } while (false) #if ENABLE(MASM_PROBE) bool isPC(MacroAssembler::RegisterID id) { #if CPU(ARM_THUMB2) return id == ARMRegisters::pc; #else UNUSED_PARAM(id); return false; #endif } bool isSP(MacroAssembler::RegisterID id) { return id == MacroAssembler::stackPointerRegister; } bool isFP(MacroAssembler::RegisterID id) { return id == MacroAssembler::framePointerRegister; } bool isSpecialGPR(MacroAssembler::RegisterID id) { if (isPC(id) || isSP(id) || isFP(id)) return true; #if CPU(ARM64) if (id == ARM64Registers::x18) return true; #elif CPU(MIPS) if (id == MIPSRegisters::zero || id == MIPSRegisters::k0 || id == MIPSRegisters::k1) return true; #endif return false; } #endif // ENABLE(MASM_PROBE) MacroAssemblerCodeRef compile(Generator&& generate) { CCallHelpers jit; generate(jit); LinkBuffer linkBuffer(jit, nullptr); return FINALIZE_CODE(linkBuffer, JSEntryPtrTag, "testmasm compilation"); } template T invoke(const MacroAssemblerCodeRef& code, Arguments... arguments) { void* executableAddress = untagCFunctionPtr(code.code().executableAddress()); T (*function)(Arguments...) = bitwise_cast(executableAddress); return function(arguments...); } template T compileAndRun(Generator&& generator, Arguments... arguments) { return invoke(compile(WTFMove(generator)), arguments...); } void emitFunctionPrologue(CCallHelpers& jit) { jit.emitFunctionPrologue(); #if CPU(ARM_THUMB2) // MacroAssemblerARMv7 uses r6 as a temporary register, which is a // callee-saved register, see 5.1.1 of the Procedure Call Standard for // the ARM Architecture. // http://infocenter.arm.com/help/topic/com.arm.doc.ihi0042f/IHI0042F_aapcs.pdf jit.push(ARMRegisters::r6); #endif } void emitFunctionEpilogue(CCallHelpers& jit) { #if CPU(ARM_THUMB2) jit.pop(ARMRegisters::r6); #endif jit.emitFunctionEpilogue(); } void testSimple() { CHECK_EQ(compileAndRun([] (CCallHelpers& jit) { emitFunctionPrologue(jit); jit.move(CCallHelpers::TrustedImm32(42), GPRInfo::returnValueGPR); emitFunctionEpilogue(jit); jit.ret(); }), 42); } void testGetEffectiveAddress(size_t pointer, ptrdiff_t length, int32_t offset, CCallHelpers::Scale scale) { CHECK_EQ(compileAndRun([=] (CCallHelpers& jit) { emitFunctionPrologue(jit); jit.move(CCallHelpers::TrustedImmPtr(bitwise_cast(pointer)), GPRInfo::regT0); jit.move(CCallHelpers::TrustedImmPtr(bitwise_cast(length)), GPRInfo::regT1); jit.getEffectiveAddress(CCallHelpers::BaseIndex(GPRInfo::regT0, GPRInfo::regT1, scale, offset), GPRInfo::returnValueGPR); emitFunctionEpilogue(jit); jit.ret(); }), pointer + offset + (static_cast(1) << static_cast(scale)) * length); } // branchTruncateDoubleToInt32(), when encountering Infinity, -Infinity or a // Nan, should either yield 0 in dest or fail. void testBranchTruncateDoubleToInt32(double val, int32_t expected) { const uint64_t valAsUInt = *reinterpret_cast(&val); #if CPU(BIG_ENDIAN) const bool isBigEndian = true; #else const bool isBigEndian = false; #endif CHECK_EQ(compileAndRun([&] (CCallHelpers& jit) { emitFunctionPrologue(jit); jit.subPtr(CCallHelpers::TrustedImm32(stackAlignmentBytes()), MacroAssembler::stackPointerRegister); if (isBigEndian) { jit.store32(CCallHelpers::TrustedImm32(valAsUInt >> 32), MacroAssembler::stackPointerRegister); jit.store32(CCallHelpers::TrustedImm32(valAsUInt & 0xffffffff), MacroAssembler::Address(MacroAssembler::stackPointerRegister, 4)); } else { jit.store32(CCallHelpers::TrustedImm32(valAsUInt & 0xffffffff), MacroAssembler::stackPointerRegister); jit.store32(CCallHelpers::TrustedImm32(valAsUInt >> 32), MacroAssembler::Address(MacroAssembler::stackPointerRegister, 4)); } jit.loadDouble(MacroAssembler::stackPointerRegister, FPRInfo::fpRegT0); MacroAssembler::Jump done; done = jit.branchTruncateDoubleToInt32(FPRInfo::fpRegT0, GPRInfo::returnValueGPR, MacroAssembler::BranchIfTruncateSuccessful); jit.move(CCallHelpers::TrustedImm32(0), GPRInfo::returnValueGPR); done.link(&jit); jit.addPtr(CCallHelpers::TrustedImm32(stackAlignmentBytes()), MacroAssembler::stackPointerRegister); emitFunctionEpilogue(jit); jit.ret(); }), expected); } #if CPU(X86_64) void testBranchTestBit32RegReg() { for (auto value : int32Operands()) { auto test = compile([=] (CCallHelpers& jit) { emitFunctionPrologue(jit); auto branch = jit.branchTestBit32(MacroAssembler::NonZero, GPRInfo::argumentGPR0, GPRInfo::argumentGPR1); jit.move(CCallHelpers::TrustedImm32(0), GPRInfo::returnValueGPR); auto done = jit.jump(); branch.link(&jit); jit.move(CCallHelpers::TrustedImm32(1), GPRInfo::returnValueGPR); done.link(&jit); emitFunctionEpilogue(jit); jit.ret(); }); for (auto value2 : int32Operands()) CHECK_EQ(invoke(test, value, value2), (value>>(value2%32))&1); } } void testBranchTestBit32RegImm() { for (auto value : int32Operands()) { auto test = compile([=] (CCallHelpers& jit) { emitFunctionPrologue(jit); auto branch = jit.branchTestBit32(MacroAssembler::NonZero, GPRInfo::argumentGPR0, CCallHelpers::TrustedImm32(value)); jit.move(CCallHelpers::TrustedImm32(0), GPRInfo::returnValueGPR); auto done = jit.jump(); branch.link(&jit); jit.move(CCallHelpers::TrustedImm32(1), GPRInfo::returnValueGPR); done.link(&jit); emitFunctionEpilogue(jit); jit.ret(); }); for (auto value2 : int32Operands()) CHECK_EQ(invoke(test, value2), (value2>>(value%32))&1); } } void testBranchTestBit32AddrImm() { for (auto value : int32Operands()) { auto test = compile([=] (CCallHelpers& jit) { emitFunctionPrologue(jit); auto branch = jit.branchTestBit32(MacroAssembler::NonZero, MacroAssembler::Address(GPRInfo::argumentGPR0, 0), CCallHelpers::TrustedImm32(value)); jit.move(CCallHelpers::TrustedImm32(0), GPRInfo::returnValueGPR); auto done = jit.jump(); branch.link(&jit); jit.move(CCallHelpers::TrustedImm32(1), GPRInfo::returnValueGPR); done.link(&jit); emitFunctionEpilogue(jit); jit.ret(); }); for (auto value2 : int32Operands()) CHECK_EQ(invoke(test, &value2), (value2>>(value%32))&1); } } void testBranchTestBit64RegReg() { for (auto value : int64Operands()) { auto test = compile([=] (CCallHelpers& jit) { emitFunctionPrologue(jit); auto branch = jit.branchTestBit64(MacroAssembler::NonZero, GPRInfo::argumentGPR0, GPRInfo::argumentGPR1); jit.move(CCallHelpers::TrustedImm64(0), GPRInfo::returnValueGPR); auto done = jit.jump(); branch.link(&jit); jit.move(CCallHelpers::TrustedImm64(1), GPRInfo::returnValueGPR); done.link(&jit); emitFunctionEpilogue(jit); jit.ret(); }); for (auto value2 : int64Operands()) CHECK_EQ(invoke(test, value, value2), (value>>(value2%64))&1); } } void testBranchTestBit64RegImm() { for (auto value : int64Operands()) { auto test = compile([=] (CCallHelpers& jit) { emitFunctionPrologue(jit); auto branch = jit.branchTestBit64(MacroAssembler::NonZero, GPRInfo::argumentGPR0, CCallHelpers::TrustedImm32(value)); jit.move(CCallHelpers::TrustedImm64(0), GPRInfo::returnValueGPR); auto done = jit.jump(); branch.link(&jit); jit.move(CCallHelpers::TrustedImm64(1), GPRInfo::returnValueGPR); done.link(&jit); emitFunctionEpilogue(jit); jit.ret(); }); for (auto value2 : int64Operands()) CHECK_EQ(invoke(test, value2), (value2>>(value%64))&1); } } void testBranchTestBit64AddrImm() { for (auto value : int64Operands()) { auto test = compile([=] (CCallHelpers& jit) { emitFunctionPrologue(jit); auto branch = jit.branchTestBit64(MacroAssembler::NonZero, MacroAssembler::Address(GPRInfo::argumentGPR0, 0), CCallHelpers::TrustedImm32(value)); jit.move(CCallHelpers::TrustedImm64(0), GPRInfo::returnValueGPR); auto done = jit.jump(); branch.link(&jit); jit.move(CCallHelpers::TrustedImm64(1), GPRInfo::returnValueGPR); done.link(&jit); emitFunctionEpilogue(jit); jit.ret(); }); for (auto value2 : int64Operands()) CHECK_EQ(invoke(test, &value2), (value2>>(value%64))&1); } } #endif #if CPU(X86_64) || CPU(ARM64) void testClearBit64() { auto test = compile([] (CCallHelpers& jit) { emitFunctionPrologue(jit); GPRReg scratchGPR = GPRInfo::argumentGPR2; jit.clearBit64(GPRInfo::argumentGPR1, GPRInfo::argumentGPR0, scratchGPR); jit.move(GPRInfo::argumentGPR0, GPRInfo::returnValueGPR); emitFunctionEpilogue(jit); jit.ret(); }); constexpr unsigned bitsInWord = sizeof(uint64_t) * 8; for (unsigned i = 0; i < bitsInWord; ++i) { uint64_t word = std::numeric_limits::max(); constexpr uint64_t one = 1; CHECK_EQ(invoke(test, word, i), (word & ~(one << i))); } for (unsigned i = 0; i < bitsInWord; ++i) { uint64_t word = 0; CHECK_EQ(invoke(test, word, i), 0); } } void testClearBits64WithMask() { auto test = compile([] (CCallHelpers& jit) { emitFunctionPrologue(jit); jit.clearBits64WithMask(GPRInfo::argumentGPR1, GPRInfo::argumentGPR0); jit.move(GPRInfo::argumentGPR0, GPRInfo::returnValueGPR); emitFunctionEpilogue(jit); jit.ret(); }); for (auto value : int64Operands()) { uint64_t word = std::numeric_limits::max(); CHECK_EQ(invoke(test, word, value), (word & ~value)); } for (auto value : int64Operands()) { uint64_t word = 0; CHECK_EQ(invoke(test, word, value), 0); } #if ENABLE(MASM_PROBE) uint64_t savedMask = 0; auto test2 = compile([&] (CCallHelpers& jit) { emitFunctionPrologue(jit); jit.probe([&] (Probe::Context& context) { savedMask = context.gpr(GPRInfo::argumentGPR1); }); jit.clearBits64WithMask(GPRInfo::argumentGPR1, GPRInfo::argumentGPR0, CCallHelpers::ClearBitsAttributes::MustPreserveMask); jit.probe([&] (Probe::Context& context) { CHECK_EQ(savedMask, context.gpr(GPRInfo::argumentGPR1)); }); jit.move(GPRInfo::argumentGPR0, GPRInfo::returnValueGPR); emitFunctionEpilogue(jit); jit.ret(); }); for (auto value : int64Operands()) { uint64_t word = std::numeric_limits::max(); CHECK_EQ(invoke(test2, word, value), (word & ~value)); } for (auto value : int64Operands()) { uint64_t word = 0; CHECK_EQ(invoke(test2, word, value), 0); } #endif } void testClearBits64WithMaskTernary() { auto test = compile([] (CCallHelpers& jit) { emitFunctionPrologue(jit); jit.move(GPRInfo::argumentGPR0, GPRInfo::argumentGPR2); jit.move(GPRInfo::argumentGPR1, GPRInfo::argumentGPR3); jit.clearBits64WithMask(GPRInfo::argumentGPR2, GPRInfo::argumentGPR3, GPRInfo::returnValueGPR); emitFunctionEpilogue(jit); jit.ret(); }); for (auto value : int64Operands()) { uint64_t word = std::numeric_limits::max(); CHECK_EQ(invoke(test, word, value), (word & ~value)); } for (auto value : int64Operands()) { uint64_t word = 0; CHECK_EQ(invoke(test, word, value), 0); } #if ENABLE(MASM_PROBE) uint64_t savedMask = 0; auto test2 = compile([&] (CCallHelpers& jit) { emitFunctionPrologue(jit); jit.move(GPRInfo::argumentGPR0, GPRInfo::argumentGPR2); jit.move(GPRInfo::argumentGPR1, GPRInfo::argumentGPR3); jit.probe([&] (Probe::Context& context) { savedMask = context.gpr(GPRInfo::argumentGPR2); }); jit.clearBits64WithMask(GPRInfo::argumentGPR2, GPRInfo::argumentGPR3, GPRInfo::returnValueGPR, CCallHelpers::ClearBitsAttributes::MustPreserveMask); jit.probe([&] (Probe::Context& context) { CHECK_EQ(savedMask, context.gpr(GPRInfo::argumentGPR2)); }); emitFunctionEpilogue(jit); jit.ret(); }); for (auto value : int64Operands()) { uint64_t word = std::numeric_limits::max(); CHECK_EQ(invoke(test2, word, value), (word & ~value)); } for (auto value : int64Operands()) { uint64_t word = 0; CHECK_EQ(invoke(test2, word, value), 0); } #endif } static void testCountTrailingZeros64Impl(bool wordCanBeZero) { auto test = compile([=] (CCallHelpers& jit) { emitFunctionPrologue(jit); if (wordCanBeZero) jit.countTrailingZeros64(GPRInfo::argumentGPR0, GPRInfo::returnValueGPR); else jit.countTrailingZeros64WithoutNullCheck(GPRInfo::argumentGPR0, GPRInfo::returnValueGPR); emitFunctionEpilogue(jit); jit.ret(); }); constexpr size_t numberOfBits = sizeof(uint64_t) * 8; auto expectedNumberOfTrailingZeros = [=] (uint64_t word) -> size_t { size_t count = 0; for (size_t i = 0; i < numberOfBits; ++i) { if (word & 1) break; word >>= 1; count++; } return count; }; for (auto word : int64Operands()) { if (!wordCanBeZero && !word) continue; CHECK_EQ(invoke(test, word), expectedNumberOfTrailingZeros(word)); } for (size_t i = 0; i < numberOfBits; ++i) { uint64_t one = 1; uint64_t word = one << i; CHECK_EQ(invoke(test, word), i); } } void testCountTrailingZeros64() { bool wordCanBeZero = true; testCountTrailingZeros64Impl(wordCanBeZero); } void testCountTrailingZeros64WithoutNullCheck() { bool wordCanBeZero = false; testCountTrailingZeros64Impl(wordCanBeZero); } void testShiftAndAdd() { constexpr intptr_t basePointer = 0x1234abcd; enum class Reg { ArgumentGPR0, ArgumentGPR1, ArgumentGPR2, ArgumentGPR3, ScratchGPR }; auto test = [&] (intptr_t index, uint8_t shift, Reg destReg, Reg baseReg, Reg indexReg) { auto test = compile([=] (CCallHelpers& jit) { CCallHelpers::RegisterID scratchGPR = jit.scratchRegister(); auto registerIDForReg = [=] (Reg reg) -> CCallHelpers::RegisterID { switch (reg) { case Reg::ArgumentGPR0: return GPRInfo::argumentGPR0; case Reg::ArgumentGPR1: return GPRInfo::argumentGPR1; case Reg::ArgumentGPR2: return GPRInfo::argumentGPR2; case Reg::ArgumentGPR3: return GPRInfo::argumentGPR3; case Reg::ScratchGPR: return scratchGPR; } RELEASE_ASSERT_NOT_REACHED(); }; CCallHelpers::RegisterID destGPR = registerIDForReg(destReg); CCallHelpers::RegisterID baseGPR = registerIDForReg(baseReg); CCallHelpers::RegisterID indexGPR = registerIDForReg(indexReg); emitFunctionPrologue(jit); jit.pushPair(scratchGPR, GPRInfo::argumentGPR3); jit.move(CCallHelpers::TrustedImmPtr(bitwise_cast(basePointer)), baseGPR); jit.move(CCallHelpers::TrustedImmPtr(bitwise_cast(index)), indexGPR); jit.shiftAndAdd(baseGPR, indexGPR, shift, destGPR); #if ENABLE(MASM_PROBE) jit.probe([=] (Probe::Context& context) { if (baseReg != destReg) CHECK_EQ(context.gpr(baseGPR), basePointer); if (indexReg != destReg) CHECK_EQ(context.gpr(indexGPR), index); }); #endif jit.move(destGPR, GPRInfo::returnValueGPR); jit.popPair(scratchGPR, GPRInfo::argumentGPR3); emitFunctionEpilogue(jit); jit.ret(); }); CHECK_EQ(invoke(test), basePointer + (index << shift)); }; for (auto index : int32Operands()) { for (uint8_t shift = 0; shift < 32; ++shift) { test(index, shift, Reg::ScratchGPR, Reg::ScratchGPR, Reg::ArgumentGPR3); // Scenario: dest == base == scratchRegister. test(index, shift, Reg::ArgumentGPR2, Reg::ArgumentGPR2, Reg::ArgumentGPR3); // Scenario: dest == base != scratchRegister. test(index, shift, Reg::ScratchGPR, Reg::ArgumentGPR2, Reg::ScratchGPR); // Scenario: dest == index == scratchRegister. test(index, shift, Reg::ArgumentGPR3, Reg::ArgumentGPR2, Reg::ArgumentGPR3); // Scenario: dest == index != scratchRegister. test(index, shift, Reg::ArgumentGPR1, Reg::ArgumentGPR2, Reg::ArgumentGPR3); // Scenario: all different registers, no scratchRegister. test(index, shift, Reg::ScratchGPR, Reg::ArgumentGPR2, Reg::ArgumentGPR3); // Scenario: all different registers, dest == scratchRegister. test(index, shift, Reg::ArgumentGPR1, Reg::ScratchGPR, Reg::ArgumentGPR3); // Scenario: all different registers, base == scratchRegister. test(index, shift, Reg::ArgumentGPR1, Reg::ArgumentGPR2, Reg::ScratchGPR); // Scenario: all different registers, index == scratchRegister. } } } void testStore64Imm64AddressPointer() { auto doTest = [] (int64_t value) { int64_t dest; void* destAddress = &dest; auto test = compile([=] (CCallHelpers& jit) { emitFunctionPrologue(jit); jit.store64(CCallHelpers::TrustedImm64(value), destAddress); emitFunctionEpilogue(jit); jit.ret(); }); invoke(test); CHECK_EQ(dest, value); }; for (auto value : int64Operands()) doTest(value); doTest(0x98765555AAAA4321); doTest(0xAAAA432198765555); } #endif // CPU(X86_64) || CPU(ARM64) void testCompareDouble(MacroAssembler::DoubleCondition condition) { double arg1 = 0; double arg2 = 0; auto compareDouble = compile([&, condition] (CCallHelpers& jit) { emitFunctionPrologue(jit); jit.loadDouble(CCallHelpers::TrustedImmPtr(&arg1), FPRInfo::fpRegT0); jit.loadDouble(CCallHelpers::TrustedImmPtr(&arg2), FPRInfo::fpRegT1); jit.move(CCallHelpers::TrustedImm32(-1), GPRInfo::returnValueGPR); jit.compareDouble(condition, FPRInfo::fpRegT0, FPRInfo::fpRegT1, GPRInfo::returnValueGPR); emitFunctionEpilogue(jit); jit.ret(); }); auto compareDoubleGeneric = compile([&, condition] (CCallHelpers& jit) { emitFunctionPrologue(jit); jit.loadDouble(CCallHelpers::TrustedImmPtr(&arg1), FPRInfo::fpRegT0); jit.loadDouble(CCallHelpers::TrustedImmPtr(&arg2), FPRInfo::fpRegT1); jit.move(CCallHelpers::TrustedImm32(1), GPRInfo::returnValueGPR); auto jump = jit.branchDouble(condition, FPRInfo::fpRegT0, FPRInfo::fpRegT1); jit.move(CCallHelpers::TrustedImm32(0), GPRInfo::returnValueGPR); jump.link(&jit); emitFunctionEpilogue(jit); jit.ret(); }); auto expectedResult = [&, condition] (double a, double b) -> int { auto isUnordered = [] (double x) { return x != x; }; switch (condition) { case MacroAssembler::DoubleEqualAndOrdered: return !isUnordered(a) && !isUnordered(b) && (a == b); case MacroAssembler::DoubleNotEqualAndOrdered: return !isUnordered(a) && !isUnordered(b) && (a != b); case MacroAssembler::DoubleGreaterThanAndOrdered: return !isUnordered(a) && !isUnordered(b) && (a > b); case MacroAssembler::DoubleGreaterThanOrEqualAndOrdered: return !isUnordered(a) && !isUnordered(b) && (a >= b); case MacroAssembler::DoubleLessThanAndOrdered: return !isUnordered(a) && !isUnordered(b) && (a < b); case MacroAssembler::DoubleLessThanOrEqualAndOrdered: return !isUnordered(a) && !isUnordered(b) && (a <= b); case MacroAssembler::DoubleEqualOrUnordered: return isUnordered(a) || isUnordered(b) || (a == b); case MacroAssembler::DoubleNotEqualOrUnordered: return isUnordered(a) || isUnordered(b) || (a != b); case MacroAssembler::DoubleGreaterThanOrUnordered: return isUnordered(a) || isUnordered(b) || (a > b); case MacroAssembler::DoubleGreaterThanOrEqualOrUnordered: return isUnordered(a) || isUnordered(b) || (a >= b); case MacroAssembler::DoubleLessThanOrUnordered: return isUnordered(a) || isUnordered(b) || (a < b); case MacroAssembler::DoubleLessThanOrEqualOrUnordered: return isUnordered(a) || isUnordered(b) || (a <= b); } // switch RELEASE_ASSERT_NOT_REACHED(); }; auto operands = doubleOperands(); for (auto a : operands) { for (auto b : operands) { arg1 = a; arg2 = b; CHECK_EQ(invoke(compareDouble), expectedResult(a, b)); CHECK_EQ(invoke(compareDoubleGeneric), expectedResult(a, b)); } } } void testCompareDoubleSameArg(MacroAssembler::DoubleCondition condition) { double arg1 = 0; auto compareDouble = compile([&, condition] (CCallHelpers& jit) { emitFunctionPrologue(jit); jit.loadDouble(CCallHelpers::TrustedImmPtr(&arg1), FPRInfo::fpRegT0); jit.move(CCallHelpers::TrustedImm32(-1), GPRInfo::returnValueGPR); jit.compareDouble(condition, FPRInfo::fpRegT0, FPRInfo::fpRegT0, GPRInfo::returnValueGPR); emitFunctionEpilogue(jit); jit.ret(); }); auto compareDoubleGeneric = compile([&, condition] (CCallHelpers& jit) { emitFunctionPrologue(jit); jit.loadDouble(CCallHelpers::TrustedImmPtr(&arg1), FPRInfo::fpRegT0); jit.move(CCallHelpers::TrustedImm32(1), GPRInfo::returnValueGPR); auto jump = jit.branchDouble(condition, FPRInfo::fpRegT0, FPRInfo::fpRegT0); jit.move(CCallHelpers::TrustedImm32(0), GPRInfo::returnValueGPR); jump.link(&jit); emitFunctionEpilogue(jit); jit.ret(); }); auto expectedResult = [&, condition] (double a) -> int { auto isUnordered = [] (double x) { return x != x; }; switch (condition) { case MacroAssembler::DoubleEqualAndOrdered: return !isUnordered(a) && (a == a); case MacroAssembler::DoubleNotEqualAndOrdered: return !isUnordered(a) && (a != a); case MacroAssembler::DoubleGreaterThanAndOrdered: return !isUnordered(a) && (a > a); case MacroAssembler::DoubleGreaterThanOrEqualAndOrdered: return !isUnordered(a) && (a >= a); case MacroAssembler::DoubleLessThanAndOrdered: return !isUnordered(a) && (a < a); case MacroAssembler::DoubleLessThanOrEqualAndOrdered: return !isUnordered(a) && (a <= a); case MacroAssembler::DoubleEqualOrUnordered: return isUnordered(a) || (a == a); case MacroAssembler::DoubleNotEqualOrUnordered: return isUnordered(a) || (a != a); case MacroAssembler::DoubleGreaterThanOrUnordered: return isUnordered(a) || (a > a); case MacroAssembler::DoubleGreaterThanOrEqualOrUnordered: return isUnordered(a) || (a >= a); case MacroAssembler::DoubleLessThanOrUnordered: return isUnordered(a) || (a < a); case MacroAssembler::DoubleLessThanOrEqualOrUnordered: return isUnordered(a) || (a <= a); } // switch RELEASE_ASSERT_NOT_REACHED(); }; auto operands = doubleOperands(); for (auto a : operands) { arg1 = a; CHECK_EQ(invoke(compareDouble), expectedResult(a)); CHECK_EQ(invoke(compareDoubleGeneric), expectedResult(a)); } } void testMul32WithImmediates() { for (auto immediate : int32Operands()) { auto mul = compile([=] (CCallHelpers& jit) { emitFunctionPrologue(jit); jit.mul32(CCallHelpers::TrustedImm32(immediate), GPRInfo::argumentGPR0, GPRInfo::returnValueGPR); emitFunctionEpilogue(jit); jit.ret(); }); for (auto value : int32Operands()) CHECK_EQ(invoke(mul, value), immediate * value); } } #if CPU(ARM64) void testMul32SignExtend() { for (auto value : int32Operands()) { auto mul = compile([=] (CCallHelpers& jit) { emitFunctionPrologue(jit); jit.multiplySignExtend32(GPRInfo::argumentGPR0, GPRInfo::argumentGPR1, GPRInfo::returnValueGPR); emitFunctionEpilogue(jit); jit.ret(); }); for (auto value2 : int32Operands()) CHECK_EQ(invoke(mul, value, value2), ((long int) value) * ((long int) value2)); } } #endif #if CPU(X86) || CPU(X86_64) || CPU(ARM64) void testCompareFloat(MacroAssembler::DoubleCondition condition) { float arg1 = 0; float arg2 = 0; auto compareFloat = compile([&, condition] (CCallHelpers& jit) { emitFunctionPrologue(jit); jit.loadFloat(CCallHelpers::TrustedImmPtr(&arg1), FPRInfo::fpRegT0); jit.loadFloat(CCallHelpers::TrustedImmPtr(&arg2), FPRInfo::fpRegT1); jit.move(CCallHelpers::TrustedImm32(-1), GPRInfo::returnValueGPR); jit.compareFloat(condition, FPRInfo::fpRegT0, FPRInfo::fpRegT1, GPRInfo::returnValueGPR); emitFunctionEpilogue(jit); jit.ret(); }); auto compareFloatGeneric = compile([&, condition] (CCallHelpers& jit) { emitFunctionPrologue(jit); jit.loadFloat(CCallHelpers::TrustedImmPtr(&arg1), FPRInfo::fpRegT0); jit.loadFloat(CCallHelpers::TrustedImmPtr(&arg2), FPRInfo::fpRegT1); jit.move(CCallHelpers::TrustedImm32(1), GPRInfo::returnValueGPR); auto jump = jit.branchFloat(condition, FPRInfo::fpRegT0, FPRInfo::fpRegT1); jit.move(CCallHelpers::TrustedImm32(0), GPRInfo::returnValueGPR); jump.link(&jit); emitFunctionEpilogue(jit); jit.ret(); }); auto operands = floatOperands(); for (auto a : operands) { for (auto b : operands) { arg1 = a; arg2 = b; CHECK_EQ(invoke(compareFloat), invoke(compareFloatGeneric)); } } } #endif // CPU(X86) || CPU(X86_64) || CPU(ARM64) #if CPU(X86_64) || CPU(ARM64) template void testMoveConditionallyFloatingPoint(MacroAssembler::DoubleCondition condition, const MacroAssemblerCodeRef& testCode, T& arg1, T& arg2, const Vector operands, SelectionType selectionA, SelectionType selectionB) { auto expectedResult = [&, condition] (T a, T b) -> SelectionType { auto isUnordered = [] (double x) { return x != x; }; switch (condition) { case MacroAssembler::DoubleEqualAndOrdered: return !isUnordered(a) && !isUnordered(b) && (a == b) ? selectionA : selectionB; case MacroAssembler::DoubleNotEqualAndOrdered: return !isUnordered(a) && !isUnordered(b) && (a != b) ? selectionA : selectionB; case MacroAssembler::DoubleGreaterThanAndOrdered: return !isUnordered(a) && !isUnordered(b) && (a > b) ? selectionA : selectionB; case MacroAssembler::DoubleGreaterThanOrEqualAndOrdered: return !isUnordered(a) && !isUnordered(b) && (a >= b) ? selectionA : selectionB; case MacroAssembler::DoubleLessThanAndOrdered: return !isUnordered(a) && !isUnordered(b) && (a < b) ? selectionA : selectionB; case MacroAssembler::DoubleLessThanOrEqualAndOrdered: return !isUnordered(a) && !isUnordered(b) && (a <= b) ? selectionA : selectionB; case MacroAssembler::DoubleEqualOrUnordered: return isUnordered(a) || isUnordered(b) || (a == b) ? selectionA : selectionB; case MacroAssembler::DoubleNotEqualOrUnordered: return isUnordered(a) || isUnordered(b) || (a != b) ? selectionA : selectionB; case MacroAssembler::DoubleGreaterThanOrUnordered: return isUnordered(a) || isUnordered(b) || (a > b) ? selectionA : selectionB; case MacroAssembler::DoubleGreaterThanOrEqualOrUnordered: return isUnordered(a) || isUnordered(b) || (a >= b) ? selectionA : selectionB; case MacroAssembler::DoubleLessThanOrUnordered: return isUnordered(a) || isUnordered(b) || (a < b) ? selectionA : selectionB; case MacroAssembler::DoubleLessThanOrEqualOrUnordered: return isUnordered(a) || isUnordered(b) || (a <= b) ? selectionA : selectionB; } // switch RELEASE_ASSERT_NOT_REACHED(); }; for (auto a : operands) { for (auto b : operands) { arg1 = a; arg2 = b; CHECK_EQ(invoke(testCode), expectedResult(a, b)); } } } void testMoveConditionallyDouble2(MacroAssembler::DoubleCondition condition) { double arg1 = 0; double arg2 = 0; unsigned selectionA = 42; unsigned selectionB = 17; auto testCode = compile([&, condition] (CCallHelpers& jit) { emitFunctionPrologue(jit); GPRReg destGPR = GPRInfo::returnValueGPR; GPRReg selectionAGPR = GPRInfo::argumentGPR2; RELEASE_ASSERT(destGPR != selectionAGPR); jit.move(CCallHelpers::TrustedImm32(selectionA), selectionAGPR); jit.move(CCallHelpers::TrustedImm32(selectionB), destGPR); jit.loadDouble(CCallHelpers::TrustedImmPtr(&arg1), FPRInfo::fpRegT0); jit.loadDouble(CCallHelpers::TrustedImmPtr(&arg2), FPRInfo::fpRegT1); jit.moveConditionallyDouble(condition, FPRInfo::fpRegT0, FPRInfo::fpRegT1, selectionAGPR, destGPR); emitFunctionEpilogue(jit); jit.ret(); }); testMoveConditionallyFloatingPoint(condition, testCode, arg1, arg2, doubleOperands(), selectionA, selectionB); } void testMoveConditionallyDouble3(MacroAssembler::DoubleCondition condition) { double arg1 = 0; double arg2 = 0; unsigned selectionA = 42; unsigned selectionB = 17; unsigned corruptedSelectionA = 0xbbad000a; unsigned corruptedSelectionB = 0xbbad000b; auto testCode = compile([&, condition] (CCallHelpers& jit) { emitFunctionPrologue(jit); GPRReg destGPR = GPRInfo::returnValueGPR; GPRReg selectionAGPR = GPRInfo::argumentGPR2; GPRReg selectionBGPR = GPRInfo::argumentGPR3; RELEASE_ASSERT(destGPR != selectionAGPR); RELEASE_ASSERT(destGPR != selectionBGPR); jit.move(CCallHelpers::TrustedImm32(selectionA), selectionAGPR); jit.move(CCallHelpers::TrustedImm32(selectionB), selectionBGPR); jit.move(CCallHelpers::TrustedImm32(-1), destGPR); jit.loadDouble(CCallHelpers::TrustedImmPtr(&arg1), FPRInfo::fpRegT0); jit.loadDouble(CCallHelpers::TrustedImmPtr(&arg2), FPRInfo::fpRegT1); jit.moveConditionallyDouble(condition, FPRInfo::fpRegT0, FPRInfo::fpRegT1, selectionAGPR, selectionBGPR, destGPR); auto aIsUnchanged = jit.branch32(CCallHelpers::Equal, selectionAGPR, CCallHelpers::TrustedImm32(selectionA)); jit.move(CCallHelpers::TrustedImm32(corruptedSelectionA), destGPR); aIsUnchanged.link(&jit); auto bIsUnchanged = jit.branch32(CCallHelpers::Equal, selectionBGPR, CCallHelpers::TrustedImm32(selectionB)); jit.move(CCallHelpers::TrustedImm32(corruptedSelectionB), destGPR); bIsUnchanged.link(&jit); emitFunctionEpilogue(jit); jit.ret(); }); testMoveConditionallyFloatingPoint(condition, testCode, arg1, arg2, doubleOperands(), selectionA, selectionB); } void testMoveConditionallyDouble3DestSameAsThenCase(MacroAssembler::DoubleCondition condition) { double arg1 = 0; double arg2 = 0; unsigned selectionA = 42; unsigned selectionB = 17; unsigned corruptedSelectionB = 0xbbad000b; auto testCode = compile([&, condition] (CCallHelpers& jit) { emitFunctionPrologue(jit); GPRReg destGPR = GPRInfo::returnValueGPR; GPRReg selectionAGPR = destGPR; GPRReg selectionBGPR = GPRInfo::argumentGPR3; RELEASE_ASSERT(destGPR == selectionAGPR); RELEASE_ASSERT(destGPR != selectionBGPR); jit.move(CCallHelpers::TrustedImm32(selectionA), selectionAGPR); jit.move(CCallHelpers::TrustedImm32(selectionB), selectionBGPR); jit.loadDouble(CCallHelpers::TrustedImmPtr(&arg1), FPRInfo::fpRegT0); jit.loadDouble(CCallHelpers::TrustedImmPtr(&arg2), FPRInfo::fpRegT1); jit.moveConditionallyDouble(condition, FPRInfo::fpRegT0, FPRInfo::fpRegT1, selectionAGPR, selectionBGPR, destGPR); auto bIsUnchanged = jit.branch32(CCallHelpers::Equal, selectionBGPR, CCallHelpers::TrustedImm32(selectionB)); jit.move(CCallHelpers::TrustedImm32(corruptedSelectionB), destGPR); bIsUnchanged.link(&jit); emitFunctionEpilogue(jit); jit.ret(); }); testMoveConditionallyFloatingPoint(condition, testCode, arg1, arg2, doubleOperands(), selectionA, selectionB); } void testMoveConditionallyDouble3DestSameAsElseCase(MacroAssembler::DoubleCondition condition) { double arg1 = 0; double arg2 = 0; unsigned selectionA = 42; unsigned selectionB = 17; unsigned corruptedSelectionA = 0xbbad000a; auto testCode = compile([&, condition] (CCallHelpers& jit) { emitFunctionPrologue(jit); GPRReg destGPR = GPRInfo::returnValueGPR; GPRReg selectionAGPR = GPRInfo::argumentGPR2; GPRReg selectionBGPR = destGPR; RELEASE_ASSERT(destGPR != selectionAGPR); RELEASE_ASSERT(destGPR == selectionBGPR); jit.move(CCallHelpers::TrustedImm32(selectionA), selectionAGPR); jit.move(CCallHelpers::TrustedImm32(selectionB), selectionBGPR); jit.loadDouble(CCallHelpers::TrustedImmPtr(&arg1), FPRInfo::fpRegT0); jit.loadDouble(CCallHelpers::TrustedImmPtr(&arg2), FPRInfo::fpRegT1); jit.moveConditionallyDouble(condition, FPRInfo::fpRegT0, FPRInfo::fpRegT1, selectionAGPR, selectionBGPR, destGPR); auto aIsUnchanged = jit.branch32(CCallHelpers::Equal, selectionAGPR, CCallHelpers::TrustedImm32(selectionA)); jit.move(CCallHelpers::TrustedImm32(corruptedSelectionA), destGPR); aIsUnchanged.link(&jit); emitFunctionEpilogue(jit); jit.ret(); }); testMoveConditionallyFloatingPoint(condition, testCode, arg1, arg2, doubleOperands(), selectionA, selectionB); } void testMoveConditionallyFloat2(MacroAssembler::DoubleCondition condition) { float arg1 = 0; float arg2 = 0; unsigned selectionA = 42; unsigned selectionB = 17; auto testCode = compile([&, condition] (CCallHelpers& jit) { emitFunctionPrologue(jit); GPRReg destGPR = GPRInfo::returnValueGPR; GPRReg selectionAGPR = GPRInfo::argumentGPR2; RELEASE_ASSERT(destGPR != selectionAGPR); jit.move(CCallHelpers::TrustedImm32(selectionA), selectionAGPR); jit.move(CCallHelpers::TrustedImm32(selectionB), GPRInfo::returnValueGPR); jit.loadFloat(CCallHelpers::TrustedImmPtr(&arg1), FPRInfo::fpRegT0); jit.loadFloat(CCallHelpers::TrustedImmPtr(&arg2), FPRInfo::fpRegT1); jit.moveConditionallyFloat(condition, FPRInfo::fpRegT0, FPRInfo::fpRegT1, selectionAGPR, destGPR); emitFunctionEpilogue(jit); jit.ret(); }); testMoveConditionallyFloatingPoint(condition, testCode, arg1, arg2, floatOperands(), selectionA, selectionB); } void testMoveConditionallyFloat3(MacroAssembler::DoubleCondition condition) { float arg1 = 0; float arg2 = 0; unsigned selectionA = 42; unsigned selectionB = 17; unsigned corruptedSelectionA = 0xbbad000a; unsigned corruptedSelectionB = 0xbbad000b; auto testCode = compile([&, condition] (CCallHelpers& jit) { emitFunctionPrologue(jit); GPRReg destGPR = GPRInfo::returnValueGPR; GPRReg selectionAGPR = GPRInfo::argumentGPR2; GPRReg selectionBGPR = GPRInfo::argumentGPR3; RELEASE_ASSERT(destGPR != selectionAGPR); RELEASE_ASSERT(destGPR != selectionBGPR); jit.move(CCallHelpers::TrustedImm32(selectionA), selectionAGPR); jit.move(CCallHelpers::TrustedImm32(selectionB), selectionBGPR); jit.move(CCallHelpers::TrustedImm32(-1), destGPR); jit.loadFloat(CCallHelpers::TrustedImmPtr(&arg1), FPRInfo::fpRegT0); jit.loadFloat(CCallHelpers::TrustedImmPtr(&arg2), FPRInfo::fpRegT1); jit.moveConditionallyFloat(condition, FPRInfo::fpRegT0, FPRInfo::fpRegT1, selectionAGPR, selectionBGPR, destGPR); auto aIsUnchanged = jit.branch32(CCallHelpers::Equal, selectionAGPR, CCallHelpers::TrustedImm32(selectionA)); jit.move(CCallHelpers::TrustedImm32(corruptedSelectionA), destGPR); aIsUnchanged.link(&jit); auto bIsUnchanged = jit.branch32(CCallHelpers::Equal, selectionBGPR, CCallHelpers::TrustedImm32(selectionB)); jit.move(CCallHelpers::TrustedImm32(corruptedSelectionB), destGPR); bIsUnchanged.link(&jit); emitFunctionEpilogue(jit); jit.ret(); }); testMoveConditionallyFloatingPoint(condition, testCode, arg1, arg2, floatOperands(), selectionA, selectionB); } void testMoveConditionallyFloat3DestSameAsThenCase(MacroAssembler::DoubleCondition condition) { float arg1 = 0; float arg2 = 0; unsigned selectionA = 42; unsigned selectionB = 17; unsigned corruptedSelectionB = 0xbbad000b; auto testCode = compile([&, condition] (CCallHelpers& jit) { emitFunctionPrologue(jit); GPRReg destGPR = GPRInfo::returnValueGPR; GPRReg selectionAGPR = destGPR; GPRReg selectionBGPR = GPRInfo::argumentGPR3; RELEASE_ASSERT(destGPR == selectionAGPR); RELEASE_ASSERT(destGPR != selectionBGPR); jit.move(CCallHelpers::TrustedImm32(selectionA), selectionAGPR); jit.move(CCallHelpers::TrustedImm32(selectionB), selectionBGPR); jit.loadFloat(CCallHelpers::TrustedImmPtr(&arg1), FPRInfo::fpRegT0); jit.loadFloat(CCallHelpers::TrustedImmPtr(&arg2), FPRInfo::fpRegT1); jit.moveConditionallyFloat(condition, FPRInfo::fpRegT0, FPRInfo::fpRegT1, selectionAGPR, selectionBGPR, destGPR); auto bIsUnchanged = jit.branch32(CCallHelpers::Equal, selectionBGPR, CCallHelpers::TrustedImm32(selectionB)); jit.move(CCallHelpers::TrustedImm32(corruptedSelectionB), destGPR); bIsUnchanged.link(&jit); emitFunctionEpilogue(jit); jit.ret(); }); testMoveConditionallyFloatingPoint(condition, testCode, arg1, arg2, floatOperands(), selectionA, selectionB); } void testMoveConditionallyFloat3DestSameAsElseCase(MacroAssembler::DoubleCondition condition) { float arg1 = 0; float arg2 = 0; unsigned selectionA = 42; unsigned selectionB = 17; unsigned corruptedSelectionA = 0xbbad000a; auto testCode = compile([&, condition] (CCallHelpers& jit) { emitFunctionPrologue(jit); GPRReg destGPR = GPRInfo::returnValueGPR; GPRReg selectionAGPR = GPRInfo::argumentGPR2; GPRReg selectionBGPR = destGPR; RELEASE_ASSERT(destGPR != selectionAGPR); RELEASE_ASSERT(destGPR == selectionBGPR); jit.move(CCallHelpers::TrustedImm32(selectionA), selectionAGPR); jit.move(CCallHelpers::TrustedImm32(selectionB), selectionBGPR); jit.loadFloat(CCallHelpers::TrustedImmPtr(&arg1), FPRInfo::fpRegT0); jit.loadFloat(CCallHelpers::TrustedImmPtr(&arg2), FPRInfo::fpRegT1); jit.moveConditionallyFloat(condition, FPRInfo::fpRegT0, FPRInfo::fpRegT1, selectionAGPR, selectionBGPR, destGPR); auto aIsUnchanged = jit.branch32(CCallHelpers::Equal, selectionAGPR, CCallHelpers::TrustedImm32(selectionA)); jit.move(CCallHelpers::TrustedImm32(corruptedSelectionA), destGPR); aIsUnchanged.link(&jit); emitFunctionEpilogue(jit); jit.ret(); }); testMoveConditionallyFloatingPoint(condition, testCode, arg1, arg2, floatOperands(), selectionA, selectionB); } void testMoveDoubleConditionallyDouble(MacroAssembler::DoubleCondition condition) { double arg1 = 0; double arg2 = 0; double selectionA = 42.0; double selectionB = 17.0; double corruptedSelectionA = 55555; double corruptedSelectionB = 66666; auto testCode = compile([&, condition] (CCallHelpers& jit) { emitFunctionPrologue(jit); FPRReg destFPR = FPRInfo::returnValueFPR; FPRReg selectionAFPR = FPRInfo::fpRegT1; FPRReg selectionBFPR = FPRInfo::fpRegT2; FPRReg arg1FPR = FPRInfo::fpRegT3; FPRReg arg2FPR = FPRInfo::fpRegT4; RELEASE_ASSERT(destFPR != selectionAFPR); RELEASE_ASSERT(destFPR != selectionBFPR); RELEASE_ASSERT(destFPR != arg1FPR); RELEASE_ASSERT(destFPR != arg2FPR); jit.loadDouble(CCallHelpers::TrustedImmPtr(&arg1), arg1FPR); jit.loadDouble(CCallHelpers::TrustedImmPtr(&arg2), arg2FPR); jit.loadDouble(CCallHelpers::TrustedImmPtr(&selectionA), selectionAFPR); jit.loadDouble(CCallHelpers::TrustedImmPtr(&selectionB), selectionBFPR); jit.moveDoubleConditionallyDouble(condition, arg1FPR, arg2FPR, selectionAFPR, selectionBFPR, destFPR); FPRReg tempFPR = FPRInfo::fpRegT5; jit.loadDouble(CCallHelpers::TrustedImmPtr(&selectionA), tempFPR); auto aIsUnchanged = jit.branchDouble(CCallHelpers::DoubleEqualAndOrdered, selectionAFPR, tempFPR); jit.loadDouble(CCallHelpers::TrustedImmPtr(&corruptedSelectionA), destFPR); aIsUnchanged.link(&jit); jit.loadDouble(CCallHelpers::TrustedImmPtr(&selectionB), tempFPR); auto bIsUnchanged = jit.branchDouble(CCallHelpers::DoubleEqualAndOrdered, selectionBFPR, tempFPR); jit.loadDouble(CCallHelpers::TrustedImmPtr(&corruptedSelectionB), destFPR); bIsUnchanged.link(&jit); emitFunctionEpilogue(jit); jit.ret(); }); testMoveConditionallyFloatingPoint(condition, testCode, arg1, arg2, doubleOperands(), selectionA, selectionB); } void testMoveDoubleConditionallyDoubleDestSameAsThenCase(MacroAssembler::DoubleCondition condition) { double arg1 = 0; double arg2 = 0; double selectionA = 42.0; double selectionB = 17.0; double corruptedSelectionB = 66666; auto testCode = compile([&, condition] (CCallHelpers& jit) { emitFunctionPrologue(jit); FPRReg destFPR = FPRInfo::returnValueFPR; FPRReg selectionAFPR = destFPR; FPRReg selectionBFPR = FPRInfo::fpRegT2; FPRReg arg1FPR = FPRInfo::fpRegT3; FPRReg arg2FPR = FPRInfo::fpRegT4; RELEASE_ASSERT(destFPR == selectionAFPR); RELEASE_ASSERT(destFPR != selectionBFPR); RELEASE_ASSERT(destFPR != arg1FPR); RELEASE_ASSERT(destFPR != arg2FPR); jit.loadDouble(CCallHelpers::TrustedImmPtr(&arg1), arg1FPR); jit.loadDouble(CCallHelpers::TrustedImmPtr(&arg2), arg2FPR); jit.loadDouble(CCallHelpers::TrustedImmPtr(&selectionA), selectionAFPR); jit.loadDouble(CCallHelpers::TrustedImmPtr(&selectionB), selectionBFPR); jit.moveDoubleConditionallyDouble(condition, arg1FPR, arg2FPR, selectionAFPR, selectionBFPR, destFPR); FPRReg tempFPR = FPRInfo::fpRegT5; jit.loadDouble(CCallHelpers::TrustedImmPtr(&selectionB), tempFPR); auto bIsUnchanged = jit.branchDouble(CCallHelpers::DoubleEqualAndOrdered, selectionBFPR, tempFPR); jit.loadDouble(CCallHelpers::TrustedImmPtr(&corruptedSelectionB), destFPR); bIsUnchanged.link(&jit); emitFunctionEpilogue(jit); jit.ret(); }); testMoveConditionallyFloatingPoint(condition, testCode, arg1, arg2, doubleOperands(), selectionA, selectionB); } void testMoveDoubleConditionallyDoubleDestSameAsElseCase(MacroAssembler::DoubleCondition condition) { double arg1 = 0; double arg2 = 0; double selectionA = 42.0; double selectionB = 17.0; double corruptedSelectionA = 55555; auto testCode = compile([&, condition] (CCallHelpers& jit) { emitFunctionPrologue(jit); FPRReg destFPR = FPRInfo::returnValueFPR; FPRReg selectionAFPR = FPRInfo::fpRegT1; FPRReg selectionBFPR = destFPR; FPRReg arg1FPR = FPRInfo::fpRegT3; FPRReg arg2FPR = FPRInfo::fpRegT4; RELEASE_ASSERT(destFPR != selectionAFPR); RELEASE_ASSERT(destFPR == selectionBFPR); RELEASE_ASSERT(destFPR != arg1FPR); RELEASE_ASSERT(destFPR != arg2FPR); jit.loadDouble(CCallHelpers::TrustedImmPtr(&arg1), arg1FPR); jit.loadDouble(CCallHelpers::TrustedImmPtr(&arg2), arg2FPR); jit.loadDouble(CCallHelpers::TrustedImmPtr(&selectionA), selectionAFPR); jit.loadDouble(CCallHelpers::TrustedImmPtr(&selectionB), selectionBFPR); jit.moveDoubleConditionallyDouble(condition, arg1FPR, arg2FPR, selectionAFPR, selectionBFPR, destFPR); FPRReg tempFPR = FPRInfo::fpRegT5; jit.loadDouble(CCallHelpers::TrustedImmPtr(&selectionA), tempFPR); auto aIsUnchanged = jit.branchDouble(CCallHelpers::DoubleEqualAndOrdered, selectionAFPR, tempFPR); jit.loadDouble(CCallHelpers::TrustedImmPtr(&corruptedSelectionA), destFPR); aIsUnchanged.link(&jit); emitFunctionEpilogue(jit); jit.ret(); }); testMoveConditionallyFloatingPoint(condition, testCode, arg1, arg2, doubleOperands(), selectionA, selectionB); } void testMoveDoubleConditionallyFloat(MacroAssembler::DoubleCondition condition) { float arg1 = 0; float arg2 = 0; double selectionA = 42.0; double selectionB = 17.0; double corruptedSelectionA = 55555; double corruptedSelectionB = 66666; auto testCode = compile([&, condition] (CCallHelpers& jit) { emitFunctionPrologue(jit); FPRReg destFPR = FPRInfo::returnValueFPR; FPRReg selectionAFPR = FPRInfo::fpRegT1; FPRReg selectionBFPR = FPRInfo::fpRegT2; FPRReg arg1FPR = FPRInfo::fpRegT3; FPRReg arg2FPR = FPRInfo::fpRegT4; RELEASE_ASSERT(destFPR != selectionAFPR); RELEASE_ASSERT(destFPR != selectionBFPR); RELEASE_ASSERT(destFPR != arg1FPR); RELEASE_ASSERT(destFPR != arg2FPR); jit.loadFloat(CCallHelpers::TrustedImmPtr(&arg1), arg1FPR); jit.loadFloat(CCallHelpers::TrustedImmPtr(&arg2), arg2FPR); jit.loadDouble(CCallHelpers::TrustedImmPtr(&selectionA), selectionAFPR); jit.loadDouble(CCallHelpers::TrustedImmPtr(&selectionB), selectionBFPR); jit.moveDoubleConditionallyFloat(condition, arg1FPR, arg2FPR, selectionAFPR, selectionBFPR, destFPR); FPRReg tempFPR = FPRInfo::fpRegT5; jit.loadDouble(CCallHelpers::TrustedImmPtr(&selectionA), tempFPR); auto aIsUnchanged = jit.branchDouble(CCallHelpers::DoubleEqualAndOrdered, selectionAFPR, tempFPR); jit.loadDouble(CCallHelpers::TrustedImmPtr(&corruptedSelectionA), destFPR); aIsUnchanged.link(&jit); jit.loadDouble(CCallHelpers::TrustedImmPtr(&selectionB), tempFPR); auto bIsUnchanged = jit.branchDouble(CCallHelpers::DoubleEqualAndOrdered, selectionBFPR, tempFPR); jit.loadDouble(CCallHelpers::TrustedImmPtr(&corruptedSelectionB), destFPR); bIsUnchanged.link(&jit); emitFunctionEpilogue(jit); jit.ret(); }); testMoveConditionallyFloatingPoint(condition, testCode, arg1, arg2, floatOperands(), selectionA, selectionB); } void testMoveDoubleConditionallyFloatDestSameAsThenCase(MacroAssembler::DoubleCondition condition) { float arg1 = 0; float arg2 = 0; double selectionA = 42.0; double selectionB = 17.0; double corruptedSelectionB = 66666; auto testCode = compile([&, condition] (CCallHelpers& jit) { emitFunctionPrologue(jit); FPRReg destFPR = FPRInfo::returnValueFPR; FPRReg selectionAFPR = destFPR; FPRReg selectionBFPR = FPRInfo::fpRegT2; FPRReg arg1FPR = FPRInfo::fpRegT3; FPRReg arg2FPR = FPRInfo::fpRegT4; RELEASE_ASSERT(destFPR == selectionAFPR); RELEASE_ASSERT(destFPR != selectionBFPR); RELEASE_ASSERT(destFPR != arg1FPR); RELEASE_ASSERT(destFPR != arg2FPR); jit.loadFloat(CCallHelpers::TrustedImmPtr(&arg1), arg1FPR); jit.loadFloat(CCallHelpers::TrustedImmPtr(&arg2), arg2FPR); jit.loadDouble(CCallHelpers::TrustedImmPtr(&selectionA), selectionAFPR); jit.loadDouble(CCallHelpers::TrustedImmPtr(&selectionB), selectionBFPR); jit.moveDoubleConditionallyFloat(condition, arg1FPR, arg2FPR, selectionAFPR, selectionBFPR, destFPR); FPRReg tempFPR = FPRInfo::fpRegT5; jit.loadDouble(CCallHelpers::TrustedImmPtr(&selectionB), tempFPR); auto bIsUnchanged = jit.branchDouble(CCallHelpers::DoubleEqualAndOrdered, selectionBFPR, tempFPR); jit.loadDouble(CCallHelpers::TrustedImmPtr(&corruptedSelectionB), destFPR); bIsUnchanged.link(&jit); emitFunctionEpilogue(jit); jit.ret(); }); testMoveConditionallyFloatingPoint(condition, testCode, arg1, arg2, floatOperands(), selectionA, selectionB); } void testMoveDoubleConditionallyFloatDestSameAsElseCase(MacroAssembler::DoubleCondition condition) { float arg1 = 0; float arg2 = 0; double selectionA = 42.0; double selectionB = 17.0; double corruptedSelectionA = 55555; auto testCode = compile([&, condition] (CCallHelpers& jit) { emitFunctionPrologue(jit); FPRReg destFPR = FPRInfo::returnValueFPR; FPRReg selectionAFPR = FPRInfo::fpRegT1; FPRReg selectionBFPR = destFPR; FPRReg arg1FPR = FPRInfo::fpRegT3; FPRReg arg2FPR = FPRInfo::fpRegT4; RELEASE_ASSERT(destFPR != selectionAFPR); RELEASE_ASSERT(destFPR == selectionBFPR); RELEASE_ASSERT(destFPR != arg1FPR); RELEASE_ASSERT(destFPR != arg2FPR); jit.loadFloat(CCallHelpers::TrustedImmPtr(&arg1), arg1FPR); jit.loadFloat(CCallHelpers::TrustedImmPtr(&arg2), arg2FPR); jit.loadDouble(CCallHelpers::TrustedImmPtr(&selectionA), selectionAFPR); jit.loadDouble(CCallHelpers::TrustedImmPtr(&selectionB), selectionBFPR); jit.moveDoubleConditionallyFloat(condition, arg1FPR, arg2FPR, selectionAFPR, selectionBFPR, destFPR); FPRReg tempFPR = FPRInfo::fpRegT5; jit.loadDouble(CCallHelpers::TrustedImmPtr(&selectionA), tempFPR); auto aIsUnchanged = jit.branchDouble(CCallHelpers::DoubleEqualAndOrdered, selectionAFPR, tempFPR); jit.loadDouble(CCallHelpers::TrustedImmPtr(&corruptedSelectionA), destFPR); aIsUnchanged.link(&jit); emitFunctionEpilogue(jit); jit.ret(); }); testMoveConditionallyFloatingPoint(condition, testCode, arg1, arg2, floatOperands(), selectionA, selectionB); } template void testMoveConditionallyFloatingPointSameArg(MacroAssembler::DoubleCondition condition, const MacroAssemblerCodeRef& testCode, T& arg1, const Vector operands, SelectionType selectionA, SelectionType selectionB) { auto expectedResult = [&, condition] (T a) -> SelectionType { auto isUnordered = [] (double x) { return x != x; }; switch (condition) { case MacroAssembler::DoubleEqualAndOrdered: return !isUnordered(a) && (a == a) ? selectionA : selectionB; case MacroAssembler::DoubleNotEqualAndOrdered: return !isUnordered(a) && (a != a) ? selectionA : selectionB; case MacroAssembler::DoubleGreaterThanAndOrdered: return !isUnordered(a) && (a > a) ? selectionA : selectionB; case MacroAssembler::DoubleGreaterThanOrEqualAndOrdered: return !isUnordered(a) && (a >= a) ? selectionA : selectionB; case MacroAssembler::DoubleLessThanAndOrdered: return !isUnordered(a) && (a < a) ? selectionA : selectionB; case MacroAssembler::DoubleLessThanOrEqualAndOrdered: return !isUnordered(a) && (a <= a) ? selectionA : selectionB; case MacroAssembler::DoubleEqualOrUnordered: return isUnordered(a) || (a == a) ? selectionA : selectionB; case MacroAssembler::DoubleNotEqualOrUnordered: return isUnordered(a) || (a != a) ? selectionA : selectionB; case MacroAssembler::DoubleGreaterThanOrUnordered: return isUnordered(a) || (a > a) ? selectionA : selectionB; case MacroAssembler::DoubleGreaterThanOrEqualOrUnordered: return isUnordered(a) || (a >= a) ? selectionA : selectionB; case MacroAssembler::DoubleLessThanOrUnordered: return isUnordered(a) || (a < a) ? selectionA : selectionB; case MacroAssembler::DoubleLessThanOrEqualOrUnordered: return isUnordered(a) || (a <= a) ? selectionA : selectionB; } // switch RELEASE_ASSERT_NOT_REACHED(); }; for (auto a : operands) { arg1 = a; CHECK_EQ(invoke(testCode), expectedResult(a)); } } void testMoveConditionallyDouble2SameArg(MacroAssembler::DoubleCondition condition) { double arg1 = 0; unsigned selectionA = 42; unsigned selectionB = 17; auto testCode = compile([&, condition] (CCallHelpers& jit) { emitFunctionPrologue(jit); GPRReg selectionAGPR = GPRInfo::argumentGPR2; RELEASE_ASSERT(GPRInfo::returnValueGPR != selectionAGPR); jit.move(CCallHelpers::TrustedImm32(selectionA), selectionAGPR); jit.move(CCallHelpers::TrustedImm32(selectionB), GPRInfo::returnValueGPR); jit.loadDouble(CCallHelpers::TrustedImmPtr(&arg1), FPRInfo::fpRegT0); jit.moveConditionallyDouble(condition, FPRInfo::fpRegT0, FPRInfo::fpRegT0, selectionAGPR, GPRInfo::returnValueGPR); emitFunctionEpilogue(jit); jit.ret(); }); testMoveConditionallyFloatingPointSameArg(condition, testCode, arg1, doubleOperands(), selectionA, selectionB); } void testMoveConditionallyDouble3SameArg(MacroAssembler::DoubleCondition condition) { double arg1 = 0; unsigned selectionA = 42; unsigned selectionB = 17; auto testCode = compile([&, condition] (CCallHelpers& jit) { emitFunctionPrologue(jit); GPRReg selectionAGPR = GPRInfo::argumentGPR2; GPRReg selectionBGPR = GPRInfo::argumentGPR3; RELEASE_ASSERT(GPRInfo::returnValueGPR != selectionAGPR); RELEASE_ASSERT(GPRInfo::returnValueGPR != selectionBGPR); jit.move(CCallHelpers::TrustedImm32(selectionA), selectionAGPR); jit.move(CCallHelpers::TrustedImm32(selectionB), selectionBGPR); jit.move(CCallHelpers::TrustedImm32(-1), GPRInfo::returnValueGPR); jit.loadDouble(CCallHelpers::TrustedImmPtr(&arg1), FPRInfo::fpRegT0); jit.moveConditionallyDouble(condition, FPRInfo::fpRegT0, FPRInfo::fpRegT0, selectionAGPR, selectionBGPR, GPRInfo::returnValueGPR); emitFunctionEpilogue(jit); jit.ret(); }); testMoveConditionallyFloatingPointSameArg(condition, testCode, arg1, doubleOperands(), selectionA, selectionB); } void testMoveConditionallyFloat2SameArg(MacroAssembler::DoubleCondition condition) { float arg1 = 0; unsigned selectionA = 42; unsigned selectionB = 17; auto testCode = compile([&, condition] (CCallHelpers& jit) { emitFunctionPrologue(jit); GPRReg selectionAGPR = GPRInfo::argumentGPR2; RELEASE_ASSERT(GPRInfo::returnValueGPR != selectionAGPR); jit.move(CCallHelpers::TrustedImm32(selectionA), selectionAGPR); jit.move(CCallHelpers::TrustedImm32(selectionB), GPRInfo::returnValueGPR); jit.loadFloat(CCallHelpers::TrustedImmPtr(&arg1), FPRInfo::fpRegT0); jit.moveConditionallyFloat(condition, FPRInfo::fpRegT0, FPRInfo::fpRegT0, selectionAGPR, GPRInfo::returnValueGPR); emitFunctionEpilogue(jit); jit.ret(); }); testMoveConditionallyFloatingPointSameArg(condition, testCode, arg1, floatOperands(), selectionA, selectionB); } void testMoveConditionallyFloat3SameArg(MacroAssembler::DoubleCondition condition) { float arg1 = 0; unsigned selectionA = 42; unsigned selectionB = 17; auto testCode = compile([&, condition] (CCallHelpers& jit) { emitFunctionPrologue(jit); GPRReg selectionAGPR = GPRInfo::argumentGPR2; GPRReg selectionBGPR = GPRInfo::argumentGPR3; RELEASE_ASSERT(GPRInfo::returnValueGPR != selectionAGPR); RELEASE_ASSERT(GPRInfo::returnValueGPR != selectionBGPR); jit.move(CCallHelpers::TrustedImm32(selectionA), selectionAGPR); jit.move(CCallHelpers::TrustedImm32(selectionB), selectionBGPR); jit.move(CCallHelpers::TrustedImm32(-1), GPRInfo::returnValueGPR); jit.loadFloat(CCallHelpers::TrustedImmPtr(&arg1), FPRInfo::fpRegT0); jit.moveConditionallyFloat(condition, FPRInfo::fpRegT0, FPRInfo::fpRegT0, selectionAGPR, selectionBGPR, GPRInfo::returnValueGPR); emitFunctionEpilogue(jit); jit.ret(); }); testMoveConditionallyFloatingPointSameArg(condition, testCode, arg1, floatOperands(), selectionA, selectionB); } void testMoveDoubleConditionallyDoubleSameArg(MacroAssembler::DoubleCondition condition) { double arg1 = 0; double selectionA = 42.0; double selectionB = 17.0; auto testCode = compile([&, condition] (CCallHelpers& jit) { emitFunctionPrologue(jit); jit.loadDouble(CCallHelpers::TrustedImmPtr(&arg1), FPRInfo::fpRegT0); jit.loadDouble(CCallHelpers::TrustedImmPtr(&selectionA), FPRInfo::fpRegT2); jit.loadDouble(CCallHelpers::TrustedImmPtr(&selectionB), FPRInfo::fpRegT3); jit.moveDoubleConditionallyDouble(condition, FPRInfo::fpRegT0, FPRInfo::fpRegT0, FPRInfo::fpRegT2, FPRInfo::fpRegT3, FPRInfo::returnValueFPR); emitFunctionEpilogue(jit); jit.ret(); }); testMoveConditionallyFloatingPointSameArg(condition, testCode, arg1, doubleOperands(), selectionA, selectionB); } void testMoveDoubleConditionallyFloatSameArg(MacroAssembler::DoubleCondition condition) { float arg1 = 0; double selectionA = 42.0; double selectionB = 17.0; auto testCode = compile([&, condition] (CCallHelpers& jit) { emitFunctionPrologue(jit); jit.loadFloat(CCallHelpers::TrustedImmPtr(&arg1), FPRInfo::fpRegT0); jit.loadDouble(CCallHelpers::TrustedImmPtr(&selectionA), FPRInfo::fpRegT2); jit.loadDouble(CCallHelpers::TrustedImmPtr(&selectionB), FPRInfo::fpRegT3); jit.moveDoubleConditionallyFloat(condition, FPRInfo::fpRegT0, FPRInfo::fpRegT0, FPRInfo::fpRegT2, FPRInfo::fpRegT3, FPRInfo::returnValueFPR); emitFunctionEpilogue(jit); jit.ret(); }); testMoveConditionallyFloatingPointSameArg(condition, testCode, arg1, floatOperands(), selectionA, selectionB); } #endif // CPU(X86_64) || CPU(ARM64) #if ENABLE(MASM_PROBE) void testProbeReadsArgumentRegisters() { bool probeWasCalled = false; compileAndRun([&] (CCallHelpers& jit) { emitFunctionPrologue(jit); jit.pushPair(GPRInfo::argumentGPR0, GPRInfo::argumentGPR1); jit.pushPair(GPRInfo::argumentGPR2, GPRInfo::argumentGPR3); jit.move(CCallHelpers::TrustedImm32(testWord32(0)), GPRInfo::argumentGPR0); jit.convertInt32ToDouble(GPRInfo::argumentGPR0, FPRInfo::fpRegT0); jit.move(CCallHelpers::TrustedImm32(testWord32(1)), GPRInfo::argumentGPR0); jit.convertInt32ToDouble(GPRInfo::argumentGPR0, FPRInfo::fpRegT1); #if USE(JSVALUE64) jit.move(CCallHelpers::TrustedImm64(testWord(0)), GPRInfo::argumentGPR0); jit.move(CCallHelpers::TrustedImm64(testWord(1)), GPRInfo::argumentGPR1); jit.move(CCallHelpers::TrustedImm64(testWord(2)), GPRInfo::argumentGPR2); jit.move(CCallHelpers::TrustedImm64(testWord(3)), GPRInfo::argumentGPR3); #else jit.move(CCallHelpers::TrustedImm32(testWord(0)), GPRInfo::argumentGPR0); jit.move(CCallHelpers::TrustedImm32(testWord(1)), GPRInfo::argumentGPR1); jit.move(CCallHelpers::TrustedImm32(testWord(2)), GPRInfo::argumentGPR2); jit.move(CCallHelpers::TrustedImm32(testWord(3)), GPRInfo::argumentGPR3); #endif jit.probe([&] (Probe::Context& context) { auto& cpu = context.cpu; probeWasCalled = true; CHECK_EQ(cpu.gpr(GPRInfo::argumentGPR0), testWord(0)); CHECK_EQ(cpu.gpr(GPRInfo::argumentGPR1), testWord(1)); CHECK_EQ(cpu.gpr(GPRInfo::argumentGPR2), testWord(2)); CHECK_EQ(cpu.gpr(GPRInfo::argumentGPR3), testWord(3)); CHECK_EQ(cpu.fpr(FPRInfo::fpRegT0), testWord32(0)); CHECK_EQ(cpu.fpr(FPRInfo::fpRegT1), testWord32(1)); }); jit.popPair(GPRInfo::argumentGPR2, GPRInfo::argumentGPR3); jit.popPair(GPRInfo::argumentGPR0, GPRInfo::argumentGPR1); emitFunctionEpilogue(jit); jit.ret(); }); CHECK_EQ(probeWasCalled, true); } void testProbeWritesArgumentRegisters() { // This test relies on testProbeReadsArgumentRegisters() having already validated // that we can read from argument registers. We'll use that ability to validate // that our writes did take effect. unsigned probeCallCount = 0; compileAndRun([&] (CCallHelpers& jit) { emitFunctionPrologue(jit); jit.pushPair(GPRInfo::argumentGPR0, GPRInfo::argumentGPR1); jit.pushPair(GPRInfo::argumentGPR2, GPRInfo::argumentGPR3); // Pre-initialize with non-expected values. #if USE(JSVALUE64) jit.move(CCallHelpers::TrustedImm64(0), GPRInfo::argumentGPR0); jit.move(CCallHelpers::TrustedImm64(0), GPRInfo::argumentGPR1); jit.move(CCallHelpers::TrustedImm64(0), GPRInfo::argumentGPR2); jit.move(CCallHelpers::TrustedImm64(0), GPRInfo::argumentGPR3); #else jit.move(CCallHelpers::TrustedImm32(0), GPRInfo::argumentGPR0); jit.move(CCallHelpers::TrustedImm32(0), GPRInfo::argumentGPR1); jit.move(CCallHelpers::TrustedImm32(0), GPRInfo::argumentGPR2); jit.move(CCallHelpers::TrustedImm32(0), GPRInfo::argumentGPR3); #endif jit.convertInt32ToDouble(GPRInfo::argumentGPR0, FPRInfo::fpRegT0); jit.convertInt32ToDouble(GPRInfo::argumentGPR0, FPRInfo::fpRegT1); // Write expected values. jit.probe([&] (Probe::Context& context) { auto& cpu = context.cpu; probeCallCount++; cpu.gpr(GPRInfo::argumentGPR0) = testWord(0); cpu.gpr(GPRInfo::argumentGPR1) = testWord(1); cpu.gpr(GPRInfo::argumentGPR2) = testWord(2); cpu.gpr(GPRInfo::argumentGPR3) = testWord(3); cpu.fpr(FPRInfo::fpRegT0) = bitwise_cast(testWord64(0)); cpu.fpr(FPRInfo::fpRegT1) = bitwise_cast(testWord64(1)); }); // Validate that expected values were written. jit.probe([&] (Probe::Context& context) { auto& cpu = context.cpu; probeCallCount++; CHECK_EQ(cpu.gpr(GPRInfo::argumentGPR0), testWord(0)); CHECK_EQ(cpu.gpr(GPRInfo::argumentGPR1), testWord(1)); CHECK_EQ(cpu.gpr(GPRInfo::argumentGPR2), testWord(2)); CHECK_EQ(cpu.gpr(GPRInfo::argumentGPR3), testWord(3)); CHECK_EQ(cpu.fpr(FPRInfo::fpRegT0), testWord64(0)); CHECK_EQ(cpu.fpr(FPRInfo::fpRegT1), testWord64(1)); }); jit.popPair(GPRInfo::argumentGPR2, GPRInfo::argumentGPR3); jit.popPair(GPRInfo::argumentGPR0, GPRInfo::argumentGPR1); emitFunctionEpilogue(jit); jit.ret(); }); CHECK_EQ(probeCallCount, 2); } static NEVER_INLINE NOT_TAIL_CALLED int testFunctionToTrashGPRs(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j) { if (j > 0) return testFunctionToTrashGPRs(a + 1, b + a, c + b, d + 5, e - a, f * 1.5, g ^ a, h - b, i, j - 1); return a + 1; } static NEVER_INLINE NOT_TAIL_CALLED double testFunctionToTrashFPRs(double a, double b, double c, double d, double e, double f, double g, double h, double i, double j) { if (j > 0) return testFunctionToTrashFPRs(a + 1, b + a, c + b, d + 5, e - a, f * 1.5, pow(g, a), h - b, i, j - 1); return a + 1; } void testProbePreservesGPRS() { // This test relies on testProbeReadsArgumentRegisters() and testProbeWritesArgumentRegisters() // having already validated that we can read and write from registers. We'll use these abilities // to validate that the probe preserves register values. unsigned probeCallCount = 0; CPUState originalState; compileAndRun([&] (CCallHelpers& jit) { emitFunctionPrologue(jit); // Write expected values into the registers (except for sp, fp, and pc). jit.probe([&] (Probe::Context& context) { auto& cpu = context.cpu; probeCallCount++; for (auto id = CCallHelpers::firstRegister(); id <= CCallHelpers::lastRegister(); id = nextID(id)) { originalState.gpr(id) = cpu.gpr(id); if (isSpecialGPR(id)) continue; cpu.gpr(id) = testWord(static_cast(id)); } for (auto id = CCallHelpers::firstFPRegister(); id <= CCallHelpers::lastFPRegister(); id = nextID(id)) { originalState.fpr(id) = cpu.fpr(id); cpu.fpr(id) = bitwise_cast(testWord64(id)); } }); // Invoke the probe to call a lot of functions and trash register values. jit.probe([&] (Probe::Context&) { probeCallCount++; CHECK_EQ(testFunctionToTrashGPRs(0, 1, 2, 3, 4, 5, 6, 7, 8, 9), 10); CHECK_EQ(testFunctionToTrashFPRs(0, 1, 2, 3, 4, 5, 6, 7, 8, 9), 10); }); // Validate that the registers have the expected values. jit.probe([&] (Probe::Context& context) { auto& cpu = context.cpu; probeCallCount++; for (auto id = CCallHelpers::firstRegister(); id <= CCallHelpers::lastRegister(); id = nextID(id)) { if (isSP(id) || isFP(id)) { CHECK_EQ(cpu.gpr(id), originalState.gpr(id)); continue; } if (isSpecialGPR(id)) continue; CHECK_EQ(cpu.gpr(id), testWord(id)); } for (auto id = CCallHelpers::firstFPRegister(); id <= CCallHelpers::lastFPRegister(); id = nextID(id)) #if CPU(MIPS) if (!(id & 1)) #endif CHECK_EQ(cpu.fpr(id), testWord64(id)); }); // Restore the original state. jit.probe([&] (Probe::Context& context) { auto& cpu = context.cpu; probeCallCount++; for (auto id = CCallHelpers::firstRegister(); id <= CCallHelpers::lastRegister(); id = nextID(id)) { if (isSpecialGPR(id)) continue; cpu.gpr(id) = originalState.gpr(id); } for (auto id = CCallHelpers::firstFPRegister(); id <= CCallHelpers::lastFPRegister(); id = nextID(id)) cpu.fpr(id) = originalState.fpr(id); }); // Validate that the original state was restored. jit.probe([&] (Probe::Context& context) { auto& cpu = context.cpu; probeCallCount++; for (auto id = CCallHelpers::firstRegister(); id <= CCallHelpers::lastRegister(); id = nextID(id)) { if (isSpecialGPR(id)) continue; CHECK_EQ(cpu.gpr(id), originalState.gpr(id)); } for (auto id = CCallHelpers::firstFPRegister(); id <= CCallHelpers::lastFPRegister(); id = nextID(id)) #if CPU(MIPS) if (!(id & 1)) #endif CHECK_EQ(cpu.fpr(id), originalState.fpr(id)); }); emitFunctionEpilogue(jit); jit.ret(); }); CHECK_EQ(probeCallCount, 5); } void testProbeModifiesStackPointer(WTF::Function computeModifiedStackPointer) { unsigned probeCallCount = 0; CPUState originalState; void* originalSP { nullptr }; void* modifiedSP { nullptr }; #if !(CPU(MIPS)) uintptr_t modifiedFlags { 0 }; #endif #if CPU(X86) || CPU(X86_64) auto flagsSPR = X86Registers::eflags; uintptr_t flagsMask = 0xc5; #elif CPU(ARM_THUMB2) auto flagsSPR = ARMRegisters::apsr; uintptr_t flagsMask = 0xf8000000; #elif CPU(ARM64) auto flagsSPR = ARM64Registers::nzcv; uintptr_t flagsMask = 0xf0000000; #endif compileAndRun([&] (CCallHelpers& jit) { emitFunctionPrologue(jit); // Preserve original stack pointer and modify the sp, and // write expected values into other registers (except for fp, and pc). jit.probe([&] (Probe::Context& context) { auto& cpu = context.cpu; probeCallCount++; for (auto id = CCallHelpers::firstRegister(); id <= CCallHelpers::lastRegister(); id = nextID(id)) { originalState.gpr(id) = cpu.gpr(id); if (isSpecialGPR(id)) continue; cpu.gpr(id) = testWord(static_cast(id)); } for (auto id = CCallHelpers::firstFPRegister(); id <= CCallHelpers::lastFPRegister(); id = nextID(id)) { originalState.fpr(id) = cpu.fpr(id); cpu.fpr(id) = bitwise_cast(testWord64(id)); } #if !(CPU(MIPS)) originalState.spr(flagsSPR) = cpu.spr(flagsSPR); modifiedFlags = originalState.spr(flagsSPR) ^ flagsMask; cpu.spr(flagsSPR) = modifiedFlags; #endif originalSP = cpu.sp(); modifiedSP = computeModifiedStackPointer(context); cpu.sp() = modifiedSP; }); // Validate that the registers have the expected values. jit.probe([&] (Probe::Context& context) { auto& cpu = context.cpu; probeCallCount++; for (auto id = CCallHelpers::firstRegister(); id <= CCallHelpers::lastRegister(); id = nextID(id)) { if (isFP(id)) { CHECK_EQ(cpu.gpr(id), originalState.gpr(id)); continue; } if (isSpecialGPR(id)) continue; CHECK_EQ(cpu.gpr(id), testWord(id)); } for (auto id = CCallHelpers::firstFPRegister(); id <= CCallHelpers::lastFPRegister(); id = nextID(id)) #if CPU(MIPS) if (!(id & 1)) #endif CHECK_EQ(cpu.fpr(id), testWord64(id)); #if !(CPU(MIPS)) CHECK_EQ(cpu.spr(flagsSPR) & flagsMask, modifiedFlags & flagsMask); #endif CHECK_EQ(cpu.sp(), modifiedSP); }); // Restore the original state. jit.probe([&] (Probe::Context& context) { auto& cpu = context.cpu; probeCallCount++; for (auto id = CCallHelpers::firstRegister(); id <= CCallHelpers::lastRegister(); id = nextID(id)) { if (isSpecialGPR(id)) continue; cpu.gpr(id) = originalState.gpr(id); } for (auto id = CCallHelpers::firstFPRegister(); id <= CCallHelpers::lastFPRegister(); id = nextID(id)) cpu.fpr(id) = originalState.fpr(id); #if !(CPU(MIPS)) cpu.spr(flagsSPR) = originalState.spr(flagsSPR); #endif cpu.sp() = originalSP; }); // Validate that the original state was restored. jit.probe([&] (Probe::Context& context) { auto& cpu = context.cpu; probeCallCount++; for (auto id = CCallHelpers::firstRegister(); id <= CCallHelpers::lastRegister(); id = nextID(id)) { if (isSpecialGPR(id)) continue; CHECK_EQ(cpu.gpr(id), originalState.gpr(id)); } for (auto id = CCallHelpers::firstFPRegister(); id <= CCallHelpers::lastFPRegister(); id = nextID(id)) #if CPU(MIPS) if (!(id & 1)) #endif CHECK_EQ(cpu.fpr(id), originalState.fpr(id)); #if !(CPU(MIPS)) CHECK_EQ(cpu.spr(flagsSPR) & flagsMask, originalState.spr(flagsSPR) & flagsMask); #endif CHECK_EQ(cpu.sp(), originalSP); }); emitFunctionEpilogue(jit); jit.ret(); }); CHECK_EQ(probeCallCount, 4); } void testProbeModifiesStackPointerToInsideProbeStateOnStack() { size_t increment = sizeof(uintptr_t); #if CPU(ARM64) // The ARM64 probe uses ldp and stp which require 16 byte alignment. increment = 2 * sizeof(uintptr_t); #endif for (size_t offset = 0; offset < sizeof(Probe::State); offset += increment) { testProbeModifiesStackPointer([=] (Probe::Context& context) -> void* { return reinterpret_cast(probeStateForContext(context)) + offset; }); } } void testProbeModifiesStackPointerToNBytesBelowSP() { size_t increment = sizeof(uintptr_t); #if CPU(ARM64) // The ARM64 probe uses ldp and stp which require 16 byte alignment. increment = 2 * sizeof(uintptr_t); #endif for (size_t offset = 0; offset < 1 * KB; offset += increment) { testProbeModifiesStackPointer([=] (Probe::Context& context) -> void* { return context.cpu.sp() - offset; }); } } void testProbeModifiesProgramCounter() { // This test relies on testProbeReadsArgumentRegisters() and testProbeWritesArgumentRegisters() // having already validated that we can read and write from registers. We'll use these abilities // to validate that the probe preserves register values. unsigned probeCallCount = 0; bool continuationWasReached = false; MacroAssemblerCodeRef continuation = compile([&] (CCallHelpers& jit) { // Validate that we reached the continuation. jit.probe([&] (Probe::Context&) { probeCallCount++; continuationWasReached = true; }); emitFunctionEpilogue(jit); jit.ret(); }); compileAndRun([&] (CCallHelpers& jit) { emitFunctionPrologue(jit); // Write expected values into the registers. jit.probe([&] (Probe::Context& context) { probeCallCount++; context.cpu.pc() = retagCodePtr(continuation.code().executableAddress()); }); jit.breakpoint(); // We should never get here. }); CHECK_EQ(probeCallCount, 2); CHECK_EQ(continuationWasReached, true); } void testProbeModifiesStackValues() { unsigned probeCallCount = 0; CPUState originalState; void* originalSP { nullptr }; void* newSP { nullptr }; #if !CPU(MIPS) uintptr_t modifiedFlags { 0 }; #endif size_t numberOfExtraEntriesToWrite { 10 }; // ARM64 requires that this be 2 word aligned. #if CPU(X86) || CPU(X86_64) MacroAssembler::SPRegisterID flagsSPR = X86Registers::eflags; uintptr_t flagsMask = 0xc5; #elif CPU(ARM_THUMB2) MacroAssembler::SPRegisterID flagsSPR = ARMRegisters::apsr; uintptr_t flagsMask = 0xf8000000; #elif CPU(ARM64) MacroAssembler::SPRegisterID flagsSPR = ARM64Registers::nzcv; uintptr_t flagsMask = 0xf0000000; #endif compileAndRun([&] (CCallHelpers& jit) { emitFunctionPrologue(jit); // Write expected values into the registers. jit.probe([&] (Probe::Context& context) { auto& cpu = context.cpu; auto& stack = context.stack(); probeCallCount++; // Preserve the original CPU state. for (auto id = CCallHelpers::firstRegister(); id <= CCallHelpers::lastRegister(); id = nextID(id)) { originalState.gpr(id) = cpu.gpr(id); if (isSpecialGPR(id)) continue; cpu.gpr(id) = testWord(static_cast(id)); } for (auto id = CCallHelpers::firstFPRegister(); id <= CCallHelpers::lastFPRegister(); id = nextID(id)) { originalState.fpr(id) = cpu.fpr(id); cpu.fpr(id) = bitwise_cast(testWord64(id)); } #if !(CPU(MIPS)) originalState.spr(flagsSPR) = cpu.spr(flagsSPR); modifiedFlags = originalState.spr(flagsSPR) ^ flagsMask; cpu.spr(flagsSPR) = modifiedFlags; #endif // Ensure that we'll be writing over the regions of the stack where the Probe::State is. originalSP = cpu.sp(); newSP = reinterpret_cast(probeStateForContext(context)) - numberOfExtraEntriesToWrite; cpu.sp() = newSP; // Fill the stack with values. uintptr_t* p = reinterpret_cast(newSP); int count = 0; stack.set(p++, 1.234567); if (is32Bit()) p++; // On 32-bit targets, a double takes up 2 uintptr_t. while (p < reinterpret_cast(originalSP)) stack.set(p++, testWord(count++)); }); // Validate that the registers and stack have the expected values. jit.probe([&] (Probe::Context& context) { auto& cpu = context.cpu; auto& stack = context.stack(); probeCallCount++; // Validate the register values. for (auto id = CCallHelpers::firstRegister(); id <= CCallHelpers::lastRegister(); id = nextID(id)) { if (isFP(id)) { CHECK_EQ(cpu.gpr(id), originalState.gpr(id)); continue; } if (isSpecialGPR(id)) continue; CHECK_EQ(cpu.gpr(id), testWord(id)); } for (auto id = CCallHelpers::firstFPRegister(); id <= CCallHelpers::lastFPRegister(); id = nextID(id)) #if CPU(MIPS) if (!(id & 1)) #endif CHECK_EQ(cpu.fpr(id), testWord64(id)); #if !(CPU(MIPS)) CHECK_EQ(cpu.spr(flagsSPR) & flagsMask, modifiedFlags & flagsMask); #endif CHECK_EQ(cpu.sp(), newSP); // Validate the stack values. uintptr_t* p = reinterpret_cast(newSP); int count = 0; CHECK_EQ(stack.get(p++), 1.234567); if (is32Bit()) p++; // On 32-bit targets, a double takes up 2 uintptr_t. while (p < reinterpret_cast(originalSP)) CHECK_EQ(stack.get(p++), testWord(count++)); }); // Restore the original state. jit.probe([&] (Probe::Context& context) { auto& cpu = context.cpu; probeCallCount++; for (auto id = CCallHelpers::firstRegister(); id <= CCallHelpers::lastRegister(); id = nextID(id)) { if (isSpecialGPR(id)) continue; cpu.gpr(id) = originalState.gpr(id); } for (auto id = CCallHelpers::firstFPRegister(); id <= CCallHelpers::lastFPRegister(); id = nextID(id)) cpu.fpr(id) = originalState.fpr(id); #if !(CPU(MIPS)) cpu.spr(flagsSPR) = originalState.spr(flagsSPR); #endif cpu.sp() = originalSP; }); emitFunctionEpilogue(jit); jit.ret(); }); CHECK_EQ(probeCallCount, 3); } #endif // ENABLE(MASM_PROBE) void testOrImmMem() { // FIXME: this does not test that the or does not touch beyond its width. // I am not sure how to do such a test without a lot of complexity (running multiple threads, with a race on the high bits of the memory location). uint64_t memoryLocation = 0x12341234; auto or32 = compile([&] (CCallHelpers& jit) { emitFunctionPrologue(jit); jit.or32(CCallHelpers::TrustedImm32(42), CCallHelpers::AbsoluteAddress(&memoryLocation)); emitFunctionEpilogue(jit); jit.ret(); }); invoke(or32); CHECK_EQ(memoryLocation, 0x12341234 | 42); memoryLocation = 0x12341234; auto or16 = compile([&] (CCallHelpers& jit) { emitFunctionPrologue(jit); jit.or16(CCallHelpers::TrustedImm32(42), CCallHelpers::AbsoluteAddress(&memoryLocation)); emitFunctionEpilogue(jit); jit.ret(); }); invoke(or16); CHECK_EQ(memoryLocation, 0x12341234 | 42); memoryLocation = 0x12341234; auto or8 = compile([&] (CCallHelpers& jit) { emitFunctionPrologue(jit); jit.or8(CCallHelpers::TrustedImm32(42), CCallHelpers::AbsoluteAddress(&memoryLocation)); emitFunctionEpilogue(jit); jit.ret(); }); invoke(or8); CHECK_EQ(memoryLocation, 0x12341234 | 42); memoryLocation = 0x12341234; auto or16InvalidLogicalImmInARM64 = compile([&] (CCallHelpers& jit) { emitFunctionPrologue(jit); jit.or16(CCallHelpers::TrustedImm32(0), CCallHelpers::AbsoluteAddress(&memoryLocation)); emitFunctionEpilogue(jit); jit.ret(); }); invoke(or16InvalidLogicalImmInARM64); CHECK_EQ(memoryLocation, 0x12341234); } void testAndOrDouble() { double arg1, arg2; auto andDouble = compile([&] (CCallHelpers& jit) { emitFunctionPrologue(jit); jit.loadDouble(CCallHelpers::TrustedImmPtr(&arg1), FPRInfo::fpRegT1); jit.loadDouble(CCallHelpers::TrustedImmPtr(&arg2), FPRInfo::fpRegT2); jit.andDouble(FPRInfo::fpRegT1, FPRInfo::fpRegT2, FPRInfo::returnValueFPR); emitFunctionEpilogue(jit); jit.ret(); }); auto operands = doubleOperands(); for (auto a : operands) { for (auto b : operands) { arg1 = a; arg2 = b; uint64_t expectedResult = bitwise_cast(arg1) & bitwise_cast(arg2); CHECK_EQ(bitwise_cast(invoke(andDouble)), expectedResult); } } auto orDouble = compile([&] (CCallHelpers& jit) { emitFunctionPrologue(jit); jit.loadDouble(CCallHelpers::TrustedImmPtr(&arg1), FPRInfo::fpRegT1); jit.loadDouble(CCallHelpers::TrustedImmPtr(&arg2), FPRInfo::fpRegT2); jit.orDouble(FPRInfo::fpRegT1, FPRInfo::fpRegT2, FPRInfo::returnValueFPR); emitFunctionEpilogue(jit); jit.ret(); }); for (auto a : operands) { for (auto b : operands) { arg1 = a; arg2 = b; uint64_t expectedResult = bitwise_cast(arg1) | bitwise_cast(arg2); CHECK_EQ(bitwise_cast(invoke(orDouble)), expectedResult); } } } void testByteSwap() { #if CPU(X86_64) || CPU(ARM64) auto byteSwap16 = compile([] (CCallHelpers& jit) { emitFunctionPrologue(jit); jit.move(GPRInfo::argumentGPR0, GPRInfo::returnValueGPR); jit.byteSwap16(GPRInfo::returnValueGPR); emitFunctionEpilogue(jit); jit.ret(); }); CHECK_EQ(invoke(byteSwap16, 0xaabbccddee001122), static_cast(0x2211)); CHECK_EQ(invoke(byteSwap16, 0xaabbccddee00ffaa), static_cast(0xaaff)); auto byteSwap32 = compile([] (CCallHelpers& jit) { emitFunctionPrologue(jit); jit.move(GPRInfo::argumentGPR0, GPRInfo::returnValueGPR); jit.byteSwap32(GPRInfo::returnValueGPR); emitFunctionEpilogue(jit); jit.ret(); }); CHECK_EQ(invoke(byteSwap32, 0xaabbccddee001122), static_cast(0x221100ee)); CHECK_EQ(invoke(byteSwap32, 0xaabbccddee00ffaa), static_cast(0xaaff00ee)); auto byteSwap64 = compile([] (CCallHelpers& jit) { emitFunctionPrologue(jit); jit.move(GPRInfo::argumentGPR0, GPRInfo::returnValueGPR); jit.byteSwap64(GPRInfo::returnValueGPR); emitFunctionEpilogue(jit); jit.ret(); }); CHECK_EQ(invoke(byteSwap64, 0xaabbccddee001122), static_cast(0x221100eeddccbbaa)); CHECK_EQ(invoke(byteSwap64, 0xaabbccddee00ffaa), static_cast(0xaaff00eeddccbbaa)); #endif } void testMoveDoubleConditionally32() { #if CPU(X86_64) | CPU(ARM64) double arg1 = 0; double arg2 = 0; const double zero = -0; const double chosenDouble = 6.00000059604644775390625; CHECK_EQ(static_cast(static_cast(chosenDouble)) == chosenDouble, false); auto sel = compile([&] (CCallHelpers& jit) { emitFunctionPrologue(jit); jit.loadDouble(CCallHelpers::TrustedImmPtr(&zero), FPRInfo::returnValueFPR); jit.loadDouble(CCallHelpers::TrustedImmPtr(&arg1), FPRInfo::fpRegT1); jit.loadDouble(CCallHelpers::TrustedImmPtr(&arg2), FPRInfo::fpRegT2); jit.move(MacroAssembler::TrustedImm32(-1), GPRInfo::regT0); jit.moveDoubleConditionally32(MacroAssembler::Equal, GPRInfo::regT0, GPRInfo::regT0, FPRInfo::fpRegT1, FPRInfo::fpRegT2, FPRInfo::returnValueFPR); emitFunctionEpilogue(jit); jit.ret(); }); arg1 = chosenDouble; arg2 = 43; CHECK_EQ(invoke(sel), chosenDouble); arg1 = 43; arg2 = chosenDouble; CHECK_EQ(invoke(sel), 43.0); #endif } void testMoveDoubleConditionally64() { #if CPU(X86_64) | CPU(ARM64) double arg1 = 0; double arg2 = 0; const double zero = -0; const double chosenDouble = 6.00000059604644775390625; CHECK_EQ(static_cast(static_cast(chosenDouble)) == chosenDouble, false); auto sel = compile([&] (CCallHelpers& jit) { emitFunctionPrologue(jit); jit.loadDouble(CCallHelpers::TrustedImmPtr(&zero), FPRInfo::returnValueFPR); jit.loadDouble(CCallHelpers::TrustedImmPtr(&arg1), FPRInfo::fpRegT1); jit.loadDouble(CCallHelpers::TrustedImmPtr(&arg2), FPRInfo::fpRegT2); jit.move(MacroAssembler::TrustedImm64(-1), GPRInfo::regT0); jit.moveDoubleConditionally64(MacroAssembler::Equal, GPRInfo::regT0, GPRInfo::regT0, FPRInfo::fpRegT1, FPRInfo::fpRegT2, FPRInfo::returnValueFPR); emitFunctionEpilogue(jit); jit.ret(); }); arg1 = chosenDouble; arg2 = 43; CHECK_EQ(invoke(sel), chosenDouble); arg1 = 43; arg2 = chosenDouble; CHECK_EQ(invoke(sel), 43.0); #endif } static void testCagePreservesPACFailureBit() { #if GIGACAGE_ENABLED // Placate ASan builds and any environments that disables the Gigacage. if (!Gigacage::shouldBeEnabled()) return; RELEASE_ASSERT(!Gigacage::disablingPrimitiveGigacageIsForbidden()); auto cage = compile([] (CCallHelpers& jit) { emitFunctionPrologue(jit); jit.cageConditionally(Gigacage::Primitive, GPRInfo::argumentGPR0, GPRInfo::argumentGPR1, GPRInfo::argumentGPR2); jit.move(GPRInfo::argumentGPR0, GPRInfo::returnValueGPR); emitFunctionEpilogue(jit); jit.ret(); }); void* ptr = Gigacage::tryMalloc(Gigacage::Primitive, 1); void* taggedPtr = tagArrayPtr(ptr, 1); RELEASE_ASSERT(hasOneBitSet(Gigacage::maxSize(Gigacage::Primitive) << 2)); void* notCagedPtr = reinterpret_cast(reinterpret_cast(ptr) + (Gigacage::maxSize(Gigacage::Primitive) << 2)); CHECK_NOT_EQ(Gigacage::caged(Gigacage::Primitive, notCagedPtr), notCagedPtr); void* taggedNotCagedPtr = tagArrayPtr(notCagedPtr, 1); if (isARM64E()) { CHECK_NOT_EQ(invoke(cage, taggedPtr, 2), ptr); CHECK_NOT_EQ(invoke(cage, taggedNotCagedPtr, 1), ptr); void* cagedTaggedNotCagedPtr = invoke(cage, taggedNotCagedPtr, 1); CHECK_NOT_EQ(cagedTaggedNotCagedPtr, removeArrayPtrTag(cagedTaggedNotCagedPtr)); } else CHECK_EQ(invoke(cage, taggedPtr, 2), ptr); CHECK_EQ(invoke(cage, taggedPtr, 1), ptr); auto cageWithoutAuthentication = compile([] (CCallHelpers& jit) { emitFunctionPrologue(jit); jit.cageWithoutUntagging(Gigacage::Primitive, GPRInfo::argumentGPR0); jit.move(GPRInfo::argumentGPR0, GPRInfo::returnValueGPR); emitFunctionEpilogue(jit); jit.ret(); }); CHECK_EQ(invoke(cageWithoutAuthentication, taggedPtr), taggedPtr); if (isARM64E()) { CHECK_NOT_EQ(invoke(cageWithoutAuthentication, taggedNotCagedPtr), taggedNotCagedPtr); CHECK_NOT_EQ(invoke(cageWithoutAuthentication, taggedNotCagedPtr), tagArrayPtr(notCagedPtr, 1)); CHECK_NOT_EQ(invoke(cageWithoutAuthentication, taggedNotCagedPtr), taggedPtr); CHECK_NOT_EQ(invoke(cageWithoutAuthentication, taggedNotCagedPtr), tagArrayPtr(ptr, 1)); } Gigacage::free(Gigacage::Primitive, ptr); #endif } static void testBranchIfType() { using JSC::JSType; struct CellLike { uint32_t structureID; uint8_t indexingType; JSType type; }; CHECK_EQ(JSCell::typeInfoTypeOffset(), OBJECT_OFFSETOF(CellLike, type)); auto isType = compile([] (CCallHelpers& jit) { emitFunctionPrologue(jit); auto isType = jit.branchIfType(GPRInfo::argumentGPR0, JSC::JSTypeRange { JSType(FirstTypedArrayType), JSType(LastTypedArrayTypeExcludingDataView) }); jit.move(CCallHelpers::TrustedImm32(false), GPRInfo::returnValueGPR); auto done = jit.jump(); isType.link(&jit); jit.move(CCallHelpers::TrustedImm32(true), GPRInfo::returnValueGPR); done.link(&jit); emitFunctionEpilogue(jit); jit.ret(); }); CellLike cell; for (unsigned i = JSC::FirstTypedArrayType; i <= JSC::LastTypedArrayTypeExcludingDataView; ++i) { cell.type = JSType(i); CHECK_EQ(invoke(isType, &cell), true); } cell.type = JSType(LastTypedArrayType); CHECK_EQ(invoke(isType, &cell), false); cell.type = JSType(FirstTypedArrayType - 1); CHECK_EQ(invoke(isType, &cell), false); } static void testBranchIfNotType() { using JSC::JSType; struct CellLike { uint32_t structureID; uint8_t indexingType; JSType type; }; CHECK_EQ(JSCell::typeInfoTypeOffset(), OBJECT_OFFSETOF(CellLike, type)); auto isNotType = compile([] (CCallHelpers& jit) { emitFunctionPrologue(jit); auto isNotType = jit.branchIfNotType(GPRInfo::argumentGPR0, JSC::JSTypeRange { JSType(FirstTypedArrayType), JSType(LastTypedArrayTypeExcludingDataView) }); jit.move(CCallHelpers::TrustedImm32(false), GPRInfo::returnValueGPR); auto done = jit.jump(); isNotType.link(&jit); jit.move(CCallHelpers::TrustedImm32(true), GPRInfo::returnValueGPR); done.link(&jit); emitFunctionEpilogue(jit); jit.ret(); }); CellLike cell; for (unsigned i = JSC::FirstTypedArrayType; i <= JSC::LastTypedArrayTypeExcludingDataView; ++i) { cell.type = JSType(i); CHECK_EQ(invoke(isNotType, &cell), false); } cell.type = JSType(LastTypedArrayType); CHECK_EQ(invoke(isNotType, &cell), true); cell.type = JSType(FirstTypedArrayType - 1); CHECK_EQ(invoke(isNotType, &cell), true); } #define RUN(test) do { \ if (!shouldRun(#test)) \ break; \ numberOfTests++; \ tasks.append( \ createSharedTask( \ [&] () { \ dataLog(#test "...\n"); \ test; \ dataLog(#test ": OK!\n"); \ })); \ } while (false); void run(const char* filter) { JSC::initialize(); unsigned numberOfTests = 0; Deque>> tasks; auto shouldRun = [&] (const char* testName) -> bool { return !filter || WTF::findIgnoringASCIICaseWithoutLength(testName, filter) != WTF::notFound; }; RUN(testSimple()); RUN(testGetEffectiveAddress(0xff00, 42, 8, CCallHelpers::TimesEight)); RUN(testGetEffectiveAddress(0xff00, -200, -300, CCallHelpers::TimesEight)); RUN(testBranchTruncateDoubleToInt32(0, 0)); RUN(testBranchTruncateDoubleToInt32(42, 42)); RUN(testBranchTruncateDoubleToInt32(42.7, 42)); RUN(testBranchTruncateDoubleToInt32(-1234, -1234)); RUN(testBranchTruncateDoubleToInt32(-1234.56, -1234)); RUN(testBranchTruncateDoubleToInt32(std::numeric_limits::infinity(), 0)); RUN(testBranchTruncateDoubleToInt32(-std::numeric_limits::infinity(), 0)); RUN(testBranchTruncateDoubleToInt32(std::numeric_limits::quiet_NaN(), 0)); RUN(testBranchTruncateDoubleToInt32(std::numeric_limits::signaling_NaN(), 0)); RUN(testBranchTruncateDoubleToInt32(std::numeric_limits::max(), 0)); RUN(testBranchTruncateDoubleToInt32(-std::numeric_limits::max(), 0)); // We run this last one to make sure that we don't use flags that were not // reset to check a conversion result RUN(testBranchTruncateDoubleToInt32(123, 123)); #define FOR_EACH_DOUBLE_CONDITION_RUN(__test) \ do { \ RUN(__test(MacroAssembler::DoubleEqualAndOrdered)); \ RUN(__test(MacroAssembler::DoubleNotEqualAndOrdered)); \ RUN(__test(MacroAssembler::DoubleGreaterThanAndOrdered)); \ RUN(__test(MacroAssembler::DoubleGreaterThanOrEqualAndOrdered)); \ RUN(__test(MacroAssembler::DoubleLessThanAndOrdered)); \ RUN(__test(MacroAssembler::DoubleLessThanOrEqualAndOrdered)); \ RUN(__test(MacroAssembler::DoubleEqualOrUnordered)); \ RUN(__test(MacroAssembler::DoubleNotEqualOrUnordered)); \ RUN(__test(MacroAssembler::DoubleGreaterThanOrUnordered)); \ RUN(__test(MacroAssembler::DoubleGreaterThanOrEqualOrUnordered)); \ RUN(__test(MacroAssembler::DoubleLessThanOrUnordered)); \ RUN(__test(MacroAssembler::DoubleLessThanOrEqualOrUnordered)); \ } while (false) FOR_EACH_DOUBLE_CONDITION_RUN(testCompareDouble); FOR_EACH_DOUBLE_CONDITION_RUN(testCompareDoubleSameArg); RUN(testMul32WithImmediates()); #if CPU(X86_64) RUN(testBranchTestBit32RegReg()); RUN(testBranchTestBit32RegImm()); RUN(testBranchTestBit32AddrImm()); RUN(testBranchTestBit64RegReg()); RUN(testBranchTestBit64RegImm()); RUN(testBranchTestBit64AddrImm()); #endif #if CPU(X86_64) || CPU(ARM64) RUN(testClearBit64()); RUN(testClearBits64WithMask()); RUN(testClearBits64WithMaskTernary()); RUN(testCountTrailingZeros64()); RUN(testCountTrailingZeros64WithoutNullCheck()); RUN(testShiftAndAdd()); RUN(testStore64Imm64AddressPointer()); #endif #if CPU(ARM64) RUN(testMul32SignExtend()); #endif #if CPU(X86) || CPU(X86_64) || CPU(ARM64) FOR_EACH_DOUBLE_CONDITION_RUN(testCompareFloat); #endif #if CPU(X86_64) || CPU(ARM64) // Comparing 2 different registers. FOR_EACH_DOUBLE_CONDITION_RUN(testMoveConditionallyDouble2); FOR_EACH_DOUBLE_CONDITION_RUN(testMoveConditionallyDouble3); FOR_EACH_DOUBLE_CONDITION_RUN(testMoveConditionallyDouble3DestSameAsThenCase); FOR_EACH_DOUBLE_CONDITION_RUN(testMoveConditionallyDouble3DestSameAsElseCase); FOR_EACH_DOUBLE_CONDITION_RUN(testMoveConditionallyFloat2); FOR_EACH_DOUBLE_CONDITION_RUN(testMoveConditionallyFloat3); FOR_EACH_DOUBLE_CONDITION_RUN(testMoveConditionallyFloat3DestSameAsThenCase); FOR_EACH_DOUBLE_CONDITION_RUN(testMoveConditionallyFloat3DestSameAsElseCase); FOR_EACH_DOUBLE_CONDITION_RUN(testMoveDoubleConditionallyDouble); FOR_EACH_DOUBLE_CONDITION_RUN(testMoveDoubleConditionallyDoubleDestSameAsThenCase); FOR_EACH_DOUBLE_CONDITION_RUN(testMoveDoubleConditionallyDoubleDestSameAsElseCase); FOR_EACH_DOUBLE_CONDITION_RUN(testMoveDoubleConditionallyFloat); FOR_EACH_DOUBLE_CONDITION_RUN(testMoveDoubleConditionallyFloatDestSameAsThenCase); FOR_EACH_DOUBLE_CONDITION_RUN(testMoveDoubleConditionallyFloatDestSameAsElseCase); // Comparing the same register against itself. FOR_EACH_DOUBLE_CONDITION_RUN(testMoveConditionallyDouble2SameArg); FOR_EACH_DOUBLE_CONDITION_RUN(testMoveConditionallyDouble3SameArg); FOR_EACH_DOUBLE_CONDITION_RUN(testMoveConditionallyFloat2SameArg); FOR_EACH_DOUBLE_CONDITION_RUN(testMoveConditionallyFloat3SameArg); FOR_EACH_DOUBLE_CONDITION_RUN(testMoveDoubleConditionallyDoubleSameArg); FOR_EACH_DOUBLE_CONDITION_RUN(testMoveDoubleConditionallyFloatSameArg); #endif #if ENABLE(MASM_PROBE) RUN(testProbeReadsArgumentRegisters()); RUN(testProbeWritesArgumentRegisters()); RUN(testProbePreservesGPRS()); RUN(testProbeModifiesStackPointerToInsideProbeStateOnStack()); RUN(testProbeModifiesStackPointerToNBytesBelowSP()); RUN(testProbeModifiesProgramCounter()); RUN(testProbeModifiesStackValues()); #endif // ENABLE(MASM_PROBE) RUN(testByteSwap()); RUN(testMoveDoubleConditionally32()); RUN(testMoveDoubleConditionally64()); RUN(testCagePreservesPACFailureBit()); RUN(testBranchIfType()); RUN(testBranchIfNotType()); RUN(testOrImmMem()); RUN(testAndOrDouble()); if (tasks.isEmpty()) usage(); Lock lock; Vector> threads; for (unsigned i = filter ? 1 : WTF::numberOfProcessorCores(); i--;) { threads.append( Thread::create( "testmasm thread", [&] () { for (;;) { RefPtr> task; { LockHolder locker(lock); if (tasks.isEmpty()) return; task = tasks.takeFirst(); } task->run(); } })); } for (auto& thread : threads) thread->waitForCompletion(); crashLock.lock(); dataLog("Completed ", numberOfTests, " tests\n"); } } // anonymous namespace #else // not ENABLE(JIT) static void run(const char*) { dataLog("JIT is not enabled.\n"); } #endif // ENABLE(JIT) int main(int argc, char** argv) { const char* filter = nullptr; switch (argc) { case 1: break; case 2: filter = argv[1]; break; default: usage(); break; } run(filter); return 0; } #if OS(WINDOWS) extern "C" __declspec(dllexport) int WINAPI dllLauncherEntryPoint(int argc, const char* argv[]) { return main(argc, const_cast(argv)); } #endif