v8/test/cctest/test-utils-arm64.cc

454 lines
14 KiB
C++
Raw Normal View History

// 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 "src/v8.h"
#include "src/arm64/assembler-arm64-inl.h"
#include "src/arm64/utils-arm64.h"
[assembler] Make Register et al. real classes Up to now, each architecture defined all Register types as structs, with lots of redundancy. An often found comment noted that they cannot be classes due to initialization order problems. As these problems are gone with C++11 constexpr constants, I now tried making Registers classes again. All register types now inherit from RegisterBase, which provides a default set of methods and named constructors (like ::from_code, code(), bit(), is_valid(), ...). This design allows to guarantee an interesting property: Each register is either valid, or it's the no_reg register. There are no other invalid registers. This is guaranteed statically by the constexpr constructor, and dynamically by ::from_code. I decided to disallow the default constructor completely, so instead of "Register reg;" you now need "Register reg = no_reg;". This makes explicit how the Register is initialized. I did this change to the x64, ia32, arm, arm64, mips and mips64 ports. Overall, code got much more compact and more safe. In theory, it should also increase performance (since the is_valid() check is simpler), but this is probably not measurable. R=mstarzinger@chromium.org Change-Id: I5ccfa4050daf4e146a557970e9d37fd3d2788d4a Reviewed-on: https://chromium-review.googlesource.com/650927 Reviewed-by: Jakob Gruber <jgruber@chromium.org> Reviewed-by: Michael Starzinger <mstarzinger@chromium.org> Reviewed-by: Igor Sheludko <ishell@chromium.org> Commit-Queue: Clemens Hammacher <clemensh@chromium.org> Cr-Commit-Position: refs/heads/master@{#47847}
2017-09-06 08:05:07 +00:00
#include "src/base/template-utils.h"
#include "src/macro-assembler-inl.h"
#include "test/cctest/cctest.h"
#include "test/cctest/test-utils-arm64.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((expected & ~NZCVFlag) == 0);
CHECK((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 EqualRegisters(const RegisterDump* a, const RegisterDump* b) {
for (unsigned i = 0; i < kNumberOfRegisters; i++) {
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 (((1UL << 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 |= (1UL << 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 (((1UL << 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 |= (1UL << 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 & (1UL << i)) {
Register xn = Register::Create(i, kXRegSizeInBits);
// We should never write into csp here.
CHECK(!xn.Is(csp));
if (!xn.IsZero()) {
if (!first.IsValid()) {
// 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 & (1UL << i)) {
VRegister dn = VRegister::Create(i, kDRegSizeInBits);
if (!first.IsValid()) {
// 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) {
CHECK(__ StackPointer().Is(csp));
// 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 (csp and wcsp).
// 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, csp, 4 * kXRegSize);
__ Str(tmp, MemOperand(dump_base, sp_offset));
__ Add(tmp_w, wcsp, 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