/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef jit_riscv64_Architecture_riscv64_h
#define jit_riscv64_Architecture_riscv64_h
// JitSpewer.h is included through MacroAssembler implementations for other
// platforms, so include it here to avoid inadvertent build bustage.
#include "mozilla/MathAlgorithms.h"
#include <algorithm>
#include <iterator>
#include "jit/JitSpewer.h"
#include "jit/shared/Architecture-shared.h"
#include "js/Utility.h"
namespace js {
namespace jit {
static const uint32_t SimdMemoryAlignment =
16;
// Make it 4 to avoid a bunch of div-by-zero warnings
// RISCV64 has 32 64-bit integer registers, x0 though x31.
// The program counter is not accessible as a register.
// RISCV INT Register Convention:
// Name Alias Usage
// x0 zero hardwired to 0, ignores writes
// x1 ra return address for calls
// x2 sp stack pointer
// x3 gp global pointer
// x4 tp thread pointer
// x5-x7 t0-t2 temporary register 0
// x8 fp/s0 Callee-saved register 0 or frame pointer
// x9 s1 Callee-saved register 1
// x10-x11 a0-a1 return value or function argument
// x12-x17 a2-a7 function argument 2
// x18-x27 s2-s11 Callee-saved register
// x28-x31 t3-t6 temporary register 3
// RISCV-64 FP Register Convention:
// Name Alias Usage
// $f0-$f7 $ft0-$ft7 Temporary registers
// $f8-$f9 $fs0-$fs1 Callee-saved registers
// $f10-$f11 $fa0-$fa1 Return values
// $f12-$f17 $fa2-$fa7 Args values
// $f18-$f27 $fs2-$fs11 Callee-saved registers
// $f28-$f31 $ft8-$ft11 Temporary registers
class Registers {
public:
enum RegisterID {
x0 = 0,
x1,
x2,
x3,
x4,
x5,
x6,
x7,
x8,
x9,
x10,
x11,
x12,
x13,
x14,
x15,
x16,
x17,
x18,
x19,
x20,
x21,
x22,
x23,
x24,
x25,
x26,
x27,
x28,
x29,
x30,
x31,
zero = x0,
ra = x1,
sp = x2,
gp = x3,
tp = x4,
t0 = x5,
t1 = x6,
t2 = x7,
fp = x8,
s1 = x9,
a0 = x10,
a1 = x11,
a2 = x12,
a3 = x13,
a4 = x14,
a5 = x15,
a6 = x16,
a7 = x17,
s2 = x18,
s3 = x19,
s4 = x20,
s5 = x21,
s6 = x22,
s7 = x23,
s8 = x24,
s9 = x25,
s10 = x26,
s11 = x27,
t3 = x28,
t4 = x29,
t5 = x30,
t6 = x31,
invalid_reg,
};
typedef uint8_t Code;
typedef RegisterID Encoding;
union RegisterContent {
uintptr_t r;
};
typedef uint32_t SetType;
static uint32_t SetSize(SetType x) {
static_assert(
sizeof(SetType) == 4,
"SetType must be 32 bits");
return mozilla::CountPopulation32(x);
}
static uint32_t FirstBit(SetType x) {
return mozilla::CountTrailingZeroes32(x);
}
static uint32_t LastBit(SetType x) {
return 31 - mozilla::CountLeadingZeroes32(x);
}
static const char* GetName(uint32_t code) {
static const char*
const Names[] = {
"zero",
"ra",
"sp",
"gp",
"tp",
"t0",
"t1",
"t2",
"fp",
"s1",
"a0",
"a1",
"a2",
"a3",
"a4",
"a5",
"a6",
"a7",
"s2",
"s3",
"s4",
"s5",
"s6",
"s7",
"s8",
"s9",
"s10",
"s11",
"t3",
"t4",
"t5",
"t6"};
static_assert(Total == std::size(Names),
"Table is the correct size");
if (code >= Total) {
return "invalid";
}
return Names[code];
}
static Code FromName(
const char*);
static const Encoding StackPointer = sp;
static const Encoding Invalid = invalid_reg;
static const uint32_t Total = 32;
static const uint32_t TotalPhys = 32;
static const uint32_t Allocatable = 24;
static const SetType NoneMask = 0x0;
static const SetType AllMask = 0xFFFFFFFF;
static const SetType ArgRegMask =
(1 << Registers::a0) | (1 << Registers::a1) | (1 << Registers::a2) |
(1 << Registers::a3) | (1 << Registers::a4) | (1 << Registers::a5) |
(1 << Registers::a6) | (1 << Registers::a7);
static const SetType VolatileMask =
ArgRegMask | (1 << Registers::t0) | (1 << Registers::t1) |
(1 << Registers::t2) | (1 << Registers::t3) | (1 << Registers::t4) |
(1 << Registers::t5) | (1 << Registers::t6);
// We use this constant to save registers when entering functions. This
// is why $ra is added here even though it is not "Non Volatile".
static const SetType NonVolatileMask =
(1 << Registers::ra) | (1 << Registers::fp) | (1 << Registers::s1) |
(1 << Registers::s2) | (1 << Registers::s3) | (1 << Registers::s4) |
(1 << Registers::s5) | (1 << Registers::s6) | (1 << Registers::s7) |
(1 << Registers::s8) | (1 << Registers::s9) | (1 << Registers::s10) |
(1 << Registers::s11);
static const SetType NonAllocatableMask =
(1 << Registers::zero) |
// Always be zero.
(1 << Registers::t4) |
// Scratch reg
(1 << Registers::t5) |
// Scratch reg
(1 << Registers::t6) |
// Scratch reg or call reg
(1 << Registers::s11) |
// Scratch reg
(1 << Registers::ra) | (1 << Registers::tp) | (1 << Registers::sp) |
(1 << Registers::fp) | (1 << Registers::gp);
static const SetType AllocatableMask = AllMask & ~NonAllocatableMask;
// Registers returned from a JS -> JS call.
static const SetType JSCallMask = (1 << Registers::a2);
// Registers returned from a JS -> C call.
static const SetType CallMask = (1 << Registers::a0);
static const SetType WrapperMask = VolatileMask;
};
// Smallest integer type that can hold a register bitmask.
typedef uint32_t PackedRegisterMask;
class FloatRegisters {
public:
enum FPRegisterID {
f0 = 0,
f1,
f2,
f3,
f4,
f5,
f6,
f7,
f8,
f9,
f10,
f11,
f12,
f13,
f14,
f15,
f16,
f17,
f18,
f19,
f20,
f21,
f22,
f23,
f24,
f25,
f26,
f27,
f28,
f29,
f30,
f31,
invalid_reg,
ft0 = f0,
ft1 = f1,
ft2 = f2,
ft3 = f3,
ft4 = f4,
ft5 = f5,
ft6 = f6,
ft7 = f7,
fs0 = f8,
fs1 = f9,
fa0 = f10,
fa1 = f11,
fa2 = f12,
fa3 = f13,
fa4 = f14,
fa5 = f15,
fa6 = f16,
fa7 = f17,
fs2 = f18,
fs3 = f19,
fs4 = f20,
fs5 = f21,
fs6 = f22,
fs7 = f23,
fs8 = f24,
fs9 = f25,
fs10 = f26,
fs11 = f27,
// Scratch register
ft8 = f28,
ft9 = f29,
ft10 = f30,
// Scratch register
ft11 = f31
};
enum Kind : uint8_t {
Double, NumTypes, Single };
typedef FPRegisterID Code;
typedef FPRegisterID Encoding;
union RegisterContent {
float s;
double d;
};
static const char* GetName(uint32_t code) {
static const char*
const Names[] = {
"ft0",
"ft1",
"ft2",
"ft3",
"ft4",
"ft5",
"ft6",
"ft7",
"fs0",
"fs2",
"fa0",
"fa1",
"fa2",
"fa3",
"fa4",
"fa5",
"fa6",
"fa7",
"fs2",
"fs3",
"fs4",
"fs5",
"fs6",
"fs7",
"fs8",
"fs9",
"fs10",
"fs11",
"ft8",
"ft9",
"ft10",
"ft11"};
static_assert(TotalPhys == std::size(Names),
"Table is the correct size");
if (code >= Total) {
return "invalid";
}
return Names[code];
}
static Code FromName(
const char* name);
typedef uint32_t SetType;
static const Code Invalid = invalid_reg;
static const uint32_t Total = 32;
static const uint32_t TotalPhys = 32;
static const uint32_t Allocatable = 23;
static const SetType AllPhysMask = 0xFFFFFFFF;
static const SetType AllMask = 0xFFFFFFFF;
static const SetType AllDoubleMask = AllMask;
// Single values are stored as 64 bits values (NaN-boxed) when pushing them to
// the stack, we do not require making distinctions between the 2 types, and
// therefore the masks are overlapping.See The RISC-V Instruction Set Manual
// for 14.2 NaN Boxing of Narrower Values.
static const SetType AllSingleMask = AllMask;
static const SetType NonVolatileMask =
SetType((1 << FloatRegisters::fs0) | (1 << FloatRegisters::fs1) |
(1 << FloatRegisters::fs2) | (1 << FloatRegisters::fs3) |
(1 << FloatRegisters::fs4) | (1 << FloatRegisters::fs5) |
(1 << FloatRegisters::fs6) | (1 << FloatRegisters::fs7) |
(1 << FloatRegisters::fs8) | (1 << FloatRegisters::fs9) |
(1 << FloatRegisters::fs10) | (1 << FloatRegisters::fs11));
static const SetType VolatileMask = AllMask & ~NonVolatileMask;
// fs11/ft10 is the scratch register.
static const SetType NonAllocatableMask =
SetType((1 << FloatRegisters::fs11) | (1 << FloatRegisters::ft10));
static const SetType AllocatableMask = AllMask & ~NonAllocatableMask;
};
template <
typename T>
class TypedRegisterSet;
struct FloatRegister {
public:
typedef FloatRegisters Codes;
typedef Codes::Code Code;
typedef Codes::Encoding Encoding;
typedef Codes::SetType SetType;
static uint32_t SetSize(SetType x) {
static_assert(
sizeof(SetType) == 4,
"SetType must be 32 bits");
x &= FloatRegisters::AllPhysMask;
return mozilla::CountPopulation32(x);
}
static uint32_t FirstBit(SetType x) {
static_assert(
sizeof(SetType) == 4,
"SetType");
return mozilla::CountTrailingZeroes64(x);
}
static uint32_t LastBit(SetType x) {
static_assert(
sizeof(SetType) == 4,
"SetType");
return 31 - mozilla::CountLeadingZeroes64(x);
}
static FloatRegister FromCode(uint32_t i) {
uint32_t code = i & 0x1f;
return FloatRegister(Code(code));
}
bool isSimd128()
const {
return false; }
bool isInvalid()
const {
return invalid_; }
FloatRegister asSingle()
const {
MOZ_ASSERT(!invalid_);
return FloatRegister(Encoding(encoding_), FloatRegisters::Single);
}
FloatRegister asDouble()
const {
MOZ_ASSERT(!invalid_);
return FloatRegister(Encoding(encoding_), FloatRegisters::
Double);
}
FloatRegister asSimd128()
const { MOZ_CRASH(); }
constexpr Code code()
const {
MOZ_ASSERT(!invalid_);
return encoding_;
}
Encoding encoding()
const {
return encoding_; }
const char* name()
const {
return FloatRegisters::GetName(code()); }
bool volatile_()
const {
MOZ_ASSERT(!invalid_);
return !!((SetType(1) << code()) & FloatRegisters::VolatileMask);
}
bool operator!=(FloatRegister other)
const {
return code() != other.code(); }
bool operator==(FloatRegister other)
const {
return code() == other.code(); }
bool aliases(FloatRegister other)
const {
return other.encoding_ == encoding_;
}
uint32_t numAliased()
const {
return 1; }
FloatRegister aliased(uint32_t aliasIdx)
const {
MOZ_ASSERT(aliasIdx == 0);
return *
this;
}
// Ensure that two floating point registers' types are equivalent.
bool equiv(FloatRegister other)
const {
MOZ_ASSERT(!invalid_);
return kind_ == other.kind_;
}
constexpr uint32_t size()
const {
MOZ_ASSERT(!invalid_);
if (kind_ == FloatRegisters::
Double) {
return sizeof(
double);
}
MOZ_ASSERT(kind_ == FloatRegisters::Single);
return sizeof(
float);
}
uint32_t numAlignedAliased() {
return numAliased(); }
FloatRegister alignedAliased(uint32_t aliasIdx) {
MOZ_ASSERT(aliasIdx < numAliased());
return aliased(aliasIdx);
}
SetType alignedOrDominatedAliasedSet()
const {
return SetType(1) << code(); }
static constexpr RegTypeName DefaultType = RegTypeName::Float64;
template <RegTypeName Name = DefaultType>
static SetType LiveAsIndexableSet(SetType s) {
return SetType(0);
}
template <RegTypeName Name = DefaultType>
static SetType AllocatableAsIndexableSet(SetType s) {
static_assert(Name != RegTypeName::Any,
"Allocatable set are not iterable");
return LiveAsIndexableSet<Name>(s);
}
FloatRegister singleOverlay()
const;
FloatRegister doubleOverlay()
const;
static TypedRegisterSet<FloatRegister> ReduceSetForPush(
const TypedRegisterSet<FloatRegister>& s);
uint32_t getRegisterDumpOffsetInBytes() {
#ifdef ENABLE_WASM_SIMD
# error
"Needs more careful logic if SIMD is enabled"
#endif
return code() *
sizeof(
double);
}
static Code FromName(
const char* name);
// This is used in static initializers, so produce a bogus value instead of
// crashing.
static uint32_t GetPushSizeInBytes(
const TypedRegisterSet<FloatRegister>& s);
private:
typedef Codes::Kind Kind;
// These fields only hold valid values: an invalid register is always
// represented as a valid encoding and kind with the invalid_ bit set.
Encoding encoding_;
// 32 encodings
Kind kind_;
// Double, Single; more later
bool invalid_;
public:
constexpr FloatRegister(Encoding encoding, Kind kind)
: encoding_(encoding), kind_(kind), invalid_(
false) {
MOZ_ASSERT(uint32_t(encoding) < Codes::Total);
}
constexpr FloatRegister(Encoding encoding)
: encoding_(encoding), kind_(FloatRegisters::
Double), invalid_(
false) {
MOZ_ASSERT(uint32_t(encoding) < Codes::Total);
}
constexpr FloatRegister()
: encoding_(FloatRegisters::invalid_reg),
kind_(FloatRegisters::
Double),
invalid_(
true) {}
bool isSingle()
const {
MOZ_ASSERT(!invalid_);
// On riscv64 arch, float register and double register using the same
// register file.
return kind_ == FloatRegisters::Single || kind_ == FloatRegisters::
Double;
}
bool isDouble()
const {
MOZ_ASSERT(!invalid_);
return kind_ == FloatRegisters::
Double;
}
Encoding code() {
return encoding_; }
};
template <>
inline FloatRegister::SetType
FloatRegister::LiveAsIndexableSet<RegTypeName::Float32>(SetType set) {
return set & FloatRegisters::AllSingleMask;
}
template <>
inline FloatRegister::SetType
FloatRegister::LiveAsIndexableSet<RegTypeName::Float64>(SetType set) {
return set & FloatRegisters::AllDoubleMask;
}
template <>
inline FloatRegister::SetType
FloatRegister::LiveAsIndexableSet<RegTypeName::Any>(SetType set) {
return set;
}
inline bool hasUnaliasedDouble() {
return false; }
inline bool hasMultiAlias() {
return false; }
static constexpr uint32_t ShadowStackSpace = 0;
static const uint32_t JumpImmediateRange = INT32_MAX;
#ifdef JS_NUNBOX32
static const int32_t NUNBOX32_TYPE_OFFSET = 4;
static const int32_t NUNBOX32_PAYLOAD_OFFSET = 0;
#endif
static const uint32_t SpillSlotSize =
std::max(
sizeof(Registers::RegisterContent),
sizeof(FloatRegisters::RegisterContent));
inline uint32_t GetRISCV64Flags() {
return 0; }
}
// namespace jit
}
// namespace js
#endif /* jit_riscv64_Architecture_riscv64_h */