1952f92838
The platform specific macro assembler headers can not be included directly. They require symbols declared in macro-assembler.h. We also cannot include macro-assembler.h from the platform specific headers, because that would form a cycle, and the include in macro-assembler.h would be skipped, which then also fails. This CL documents and enforces this unfortunate situation. This helps with further iwyu cleanups. Note that current code which includes the platform specific headers only works because we transitively included macro-assembler.h already before. R=mstarzinger@chromium.org Bug: v8:8238, v8:7490 Change-Id: I2dc65ad950400941406e1f2f8969d0d15f524bf8 Reviewed-on: https://chromium-review.googlesource.com/c/1340240 Commit-Queue: Clemens Hammacher <clemensh@chromium.org> Reviewed-by: Michael Starzinger <mstarzinger@chromium.org> Cr-Commit-Position: refs/heads/master@{#57578}
454 lines
14 KiB
C++
454 lines
14 KiB
C++
// 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/arm64/assembler-arm64-inl.h"
|
|
#include "src/arm64/utils-arm64.h"
|
|
#include "src/base/template-utils.h"
|
|
#include "src/macro-assembler-inl.h"
|
|
#include "src/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 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 (((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(!xn.Is(sp));
|
|
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 & (1ULL << 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) {
|
|
// 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 __
|