diff options
Diffstat (limited to 'compiler')
38 files changed, 2383 insertions, 976 deletions
diff --git a/compiler/Android.mk b/compiler/Android.mk index 904f117a5a..eaea031b62 100644 --- a/compiler/Android.mk +++ b/compiler/Android.mk @@ -79,6 +79,13 @@ LIBART_COMPILER_SRC_FILES := \ driver/compiler_driver.cc \ driver/compiler_options.cc \ driver/dex_compilation_unit.cc \ + linker/relative_patcher.cc \ + linker/arm/relative_patcher_arm_base.cc \ + linker/arm/relative_patcher_thumb2.cc \ + linker/arm64/relative_patcher_arm64.cc \ + linker/x86/relative_patcher_x86_base.cc \ + linker/x86/relative_patcher_x86.cc \ + linker/x86_64/relative_patcher_x86_64.cc \ jit/jit_compiler.cc \ jni/quick/arm/calling_convention_arm.cc \ jni/quick/arm64/calling_convention_arm64.cc \ diff --git a/compiler/compiled_method.cc b/compiler/compiled_method.cc index 1849e7ef64..03370db6c0 100644 --- a/compiler/compiled_method.cc +++ b/compiler/compiled_method.cc @@ -142,7 +142,6 @@ CompiledMethod::CompiledMethod(CompilerDriver* driver, if (src_mapping_table == nullptr) { src_mapping_table_ = new SwapSrcMap(driver->GetSwapSpaceAllocator()); } else { - src_mapping_table->Arrange(); src_mapping_table_ = new SwapSrcMap(src_mapping_table->begin(), src_mapping_table->end(), driver->GetSwapSpaceAllocator()); } @@ -159,7 +158,7 @@ CompiledMethod::CompiledMethod(CompilerDriver* driver, } else { src_mapping_table_ = src_mapping_table == nullptr ? driver->DeduplicateSrcMappingTable(ArrayRef<SrcMapElem>()) : - driver->DeduplicateSrcMappingTable(ArrayRef<SrcMapElem>(src_mapping_table->Arrange())); + driver->DeduplicateSrcMappingTable(ArrayRef<SrcMapElem>(*src_mapping_table)); mapping_table_ = mapping_table.empty() ? nullptr : driver->DeduplicateMappingTable(mapping_table); vmap_table_ = driver->DeduplicateVMapTable(vmap_table); diff --git a/compiler/compiled_method.h b/compiler/compiled_method.h index 23869140ae..7497b175fc 100644 --- a/compiler/compiled_method.h +++ b/compiler/compiled_method.h @@ -94,20 +94,12 @@ class SrcMapElem { uint32_t from_; int32_t to_; - explicit operator int64_t() const { - return (static_cast<int64_t>(to_) << 32) | from_; - } - - bool operator<(const SrcMapElem& sme) const { - return int64_t(*this) < int64_t(sme); - } - - bool operator==(const SrcMapElem& sme) const { - return int64_t(*this) == int64_t(sme); - } - - explicit operator uint8_t() const { - return static_cast<uint8_t>(from_ + to_); + // Lexicographical compare. + bool operator<(const SrcMapElem& other) const { + if (from_ != other.from_) { + return from_ < other.from_; + } + return to_ < other.to_; } }; @@ -129,49 +121,33 @@ class SrcMap FINAL : public std::vector<SrcMapElem, Allocator> { SrcMap(InputIt first, InputIt last, const Allocator& alloc) : std::vector<SrcMapElem, Allocator>(first, last, alloc) {} - void SortByFrom() { - std::sort(begin(), end(), [] (const SrcMapElem& lhs, const SrcMapElem& rhs) -> bool { - return lhs.from_ < rhs.from_; - }); - } - - const_iterator FindByTo(int32_t to) const { - return std::lower_bound(begin(), end(), SrcMapElem({0, to})); - } - - SrcMap& Arrange() { - if (!empty()) { - std::sort(begin(), end()); - resize(std::unique(begin(), end()) - begin()); - shrink_to_fit(); - } - return *this; - } - - void DeltaFormat(const SrcMapElem& start, uint32_t highest_pc) { - // Convert from abs values to deltas. + void push_back(const SrcMapElem& elem) { if (!empty()) { - SortByFrom(); - - // TODO: one PC can be mapped to several Java src lines. - // do we want such a one-to-many correspondence? - - // get rid of the highest values - size_t i = size() - 1; - for (; i > 0 ; i--) { - if ((*this)[i].from_ < highest_pc) { - break; - } + // Check that the addresses are inserted in sorted order. + DCHECK_GE(elem.from_, this->back().from_); + // If two consequitive entries map to the same value, ignore the later. + // E.g. for map {{0, 1}, {4, 1}, {8, 2}}, all values in [0,8) map to 1. + if (elem.to_ == this->back().to_) { + return; } - this->resize(i + 1); - - for (i = size(); --i >= 1; ) { - (*this)[i].from_ -= (*this)[i-1].from_; - (*this)[i].to_ -= (*this)[i-1].to_; - } - DCHECK((*this)[0].from_ >= start.from_); - (*this)[0].from_ -= start.from_; - (*this)[0].to_ -= start.to_; + } + std::vector<SrcMapElem, Allocator>::push_back(elem); + } + + // Returns true and the corresponding "to" value if the mapping is found. + // Oterwise returns false and 0. + std::pair<bool, int32_t> Find(uint32_t from) const { + // Finds first mapping such that lb.from_ >= from. + auto lb = std::lower_bound(begin(), end(), SrcMapElem {from, INT32_MIN}); + if (lb != end() && lb->from_ == from) { + // Found exact match. + return std::make_pair(true, lb->to_); + } else if (lb != begin()) { + // The previous mapping is still in effect. + return std::make_pair(true, (--lb)->to_); + } else { + // Not found because 'from' is smaller than first entry in the map. + return std::make_pair(false, 0); } } }; @@ -428,7 +404,7 @@ class CompiledMethod FINAL : public CompiledCode { const uint32_t core_spill_mask_; // For quick code, a bit mask describing spilled FPR callee-save registers. const uint32_t fp_spill_mask_; - // For quick code, a set of pairs (PC, Line) mapping from native PC offset to Java line + // For quick code, a set of pairs (PC, DEX) mapping from native PC offset to DEX offset. SwapSrcMap* src_mapping_table_; // For quick code, a uleb128 encoded map from native PC offset to dex PC aswell as dex PC to // native PC offset. Size prefixed. diff --git a/compiler/dex/compiler_enums.h b/compiler/dex/compiler_enums.h index 39725dee38..0acdd422df 100644 --- a/compiler/dex/compiler_enums.h +++ b/compiler/dex/compiler_enums.h @@ -99,14 +99,16 @@ std::ostream& operator<<(std::ostream& os, const BBType& code); // Shared pseudo opcodes - must be < 0. enum LIRPseudoOpcode { - kPseudoExportedPC = -16, - kPseudoSafepointPC = -15, - kPseudoIntrinsicRetry = -14, - kPseudoSuspendTarget = -13, - kPseudoThrowTarget = -12, - kPseudoCaseLabel = -11, - kPseudoMethodEntry = -10, - kPseudoMethodExit = -9, + kPseudoPrologueBegin = -18, + kPseudoPrologueEnd = -17, + kPseudoEpilogueBegin = -16, + kPseudoEpilogueEnd = -15, + kPseudoExportedPC = -14, + kPseudoSafepointPC = -13, + kPseudoIntrinsicRetry = -12, + kPseudoSuspendTarget = -11, + kPseudoThrowTarget = -10, + kPseudoCaseLabel = -9, kPseudoBarrier = -8, kPseudoEntryBlock = -7, kPseudoExitBlock = -6, diff --git a/compiler/dex/quick/arm/assemble_arm.cc b/compiler/dex/quick/arm/assemble_arm.cc index 3e69878846..c5ac4c1508 100644 --- a/compiler/dex/quick/arm/assemble_arm.cc +++ b/compiler/dex/quick/arm/assemble_arm.cc @@ -1083,7 +1083,9 @@ void ArmMir2Lir::InsertFixupBefore(LIR* prev_lir, LIR* orig_lir, LIR* new_lir) { #define PADDING_MOV_R5_R5 0x1C2D uint8_t* ArmMir2Lir::EncodeLIRs(uint8_t* write_pos, LIR* lir) { + uint8_t* const write_buffer = write_pos; for (; lir != NULL; lir = NEXT_LIR(lir)) { + lir->offset = (write_pos - write_buffer); if (!lir->flags.is_nop) { int opcode = lir->opcode; if (IsPseudoLirOp(opcode)) { diff --git a/compiler/dex/quick/arm/call_arm.cc b/compiler/dex/quick/arm/call_arm.cc index 3081c9e752..e6158c3200 100644 --- a/compiler/dex/quick/arm/call_arm.cc +++ b/compiler/dex/quick/arm/call_arm.cc @@ -372,7 +372,6 @@ void ArmMir2Lir::GenEntrySequence(RegLocation* ArgLocs, RegLocation rl_method) { * a leaf *and* our frame size < fudge factor. */ bool skip_overflow_check = mir_graph_->MethodIsLeaf() && !FrameNeedsStackCheck(frame_size_, kArm); - NewLIR0(kPseudoMethodEntry); const size_t kStackOverflowReservedUsableBytes = GetStackOverflowReservedBytes(kArm); bool large_frame = (static_cast<size_t>(frame_size_) > kStackOverflowReservedUsableBytes); bool generate_explicit_stack_overflow_check = large_frame || @@ -507,7 +506,6 @@ void ArmMir2Lir::GenExitSequence() { LockTemp(rs_r0); LockTemp(rs_r1); - NewLIR0(kPseudoMethodExit); OpRegImm(kOpAdd, rs_rARM_SP, frame_size_ - (spill_count * 4)); /* Need to restore any FP callee saves? */ if (num_fp_spills_) { diff --git a/compiler/dex/quick/arm64/assemble_arm64.cc b/compiler/dex/quick/arm64/assemble_arm64.cc index a59deb5db7..2f1ae66bfc 100644 --- a/compiler/dex/quick/arm64/assemble_arm64.cc +++ b/compiler/dex/quick/arm64/assemble_arm64.cc @@ -686,7 +686,9 @@ void Arm64Mir2Lir::InsertFixupBefore(LIR* prev_lir, LIR* orig_lir, LIR* new_lir) #define PADDING_NOP (UINT32_C(0xd503201f)) uint8_t* Arm64Mir2Lir::EncodeLIRs(uint8_t* write_pos, LIR* lir) { + uint8_t* const write_buffer = write_pos; for (; lir != nullptr; lir = NEXT_LIR(lir)) { + lir->offset = (write_pos - write_buffer); bool opcode_is_wide = IS_WIDE(lir->opcode); A64Opcode opcode = UNWIDE(lir->opcode); diff --git a/compiler/dex/quick/arm64/call_arm64.cc b/compiler/dex/quick/arm64/call_arm64.cc index 3316945ed1..6b47bba884 100644 --- a/compiler/dex/quick/arm64/call_arm64.cc +++ b/compiler/dex/quick/arm64/call_arm64.cc @@ -312,8 +312,6 @@ void Arm64Mir2Lir::GenEntrySequence(RegLocation* ArgLocs, RegLocation rl_method) bool skip_overflow_check = mir_graph_->MethodIsLeaf() && !FrameNeedsStackCheck(frame_size_, kArm64); - NewLIR0(kPseudoMethodEntry); - const size_t kStackOverflowReservedUsableBytes = GetStackOverflowReservedBytes(kArm64); const bool large_frame = static_cast<size_t>(frame_size_) > kStackOverflowReservedUsableBytes; bool generate_explicit_stack_overflow_check = large_frame || @@ -401,9 +399,6 @@ void Arm64Mir2Lir::GenExitSequence() { */ LockTemp(rs_x0); LockTemp(rs_x1); - - NewLIR0(kPseudoMethodExit); - UnspillRegs(rs_sp, core_spill_mask_, fp_spill_mask_, frame_size_); // Finally return. diff --git a/compiler/dex/quick/codegen_util.cc b/compiler/dex/quick/codegen_util.cc index 509d4487ad..483a5d06cc 100644 --- a/compiler/dex/quick/codegen_util.cc +++ b/compiler/dex/quick/codegen_util.cc @@ -203,12 +203,17 @@ void Mir2Lir::DumpLIRInsn(LIR* lir, unsigned char* base_addr) { /* Handle pseudo-ops individually, and all regular insns as a group */ switch (lir->opcode) { - case kPseudoMethodEntry: - LOG(INFO) << "-------- method entry " - << PrettyMethod(cu_->method_idx, *cu_->dex_file); + case kPseudoPrologueBegin: + LOG(INFO) << "-------- PrologueBegin"; break; - case kPseudoMethodExit: - LOG(INFO) << "-------- Method_Exit"; + case kPseudoPrologueEnd: + LOG(INFO) << "-------- PrologueEnd"; + break; + case kPseudoEpilogueBegin: + LOG(INFO) << "-------- EpilogueBegin"; + break; + case kPseudoEpilogueEnd: + LOG(INFO) << "-------- EpilogueEnd"; break; case kPseudoBarrier: LOG(INFO) << "-------- BARRIER"; @@ -267,8 +272,9 @@ void Mir2Lir::DumpLIRInsn(LIR* lir, unsigned char* base_addr) { lir, base_addr)); std::string op_operands(BuildInsnString(GetTargetInstFmt(lir->opcode), lir, base_addr)); - LOG(INFO) << StringPrintf("%5p: %-9s%s%s", + LOG(INFO) << StringPrintf("%5p|0x%02x: %-9s%s%s", base_addr + offset, + lir->dalvik_offset, op_name.c_str(), op_operands.c_str(), lir->flags.is_nop ? "(nop)" : ""); } @@ -713,14 +719,17 @@ void Mir2Lir::CreateMappingTables() { DCHECK_EQ(static_cast<size_t>(write_pos - &encoded_mapping_table_[0]), hdr_data_size); uint8_t* write_pos2 = write_pos + pc2dex_data_size; + bool is_in_prologue_or_epilogue = false; pc2dex_offset = 0u; pc2dex_dalvik_offset = 0u; dex2pc_offset = 0u; dex2pc_dalvik_offset = 0u; for (LIR* tgt_lir = first_lir_insn_; tgt_lir != nullptr; tgt_lir = NEXT_LIR(tgt_lir)) { - if (generate_src_map && !tgt_lir->flags.is_nop) { - src_mapping_table_.push_back(SrcMapElem({tgt_lir->offset, - static_cast<int32_t>(tgt_lir->dalvik_offset)})); + if (generate_src_map && !tgt_lir->flags.is_nop && tgt_lir->opcode >= 0) { + if (!is_in_prologue_or_epilogue) { + src_mapping_table_.push_back(SrcMapElem({tgt_lir->offset, + static_cast<int32_t>(tgt_lir->dalvik_offset)})); + } } if (!tgt_lir->flags.is_nop && (tgt_lir->opcode == kPseudoSafepointPC)) { DCHECK(pc2dex_offset <= tgt_lir->offset); @@ -738,6 +747,12 @@ void Mir2Lir::CreateMappingTables() { dex2pc_offset = tgt_lir->offset; dex2pc_dalvik_offset = tgt_lir->dalvik_offset; } + if (tgt_lir->opcode == kPseudoPrologueBegin || tgt_lir->opcode == kPseudoEpilogueBegin) { + is_in_prologue_or_epilogue = true; + } + if (tgt_lir->opcode == kPseudoPrologueEnd || tgt_lir->opcode == kPseudoEpilogueEnd) { + is_in_prologue_or_epilogue = false; + } } DCHECK_EQ(static_cast<size_t>(write_pos - &encoded_mapping_table_[0]), hdr_data_size + pc2dex_data_size); diff --git a/compiler/dex/quick/mips/call_mips.cc b/compiler/dex/quick/mips/call_mips.cc index de66b35418..c932df6dc9 100644 --- a/compiler/dex/quick/mips/call_mips.cc +++ b/compiler/dex/quick/mips/call_mips.cc @@ -275,7 +275,6 @@ void MipsMir2Lir::GenEntrySequence(RegLocation* ArgLocs, RegLocation rl_method) */ skip_overflow_check = mir_graph_->MethodIsLeaf() && !FrameNeedsStackCheck(frame_size_, target); - NewLIR0(kPseudoMethodEntry); RegStorage check_reg = AllocPtrSizeTemp(); RegStorage new_sp = AllocPtrSizeTemp(); const RegStorage rs_sp = TargetPtrReg(kSp); @@ -345,7 +344,6 @@ void MipsMir2Lir::GenExitSequence() { LockTemp(TargetPtrReg(kRet0)); LockTemp(TargetPtrReg(kRet1)); - NewLIR0(kPseudoMethodExit); UnSpillCoreRegs(); OpReg(kOpBx, TargetPtrReg(kLr)); } diff --git a/compiler/dex/quick/mir_to_lir.cc b/compiler/dex/quick/mir_to_lir.cc index 0b480a09c6..ed8e21e817 100644 --- a/compiler/dex/quick/mir_to_lir.cc +++ b/compiler/dex/quick/mir_to_lir.cc @@ -1250,10 +1250,14 @@ bool Mir2Lir::MethodBlockCodeGen(BasicBlock* bb) { if (bb->block_type == kEntryBlock) { ResetRegPool(); int start_vreg = mir_graph_->GetFirstInVR(); + AppendLIR(NewLIR0(kPseudoPrologueBegin)); GenEntrySequence(&mir_graph_->reg_location_[start_vreg], mir_graph_->GetMethodLoc()); + AppendLIR(NewLIR0(kPseudoPrologueEnd)); } else if (bb->block_type == kExitBlock) { ResetRegPool(); + AppendLIR(NewLIR0(kPseudoEpilogueBegin)); GenExitSequence(); + AppendLIR(NewLIR0(kPseudoEpilogueEnd)); } for (mir = bb->first_mir_insn; mir != NULL; mir = mir->next) { diff --git a/compiler/dex/quick/x86/call_x86.cc b/compiler/dex/quick/x86/call_x86.cc index e81228a5e9..fd23692d24 100644 --- a/compiler/dex/quick/x86/call_x86.cc +++ b/compiler/dex/quick/x86/call_x86.cc @@ -25,6 +25,7 @@ #include "gc/accounting/card_table.h" #include "mirror/art_method.h" #include "mirror/object_array-inl.h" +#include "utils/dex_cache_arrays_layout-inl.h" #include "x86_lir.h" namespace art { @@ -186,7 +187,6 @@ void X86Mir2Lir::GenEntrySequence(RegLocation* ArgLocs, RegLocation rl_method) { stack_decrement_ = OpRegImm(kOpSub, rs_rSP, frame_size_ - GetInstructionSetPointerSize(cu_->instruction_set)); - NewLIR0(kPseudoMethodEntry); /* Spill core callee saves */ SpillCoreRegs(); SpillFPRegs(); @@ -259,7 +259,6 @@ void X86Mir2Lir::GenExitSequence() { LockTemp(rs_rX86_RET0); LockTemp(rs_rX86_RET1); - NewLIR0(kPseudoMethodExit); UnSpillCoreRegs(); UnSpillFPRegs(); /* Remove frame except for return address */ @@ -322,13 +321,13 @@ void X86Mir2Lir::GenImplicitNullCheck(RegStorage reg, int opt_flags) { * Bit of a hack here - in the absence of a real scheduling pass, * emit the next instruction in static & direct invoke sequences. */ -static int X86NextSDCallInsn(CompilationUnit* cu, CallInfo* info, - int state, const MethodReference& target_method, - uint32_t, - uintptr_t direct_code, uintptr_t direct_method, - InvokeType type) { +int X86Mir2Lir::X86NextSDCallInsn(CompilationUnit* cu, CallInfo* info, + int state, const MethodReference& target_method, + uint32_t, + uintptr_t direct_code, uintptr_t direct_method, + InvokeType type) { UNUSED(info, direct_code); - Mir2Lir* cg = static_cast<Mir2Lir*>(cu->cg.get()); + X86Mir2Lir* cg = static_cast<X86Mir2Lir*>(cu->cg.get()); if (direct_method != 0) { switch (state) { case 0: // Get the current Method* [sets kArg0] @@ -346,6 +345,17 @@ static int X86NextSDCallInsn(CompilationUnit* cu, CallInfo* info, default: return -1; } + } else if (cg->CanUseOpPcRelDexCacheArrayLoad()) { + switch (state) { + case 0: { + CHECK_EQ(cu->dex_file, target_method.dex_file); + size_t offset = cg->dex_cache_arrays_layout_.MethodOffset(target_method.dex_method_index); + cg->OpPcRelDexCacheArrayLoad(cu->dex_file, offset, cg->TargetReg(kArg0, kRef)); + break; + } + default: + return -1; + } } else { RegStorage arg0_ref = cg->TargetReg(kArg0, kRef); switch (state) { diff --git a/compiler/dex/quick/x86/codegen_x86.h b/compiler/dex/quick/x86/codegen_x86.h index 040a8c4bef..758684e835 100644 --- a/compiler/dex/quick/x86/codegen_x86.h +++ b/compiler/dex/quick/x86/codegen_x86.h @@ -104,6 +104,9 @@ class X86Mir2Lir : public Mir2Lir { /// @copydoc Mir2Lir::UnconditionallyMarkGCCard(RegStorage) void UnconditionallyMarkGCCard(RegStorage tgt_addr_reg) OVERRIDE; + bool CanUseOpPcRelDexCacheArrayLoad() const OVERRIDE; + void OpPcRelDexCacheArrayLoad(const DexFile* dex_file, int offset, RegStorage r_dest) OVERRIDE; + void GenImplicitNullCheck(RegStorage reg, int opt_flags) OVERRIDE; // Required for target - register utilities. @@ -952,6 +955,9 @@ class X86Mir2Lir : public Mir2Lir { // Instructions needing patching with PC relative code addresses. ArenaVector<LIR*> call_method_insns_; + // Instructions needing patching with PC relative code addresses. + ArenaVector<LIR*> dex_cache_access_insns_; + // Prologue decrement of stack pointer. LIR* stack_decrement_; @@ -992,6 +998,12 @@ class X86Mir2Lir : public Mir2Lir { void SwapBits(RegStorage result_reg, int shift, int32_t value); void SwapBits64(RegStorage result_reg, int shift, int64_t value); + static int X86NextSDCallInsn(CompilationUnit* cu, CallInfo* info, + int state, const MethodReference& target_method, + uint32_t, + uintptr_t direct_code, uintptr_t direct_method, + InvokeType type); + static const X86EncodingMap EncodingMap[kX86Last]; friend std::ostream& operator<<(std::ostream& os, const X86OpCode& rhs); diff --git a/compiler/dex/quick/x86/int_x86.cc b/compiler/dex/quick/x86/int_x86.cc index 4eb626c14f..5def5c8bb0 100755 --- a/compiler/dex/quick/x86/int_x86.cc +++ b/compiler/dex/quick/x86/int_x86.cc @@ -1324,14 +1324,16 @@ bool X86Mir2Lir::GenInlinedReverseBits(CallInfo* info, OpSize size) { return true; } +// When we don't know the proper offset for the value, pick one that will force +// 4 byte offset. We will fix this up in the assembler or linker later to have +// the right value. +static constexpr int kDummy32BitOffset = 256; + void X86Mir2Lir::OpPcRelLoad(RegStorage reg, LIR* target) { if (cu_->target64) { // We can do this directly using RIP addressing. - // We don't know the proper offset for the value, so pick one that will force - // 4 byte offset. We will fix this up in the assembler later to have the right - // value. ScopedMemRefType mem_ref_type(this, ResourceMask::kLiteral); - LIR* res = NewLIR3(kX86Mov32RM, reg.GetReg(), kRIPReg, 256); + LIR* res = NewLIR3(kX86Mov32RM, reg.GetReg(), kRIPReg, kDummy32BitOffset); res->target = target; res->flags.fixup = kFixupLoad; return; @@ -1349,15 +1351,32 @@ void X86Mir2Lir::OpPcRelLoad(RegStorage reg, LIR* target) { store_method_addr_used_ = true; // Load the proper value from the literal area. - // We don't know the proper offset for the value, so pick one that will force - // 4 byte offset. We will fix this up in the assembler later to have the right - // value. ScopedMemRefType mem_ref_type(this, ResourceMask::kLiteral); - LIR* res = NewLIR3(kX86Mov32RM, reg.GetReg(), reg.GetReg(), 256); + LIR* res = NewLIR3(kX86Mov32RM, reg.GetReg(), reg.GetReg(), kDummy32BitOffset); res->target = target; res->flags.fixup = kFixupLoad; } +bool X86Mir2Lir::CanUseOpPcRelDexCacheArrayLoad() const { + // TODO: Implement for 32-bit. + return cu_->target64 && dex_cache_arrays_layout_.Valid(); +} + +void X86Mir2Lir::OpPcRelDexCacheArrayLoad(const DexFile* dex_file, int offset, + RegStorage r_dest) { + if (cu_->target64) { + LIR* mov = NewLIR3(kX86Mov32RM, r_dest.GetReg(), kRIPReg, kDummy32BitOffset); + mov->flags.fixup = kFixupLabel; + mov->operands[3] = WrapPointer(dex_file); + mov->operands[4] = offset; + dex_cache_access_insns_.push_back(mov); + } else { + // TODO: Implement for 32-bit. + LOG(FATAL) << "Unimplemented."; + UNREACHABLE(); + } +} + LIR* X86Mir2Lir::OpVldm(RegStorage r_base, int count) { UNUSED(r_base, count); LOG(FATAL) << "Unexpected use of OpVldm for x86"; diff --git a/compiler/dex/quick/x86/target_x86.cc b/compiler/dex/quick/x86/target_x86.cc index f128eb78a3..cad82a183e 100755 --- a/compiler/dex/quick/x86/target_x86.cc +++ b/compiler/dex/quick/x86/target_x86.cc @@ -829,6 +829,7 @@ X86Mir2Lir::X86Mir2Lir(CompilationUnit* cu, MIRGraph* mir_graph, ArenaAllocator* method_address_insns_(arena->Adapter()), class_type_address_insns_(arena->Adapter()), call_method_insns_(arena->Adapter()), + dex_cache_access_insns_(arena->Adapter()), stack_decrement_(nullptr), stack_increment_(nullptr), const_vectors_(nullptr) { method_address_insns_.reserve(100); @@ -1058,6 +1059,9 @@ void X86Mir2Lir::InstallLiteralPools() { } } + patches_.reserve(method_address_insns_.size() + class_type_address_insns_.size() + + call_method_insns_.size() + dex_cache_access_insns_.size()); + // Handle the fixups for methods. for (LIR* p : method_address_insns_) { DCHECK_EQ(p->opcode, kX86Mov32RI); @@ -1084,7 +1088,6 @@ void X86Mir2Lir::InstallLiteralPools() { } // And now the PC-relative calls to methods. - patches_.reserve(call_method_insns_.size()); for (LIR* p : call_method_insns_) { DCHECK_EQ(p->opcode, kX86CallI); uint32_t target_method_idx = p->operands[1]; @@ -1096,6 +1099,17 @@ void X86Mir2Lir::InstallLiteralPools() { target_dex_file, target_method_idx)); } + // PC-relative references to dex cache arrays. + for (LIR* p : dex_cache_access_insns_) { + DCHECK(p->opcode == kX86Mov32RM); + const DexFile* dex_file = UnwrapPointer<DexFile>(p->operands[3]); + uint32_t offset = p->operands[4]; + // The offset to patch is the last 4 bytes of the instruction. + int patch_offset = p->offset + p->flags.size - 4; + DCHECK(!p->flags.is_nop); + patches_.push_back(LinkerPatch::DexCacheArrayPatch(patch_offset, dex_file, p->offset, offset)); + } + // And do the normal processing. Mir2Lir::InstallLiteralPools(); } diff --git a/compiler/driver/compiler_options.cc b/compiler/driver/compiler_options.cc index e436f52db3..fc00c926b2 100644 --- a/compiler/driver/compiler_options.cc +++ b/compiler/driver/compiler_options.cc @@ -42,6 +42,11 @@ CompilerOptions::CompilerOptions() init_failure_output_(nullptr) { } +CompilerOptions::~CompilerOptions() { + // The destructor looks empty but it destroys a PassManagerOptions object. We keep it here + // because we don't want to include the PassManagerOptions definition from the header file. +} + CompilerOptions::CompilerOptions(CompilerFilter compiler_filter, size_t huge_method_threshold, size_t large_method_threshold, diff --git a/compiler/driver/compiler_options.h b/compiler/driver/compiler_options.h index d06ec278ab..f7ea385e19 100644 --- a/compiler/driver/compiler_options.h +++ b/compiler/driver/compiler_options.h @@ -53,6 +53,7 @@ class CompilerOptions FINAL { static const bool kDefaultIncludePatchInformation = false; CompilerOptions(); + ~CompilerOptions(); CompilerOptions(CompilerFilter compiler_filter, size_t huge_method_threshold, diff --git a/compiler/elf_writer_quick.cc b/compiler/elf_writer_quick.cc index ca5ec66381..a92ce69c6d 100644 --- a/compiler/elf_writer_quick.cc +++ b/compiler/elf_writer_quick.cc @@ -25,6 +25,8 @@ #include "driver/compiler_driver.h" #include "driver/compiler_options.h" #include "dwarf.h" +#include "dwarf/debug_frame_writer.h" +#include "dwarf/debug_line_writer.h" #include "elf_builder.h" #include "elf_file.h" #include "elf_utils.h" @@ -275,96 +277,8 @@ bool ElfWriterQuick<Elf_Word, Elf_Sword, Elf_Addr, Elf_Dyn, return builder->Write(); } -class LineTableGenerator FINAL : public Leb128Encoder { - public: - LineTableGenerator(int line_base, int line_range, int opcode_base, - std::vector<uint8_t>* data, uintptr_t current_address, - size_t current_line) - : Leb128Encoder(data), line_base_(line_base), line_range_(line_range), - opcode_base_(opcode_base), current_address_(current_address), - current_line_(current_line), current_file_index_(0) {} - - void PutDelta(unsigned delta_addr, int delta_line) { - current_line_ += delta_line; - current_address_ += delta_addr; - - if (delta_line >= line_base_ && delta_line < line_base_ + line_range_) { - unsigned special_opcode = (delta_line - line_base_) + - (line_range_ * delta_addr) + opcode_base_; - if (special_opcode <= 255) { - PushByte(data_, special_opcode); - return; - } - } - - // generate standart opcode for address advance - if (delta_addr != 0) { - PushByte(data_, DW_LNS_advance_pc); - PushBackUnsigned(delta_addr); - } - - // generate standart opcode for line delta - if (delta_line != 0) { - PushByte(data_, DW_LNS_advance_line); - PushBackSigned(delta_line); - } - - // generate standart opcode for new LTN entry - PushByte(data_, DW_LNS_copy); - } - - void SetAddr(uintptr_t addr) { - if (current_address_ == addr) { - return; - } - - current_address_ = addr; - - PushByte(data_, 0); // extended opcode: - PushByte(data_, 1 + 4); // length: opcode_size + address_size - PushByte(data_, DW_LNE_set_address); - Push32(data_, addr); - } - - void SetLine(unsigned line) { - int delta_line = line - current_line_; - if (delta_line) { - current_line_ = line; - PushByte(data_, DW_LNS_advance_line); - PushBackSigned(delta_line); - } - } - - void SetFile(unsigned file_index) { - if (current_file_index_ != file_index) { - current_file_index_ = file_index; - PushByte(data_, DW_LNS_set_file); - PushBackUnsigned(file_index); - } - } - - void EndSequence() { - // End of Line Table Program - // 0(=ext), 1(len), DW_LNE_end_sequence - PushByte(data_, 0); - PushByte(data_, 1); - PushByte(data_, DW_LNE_end_sequence); - } - - private: - const int line_base_; - const int line_range_; - const int opcode_base_; - uintptr_t current_address_; - size_t current_line_; - unsigned current_file_index_; - - DISALLOW_COPY_AND_ASSIGN(LineTableGenerator); -}; - // TODO: rewriting it using DexFile::DecodeDebugInfo needs unneeded stuff. -static void GetLineInfoForJava(const uint8_t* dbgstream, const SwapSrcMap& pc2dex, - DefaultSrcMap* result, uint32_t start_pc = 0) { +static void GetLineInfoForJava(const uint8_t* dbgstream, DefaultSrcMap* dex2line) { if (dbgstream == nullptr) { return; } @@ -419,12 +333,7 @@ static void GetLineInfoForJava(const uint8_t* dbgstream, const SwapSrcMap& pc2de adjopcode = opcode - DexFile::DBG_FIRST_SPECIAL; dex_offset += adjopcode / DexFile::DBG_LINE_RANGE; java_line += DexFile::DBG_LINE_BASE + (adjopcode % DexFile::DBG_LINE_RANGE); - - for (SwapSrcMap::const_iterator found = pc2dex.FindByTo(dex_offset); - found != pc2dex.end() && found->to_ == static_cast<int32_t>(dex_offset); - found++) { - result->push_back({found->from_ + start_pc, static_cast<int32_t>(java_line)}); - } + dex2line->push_back({dex_offset, static_cast<int32_t>(java_line)}); break; } } @@ -443,71 +352,78 @@ static void FillInCFIInformation(OatWriter* oat_writer, std::vector<uint8_t>* dbg_str, std::vector<uint8_t>* dbg_line, uint32_t text_section_offset) { - const std::vector<OatWriter::DebugInfo>& method_info = oat_writer->GetCFIMethodInfo(); + const std::vector<OatWriter::DebugInfo>& method_infos = oat_writer->GetCFIMethodInfo(); uint32_t producer_str_offset = PushStr(dbg_str, "Android dex2oat"); + constexpr bool use_64bit_addresses = false; + // Create the debug_abbrev section with boilerplate information. // We only care about low_pc and high_pc right now for the compilation // unit and methods. // Tag 1: Compilation unit: DW_TAG_compile_unit. PushByte(dbg_abbrev, 1); - PushByte(dbg_abbrev, DW_TAG_compile_unit); + PushByte(dbg_abbrev, dwarf::DW_TAG_compile_unit); // There are children (the methods). - PushByte(dbg_abbrev, DW_CHILDREN_yes); + PushByte(dbg_abbrev, dwarf::DW_CHILDREN_yes); // DW_AT_producer DW_FORM_data1. // REVIEW: we can get rid of dbg_str section if // DW_FORM_string (immediate string) was used everywhere instead of // DW_FORM_strp (ref to string from .debug_str section). // DW_FORM_strp makes sense only if we reuse the strings. - PushByte(dbg_abbrev, DW_AT_producer); - PushByte(dbg_abbrev, DW_FORM_strp); + PushByte(dbg_abbrev, dwarf::DW_AT_producer); + PushByte(dbg_abbrev, dwarf::DW_FORM_strp); // DW_LANG_Java DW_FORM_data1. - PushByte(dbg_abbrev, DW_AT_language); - PushByte(dbg_abbrev, DW_FORM_data1); + PushByte(dbg_abbrev, dwarf::DW_AT_language); + PushByte(dbg_abbrev, dwarf::DW_FORM_data1); // DW_AT_low_pc DW_FORM_addr. - PushByte(dbg_abbrev, DW_AT_low_pc); - PushByte(dbg_abbrev, DW_FORM_addr); + PushByte(dbg_abbrev, dwarf::DW_AT_low_pc); + PushByte(dbg_abbrev, dwarf::DW_FORM_addr); // DW_AT_high_pc DW_FORM_addr. - PushByte(dbg_abbrev, DW_AT_high_pc); - PushByte(dbg_abbrev, DW_FORM_addr); + PushByte(dbg_abbrev, dwarf::DW_AT_high_pc); + PushByte(dbg_abbrev, dwarf::DW_FORM_addr); if (dbg_line != nullptr) { // DW_AT_stmt_list DW_FORM_sec_offset. - PushByte(dbg_abbrev, DW_AT_stmt_list); - PushByte(dbg_abbrev, DW_FORM_sec_offset); + PushByte(dbg_abbrev, dwarf::DW_AT_stmt_list); + PushByte(dbg_abbrev, dwarf::DW_FORM_data4); } // End of DW_TAG_compile_unit. - PushHalf(dbg_abbrev, 0); + PushByte(dbg_abbrev, 0); // DW_AT. + PushByte(dbg_abbrev, 0); // DW_FORM. // Tag 2: Compilation unit: DW_TAG_subprogram. PushByte(dbg_abbrev, 2); - PushByte(dbg_abbrev, DW_TAG_subprogram); + PushByte(dbg_abbrev, dwarf::DW_TAG_subprogram); // There are no children. - PushByte(dbg_abbrev, DW_CHILDREN_no); + PushByte(dbg_abbrev, dwarf::DW_CHILDREN_no); // Name of the method. - PushByte(dbg_abbrev, DW_AT_name); - PushByte(dbg_abbrev, DW_FORM_strp); + PushByte(dbg_abbrev, dwarf::DW_AT_name); + PushByte(dbg_abbrev, dwarf::DW_FORM_strp); // DW_AT_low_pc DW_FORM_addr. - PushByte(dbg_abbrev, DW_AT_low_pc); - PushByte(dbg_abbrev, DW_FORM_addr); + PushByte(dbg_abbrev, dwarf::DW_AT_low_pc); + PushByte(dbg_abbrev, dwarf::DW_FORM_addr); // DW_AT_high_pc DW_FORM_addr. - PushByte(dbg_abbrev, DW_AT_high_pc); - PushByte(dbg_abbrev, DW_FORM_addr); + PushByte(dbg_abbrev, dwarf::DW_AT_high_pc); + PushByte(dbg_abbrev, dwarf::DW_FORM_addr); // End of DW_TAG_subprogram. - PushHalf(dbg_abbrev, 0); + PushByte(dbg_abbrev, 0); // DW_AT. + PushByte(dbg_abbrev, 0); // DW_FORM. + + // End of abbrevs for compilation unit + PushByte(dbg_abbrev, 0); // Start the debug_info section with the header information // 'unit_length' will be filled in later. @@ -520,8 +436,8 @@ static void FillInCFIInformation(OatWriter* oat_writer, // Offset into .debug_abbrev section (always 0). Push32(dbg_info, 0); - // Address size: 4. - PushByte(dbg_info, 4); + // Address size: 4 or 8. + PushByte(dbg_info, use_64bit_addresses ? 8 : 4); // Start the description for the compilation unit. // This uses tag 1. @@ -531,31 +447,34 @@ static void FillInCFIInformation(OatWriter* oat_writer, Push32(dbg_info, producer_str_offset); // The language is Java. - PushByte(dbg_info, DW_LANG_Java); + PushByte(dbg_info, dwarf::DW_LANG_Java); // low_pc and high_pc. - uint32_t cunit_low_pc = 0 - 1; + uint32_t cunit_low_pc = static_cast<uint32_t>(-1); uint32_t cunit_high_pc = 0; - int cunit_low_pc_pos = dbg_info->size(); - Push32(dbg_info, 0); - Push32(dbg_info, 0); + for (auto method_info : method_infos) { + cunit_low_pc = std::min(cunit_low_pc, method_info.low_pc_); + cunit_high_pc = std::max(cunit_high_pc, method_info.high_pc_); + } + Push32(dbg_info, cunit_low_pc + text_section_offset); + Push32(dbg_info, cunit_high_pc + text_section_offset); - if (dbg_line == nullptr) { - for (size_t i = 0; i < method_info.size(); ++i) { - const OatWriter::DebugInfo &dbg = method_info[i]; + if (dbg_line != nullptr) { + // Line number table offset. + Push32(dbg_info, dbg_line->size()); + } - cunit_low_pc = std::min(cunit_low_pc, dbg.low_pc_); - cunit_high_pc = std::max(cunit_high_pc, dbg.high_pc_); + for (auto method_info : method_infos) { + // Start a new TAG: subroutine (2). + PushByte(dbg_info, 2); - // Start a new TAG: subroutine (2). - PushByte(dbg_info, 2); + // Enter name, low_pc, high_pc. + Push32(dbg_info, PushStr(dbg_str, method_info.method_name_)); + Push32(dbg_info, method_info.low_pc_ + text_section_offset); + Push32(dbg_info, method_info.high_pc_ + text_section_offset); + } - // Enter name, low_pc, high_pc. - Push32(dbg_info, PushStr(dbg_str, dbg.method_name_)); - Push32(dbg_info, dbg.low_pc_ + text_section_offset); - Push32(dbg_info, dbg.high_pc_ + text_section_offset); - } - } else { + if (dbg_line != nullptr) { // TODO: in gdb info functions <regexp> - reports Java functions, but // source file is <unknown> because .debug_line is formed as one // compilation unit. To fix this it is possible to generate @@ -563,110 +482,135 @@ static void FillInCFIInformation(OatWriter* oat_writer, // Each of the these compilation units can have several non-adjacent // method ranges. - // Line number table offset - Push32(dbg_info, dbg_line->size()); + std::vector<dwarf::DebugLineWriter<>::FileEntry> files; + std::unordered_map<std::string, size_t> files_map; + std::vector<std::string> directories; + std::unordered_map<std::string, size_t> directories_map; + + int code_factor_bits_ = 0; + int isa = -1; + switch (oat_writer->GetOatHeader().GetInstructionSet()) { + case kThumb2: + code_factor_bits_ = 1; // 16-bit instuctions + isa = 1; // DW_ISA_ARM_thumb. + break; + case kArm: + code_factor_bits_ = 2; // 32-bit instructions + isa = 2; // DW_ISA_ARM_arm. + break; + case kArm64: + case kMips: + case kMips64: + code_factor_bits_ = 2; // 32-bit instructions + break; + case kNone: + case kX86: + case kX86_64: + break; + } - size_t lnt_length = dbg_line->size(); - Push32(dbg_line, 0); - - PushHalf(dbg_line, 4); // LNT Version DWARF v4 => 4 - - size_t lnt_hdr_length = dbg_line->size(); - Push32(dbg_line, 0); // TODO: 64-bit uses 8-byte here - - PushByte(dbg_line, 1); // minimum_instruction_length (ubyte) - PushByte(dbg_line, 1); // maximum_operations_per_instruction (ubyte) = always 1 - PushByte(dbg_line, 1); // default_is_stmt (ubyte) - - const int8_t LINE_BASE = -5; - PushByte(dbg_line, LINE_BASE); // line_base (sbyte) - - const uint8_t LINE_RANGE = 14; - PushByte(dbg_line, LINE_RANGE); // line_range (ubyte) - - const uint8_t OPCODE_BASE = 13; - PushByte(dbg_line, OPCODE_BASE); // opcode_base (ubyte) - - // Standard_opcode_lengths (array of ubyte). - PushByte(dbg_line, 0); PushByte(dbg_line, 1); PushByte(dbg_line, 1); - PushByte(dbg_line, 1); PushByte(dbg_line, 1); PushByte(dbg_line, 0); - PushByte(dbg_line, 0); PushByte(dbg_line, 0); PushByte(dbg_line, 1); - PushByte(dbg_line, 0); PushByte(dbg_line, 0); PushByte(dbg_line, 1); - - PushByte(dbg_line, 0); // include_directories (sequence of path names) = EMPTY - - // File_names (sequence of file entries). - std::unordered_map<const char*, size_t> files; - for (size_t i = 0; i < method_info.size(); ++i) { - const OatWriter::DebugInfo &dbg = method_info[i]; - // TODO: add package directory to the file name - const char* file_name = dbg.src_file_name_ == nullptr ? "null" : dbg.src_file_name_; - auto found = files.find(file_name); - if (found == files.end()) { - size_t file_index = 1 + files.size(); - files[file_name] = file_index; - PushStr(dbg_line, file_name); - PushByte(dbg_line, 0); // include directory index = LEB128(0) - no directory - PushByte(dbg_line, 0); // modification time = LEB128(0) - NA - PushByte(dbg_line, 0); // file length = LEB128(0) - NA - } + dwarf::DebugLineOpCodeWriter<> opcodes(use_64bit_addresses, code_factor_bits_); + opcodes.SetAddress(text_section_offset + cunit_low_pc); + if (isa != -1) { + opcodes.SetISA(isa); } - PushByte(dbg_line, 0); // End of file_names. - - // Set lnt header length. - UpdateWord(dbg_line, lnt_hdr_length, dbg_line->size() - lnt_hdr_length - 4); - - // Generate Line Number Program code, one long program for all methods. - LineTableGenerator line_table_generator(LINE_BASE, LINE_RANGE, OPCODE_BASE, - dbg_line, 0, 1); - - DefaultSrcMap pc2java_map; - for (size_t i = 0; i < method_info.size(); ++i) { - const OatWriter::DebugInfo &dbg = method_info[i]; - const char* file_name = (dbg.src_file_name_ == nullptr) ? "null" : dbg.src_file_name_; - size_t file_index = files[file_name]; - DCHECK_NE(file_index, 0U) << file_name; - - cunit_low_pc = std::min(cunit_low_pc, dbg.low_pc_); - cunit_high_pc = std::max(cunit_high_pc, dbg.high_pc_); - - // Start a new TAG: subroutine (2). - PushByte(dbg_info, 2); - - // Enter name, low_pc, high_pc. - Push32(dbg_info, PushStr(dbg_str, dbg.method_name_)); - Push32(dbg_info, dbg.low_pc_ + text_section_offset); - Push32(dbg_info, dbg.high_pc_ + text_section_offset); - - GetLineInfoForJava(dbg.dbgstream_, dbg.compiled_method_->GetSrcMappingTable(), - &pc2java_map, dbg.low_pc_); - pc2java_map.DeltaFormat({dbg.low_pc_, 1}, dbg.high_pc_); - if (!pc2java_map.empty()) { - line_table_generator.SetFile(file_index); - line_table_generator.SetAddr(dbg.low_pc_ + text_section_offset); - line_table_generator.SetLine(1); - for (auto& src_map_elem : pc2java_map) { - line_table_generator.PutDelta(src_map_elem.from_, src_map_elem.to_); + DefaultSrcMap dex2line_map; + for (size_t i = 0; i < method_infos.size(); i++) { + const OatWriter::DebugInfo& method_info = method_infos[i]; + + // Addresses in the line table should be unique and increasing. + if (method_info.deduped_) { + continue; + } + + // Get and deduplicate directory and filename. + int file_index = 0; // 0 - primary source file of the compilation. + if (method_info.src_file_name_ != nullptr) { + std::string file_name(method_info.src_file_name_); + size_t file_name_slash = file_name.find_last_of('/'); + std::string class_name(method_info.class_descriptor_); + size_t class_name_slash = class_name.find_last_of('/'); + std::string full_path(file_name); + + // Guess directory from package name. + int directory_index = 0; // 0 - current directory of the compilation. + if (file_name_slash == std::string::npos && // Just filename. + class_name.front() == 'L' && // Type descriptor for a class. + class_name_slash != std::string::npos) { // Has package name. + std::string package_name = class_name.substr(1, class_name_slash - 1); + auto it = directories_map.find(package_name); + if (it == directories_map.end()) { + directory_index = 1 + directories.size(); + directories_map.emplace(package_name, directory_index); + directories.push_back(package_name); + } else { + directory_index = it->second; + } + full_path = package_name + "/" + file_name; + } + + // Add file entry. + auto it2 = files_map.find(full_path); + if (it2 == files_map.end()) { + file_index = 1 + files.size(); + files_map.emplace(full_path, file_index); + files.push_back(dwarf::DebugLineWriter<>::FileEntry { + file_name, + directory_index, + 0, // Modification time - NA. + 0, // File size - NA. + }); + } else { + file_index = it2->second; } - pc2java_map.clear(); + } + opcodes.SetFile(file_index); + + // Generate mapping opcodes from PC to Java lines. + dex2line_map.clear(); + GetLineInfoForJava(method_info.dbgstream_, &dex2line_map); + uint32_t low_pc = text_section_offset + method_info.low_pc_; + if (file_index != 0 && !dex2line_map.empty()) { + bool first = true; + for (SrcMapElem pc2dex : method_info.compiled_method_->GetSrcMappingTable()) { + uint32_t pc = pc2dex.from_; + int dex = pc2dex.to_; + auto dex2line = dex2line_map.Find(static_cast<uint32_t>(dex)); + if (dex2line.first) { + int line = dex2line.second; + if (first) { + first = false; + if (pc > 0) { + // Assume that any preceding code is prologue. + int first_line = dex2line_map.front().to_; + // Prologue is not a sensible place for a breakpoint. + opcodes.NegateStmt(); + opcodes.AddRow(low_pc, first_line); + opcodes.NegateStmt(); + opcodes.SetPrologueEnd(); + } + opcodes.AddRow(low_pc + pc, line); + } else if (line != opcodes.CurrentLine()) { + opcodes.AddRow(low_pc + pc, line); + } + } + } + } else { + // line 0 - instruction cannot be attributed to any source line. + opcodes.AddRow(low_pc, 0); } } - // End Sequence should have the highest address set. - line_table_generator.SetAddr(cunit_high_pc + text_section_offset); - line_table_generator.EndSequence(); + opcodes.AdvancePC(text_section_offset + cunit_high_pc); + opcodes.EndSequence(); - // set lnt length - UpdateWord(dbg_line, lnt_length, dbg_line->size() - lnt_length - 4); + dwarf::DebugLineWriter<> dbg_line_writer(dbg_line); + dbg_line_writer.WriteTable(directories, files, opcodes); } - // One byte terminator + // One byte terminator. PushByte(dbg_info, 0); - // Fill in cunit's low_pc and high_pc. - UpdateWord(dbg_info, cunit_low_pc_pos, cunit_low_pc + text_section_offset); - UpdateWord(dbg_info, cunit_low_pc_pos + 4, cunit_high_pc + text_section_offset); - // We have now walked all the methods. Fill in lengths. UpdateWord(dbg_info, cunit_length, dbg_info->size() - cunit_length - 4); } @@ -690,8 +634,11 @@ static void WriteDebugSymbols(const CompilerDriver* compiler_driver, ElfSymtabBuilder<Elf_Word, Elf_Sword, Elf_Addr, Elf_Sym, Elf_Shdr>* symtab = builder->GetSymtabBuilder(); for (auto it = method_info.begin(); it != method_info.end(); ++it) { - symtab->AddSymbol(it->method_name_, &builder->GetTextBuilder(), it->low_pc_, true, - it->high_pc_ - it->low_pc_, STB_GLOBAL, STT_FUNC); + uint32_t low_pc = it->low_pc_; + // Add in code delta, e.g., thumb bit 0 for Thumb2 code. + low_pc += it->compiled_method_->CodeDelta(); + symtab->AddSymbol(it->method_name_, &builder->GetTextBuilder(), low_pc, + true, it->high_pc_ - it->low_pc_, STB_GLOBAL, STT_FUNC); // Conforming to aaelf, add $t mapping symbol to indicate start of a sequence of thumb2 // instructions, so that disassembler tools can correctly disassemble. diff --git a/compiler/linker/arm/relative_patcher_arm_base.cc b/compiler/linker/arm/relative_patcher_arm_base.cc new file mode 100644 index 0000000000..2eae2a8001 --- /dev/null +++ b/compiler/linker/arm/relative_patcher_arm_base.cc @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "linker/arm/relative_patcher_arm_base.h" + +#include "compiled_method.h" +#include "oat.h" +#include "output_stream.h" + +namespace art { +namespace linker { + +uint32_t ArmBaseRelativePatcher::ReserveSpace(uint32_t offset, + const CompiledMethod* compiled_method, + MethodReference method_ref) { + return ReserveSpaceInternal(offset, compiled_method, method_ref, 0u); +} + +uint32_t ArmBaseRelativePatcher::WriteThunks(OutputStream* out, uint32_t offset) { + if (current_thunk_to_write_ == thunk_locations_.size()) { + return offset; + } + uint32_t aligned_offset = CompiledMethod::AlignCode(offset, instruction_set_); + if (UNLIKELY(aligned_offset == thunk_locations_[current_thunk_to_write_])) { + ++current_thunk_to_write_; + uint32_t aligned_code_delta = aligned_offset - offset; + if (aligned_code_delta != 0u && !WriteCodeAlignment(out, aligned_code_delta)) { + return 0u; + } + if (UNLIKELY(!WriteRelCallThunk(out, ArrayRef<const uint8_t>(thunk_code_)))) { + return 0u; + } + uint32_t thunk_end_offset = aligned_offset + thunk_code_.size(); + // Align after writing chunk, see the ReserveSpace() above. + offset = CompiledMethod::AlignCode(thunk_end_offset, instruction_set_); + aligned_code_delta = offset - thunk_end_offset; + if (aligned_code_delta != 0u && !WriteCodeAlignment(out, aligned_code_delta)) { + return 0u; + } + } + return offset; +} + +ArmBaseRelativePatcher::ArmBaseRelativePatcher(RelativePatcherTargetProvider* provider, + InstructionSet instruction_set, + std::vector<uint8_t> thunk_code, + uint32_t max_positive_displacement, + uint32_t max_negative_displacement) + : provider_(provider), instruction_set_(instruction_set), thunk_code_(thunk_code), + max_positive_displacement_(max_positive_displacement), + max_negative_displacement_(max_negative_displacement), + thunk_locations_(), current_thunk_to_write_(0u), unprocessed_patches_() { +} + +uint32_t ArmBaseRelativePatcher::ReserveSpaceInternal(uint32_t offset, + const CompiledMethod* compiled_method, + MethodReference method_ref, + uint32_t max_extra_space) { + // NOTE: The final thunk can be reserved from InitCodeMethodVisitor::EndClass() while it + // may be written early by WriteCodeMethodVisitor::VisitMethod() for a deduplicated chunk + // of code. To avoid any alignment discrepancies for the final chunk, we always align the + // offset after reserving of writing any chunk. + if (UNLIKELY(compiled_method == nullptr)) { + uint32_t aligned_offset = CompiledMethod::AlignCode(offset, instruction_set_); + DCHECK(method_ref.dex_file == nullptr && method_ref.dex_method_index == 0u); + bool needs_thunk = ReserveSpaceProcessPatches(aligned_offset, method_ref, aligned_offset); + if (needs_thunk) { + thunk_locations_.push_back(aligned_offset); + offset = CompiledMethod::AlignCode(aligned_offset + thunk_code_.size(), instruction_set_); + } + return offset; + } + DCHECK(compiled_method->GetQuickCode() != nullptr); + uint32_t quick_code_size = compiled_method->GetQuickCode()->size(); + uint32_t quick_code_offset = compiled_method->AlignCode(offset) + sizeof(OatQuickMethodHeader); + uint32_t next_aligned_offset = compiled_method->AlignCode(quick_code_offset + quick_code_size); + // Adjust for extra space required by the subclass. + next_aligned_offset = compiled_method->AlignCode(next_aligned_offset + max_extra_space); + // TODO: ignore unprocessed patches targeting this method if they can reach quick_code_offset. + // We need the MethodReference for that. + if (!unprocessed_patches_.empty() && + next_aligned_offset - unprocessed_patches_.front().second > max_positive_displacement_) { + bool needs_thunk = ReserveSpaceProcessPatches(quick_code_offset, method_ref, + next_aligned_offset); + if (needs_thunk) { + // A single thunk will cover all pending patches. + unprocessed_patches_.clear(); + uint32_t thunk_location = compiled_method->AlignCode(offset); + thunk_locations_.push_back(thunk_location); + offset = CompiledMethod::AlignCode(thunk_location + thunk_code_.size(), instruction_set_); + } + } + for (const LinkerPatch& patch : compiled_method->GetPatches()) { + if (patch.Type() == kLinkerPatchCallRelative) { + unprocessed_patches_.emplace_back(patch.TargetMethod(), + quick_code_offset + patch.LiteralOffset()); + } + } + return offset; +} + +uint32_t ArmBaseRelativePatcher::CalculateDisplacement(uint32_t patch_offset, + uint32_t target_offset) { + // Unsigned arithmetic with its well-defined overflow behavior is just fine here. + uint32_t displacement = target_offset - patch_offset; + // NOTE: With unsigned arithmetic we do mean to use && rather than || below. + if (displacement > max_positive_displacement_ && displacement < -max_negative_displacement_) { + // Unwritten thunks have higher offsets, check if it's within range. + DCHECK(current_thunk_to_write_ == thunk_locations_.size() || + thunk_locations_[current_thunk_to_write_] > patch_offset); + if (current_thunk_to_write_ != thunk_locations_.size() && + thunk_locations_[current_thunk_to_write_] - patch_offset < max_positive_displacement_) { + displacement = thunk_locations_[current_thunk_to_write_] - patch_offset; + } else { + // We must have a previous thunk then. + DCHECK_NE(current_thunk_to_write_, 0u); + DCHECK_LT(thunk_locations_[current_thunk_to_write_ - 1], patch_offset); + displacement = thunk_locations_[current_thunk_to_write_ - 1] - patch_offset; + DCHECK(displacement >= -max_negative_displacement_); + } + } + return displacement; +} + +bool ArmBaseRelativePatcher::ReserveSpaceProcessPatches(uint32_t quick_code_offset, + MethodReference method_ref, + uint32_t next_aligned_offset) { + // Process as many patches as possible, stop only on unresolved targets or calls too far back. + while (!unprocessed_patches_.empty()) { + MethodReference patch_ref = unprocessed_patches_.front().first; + uint32_t patch_offset = unprocessed_patches_.front().second; + DCHECK(thunk_locations_.empty() || thunk_locations_.back() <= patch_offset); + if (patch_ref.dex_file == method_ref.dex_file && + patch_ref.dex_method_index == method_ref.dex_method_index) { + DCHECK_GT(quick_code_offset, patch_offset); + if (quick_code_offset - patch_offset > max_positive_displacement_) { + return true; + } + } else { + auto result = provider_->FindMethodOffset(patch_ref); + if (!result.first) { + // If still unresolved, check if we have a thunk within range. + if (thunk_locations_.empty() || + patch_offset - thunk_locations_.back() > max_negative_displacement_) { + return next_aligned_offset - patch_offset > max_positive_displacement_; + } + } else { + uint32_t target_offset = result.second - CompiledCode::CodeDelta(instruction_set_); + if (target_offset >= patch_offset) { + DCHECK_LE(target_offset - patch_offset, max_positive_displacement_); + } else { + // When calling back, check if we have a thunk that's closer than the actual target. + if (!thunk_locations_.empty()) { + target_offset = std::max(target_offset, thunk_locations_.back()); + } + if (patch_offset - target_offset > max_negative_displacement_) { + return true; + } + } + } + } + unprocessed_patches_.pop_front(); + } + return false; +} + +} // namespace linker +} // namespace art diff --git a/compiler/linker/arm/relative_patcher_arm_base.h b/compiler/linker/arm/relative_patcher_arm_base.h new file mode 100644 index 0000000000..35a8b8e5f0 --- /dev/null +++ b/compiler/linker/arm/relative_patcher_arm_base.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_COMPILER_LINKER_ARM_RELATIVE_PATCHER_ARM_BASE_H_ +#define ART_COMPILER_LINKER_ARM_RELATIVE_PATCHER_ARM_BASE_H_ + +#include <deque> + +#include "linker/relative_patcher.h" +#include "method_reference.h" + +namespace art { +namespace linker { + +class ArmBaseRelativePatcher : public RelativePatcher { + public: + uint32_t ReserveSpace(uint32_t offset, const CompiledMethod* compiled_method, + MethodReference method_ref) OVERRIDE; + uint32_t WriteThunks(OutputStream* out, uint32_t offset) OVERRIDE; + + protected: + ArmBaseRelativePatcher(RelativePatcherTargetProvider* provider, + InstructionSet instruction_set, std::vector<uint8_t> thunk_code, + uint32_t max_positive_displacement, uint32_t max_negative_displacement); + + uint32_t ReserveSpaceInternal(uint32_t offset, const CompiledMethod* compiled_method, + MethodReference method_ref, uint32_t max_extra_space); + uint32_t CalculateDisplacement(uint32_t patch_offset, uint32_t target_offset); + + private: + bool ReserveSpaceProcessPatches(uint32_t quick_code_offset, MethodReference method_ref, + uint32_t next_aligned_offset); + + RelativePatcherTargetProvider* const provider_; + const InstructionSet instruction_set_; + const std::vector<uint8_t> thunk_code_; + const uint32_t max_positive_displacement_; + const uint32_t max_negative_displacement_; + std::vector<uint32_t> thunk_locations_; + size_t current_thunk_to_write_; + + // ReserveSpace() tracks unprocessed patches. + typedef std::pair<MethodReference, uint32_t> UnprocessedPatch; + std::deque<UnprocessedPatch> unprocessed_patches_; + + friend class Thumb2RelativePatcherTest; + + DISALLOW_COPY_AND_ASSIGN(ArmBaseRelativePatcher); +}; + +} // namespace linker +} // namespace art + +#endif // ART_COMPILER_LINKER_ARM_RELATIVE_PATCHER_ARM_BASE_H_ diff --git a/compiler/linker/arm/relative_patcher_thumb2.cc b/compiler/linker/arm/relative_patcher_thumb2.cc new file mode 100644 index 0000000000..4267743097 --- /dev/null +++ b/compiler/linker/arm/relative_patcher_thumb2.cc @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "linker/arm/relative_patcher_thumb2.h" + +#include "compiled_method.h" +#include "mirror/art_method.h" +#include "utils/arm/assembler_thumb2.h" + +namespace art { +namespace linker { + +Thumb2RelativePatcher::Thumb2RelativePatcher(RelativePatcherTargetProvider* provider) + : ArmBaseRelativePatcher(provider, kThumb2, CompileThunkCode(), + kMaxPositiveDisplacement, kMaxNegativeDisplacement) { +} + +void Thumb2RelativePatcher::PatchCall(std::vector<uint8_t>* code, uint32_t literal_offset, + uint32_t patch_offset, uint32_t target_offset) { + DCHECK_LE(literal_offset + 4u, code->size()); + DCHECK_EQ(literal_offset & 1u, 0u); + DCHECK_EQ(patch_offset & 1u, 0u); + DCHECK_EQ(target_offset & 1u, 1u); // Thumb2 mode bit. + uint32_t displacement = CalculateDisplacement(patch_offset, target_offset & ~1u); + displacement -= kPcDisplacement; // The base PC is at the end of the 4-byte patch. + DCHECK_EQ(displacement & 1u, 0u); + DCHECK((displacement >> 24) == 0u || (displacement >> 24) == 255u); // 25-bit signed. + uint32_t signbit = (displacement >> 31) & 0x1; + uint32_t i1 = (displacement >> 23) & 0x1; + uint32_t i2 = (displacement >> 22) & 0x1; + uint32_t imm10 = (displacement >> 12) & 0x03ff; + uint32_t imm11 = (displacement >> 1) & 0x07ff; + uint32_t j1 = i1 ^ (signbit ^ 1); + uint32_t j2 = i2 ^ (signbit ^ 1); + uint32_t value = (signbit << 26) | (j1 << 13) | (j2 << 11) | (imm10 << 16) | imm11; + value |= 0xf000d000; // BL + + uint8_t* addr = &(*code)[literal_offset]; + // Check that we're just overwriting an existing BL. + DCHECK_EQ(addr[1] & 0xf8, 0xf0); + DCHECK_EQ(addr[3] & 0xd0, 0xd0); + // Write the new BL. + addr[0] = (value >> 16) & 0xff; + addr[1] = (value >> 24) & 0xff; + addr[2] = (value >> 0) & 0xff; + addr[3] = (value >> 8) & 0xff; +} + +void Thumb2RelativePatcher::PatchDexCacheReference(std::vector<uint8_t>* code ATTRIBUTE_UNUSED, + const LinkerPatch& patch ATTRIBUTE_UNUSED, + uint32_t patch_offset ATTRIBUTE_UNUSED, + uint32_t target_offset ATTRIBUTE_UNUSED) { + LOG(FATAL) << "Unexpected relative dex cache array patch."; +} + +std::vector<uint8_t> Thumb2RelativePatcher::CompileThunkCode() { + // The thunk just uses the entry point in the ArtMethod. This works even for calls + // to the generic JNI and interpreter trampolines. + arm::Thumb2Assembler assembler; + assembler.LoadFromOffset( + arm::kLoadWord, arm::PC, arm::R0, + mirror::ArtMethod::EntryPointFromQuickCompiledCodeOffset(kArmPointerSize).Int32Value()); + assembler.bkpt(0); + std::vector<uint8_t> thunk_code(assembler.CodeSize()); + MemoryRegion code(thunk_code.data(), thunk_code.size()); + assembler.FinalizeInstructions(code); + return thunk_code; +} + +} // namespace linker +} // namespace art diff --git a/compiler/linker/arm/relative_patcher_thumb2.h b/compiler/linker/arm/relative_patcher_thumb2.h new file mode 100644 index 0000000000..561130305e --- /dev/null +++ b/compiler/linker/arm/relative_patcher_thumb2.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_COMPILER_LINKER_ARM_RELATIVE_PATCHER_THUMB2_H_ +#define ART_COMPILER_LINKER_ARM_RELATIVE_PATCHER_THUMB2_H_ + +#include "linker/arm/relative_patcher_arm_base.h" + +namespace art { +namespace linker { + +class Thumb2RelativePatcher FINAL : public ArmBaseRelativePatcher { + public: + explicit Thumb2RelativePatcher(RelativePatcherTargetProvider* provider); + + void PatchCall(std::vector<uint8_t>* code, uint32_t literal_offset, + uint32_t patch_offset, uint32_t target_offset) OVERRIDE; + void PatchDexCacheReference(std::vector<uint8_t>* code, const LinkerPatch& patch, + uint32_t patch_offset, uint32_t target_offset) OVERRIDE; + + private: + static std::vector<uint8_t> CompileThunkCode(); + + // PC displacement from patch location; Thumb2 PC is always at instruction address + 4. + static constexpr int32_t kPcDisplacement = 4; + + // Maximum positive and negative displacement measured from the patch location. + // (Signed 25 bit displacement with the last bit 0 has range [-2^24, 2^24-2] measured from + // the Thumb2 PC pointing right after the BL, i.e. 4 bytes later than the patch location.) + static constexpr uint32_t kMaxPositiveDisplacement = (1u << 24) - 2 + kPcDisplacement; + static constexpr uint32_t kMaxNegativeDisplacement = (1u << 24) - kPcDisplacement; + + DISALLOW_COPY_AND_ASSIGN(Thumb2RelativePatcher); +}; + +} // namespace linker +} // namespace art + +#endif // ART_COMPILER_LINKER_ARM_RELATIVE_PATCHER_THUMB2_H_ diff --git a/compiler/linker/arm/relative_patcher_thumb2_test.cc b/compiler/linker/arm/relative_patcher_thumb2_test.cc new file mode 100644 index 0000000000..abdfd6d64b --- /dev/null +++ b/compiler/linker/arm/relative_patcher_thumb2_test.cc @@ -0,0 +1,289 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "linker/relative_patcher_test.h" +#include "linker/arm/relative_patcher_thumb2.h" + +namespace art { +namespace linker { + +class Thumb2RelativePatcherTest : public RelativePatcherTest { + public: + Thumb2RelativePatcherTest() : RelativePatcherTest(kThumb2, "default") { } + + protected: + static const uint8_t kCallRawCode[]; + static const ArrayRef<const uint8_t> kCallCode; + static const uint8_t kNopRawCode[]; + static const ArrayRef<const uint8_t> kNopCode; + + // Branches within range [-256, 256) can be created from these by adding the low 8 bits. + static constexpr uint32_t kBlPlus0 = 0xf000f800; + static constexpr uint32_t kBlMinus256 = 0xf7ffff00; + + // Special BL values. + static constexpr uint32_t kBlPlusMax = 0xf3ffd7ff; + static constexpr uint32_t kBlMinusMax = 0xf400d000; + + bool Create2MethodsWithGap(const ArrayRef<const uint8_t>& method1_code, + const ArrayRef<LinkerPatch>& method1_patches, + const ArrayRef<const uint8_t>& method3_code, + const ArrayRef<LinkerPatch>& method3_patches, + uint32_t distance_without_thunks) { + CHECK_EQ(distance_without_thunks % kArmAlignment, 0u); + const uint32_t method1_offset = + CompiledCode::AlignCode(kTrampolineSize, kThumb2) + sizeof(OatQuickMethodHeader); + AddCompiledMethod(MethodRef(1u), method1_code, ArrayRef<LinkerPatch>(method1_patches)); + + // We want to put the method3 at a very precise offset. + const uint32_t method3_offset = method1_offset + distance_without_thunks; + CHECK(IsAligned<kArmAlignment>(method3_offset - sizeof(OatQuickMethodHeader))); + + // Calculate size of method2 so that we put method3 at the correct place. + const uint32_t method2_offset = + CompiledCode::AlignCode(method1_offset + method1_code.size(), kThumb2) + + sizeof(OatQuickMethodHeader); + const uint32_t method2_size = (method3_offset - sizeof(OatQuickMethodHeader) - method2_offset); + std::vector<uint8_t> method2_raw_code(method2_size); + ArrayRef<const uint8_t> method2_code(method2_raw_code); + AddCompiledMethod(MethodRef(2u), method2_code, ArrayRef<LinkerPatch>()); + + AddCompiledMethod(MethodRef(3u), method3_code, method3_patches); + + Link(); + + // Check assumptions. + CHECK_EQ(GetMethodOffset(1), method1_offset); + CHECK_EQ(GetMethodOffset(2), method2_offset); + auto result3 = method_offset_map_.FindMethodOffset(MethodRef(3)); + CHECK(result3.first); + // There may be a thunk before method2. + if (result3.second == method3_offset + 1 /* thumb mode */) { + return false; // No thunk. + } else { + uint32_t aligned_thunk_size = CompiledCode::AlignCode(ThunkSize(), kThumb2); + CHECK_EQ(result3.second, method3_offset + aligned_thunk_size + 1 /* thumb mode */); + return true; // Thunk present. + } + } + + uint32_t GetMethodOffset(uint32_t method_idx) { + auto result = method_offset_map_.FindMethodOffset(MethodRef(method_idx)); + CHECK(result.first); + CHECK_NE(result.second & 1u, 0u); + return result.second - 1 /* thumb mode */; + } + + uint32_t ThunkSize() { + return static_cast<Thumb2RelativePatcher*>(patcher_.get())->thunk_code_.size(); + } + + bool CheckThunk(uint32_t thunk_offset) { + Thumb2RelativePatcher* patcher = static_cast<Thumb2RelativePatcher*>(patcher_.get()); + ArrayRef<const uint8_t> expected_code(patcher->thunk_code_); + if (output_.size() < thunk_offset + expected_code.size()) { + LOG(ERROR) << "output_.size() == " << output_.size() << " < " + << "thunk_offset + expected_code.size() == " << (thunk_offset + expected_code.size()); + return false; + } + ArrayRef<const uint8_t> linked_code(&output_[thunk_offset], expected_code.size()); + if (linked_code == expected_code) { + return true; + } + // Log failure info. + DumpDiff(expected_code, linked_code); + return false; + } + + std::vector<uint8_t> GenNopsAndBl(size_t num_nops, uint32_t bl) { + std::vector<uint8_t> result; + result.reserve(num_nops * 2u + 4u); + for (size_t i = 0; i != num_nops; ++i) { + result.push_back(0x00); + result.push_back(0xbf); + } + result.push_back(static_cast<uint8_t>(bl >> 16)); + result.push_back(static_cast<uint8_t>(bl >> 24)); + result.push_back(static_cast<uint8_t>(bl)); + result.push_back(static_cast<uint8_t>(bl >> 8)); + return result; + } +}; + +const uint8_t Thumb2RelativePatcherTest::kCallRawCode[] = { + 0x00, 0xf0, 0x00, 0xf8 +}; + +const ArrayRef<const uint8_t> Thumb2RelativePatcherTest::kCallCode(kCallRawCode); + +const uint8_t Thumb2RelativePatcherTest::kNopRawCode[] = { + 0x00, 0xbf +}; + +const ArrayRef<const uint8_t> Thumb2RelativePatcherTest::kNopCode(kNopRawCode); + +TEST_F(Thumb2RelativePatcherTest, CallSelf) { + LinkerPatch patches[] = { + LinkerPatch::RelativeCodePatch(0u, nullptr, 1u), + }; + AddCompiledMethod(MethodRef(1u), kCallCode, ArrayRef<LinkerPatch>(patches)); + Link(); + + static const uint8_t expected_code[] = { + 0xff, 0xf7, 0xfe, 0xff + }; + EXPECT_TRUE(CheckLinkedMethod(MethodRef(1u), ArrayRef<const uint8_t>(expected_code))); +} + +TEST_F(Thumb2RelativePatcherTest, CallOther) { + LinkerPatch method1_patches[] = { + LinkerPatch::RelativeCodePatch(0u, nullptr, 2u), + }; + AddCompiledMethod(MethodRef(1u), kCallCode, ArrayRef<LinkerPatch>(method1_patches)); + LinkerPatch method2_patches[] = { + LinkerPatch::RelativeCodePatch(0u, nullptr, 1u), + }; + AddCompiledMethod(MethodRef(2u), kCallCode, ArrayRef<LinkerPatch>(method2_patches)); + Link(); + + uint32_t method1_offset = GetMethodOffset(1u); + uint32_t method2_offset = GetMethodOffset(2u); + uint32_t diff_after = method2_offset - (method1_offset + 4u /* PC adjustment */); + ASSERT_EQ(diff_after & 1u, 0u); + ASSERT_LT(diff_after >> 1, 1u << 8); // Simple encoding, (diff_after >> 1) fits into 8 bits. + static const uint8_t method1_expected_code[] = { + 0x00, 0xf0, static_cast<uint8_t>(diff_after >> 1), 0xf8 + }; + EXPECT_TRUE(CheckLinkedMethod(MethodRef(1u), ArrayRef<const uint8_t>(method1_expected_code))); + uint32_t diff_before = method1_offset - (method2_offset + 4u /* PC adjustment */); + ASSERT_EQ(diff_before & 1u, 0u); + ASSERT_GE(diff_before, -1u << 9); // Simple encoding, -256 <= (diff >> 1) < 0. + auto method2_expected_code = GenNopsAndBl(0u, kBlMinus256 | ((diff_before >> 1) & 0xffu)); + EXPECT_TRUE(CheckLinkedMethod(MethodRef(2u), ArrayRef<const uint8_t>(method2_expected_code))); +} + +TEST_F(Thumb2RelativePatcherTest, CallTrampoline) { + LinkerPatch patches[] = { + LinkerPatch::RelativeCodePatch(0u, nullptr, 2u), + }; + AddCompiledMethod(MethodRef(1u), kCallCode, ArrayRef<LinkerPatch>(patches)); + Link(); + + uint32_t method1_offset = GetMethodOffset(1u); + uint32_t diff = kTrampolineOffset - (method1_offset + 4u); + ASSERT_EQ(diff & 1u, 0u); + ASSERT_GE(diff, -1u << 9); // Simple encoding, -256 <= (diff >> 1) < 0 (checked as unsigned). + auto expected_code = GenNopsAndBl(0u, kBlMinus256 | ((diff >> 1) & 0xffu)); + EXPECT_TRUE(CheckLinkedMethod(MethodRef(1u), ArrayRef<const uint8_t>(expected_code))); +} + +TEST_F(Thumb2RelativePatcherTest, CallOtherAlmostTooFarAfter) { + auto method1_raw_code = GenNopsAndBl(3u, kBlPlus0); + constexpr uint32_t bl_offset_in_method1 = 3u * 2u; // After NOPs. + ArrayRef<const uint8_t> method1_code(method1_raw_code); + ASSERT_EQ(bl_offset_in_method1 + 4u, method1_code.size()); + LinkerPatch method1_patches[] = { + LinkerPatch::RelativeCodePatch(bl_offset_in_method1, nullptr, 3u), + }; + + constexpr uint32_t max_positive_disp = 16 * MB - 2u + 4u /* PC adjustment */; + bool thunk_in_gap = Create2MethodsWithGap(method1_code, method1_patches, + kNopCode, ArrayRef<LinkerPatch>(), + bl_offset_in_method1 + max_positive_disp); + ASSERT_FALSE(thunk_in_gap); // There should be no thunk. + + // Check linked code. + auto expected_code = GenNopsAndBl(3u, kBlPlusMax); + EXPECT_TRUE(CheckLinkedMethod(MethodRef(1u), ArrayRef<const uint8_t>(expected_code))); +} + +TEST_F(Thumb2RelativePatcherTest, CallOtherAlmostTooFarBefore) { + auto method3_raw_code = GenNopsAndBl(2u, kBlPlus0); + constexpr uint32_t bl_offset_in_method3 = 2u * 2u; // After NOPs. + ArrayRef<const uint8_t> method3_code(method3_raw_code); + ASSERT_EQ(bl_offset_in_method3 + 4u, method3_code.size()); + LinkerPatch method3_patches[] = { + LinkerPatch::RelativeCodePatch(bl_offset_in_method3, nullptr, 1u), + }; + + constexpr uint32_t just_over_max_negative_disp = 16 * MB - 4u /* PC adjustment */; + bool thunk_in_gap = Create2MethodsWithGap(kNopCode, ArrayRef<LinkerPatch>(), + method3_code, method3_patches, + just_over_max_negative_disp - bl_offset_in_method3); + ASSERT_FALSE(thunk_in_gap); // There should be no thunk. + + // Check linked code. + auto expected_code = GenNopsAndBl(2u, kBlMinusMax); + EXPECT_TRUE(CheckLinkedMethod(MethodRef(3u), ArrayRef<const uint8_t>(expected_code))); +} + +TEST_F(Thumb2RelativePatcherTest, CallOtherJustTooFarAfter) { + auto method1_raw_code = GenNopsAndBl(2u, kBlPlus0); + constexpr uint32_t bl_offset_in_method1 = 2u * 2u; // After NOPs. + ArrayRef<const uint8_t> method1_code(method1_raw_code); + ASSERT_EQ(bl_offset_in_method1 + 4u, method1_code.size()); + LinkerPatch method1_patches[] = { + LinkerPatch::RelativeCodePatch(bl_offset_in_method1, nullptr, 3u), + }; + + constexpr uint32_t just_over_max_positive_disp = 16 * MB + 4u /* PC adjustment */; + bool thunk_in_gap = Create2MethodsWithGap(method1_code, method1_patches, + kNopCode, ArrayRef<LinkerPatch>(), + bl_offset_in_method1 + just_over_max_positive_disp); + ASSERT_TRUE(thunk_in_gap); + + uint32_t method1_offset = GetMethodOffset(1u); + uint32_t method3_offset = GetMethodOffset(3u); + uint32_t method3_header_offset = method3_offset - sizeof(OatQuickMethodHeader); + ASSERT_TRUE(IsAligned<kArmAlignment>(method3_header_offset)); + uint32_t thunk_offset = method3_header_offset - CompiledCode::AlignCode(ThunkSize(), kThumb2); + ASSERT_TRUE(IsAligned<kArmAlignment>(thunk_offset)); + uint32_t diff = thunk_offset - (method1_offset + bl_offset_in_method1 + 4u /* PC adjustment */); + ASSERT_EQ(diff & 1u, 0u); + ASSERT_GE(diff, 16 * MB - (1u << 9)); // Simple encoding, unknown bits fit into the low 8 bits. + auto expected_code = GenNopsAndBl(2u, 0xf3ffd700 | ((diff >> 1) & 0xffu)); + EXPECT_TRUE(CheckLinkedMethod(MethodRef(1u), ArrayRef<const uint8_t>(expected_code))); + CheckThunk(thunk_offset); +} + +TEST_F(Thumb2RelativePatcherTest, CallOtherJustTooFarBefore) { + auto method3_raw_code = GenNopsAndBl(3u, kBlPlus0); + constexpr uint32_t bl_offset_in_method3 = 3u * 2u; // After NOPs. + ArrayRef<const uint8_t> method3_code(method3_raw_code); + ASSERT_EQ(bl_offset_in_method3 + 4u, method3_code.size()); + LinkerPatch method3_patches[] = { + LinkerPatch::RelativeCodePatch(bl_offset_in_method3, nullptr, 1u), + }; + + constexpr uint32_t just_over_max_negative_disp = 16 * MB + 2 - 4u /* PC adjustment */; + bool thunk_in_gap = Create2MethodsWithGap(kNopCode, ArrayRef<LinkerPatch>(), + method3_code, method3_patches, + just_over_max_negative_disp - bl_offset_in_method3); + ASSERT_FALSE(thunk_in_gap); // There should be a thunk but it should be after the method2. + + // Check linked code. + uint32_t method3_offset = GetMethodOffset(3u); + uint32_t thunk_offset = CompiledCode::AlignCode(method3_offset + method3_code.size(), kThumb2); + uint32_t diff = thunk_offset - (method3_offset + bl_offset_in_method3 + 4u /* PC adjustment */); + ASSERT_EQ(diff & 1u, 0u); + ASSERT_LT(diff >> 1, 1u << 8); // Simple encoding, (diff >> 1) fits into 8 bits. + auto expected_code = GenNopsAndBl(3u, kBlPlus0 | ((diff >> 1) & 0xffu)); + EXPECT_TRUE(CheckLinkedMethod(MethodRef(3u), ArrayRef<const uint8_t>(expected_code))); + EXPECT_TRUE(CheckThunk(thunk_offset)); +} + +} // namespace linker +} // namespace art diff --git a/compiler/linker/arm64/relative_patcher_arm64.cc b/compiler/linker/arm64/relative_patcher_arm64.cc new file mode 100644 index 0000000000..b61b3d8e44 --- /dev/null +++ b/compiler/linker/arm64/relative_patcher_arm64.cc @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "linker/arm64/relative_patcher_arm64.h" + +#include "arch/arm64/instruction_set_features_arm64.h" +#include "compiled_method.h" +#include "driver/compiler_driver.h" +#include "mirror/art_method.h" +#include "utils/arm64/assembler_arm64.h" +#include "oat.h" +#include "output_stream.h" + +namespace art { +namespace linker { + +Arm64RelativePatcher::Arm64RelativePatcher(RelativePatcherTargetProvider* provider, + const Arm64InstructionSetFeatures* features) + : ArmBaseRelativePatcher(provider, kArm64, CompileThunkCode(), + kMaxPositiveDisplacement, kMaxNegativeDisplacement), + fix_cortex_a53_843419_(features->NeedFixCortexA53_843419()), + reserved_adrp_thunks_(0u), + processed_adrp_thunks_(0u) { + if (fix_cortex_a53_843419_) { + adrp_thunk_locations_.reserve(16u); + current_method_thunks_.reserve(16u * kAdrpThunkSize); + } +} + +uint32_t Arm64RelativePatcher::ReserveSpace(uint32_t offset, + const CompiledMethod* compiled_method, + MethodReference method_ref) { + if (!fix_cortex_a53_843419_) { + DCHECK(adrp_thunk_locations_.empty()); + return ReserveSpaceInternal(offset, compiled_method, method_ref, 0u); + } + + // Add thunks for previous method if any. + if (reserved_adrp_thunks_ != adrp_thunk_locations_.size()) { + size_t num_adrp_thunks = adrp_thunk_locations_.size() - reserved_adrp_thunks_; + offset = CompiledMethod::AlignCode(offset, kArm64) + kAdrpThunkSize * num_adrp_thunks; + reserved_adrp_thunks_ = adrp_thunk_locations_.size(); + } + + // Count the number of ADRP insns as the upper bound on the number of thunks needed + // and use it to reserve space for other linker patches. + size_t num_adrp = 0u; + if (LIKELY(compiled_method != nullptr)) { + for (const LinkerPatch& patch : compiled_method->GetPatches()) { + if (patch.Type() == kLinkerPatchDexCacheArray && + patch.LiteralOffset() == patch.PcInsnOffset()) { // ADRP patch + ++num_adrp; + } + } + } + offset = ReserveSpaceInternal(offset, compiled_method, method_ref, kAdrpThunkSize * num_adrp); + if (num_adrp == 0u) { + return offset; + } + + // Now that we have the actual offset where the code will be placed, locate the ADRP insns + // that actually require the thunk. + uint32_t quick_code_offset = compiled_method->AlignCode(offset) + sizeof(OatQuickMethodHeader); + ArrayRef<const uint8_t> code(*compiled_method->GetQuickCode()); + uint32_t thunk_offset = compiled_method->AlignCode(quick_code_offset + code.size()); + DCHECK(compiled_method != nullptr); + for (const LinkerPatch& patch : compiled_method->GetPatches()) { + if (patch.Type() == kLinkerPatchDexCacheArray && + patch.LiteralOffset() == patch.PcInsnOffset()) { // ADRP patch + uint32_t patch_offset = quick_code_offset + patch.LiteralOffset(); + if (NeedsErratum843419Thunk(code, patch.LiteralOffset(), patch_offset)) { + adrp_thunk_locations_.emplace_back(patch_offset, thunk_offset); + thunk_offset += kAdrpThunkSize; + } + } + } + return offset; +} + +uint32_t Arm64RelativePatcher::WriteThunks(OutputStream* out, uint32_t offset) { + if (fix_cortex_a53_843419_) { + if (!current_method_thunks_.empty()) { + uint32_t aligned_offset = CompiledMethod::AlignCode(offset, kArm64); + if (kIsDebugBuild) { + CHECK(IsAligned<kAdrpThunkSize>(current_method_thunks_.size())); + size_t num_thunks = current_method_thunks_.size() / kAdrpThunkSize; + CHECK_LE(num_thunks, processed_adrp_thunks_); + for (size_t i = 0u; i != num_thunks; ++i) { + const auto& entry = adrp_thunk_locations_[processed_adrp_thunks_ - num_thunks + i]; + CHECK_EQ(entry.second, aligned_offset + i * kAdrpThunkSize); + } + } + uint32_t aligned_code_delta = aligned_offset - offset; + if (aligned_code_delta != 0u && !WriteCodeAlignment(out, aligned_code_delta)) { + return 0u; + } + if (!WriteMiscThunk(out, ArrayRef<const uint8_t>(current_method_thunks_))) { + return 0u; + } + offset = aligned_offset + current_method_thunks_.size(); + current_method_thunks_.clear(); + } + } + return ArmBaseRelativePatcher::WriteThunks(out, offset); +} + +void Arm64RelativePatcher::PatchCall(std::vector<uint8_t>* code, uint32_t literal_offset, + uint32_t patch_offset, uint32_t target_offset) { + DCHECK_LE(literal_offset + 4u, code->size()); + DCHECK_EQ(literal_offset & 3u, 0u); + DCHECK_EQ(patch_offset & 3u, 0u); + DCHECK_EQ(target_offset & 3u, 0u); + uint32_t displacement = CalculateDisplacement(patch_offset, target_offset & ~1u); + DCHECK_EQ(displacement & 3u, 0u); + DCHECK((displacement >> 27) == 0u || (displacement >> 27) == 31u); // 28-bit signed. + uint32_t insn = (displacement & 0x0fffffffu) >> 2; + insn |= 0x94000000; // BL + + // Check that we're just overwriting an existing BL. + DCHECK_EQ(GetInsn(code, literal_offset) & 0xfc000000u, 0x94000000u); + // Write the new BL. + SetInsn(code, literal_offset, insn); +} + +void Arm64RelativePatcher::PatchDexCacheReference(std::vector<uint8_t>* code, + const LinkerPatch& patch, + uint32_t patch_offset, + uint32_t target_offset) { + DCHECK_EQ(patch_offset & 3u, 0u); + DCHECK_EQ(target_offset & 3u, 0u); + uint32_t literal_offset = patch.LiteralOffset(); + uint32_t insn = GetInsn(code, literal_offset); + uint32_t pc_insn_offset = patch.PcInsnOffset(); + uint32_t disp = target_offset - ((patch_offset - literal_offset + pc_insn_offset) & ~0xfffu); + if (literal_offset == pc_insn_offset) { + // Check it's an ADRP with imm == 0 (unset). + DCHECK_EQ((insn & 0xffffffe0u), 0x90000000u) + << literal_offset << ", " << pc_insn_offset << ", 0x" << std::hex << insn; + if (fix_cortex_a53_843419_ && processed_adrp_thunks_ != adrp_thunk_locations_.size() && + adrp_thunk_locations_[processed_adrp_thunks_].first == patch_offset) { + DCHECK(NeedsErratum843419Thunk(ArrayRef<const uint8_t>(*code), + literal_offset, patch_offset)); + uint32_t thunk_offset = adrp_thunk_locations_[processed_adrp_thunks_].second; + uint32_t adrp_disp = target_offset - (thunk_offset & ~0xfffu); + uint32_t adrp = PatchAdrp(insn, adrp_disp); + + uint32_t out_disp = thunk_offset - patch_offset; + DCHECK_EQ(out_disp & 3u, 0u); + DCHECK((out_disp >> 27) == 0u || (out_disp >> 27) == 31u); // 28-bit signed. + insn = (out_disp & 0x0fffffffu) >> 2; + insn |= 0x14000000; // B <thunk> + + uint32_t back_disp = -out_disp; + DCHECK_EQ(back_disp & 3u, 0u); + DCHECK((back_disp >> 27) == 0u || (back_disp >> 27) == 31u); // 28-bit signed. + uint32_t b_back = (back_disp & 0x0fffffffu) >> 2; + b_back |= 0x14000000; // B <back> + size_t thunks_code_offset = current_method_thunks_.size(); + current_method_thunks_.resize(thunks_code_offset + kAdrpThunkSize); + SetInsn(¤t_method_thunks_, thunks_code_offset, adrp); + SetInsn(¤t_method_thunks_, thunks_code_offset + 4u, b_back); + static_assert(kAdrpThunkSize == 2 * 4u, "thunk has 2 instructions"); + + processed_adrp_thunks_ += 1u; + } else { + insn = PatchAdrp(insn, disp); + } + // Write the new ADRP (or B to the erratum 843419 thunk). + SetInsn(code, literal_offset, insn); + } else { + DCHECK_EQ(insn & 0xfffffc00, 0xb9400000); // LDR 32-bit with imm12 == 0 (unset). + if (kIsDebugBuild) { + uint32_t adrp = GetInsn(code, pc_insn_offset); + if ((adrp & 0x9f000000u) != 0x90000000u) { + CHECK(fix_cortex_a53_843419_); + CHECK_EQ(adrp & 0xfc000000u, 0x14000000u); // B <thunk> + CHECK(IsAligned<kAdrpThunkSize>(current_method_thunks_.size())); + size_t num_thunks = current_method_thunks_.size() / kAdrpThunkSize; + CHECK_LE(num_thunks, processed_adrp_thunks_); + uint32_t b_offset = patch_offset - literal_offset + pc_insn_offset; + for (size_t i = processed_adrp_thunks_ - num_thunks; ; ++i) { + CHECK_NE(i, processed_adrp_thunks_); + if (adrp_thunk_locations_[i].first == b_offset) { + size_t idx = num_thunks - (processed_adrp_thunks_ - i); + adrp = GetInsn(¤t_method_thunks_, idx * kAdrpThunkSize); + break; + } + } + } + CHECK_EQ(adrp & 0x9f00001fu, // Check that pc_insn_offset points + 0x90000000 | ((insn >> 5) & 0x1fu)); // to ADRP with matching register. + } + uint32_t imm12 = (disp & 0xfffu) >> 2; + insn = (insn & ~(0xfffu << 10)) | (imm12 << 10); + SetInsn(code, literal_offset, insn); + } +} + +std::vector<uint8_t> Arm64RelativePatcher::CompileThunkCode() { + // The thunk just uses the entry point in the ArtMethod. This works even for calls + // to the generic JNI and interpreter trampolines. + arm64::Arm64Assembler assembler; + Offset offset(mirror::ArtMethod::EntryPointFromQuickCompiledCodeOffset( + kArm64PointerSize).Int32Value()); + assembler.JumpTo(ManagedRegister(arm64::X0), offset, ManagedRegister(arm64::IP0)); + // Ensure we emit the literal pool. + assembler.EmitSlowPaths(); + std::vector<uint8_t> thunk_code(assembler.CodeSize()); + MemoryRegion code(thunk_code.data(), thunk_code.size()); + assembler.FinalizeInstructions(code); + return thunk_code; +} + +uint32_t Arm64RelativePatcher::PatchAdrp(uint32_t adrp, uint32_t disp) { + return (adrp & 0x9f00001fu) | // Clear offset bits, keep ADRP with destination reg. + // Bottom 12 bits are ignored, the next 2 lowest bits are encoded in bits 29-30. + ((disp & 0x00003000u) << (29 - 12)) | + // The next 16 bits are encoded in bits 5-22. + ((disp & 0xffffc000u) >> (12 + 2 - 5)) | + // Since the target_offset is based on the beginning of the oat file and the + // image space precedes the oat file, the target_offset into image space will + // be negative yet passed as uint32_t. Therefore we limit the displacement + // to +-2GiB (rather than the maximim +-4GiB) and determine the sign bit from + // the highest bit of the displacement. This is encoded in bit 23. + ((disp & 0x80000000u) >> (31 - 23)); +} + +bool Arm64RelativePatcher::NeedsErratum843419Thunk(ArrayRef<const uint8_t> code, + uint32_t literal_offset, + uint32_t patch_offset) { + DCHECK_EQ(patch_offset & 0x3u, 0u); + if ((patch_offset & 0xff8) == 0xff8) { // ...ff8 or ...ffc + uint32_t adrp = GetInsn(code, literal_offset); + DCHECK_EQ(adrp & 0xff000000, 0x90000000); + // TODO: Improve the check. For now, we're just checking if the next insn is + // the LDR using the result of the ADRP, otherwise we implement the workaround. + uint32_t next_insn = GetInsn(code, literal_offset + 4u); + bool ok = (next_insn & 0xffc00000) == 0xb9400000 && // LDR <Wt>, [<Xn>, #pimm] + (((next_insn >> 5) ^ adrp) & 0x1f) == 0; // <Xn> == ADRP destination reg + return !ok; + } + return false; +} + +void Arm64RelativePatcher::SetInsn(std::vector<uint8_t>* code, uint32_t offset, uint32_t value) { + DCHECK_LE(offset + 4u, code->size()); + DCHECK_EQ(offset & 3u, 0u); + uint8_t* addr = &(*code)[offset]; + addr[0] = (value >> 0) & 0xff; + addr[1] = (value >> 8) & 0xff; + addr[2] = (value >> 16) & 0xff; + addr[3] = (value >> 24) & 0xff; +} + +uint32_t Arm64RelativePatcher::GetInsn(ArrayRef<const uint8_t> code, uint32_t offset) { + DCHECK_LE(offset + 4u, code.size()); + DCHECK_EQ(offset & 3u, 0u); + const uint8_t* addr = &code[offset]; + return + (static_cast<uint32_t>(addr[0]) << 0) + + (static_cast<uint32_t>(addr[1]) << 8) + + (static_cast<uint32_t>(addr[2]) << 16)+ + (static_cast<uint32_t>(addr[3]) << 24); +} + +template <typename Alloc> +uint32_t Arm64RelativePatcher::GetInsn(std::vector<uint8_t, Alloc>* code, uint32_t offset) { + return GetInsn(ArrayRef<const uint8_t>(*code), offset); +} + +} // namespace linker +} // namespace art diff --git a/compiler/linker/arm64/relative_patcher_arm64.h b/compiler/linker/arm64/relative_patcher_arm64.h new file mode 100644 index 0000000000..b2a1da50d3 --- /dev/null +++ b/compiler/linker/arm64/relative_patcher_arm64.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_COMPILER_LINKER_ARM64_RELATIVE_PATCHER_ARM64_H_ +#define ART_COMPILER_LINKER_ARM64_RELATIVE_PATCHER_ARM64_H_ + +#include "linker/arm/relative_patcher_arm_base.h" +#include "utils/array_ref.h" + +namespace art { +namespace linker { + +class Arm64RelativePatcher FINAL : public ArmBaseRelativePatcher { + public: + Arm64RelativePatcher(RelativePatcherTargetProvider* provider, + const Arm64InstructionSetFeatures* features); + + uint32_t ReserveSpace(uint32_t offset, const CompiledMethod* compiled_method, + MethodReference method_ref) OVERRIDE; + uint32_t WriteThunks(OutputStream* out, uint32_t offset) OVERRIDE; + void PatchCall(std::vector<uint8_t>* code, uint32_t literal_offset, + uint32_t patch_offset, uint32_t target_offset) OVERRIDE; + void PatchDexCacheReference(std::vector<uint8_t>* code, const LinkerPatch& patch, + uint32_t patch_offset, uint32_t target_offset) OVERRIDE; + + private: + static std::vector<uint8_t> CompileThunkCode(); + static uint32_t PatchAdrp(uint32_t adrp, uint32_t disp); + + static bool NeedsErratum843419Thunk(ArrayRef<const uint8_t> code, uint32_t literal_offset, + uint32_t patch_offset); + void SetInsn(std::vector<uint8_t>* code, uint32_t offset, uint32_t value); + static uint32_t GetInsn(ArrayRef<const uint8_t> code, uint32_t offset); + + template <typename Alloc> + static uint32_t GetInsn(std::vector<uint8_t, Alloc>* code, uint32_t offset); + + // Maximum positive and negative displacement measured from the patch location. + // (Signed 28 bit displacement with the last bit 0 has range [-2^27, 2^27-4] measured from + // the ARM64 PC pointing to the BL.) + static constexpr uint32_t kMaxPositiveDisplacement = (1u << 27) - 4u; + static constexpr uint32_t kMaxNegativeDisplacement = (1u << 27); + + // The ADRP thunk for erratum 843419 is 2 instructions, i.e. 8 bytes. + static constexpr uint32_t kAdrpThunkSize = 8u; + + const bool fix_cortex_a53_843419_; + // Map original patch_offset to thunk offset. + std::vector<std::pair<uint32_t, uint32_t>> adrp_thunk_locations_; + size_t reserved_adrp_thunks_; + size_t processed_adrp_thunks_; + std::vector<uint8_t> current_method_thunks_; + + DISALLOW_COPY_AND_ASSIGN(Arm64RelativePatcher); +}; + +} // namespace linker +} // namespace art + +#endif // ART_COMPILER_LINKER_ARM64_RELATIVE_PATCHER_ARM64_H_ diff --git a/compiler/linker/relative_patcher.cc b/compiler/linker/relative_patcher.cc new file mode 100644 index 0000000000..71f38b408a --- /dev/null +++ b/compiler/linker/relative_patcher.cc @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "linker/relative_patcher.h" + +#include "linker/arm/relative_patcher_thumb2.h" +#include "linker/arm64/relative_patcher_arm64.h" +#include "linker/x86/relative_patcher_x86.h" +#include "linker/x86_64/relative_patcher_x86_64.h" +#include "output_stream.h" + +namespace art { +namespace linker { + +std::unique_ptr<RelativePatcher> RelativePatcher::Create( + InstructionSet instruction_set, const InstructionSetFeatures* features, + RelativePatcherTargetProvider* provider) { + class RelativePatcherNone FINAL : public RelativePatcher { + public: + RelativePatcherNone() { } + + uint32_t ReserveSpace(uint32_t offset, + const CompiledMethod* compiled_method ATTRIBUTE_UNUSED, + MethodReference method_ref ATTRIBUTE_UNUSED) OVERRIDE { + return offset; // No space reserved; no patches expected. + } + + uint32_t WriteThunks(OutputStream* out ATTRIBUTE_UNUSED, uint32_t offset) OVERRIDE { + return offset; // No thunks added; no patches expected. + } + + void PatchCall(std::vector<uint8_t>* code ATTRIBUTE_UNUSED, + uint32_t literal_offset ATTRIBUTE_UNUSED, + uint32_t patch_offset ATTRIBUTE_UNUSED, + uint32_t target_offset ATTRIBUTE_UNUSED) OVERRIDE { + LOG(FATAL) << "Unexpected relative call patch."; + } + + virtual void PatchDexCacheReference(std::vector<uint8_t>* code ATTRIBUTE_UNUSED, + const LinkerPatch& patch ATTRIBUTE_UNUSED, + uint32_t patch_offset ATTRIBUTE_UNUSED, + uint32_t target_offset ATTRIBUTE_UNUSED) { + LOG(FATAL) << "Unexpected relative dex cache array patch."; + } + + private: + DISALLOW_COPY_AND_ASSIGN(RelativePatcherNone); + }; + + switch (instruction_set) { + case kX86: + return std::unique_ptr<RelativePatcher>(new X86RelativePatcher()); + break; + case kX86_64: + return std::unique_ptr<RelativePatcher>(new X86_64RelativePatcher()); + break; + case kArm: + // Fall through: we generate Thumb2 code for "arm". + case kThumb2: + return std::unique_ptr<RelativePatcher>(new Thumb2RelativePatcher(provider)); + break; + case kArm64: + return std::unique_ptr<RelativePatcher>( + new Arm64RelativePatcher(provider, features->AsArm64InstructionSetFeatures())); + break; + default: + return std::unique_ptr<RelativePatcher>(new RelativePatcherNone); + break; + } +} + +bool RelativePatcher::WriteCodeAlignment(OutputStream* out, uint32_t aligned_code_delta) { + static const uint8_t kPadding[] = { + 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u + }; + DCHECK_LE(aligned_code_delta, sizeof(kPadding)); + if (UNLIKELY(!out->WriteFully(kPadding, aligned_code_delta))) { + return false; + } + size_code_alignment_ += aligned_code_delta; + return true; +} + +bool RelativePatcher::WriteRelCallThunk(OutputStream* out, const ArrayRef<const uint8_t>& thunk) { + if (UNLIKELY(!out->WriteFully(thunk.data(), thunk.size()))) { + return false; + } + size_relative_call_thunks_ += thunk.size(); + return true; +} + +bool RelativePatcher::WriteMiscThunk(OutputStream* out, const ArrayRef<const uint8_t>& thunk) { + if (UNLIKELY(!out->WriteFully(thunk.data(), thunk.size()))) { + return false; + } + size_misc_thunks_ += thunk.size(); + return true; +} + +} // namespace linker +} // namespace art diff --git a/compiler/linker/relative_patcher.h b/compiler/linker/relative_patcher.h new file mode 100644 index 0000000000..7a78254787 --- /dev/null +++ b/compiler/linker/relative_patcher.h @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_COMPILER_LINKER_RELATIVE_PATCHER_H_ +#define ART_COMPILER_LINKER_RELATIVE_PATCHER_H_ + +#include <vector> + +#include "arch/instruction_set.h" +#include "arch/instruction_set_features.h" +#include "base/macros.h" +#include "method_reference.h" +#include "utils/array_ref.h" + +namespace art { + +class CompiledMethod; +class LinkerPatch; +class OutputStream; + +namespace linker { + +/** + * @class RelativePatcherTargetProvider + * @brief Interface for providing method offsets for relative call targets. + */ +class RelativePatcherTargetProvider { + public: + /** + * Find the offset of the target method of a relative call if known. + * + * The process of assigning target method offsets includes calls to the relative patcher's + * ReserveSpace() which in turn can use FindMethodOffset() to determine if a method already + * has an offset assigned and, if so, what's that offset. If the offset has not yet been + * assigned or if it's too far for the particular architecture's relative call, + * ReserveSpace() may need to allocate space for a special dispatch thunk. + * + * @param ref the target method of the relative call. + * @return true in the first element of the pair if the method was found, false otherwise; + * if found, the second element specifies the offset. + */ + virtual std::pair<bool, uint32_t> FindMethodOffset(MethodReference ref) = 0; + + protected: + virtual ~RelativePatcherTargetProvider() { } +}; + +/** + * @class RelativePatcher + * @brief Interface for architecture-specific link-time patching of PC-relative references. + */ +class RelativePatcher { + public: + static std::unique_ptr<RelativePatcher> Create( + InstructionSet instruction_set, const InstructionSetFeatures* features, + RelativePatcherTargetProvider* provider); + + virtual ~RelativePatcher() { } + + uint32_t CodeAlignmentSize() const { + return size_code_alignment_; + } + + uint32_t RelativeCallThunksSize() const { + return size_relative_call_thunks_; + } + + uint32_t MiscThunksSize() const { + return size_misc_thunks_; + } + + // Reserve space for relative call thunks if needed, return adjusted offset. After all methods + // of a class have been processed it's called one last time with compiled_method == nullptr. + virtual uint32_t ReserveSpace(uint32_t offset, const CompiledMethod* compiled_method, + MethodReference method_ref) = 0; + + // Write relative call thunks if needed, return adjusted offset. + virtual uint32_t WriteThunks(OutputStream* out, uint32_t offset) = 0; + + // Patch method code. The input displacement is relative to the patched location, + // the patcher may need to adjust it if the correct base is different. + virtual void PatchCall(std::vector<uint8_t>* code, uint32_t literal_offset, + uint32_t patch_offset, uint32_t target_offset) = 0; + + // Patch a reference to a dex cache location. + virtual void PatchDexCacheReference(std::vector<uint8_t>* code, const LinkerPatch& patch, + uint32_t patch_offset, uint32_t target_offset) = 0; + + protected: + RelativePatcher() + : size_code_alignment_(0u), + size_relative_call_thunks_(0u), + size_misc_thunks_(0u) { + } + + bool WriteCodeAlignment(OutputStream* out, uint32_t aligned_code_delta); + bool WriteRelCallThunk(OutputStream* out, const ArrayRef<const uint8_t>& thunk); + bool WriteMiscThunk(OutputStream* out, const ArrayRef<const uint8_t>& thunk); + + private: + uint32_t size_code_alignment_; + uint32_t size_relative_call_thunks_; + uint32_t size_misc_thunks_; + + DISALLOW_COPY_AND_ASSIGN(RelativePatcher); +}; + +} // namespace linker +} // namespace art + +#endif // ART_COMPILER_LINKER_RELATIVE_PATCHER_H_ diff --git a/compiler/linker/relative_patcher_test.h b/compiler/linker/relative_patcher_test.h new file mode 100644 index 0000000000..9efcf6082b --- /dev/null +++ b/compiler/linker/relative_patcher_test.h @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_COMPILER_LINKER_RELATIVE_PATCHER_TEST_H_ +#define ART_COMPILER_LINKER_RELATIVE_PATCHER_TEST_H_ + +#include "arch/instruction_set.h" +#include "arch/instruction_set_features.h" +#include "base/macros.h" +#include "compiled_method.h" +#include "dex/quick/dex_file_to_method_inliner_map.h" +#include "dex/verification_results.h" +#include "driver/compiler_driver.h" +#include "driver/compiler_options.h" +#include "globals.h" +#include "gtest/gtest.h" +#include "linker/relative_patcher.h" +#include "method_reference.h" +#include "oat.h" +#include "utils/array_ref.h" +#include "vector_output_stream.h" + +namespace art { +namespace linker { + +// Base class providing infrastructure for architecture-specific tests. +class RelativePatcherTest : public testing::Test { + protected: + RelativePatcherTest(InstructionSet instruction_set, const std::string& variant) + : compiler_options_(), + verification_results_(&compiler_options_), + inliner_map_(), + driver_(&compiler_options_, &verification_results_, &inliner_map_, + Compiler::kQuick, instruction_set, nullptr, + false, nullptr, nullptr, 1u, + false, false, "", nullptr, -1, ""), + error_msg_(), + instruction_set_(instruction_set), + features_(InstructionSetFeatures::FromVariant(instruction_set, variant, &error_msg_)), + method_offset_map_(), + patcher_(RelativePatcher::Create(instruction_set, features_.get(), &method_offset_map_)), + dex_cache_arrays_begin_(0u), + compiled_method_refs_(), + compiled_methods_(), + patched_code_(), + output_(), + out_("test output stream", &output_) { + CHECK(error_msg_.empty()) << instruction_set << "/" << variant; + patched_code_.reserve(16 * KB); + } + + MethodReference MethodRef(uint32_t method_idx) { + CHECK_NE(method_idx, 0u); + return MethodReference(nullptr, method_idx); + } + + void AddCompiledMethod(MethodReference method_ref, + const ArrayRef<const uint8_t>& code, + const ArrayRef<LinkerPatch>& patches) { + compiled_method_refs_.push_back(method_ref); + compiled_methods_.emplace_back(new CompiledMethod( + &driver_, instruction_set_, code, + 0u, 0u, 0u, nullptr, ArrayRef<const uint8_t>(), ArrayRef<const uint8_t>(), + ArrayRef<const uint8_t>(), ArrayRef<const uint8_t>(), + patches)); + } + + void Link() { + // Reserve space. + static_assert(kTrampolineOffset == 0u, "Unexpected trampoline offset."); + uint32_t offset = kTrampolineSize; + size_t idx = 0u; + for (auto& compiled_method : compiled_methods_) { + offset = patcher_->ReserveSpace(offset, compiled_method.get(), compiled_method_refs_[idx]); + + uint32_t aligned_offset = compiled_method->AlignCode(offset); + uint32_t aligned_code_delta = aligned_offset - offset; + offset += aligned_code_delta; + + offset += sizeof(OatQuickMethodHeader); + uint32_t quick_code_offset = offset + compiled_method->CodeDelta(); + const auto& code = *compiled_method->GetQuickCode(); + offset += code.size(); + + method_offset_map_.map.Put(compiled_method_refs_[idx], quick_code_offset); + ++idx; + } + offset = patcher_->ReserveSpace(offset, nullptr, MethodReference(nullptr, 0u)); + uint32_t output_size = offset; + output_.reserve(output_size); + + // Write data. + DCHECK(output_.empty()); + uint8_t dummy_trampoline[kTrampolineSize]; + memset(dummy_trampoline, 0, sizeof(dummy_trampoline)); + out_.WriteFully(dummy_trampoline, kTrampolineSize); + offset = kTrampolineSize; + static const uint8_t kPadding[] = { + 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u + }; + uint8_t dummy_header[sizeof(OatQuickMethodHeader)]; + memset(dummy_header, 0, sizeof(dummy_header)); + for (auto& compiled_method : compiled_methods_) { + offset = patcher_->WriteThunks(&out_, offset); + + uint32_t aligned_offset = compiled_method->AlignCode(offset); + uint32_t aligned_code_delta = aligned_offset - offset; + CHECK_LE(aligned_code_delta, sizeof(kPadding)); + out_.WriteFully(kPadding, aligned_code_delta); + offset += aligned_code_delta; + + out_.WriteFully(dummy_header, sizeof(OatQuickMethodHeader)); + offset += sizeof(OatQuickMethodHeader); + ArrayRef<const uint8_t> code(*compiled_method->GetQuickCode()); + if (!compiled_method->GetPatches().empty()) { + patched_code_.assign(code.begin(), code.end()); + code = ArrayRef<const uint8_t>(patched_code_); + for (const LinkerPatch& patch : compiled_method->GetPatches()) { + if (patch.Type() == kLinkerPatchCallRelative) { + auto result = method_offset_map_.FindMethodOffset(patch.TargetMethod()); + uint32_t target_offset = + result.first ? result.second : kTrampolineOffset + compiled_method->CodeDelta(); + patcher_->PatchCall(&patched_code_, patch.LiteralOffset(), + offset + patch.LiteralOffset(), target_offset); + } else if (patch.Type() == kLinkerPatchDexCacheArray) { + uint32_t target_offset = dex_cache_arrays_begin_ + patch.TargetDexCacheElementOffset(); + patcher_->PatchDexCacheReference(&patched_code_, patch, + offset + patch.LiteralOffset(), target_offset); + } else { + LOG(FATAL) << "Bad patch type."; + } + } + } + out_.WriteFully(&code[0], code.size()); + offset += code.size(); + } + offset = patcher_->WriteThunks(&out_, offset); + CHECK_EQ(offset, output_size); + CHECK_EQ(output_.size(), output_size); + } + + bool CheckLinkedMethod(MethodReference method_ref, const ArrayRef<const uint8_t>& expected_code) { + // Sanity check: original code size must match linked_code.size(). + size_t idx = 0u; + for (auto ref : compiled_method_refs_) { + if (ref.dex_file == method_ref.dex_file && + ref.dex_method_index == method_ref.dex_method_index) { + break; + } + ++idx; + } + CHECK_NE(idx, compiled_method_refs_.size()); + CHECK_EQ(compiled_methods_[idx]->GetQuickCode()->size(), expected_code.size()); + + auto result = method_offset_map_.FindMethodOffset(method_ref); + CHECK(result.first); // Must have been linked. + size_t offset = result.second - compiled_methods_[idx]->CodeDelta(); + CHECK_LT(offset, output_.size()); + CHECK_LE(offset + expected_code.size(), output_.size()); + ArrayRef<const uint8_t> linked_code(&output_[offset], expected_code.size()); + if (linked_code == expected_code) { + return true; + } + // Log failure info. + DumpDiff(expected_code, linked_code); + return false; + } + + void DumpDiff(const ArrayRef<const uint8_t>& expected_code, + const ArrayRef<const uint8_t>& linked_code) { + std::ostringstream expected_hex; + std::ostringstream linked_hex; + std::ostringstream diff_indicator; + static const char digits[] = "0123456789abcdef"; + bool found_diff = false; + for (size_t i = 0; i != expected_code.size(); ++i) { + expected_hex << " " << digits[expected_code[i] >> 4] << digits[expected_code[i] & 0xf]; + linked_hex << " " << digits[linked_code[i] >> 4] << digits[linked_code[i] & 0xf]; + diff_indicator << " "; + if (!found_diff) { + found_diff = (expected_code[i] != linked_code[i]); + diff_indicator << (found_diff ? "^^" : " "); + } + } + CHECK(found_diff); + LOG(ERROR) << "diff expected_code linked_code"; + LOG(ERROR) << "<" << expected_hex.str(); + LOG(ERROR) << ">" << linked_hex.str(); + LOG(ERROR) << " " << diff_indicator.str(); + } + + // Map method reference to assinged offset. + // Wrap the map in a class implementing linker::RelativePatcherTargetProvider. + class MethodOffsetMap FINAL : public linker::RelativePatcherTargetProvider { + public: + std::pair<bool, uint32_t> FindMethodOffset(MethodReference ref) OVERRIDE { + auto it = map.find(ref); + if (it == map.end()) { + return std::pair<bool, uint32_t>(false, 0u); + } else { + return std::pair<bool, uint32_t>(true, it->second); + } + } + SafeMap<MethodReference, uint32_t, MethodReferenceComparator> map; + }; + + static const uint32_t kTrampolineSize = 4u; + static const uint32_t kTrampolineOffset = 0u; + + CompilerOptions compiler_options_; + VerificationResults verification_results_; + DexFileToMethodInlinerMap inliner_map_; + CompilerDriver driver_; // Needed for constructing CompiledMethod. + std::string error_msg_; + InstructionSet instruction_set_; + std::unique_ptr<const InstructionSetFeatures> features_; + MethodOffsetMap method_offset_map_; + std::unique_ptr<RelativePatcher> patcher_; + uint32_t dex_cache_arrays_begin_; + std::vector<MethodReference> compiled_method_refs_; + std::vector<std::unique_ptr<CompiledMethod>> compiled_methods_; + std::vector<uint8_t> patched_code_; + std::vector<uint8_t> output_; + VectorOutputStream out_; +}; + +} // namespace linker +} // namespace art + +#endif // ART_COMPILER_LINKER_RELATIVE_PATCHER_TEST_H_ diff --git a/compiler/linker/x86/relative_patcher_x86.cc b/compiler/linker/x86/relative_patcher_x86.cc new file mode 100644 index 0000000000..246cf11dae --- /dev/null +++ b/compiler/linker/x86/relative_patcher_x86.cc @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "linker/x86/relative_patcher_x86.h" + +namespace art { +namespace linker { + +void X86RelativePatcher::PatchDexCacheReference(std::vector<uint8_t>* code ATTRIBUTE_UNUSED, + const LinkerPatch& patch ATTRIBUTE_UNUSED, + uint32_t patch_offset ATTRIBUTE_UNUSED, + uint32_t target_offset ATTRIBUTE_UNUSED) { + LOG(FATAL) << "Unexpected relative dex cache array patch."; +} + +} // namespace linker +} // namespace art diff --git a/compiler/linker/x86/relative_patcher_x86.h b/compiler/linker/x86/relative_patcher_x86.h new file mode 100644 index 0000000000..0c881f00ba --- /dev/null +++ b/compiler/linker/x86/relative_patcher_x86.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_COMPILER_LINKER_X86_RELATIVE_PATCHER_X86_H_ +#define ART_COMPILER_LINKER_X86_RELATIVE_PATCHER_X86_H_ + +#include "linker/x86/relative_patcher_x86_base.h" + +namespace art { +namespace linker { + +class X86RelativePatcher FINAL : public X86BaseRelativePatcher { + public: + X86RelativePatcher() { } + + void PatchDexCacheReference(std::vector<uint8_t>* code, const LinkerPatch& patch, + uint32_t patch_offset, uint32_t target_offset) OVERRIDE; +}; + +} // namespace linker +} // namespace art + +#endif // ART_COMPILER_LINKER_X86_RELATIVE_PATCHER_X86_H_ diff --git a/compiler/linker/x86/relative_patcher_x86_base.cc b/compiler/linker/x86/relative_patcher_x86_base.cc new file mode 100644 index 0000000000..ea3472d232 --- /dev/null +++ b/compiler/linker/x86/relative_patcher_x86_base.cc @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "linker/x86/relative_patcher_x86_base.h" + +namespace art { +namespace linker { + +uint32_t X86BaseRelativePatcher::ReserveSpace( + uint32_t offset, + const CompiledMethod* compiled_method ATTRIBUTE_UNUSED, + MethodReference method_ref ATTRIBUTE_UNUSED) { + return offset; // No space reserved; no limit on relative call distance. +} + +uint32_t X86BaseRelativePatcher::WriteThunks(OutputStream* out ATTRIBUTE_UNUSED, uint32_t offset) { + return offset; // No thunks added; no limit on relative call distance. +} + +void X86BaseRelativePatcher::PatchCall(std::vector<uint8_t>* code, uint32_t literal_offset, + uint32_t patch_offset, uint32_t target_offset) { + DCHECK_LE(literal_offset + 4u, code->size()); + // Unsigned arithmetic with its well-defined overflow behavior is just fine here. + uint32_t displacement = target_offset - patch_offset; + displacement -= kPcDisplacement; // The base PC is at the end of the 4-byte patch. + + typedef __attribute__((__aligned__(1))) int32_t unaligned_int32_t; + reinterpret_cast<unaligned_int32_t*>(&(*code)[literal_offset])[0] = displacement; +} + +} // namespace linker +} // namespace art diff --git a/compiler/linker/x86/relative_patcher_x86_base.h b/compiler/linker/x86/relative_patcher_x86_base.h new file mode 100644 index 0000000000..1f38cf2996 --- /dev/null +++ b/compiler/linker/x86/relative_patcher_x86_base.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_COMPILER_LINKER_X86_RELATIVE_PATCHER_X86_BASE_H_ +#define ART_COMPILER_LINKER_X86_RELATIVE_PATCHER_X86_BASE_H_ + +#include "linker/relative_patcher.h" + +namespace art { +namespace linker { + +class X86BaseRelativePatcher : public RelativePatcher { + public: + uint32_t ReserveSpace(uint32_t offset, + const CompiledMethod* compiled_method, + MethodReference method_ref) OVERRIDE; + uint32_t WriteThunks(OutputStream* out, uint32_t offset) OVERRIDE; + void PatchCall(std::vector<uint8_t>* code, uint32_t literal_offset, + uint32_t patch_offset, uint32_t target_offset) OVERRIDE; + + protected: + X86BaseRelativePatcher() { } + + // PC displacement from patch location; the base address of x86/x86-64 relative + // calls and x86-64 RIP-relative addressing is the PC of the next instruction and + // the patch location is 4 bytes earlier. + static constexpr int32_t kPcDisplacement = 4; + + private: + DISALLOW_COPY_AND_ASSIGN(X86BaseRelativePatcher); +}; + +} // namespace linker +} // namespace art + +#endif // ART_COMPILER_LINKER_X86_RELATIVE_PATCHER_X86_BASE_H_ diff --git a/compiler/linker/x86/relative_patcher_x86_test.cc b/compiler/linker/x86/relative_patcher_x86_test.cc new file mode 100644 index 0000000000..c18a743b6b --- /dev/null +++ b/compiler/linker/x86/relative_patcher_x86_test.cc @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "linker/relative_patcher_test.h" +#include "linker/x86/relative_patcher_x86.h" + +namespace art { +namespace linker { + +class X86RelativePatcherTest : public RelativePatcherTest { + public: + X86RelativePatcherTest() : RelativePatcherTest(kX86, "default") { } + + protected: + static const uint8_t kCallRawCode[]; + static const ArrayRef<const uint8_t> kCallCode; + + uint32_t GetMethodOffset(uint32_t method_idx) { + auto result = method_offset_map_.FindMethodOffset(MethodRef(method_idx)); + CHECK(result.first); + return result.second; + } +}; + +const uint8_t X86RelativePatcherTest::kCallRawCode[] = { + 0xe8, 0x00, 0x01, 0x00, 0x00 +}; + +const ArrayRef<const uint8_t> X86RelativePatcherTest::kCallCode(kCallRawCode); + +TEST_F(X86RelativePatcherTest, CallSelf) { + LinkerPatch patches[] = { + LinkerPatch::RelativeCodePatch(kCallCode.size() - 4u, nullptr, 1u), + }; + AddCompiledMethod(MethodRef(1u), kCallCode, ArrayRef<LinkerPatch>(patches)); + Link(); + + static const uint8_t expected_code[] = { + 0xe8, 0xfb, 0xff, 0xff, 0xff + }; + EXPECT_TRUE(CheckLinkedMethod(MethodRef(1u), ArrayRef<const uint8_t>(expected_code))); +} + +TEST_F(X86RelativePatcherTest, CallOther) { + LinkerPatch method1_patches[] = { + LinkerPatch::RelativeCodePatch(kCallCode.size() - 4u, nullptr, 2u), + }; + AddCompiledMethod(MethodRef(1u), kCallCode, ArrayRef<LinkerPatch>(method1_patches)); + LinkerPatch method2_patches[] = { + LinkerPatch::RelativeCodePatch(kCallCode.size() - 4u, nullptr, 1u), + }; + AddCompiledMethod(MethodRef(2u), kCallCode, ArrayRef<LinkerPatch>(method2_patches)); + Link(); + + uint32_t method1_offset = GetMethodOffset(1u); + uint32_t method2_offset = GetMethodOffset(2u); + uint32_t diff_after = method2_offset - (method1_offset + kCallCode.size() /* PC adjustment */); + static const uint8_t method1_expected_code[] = { + 0xe8, + static_cast<uint8_t>(diff_after), static_cast<uint8_t>(diff_after >> 8), + static_cast<uint8_t>(diff_after >> 16), static_cast<uint8_t>(diff_after >> 24) + }; + EXPECT_TRUE(CheckLinkedMethod(MethodRef(1u), ArrayRef<const uint8_t>(method1_expected_code))); + uint32_t diff_before = method1_offset - (method2_offset + kCallCode.size() /* PC adjustment */); + static const uint8_t method2_expected_code[] = { + 0xe8, + static_cast<uint8_t>(diff_before), static_cast<uint8_t>(diff_before >> 8), + static_cast<uint8_t>(diff_before >> 16), static_cast<uint8_t>(diff_before >> 24) + }; + EXPECT_TRUE(CheckLinkedMethod(MethodRef(2u), ArrayRef<const uint8_t>(method2_expected_code))); +} + +TEST_F(X86RelativePatcherTest, CallTrampoline) { + LinkerPatch patches[] = { + LinkerPatch::RelativeCodePatch(kCallCode.size() - 4u, nullptr, 2u), + }; + AddCompiledMethod(MethodRef(1u), kCallCode, ArrayRef<LinkerPatch>(patches)); + Link(); + + auto result = method_offset_map_.FindMethodOffset(MethodRef(1)); + ASSERT_TRUE(result.first); + uint32_t diff = kTrampolineOffset - (result.second + kCallCode.size()); + static const uint8_t expected_code[] = { + 0xe8, + static_cast<uint8_t>(diff), static_cast<uint8_t>(diff >> 8), + static_cast<uint8_t>(diff >> 16), static_cast<uint8_t>(diff >> 24) + }; + EXPECT_TRUE(CheckLinkedMethod(MethodRef(1u), ArrayRef<const uint8_t>(expected_code))); +} + +} // namespace linker +} // namespace art diff --git a/compiler/linker/x86_64/relative_patcher_x86_64.cc b/compiler/linker/x86_64/relative_patcher_x86_64.cc new file mode 100644 index 0000000000..598f3ac4a8 --- /dev/null +++ b/compiler/linker/x86_64/relative_patcher_x86_64.cc @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "linker/x86_64/relative_patcher_x86_64.h" + +#include "compiled_method.h" + +namespace art { +namespace linker { + +void X86_64RelativePatcher::PatchDexCacheReference(std::vector<uint8_t>* code, + const LinkerPatch& patch, + uint32_t patch_offset, uint32_t target_offset) { + DCHECK_LE(patch.LiteralOffset() + 4u, code->size()); + // Unsigned arithmetic with its well-defined overflow behavior is just fine here. + uint32_t displacement = target_offset - patch_offset; + displacement -= kPcDisplacement; // The base PC is at the end of the 4-byte patch. + + typedef __attribute__((__aligned__(1))) int32_t unaligned_int32_t; + reinterpret_cast<unaligned_int32_t*>(&(*code)[patch.LiteralOffset()])[0] = displacement; +} + +} // namespace linker +} // namespace art diff --git a/compiler/linker/x86_64/relative_patcher_x86_64.h b/compiler/linker/x86_64/relative_patcher_x86_64.h new file mode 100644 index 0000000000..af687b4a2f --- /dev/null +++ b/compiler/linker/x86_64/relative_patcher_x86_64.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_COMPILER_LINKER_X86_64_RELATIVE_PATCHER_X86_64_H_ +#define ART_COMPILER_LINKER_X86_64_RELATIVE_PATCHER_X86_64_H_ + +#include "linker/x86/relative_patcher_x86_base.h" + +namespace art { +namespace linker { + +class X86_64RelativePatcher FINAL : public X86BaseRelativePatcher { + public: + X86_64RelativePatcher() { } + + void PatchDexCacheReference(std::vector<uint8_t>* code, const LinkerPatch& patch, + uint32_t patch_offset, uint32_t target_offset) OVERRIDE; +}; + +} // namespace linker +} // namespace art + +#endif // ART_COMPILER_LINKER_X86_64_RELATIVE_PATCHER_X86_64_H_ diff --git a/compiler/linker/x86_64/relative_patcher_x86_64_test.cc b/compiler/linker/x86_64/relative_patcher_x86_64_test.cc new file mode 100644 index 0000000000..9d9529ced7 --- /dev/null +++ b/compiler/linker/x86_64/relative_patcher_x86_64_test.cc @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "linker/relative_patcher_test.h" +#include "linker/x86_64/relative_patcher_x86_64.h" + +namespace art { +namespace linker { + +class X86_64RelativePatcherTest : public RelativePatcherTest { + public: + X86_64RelativePatcherTest() : RelativePatcherTest(kX86_64, "default") { } + + protected: + static const uint8_t kCallRawCode[]; + static const ArrayRef<const uint8_t> kCallCode; + static const uint8_t kDexCacheLoadRawCode[]; + static const ArrayRef<const uint8_t> kDexCacheLoadCode; + + uint32_t GetMethodOffset(uint32_t method_idx) { + auto result = method_offset_map_.FindMethodOffset(MethodRef(method_idx)); + CHECK(result.first); + return result.second; + } +}; + +const uint8_t X86_64RelativePatcherTest::kCallRawCode[] = { + 0xe8, 0x00, 0x01, 0x00, 0x00 +}; + +const ArrayRef<const uint8_t> X86_64RelativePatcherTest::kCallCode(kCallRawCode); + +const uint8_t X86_64RelativePatcherTest::kDexCacheLoadRawCode[] = { + 0x8b, 0x05, // mov eax, [rip + <offset>] + 0x00, 0x01, 0x00, 0x00 +}; + +const ArrayRef<const uint8_t> X86_64RelativePatcherTest::kDexCacheLoadCode( + kDexCacheLoadRawCode); + +TEST_F(X86_64RelativePatcherTest, CallSelf) { + LinkerPatch patches[] = { + LinkerPatch::RelativeCodePatch(kCallCode.size() - 4u, nullptr, 1u), + }; + AddCompiledMethod(MethodRef(1u), kCallCode, ArrayRef<LinkerPatch>(patches)); + Link(); + + static const uint8_t expected_code[] = { + 0xe8, 0xfb, 0xff, 0xff, 0xff + }; + EXPECT_TRUE(CheckLinkedMethod(MethodRef(1u), ArrayRef<const uint8_t>(expected_code))); +} + +TEST_F(X86_64RelativePatcherTest, CallOther) { + LinkerPatch method1_patches[] = { + LinkerPatch::RelativeCodePatch(kCallCode.size() - 4u, nullptr, 2u), + }; + AddCompiledMethod(MethodRef(1u), kCallCode, ArrayRef<LinkerPatch>(method1_patches)); + LinkerPatch method2_patches[] = { + LinkerPatch::RelativeCodePatch(kCallCode.size() - 4u, nullptr, 1u), + }; + AddCompiledMethod(MethodRef(2u), kCallCode, ArrayRef<LinkerPatch>(method2_patches)); + Link(); + + uint32_t method1_offset = GetMethodOffset(1u); + uint32_t method2_offset = GetMethodOffset(2u); + uint32_t diff_after = method2_offset - (method1_offset + kCallCode.size() /* PC adjustment */); + static const uint8_t method1_expected_code[] = { + 0xe8, + static_cast<uint8_t>(diff_after), static_cast<uint8_t>(diff_after >> 8), + static_cast<uint8_t>(diff_after >> 16), static_cast<uint8_t>(diff_after >> 24) + }; + EXPECT_TRUE(CheckLinkedMethod(MethodRef(1u), ArrayRef<const uint8_t>(method1_expected_code))); + uint32_t diff_before = method1_offset - (method2_offset + kCallCode.size() /* PC adjustment */); + static const uint8_t method2_expected_code[] = { + 0xe8, + static_cast<uint8_t>(diff_before), static_cast<uint8_t>(diff_before >> 8), + static_cast<uint8_t>(diff_before >> 16), static_cast<uint8_t>(diff_before >> 24) + }; + EXPECT_TRUE(CheckLinkedMethod(MethodRef(2u), ArrayRef<const uint8_t>(method2_expected_code))); +} + +TEST_F(X86_64RelativePatcherTest, CallTrampoline) { + LinkerPatch patches[] = { + LinkerPatch::RelativeCodePatch(kCallCode.size() - 4u, nullptr, 2u), + }; + AddCompiledMethod(MethodRef(1u), kCallCode, ArrayRef<LinkerPatch>(patches)); + Link(); + + auto result = method_offset_map_.FindMethodOffset(MethodRef(1u)); + ASSERT_TRUE(result.first); + uint32_t diff = kTrampolineOffset - (result.second + kCallCode.size()); + static const uint8_t expected_code[] = { + 0xe8, + static_cast<uint8_t>(diff), static_cast<uint8_t>(diff >> 8), + static_cast<uint8_t>(diff >> 16), static_cast<uint8_t>(diff >> 24) + }; + EXPECT_TRUE(CheckLinkedMethod(MethodRef(1u), ArrayRef<const uint8_t>(expected_code))); +} + +TEST_F(X86_64RelativePatcherTest, DexCacheReference) { + dex_cache_arrays_begin_ = 0x12345678; + constexpr size_t kElementOffset = 0x1234; + LinkerPatch patches[] = { + LinkerPatch::DexCacheArrayPatch(kDexCacheLoadCode.size() - 4u, nullptr, 0u, kElementOffset), + }; + AddCompiledMethod(MethodRef(1u), kDexCacheLoadCode, ArrayRef<LinkerPatch>(patches)); + Link(); + + auto result = method_offset_map_.FindMethodOffset(MethodRef(1u)); + ASSERT_TRUE(result.first); + uint32_t diff = + dex_cache_arrays_begin_ + kElementOffset - (result.second + kDexCacheLoadCode.size()); + static const uint8_t expected_code[] = { + 0x8b, 0x05, + static_cast<uint8_t>(diff), static_cast<uint8_t>(diff >> 8), + static_cast<uint8_t>(diff >> 16), static_cast<uint8_t>(diff >> 24) + }; + EXPECT_TRUE(CheckLinkedMethod(MethodRef(1u), ArrayRef<const uint8_t>(expected_code))); +} + +} // namespace linker +} // namespace art diff --git a/compiler/oat_writer.cc b/compiler/oat_writer.cc index 04f0db6572..19013cf7a1 100644 --- a/compiler/oat_writer.cc +++ b/compiler/oat_writer.cc @@ -32,6 +32,7 @@ #include "driver/compiler_options.h" #include "gc/space/space.h" #include "image_writer.h" +#include "linker/relative_patcher.h" #include "mirror/art_method-inl.h" #include "mirror/array.h" #include "mirror/class_loader.h" @@ -41,609 +42,10 @@ #include "safe_map.h" #include "scoped_thread_state_change.h" #include "handle_scope-inl.h" -#include "utils/arm/assembler_thumb2.h" -#include "utils/arm64/assembler_arm64.h" #include "verifier/method_verifier.h" namespace art { -class OatWriter::RelativePatcher { - public: - virtual ~RelativePatcher() { } - - // Reserve space for relative call thunks if needed, return adjusted offset. After all methods - // of a class have been processed it's called one last time with compiled_method == nullptr. - virtual uint32_t ReserveSpace(uint32_t offset, const CompiledMethod* compiled_method) = 0; - - // Write relative call thunks if needed, return adjusted offset. - virtual uint32_t WriteThunks(OutputStream* out, uint32_t offset) = 0; - - // Patch method code. The input displacement is relative to the patched location, - // the patcher may need to adjust it if the correct base is different. - virtual void PatchCall(std::vector<uint8_t>* code, uint32_t literal_offset, - uint32_t patch_offset, uint32_t target_offset) = 0; - - // Patch a reference to a dex cache location. - virtual void PatchDexCacheReference(std::vector<uint8_t>* code, const LinkerPatch& patch, - uint32_t patch_offset, uint32_t target_offset) = 0; - - protected: - RelativePatcher() { } - - private: - DISALLOW_COPY_AND_ASSIGN(RelativePatcher); -}; - -class OatWriter::NoRelativePatcher FINAL : public RelativePatcher { - public: - NoRelativePatcher() { } - - uint32_t ReserveSpace(uint32_t offset, - const CompiledMethod* compiled_method ATTRIBUTE_UNUSED) OVERRIDE { - return offset; // No space reserved; no patches expected. - } - - uint32_t WriteThunks(OutputStream* out ATTRIBUTE_UNUSED, uint32_t offset) OVERRIDE { - return offset; // No thunks added; no patches expected. - } - - void PatchCall(std::vector<uint8_t>* code ATTRIBUTE_UNUSED, - uint32_t literal_offset ATTRIBUTE_UNUSED, - uint32_t patch_offset ATTRIBUTE_UNUSED, - uint32_t target_offset ATTRIBUTE_UNUSED) OVERRIDE { - LOG(FATAL) << "Unexpected relative call patch."; - } - - virtual void PatchDexCacheReference(std::vector<uint8_t>* code ATTRIBUTE_UNUSED, - const LinkerPatch& patch ATTRIBUTE_UNUSED, - uint32_t patch_offset ATTRIBUTE_UNUSED, - uint32_t target_offset ATTRIBUTE_UNUSED) { - LOG(FATAL) << "Unexpected relative dex cache array patch."; - } - - private: - DISALLOW_COPY_AND_ASSIGN(NoRelativePatcher); -}; - -class OatWriter::X86RelativePatcher FINAL : public RelativePatcher { - public: - X86RelativePatcher() { } - - uint32_t ReserveSpace(uint32_t offset, - const CompiledMethod* compiled_method ATTRIBUTE_UNUSED) OVERRIDE { - return offset; // No space reserved; no limit on relative call distance. - } - - uint32_t WriteThunks(OutputStream* out ATTRIBUTE_UNUSED, uint32_t offset) OVERRIDE { - return offset; // No thunks added; no limit on relative call distance. - } - - void PatchCall(std::vector<uint8_t>* code, uint32_t literal_offset, - uint32_t patch_offset, uint32_t target_offset) OVERRIDE { - DCHECK_LE(literal_offset + 4u, code->size()); - // Unsigned arithmetic with its well-defined overflow behavior is just fine here. - uint32_t displacement = target_offset - patch_offset; - displacement -= kPcDisplacement; // The base PC is at the end of the 4-byte patch. - - typedef __attribute__((__aligned__(1))) int32_t unaligned_int32_t; - reinterpret_cast<unaligned_int32_t*>(&(*code)[literal_offset])[0] = displacement; - } - - virtual void PatchDexCacheReference(std::vector<uint8_t>* code ATTRIBUTE_UNUSED, - const LinkerPatch& patch ATTRIBUTE_UNUSED, - uint32_t patch_offset ATTRIBUTE_UNUSED, - uint32_t target_offset ATTRIBUTE_UNUSED) { - LOG(FATAL) << "Unexpected relative dex cache array patch."; - } - - private: - // PC displacement from patch location; x86 PC for relative calls points to the next - // instruction and the patch location is 4 bytes earlier. - static constexpr int32_t kPcDisplacement = 4; - - DISALLOW_COPY_AND_ASSIGN(X86RelativePatcher); -}; - -class OatWriter::ArmBaseRelativePatcher : public RelativePatcher { - public: - ArmBaseRelativePatcher(OatWriter* writer, - InstructionSet instruction_set, std::vector<uint8_t> thunk_code, - uint32_t max_positive_displacement, uint32_t max_negative_displacement) - : writer_(writer), instruction_set_(instruction_set), thunk_code_(thunk_code), - max_positive_displacement_(max_positive_displacement), - max_negative_displacement_(max_negative_displacement), - thunk_locations_(), current_thunk_to_write_(0u), unprocessed_patches_() { - } - - uint32_t ReserveSpace(uint32_t offset, const CompiledMethod* compiled_method) OVERRIDE { - return ReserveSpaceInternal(offset, compiled_method, 0u); - } - - uint32_t WriteThunks(OutputStream* out, uint32_t offset) OVERRIDE { - if (current_thunk_to_write_ == thunk_locations_.size()) { - return offset; - } - uint32_t aligned_offset = CompiledMethod::AlignCode(offset, instruction_set_); - if (UNLIKELY(aligned_offset == thunk_locations_[current_thunk_to_write_])) { - ++current_thunk_to_write_; - uint32_t aligned_code_delta = aligned_offset - offset; - if (aligned_code_delta != 0u && !writer_->WriteCodeAlignment(out, aligned_code_delta)) { - return 0u; - } - if (!out->WriteFully(thunk_code_.data(), thunk_code_.size())) { - return 0u; - } - writer_->size_relative_call_thunks_ += thunk_code_.size(); - uint32_t thunk_end_offset = aligned_offset + thunk_code_.size(); - // Align after writing chunk, see the ReserveSpace() above. - offset = CompiledMethod::AlignCode(thunk_end_offset, instruction_set_); - aligned_code_delta = offset - thunk_end_offset; - if (aligned_code_delta != 0u && !writer_->WriteCodeAlignment(out, aligned_code_delta)) { - return 0u; - } - } - return offset; - } - - protected: - uint32_t ReserveSpaceInternal(uint32_t offset, const CompiledMethod* compiled_method, - uint32_t max_extra_space) { - // NOTE: The final thunk can be reserved from InitCodeMethodVisitor::EndClass() while it - // may be written early by WriteCodeMethodVisitor::VisitMethod() for a deduplicated chunk - // of code. To avoid any alignment discrepancies for the final chunk, we always align the - // offset after reserving of writing any chunk. - if (UNLIKELY(compiled_method == nullptr)) { - uint32_t aligned_offset = CompiledMethod::AlignCode(offset, instruction_set_); - bool needs_thunk = ReserveSpaceProcessPatches(aligned_offset); - if (needs_thunk) { - thunk_locations_.push_back(aligned_offset); - offset = CompiledMethod::AlignCode(aligned_offset + thunk_code_.size(), instruction_set_); - } - return offset; - } - DCHECK(compiled_method->GetQuickCode() != nullptr); - uint32_t quick_code_size = compiled_method->GetQuickCode()->size(); - uint32_t quick_code_offset = compiled_method->AlignCode(offset) + sizeof(OatQuickMethodHeader); - uint32_t next_aligned_offset = compiled_method->AlignCode(quick_code_offset + quick_code_size); - // Adjust for extra space required by the subclass. - next_aligned_offset = compiled_method->AlignCode(next_aligned_offset + max_extra_space); - if (!unprocessed_patches_.empty() && - next_aligned_offset - unprocessed_patches_.front().second > max_positive_displacement_) { - bool needs_thunk = ReserveSpaceProcessPatches(next_aligned_offset); - if (needs_thunk) { - // A single thunk will cover all pending patches. - unprocessed_patches_.clear(); - uint32_t thunk_location = compiled_method->AlignCode(offset); - thunk_locations_.push_back(thunk_location); - offset = CompiledMethod::AlignCode(thunk_location + thunk_code_.size(), instruction_set_); - } - } - for (const LinkerPatch& patch : compiled_method->GetPatches()) { - if (patch.Type() == kLinkerPatchCallRelative) { - unprocessed_patches_.emplace_back(patch.TargetMethod(), - quick_code_offset + patch.LiteralOffset()); - } - } - return offset; - } - - uint32_t CalculateDisplacement(uint32_t patch_offset, uint32_t target_offset) { - // Unsigned arithmetic with its well-defined overflow behavior is just fine here. - uint32_t displacement = target_offset - patch_offset; - // NOTE: With unsigned arithmetic we do mean to use && rather than || below. - if (displacement > max_positive_displacement_ && displacement < -max_negative_displacement_) { - // Unwritten thunks have higher offsets, check if it's within range. - DCHECK(current_thunk_to_write_ == thunk_locations_.size() || - thunk_locations_[current_thunk_to_write_] > patch_offset); - if (current_thunk_to_write_ != thunk_locations_.size() && - thunk_locations_[current_thunk_to_write_] - patch_offset < max_positive_displacement_) { - displacement = thunk_locations_[current_thunk_to_write_] - patch_offset; - } else { - // We must have a previous thunk then. - DCHECK_NE(current_thunk_to_write_, 0u); - DCHECK_LT(thunk_locations_[current_thunk_to_write_ - 1], patch_offset); - displacement = thunk_locations_[current_thunk_to_write_ - 1] - patch_offset; - DCHECK(displacement >= -max_negative_displacement_); - } - } - return displacement; - } - - OatWriter* Writer() const { - return writer_; - } - - private: - bool ReserveSpaceProcessPatches(uint32_t next_aligned_offset) { - // Process as many patches as possible, stop only on unresolved targets or calls too far back. - while (!unprocessed_patches_.empty()) { - uint32_t patch_offset = unprocessed_patches_.front().second; - auto it = writer_->method_offset_map_.find(unprocessed_patches_.front().first); - if (it == writer_->method_offset_map_.end()) { - // If still unresolved, check if we have a thunk within range. - DCHECK(thunk_locations_.empty() || thunk_locations_.back() <= patch_offset); - if (thunk_locations_.empty() || - patch_offset - thunk_locations_.back() > max_negative_displacement_) { - return next_aligned_offset - patch_offset > max_positive_displacement_; - } - } else if (it->second >= patch_offset) { - DCHECK_LE(it->second - patch_offset, max_positive_displacement_); - } else { - // When calling back, check if we have a thunk that's closer than the actual target. - uint32_t target_offset = (thunk_locations_.empty() || it->second > thunk_locations_.back()) - ? it->second - : thunk_locations_.back(); - DCHECK_GT(patch_offset, target_offset); - if (patch_offset - target_offset > max_negative_displacement_) { - return true; - } - } - unprocessed_patches_.pop_front(); - } - return false; - } - - OatWriter* const writer_; - const InstructionSet instruction_set_; - const std::vector<uint8_t> thunk_code_; - const uint32_t max_positive_displacement_; - const uint32_t max_negative_displacement_; - std::vector<uint32_t> thunk_locations_; - size_t current_thunk_to_write_; - - // ReserveSpace() tracks unprocessed patches. - typedef std::pair<MethodReference, uint32_t> UnprocessedPatch; - std::deque<UnprocessedPatch> unprocessed_patches_; - - DISALLOW_COPY_AND_ASSIGN(ArmBaseRelativePatcher); -}; - -class OatWriter::Thumb2RelativePatcher FINAL : public ArmBaseRelativePatcher { - public: - explicit Thumb2RelativePatcher(OatWriter* writer) - : ArmBaseRelativePatcher(writer, kThumb2, CompileThunkCode(), - kMaxPositiveDisplacement, kMaxNegativeDisplacement) { - } - - void PatchCall(std::vector<uint8_t>* code, uint32_t literal_offset, - uint32_t patch_offset, uint32_t target_offset) OVERRIDE { - DCHECK_LE(literal_offset + 4u, code->size()); - DCHECK_EQ(literal_offset & 1u, 0u); - DCHECK_EQ(patch_offset & 1u, 0u); - DCHECK_EQ(target_offset & 1u, 1u); // Thumb2 mode bit. - uint32_t displacement = CalculateDisplacement(patch_offset, target_offset & ~1u); - displacement -= kPcDisplacement; // The base PC is at the end of the 4-byte patch. - DCHECK_EQ(displacement & 1u, 0u); - DCHECK((displacement >> 24) == 0u || (displacement >> 24) == 255u); // 25-bit signed. - uint32_t signbit = (displacement >> 31) & 0x1; - uint32_t i1 = (displacement >> 23) & 0x1; - uint32_t i2 = (displacement >> 22) & 0x1; - uint32_t imm10 = (displacement >> 12) & 0x03ff; - uint32_t imm11 = (displacement >> 1) & 0x07ff; - uint32_t j1 = i1 ^ (signbit ^ 1); - uint32_t j2 = i2 ^ (signbit ^ 1); - uint32_t value = (signbit << 26) | (j1 << 13) | (j2 << 11) | (imm10 << 16) | imm11; - value |= 0xf000d000; // BL - - uint8_t* addr = &(*code)[literal_offset]; - // Check that we're just overwriting an existing BL. - DCHECK_EQ(addr[1] & 0xf8, 0xf0); - DCHECK_EQ(addr[3] & 0xd0, 0xd0); - // Write the new BL. - addr[0] = (value >> 16) & 0xff; - addr[1] = (value >> 24) & 0xff; - addr[2] = (value >> 0) & 0xff; - addr[3] = (value >> 8) & 0xff; - } - - virtual void PatchDexCacheReference(std::vector<uint8_t>* code ATTRIBUTE_UNUSED, - const LinkerPatch& patch ATTRIBUTE_UNUSED, - uint32_t patch_offset ATTRIBUTE_UNUSED, - uint32_t target_offset ATTRIBUTE_UNUSED) { - LOG(FATAL) << "Unexpected relative dex cache array patch."; - } - - private: - static std::vector<uint8_t> CompileThunkCode() { - // The thunk just uses the entry point in the ArtMethod. This works even for calls - // to the generic JNI and interpreter trampolines. - arm::Thumb2Assembler assembler; - assembler.LoadFromOffset( - arm::kLoadWord, arm::PC, arm::R0, - mirror::ArtMethod::EntryPointFromQuickCompiledCodeOffset(kArmPointerSize).Int32Value()); - assembler.bkpt(0); - std::vector<uint8_t> thunk_code(assembler.CodeSize()); - MemoryRegion code(thunk_code.data(), thunk_code.size()); - assembler.FinalizeInstructions(code); - return thunk_code; - } - - // PC displacement from patch location; Thumb2 PC is always at instruction address + 4. - static constexpr int32_t kPcDisplacement = 4; - - // Maximum positive and negative displacement measured from the patch location. - // (Signed 25 bit displacement with the last bit 0 has range [-2^24, 2^24-2] measured from - // the Thumb2 PC pointing right after the BL, i.e. 4 bytes later than the patch location.) - static constexpr uint32_t kMaxPositiveDisplacement = (1u << 24) - 2 + kPcDisplacement; - static constexpr uint32_t kMaxNegativeDisplacement = (1u << 24) - kPcDisplacement; - - DISALLOW_COPY_AND_ASSIGN(Thumb2RelativePatcher); -}; - -class OatWriter::Arm64RelativePatcher FINAL : public ArmBaseRelativePatcher { - public: - explicit Arm64RelativePatcher(OatWriter* writer) - : ArmBaseRelativePatcher(writer, kArm64, CompileThunkCode(), - kMaxPositiveDisplacement, kMaxNegativeDisplacement), - fix_cortex_a53_843419_(writer->compiler_driver_->GetInstructionSetFeatures() - ->AsArm64InstructionSetFeatures()->NeedFixCortexA53_835769()), - reserved_adrp_thunks_(0u), - processed_adrp_thunks_(0u) { - if (fix_cortex_a53_843419_) { - adrp_thunk_locations_.reserve(16u); - current_method_thunks_.reserve(16u * kAdrpThunkSize); - } - } - - uint32_t ReserveSpace(uint32_t offset, const CompiledMethod* compiled_method) OVERRIDE { - if (!fix_cortex_a53_843419_) { - DCHECK(adrp_thunk_locations_.empty()); - return ReserveSpaceInternal(offset, compiled_method, 0u); - } - - // Add thunks for previous method if any. - if (reserved_adrp_thunks_ != adrp_thunk_locations_.size()) { - size_t num_adrp_thunks = adrp_thunk_locations_.size() - reserved_adrp_thunks_; - offset = CompiledMethod::AlignCode(offset, kArm64) + kAdrpThunkSize * num_adrp_thunks; - reserved_adrp_thunks_ = adrp_thunk_locations_.size(); - } - - // Count the number of ADRP insns as the upper bound on the number of thunks needed - // and use it to reserve space for other linker patches. - size_t num_adrp = 0u; - if (LIKELY(compiled_method != nullptr)) { - for (const LinkerPatch& patch : compiled_method->GetPatches()) { - if (patch.Type() == kLinkerPatchDexCacheArray && - patch.LiteralOffset() == patch.PcInsnOffset()) { // ADRP patch - ++num_adrp; - } - } - } - offset = ReserveSpaceInternal(offset, compiled_method, kAdrpThunkSize * num_adrp); - if (num_adrp == 0u) { - return offset; - } - - // Now that we have the actual offset where the code will be placed, locate the ADRP insns - // that actually require the thunk. - uint32_t quick_code_offset = compiled_method->AlignCode(offset) + sizeof(OatQuickMethodHeader); - ArrayRef<const uint8_t> code(*compiled_method->GetQuickCode()); - uint32_t thunk_offset = compiled_method->AlignCode(quick_code_offset + code.size()); - DCHECK(compiled_method != nullptr); - for (const LinkerPatch& patch : compiled_method->GetPatches()) { - if (patch.Type() == kLinkerPatchDexCacheArray && - patch.LiteralOffset() == patch.PcInsnOffset()) { // ADRP patch - uint32_t patch_offset = quick_code_offset + patch.LiteralOffset(); - if (NeedsErratum843419Thunk(code, patch.LiteralOffset(), patch_offset)) { - adrp_thunk_locations_.emplace_back(patch_offset, thunk_offset); - thunk_offset += kAdrpThunkSize; - } - } - } - return offset; - } - - uint32_t WriteThunks(OutputStream* out, uint32_t offset) OVERRIDE { - if (fix_cortex_a53_843419_) { - if (!current_method_thunks_.empty()) { - uint32_t aligned_offset = CompiledMethod::AlignCode(offset, kArm64); - if (kIsDebugBuild) { - CHECK(IsAligned<kAdrpThunkSize>(current_method_thunks_.size())); - size_t num_thunks = current_method_thunks_.size() / kAdrpThunkSize; - CHECK_LE(num_thunks, processed_adrp_thunks_); - for (size_t i = 0u; i != num_thunks; ++i) { - const auto& entry = adrp_thunk_locations_[processed_adrp_thunks_ - num_thunks + i]; - CHECK_EQ(entry.second, aligned_offset + i * kAdrpThunkSize); - } - } - uint32_t aligned_code_delta = aligned_offset - offset; - if (aligned_code_delta != 0u && !Writer()->WriteCodeAlignment(out, aligned_code_delta)) { - return 0u; - } - if (!out->WriteFully(¤t_method_thunks_[0], current_method_thunks_.size())) { - return 0u; - } - Writer()->size_misc_thunks_ += current_method_thunks_.size(); - offset = aligned_offset + current_method_thunks_.size(); - current_method_thunks_.clear(); - } - } - return ArmBaseRelativePatcher::WriteThunks(out, offset); - } - - void PatchCall(std::vector<uint8_t>* code, uint32_t literal_offset, - uint32_t patch_offset, uint32_t target_offset) OVERRIDE { - DCHECK_LE(literal_offset + 4u, code->size()); - DCHECK_EQ(literal_offset & 3u, 0u); - DCHECK_EQ(patch_offset & 3u, 0u); - DCHECK_EQ(target_offset & 3u, 0u); - uint32_t displacement = CalculateDisplacement(patch_offset, target_offset & ~1u); - DCHECK_EQ(displacement & 3u, 0u); - DCHECK((displacement >> 27) == 0u || (displacement >> 27) == 31u); // 28-bit signed. - uint32_t insn = (displacement & 0x0fffffffu) >> 2; - insn |= 0x94000000; // BL - - // Check that we're just overwriting an existing BL. - DCHECK_EQ(GetInsn(code, literal_offset) & 0xfc000000u, 0x94000000u); - // Write the new BL. - SetInsn(code, literal_offset, insn); - } - - virtual void PatchDexCacheReference(std::vector<uint8_t>* code ATTRIBUTE_UNUSED, - const LinkerPatch& patch ATTRIBUTE_UNUSED, - uint32_t patch_offset ATTRIBUTE_UNUSED, - uint32_t target_offset ATTRIBUTE_UNUSED) { - DCHECK_EQ(patch_offset & 3u, 0u); - DCHECK_EQ(target_offset & 3u, 0u); - uint32_t literal_offset = patch.LiteralOffset(); - uint32_t insn = GetInsn(code, literal_offset); - uint32_t pc_insn_offset = patch.PcInsnOffset(); - uint32_t disp = target_offset - ((patch_offset - literal_offset + pc_insn_offset) & ~0xfffu); - if (literal_offset == pc_insn_offset) { - // Check it's an ADRP with imm == 0 (unset). - DCHECK_EQ((insn & 0xffffffe0u), 0x90000000u) - << literal_offset << ", " << pc_insn_offset << ", 0x" << std::hex << insn; - if (fix_cortex_a53_843419_ && processed_adrp_thunks_ != adrp_thunk_locations_.size() && - adrp_thunk_locations_[processed_adrp_thunks_].first == patch_offset) { - DCHECK(NeedsErratum843419Thunk(ArrayRef<const uint8_t>(*code), - literal_offset, patch_offset)); - uint32_t thunk_offset = adrp_thunk_locations_[processed_adrp_thunks_].second; - uint32_t adrp_disp = target_offset - (thunk_offset & ~0xfffu); - uint32_t adrp = PatchAdrp(insn, adrp_disp); - - uint32_t out_disp = thunk_offset - patch_offset; - DCHECK_EQ(out_disp & 3u, 0u); - DCHECK((out_disp >> 27) == 0u || (out_disp >> 27) == 31u); // 28-bit signed. - insn = (out_disp & 0x0fffffffu) >> 2; - insn |= 0x14000000; // B <thunk> - - uint32_t back_disp = -out_disp; - DCHECK_EQ(back_disp & 3u, 0u); - DCHECK((back_disp >> 27) == 0u || (back_disp >> 27) == 31u); // 28-bit signed. - uint32_t b_back = (back_disp & 0x0fffffffu) >> 2; - b_back |= 0x14000000; // B <back> - size_t thunks_code_offset = current_method_thunks_.size(); - current_method_thunks_.resize(thunks_code_offset + kAdrpThunkSize); - SetInsn(¤t_method_thunks_, thunks_code_offset, adrp); - SetInsn(¤t_method_thunks_, thunks_code_offset + 4u, b_back); - static_assert(kAdrpThunkSize == 2 * 4u, "thunk has 2 instructions"); - - processed_adrp_thunks_ += 1u; - } else { - insn = PatchAdrp(insn, disp); - } - // Write the new ADRP (or B to the erratum 843419 thunk). - SetInsn(code, literal_offset, insn); - } else { - DCHECK_EQ(insn & 0xfffffc00, 0xb9400000); // LDR 32-bit with imm12 == 0 (unset). - if (kIsDebugBuild) { - uint32_t adrp = GetInsn(code, pc_insn_offset); - if ((adrp & 0x9f000000u) != 0x90000000u) { - CHECK(fix_cortex_a53_843419_); - CHECK_EQ(adrp & 0xfc000000u, 0x14000000u); // B <thunk> - CHECK(IsAligned<kAdrpThunkSize>(current_method_thunks_.size())); - size_t num_thunks = current_method_thunks_.size() / kAdrpThunkSize; - CHECK_LE(num_thunks, processed_adrp_thunks_); - uint32_t b_offset = patch_offset - literal_offset + pc_insn_offset; - for (size_t i = processed_adrp_thunks_ - num_thunks; ; ++i) { - CHECK_NE(i, processed_adrp_thunks_); - if (adrp_thunk_locations_[i].first == b_offset) { - size_t idx = num_thunks - (processed_adrp_thunks_ - i); - adrp = GetInsn(¤t_method_thunks_, idx * kAdrpThunkSize); - break; - } - } - } - CHECK_EQ(adrp & 0x9f00001fu, // Check that pc_insn_offset points - 0x90000000 | ((insn >> 5) & 0x1fu)); // to ADRP with matching register. - } - uint32_t imm12 = (disp & 0xfffu) >> 2; - insn = (insn & ~(0xfffu << 10)) | (imm12 << 10); - SetInsn(code, literal_offset, insn); - } - } - - private: - static uint32_t PatchAdrp(uint32_t adrp, uint32_t disp) { - return (adrp & 0x9f00001fu) | // Clear offset bits, keep ADRP with destination reg. - // Bottom 12 bits are ignored, the next 2 lowest bits are encoded in bits 29-30. - ((disp & 0x00003000u) << (29 - 12)) | - // The next 16 bits are encoded in bits 5-22. - ((disp & 0xffffc000u) >> (12 + 2 - 5)) | - // Since the target_offset is based on the beginning of the oat file and the - // image space precedes the oat file, the target_offset into image space will - // be negative yet passed as uint32_t. Therefore we limit the displacement - // to +-2GiB (rather than the maximim +-4GiB) and determine the sign bit from - // the highest bit of the displacement. This is encoded in bit 23. - ((disp & 0x80000000u) >> (31 - 23)); - } - - static std::vector<uint8_t> CompileThunkCode() { - // The thunk just uses the entry point in the ArtMethod. This works even for calls - // to the generic JNI and interpreter trampolines. - arm64::Arm64Assembler assembler; - Offset offset(mirror::ArtMethod::EntryPointFromQuickCompiledCodeOffset( - kArm64PointerSize).Int32Value()); - assembler.JumpTo(ManagedRegister(arm64::X0), offset, ManagedRegister(arm64::IP0)); - // Ensure we emit the literal pool. - assembler.EmitSlowPaths(); - std::vector<uint8_t> thunk_code(assembler.CodeSize()); - MemoryRegion code(thunk_code.data(), thunk_code.size()); - assembler.FinalizeInstructions(code); - return thunk_code; - } - - static bool NeedsErratum843419Thunk(ArrayRef<const uint8_t> code, uint32_t literal_offset, - uint32_t patch_offset) { - DCHECK_EQ(patch_offset & 0x3u, 0u); - if ((patch_offset & 0xff8) == 0xff8) { // ...ff8 or ...ffc - uint32_t adrp = GetInsn(code, literal_offset); - DCHECK_EQ(adrp & 0xff000000, 0x90000000); - // TODO: Improve the check. For now, we're just checking if the next insn is - // the LDR using the result of the ADRP, otherwise we implement the workaround. - uint32_t next_insn = GetInsn(code, literal_offset + 4u); - bool ok = (next_insn & 0xffc00000) == 0xb9400000 && // LDR <Wt>, [<Xn>, #pimm] - (((next_insn >> 5) ^ adrp) & 0x1f) == 0; // <Xn> == ADRP destination reg - return !ok; - } - return false; - } - - static uint32_t GetInsn(ArrayRef<const uint8_t> code, uint32_t offset) { - DCHECK_LE(offset + 4u, code.size()); - DCHECK_EQ(offset & 3u, 0u); - const uint8_t* addr = &code[offset]; - return - (static_cast<uint32_t>(addr[0]) << 0) + - (static_cast<uint32_t>(addr[1]) << 8) + - (static_cast<uint32_t>(addr[2]) << 16)+ - (static_cast<uint32_t>(addr[3]) << 24); - } - - template <typename Alloc> - static uint32_t GetInsn(std::vector<uint8_t, Alloc>* code, uint32_t offset) { - return GetInsn(ArrayRef<const uint8_t>(*code), offset); - } - - void SetInsn(std::vector<uint8_t>* code, uint32_t offset, uint32_t value) { - DCHECK_LE(offset + 4u, code->size()); - DCHECK_EQ(offset & 3u, 0u); - uint8_t* addr = &(*code)[offset]; - addr[0] = (value >> 0) & 0xff; - addr[1] = (value >> 8) & 0xff; - addr[2] = (value >> 16) & 0xff; - addr[3] = (value >> 24) & 0xff; - } - - // Maximum positive and negative displacement measured from the patch location. - // (Signed 28 bit displacement with the last bit 0 has range [-2^27, 2^27-4] measured from - // the ARM64 PC pointing to the BL.) - static constexpr uint32_t kMaxPositiveDisplacement = (1u << 27) - 4u; - static constexpr uint32_t kMaxNegativeDisplacement = (1u << 27); - - // The ADRP thunk for erratum 843419 is 2 instructions, i.e. 8 bytes. - static constexpr uint32_t kAdrpThunkSize = 8u; - - const bool fix_cortex_a53_843419_; - // Map original patch_offset to thunk offset. - std::vector<std::pair<uint32_t, uint32_t>> adrp_thunk_locations_; - size_t reserved_adrp_thunks_; - size_t processed_adrp_thunks_; - std::vector<uint8_t> current_method_thunks_; - - DISALLOW_COPY_AND_ASSIGN(Arm64RelativePatcher); -}; - #define DCHECK_OFFSET() \ DCHECK_EQ(static_cast<off_t>(file_offset + relative_offset), out->Seek(0, kSeekCurrent)) \ << "file_offset=" << file_offset << " relative_offset=" << relative_offset @@ -704,23 +106,10 @@ OatWriter::OatWriter(const std::vector<const DexFile*>& dex_files, method_offset_map_() { CHECK(key_value_store != nullptr); - switch (compiler_driver_->GetInstructionSet()) { - case kX86: - case kX86_64: - relative_patcher_.reset(new X86RelativePatcher); - break; - case kArm: - // Fall through: we generate Thumb2 code for "arm". - case kThumb2: - relative_patcher_.reset(new Thumb2RelativePatcher(this)); - break; - case kArm64: - relative_patcher_.reset(new Arm64RelativePatcher(this)); - break; - default: - relative_patcher_.reset(new NoRelativePatcher); - break; - } + InstructionSet instruction_set = compiler_driver_->GetInstructionSet(); + const InstructionSetFeatures* features = compiler_driver_->GetInstructionSetFeatures(); + relative_patcher_ = linker::RelativePatcher::Create(instruction_set, features, + &method_offset_map_); size_t offset; { @@ -968,7 +357,8 @@ class OatWriter::InitCodeMethodVisitor : public OatDexMethodVisitor { bool EndClass() { OatDexMethodVisitor::EndClass(); if (oat_class_index_ == writer_->oat_classes_.size()) { - offset_ = writer_->relative_patcher_->ReserveSpace(offset_, nullptr); + offset_ = writer_->relative_patcher_->ReserveSpace(offset_, nullptr, + MethodReference(nullptr, 0u)); } return true; } @@ -995,7 +385,8 @@ class OatWriter::InitCodeMethodVisitor : public OatDexMethodVisitor { quick_code_offset = lb->second; deduped = true; } else { - offset_ = writer_->relative_patcher_->ReserveSpace(offset_, compiled_method); + offset_ = writer_->relative_patcher_->ReserveSpace( + offset_, compiled_method, MethodReference(dex_file_, it.GetMemberIndex())); offset_ = compiled_method->AlignCode(offset_); DCHECK_ALIGNED_PARAM(offset_, GetInstructionSetAlignment(compiled_method->GetInstructionSet())); @@ -1004,15 +395,15 @@ class OatWriter::InitCodeMethodVisitor : public OatDexMethodVisitor { } MethodReference method_ref(dex_file_, it.GetMemberIndex()); - auto method_lb = writer_->method_offset_map_.lower_bound(method_ref); - if (method_lb != writer_->method_offset_map_.end() && - !writer_->method_offset_map_.key_comp()(method_ref, method_lb->first)) { + auto method_lb = writer_->method_offset_map_.map.lower_bound(method_ref); + if (method_lb != writer_->method_offset_map_.map.end() && + !writer_->method_offset_map_.map.key_comp()(method_ref, method_lb->first)) { // TODO: Should this be a hard failure? LOG(WARNING) << "Multiple definitions of " << PrettyMethod(method_ref.dex_method_index, *method_ref.dex_file) << ((method_lb->second != quick_code_offset) ? "; OFFSET MISMATCH" : ""); } else { - writer_->method_offset_map_.PutBefore(method_lb, method_ref, quick_code_offset); + writer_->method_offset_map_.map.PutBefore(method_lb, method_ref, quick_code_offset); } // Update quick method header. @@ -1069,10 +460,12 @@ class OatWriter::InitCodeMethodVisitor : public OatDexMethodVisitor { } const uint32_t quick_code_start = quick_code_offset - - writer_->oat_header_->GetExecutableOffset(); + writer_->oat_header_->GetExecutableOffset() - thumb_offset; const DexFile::CodeItem *code_item = it.GetMethodCodeItem(); - writer_->method_info_.push_back(DebugInfo(name, - dex_file_->GetSourceFile(dex_file_->GetClassDef(class_def_index_)), + const DexFile::ClassDef& class_def = dex_file_->GetClassDef(class_def_index_); + writer_->method_info_.push_back(DebugInfo(name, deduped, + dex_file_->GetClassDescriptor(class_def), + dex_file_->GetSourceFile(class_def), quick_code_start, quick_code_start + code_size, code_item == nullptr ? nullptr : dex_file_->GetDebugInfoStream(code_item), compiled_method)); @@ -1399,9 +792,9 @@ class OatWriter::WriteCodeMethodVisitor : public OatDexMethodVisitor { } uint32_t GetTargetOffset(const LinkerPatch& patch) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { - auto target_it = writer_->method_offset_map_.find(patch.TargetMethod()); + auto target_it = writer_->method_offset_map_.map.find(patch.TargetMethod()); uint32_t target_offset = - (target_it != writer_->method_offset_map_.end()) ? target_it->second : 0u; + (target_it != writer_->method_offset_map_.map.end()) ? target_it->second : 0u; // If there's no compiled code, point to the correct trampoline. if (UNLIKELY(target_offset == 0)) { mirror::ArtMethod* target = GetTargetMethod(patch); @@ -1940,6 +1333,10 @@ size_t OatWriter::WriteCodeDexFiles(OutputStream* out, #undef VISIT + size_code_alignment_ += relative_patcher_->CodeAlignmentSize(); + size_relative_call_thunks_ += relative_patcher_->RelativeCallThunksSize(); + size_misc_thunks_ += relative_patcher_->MiscThunksSize(); + return relative_offset; } @@ -1955,6 +1352,15 @@ bool OatWriter::WriteCodeAlignment(OutputStream* out, uint32_t aligned_code_delt return true; } +std::pair<bool, uint32_t> OatWriter::MethodOffsetMap::FindMethodOffset(MethodReference ref) { + auto it = map.find(ref); + if (it == map.end()) { + return std::pair<bool, uint32_t>(false, 0u); + } else { + return std::pair<bool, uint32_t>(true, it->second); + } +} + OatWriter::OatDexFile::OatDexFile(size_t offset, const DexFile& dex_file) { offset_ = offset; const std::string& location(dex_file.GetLocation()); diff --git a/compiler/oat_writer.h b/compiler/oat_writer.h index 676d628450..c472000f37 100644 --- a/compiler/oat_writer.h +++ b/compiler/oat_writer.h @@ -21,6 +21,7 @@ #include <cstddef> #include <memory> +#include "linker/relative_patcher.h" // For linker::RelativePatcherTargetProvider. #include "mem_map.h" #include "method_reference.h" #include "oat.h" @@ -114,14 +115,18 @@ class OatWriter { ~OatWriter(); struct DebugInfo { - DebugInfo(const std::string& method_name, const char* src_file_name, - uint32_t low_pc, uint32_t high_pc, const uint8_t* dbgstream, - CompiledMethod* compiled_method) - : method_name_(method_name), src_file_name_(src_file_name), - low_pc_(low_pc), high_pc_(high_pc), dbgstream_(dbgstream), - compiled_method_(compiled_method) { + DebugInfo(const std::string& method_name, bool deduped, + const char* class_descriptor, const char* src_file_name, + uint32_t low_pc, uint32_t high_pc, + const uint8_t* dbgstream, CompiledMethod* compiled_method) + : method_name_(method_name), deduped_(deduped), + class_descriptor_(class_descriptor), src_file_name_(src_file_name), + low_pc_(low_pc), high_pc_(high_pc), + dbgstream_(dbgstream), compiled_method_(compiled_method) { } std::string method_name_; // Note: this name is a pretty-printed name. + bool deduped_; + const char* class_descriptor_; const char* src_file_name_; uint32_t low_pc_; uint32_t high_pc_; @@ -133,6 +138,10 @@ class OatWriter { return method_info_; } + const CompilerDriver* GetCompilerDriver() { + return compiler_driver_; + } + private: // The DataAccess classes are helper classes that provide access to members related to // a given map, i.e. GC map, mapping table or vmap table. By abstracting these away @@ -327,19 +336,19 @@ class OatWriter { uint32_t size_oat_class_method_bitmaps_; uint32_t size_oat_class_method_offsets_; - class RelativePatcher; - class NoRelativePatcher; - class X86RelativePatcher; - class ArmBaseRelativePatcher; - class Thumb2RelativePatcher; - class Arm64RelativePatcher; - - std::unique_ptr<RelativePatcher> relative_patcher_; + std::unique_ptr<linker::RelativePatcher> relative_patcher_; // The locations of absolute patches relative to the start of the executable section. std::vector<uintptr_t> absolute_patch_locations_; - SafeMap<MethodReference, uint32_t, MethodReferenceComparator> method_offset_map_; + // Map method reference to assigned offset. + // Wrap the map in a class implementing linker::RelativePatcherTargetProvider. + class MethodOffsetMap FINAL : public linker::RelativePatcherTargetProvider { + public: + std::pair<bool, uint32_t> FindMethodOffset(MethodReference ref) OVERRIDE; + SafeMap<MethodReference, uint32_t, MethodReferenceComparator> map; + }; + MethodOffsetMap method_offset_map_; DISALLOW_COPY_AND_ASSIGN(OatWriter); }; |