| // Copyright 2013 the V8 project authors. All rights reserved. |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are |
| // met: |
| // |
| // * Redistributions of source code must retain the above copyright |
| // notice, this list of conditions and the following disclaimer. |
| // * 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. |
| // * Neither the name of Google Inc. nor the names of its |
| // contributors may be used to endorse or promote products derived |
| // from this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| // "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 THE COPYRIGHT |
| // OWNER 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 "test/cctest/test-utils-arm64.h" |
| |
| #include "src/base/template-utils.h" |
| #include "src/codegen/arm64/assembler-arm64-inl.h" |
| #include "src/codegen/arm64/utils-arm64.h" |
| #include "src/codegen/macro-assembler-inl.h" |
| #include "src/init/v8.h" |
| #include "test/cctest/cctest.h" |
| |
| namespace v8 { |
| namespace internal { |
| |
| |
| #define __ masm-> |
| |
| |
| bool Equal32(uint32_t expected, const RegisterDump*, uint32_t result) { |
| if (result != expected) { |
| printf("Expected 0x%08" PRIx32 "\t Found 0x%08" PRIx32 "\n", |
| expected, result); |
| } |
| |
| return expected == result; |
| } |
| |
| |
| bool Equal64(uint64_t expected, const RegisterDump*, uint64_t result) { |
| if (result != expected) { |
| printf("Expected 0x%016" PRIx64 "\t Found 0x%016" PRIx64 "\n", |
| expected, result); |
| } |
| |
| return expected == result; |
| } |
| |
| bool Equal128(vec128_t expected, const RegisterDump*, vec128_t result) { |
| if ((result.h != expected.h) || (result.l != expected.l)) { |
| printf("Expected 0x%016" PRIx64 "%016" PRIx64 |
| "\t " |
| "Found 0x%016" PRIx64 "%016" PRIx64 "\n", |
| expected.h, expected.l, result.h, result.l); |
| } |
| |
| return ((expected.h == result.h) && (expected.l == result.l)); |
| } |
| |
| bool EqualFP32(float expected, const RegisterDump*, float result) { |
| if (bit_cast<uint32_t>(expected) == bit_cast<uint32_t>(result)) { |
| return true; |
| } else { |
| if (std::isnan(expected) || (expected == 0.0)) { |
| printf("Expected 0x%08" PRIx32 "\t Found 0x%08" PRIx32 "\n", |
| bit_cast<uint32_t>(expected), bit_cast<uint32_t>(result)); |
| } else { |
| printf("Expected %.9f (0x%08" PRIx32 |
| ")\t " |
| "Found %.9f (0x%08" PRIx32 ")\n", |
| expected, bit_cast<uint32_t>(expected), result, |
| bit_cast<uint32_t>(result)); |
| } |
| return false; |
| } |
| } |
| |
| |
| bool EqualFP64(double expected, const RegisterDump*, double result) { |
| if (bit_cast<uint64_t>(expected) == bit_cast<uint64_t>(result)) { |
| return true; |
| } |
| |
| if (std::isnan(expected) || (expected == 0.0)) { |
| printf("Expected 0x%016" PRIx64 "\t Found 0x%016" PRIx64 "\n", |
| bit_cast<uint64_t>(expected), bit_cast<uint64_t>(result)); |
| } else { |
| printf("Expected %.17f (0x%016" PRIx64 |
| ")\t " |
| "Found %.17f (0x%016" PRIx64 ")\n", |
| expected, bit_cast<uint64_t>(expected), result, |
| bit_cast<uint64_t>(result)); |
| } |
| return false; |
| } |
| |
| |
| bool Equal32(uint32_t expected, const RegisterDump* core, const Register& reg) { |
| CHECK(reg.Is32Bits()); |
| // Retrieve the corresponding X register so we can check that the upper part |
| // was properly cleared. |
| int64_t result_x = core->xreg(reg.code()); |
| if ((result_x & 0xFFFFFFFF00000000L) != 0) { |
| printf("Expected 0x%08" PRIx32 "\t Found 0x%016" PRIx64 "\n", |
| expected, result_x); |
| return false; |
| } |
| uint32_t result_w = core->wreg(reg.code()); |
| return Equal32(expected, core, result_w); |
| } |
| |
| |
| bool Equal64(uint64_t expected, |
| const RegisterDump* core, |
| const Register& reg) { |
| CHECK(reg.Is64Bits()); |
| uint64_t result = core->xreg(reg.code()); |
| return Equal64(expected, core, result); |
| } |
| |
| bool Equal128(uint64_t expected_h, uint64_t expected_l, |
| const RegisterDump* core, const VRegister& vreg) { |
| CHECK(vreg.Is128Bits()); |
| vec128_t expected = {expected_l, expected_h}; |
| vec128_t result = core->qreg(vreg.code()); |
| return Equal128(expected, core, result); |
| } |
| |
| bool EqualFP32(float expected, const RegisterDump* core, |
| const VRegister& fpreg) { |
| CHECK(fpreg.Is32Bits()); |
| // Retrieve the corresponding D register so we can check that the upper part |
| // was properly cleared. |
| uint64_t result_64 = core->dreg_bits(fpreg.code()); |
| if ((result_64 & 0xFFFFFFFF00000000L) != 0) { |
| printf("Expected 0x%08" PRIx32 " (%f)\t Found 0x%016" PRIx64 "\n", |
| bit_cast<uint32_t>(expected), expected, result_64); |
| return false; |
| } |
| |
| return EqualFP32(expected, core, core->sreg(fpreg.code())); |
| } |
| |
| bool EqualFP64(double expected, const RegisterDump* core, |
| const VRegister& fpreg) { |
| CHECK(fpreg.Is64Bits()); |
| return EqualFP64(expected, core, core->dreg(fpreg.code())); |
| } |
| |
| |
| bool Equal64(const Register& reg0, |
| const RegisterDump* core, |
| const Register& reg1) { |
| CHECK(reg0.Is64Bits() && reg1.Is64Bits()); |
| int64_t expected = core->xreg(reg0.code()); |
| int64_t result = core->xreg(reg1.code()); |
| return Equal64(expected, core, result); |
| } |
| |
| |
| static char FlagN(uint32_t flags) { |
| return (flags & NFlag) ? 'N' : 'n'; |
| } |
| |
| |
| static char FlagZ(uint32_t flags) { |
| return (flags & ZFlag) ? 'Z' : 'z'; |
| } |
| |
| |
| static char FlagC(uint32_t flags) { |
| return (flags & CFlag) ? 'C' : 'c'; |
| } |
| |
| |
| static char FlagV(uint32_t flags) { |
| return (flags & VFlag) ? 'V' : 'v'; |
| } |
| |
| |
| bool EqualNzcv(uint32_t expected, uint32_t result) { |
| CHECK_EQ(expected & ~NZCVFlag, 0); |
| CHECK_EQ(result & ~NZCVFlag, 0); |
| if (result != expected) { |
| printf("Expected: %c%c%c%c\t Found: %c%c%c%c\n", |
| FlagN(expected), FlagZ(expected), FlagC(expected), FlagV(expected), |
| FlagN(result), FlagZ(result), FlagC(result), FlagV(result)); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool EqualV8Registers(const RegisterDump* a, const RegisterDump* b) { |
| CPURegList available_regs = kCallerSaved; |
| available_regs.Combine(kCalleeSaved); |
| while (!available_regs.IsEmpty()) { |
| int i = available_regs.PopLowestIndex().code(); |
| if (a->xreg(i) != b->xreg(i)) { |
| printf("x%d\t Expected 0x%016" PRIx64 "\t Found 0x%016" PRIx64 "\n", |
| i, a->xreg(i), b->xreg(i)); |
| return false; |
| } |
| } |
| |
| for (unsigned i = 0; i < kNumberOfVRegisters; i++) { |
| uint64_t a_bits = a->dreg_bits(i); |
| uint64_t b_bits = b->dreg_bits(i); |
| if (a_bits != b_bits) { |
| printf("d%d\t Expected 0x%016" PRIx64 "\t Found 0x%016" PRIx64 "\n", |
| i, a_bits, b_bits); |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| RegList PopulateRegisterArray(Register* w, Register* x, Register* r, |
| int reg_size, int reg_count, RegList allowed) { |
| RegList list = 0; |
| int i = 0; |
| for (unsigned n = 0; (n < kNumberOfRegisters) && (i < reg_count); n++) { |
| if (((1ULL << n) & allowed) != 0) { |
| // Only assign allowed registers. |
| if (r) { |
| r[i] = Register::Create(n, reg_size); |
| } |
| if (x) { |
| x[i] = Register::Create(n, kXRegSizeInBits); |
| } |
| if (w) { |
| w[i] = Register::Create(n, kWRegSizeInBits); |
| } |
| list |= (1ULL << n); |
| i++; |
| } |
| } |
| // Check that we got enough registers. |
| CHECK(CountSetBits(list, kNumberOfRegisters) == reg_count); |
| |
| return list; |
| } |
| |
| RegList PopulateVRegisterArray(VRegister* s, VRegister* d, VRegister* v, |
| int reg_size, int reg_count, RegList allowed) { |
| RegList list = 0; |
| int i = 0; |
| for (unsigned n = 0; (n < kNumberOfVRegisters) && (i < reg_count); n++) { |
| if (((1ULL << n) & allowed) != 0) { |
| // Only assigned allowed registers. |
| if (v) { |
| v[i] = VRegister::Create(n, reg_size); |
| } |
| if (d) { |
| d[i] = VRegister::Create(n, kDRegSizeInBits); |
| } |
| if (s) { |
| s[i] = VRegister::Create(n, kSRegSizeInBits); |
| } |
| list |= (1ULL << n); |
| i++; |
| } |
| } |
| // Check that we got enough registers. |
| CHECK(CountSetBits(list, kNumberOfVRegisters) == reg_count); |
| |
| return list; |
| } |
| |
| |
| void Clobber(MacroAssembler* masm, RegList reg_list, uint64_t const value) { |
| Register first = NoReg; |
| for (unsigned i = 0; i < kNumberOfRegisters; i++) { |
| if (reg_list & (1ULL << i)) { |
| Register xn = Register::Create(i, kXRegSizeInBits); |
| // We should never write into sp here. |
| CHECK_NE(xn, sp); |
| if (!xn.IsZero()) { |
| if (!first.is_valid()) { |
| // This is the first register we've hit, so construct the literal. |
| __ Mov(xn, value); |
| first = xn; |
| } else { |
| // We've already loaded the literal, so re-use the value already |
| // loaded into the first register we hit. |
| __ Mov(xn, first); |
| } |
| } |
| } |
| } |
| } |
| |
| |
| void ClobberFP(MacroAssembler* masm, RegList reg_list, double const value) { |
| VRegister first = NoVReg; |
| for (unsigned i = 0; i < kNumberOfVRegisters; i++) { |
| if (reg_list & (1ULL << i)) { |
| VRegister dn = VRegister::Create(i, kDRegSizeInBits); |
| if (!first.is_valid()) { |
| // This is the first register we've hit, so construct the literal. |
| __ Fmov(dn, value); |
| first = dn; |
| } else { |
| // We've already loaded the literal, so re-use the value already loaded |
| // into the first register we hit. |
| __ Fmov(dn, first); |
| } |
| } |
| } |
| } |
| |
| |
| void Clobber(MacroAssembler* masm, CPURegList reg_list) { |
| if (reg_list.type() == CPURegister::kRegister) { |
| // This will always clobber X registers. |
| Clobber(masm, reg_list.list()); |
| } else if (reg_list.type() == CPURegister::kVRegister) { |
| // This will always clobber D registers. |
| ClobberFP(masm, reg_list.list()); |
| } else { |
| UNREACHABLE(); |
| } |
| } |
| |
| |
| void RegisterDump::Dump(MacroAssembler* masm) { |
| // Ensure that we don't unintentionally clobber any registers. |
| RegList old_tmp_list = masm->TmpList()->list(); |
| RegList old_fptmp_list = masm->FPTmpList()->list(); |
| masm->TmpList()->set_list(0); |
| masm->FPTmpList()->set_list(0); |
| |
| // Preserve some temporary registers. |
| Register dump_base = x0; |
| Register dump = x1; |
| Register tmp = x2; |
| Register dump_base_w = dump_base.W(); |
| Register dump_w = dump.W(); |
| Register tmp_w = tmp.W(); |
| |
| // Offsets into the dump_ structure. |
| const int x_offset = offsetof(dump_t, x_); |
| const int w_offset = offsetof(dump_t, w_); |
| const int d_offset = offsetof(dump_t, d_); |
| const int s_offset = offsetof(dump_t, s_); |
| const int q_offset = offsetof(dump_t, q_); |
| const int sp_offset = offsetof(dump_t, sp_); |
| const int wsp_offset = offsetof(dump_t, wsp_); |
| const int flags_offset = offsetof(dump_t, flags_); |
| |
| __ Push(xzr, dump_base, dump, tmp); |
| |
| // Load the address where we will dump the state. |
| __ Mov(dump_base, reinterpret_cast<uint64_t>(&dump_)); |
| |
| // Dump the stack pointer (sp and wsp). |
| // The stack pointer cannot be stored directly; it needs to be moved into |
| // another register first. Also, we pushed four X registers, so we need to |
| // compensate here. |
| __ Add(tmp, sp, 4 * kXRegSize); |
| __ Str(tmp, MemOperand(dump_base, sp_offset)); |
| __ Add(tmp_w, wsp, 4 * kXRegSize); |
| __ Str(tmp_w, MemOperand(dump_base, wsp_offset)); |
| |
| // Dump X registers. |
| __ Add(dump, dump_base, x_offset); |
| for (unsigned i = 0; i < kNumberOfRegisters; i += 2) { |
| __ Stp(Register::XRegFromCode(i), Register::XRegFromCode(i + 1), |
| MemOperand(dump, i * kXRegSize)); |
| } |
| |
| // Dump W registers. |
| __ Add(dump, dump_base, w_offset); |
| for (unsigned i = 0; i < kNumberOfRegisters; i += 2) { |
| __ Stp(Register::WRegFromCode(i), Register::WRegFromCode(i + 1), |
| MemOperand(dump, i * kWRegSize)); |
| } |
| |
| // Dump D registers. |
| __ Add(dump, dump_base, d_offset); |
| for (unsigned i = 0; i < kNumberOfVRegisters; i += 2) { |
| __ Stp(VRegister::DRegFromCode(i), VRegister::DRegFromCode(i + 1), |
| MemOperand(dump, i * kDRegSize)); |
| } |
| |
| // Dump S registers. |
| __ Add(dump, dump_base, s_offset); |
| for (unsigned i = 0; i < kNumberOfVRegisters; i += 2) { |
| __ Stp(VRegister::SRegFromCode(i), VRegister::SRegFromCode(i + 1), |
| MemOperand(dump, i * kSRegSize)); |
| } |
| |
| // Dump Q registers. |
| __ Add(dump, dump_base, q_offset); |
| for (unsigned i = 0; i < kNumberOfVRegisters; i += 2) { |
| __ Stp(VRegister::QRegFromCode(i), VRegister::QRegFromCode(i + 1), |
| MemOperand(dump, i * kQRegSize)); |
| } |
| |
| // Dump the flags. |
| __ Mrs(tmp, NZCV); |
| __ Str(tmp, MemOperand(dump_base, flags_offset)); |
| |
| // To dump the values that were in tmp amd dump, we need a new scratch |
| // register. We can use any of the already dumped registers since we can |
| // easily restore them. |
| Register dump2_base = x10; |
| Register dump2 = x11; |
| CHECK(!AreAliased(dump_base, dump, tmp, dump2_base, dump2)); |
| |
| // Don't lose the dump_ address. |
| __ Mov(dump2_base, dump_base); |
| |
| __ Pop(tmp, dump, dump_base, xzr); |
| |
| __ Add(dump2, dump2_base, w_offset); |
| __ Str(dump_base_w, MemOperand(dump2, dump_base.code() * kWRegSize)); |
| __ Str(dump_w, MemOperand(dump2, dump.code() * kWRegSize)); |
| __ Str(tmp_w, MemOperand(dump2, tmp.code() * kWRegSize)); |
| |
| __ Add(dump2, dump2_base, x_offset); |
| __ Str(dump_base, MemOperand(dump2, dump_base.code() * kXRegSize)); |
| __ Str(dump, MemOperand(dump2, dump.code() * kXRegSize)); |
| __ Str(tmp, MemOperand(dump2, tmp.code() * kXRegSize)); |
| |
| // Finally, restore dump2_base and dump2. |
| __ Ldr(dump2_base, MemOperand(dump2, dump2_base.code() * kXRegSize)); |
| __ Ldr(dump2, MemOperand(dump2, dump2.code() * kXRegSize)); |
| |
| // Restore the MacroAssembler's scratch registers. |
| masm->TmpList()->set_list(old_tmp_list); |
| masm->FPTmpList()->set_list(old_fptmp_list); |
| |
| completed_ = true; |
| } |
| |
| } // namespace internal |
| } // namespace v8 |
| |
| #undef __ |