|  | // 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, ®, | 
|  | &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, ®, &target_pool_index_); | 
|  | ASSERT(IsJumpAndLinkScratch(reg)); | 
|  |  | 
|  | InstructionPattern::DecodeLoadWordFromPool(data_load_end, ®, | 
|  | &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, ®, &target_code_pool_index_); | 
|  | ASSERT(IsJumpAndLinkScratch(reg)); | 
|  | InstructionPattern::DecodeLoadWordFromPool(native_function_load_end, ®, | 
|  | &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, ®, &data_pool_index_); | 
|  | ASSERT_EQUAL(reg, IC_DATA_REG); | 
|  |  | 
|  | InstructionPattern::DecodeLoadWordFromPool(target_load_end, ®, | 
|  | &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, ®, &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, ®, &pool_index); | 
|  | ASSERT_EQUAL(reg, TypeTestABI::kSubtypeTestCacheReg); | 
|  | return pool_index; | 
|  | } | 
|  | } | 
|  |  | 
|  | }  // namespace dart | 
|  |  | 
|  | #endif  // defined TARGET_ARCH_RISCV |