/* * Copyright (C) 2014 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 "code_generator_arm.h" #include "arch/arm/instruction_set_features_arm.h" #include "art_method.h" #include "code_generator_utils.h" #include "compiled_method.h" #include "entrypoints/quick/quick_entrypoints.h" #include "gc/accounting/card_table.h" #include "intrinsics.h" #include "intrinsics_arm.h" #include "mirror/array-inl.h" #include "mirror/class-inl.h" #include "thread.h" #include "utils/arm/assembler_arm.h" #include "utils/arm/managed_register_arm.h" #include "utils/assembler.h" #include "utils/stack_checks.h" namespace art { namespace arm { static bool ExpectedPairLayout(Location location) { // We expected this for both core and fpu register pairs. return ((location.low() & 1) == 0) && (location.low() + 1 == location.high()); } static constexpr int kCurrentMethodStackOffset = 0; static constexpr Register kMethodRegisterArgument = R0; // We unconditionally allocate R5 to ensure we can do long operations // with baseline. static constexpr Register kCoreSavedRegisterForBaseline = R5; static constexpr Register kCoreCalleeSaves[] = { R5, R6, R7, R8, R10, R11, LR }; static constexpr SRegister kFpuCalleeSaves[] = { S16, S17, S18, S19, S20, S21, S22, S23, S24, S25, S26, S27, S28, S29, S30, S31 }; // D31 cannot be split into two S registers, and the register allocator only works on // S registers. Therefore there is no need to block it. static constexpr DRegister DTMP = D31; #define __ down_cast(codegen->GetAssembler())-> #define QUICK_ENTRY_POINT(x) QUICK_ENTRYPOINT_OFFSET(kArmWordSize, x).Int32Value() class NullCheckSlowPathARM : public SlowPathCode { public: explicit NullCheckSlowPathARM(HNullCheck* instruction) : instruction_(instruction) {} void EmitNativeCode(CodeGenerator* codegen) OVERRIDE { CodeGeneratorARM* arm_codegen = down_cast(codegen); __ Bind(GetEntryLabel()); if (instruction_->CanThrowIntoCatchBlock()) { // Live registers will be restored in the catch block if caught. SaveLiveRegisters(codegen, instruction_->GetLocations()); } arm_codegen->InvokeRuntime( QUICK_ENTRY_POINT(pThrowNullPointer), instruction_, instruction_->GetDexPc(), this); } bool IsFatal() const OVERRIDE { return true; } const char* GetDescription() const OVERRIDE { return "NullCheckSlowPathARM"; } private: HNullCheck* const instruction_; DISALLOW_COPY_AND_ASSIGN(NullCheckSlowPathARM); }; class DivZeroCheckSlowPathARM : public SlowPathCode { public: explicit DivZeroCheckSlowPathARM(HDivZeroCheck* instruction) : instruction_(instruction) {} void EmitNativeCode(CodeGenerator* codegen) OVERRIDE { CodeGeneratorARM* arm_codegen = down_cast(codegen); __ Bind(GetEntryLabel()); if (instruction_->CanThrowIntoCatchBlock()) { // Live registers will be restored in the catch block if caught. SaveLiveRegisters(codegen, instruction_->GetLocations()); } arm_codegen->InvokeRuntime( QUICK_ENTRY_POINT(pThrowDivZero), instruction_, instruction_->GetDexPc(), this); } bool IsFatal() const OVERRIDE { return true; } const char* GetDescription() const OVERRIDE { return "DivZeroCheckSlowPathARM"; } private: HDivZeroCheck* const instruction_; DISALLOW_COPY_AND_ASSIGN(DivZeroCheckSlowPathARM); }; class SuspendCheckSlowPathARM : public SlowPathCode { public: SuspendCheckSlowPathARM(HSuspendCheck* instruction, HBasicBlock* successor) : instruction_(instruction), successor_(successor) {} void EmitNativeCode(CodeGenerator* codegen) OVERRIDE { CodeGeneratorARM* arm_codegen = down_cast(codegen); __ Bind(GetEntryLabel()); SaveLiveRegisters(codegen, instruction_->GetLocations()); arm_codegen->InvokeRuntime( QUICK_ENTRY_POINT(pTestSuspend), instruction_, instruction_->GetDexPc(), this); RestoreLiveRegisters(codegen, instruction_->GetLocations()); if (successor_ == nullptr) { __ b(GetReturnLabel()); } else { __ b(arm_codegen->GetLabelOf(successor_)); } } Label* GetReturnLabel() { DCHECK(successor_ == nullptr); return &return_label_; } HBasicBlock* GetSuccessor() const { return successor_; } const char* GetDescription() const OVERRIDE { return "SuspendCheckSlowPathARM"; } private: HSuspendCheck* const instruction_; // If not null, the block to branch to after the suspend check. HBasicBlock* const successor_; // If `successor_` is null, the label to branch to after the suspend check. Label return_label_; DISALLOW_COPY_AND_ASSIGN(SuspendCheckSlowPathARM); }; class BoundsCheckSlowPathARM : public SlowPathCode { public: explicit BoundsCheckSlowPathARM(HBoundsCheck* instruction) : instruction_(instruction) {} void EmitNativeCode(CodeGenerator* codegen) OVERRIDE { CodeGeneratorARM* arm_codegen = down_cast(codegen); LocationSummary* locations = instruction_->GetLocations(); __ Bind(GetEntryLabel()); if (instruction_->CanThrowIntoCatchBlock()) { // Live registers will be restored in the catch block if caught. SaveLiveRegisters(codegen, instruction_->GetLocations()); } // We're moving two locations to locations that could overlap, so we need a parallel // move resolver. InvokeRuntimeCallingConvention calling_convention; codegen->EmitParallelMoves( locations->InAt(0), Location::RegisterLocation(calling_convention.GetRegisterAt(0)), Primitive::kPrimInt, locations->InAt(1), Location::RegisterLocation(calling_convention.GetRegisterAt(1)), Primitive::kPrimInt); arm_codegen->InvokeRuntime( QUICK_ENTRY_POINT(pThrowArrayBounds), instruction_, instruction_->GetDexPc(), this); } bool IsFatal() const OVERRIDE { return true; } const char* GetDescription() const OVERRIDE { return "BoundsCheckSlowPathARM"; } private: HBoundsCheck* const instruction_; DISALLOW_COPY_AND_ASSIGN(BoundsCheckSlowPathARM); }; class LoadClassSlowPathARM : public SlowPathCode { public: LoadClassSlowPathARM(HLoadClass* cls, HInstruction* at, uint32_t dex_pc, bool do_clinit) : cls_(cls), at_(at), dex_pc_(dex_pc), do_clinit_(do_clinit) { DCHECK(at->IsLoadClass() || at->IsClinitCheck()); } void EmitNativeCode(CodeGenerator* codegen) OVERRIDE { LocationSummary* locations = at_->GetLocations(); CodeGeneratorARM* arm_codegen = down_cast(codegen); __ Bind(GetEntryLabel()); SaveLiveRegisters(codegen, locations); InvokeRuntimeCallingConvention calling_convention; __ LoadImmediate(calling_convention.GetRegisterAt(0), cls_->GetTypeIndex()); int32_t entry_point_offset = do_clinit_ ? QUICK_ENTRY_POINT(pInitializeStaticStorage) : QUICK_ENTRY_POINT(pInitializeType); arm_codegen->InvokeRuntime(entry_point_offset, at_, dex_pc_, this); // Move the class to the desired location. Location out = locations->Out(); if (out.IsValid()) { DCHECK(out.IsRegister() && !locations->GetLiveRegisters()->ContainsCoreRegister(out.reg())); arm_codegen->Move32(locations->Out(), Location::RegisterLocation(R0)); } RestoreLiveRegisters(codegen, locations); __ b(GetExitLabel()); } const char* GetDescription() const OVERRIDE { return "LoadClassSlowPathARM"; } private: // The class this slow path will load. HLoadClass* const cls_; // The instruction where this slow path is happening. // (Might be the load class or an initialization check). HInstruction* const at_; // The dex PC of `at_`. const uint32_t dex_pc_; // Whether to initialize the class. const bool do_clinit_; DISALLOW_COPY_AND_ASSIGN(LoadClassSlowPathARM); }; class LoadStringSlowPathARM : public SlowPathCode { public: explicit LoadStringSlowPathARM(HLoadString* instruction) : instruction_(instruction) {} void EmitNativeCode(CodeGenerator* codegen) OVERRIDE { LocationSummary* locations = instruction_->GetLocations(); DCHECK(!locations->GetLiveRegisters()->ContainsCoreRegister(locations->Out().reg())); CodeGeneratorARM* arm_codegen = down_cast(codegen); __ Bind(GetEntryLabel()); SaveLiveRegisters(codegen, locations); InvokeRuntimeCallingConvention calling_convention; __ LoadImmediate(calling_convention.GetRegisterAt(0), instruction_->GetStringIndex()); arm_codegen->InvokeRuntime( QUICK_ENTRY_POINT(pResolveString), instruction_, instruction_->GetDexPc(), this); arm_codegen->Move32(locations->Out(), Location::RegisterLocation(R0)); RestoreLiveRegisters(codegen, locations); __ b(GetExitLabel()); } const char* GetDescription() const OVERRIDE { return "LoadStringSlowPathARM"; } private: HLoadString* const instruction_; DISALLOW_COPY_AND_ASSIGN(LoadStringSlowPathARM); }; class TypeCheckSlowPathARM : public SlowPathCode { public: TypeCheckSlowPathARM(HInstruction* instruction, bool is_fatal) : instruction_(instruction), is_fatal_(is_fatal) {} void EmitNativeCode(CodeGenerator* codegen) OVERRIDE { LocationSummary* locations = instruction_->GetLocations(); Location object_class = instruction_->IsCheckCast() ? locations->GetTemp(0) : locations->Out(); DCHECK(instruction_->IsCheckCast() || !locations->GetLiveRegisters()->ContainsCoreRegister(locations->Out().reg())); CodeGeneratorARM* arm_codegen = down_cast(codegen); __ Bind(GetEntryLabel()); if (instruction_->IsCheckCast()) { // The codegen for the instruction overwrites `temp`, so put it back in place. Register obj = locations->InAt(0).AsRegister(); Register temp = locations->GetTemp(0).AsRegister(); uint32_t class_offset = mirror::Object::ClassOffset().Int32Value(); __ LoadFromOffset(kLoadWord, temp, obj, class_offset); __ MaybeUnpoisonHeapReference(temp); } if (!is_fatal_) { SaveLiveRegisters(codegen, locations); } // We're moving two locations to locations that could overlap, so we need a parallel // move resolver. InvokeRuntimeCallingConvention calling_convention; codegen->EmitParallelMoves( locations->InAt(1), Location::RegisterLocation(calling_convention.GetRegisterAt(0)), Primitive::kPrimNot, object_class, Location::RegisterLocation(calling_convention.GetRegisterAt(1)), Primitive::kPrimNot); if (instruction_->IsInstanceOf()) { arm_codegen->InvokeRuntime(QUICK_ENTRY_POINT(pInstanceofNonTrivial), instruction_, instruction_->GetDexPc(), this); arm_codegen->Move32(locations->Out(), Location::RegisterLocation(R0)); } else { DCHECK(instruction_->IsCheckCast()); arm_codegen->InvokeRuntime(QUICK_ENTRY_POINT(pCheckCast), instruction_, instruction_->GetDexPc(), this); } if (!is_fatal_) { RestoreLiveRegisters(codegen, locations); __ b(GetExitLabel()); } } const char* GetDescription() const OVERRIDE { return "TypeCheckSlowPathARM"; } bool IsFatal() const OVERRIDE { return is_fatal_; } private: HInstruction* const instruction_; const bool is_fatal_; DISALLOW_COPY_AND_ASSIGN(TypeCheckSlowPathARM); }; class DeoptimizationSlowPathARM : public SlowPathCode { public: explicit DeoptimizationSlowPathARM(HInstruction* instruction) : instruction_(instruction) {} void EmitNativeCode(CodeGenerator* codegen) OVERRIDE { __ Bind(GetEntryLabel()); SaveLiveRegisters(codegen, instruction_->GetLocations()); DCHECK(instruction_->IsDeoptimize()); HDeoptimize* deoptimize = instruction_->AsDeoptimize(); uint32_t dex_pc = deoptimize->GetDexPc(); CodeGeneratorARM* arm_codegen = down_cast(codegen); arm_codegen->InvokeRuntime(QUICK_ENTRY_POINT(pDeoptimize), instruction_, dex_pc, this); } const char* GetDescription() const OVERRIDE { return "DeoptimizationSlowPathARM"; } private: HInstruction* const instruction_; DISALLOW_COPY_AND_ASSIGN(DeoptimizationSlowPathARM); }; #undef __ #define __ down_cast(GetAssembler())-> inline Condition ARMSignedOrFPCondition(IfCondition cond) { switch (cond) { case kCondEQ: return EQ; case kCondNE: return NE; case kCondLT: return LT; case kCondLE: return LE; case kCondGT: return GT; case kCondGE: return GE; } LOG(FATAL) << "Unreachable"; UNREACHABLE(); } inline Condition ARMUnsignedCondition(IfCondition cond) { switch (cond) { case kCondEQ: return EQ; case kCondNE: return NE; case kCondLT: return LO; case kCondLE: return LS; case kCondGT: return HI; case kCondGE: return HS; } LOG(FATAL) << "Unreachable"; UNREACHABLE(); } void CodeGeneratorARM::DumpCoreRegister(std::ostream& stream, int reg) const { stream << Register(reg); } void CodeGeneratorARM::DumpFloatingPointRegister(std::ostream& stream, int reg) const { stream << SRegister(reg); } size_t CodeGeneratorARM::SaveCoreRegister(size_t stack_index, uint32_t reg_id) { __ StoreToOffset(kStoreWord, static_cast(reg_id), SP, stack_index); return kArmWordSize; } size_t CodeGeneratorARM::RestoreCoreRegister(size_t stack_index, uint32_t reg_id) { __ LoadFromOffset(kLoadWord, static_cast(reg_id), SP, stack_index); return kArmWordSize; } size_t CodeGeneratorARM::SaveFloatingPointRegister(size_t stack_index, uint32_t reg_id) { __ StoreSToOffset(static_cast(reg_id), SP, stack_index); return kArmWordSize; } size_t CodeGeneratorARM::RestoreFloatingPointRegister(size_t stack_index, uint32_t reg_id) { __ LoadSFromOffset(static_cast(reg_id), SP, stack_index); return kArmWordSize; } CodeGeneratorARM::CodeGeneratorARM(HGraph* graph, const ArmInstructionSetFeatures& isa_features, const CompilerOptions& compiler_options, OptimizingCompilerStats* stats) : CodeGenerator(graph, kNumberOfCoreRegisters, kNumberOfSRegisters, kNumberOfRegisterPairs, ComputeRegisterMask(reinterpret_cast(kCoreCalleeSaves), arraysize(kCoreCalleeSaves)), ComputeRegisterMask(reinterpret_cast(kFpuCalleeSaves), arraysize(kFpuCalleeSaves)), compiler_options, stats), block_labels_(graph->GetArena(), 0), location_builder_(graph, this), instruction_visitor_(graph, this), move_resolver_(graph->GetArena(), this), assembler_(), isa_features_(isa_features), method_patches_(MethodReferenceComparator(), graph->GetArena()->Adapter()), call_patches_(MethodReferenceComparator(), graph->GetArena()->Adapter()), relative_call_patches_(graph->GetArena()->Adapter()) { // Always save the LR register to mimic Quick. AddAllocatedRegister(Location::RegisterLocation(LR)); } void CodeGeneratorARM::Finalize(CodeAllocator* allocator) { // Ensure that we fix up branches and literal loads and emit the literal pool. __ FinalizeCode(); // Adjust native pc offsets in stack maps. for (size_t i = 0, num = stack_map_stream_.GetNumberOfStackMaps(); i != num; ++i) { uint32_t old_position = stack_map_stream_.GetStackMap(i).native_pc_offset; uint32_t new_position = __ GetAdjustedPosition(old_position); stack_map_stream_.SetStackMapNativePcOffset(i, new_position); } // Adjust native pc offsets of block labels. for (HBasicBlock* block : *block_order_) { // Get the label directly from block_labels_ rather than through GetLabelOf() to avoid // FirstNonEmptyBlock() which could lead to adjusting a label more than once. DCHECK_LT(static_cast(block->GetBlockId()), block_labels_.Size()); Label* block_label = &block_labels_.GetRawStorage()[block->GetBlockId()]; DCHECK_EQ(block_label->IsBound(), !block->IsSingleJump()); if (block_label->IsBound()) { __ AdjustLabelPosition(block_label); } } // Adjust pc offsets for the disassembly information. if (disasm_info_ != nullptr) { GeneratedCodeInterval* frame_entry_interval = disasm_info_->GetFrameEntryInterval(); frame_entry_interval->start = __ GetAdjustedPosition(frame_entry_interval->start); frame_entry_interval->end = __ GetAdjustedPosition(frame_entry_interval->end); for (auto& it : *disasm_info_->GetInstructionIntervals()) { it.second.start = __ GetAdjustedPosition(it.second.start); it.second.end = __ GetAdjustedPosition(it.second.end); } for (auto& it : *disasm_info_->GetSlowPathIntervals()) { it.code_interval.start = __ GetAdjustedPosition(it.code_interval.start); it.code_interval.end = __ GetAdjustedPosition(it.code_interval.end); } } // Adjust pc offsets for relative call patches. for (MethodPatchInfo