blob: 0f9b939ba35887f4cc4503eea48ec8d9a0f90cdb [file] [log] [blame] [edit]
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#include "vm/globals.h" // Needed here to get TARGET_ARCH_RISCV*.
#if defined(TARGET_ARCH_RISCV32) || defined(TARGET_ARCH_RISCV64)
#include "vm/instructions.h"
#include "vm/instructions_riscv.h"
#include "vm/constants.h"
#include "vm/cpu.h"
#include "vm/object.h"
#include "vm/object_store.h"
#include "vm/reverse_pc_lookup_cache.h"
namespace dart {
static bool IsJumpAndLinkScratch(Register reg) {
return reg == (FLAG_precompiled_mode ? TMP : CODE_REG);
}
CallPattern::CallPattern(uword pc, const Code& code)
: object_pool_(ObjectPool::Handle(code.GetObjectPool())),
target_code_pool_index_(-1) {
ASSERT(code.ContainsInstructionAt(pc));
// R is either CODE_REG (JIT) or TMP (AOT)
// [lui,add,]lx R, ##(pp)
// xxxxxxxx lx ra, ##(R)
// xxxx jalr ra
// Last instruction: jalr ra.
ASSERT(*reinterpret_cast<uint16_t*>(pc - 2) == 0x9082);
Register reg;
InstructionPattern::DecodeLoadWordFromPool(pc - 6, &reg,
&target_code_pool_index_);
ASSERT(IsJumpAndLinkScratch(reg));
}
ICCallPattern::ICCallPattern(uword pc, const Code& code)
: object_pool_(ObjectPool::Handle(code.GetObjectPool())),
target_pool_index_(-1),
data_pool_index_(-1) {
ASSERT(code.ContainsInstructionAt(pc));
// R is either CODE_REG (JIT) or TMP (AOT)
// [lui,add,]lx IC_DATA_REG, ##(pp)
// [lui,add,]lx R, ##(pp)
// xxxxxxxx lx ra, ##(R)
// xxxx jalr ra
// Last instruction: jalr ra.
ASSERT(*reinterpret_cast<uint16_t*>(pc - 2) == 0x9082);
Register reg;
uword data_load_end = InstructionPattern::DecodeLoadWordFromPool(
pc - 6, &reg, &target_pool_index_);
ASSERT(IsJumpAndLinkScratch(reg));
InstructionPattern::DecodeLoadWordFromPool(data_load_end, &reg,
&data_pool_index_);
ASSERT(reg == IC_DATA_REG);
}
NativeCallPattern::NativeCallPattern(uword pc, const Code& code)
: object_pool_(ObjectPool::Handle(code.GetObjectPool())),
end_(pc),
native_function_pool_index_(-1),
target_code_pool_index_(-1) {
ASSERT(code.ContainsInstructionAt(pc));
// R is either CODE_REG (JIT) or TMP (AOT)
// [lui,add,]lx t5, ##(pp)
// [lui,add,]lx R, ##(pp)
// xxxxxxxx lx ra, ##(R)
// xxxx jalr ra
// Last instruction: jalr ra.
ASSERT(*reinterpret_cast<uint16_t*>(pc - 2) == 0x9082);
Register reg;
uword native_function_load_end = InstructionPattern::DecodeLoadWordFromPool(
pc - 6, &reg, &target_code_pool_index_);
ASSERT(IsJumpAndLinkScratch(reg));
InstructionPattern::DecodeLoadWordFromPool(native_function_load_end, &reg,
&native_function_pool_index_);
ASSERT(reg == T5);
}
CodePtr NativeCallPattern::target() const {
return static_cast<CodePtr>(object_pool_.ObjectAt(target_code_pool_index_));
}
void NativeCallPattern::set_target(const Code& target) const {
object_pool_.SetObjectAt(target_code_pool_index_, target);
// No need to flush the instruction cache, since the code is not modified.
}
NativeFunction NativeCallPattern::native_function() const {
return reinterpret_cast<NativeFunction>(
object_pool_.RawValueAt(native_function_pool_index_));
}
void NativeCallPattern::set_native_function(NativeFunction func) const {
object_pool_.SetRawValueAt(native_function_pool_index_,
reinterpret_cast<uword>(func));
}
// Decodes a load sequence ending at 'end' (the last instruction of the load
// sequence is the instruction before the one at end). Returns a pointer to
// the first instruction in the sequence. Returns the register being loaded
// and the loaded immediate value in the output parameters 'reg' and 'value'
// respectively.
uword InstructionPattern::DecodeLoadWordImmediate(uword end,
Register* reg,
intptr_t* value) {
UNIMPLEMENTED();
return 0;
}
static bool DecodeLoadX(uword end,
Register* dst,
Register* base,
intptr_t* offset,
intptr_t* length) {
Instr instr(LoadUnaligned(reinterpret_cast<uint32_t*>(end - 4)));
#if XLEN == 32
if (instr.opcode() == LOAD && instr.funct3() == LW) {
#elif XLEN == 64
if (instr.opcode() == LOAD && instr.funct3() == LD) {
#endif
*dst = instr.rd();
*base = instr.rs1();
*offset = instr.itype_imm();
*length = 4;
return true;
}
CInstr cinstr(*reinterpret_cast<uint16_t*>(end - 2));
#if XLEN == 32
if (cinstr.opcode() == C_LW) {
#elif XLEN == 64
if (cinstr.opcode() == C_LD) {
#endif
*dst = cinstr.rdp();
*base = cinstr.rs1p();
#if XLEN == 32
*offset = cinstr.mem4_imm();
#elif XLEN == 64
*offset = cinstr.mem8_imm();
#endif
*length = 2;
return true;
}
return false;
}
static bool DecodeLUI(uword end,
Register* dst,
intptr_t* imm,
intptr_t* length) {
Instr instr(LoadUnaligned(reinterpret_cast<uint32_t*>(end - 4)));
if (instr.opcode() == LUI) {
*dst = instr.rd();
*imm = instr.utype_imm();
*length = 4;
return true;
}
CInstr cinstr(*reinterpret_cast<uint16_t*>(end - 2));
if (cinstr.opcode() == C_LUI) {
*dst = cinstr.rd();
*imm = cinstr.u_imm();
*length = 2;
return true;
}
return false;
}
// See comment in instructions_arm64.h
uword InstructionPattern::DecodeLoadWordFromPool(uword end,
Register* reg,
intptr_t* index) {
// [c.]lx dst, offset(pp)
// or
// [c.]lui dst, hi
// c.add dst, dst, pp
// [c.]lx dst, lo(dst)
Register base;
intptr_t lo, length;
if (!DecodeLoadX(end, reg, &base, &lo, &length)) {
UNREACHABLE();
}
if (base == PP) {
// PP is untagged on RISCV.
*index = ObjectPool::IndexFromOffset(lo - kHeapObjectTag);
return end - length;
}
ASSERT(base == *reg);
end -= length;
CInstr add_instr(*reinterpret_cast<uint16_t*>(end - 2));
ASSERT(add_instr.opcode() ==
C_MV); // Not C_ADD, which extends past the opcode proper.
ASSERT(add_instr.rd() == base);
ASSERT(add_instr.rs1() == base);
ASSERT(add_instr.rs2() == PP);
end -= 2;
Register dst;
intptr_t hi;
if (!DecodeLUI(end, &dst, &hi, &length)) {
UNREACHABLE();
}
ASSERT(dst == base);
// PP is untagged on RISC-V.
*index = ObjectPool::IndexFromOffset(hi + lo - kHeapObjectTag);
return end - length;
}
bool DecodeLoadObjectFromPoolOrThread(uword pc, const Code& code, Object* obj) {
ASSERT(code.ContainsInstructionAt(pc));
uint16_t parcel = *reinterpret_cast<uint16_t*>(pc);
if (IsCInstruction(parcel)) {
CInstr instr(parcel);
#if XLEN == 32
if (instr.opcode() == C_LW) {
intptr_t offset = instr.mem4_imm();
#elif XLEN == 64
if (instr.opcode() == C_LD) {
intptr_t offset = instr.mem8_imm();
#endif
if (instr.rs1p() == PP) {
// PP is untagged on RISC-V.
if (!Utils::IsAligned(offset, kWordSize)) {
return false; // Being used as argument register A5.
}
intptr_t index = ObjectPool::IndexFromOffset(offset - kHeapObjectTag);
return ObjectAtPoolIndex(code, index, obj);
} else if (instr.rs1p() == THR) {
return Thread::ObjectAtOffset(offset, obj);
}
}
} else {
Instr instr(LoadUnaligned(reinterpret_cast<uint32_t*>(pc)));
#if XLEN == 32
if (instr.opcode() == LOAD && instr.funct3() == LW) {
#elif XLEN == 64
if (instr.opcode() == LOAD && instr.funct3() == LD) {
#endif
intptr_t offset = instr.itype_imm();
if (instr.rs1() == PP) {
// PP is untagged on RISC-V.
if (!Utils::IsAligned(offset, kWordSize)) {
return false; // Being used as argument register A5.
}
intptr_t index = ObjectPool::IndexFromOffset(offset - kHeapObjectTag);
return ObjectAtPoolIndex(code, index, obj);
} else if (instr.rs1() == THR) {
return Thread::ObjectAtOffset(offset, obj);
}
}
if ((instr.opcode() == OPIMM) && (instr.funct3() == ADDI) &&
(instr.rs1() == NULL_REG)) {
if (instr.itype_imm() == 0) {
*obj = Object::null();
return true;
}
if (instr.itype_imm() == kTrueOffsetFromNull) {
*obj = Object::bool_true().ptr();
return true;
}
if (instr.itype_imm() == kFalseOffsetFromNull) {
*obj = Object::bool_false().ptr();
return true;
}
}
}
// TODO(riscv): Loads with offsets beyond 12 bits.
return false;
}
// Encodes a load sequence ending at 'end'. Encodes a fixed length two
// instruction load from the pool pointer in PP using the destination
// register reg as a temporary for the base address.
// Assumes that the location has already been validated for patching.
void InstructionPattern::EncodeLoadWordFromPoolFixed(uword end,
int32_t offset) {
UNIMPLEMENTED();
}
CodePtr CallPattern::TargetCode() const {
return static_cast<CodePtr>(object_pool_.ObjectAt(target_code_pool_index_));
}
void CallPattern::SetTargetCode(const Code& target) const {
object_pool_.SetObjectAt(target_code_pool_index_, target);
// No need to flush the instruction cache, since the code is not modified.
}
ObjectPtr ICCallPattern::Data() const {
return object_pool_.ObjectAt(data_pool_index_);
}
void ICCallPattern::SetData(const Object& data) const {
ASSERT(data.IsArray() || data.IsICData() || data.IsMegamorphicCache());
object_pool_.SetObjectAt(data_pool_index_, data);
}
CodePtr ICCallPattern::TargetCode() const {
return static_cast<CodePtr>(object_pool_.ObjectAt(target_pool_index_));
}
void ICCallPattern::SetTargetCode(const Code& target) const {
object_pool_.SetObjectAt(target_pool_index_, target);
// No need to flush the instruction cache, since the code is not modified.
}
SwitchableCallPatternBase::SwitchableCallPatternBase(
const ObjectPool& object_pool)
: object_pool_(object_pool), data_pool_index_(-1), target_pool_index_(-1) {}
ObjectPtr SwitchableCallPatternBase::data() const {
return object_pool_.ObjectAt(data_pool_index_);
}
void SwitchableCallPatternBase::SetData(const Object& data) const {
ASSERT(!Object::Handle(object_pool_.ObjectAt(data_pool_index_)).IsCode());
object_pool_.SetObjectAt(data_pool_index_, data);
}
SwitchableCallPattern::SwitchableCallPattern(uword pc, const Code& code)
: SwitchableCallPatternBase(ObjectPool::Handle(code.GetObjectPool())) {
ASSERT(code.ContainsInstructionAt(pc));
UNIMPLEMENTED();
}
uword SwitchableCallPattern::target_entry() const {
return Code::Handle(Code::RawCast(object_pool_.ObjectAt(target_pool_index_)))
.MonomorphicEntryPoint();
}
void SwitchableCallPattern::SetTarget(const Code& target) const {
ASSERT(Object::Handle(object_pool_.ObjectAt(target_pool_index_)).IsCode());
object_pool_.SetObjectAt(target_pool_index_, target);
}
BareSwitchableCallPattern::BareSwitchableCallPattern(uword pc)
: SwitchableCallPatternBase(ObjectPool::Handle(
IsolateGroup::Current()->object_store()->global_object_pool())) {
// [lui,add,]lx RA, ##(pp)
// [lui,add,]lx IC_DATA_REG, ##(pp)
// xxxx jalr RA
// Last instruction: jalr ra.
ASSERT(*reinterpret_cast<uint16_t*>(pc - 2) == 0x9082);
Register reg;
uword target_load_end = InstructionPattern::DecodeLoadWordFromPool(
pc - 2, &reg, &data_pool_index_);
ASSERT_EQUAL(reg, IC_DATA_REG);
InstructionPattern::DecodeLoadWordFromPool(target_load_end, &reg,
&target_pool_index_);
ASSERT_EQUAL(reg, RA);
}
uword BareSwitchableCallPattern::target_entry() const {
return object_pool_.RawValueAt(target_pool_index_);
}
void BareSwitchableCallPattern::SetTarget(const Code& target) const {
ASSERT(object_pool_.TypeAt(target_pool_index_) ==
ObjectPool::EntryType::kImmediate);
object_pool_.SetRawValueAt(target_pool_index_,
target.MonomorphicEntryPoint());
}
ReturnPattern::ReturnPattern(uword pc) : pc_(pc) {}
bool ReturnPattern::IsValid() const {
return *reinterpret_cast<uint16_t*>(pc_) == 0x8082;
}
bool PcRelativeCallPattern::IsValid() const {
Instr aupic(LoadUnaligned(reinterpret_cast<uint32_t*>(pc_)));
if (aupic.opcode() != AUIPC) return false;
Instr jalr(LoadUnaligned(reinterpret_cast<uint32_t*>(pc_ + 4)));
if (jalr.opcode() != JALR) return false;
if (aupic.rd() != jalr.rs1()) return false;
if (jalr.rd() != RA) return false;
return true;
}
bool PcRelativeTailCallPattern::IsValid() const {
Instr aupic(LoadUnaligned(reinterpret_cast<uint32_t*>(pc_)));
if (aupic.opcode() != AUIPC) return false;
Instr jr(LoadUnaligned(reinterpret_cast<uint32_t*>(pc_ + 4)));
if (jr.opcode() != JALR) return false;
if (aupic.rd() != jr.rs1()) return false;
if (jr.rd() != ZR) return false;
return true;
}
void PcRelativeTrampolineJumpPattern::Initialize() {
StoreUnaligned(reinterpret_cast<uint32_t*>(pc_),
EncodeOpcode(AUIPC) | EncodeRd(TMP) | EncodeUTypeImm(0));
StoreUnaligned(reinterpret_cast<uint32_t*>(pc_ + 4),
EncodeOpcode(JALR) | EncodeFunct3(F3_0) | EncodeRd(ZR) |
EncodeRs1(TMP) | EncodeITypeImm(0));
}
intptr_t TypeTestingStubCallPattern::GetSubtypeTestCachePoolIndex() {
// Calls to the type testing stubs look like:
// lx s4, ...
// lx Rn, idx(pp)
// jalr s4
// where Rn = TypeTestABI::kSubtypeTestCacheReg.
// Ensure the caller of the type testing stub (whose return address is [pc_])
// branched via `jalr s3` or a pc-relative call.
if (*reinterpret_cast<uint16_t*>(pc_ - 2) == 0x9982) { // jalr s3
// indirect call
// xxxx c.jalr s3
Register reg;
intptr_t pool_index = -1;
InstructionPattern::DecodeLoadWordFromPool(pc_ - 2, &reg, &pool_index);
ASSERT_EQUAL(reg, TypeTestABI::kSubtypeTestCacheReg);
return pool_index;
} else {
ASSERT(FLAG_precompiled_mode);
// pc-relative call
// xxxxxxxx aupic ra, hi
// xxxxxxxx jalr ra, lo
Instr jalr(LoadUnaligned(reinterpret_cast<uint32_t*>(pc_ - 4)));
ASSERT(jalr.opcode() == JALR);
Instr auipc(LoadUnaligned(reinterpret_cast<uint32_t*>(pc_ - 8)));
ASSERT(auipc.opcode() == AUIPC);
Register reg;
intptr_t pool_index = -1;
InstructionPattern::DecodeLoadWordFromPool(pc_ - 8, &reg, &pool_index);
ASSERT_EQUAL(reg, TypeTestABI::kSubtypeTestCacheReg);
return pool_index;
}
}
} // namespace dart
#endif // defined TARGET_ARCH_RISCV