fcbb023b0e
This change added Windows ARM64 ABI support, major things are: 1. Excluding x18 register from any usage because it is reserved as platform register. Preserve alignment after the change. 2. Fix the assumption of LP64 in arm64 backend. Windows ARM64 is still LLP64. 3. Stack guard page probe for large allocation on stack. Reference: Windows ARM64 ABI: https://docs.microsoft.com/en-us/cpp/build/arm64-windows-abi-conventions?view=vs-2017 Bug: chromium:893460 Change-Id: I325884ac8dab719154a0047141e18a9fcb8dff7e Reviewed-on: https://chromium-review.googlesource.com/c/1285129 Commit-Queue: Michael Achenbach <machenbach@chromium.org> Reviewed-by: Andreas Haas <ahaas@chromium.org> Reviewed-by: Michael Lippautz <mlippautz@chromium.org> Reviewed-by: Benedikt Meurer <bmeurer@chromium.org> Reviewed-by: Ulan Degenbaev <ulan@chromium.org> Cr-Commit-Position: refs/heads/master@{#56881}
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 "src/v8.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 "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_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 __
|