| /* |
| * Copyright (C) 2023 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 "fast_compiler.h" |
| |
| // TODO(VIXL): Make VIXL compile cleanly with -Wshadow, -Wdeprecated-declarations. |
| #pragma GCC diagnostic push |
| #pragma GCC diagnostic ignored "-Wshadow" |
| #pragma GCC diagnostic ignored "-Wdeprecated-declarations" |
| #include "aarch64/disasm-aarch64.h" |
| #include "aarch64/macro-assembler-aarch64.h" |
| #include "aarch64/disasm-aarch64.h" |
| #pragma GCC diagnostic pop |
| |
| #include "code_generation_data.h" |
| #include "code_generator_arm64.h" |
| #include "data_type-inl.h" |
| #include "dex/code_item_accessors-inl.h" |
| #include "dex/dex_file_exception_helpers.h" |
| #include "dex/dex_instruction-inl.h" |
| #include "driver/dex_compilation_unit.h" |
| #include "entrypoints/entrypoint_utils-inl.h" |
| #include "jit_patches_arm64.h" |
| #include "nodes.h" |
| #include "thread-inl.h" |
| #include "utils/arm64/assembler_arm64.h" |
| |
| |
| using namespace vixl::aarch64; // NOLINT(build/namespaces) |
| using vixl::ExactAssemblyScope; |
| using vixl::CodeBufferCheckScope; |
| using vixl::EmissionCheckScope; |
| |
| #ifdef __ |
| #error "ARM64 Codegen VIXL macro-assembler macro already defined." |
| #endif |
| #define __ GetVIXLAssembler()-> |
| |
| namespace art HIDDEN { |
| namespace arm64 { |
| |
| using helpers::CPURegisterFrom; |
| using helpers::HeapOperand; |
| using helpers::LocationFrom; |
| using helpers::RegisterFrom; |
| using helpers::WRegisterFrom; |
| using helpers::DRegisterFrom; |
| using helpers::SRegisterFrom; |
| |
| static const vixl::aarch64::Register kAvailableCalleeSaveRegisters[] = { |
| vixl::aarch64::x22, |
| vixl::aarch64::x23, |
| vixl::aarch64::x24, |
| vixl::aarch64::x25, |
| vixl::aarch64::x26, |
| vixl::aarch64::x27, |
| vixl::aarch64::x28, |
| vixl::aarch64::x29, |
| }; |
| |
| static const vixl::aarch64::Register kAvailableTempRegisters[] = { |
| vixl::aarch64::x8, |
| vixl::aarch64::x9, |
| vixl::aarch64::x10, |
| vixl::aarch64::x11, |
| vixl::aarch64::x12, |
| vixl::aarch64::x13, |
| vixl::aarch64::x14, |
| vixl::aarch64::x15, |
| }; |
| |
| static const vixl::aarch64::VRegister kAvailableCalleeSaveFpuRegisters[] = { |
| vixl::aarch64::d8, |
| vixl::aarch64::d9, |
| vixl::aarch64::d10, |
| vixl::aarch64::d11, |
| vixl::aarch64::d12, |
| vixl::aarch64::d13, |
| vixl::aarch64::d14, |
| vixl::aarch64::d15, |
| }; |
| |
| static const vixl::aarch64::VRegister kAvailableTempFpuRegisters[] = { |
| vixl::aarch64::d0, |
| vixl::aarch64::d1, |
| vixl::aarch64::d2, |
| vixl::aarch64::d3, |
| vixl::aarch64::d4, |
| vixl::aarch64::d5, |
| vixl::aarch64::d6, |
| vixl::aarch64::d7, |
| }; |
| |
| class FastCompilerARM64 : public FastCompiler { |
| public: |
| FastCompilerARM64(ArtMethod* method, |
| ArenaAllocator* allocator, |
| ArenaStack* arena_stack, |
| VariableSizedHandleScope* handles, |
| const CompilerOptions& compiler_options, |
| const DexCompilationUnit& dex_compilation_unit) |
| : method_(method), |
| allocator_(allocator), |
| handles_(handles), |
| assembler_(allocator, |
| compiler_options.GetInstructionSetFeatures()->AsArm64InstructionSetFeatures()), |
| jit_patches_(&assembler_, allocator), |
| compiler_options_(compiler_options), |
| dex_compilation_unit_(dex_compilation_unit), |
| code_generation_data_(CodeGenerationData::Create(arena_stack, InstructionSet::kArm64)), |
| vreg_locations_(dex_compilation_unit.GetCodeItemAccessor().RegistersSize(), |
| allocator->Adapter()), |
| branch_targets_(dex_compilation_unit.GetCodeItemAccessor().InsnsSizeInCodeUnits(), |
| allocator->Adapter()), |
| object_register_masks_(dex_compilation_unit.GetCodeItemAccessor().InsnsSizeInCodeUnits(), |
| allocator->Adapter()), |
| is_non_null_masks_(dex_compilation_unit.GetCodeItemAccessor().InsnsSizeInCodeUnits(), |
| allocator->Adapter()), |
| catch_pcs_(ArenaBitVector::CreateFixedSize( |
| allocator, |
| dex_compilation_unit.GetCodeItemAccessor().InsnsSizeInCodeUnits())), |
| catch_stack_maps_(dex_compilation_unit.GetCodeItemAccessor().TriesSize(), |
| allocator->Adapter()), |
| has_frame_(false), |
| core_spill_mask_(0u), |
| fpu_spill_mask_(0u), |
| object_register_mask_(0u), |
| is_non_null_mask_(0u) { |
| memset(is_non_null_masks_.data(), ~0, is_non_null_masks_.size() * sizeof(uint64_t)); |
| memset(object_register_masks_.data(), ~0, object_register_masks_.size() * sizeof(uint64_t)); |
| GetAssembler()->cfi().SetEnabled(compiler_options.GenerateAnyDebugInfo()); |
| } |
| |
| // Top-level method to generate code for `method_`. |
| bool Compile(); |
| |
| ArrayRef<const uint8_t> GetCode() const override { |
| return ArrayRef<const uint8_t>(assembler_.CodeBufferBaseAddress(), assembler_.CodeSize()); |
| } |
| |
| ScopedArenaVector<uint8_t> BuildStackMaps() const override { |
| return code_generation_data_->GetStackMapStream()->Encode(); |
| } |
| |
| ArrayRef<const uint8_t> GetCfiData() const override { |
| return ArrayRef<const uint8_t>(*assembler_.cfi().data()); |
| } |
| |
| int32_t GetFrameSize() const override { |
| if (!has_frame_) { |
| return 0; |
| } |
| size_t size = FrameEntrySpillSize() + |
| /* method */ static_cast<size_t>(kArm64PointerSize) + |
| /* out registers */ GetCodeItemAccessor().OutsSize() * kVRegSize; |
| return RoundUp(size, kStackAlignment); |
| } |
| |
| uint32_t GetNumberOfJitRoots() const override { |
| return code_generation_data_->GetNumberOfJitRoots(); |
| } |
| |
| void EmitJitRoots(uint8_t* code, |
| const uint8_t* roots_data, |
| /*out*/std::vector<Handle<mirror::Object>>* roots) override |
| REQUIRES_SHARED(Locks::mutator_lock_) { |
| code_generation_data_->EmitJitRoots(roots); |
| jit_patches_.EmitJitRootPatches(code, roots_data, *code_generation_data_); |
| } |
| |
| ~FastCompilerARM64() override { |
| GetVIXLAssembler()->Reset(); |
| } |
| |
| const char* GetUnimplementedReason() const { |
| return unimplemented_reason_; |
| } |
| |
| private: |
| // Go over each instruction of the method, and generate code for them. |
| bool ProcessInstructions(); |
| |
| // Initialize the locations of parameters for this method. |
| bool InitializeParameters(); |
| |
| // Generate code for the frame entry. Only called when needed. If the frame |
| // entry has already been generated, do nothing. |
| bool EnsureHasFrame(); |
| |
| // Generate code for a frame exit. |
| void PopFrameAndReturn(); |
| |
| // Record a stack map at the given dex_pc. |
| void RecordPcInfo(uint32_t dex_pc); |
| |
| // Generate code to move from one location to another. |
| bool MoveLocation(Location destination, Location source, DataType::Type dst_type); |
| |
| // Get a register location for the dex register `reg`. Saves the location into |
| // `vreg_locations_` for next uses of `reg`. |
| // `next` should be the next dex instruction, to help choose the register. |
| Location CreateNewRegisterLocation(uint32_t reg, DataType::Type type, const Instruction* next); |
| |
| // Return the existing register location for `reg`. |
| Location GetExistingRegisterLocation(uint32_t reg, DataType::Type type); |
| |
| // Move dex registers holding constants into physical registers. Used when |
| // branching. |
| void MoveConstantsToRegisters(); |
| |
| // Update the masks associated to the given dex_pc. Used when dex_pc is a |
| // branch target. |
| void UpdateMasks(uint32_t dex_pc); |
| |
| // Generate code for one instruction. |
| bool ProcessDexInstruction(const Instruction& instruction, |
| uint32_t dex_pc, |
| const Instruction* next); |
| |
| // Setup the arguments for an invoke. |
| bool SetupArguments(InvokeType invoke_type, |
| const InstructionOperands& operands, |
| const char* shorty, |
| /* out */ uint32_t* obj_reg); |
| |
| // Generate code for doing a Java invoke. |
| bool HandleInvoke(const Instruction& instruction, uint32_t dex_pc, InvokeType invoke_type); |
| |
| // Generate code for IF_* instructions. |
| template<vixl::aarch64::Condition kCond, bool kCompareWithZero> |
| bool If_21_22t(const Instruction& instruction, uint32_t dex_pc); |
| |
| // Generate code for doing a runtime invoke. |
| void InvokeRuntime(QuickEntrypointEnum entrypoint, uint32_t dex_pc); |
| |
| bool BuildLoadString(uint32_t vreg, dex::StringIndex string_index, const Instruction* next); |
| bool BuildNewInstance( |
| uint32_t vreg, dex::TypeIndex string_index, uint32_t dex_pc, const Instruction* next); |
| bool BuildCheckCast(uint32_t vreg, dex::TypeIndex type_index, uint32_t dex_pc); |
| bool BuildInstanceOf( |
| uint32_t vreg, uint32_t vreg_result, dex::TypeIndex type_index, uint32_t dex_pc); |
| bool LoadMethod(Register reg, ArtMethod* method); |
| void DoReadBarrierOn(Register reg, vixl::aarch64::Label* exit = nullptr, bool do_mr_check = true); |
| bool CanGenerateCodeFor(ArtField* field, bool can_receiver_be_null) |
| REQUIRES_SHARED(Locks::mutator_lock_); |
| bool DoGet(const MemOperand& mem, |
| uint16_t field_index, |
| Instruction::Code code, |
| uint32_t dest_reg, |
| bool can_receiver_be_null, |
| bool is_object, |
| uint32_t dex_pc, |
| const Instruction* next); |
| |
| // Mark whether dex register `vreg_index` is an object. |
| void UpdateRegisterMask(uint32_t vreg_index, bool is_object) { |
| // Note that the register mask is only useful when there is a frame, so we |
| // use the callee save registers for the mask. |
| if (is_object) { |
| object_register_mask_ |= (1 << kAvailableCalleeSaveRegisters[vreg_index].GetCode()); |
| } else { |
| object_register_mask_ &= ~(1 << kAvailableCalleeSaveRegisters[vreg_index].GetCode()); |
| } |
| } |
| |
| // Mark whether dex register `vreg_index` can be null. |
| void UpdateNonNullMask(uint32_t vreg_index, bool can_be_null) { |
| if (can_be_null) { |
| is_non_null_mask_ &= ~(1 << vreg_index); |
| } else { |
| is_non_null_mask_ |= (1 << vreg_index); |
| } |
| } |
| |
| // Update information about dex register `vreg_index`. |
| void UpdateLocal(uint32_t vreg_index, bool is_object, bool can_be_null = true) { |
| UpdateRegisterMask(vreg_index, is_object); |
| UpdateNonNullMask(vreg_index, can_be_null); |
| } |
| |
| // Whether dex register `vreg_index` can be null. |
| bool CanBeNull(uint32_t vreg_index) const { |
| return (is_non_null_mask_ & (1 << vreg_index)) == 0; |
| } |
| |
| // Get the label associated with the given `dex_pc`. |
| vixl::aarch64::Label* GetLabelOf(uint32_t dex_pc) { |
| return &branch_targets_[dex_pc]; |
| } |
| |
| // If we need to abort compilation, clear branch targets, required by vixl. |
| void AbortCompilation() { |
| for (vixl::aarch64::Label& label : branch_targets_) { |
| if (label.IsLinked()) { |
| __ Bind(&label); |
| } |
| } |
| } |
| |
| |
| // Compiler utilities. |
| // |
| Arm64Assembler* GetAssembler() { return &assembler_; } |
| vixl::aarch64::MacroAssembler* GetVIXLAssembler() { return GetAssembler()->GetVIXLAssembler(); } |
| const DexFile& GetDexFile() const { return *dex_compilation_unit_.GetDexFile(); } |
| const CodeItemDataAccessor& GetCodeItemAccessor() const { |
| return dex_compilation_unit_.GetCodeItemAccessor(); |
| } |
| bool HitUnimplemented() const { |
| return unimplemented_reason_ != nullptr; |
| } |
| |
| // Frame related utilities. |
| // |
| uint32_t GetCoreSpillSize() const { |
| return GetFramePreservedCoreRegisters().GetTotalSizeInBytes(); |
| } |
| uint32_t FrameEntrySpillSize() const { |
| return GetFramePreservedFPRegisters().GetTotalSizeInBytes() + GetCoreSpillSize(); |
| } |
| CPURegList GetFramePreservedCoreRegisters() const { |
| return CPURegList(CPURegister::kRegister, kXRegSize, core_spill_mask_); |
| } |
| CPURegList GetFramePreservedFPRegisters() const { |
| return CPURegList(CPURegister::kVRegister, kDRegSize, fpu_spill_mask_); |
| } |
| |
| // Method being compiled. |
| ArtMethod* method_; |
| |
| // Allocator for any allocation happening in the compiler. |
| ArenaAllocator* allocator_; |
| |
| VariableSizedHandleScope* handles_; |
| |
| // Compilation utilities. |
| Arm64Assembler assembler_; |
| JitPatchesARM64 jit_patches_; |
| const CompilerOptions& compiler_options_; |
| const DexCompilationUnit& dex_compilation_unit_; |
| std::unique_ptr<CodeGenerationData> code_generation_data_; |
| |
| // The current location of each dex register. |
| ArenaVector<Location> vreg_locations_; |
| |
| // A vector of size code units for dex pcs that are branch targets. |
| ArenaVector<vixl::aarch64::Label> branch_targets_; |
| |
| // For dex pcs that are branch targets, the register mask that will be used at |
| // the point of that pc. |
| ArenaVector<uint64_t> object_register_masks_; |
| |
| // For dex pcs that are branch targets, the mask for non-null objects that will |
| // be used at the point of that pc. |
| ArenaVector<uint64_t> is_non_null_masks_; |
| |
| // Dex pcs that are catch targets. |
| BitVectorView<size_t> catch_pcs_; |
| |
| // Pair of {dex_pc, native_pc} collected during compilation, used when |
| // generating stack map entries for catch instructions at the end of |
| // compilation. |
| ArenaVector<std::pair<uint32_t, uint32_t>> catch_stack_maps_; |
| |
| // Whether we've created a frame for this compiled method. |
| bool has_frame_; |
| |
| // CPU registers that have been spilled in the frame. |
| uint32_t core_spill_mask_; |
| |
| // FPU registers that have been spilled in the frame. |
| uint32_t fpu_spill_mask_; |
| |
| // The current mask to know which physical register holds an object. |
| uint64_t object_register_mask_; |
| |
| // The current mask to know if a dex register is known non-null. |
| uint64_t is_non_null_mask_; |
| |
| // The return type of the compiled method. Saved to avoid re-computing it on |
| // the return instruction. |
| DataType::Type return_type_; |
| |
| // The return type of the last invoke instruction. |
| DataType::Type previous_invoke_return_type_; |
| |
| // If non-empty, the reason the compilation could not be finished. |
| const char* unimplemented_reason_ = nullptr; |
| }; |
| |
| bool FastCompilerARM64::InitializeParameters() { |
| const char* shorty = dex_compilation_unit_.GetShorty(); |
| uint16_t number_of_vregs = GetCodeItemAccessor().RegistersSize(); |
| uint16_t number_of_parameters = GetCodeItemAccessor().InsSize(); |
| uint16_t vreg_parameter_index = number_of_vregs - number_of_parameters; |
| |
| if (number_of_vregs > arraysize(kAvailableTempRegisters) || |
| number_of_vregs > arraysize(kAvailableCalleeSaveRegisters) || |
| number_of_vregs > arraysize(kAvailableTempFpuRegisters) || |
| number_of_vregs > arraysize(kAvailableCalleeSaveFpuRegisters)) { |
| // Too many registers for this compiler. |
| unimplemented_reason_ = "TooManyRegisters"; |
| return false; |
| } |
| |
| InvokeDexCallingConventionVisitorARM64 convention; |
| if (!dex_compilation_unit_.IsStatic()) { |
| // Add the implicit 'this' argument, not expressed in the signature. |
| vreg_locations_[vreg_parameter_index] = convention.GetNextLocation(DataType::Type::kReference); |
| UpdateLocal(vreg_parameter_index, /* is_object= */ true, /* can_be_null= */ false); |
| ++vreg_parameter_index; |
| --number_of_parameters; |
| } |
| |
| for (int i = 0, shorty_pos = 1; |
| i < number_of_parameters; |
| i++, shorty_pos++, vreg_parameter_index++) { |
| DataType::Type type = DataType::FromShorty(shorty[shorty_pos]); |
| vreg_locations_[vreg_parameter_index] = convention.GetNextLocation(type); |
| UpdateLocal(vreg_parameter_index, |
| /* is_object= */ (type == DataType::Type::kReference), |
| /* can_be_null= */ true); |
| if (DataType::Is64BitType(type)) { |
| ++i; |
| ++vreg_parameter_index; |
| } |
| } |
| return_type_ = DataType::FromShorty(shorty[0]); |
| |
| if (GetCodeItemAccessor().TriesSize() != 0) { |
| if (!EnsureHasFrame()) { |
| return false; |
| } |
| const uint8_t* handlers_ptr = GetCodeItemAccessor().GetCatchHandlerData(); |
| uint32_t handlers_size = DecodeUnsignedLeb128(&handlers_ptr); |
| for (uint32_t idx = 0; idx < handlers_size; ++idx) { |
| CatchHandlerIterator iterator(handlers_ptr); |
| for (; iterator.HasNext(); iterator.Next()) { |
| catch_pcs_.SetBit(iterator.GetHandlerAddress()); |
| } |
| handlers_ptr = iterator.EndDataPointer(); |
| } |
| } |
| |
| return true; |
| } |
| |
| void FastCompilerARM64::MoveConstantsToRegisters() { |
| for (uint32_t i = 0; i < vreg_locations_.size(); ++i) { |
| Location location = vreg_locations_[i]; |
| if (location.IsConstant()) { |
| vreg_locations_[i] = |
| CreateNewRegisterLocation(i, DataType::Type::kInt32, /* next= */ nullptr); |
| MoveLocation(vreg_locations_[i], location, DataType::Type::kInt32); |
| DCHECK(!HitUnimplemented()); |
| if (location.GetConstant()->IsArithmeticZero()) { |
| // In case we branch, we need to make sure a null value can be merged |
| // with an object value, so treat the 0 value as an object. |
| UpdateRegisterMask(i, /* is_object= */ true); |
| } |
| } |
| } |
| } |
| |
| void FastCompilerARM64::UpdateMasks(uint32_t dex_pc) { |
| object_register_masks_[dex_pc] &= object_register_mask_; |
| is_non_null_masks_[dex_pc] &= is_non_null_mask_; |
| } |
| |
| bool FastCompilerARM64::ProcessInstructions() { |
| DCHECK(GetCodeItemAccessor().HasCodeItem()); |
| |
| DexInstructionIterator it = GetCodeItemAccessor().begin(); |
| DexInstructionIterator end = GetCodeItemAccessor().end(); |
| DCHECK(it != end); |
| do { |
| DexInstructionPcPair pair = *it; |
| ++it; |
| |
| // Fetch the next instruction as a micro-optimization currently only used |
| // for optimizing returns. |
| const Instruction* next = nullptr; |
| if (it != end) { |
| const DexInstructionPcPair& next_pair = *it; |
| next = &next_pair.Inst(); |
| if (GetLabelOf(next_pair.DexPc())->IsLinked()) { |
| // Disable the micro-optimization, as the next instruction is a branch |
| // target. |
| next = nullptr; |
| } |
| } |
| vixl::aarch64::Label* label = GetLabelOf(pair.DexPc()); |
| if (label->IsLinked()) { |
| // Emulate a branch to this pc. |
| MoveConstantsToRegisters(); |
| UpdateMasks(pair.DexPc()); |
| // Set new masks based on all incoming edges. |
| is_non_null_mask_ = is_non_null_masks_[pair.DexPc()]; |
| object_register_mask_ = object_register_masks_[pair.DexPc()]; |
| __ Bind(label); |
| } |
| |
| if (catch_pcs_.IsBitSet(pair.DexPc())) { |
| catch_stack_maps_.push_back(std::make_pair(pair.DexPc(), GetAssembler()->CodePosition())); |
| } |
| |
| if (!ProcessDexInstruction(pair.Inst(), pair.DexPc(), next)) { |
| DCHECK(HitUnimplemented()); |
| return false; |
| } |
| // Note: There may be no Thread for gtests. |
| DCHECK(Thread::Current() == nullptr || !Thread::Current()->IsExceptionPending()) |
| << GetDexFile().PrettyMethod(dex_compilation_unit_.GetDexMethodIndex()) |
| << " " << pair.Inst().Name() << "@" << pair.DexPc(); |
| |
| DCHECK(!HitUnimplemented()) << GetUnimplementedReason(); |
| } while (it != end); |
| return true; |
| } |
| |
| bool FastCompilerARM64::MoveLocation(Location destination, |
| Location source, |
| DataType::Type dst_type) { |
| if (source.Equals(destination)) { |
| return true; |
| } |
| if (source.IsRegister() && destination.IsRegister()) { |
| CPURegister dst = CPURegisterFrom(destination, dst_type); |
| __ Mov(Register(dst), RegisterFrom(source, dst_type)); |
| return true; |
| } |
| if (source.IsConstant() && destination.IsRegister()) { |
| if (source.GetConstant()->IsIntConstant()) { |
| __ Mov(RegisterFrom(destination, DataType::Type::kInt32), |
| source.GetConstant()->AsIntConstant()->GetValue()); |
| return true; |
| } |
| } |
| unimplemented_reason_ = "MoveLocation"; |
| return false; |
| } |
| |
| Location FastCompilerARM64::CreateNewRegisterLocation(uint32_t reg, |
| DataType::Type type, |
| const Instruction* next) { |
| if (next != nullptr && |
| (next->Opcode() == Instruction::RETURN_OBJECT || next->Opcode() == Instruction::RETURN) && |
| (next->VRegA_11x() == reg)) { |
| // If the next instruction is a return, use the return register from the calling |
| // convention. |
| InvokeDexCallingConventionVisitorARM64 convention; |
| vreg_locations_[reg] = convention.GetReturnLocation(return_type_); |
| return vreg_locations_[reg]; |
| } else if (vreg_locations_[reg].IsStackSlot() || |
| vreg_locations_[reg].IsDoubleStackSlot()) { |
| unimplemented_reason_ = "MoveStackSlot"; |
| // Return a phony location. |
| return DataType::IsFloatingPointType(type) |
| ? Location::FpuRegisterLocation(1) |
| : Location::RegisterLocation(1); |
| } else if (DataType::IsFloatingPointType(type)) { |
| if (vreg_locations_[reg].IsFpuRegister()) { |
| // Re-use existing register. |
| return vreg_locations_[reg]; |
| } else if (has_frame_) { |
| // TODO: Regenerate the method with floating point support. |
| unimplemented_reason_ = "FpuRegisterAllocation"; |
| vreg_locations_[reg] = Location::FpuRegisterLocation(1); |
| return vreg_locations_[reg]; |
| } else { |
| vreg_locations_[reg] = |
| Location::FpuRegisterLocation(kAvailableTempFpuRegisters[reg].GetCode()); |
| return vreg_locations_[reg]; |
| } |
| } else if (vreg_locations_[reg].IsRegister()) { |
| // Re-use existing register. |
| return vreg_locations_[reg]; |
| } else { |
| // Get the associated register with `reg`. |
| uint32_t register_code = has_frame_ |
| ? kAvailableCalleeSaveRegisters[reg].GetCode() |
| : kAvailableTempRegisters[reg].GetCode(); |
| vreg_locations_[reg] = Location::RegisterLocation(register_code); |
| return vreg_locations_[reg]; |
| } |
| } |
| |
| Location FastCompilerARM64::GetExistingRegisterLocation(uint32_t reg, DataType::Type type) { |
| if (vreg_locations_[reg].IsStackSlot() || vreg_locations_[reg].IsDoubleStackSlot()) { |
| unimplemented_reason_ = "MoveStackSlot"; |
| // Return a phony location. |
| return DataType::IsFloatingPointType(type) |
| ? Location::FpuRegisterLocation(1) |
| : Location::RegisterLocation(1); |
| } else if (DataType::IsFloatingPointType(type)) { |
| if (vreg_locations_[reg].IsFpuRegister()) { |
| return vreg_locations_[reg]; |
| } else { |
| // TODO: Regenerate the method with floating point support. |
| unimplemented_reason_ = "FpuRegisterAllocation"; |
| vreg_locations_[reg] = Location::FpuRegisterLocation(1); |
| return vreg_locations_[reg]; |
| } |
| } else if (vreg_locations_[reg].IsRegister()) { |
| return vreg_locations_[reg]; |
| } else { |
| unimplemented_reason_ = "UnknownLocation"; |
| vreg_locations_[reg] = Location::RegisterLocation(1); |
| return Location::RegisterLocation(1); |
| } |
| } |
| |
| void FastCompilerARM64::RecordPcInfo(uint32_t dex_pc) { |
| DCHECK(has_frame_); |
| uint32_t native_pc = GetAssembler()->CodePosition(); |
| StackMapStream* stack_map_stream = code_generation_data_->GetStackMapStream(); |
| CHECK_EQ(object_register_mask_ & callee_saved_core_registers.GetList(), object_register_mask_); |
| stack_map_stream->BeginStackMapEntry(dex_pc, native_pc, object_register_mask_); |
| stack_map_stream->EndStackMapEntry(); |
| } |
| |
| void FastCompilerARM64::PopFrameAndReturn() { |
| if (has_frame_) { |
| CodeGeneratorARM64::PopFrameAndReturn(GetAssembler(), |
| GetFrameSize(), |
| GetFramePreservedCoreRegisters(), |
| GetFramePreservedFPRegisters()); |
| } else { |
| DCHECK_EQ(GetFrameSize(), 0); |
| __ Ret(); |
| } |
| } |
| |
| bool FastCompilerARM64::EnsureHasFrame() { |
| if (has_frame_) { |
| // Frame entry has already been generated. |
| return true; |
| } |
| has_frame_ = true; |
| uint16_t number_of_vregs = GetCodeItemAccessor().RegistersSize(); |
| for (int i = 0; i < number_of_vregs; ++i) { |
| // Assume any vreg will be held in a callee-save register. |
| core_spill_mask_ |= (1 << kAvailableCalleeSaveRegisters[i].GetCode()); |
| if (vreg_locations_[i].IsFpuRegister()) { |
| // TODO: Re-generate method with floating points. |
| unimplemented_reason_ = "FloatingPoint"; |
| return false; |
| } |
| } |
| core_spill_mask_ |= (1 << lr.GetCode()); |
| |
| code_generation_data_->GetStackMapStream()->BeginMethod(GetFrameSize(), |
| core_spill_mask_, |
| fpu_spill_mask_, |
| GetCodeItemAccessor().RegistersSize(), |
| /* is_compiling_baseline= */ true, |
| /* is_debuggable= */ false); |
| MacroAssembler* masm = GetVIXLAssembler(); |
| { |
| UseScratchRegisterScope temps(masm); |
| Register temp = temps.AcquireX(); |
| __ Sub(temp, sp, static_cast<int32_t>(GetStackOverflowReservedBytes(InstructionSet::kArm64))); |
| // Ensure that between load and RecordPcInfo there are no pools emitted. |
| ExactAssemblyScope eas(GetVIXLAssembler(), |
| kInstructionSize, |
| CodeBufferCheckScope::kExactSize); |
| __ ldr(wzr, MemOperand(temp, 0)); |
| RecordPcInfo(0); |
| } |
| |
| CodeGeneratorARM64::GenerateFrame(GetAssembler(), |
| GetFrameSize(), |
| GetFramePreservedCoreRegisters(), |
| GetFramePreservedFPRegisters(), |
| /* requires_current_method= */ true); |
| |
| // Move registers which are currently allocated from caller-saves to callee-saves, |
| // and adjust the offsets of stack locations. |
| for (int i = 0; i < number_of_vregs; ++i) { |
| if (vreg_locations_[i].IsRegister()) { |
| Location new_location = |
| Location::RegisterLocation(kAvailableCalleeSaveRegisters[i].GetCode()); |
| if (!MoveLocation(new_location, vreg_locations_[i], DataType::Type::kInt64)) { |
| return false; |
| } |
| vreg_locations_[i] = new_location; |
| } else if (vreg_locations_[i].IsFpuRegister()) { |
| Location new_location = |
| Location::FpuRegisterLocation(kAvailableCalleeSaveFpuRegisters[i].GetCode()); |
| if (!MoveLocation(new_location, vreg_locations_[i], DataType::Type::kFloat64)) { |
| return false; |
| } |
| vreg_locations_[i] = new_location; |
| } else if (vreg_locations_[i].IsStackSlot()) { |
| vreg_locations_[i] = Location::StackSlot(vreg_locations_[i].GetStackIndex() + GetFrameSize()); |
| } else if (vreg_locations_[i].IsDoubleStackSlot()) { |
| vreg_locations_[i] = |
| Location::DoubleStackSlot(vreg_locations_[i].GetStackIndex() + GetFrameSize()); |
| } else if (vreg_locations_[i].IsConstant() || vreg_locations_[i].IsInvalid()) { |
| // Nothing to do. |
| } else { |
| unimplemented_reason_ = "Unhandled location"; |
| return false; |
| } |
| } |
| |
| // Increment hotness. We use the ArtMethod's counter as we're not allocating a |
| // `ProfilingInfo` object in the fast baseline compiler. |
| if (!Runtime::Current()->IsAotCompiler()) { |
| uint64_t address = reinterpret_cast64<uint64_t>(method_); |
| UseScratchRegisterScope temps(masm); |
| Register counter = temps.AcquireW(); |
| vixl::aarch64::Label increment, done; |
| uint32_t entrypoint_offset = |
| GetThreadOffset<kArm64PointerSize>(kQuickCompileOptimized).Int32Value(); |
| |
| __ Ldrh(counter, MemOperand(kArtMethodRegister, ArtMethod::HotnessCountOffset().Int32Value())); |
| __ Cbnz(counter, &increment); |
| __ Ldr(lr, MemOperand(tr, entrypoint_offset)); |
| // Note: we don't record the call here (and therefore don't generate a stack |
| // map), as the entrypoint should never be suspended. |
| __ Blr(lr); |
| __ Bind(&increment); |
| __ Add(counter, counter, -1); |
| __ Strh(counter, MemOperand(kArtMethodRegister, ArtMethod::HotnessCountOffset().Int32Value())); |
| __ Bind(&done); |
| } |
| |
| // Do the suspend check. |
| if (compiler_options_.GetImplicitSuspendChecks()) { |
| ExactAssemblyScope eas(GetVIXLAssembler(), |
| kInstructionSize, |
| CodeBufferCheckScope::kExactSize); |
| __ ldr(kImplicitSuspendCheckRegister, MemOperand(kImplicitSuspendCheckRegister)); |
| RecordPcInfo(0); |
| } else { |
| UseScratchRegisterScope temps(masm); |
| Register temp = temps.AcquireW(); |
| vixl::aarch64::Label continue_label; |
| __ Ldr(temp, MemOperand(tr, Thread::ThreadFlagsOffset<kArm64PointerSize>().SizeValue())); |
| __ Tst(temp, Thread::SuspendOrCheckpointRequestFlags()); |
| __ B(eq, &continue_label); |
| uint32_t entrypoint_offset = |
| GetThreadOffset<kArm64PointerSize>(kQuickTestSuspend).Int32Value(); |
| __ Ldr(lr, MemOperand(tr, entrypoint_offset)); |
| { |
| ExactAssemblyScope eas(GetVIXLAssembler(), |
| kInstructionSize, |
| CodeBufferCheckScope::kExactSize); |
| __ blr(lr); |
| RecordPcInfo(0); |
| } |
| __ Bind(&continue_label); |
| } |
| return true; |
| } |
| |
| |
| bool FastCompilerARM64::SetupArguments(InvokeType invoke_type, |
| const InstructionOperands& operands, |
| const char* shorty, |
| /* out */ uint32_t* obj_reg) { |
| const size_t number_of_operands = operands.GetNumberOfOperands(); |
| |
| size_t start_index = 0u; |
| size_t argument_index = 0u; |
| InvokeDexCallingConventionVisitorARM64 convention; |
| |
| // Handle 'this' parameter. |
| if (invoke_type != kStatic) { |
| if (number_of_operands == 0u) { |
| unimplemented_reason_ = "BogusSignature"; |
| return false; |
| } |
| start_index = 1u; |
| *obj_reg = operands.GetOperand(0u); |
| if (!MoveLocation(convention.GetNextLocation(DataType::Type::kReference), |
| vreg_locations_[*obj_reg], |
| DataType::Type::kReference)) { |
| return false; |
| } |
| } |
| |
| uint32_t shorty_index = 1; // Skip the return type. |
| // Handle all parameters except 'this'. |
| for (size_t i = start_index; i < number_of_operands; ++i, ++argument_index, ++shorty_index) { |
| // Make sure we don't go over the expected arguments or over the number of |
| // dex registers given. If the instruction was seen as dead by the verifier, |
| // it hasn't been properly checked. |
| char c = shorty[shorty_index]; |
| if (UNLIKELY(c == 0)) { |
| unimplemented_reason_ = "BogusSignature"; |
| return false; |
| } |
| DataType::Type type = DataType::FromShorty(c); |
| bool is_wide = (type == DataType::Type::kInt64) || (type == DataType::Type::kFloat64); |
| if (is_wide && ((i + 1 == number_of_operands) || |
| (operands.GetOperand(i) + 1 != operands.GetOperand(i + 1)))) { |
| unimplemented_reason_ = "BogusSignature"; |
| return false; |
| } |
| if (!MoveLocation(convention.GetNextLocation(type), |
| vreg_locations_[operands.GetOperand(i)], |
| type)) { |
| return false; |
| } |
| if (is_wide) { |
| ++i; |
| } |
| } |
| return true; |
| } |
| |
| bool FastCompilerARM64::LoadMethod(Register reg, ArtMethod* method) { |
| if (Runtime::Current()->IsAotCompiler()) { |
| unimplemented_reason_ = "AOTLoadMethod"; |
| return false; |
| } |
| __ Ldr(reg, jit_patches_.DeduplicateUint64Literal(reinterpret_cast<uint64_t>(method))); |
| return true; |
| } |
| |
| bool FastCompilerARM64::HandleInvoke(const Instruction& instruction, |
| uint32_t dex_pc, |
| InvokeType invoke_type) { |
| Instruction::Code opcode = instruction.Opcode(); |
| uint16_t method_index = (opcode >= Instruction::INVOKE_VIRTUAL_RANGE) |
| ? instruction.VRegB_3rc() |
| : instruction.VRegB_35c(); |
| ArtMethod* resolved_method = nullptr; |
| size_t offset = 0u; |
| { |
| Thread* self = Thread::Current(); |
| ScopedObjectAccess soa(self); |
| ClassLinker* const class_linker = dex_compilation_unit_.GetClassLinker(); |
| resolved_method = method_->SkipAccessChecks() |
| ? class_linker->ResolveMethodId(method_index, method_) |
| : class_linker->ResolveMethodWithChecks( |
| method_index, method_, invoke_type); |
| if (resolved_method == nullptr) { |
| DCHECK(self->IsExceptionPending()); |
| self->ClearException(); |
| unimplemented_reason_ = "UnresolvedInvoke"; |
| return false; |
| } |
| |
| if (resolved_method->IsConstructor() && resolved_method->GetDeclaringClass()->IsObjectClass()) { |
| // Object.<init> is always empty. Return early to not generate a frame. |
| if (kIsDebugBuild) { |
| CHECK(resolved_method->GetDeclaringClass()->IsVerified()); |
| CodeItemDataAccessor accessor(*resolved_method->GetDexFile(), |
| resolved_method->GetCodeItem()); |
| CHECK_EQ(accessor.InsnsSizeInCodeUnits(), 1u); |
| CHECK_EQ(accessor.begin().Inst().Opcode(), Instruction::RETURN_VOID); |
| } |
| // No need to update `previous_invoke_return_type_`, we know it is not going the |
| // be used. |
| return true; |
| } |
| |
| if (invoke_type == kSuper) { |
| resolved_method = method_->SkipAccessChecks() |
| ? FindSuperMethodToCall</*access_check=*/false>(method_index, |
| resolved_method, |
| method_, |
| self) |
| : FindSuperMethodToCall</*access_check=*/true>(method_index, |
| resolved_method, |
| method_, |
| self); |
| if (resolved_method == nullptr) { |
| DCHECK(self->IsExceptionPending()) << method_->PrettyMethod(); |
| self->ClearException(); |
| unimplemented_reason_ = "UnresolvedInvokeSuper"; |
| return false; |
| } |
| } else if (invoke_type == kVirtual) { |
| offset = resolved_method->GetVtableIndex(); |
| } else if (invoke_type == kInterface) { |
| if (resolved_method->GetDeclaringClass()->IsObjectClass()) { |
| // If the resolved method is from j.l.Object, emit a virtual call instead. |
| // The IMT conflict stub only handles interface methods. |
| offset = resolved_method->GetVtableIndex(); |
| invoke_type = kVirtual; |
| } else { |
| offset = resolved_method->GetImtIndex(); |
| } |
| } |
| |
| if (resolved_method->IsStringConstructor()) { |
| unimplemented_reason_ = "StringConstructor"; |
| return false; |
| } |
| } |
| |
| // Given we are calling a method, generate a frame. |
| if (!EnsureHasFrame()) { |
| return false; |
| } |
| |
| // Setup the arguments for the call. |
| uint32_t obj_reg = -1; |
| const char* shorty = dex_compilation_unit_.GetDexFile()->GetMethodShorty(method_index); |
| if (opcode >= Instruction::INVOKE_VIRTUAL_RANGE) { |
| RangeInstructionOperands operands(instruction.VRegC(), instruction.VRegA_3rc()); |
| if (!SetupArguments(invoke_type, operands, shorty, &obj_reg)) { |
| return false; |
| } |
| } else { |
| uint32_t args[5]; |
| uint32_t number_of_vreg_arguments = instruction.GetVarArgs(args); |
| VarArgsInstructionOperands operands(args, number_of_vreg_arguments); |
| if (!SetupArguments(invoke_type, operands, shorty, &obj_reg)) { |
| return false; |
| } |
| } |
| // Save the invoke return type for the next move-result instruction. |
| previous_invoke_return_type_ = DataType::FromShorty(shorty[0]); |
| |
| if (invoke_type != kStatic) { |
| bool can_be_null = CanBeNull(obj_reg); |
| // Load the class of the instance. For kDirect and kSuper, this acts as a |
| // null check. |
| if (can_be_null || invoke_type == kVirtual || invoke_type == kInterface) { |
| InvokeDexCallingConvention calling_convention; |
| Register receiver = calling_convention.GetRegisterAt(0); |
| Offset class_offset = mirror::Object::ClassOffset(); |
| EmissionCheckScope guard(GetVIXLAssembler(), kMaxMacroInstructionSizeInBytes); |
| __ Ldr(kArtMethodRegister.W(), HeapOperand(receiver.W(), class_offset)); |
| if (can_be_null) { |
| RecordPcInfo(dex_pc); |
| } |
| } |
| } |
| |
| if (invoke_type == kVirtual) { |
| size_t method_offset = |
| mirror::Class::EmbeddedVTableEntryOffset(offset, kArm64PointerSize).SizeValue(); |
| __ Ldr(kArtMethodRegister, MemOperand(kArtMethodRegister, method_offset)); |
| } else if (invoke_type == kInterface) { |
| __ Ldr(kArtMethodRegister, |
| MemOperand(kArtMethodRegister, |
| mirror::Class::ImtPtrOffset(kArm64PointerSize).Uint32Value())); |
| uint32_t method_offset = |
| static_cast<uint32_t>(ImTable::OffsetOfElement(offset, kArm64PointerSize)); |
| __ Ldr(kArtMethodRegister, MemOperand(kArtMethodRegister, method_offset)); |
| if (!LoadMethod(ip1, resolved_method)) { |
| return false; |
| } |
| } else { |
| DCHECK(invoke_type == kDirect || invoke_type == kSuper || invoke_type == kStatic); |
| if (!LoadMethod(kArtMethodRegister, resolved_method)) { |
| return false; |
| } |
| } |
| |
| Offset entry_point = ArtMethod::EntryPointFromQuickCompiledCodeOffset(kArm64PointerSize); |
| __ Ldr(lr, MemOperand(kArtMethodRegister, entry_point.SizeValue())); |
| { |
| // Use a scope to help guarantee that `RecordPcInfo()` records the correct pc. |
| ExactAssemblyScope eas(GetVIXLAssembler(), kInstructionSize, CodeBufferCheckScope::kExactSize); |
| __ blr(lr); |
| RecordPcInfo(dex_pc); |
| } |
| return true; |
| } |
| |
| void FastCompilerARM64::InvokeRuntime(QuickEntrypointEnum entrypoint, uint32_t dex_pc) { |
| ThreadOffset64 entrypoint_offset = GetThreadOffset<kArm64PointerSize>(entrypoint); |
| __ Ldr(lr, MemOperand(tr, entrypoint_offset.Int32Value())); |
| // Ensure the pc position is recorded immediately after the `blr` instruction. |
| ExactAssemblyScope eas(GetVIXLAssembler(), kInstructionSize, CodeBufferCheckScope::kExactSize); |
| __ blr(lr); |
| if (EntrypointRequiresStackMap(entrypoint)) { |
| RecordPcInfo(dex_pc); |
| } |
| } |
| |
| bool FastCompilerARM64::BuildLoadString(uint32_t vreg, |
| dex::StringIndex string_index, |
| const Instruction* next) { |
| // Generate a frame because of the read barrier. |
| if (!EnsureHasFrame()) { |
| return false; |
| } |
| Location loc = CreateNewRegisterLocation(vreg, DataType::Type::kReference, next); |
| if (HitUnimplemented()) { |
| return false; |
| } |
| if (Runtime::Current()->IsAotCompiler()) { |
| unimplemented_reason_ = "AOTLoadString"; |
| return false; |
| } |
| |
| ScopedObjectAccess soa(Thread::Current()); |
| ClassLinker* const class_linker = dex_compilation_unit_.GetClassLinker(); |
| ObjPtr<mirror::String> str = class_linker->ResolveString(string_index, method_); |
| if (str == nullptr) { |
| soa.Self()->ClearException(); |
| unimplemented_reason_ = "NullString"; |
| return false; |
| } |
| |
| Handle<mirror::String> h_str = handles_->NewHandle(str); |
| Register dst = RegisterFrom(loc, DataType::Type::kReference); |
| __ Ldr(dst.W(), jit_patches_.DeduplicateJitStringLiteral(GetDexFile(), |
| string_index, |
| h_str, |
| code_generation_data_.get())); |
| __ Ldr(dst.W(), MemOperand(dst.X())); |
| DoReadBarrierOn(dst); |
| UpdateLocal(vreg, /* is_object= */ true, /* can_be_null= */ false); |
| return true; |
| } |
| |
| bool FastCompilerARM64::BuildNewInstance(uint32_t vreg, |
| dex::TypeIndex type_index, |
| uint32_t dex_pc, |
| const Instruction* next) { |
| if (!EnsureHasFrame()) { |
| return false; |
| } |
| if (Runtime::Current()->IsAotCompiler()) { |
| unimplemented_reason_ = "AOTNewInstance"; |
| return false; |
| } |
| |
| ScopedObjectAccess soa(Thread::Current()); |
| ObjPtr<mirror::Class> klass = dex_compilation_unit_.GetClassLinker()->ResolveType( |
| type_index, dex_compilation_unit_.GetDexCache(), dex_compilation_unit_.GetClassLoader()); |
| if (klass == nullptr || |
| !method_->GetDeclaringClass()->CanAccess(klass) || |
| klass->IsStringClass()) { |
| soa.Self()->ClearException(); |
| unimplemented_reason_ = "UnsupportedClassForNewInstance"; |
| return false; |
| } |
| |
| InvokeRuntimeCallingConvention calling_convention; |
| Register cls_reg = calling_convention.GetRegisterAt(0); |
| Handle<mirror::Class> h_klass = handles_->NewHandle(klass); |
| __ Ldr(cls_reg.W(), jit_patches_.DeduplicateJitClassLiteral(GetDexFile(), |
| type_index, |
| h_klass , |
| code_generation_data_.get())); |
| __ Ldr(cls_reg.W(), MemOperand(cls_reg.X())); |
| DoReadBarrierOn(cls_reg); |
| |
| QuickEntrypointEnum entrypoint = kQuickAllocObjectInitialized; |
| if (h_klass->IsFinalizable() || |
| !h_klass->IsVisiblyInitialized() || |
| h_klass->IsClassClass() || // Classes cannot be allocated in code |
| !klass->IsInstantiable()) { |
| entrypoint = kQuickAllocObjectWithChecks; |
| } |
| InvokeRuntime(entrypoint, dex_pc); |
| __ Dmb(InnerShareable, BarrierWrites); |
| if (!MoveLocation(CreateNewRegisterLocation(vreg, DataType::Type::kReference, next), |
| calling_convention.GetReturnLocation(DataType::Type::kReference), |
| DataType::Type::kReference)) { |
| return false; |
| } |
| if (HitUnimplemented()) { |
| return false; |
| } |
| UpdateLocal(vreg, /* is_object= */ true, /* can_be_null= */ false); |
| return true; |
| } |
| |
| bool FastCompilerARM64::BuildCheckCast(uint32_t vreg, dex::TypeIndex type_index, uint32_t dex_pc) { |
| if (!EnsureHasFrame()) { |
| return false; |
| } |
| |
| InvokeRuntimeCallingConvention calling_convention; |
| UseScratchRegisterScope temps(GetVIXLAssembler()); |
| Register cls = calling_convention.GetRegisterAt(1); |
| Register obj_cls = calling_convention.GetRegisterAt(2); |
| Register obj = WRegisterFrom(GetExistingRegisterLocation(vreg, DataType::Type::kReference)); |
| if (HitUnimplemented()) { |
| return false; |
| } |
| |
| ScopedObjectAccess soa(Thread::Current()); |
| ObjPtr<mirror::Class> klass = dex_compilation_unit_.GetClassLinker()->ResolveType( |
| type_index, dex_compilation_unit_.GetDexCache(), dex_compilation_unit_.GetClassLoader()); |
| if (klass == nullptr || !method_->GetDeclaringClass()->CanAccess(klass)) { |
| soa.Self()->ClearException(); |
| unimplemented_reason_ = "UnsupportedCheckCast"; |
| return false; |
| } |
| Handle<mirror::Class> h_klass = handles_->NewHandle(klass); |
| |
| vixl::aarch64::Label exit, read_barrier_exit; |
| __ Cbz(obj, &exit); |
| __ Ldr(cls.W(), jit_patches_.DeduplicateJitClassLiteral(GetDexFile(), |
| type_index, |
| h_klass , |
| code_generation_data_.get())); |
| __ Ldr(cls.W(), MemOperand(cls.X())); |
| __ Ldr(obj_cls.W(), MemOperand(obj.X())); |
| __ Cmp(cls.W(), obj_cls.W()); |
| __ B(eq, &exit); |
| |
| // Read barrier on the GC Root. |
| DoReadBarrierOn(cls, &read_barrier_exit); |
| // Read barrier on the object's class. |
| DoReadBarrierOn(obj_cls, &read_barrier_exit, /* do_mr_check= */ false); |
| |
| __ Bind(&read_barrier_exit); |
| __ Cmp(cls.W(), obj_cls.W()); |
| __ B(eq, &exit); |
| if (!MoveLocation(LocationFrom(calling_convention.GetRegisterAt(0)), |
| LocationFrom(obj), |
| DataType::Type::kReference)) { |
| return false; |
| } |
| InvokeRuntime(kQuickCheckInstanceOf, dex_pc); |
| |
| __ Bind(&exit); |
| return true; |
| } |
| |
| bool FastCompilerARM64::BuildInstanceOf(uint32_t vreg, |
| uint32_t vreg_result, |
| dex::TypeIndex type_index, |
| uint32_t dex_pc) { |
| if (!EnsureHasFrame()) { |
| return false; |
| } |
| |
| InvokeRuntimeCallingConvention calling_convention; |
| Register cls = calling_convention.GetRegisterAt(1); |
| // Use a temporary register for `obj_cls`. Cannot be a vixl temp as it needs |
| // to survive a read barrier. |
| Register obj_cls = calling_convention.GetRegisterAt(0); |
| Register obj = WRegisterFrom(GetExistingRegisterLocation(vreg, DataType::Type::kReference)); |
| Location result = GetExistingRegisterLocation(vreg_result, DataType::Type::kInt32); |
| if (HitUnimplemented()) { |
| return false; |
| } |
| |
| vixl::aarch64::Label exit, read_barrier_exit, set_zero, set_one; |
| { |
| ScopedObjectAccess soa(Thread::Current()); |
| ObjPtr<mirror::Class> klass = dex_compilation_unit_.GetClassLinker()->ResolveType( |
| type_index, dex_compilation_unit_.GetDexCache(), dex_compilation_unit_.GetClassLoader()); |
| if (klass == nullptr || !method_->GetDeclaringClass()->CanAccess(klass)) { |
| soa.Self()->ClearException(); |
| unimplemented_reason_ = "UnsupportedCheckCast"; |
| return false; |
| } |
| Handle<mirror::Class> h_klass = handles_->NewHandle(klass); |
| __ Cbz(obj, &set_zero); |
| __ Ldr(cls.W(), jit_patches_.DeduplicateJitClassLiteral(GetDexFile(), |
| type_index, |
| h_klass, |
| code_generation_data_.get())); |
| } |
| __ Ldr(cls.W(), MemOperand(cls.X())); |
| __ Ldr(obj_cls.W(), MemOperand(obj.X())); |
| __ Cmp(cls.W(), obj_cls.W()); |
| __ B(eq, &set_one); |
| |
| // Read barrier on the GC Root. |
| DoReadBarrierOn(cls, &read_barrier_exit); |
| // Read barrier on the object's class. |
| DoReadBarrierOn(obj_cls, &read_barrier_exit, /* do_mr_check= */ false); |
| |
| __ Bind(&read_barrier_exit); |
| __ Cmp(cls.W(), obj_cls.W()); |
| __ B(eq, &set_one); |
| // We clobber `obj_cls` here, which is fine as we don't need it anymore. |
| if (!MoveLocation(LocationFrom(calling_convention.GetRegisterAt(0)), |
| LocationFrom(obj), |
| DataType::Type::kReference)) { |
| return false; |
| } |
| InvokeRuntime(kQuickInstanceofNonTrivial, dex_pc); |
| if (!MoveLocation(result, |
| calling_convention.GetReturnLocation(DataType::Type::kInt32), |
| DataType::Type::kInt32)) { |
| return false; |
| } |
| __ B(&exit); |
| __ Bind(&set_zero); |
| if (!MoveLocation(result, |
| Location::ConstantLocation(new (allocator_) HIntConstant(0)), |
| DataType::Type::kInt32)) { |
| return false; |
| } |
| __ B(&exit); |
| __ Bind(&set_one); |
| if (!MoveLocation(result, |
| Location::ConstantLocation(new (allocator_) HIntConstant(1)), |
| DataType::Type::kInt32)) { |
| return false; |
| } |
| __ Bind(&exit); |
| return true; |
| } |
| |
| void FastCompilerARM64::DoReadBarrierOn(Register reg, |
| vixl::aarch64::Label* exit, |
| bool do_mr_check) { |
| DCHECK(has_frame_); |
| vixl::aarch64::Label local_exit; |
| if (do_mr_check) { |
| __ Cbz(mr, (exit != nullptr) ? exit : &local_exit); |
| } |
| int32_t entry_point_offset = |
| Thread::ReadBarrierMarkEntryPointsOffset<kArm64PointerSize>(reg.GetCode()); |
| __ Ldr(lr, MemOperand(tr, entry_point_offset)); |
| __ Blr(lr); |
| if (exit == nullptr && do_mr_check) { |
| __ Bind(&local_exit); |
| } |
| } |
| |
| bool FastCompilerARM64::CanGenerateCodeFor(ArtField* field, bool can_receiver_be_null) { |
| if (field == nullptr) { |
| // Clear potential resolution exception. |
| Thread::Current()->ClearException(); |
| unimplemented_reason_ = "UnresolvedField"; |
| return false; |
| } |
| if (field->IsVolatile()) { |
| unimplemented_reason_ = "VolatileField"; |
| return false; |
| } |
| |
| if (can_receiver_be_null) { |
| if (!CanDoImplicitNullCheckOn(field->GetOffset().Uint32Value())) { |
| unimplemented_reason_ = "TooLargeFieldOffset"; |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| #define DO_CASE(arm_op, op, other) \ |
| case arm_op: { \ |
| if (constant op other) { \ |
| __ B(label); \ |
| } \ |
| return true; \ |
| } \ |
| |
| template<vixl::aarch64::Condition kCond, bool kCompareWithZero> |
| bool FastCompilerARM64::If_21_22t(const Instruction& instruction, uint32_t dex_pc) { |
| DCHECK_EQ(kCompareWithZero ? Instruction::Format::k21t : Instruction::Format::k22t, |
| Instruction::FormatOf(instruction.Opcode())); |
| if (!EnsureHasFrame()) { |
| return false; |
| } |
| int32_t target_offset = kCompareWithZero ? instruction.VRegB_21t() : instruction.VRegC_22t(); |
| DCHECK_EQ(target_offset, instruction.GetTargetOffset()); |
| if (target_offset < 0) { |
| // TODO: Support for negative branches requires two passes. |
| unimplemented_reason_ = "NegativeBranch"; |
| return false; |
| } |
| int32_t register_index = kCompareWithZero ? instruction.VRegA_21t() : instruction.VRegA_22t(); |
| vixl::aarch64::Label* label = GetLabelOf(dex_pc + target_offset); |
| Location location = vreg_locations_[register_index]; |
| |
| if (kCompareWithZero) { |
| // We are going to branch, move all constants to registers to make the merge |
| // point use the same locations. |
| MoveConstantsToRegisters(); |
| UpdateMasks(dex_pc + target_offset); |
| if (location.IsConstant()) { |
| DCHECK(location.GetConstant()->IsIntConstant()); |
| int32_t constant = location.GetConstant()->AsIntConstant()->GetValue(); |
| switch (kCond) { |
| DO_CASE(vixl::aarch64::eq, ==, 0); |
| DO_CASE(vixl::aarch64::ne, !=, 0); |
| DO_CASE(vixl::aarch64::lt, <, 0); |
| DO_CASE(vixl::aarch64::le, <=, 0); |
| DO_CASE(vixl::aarch64::gt, >, 0); |
| DO_CASE(vixl::aarch64::ge, >=, 0); |
| } |
| return true; |
| } else if (location.IsRegister()) { |
| CPURegister reg = CPURegisterFrom(location, DataType::Type::kInt32); |
| switch (kCond) { |
| case vixl::aarch64::eq: { |
| __ Cbz(Register(reg), label); |
| return true; |
| } |
| case vixl::aarch64::ne: { |
| __ Cbnz(Register(reg), label); |
| return true; |
| } |
| default: { |
| __ Cmp(Register(reg), 0); |
| __ B(kCond, label); |
| return true; |
| } |
| } |
| } else { |
| DCHECK(location.IsStackSlot()); |
| unimplemented_reason_ = "CompareWithZeroOnStackSlot"; |
| } |
| return false; |
| } |
| |
| // !kCompareWithZero |
| Location other_location = vreg_locations_[instruction.VRegB_22t()]; |
| // We are going to branch, move all constants to registers to make the merge |
| // point use the same locations. |
| MoveConstantsToRegisters(); |
| UpdateMasks(dex_pc + target_offset); |
| if (location.IsConstant() && other_location.IsConstant()) { |
| int32_t constant = location.GetConstant()->AsIntConstant()->GetValue(); |
| int32_t other_constant = other_location.GetConstant()->AsIntConstant()->GetValue(); |
| switch (kCond) { |
| DO_CASE(vixl::aarch64::eq, ==, other_constant); |
| DO_CASE(vixl::aarch64::ne, !=, other_constant); |
| DO_CASE(vixl::aarch64::lt, <, other_constant); |
| DO_CASE(vixl::aarch64::le, <=, other_constant); |
| DO_CASE(vixl::aarch64::gt, >, other_constant); |
| DO_CASE(vixl::aarch64::ge, >=, other_constant); |
| } |
| return true; |
| } |
| // Reload the locations, which can now be registers. |
| location = vreg_locations_[register_index]; |
| other_location = vreg_locations_[instruction.VRegB_22t()]; |
| if (location.IsRegister() && other_location.IsRegister()) { |
| CPURegister reg = CPURegisterFrom(location, DataType::Type::kInt32); |
| CPURegister other_reg = CPURegisterFrom(other_location, DataType::Type::kInt32); |
| __ Cmp(Register(reg), Register(other_reg)); |
| __ B(kCond, label); |
| return true; |
| } |
| |
| unimplemented_reason_ = "UnimplementedCompare"; |
| return false; |
| } |
| #undef DO_CASE |
| |
| bool FastCompilerARM64::DoGet(const MemOperand& mem, |
| uint16_t field_index, |
| Instruction::Code opcode, |
| uint32_t dest_reg, |
| bool can_receiver_be_null, |
| bool is_object, |
| uint32_t dex_pc, |
| const Instruction* next) { |
| if (is_object) { |
| Register dst = WRegisterFrom( |
| CreateNewRegisterLocation(dest_reg, DataType::Type::kReference, next)); |
| if (HitUnimplemented()) { |
| return false; |
| } |
| { |
| // Ensure the pc position is recorded immediately after the load instruction. |
| EmissionCheckScope guard(GetVIXLAssembler(), kMaxMacroInstructionSizeInBytes); |
| __ Ldr(dst, mem); |
| if (can_receiver_be_null) { |
| RecordPcInfo(dex_pc); |
| } |
| } |
| UpdateLocal(dest_reg, /* is_object= */ true); |
| DoReadBarrierOn(dst); |
| return true; |
| } |
| |
| // Ensure the pc position is recorded immediately after the load instruction. |
| EmissionCheckScope guard(GetVIXLAssembler(), kMaxMacroInstructionSizeInBytes); |
| switch (opcode) { |
| case Instruction::SGET_BOOLEAN: |
| case Instruction::IGET_BOOLEAN: { |
| Register dst = WRegisterFrom( |
| CreateNewRegisterLocation(dest_reg, DataType::Type::kInt32, next)); |
| __ Ldrb(Register(dst), mem); |
| break; |
| } |
| case Instruction::SGET_BYTE: |
| case Instruction::IGET_BYTE: { |
| Register dst = WRegisterFrom( |
| CreateNewRegisterLocation(dest_reg, DataType::Type::kInt32, next)); |
| __ Ldrsb(Register(dst), mem); |
| break; |
| } |
| case Instruction::SGET_CHAR: |
| case Instruction::IGET_CHAR: { |
| Register dst = WRegisterFrom( |
| CreateNewRegisterLocation(dest_reg, DataType::Type::kInt32, next)); |
| __ Ldrh(Register(dst), mem); |
| break; |
| } |
| case Instruction::SGET_SHORT: |
| case Instruction::IGET_SHORT: { |
| Register dst = WRegisterFrom( |
| CreateNewRegisterLocation(dest_reg, DataType::Type::kInt32, next)); |
| __ Ldrsh(Register(dst), mem); |
| break; |
| } |
| case Instruction::SGET: |
| case Instruction::IGET: { |
| const dex::FieldId& field_id = GetDexFile().GetFieldId(field_index); |
| const char* type = GetDexFile().GetFieldTypeDescriptor(field_id); |
| DataType::Type field_type = DataType::FromShorty(type[0]); |
| if (DataType::IsFloatingPointType(field_type)) { |
| VRegister dst = SRegisterFrom( |
| CreateNewRegisterLocation(dest_reg, field_type, next)); |
| __ Ldr(dst, mem); |
| } else { |
| Register dst = WRegisterFrom( |
| CreateNewRegisterLocation(dest_reg, DataType::Type::kInt32, next)); |
| __ Ldr(dst, mem); |
| } |
| if (HitUnimplemented()) { |
| return false; |
| } |
| break; |
| } |
| default: |
| unimplemented_reason_ = "UnimplementedGet"; |
| return false; |
| } |
| UpdateLocal(dest_reg, is_object); |
| if (can_receiver_be_null) { |
| RecordPcInfo(dex_pc); |
| } |
| return true; |
| } |
| |
| bool FastCompilerARM64::ProcessDexInstruction(const Instruction& instruction, |
| uint32_t dex_pc, |
| const Instruction* next) { |
| bool is_object = false; |
| switch (instruction.Opcode()) { |
| case Instruction::CONST_4: { |
| int32_t register_index = instruction.VRegA_11n(); |
| int32_t constant = instruction.VRegB_11n(); |
| vreg_locations_[register_index] = |
| Location::ConstantLocation(new (allocator_) HIntConstant(constant)); |
| UpdateLocal(register_index, /* is_object= */ false); |
| return true; |
| } |
| |
| case Instruction::CONST_16: { |
| int32_t register_index = instruction.VRegA_21s(); |
| int32_t constant = instruction.VRegB_21s(); |
| vreg_locations_[register_index] = |
| Location::ConstantLocation(new (allocator_) HIntConstant(constant)); |
| UpdateLocal(register_index, /* is_object= */ false); |
| return true; |
| } |
| |
| case Instruction::CONST: { |
| break; |
| } |
| |
| case Instruction::CONST_HIGH16: { |
| break; |
| } |
| |
| case Instruction::CONST_WIDE_16: { |
| break; |
| } |
| |
| case Instruction::CONST_WIDE_32: { |
| break; |
| } |
| |
| case Instruction::CONST_WIDE: { |
| break; |
| } |
| |
| case Instruction::CONST_WIDE_HIGH16: { |
| break; |
| } |
| |
| case Instruction::MOVE: |
| case Instruction::MOVE_FROM16: |
| case Instruction::MOVE_16: { |
| break; |
| } |
| |
| case Instruction::MOVE_WIDE: |
| case Instruction::MOVE_WIDE_FROM16: |
| case Instruction::MOVE_WIDE_16: { |
| break; |
| } |
| |
| case Instruction::MOVE_OBJECT: |
| case Instruction::MOVE_OBJECT_16: |
| case Instruction::MOVE_OBJECT_FROM16: { |
| break; |
| } |
| |
| case Instruction::RETURN_VOID: { |
| if (method_->IsConstructor() && |
| !method_->IsStatic() && |
| dex_compilation_unit_.RequiresConstructorBarrier()) { |
| __ Dmb(InnerShareable, BarrierWrites); |
| } |
| PopFrameAndReturn(); |
| return true; |
| } |
| |
| #define IF_XX(comparison, cond) \ |
| case Instruction::IF_##cond: \ |
| return If_21_22t<comparison, /* kCompareWithZero= */ false>(instruction, dex_pc); \ |
| case Instruction::IF_##cond##Z: \ |
| return If_21_22t<comparison, /* kCompareWithZero= */ true>(instruction, dex_pc); |
| |
| IF_XX(vixl::aarch64::eq, EQ); |
| IF_XX(vixl::aarch64::ne, NE); |
| IF_XX(vixl::aarch64::lt, LT); |
| IF_XX(vixl::aarch64::le, LE); |
| IF_XX(vixl::aarch64::gt, GT); |
| IF_XX(vixl::aarch64::ge, GE); |
| |
| case Instruction::GOTO: |
| case Instruction::GOTO_16: |
| case Instruction::GOTO_32: { |
| break; |
| } |
| |
| case Instruction::RETURN: |
| case Instruction::RETURN_OBJECT: { |
| int32_t register_index = instruction.VRegA_11x(); |
| InvokeDexCallingConventionVisitorARM64 convention; |
| if (!MoveLocation(convention.GetReturnLocation(return_type_), |
| vreg_locations_[register_index], |
| return_type_)) { |
| return false; |
| } |
| if (has_frame_) { |
| // We may have used the "record last instruction before return in return |
| // register" optimization (see `CreateNewRegisterLocation`), |
| // so set the returned register back to a callee save location in case the |
| // method has a frame and there are instructions after this return that |
| // may use this register. |
| uint32_t register_code = kAvailableCalleeSaveRegisters[register_index].GetCode(); |
| vreg_locations_[register_index] = Location::RegisterLocation(register_code); |
| } |
| PopFrameAndReturn(); |
| return true; |
| } |
| |
| case Instruction::RETURN_WIDE: { |
| break; |
| } |
| |
| case Instruction::INVOKE_DIRECT: |
| case Instruction::INVOKE_DIRECT_RANGE: |
| return HandleInvoke(instruction, dex_pc, kDirect); |
| case Instruction::INVOKE_INTERFACE: |
| case Instruction::INVOKE_INTERFACE_RANGE: |
| return HandleInvoke(instruction, dex_pc, kInterface); |
| case Instruction::INVOKE_STATIC: |
| case Instruction::INVOKE_STATIC_RANGE: |
| return HandleInvoke(instruction, dex_pc, kStatic); |
| case Instruction::INVOKE_SUPER: |
| case Instruction::INVOKE_SUPER_RANGE: |
| return HandleInvoke(instruction, dex_pc, kSuper); |
| case Instruction::INVOKE_VIRTUAL: |
| case Instruction::INVOKE_VIRTUAL_RANGE: { |
| return HandleInvoke(instruction, dex_pc, kVirtual); |
| } |
| |
| case Instruction::INVOKE_POLYMORPHIC: { |
| break; |
| } |
| |
| case Instruction::INVOKE_POLYMORPHIC_RANGE: { |
| break; |
| } |
| |
| case Instruction::INVOKE_CUSTOM: { |
| break; |
| } |
| |
| case Instruction::INVOKE_CUSTOM_RANGE: { |
| break; |
| } |
| |
| case Instruction::NEG_INT: { |
| break; |
| } |
| |
| case Instruction::NEG_LONG: { |
| break; |
| } |
| |
| case Instruction::NEG_FLOAT: { |
| break; |
| } |
| |
| case Instruction::NEG_DOUBLE: { |
| break; |
| } |
| |
| case Instruction::NOT_INT: { |
| break; |
| } |
| |
| case Instruction::NOT_LONG: { |
| break; |
| } |
| |
| case Instruction::INT_TO_LONG: { |
| break; |
| } |
| |
| case Instruction::INT_TO_FLOAT: { |
| break; |
| } |
| |
| case Instruction::INT_TO_DOUBLE: { |
| break; |
| } |
| |
| case Instruction::LONG_TO_INT: { |
| break; |
| } |
| |
| case Instruction::LONG_TO_FLOAT: { |
| break; |
| } |
| |
| case Instruction::LONG_TO_DOUBLE: { |
| break; |
| } |
| |
| case Instruction::FLOAT_TO_INT: { |
| break; |
| } |
| |
| case Instruction::FLOAT_TO_LONG: { |
| break; |
| } |
| |
| case Instruction::FLOAT_TO_DOUBLE: { |
| break; |
| } |
| |
| case Instruction::DOUBLE_TO_INT: { |
| break; |
| } |
| |
| case Instruction::DOUBLE_TO_LONG: { |
| break; |
| } |
| |
| case Instruction::DOUBLE_TO_FLOAT: { |
| break; |
| } |
| |
| case Instruction::INT_TO_BYTE: { |
| break; |
| } |
| |
| case Instruction::INT_TO_SHORT: { |
| break; |
| } |
| |
| case Instruction::INT_TO_CHAR: { |
| break; |
| } |
| |
| case Instruction::ADD_INT: { |
| break; |
| } |
| |
| case Instruction::ADD_LONG: { |
| break; |
| } |
| |
| case Instruction::ADD_DOUBLE: { |
| break; |
| } |
| |
| case Instruction::ADD_FLOAT: { |
| break; |
| } |
| |
| case Instruction::SUB_INT: { |
| break; |
| } |
| |
| case Instruction::SUB_LONG: { |
| break; |
| } |
| |
| case Instruction::SUB_FLOAT: { |
| break; |
| } |
| |
| case Instruction::SUB_DOUBLE: { |
| break; |
| } |
| |
| case Instruction::ADD_INT_2ADDR: { |
| break; |
| } |
| |
| case Instruction::MUL_INT: { |
| break; |
| } |
| |
| case Instruction::MUL_LONG: { |
| break; |
| } |
| |
| case Instruction::MUL_FLOAT: { |
| break; |
| } |
| |
| case Instruction::MUL_DOUBLE: { |
| break; |
| } |
| |
| case Instruction::DIV_INT: { |
| break; |
| } |
| |
| case Instruction::DIV_LONG: { |
| break; |
| } |
| |
| case Instruction::DIV_FLOAT: { |
| break; |
| } |
| |
| case Instruction::DIV_DOUBLE: { |
| break; |
| } |
| |
| case Instruction::REM_INT: { |
| break; |
| } |
| |
| case Instruction::REM_LONG: { |
| break; |
| } |
| |
| case Instruction::REM_FLOAT: { |
| break; |
| } |
| |
| case Instruction::REM_DOUBLE: { |
| break; |
| } |
| |
| case Instruction::AND_INT: { |
| break; |
| } |
| |
| case Instruction::AND_LONG: { |
| break; |
| } |
| |
| case Instruction::SHL_INT: { |
| break; |
| } |
| |
| case Instruction::SHL_LONG: { |
| break; |
| } |
| |
| case Instruction::SHR_INT: { |
| break; |
| } |
| |
| case Instruction::SHR_LONG: { |
| break; |
| } |
| |
| case Instruction::USHR_INT: { |
| break; |
| } |
| |
| case Instruction::USHR_LONG: { |
| break; |
| } |
| |
| case Instruction::OR_INT: { |
| break; |
| } |
| |
| case Instruction::OR_LONG: { |
| break; |
| } |
| |
| case Instruction::XOR_INT: { |
| break; |
| } |
| |
| case Instruction::XOR_LONG: { |
| break; |
| } |
| |
| case Instruction::ADD_LONG_2ADDR: { |
| break; |
| } |
| |
| case Instruction::ADD_DOUBLE_2ADDR: { |
| break; |
| } |
| |
| case Instruction::ADD_FLOAT_2ADDR: { |
| break; |
| } |
| |
| case Instruction::SUB_INT_2ADDR: { |
| break; |
| } |
| |
| case Instruction::SUB_LONG_2ADDR: { |
| break; |
| } |
| |
| case Instruction::SUB_FLOAT_2ADDR: { |
| break; |
| } |
| |
| case Instruction::SUB_DOUBLE_2ADDR: { |
| break; |
| } |
| |
| case Instruction::MUL_INT_2ADDR: { |
| break; |
| } |
| |
| case Instruction::MUL_LONG_2ADDR: { |
| break; |
| } |
| |
| case Instruction::MUL_FLOAT_2ADDR: { |
| break; |
| } |
| |
| case Instruction::MUL_DOUBLE_2ADDR: { |
| break; |
| } |
| |
| case Instruction::DIV_INT_2ADDR: { |
| break; |
| } |
| |
| case Instruction::DIV_LONG_2ADDR: { |
| break; |
| } |
| |
| case Instruction::REM_INT_2ADDR: { |
| break; |
| } |
| |
| case Instruction::REM_LONG_2ADDR: { |
| break; |
| } |
| |
| case Instruction::REM_FLOAT_2ADDR: { |
| break; |
| } |
| |
| case Instruction::REM_DOUBLE_2ADDR: { |
| break; |
| } |
| |
| case Instruction::SHL_INT_2ADDR: { |
| break; |
| } |
| |
| case Instruction::SHL_LONG_2ADDR: { |
| break; |
| } |
| |
| case Instruction::SHR_INT_2ADDR: { |
| break; |
| } |
| |
| case Instruction::SHR_LONG_2ADDR: { |
| break; |
| } |
| |
| case Instruction::USHR_INT_2ADDR: { |
| break; |
| } |
| |
| case Instruction::USHR_LONG_2ADDR: { |
| break; |
| } |
| |
| case Instruction::DIV_FLOAT_2ADDR: { |
| break; |
| } |
| |
| case Instruction::DIV_DOUBLE_2ADDR: { |
| break; |
| } |
| |
| case Instruction::AND_INT_2ADDR: { |
| break; |
| } |
| |
| case Instruction::AND_LONG_2ADDR: { |
| break; |
| } |
| |
| case Instruction::OR_INT_2ADDR: { |
| break; |
| } |
| |
| case Instruction::OR_LONG_2ADDR: { |
| break; |
| } |
| |
| case Instruction::XOR_INT_2ADDR: { |
| break; |
| } |
| |
| case Instruction::XOR_LONG_2ADDR: { |
| break; |
| } |
| |
| case Instruction::ADD_INT_LIT16: { |
| break; |
| } |
| |
| case Instruction::AND_INT_LIT16: { |
| break; |
| } |
| |
| case Instruction::OR_INT_LIT16: { |
| break; |
| } |
| |
| case Instruction::XOR_INT_LIT16: { |
| break; |
| } |
| |
| case Instruction::RSUB_INT: { |
| break; |
| } |
| |
| case Instruction::MUL_INT_LIT16: { |
| break; |
| } |
| |
| case Instruction::ADD_INT_LIT8: { |
| break; |
| } |
| |
| case Instruction::AND_INT_LIT8: { |
| break; |
| } |
| |
| case Instruction::OR_INT_LIT8: { |
| break; |
| } |
| |
| case Instruction::XOR_INT_LIT8: { |
| break; |
| } |
| |
| case Instruction::RSUB_INT_LIT8: { |
| break; |
| } |
| |
| case Instruction::MUL_INT_LIT8: { |
| break; |
| } |
| |
| case Instruction::DIV_INT_LIT16: |
| case Instruction::DIV_INT_LIT8: { |
| break; |
| } |
| |
| case Instruction::REM_INT_LIT16: |
| case Instruction::REM_INT_LIT8: { |
| break; |
| } |
| |
| case Instruction::SHL_INT_LIT8: { |
| break; |
| } |
| |
| case Instruction::SHR_INT_LIT8: { |
| break; |
| } |
| |
| case Instruction::USHR_INT_LIT8: { |
| break; |
| } |
| |
| case Instruction::NEW_INSTANCE: { |
| dex::TypeIndex type_index(instruction.VRegB_21c()); |
| return BuildNewInstance(instruction.VRegA_21c(), type_index, dex_pc, next); |
| } |
| |
| case Instruction::NEW_ARRAY: { |
| break; |
| } |
| |
| case Instruction::FILLED_NEW_ARRAY: { |
| break; |
| } |
| |
| case Instruction::FILLED_NEW_ARRAY_RANGE: { |
| break; |
| } |
| |
| case Instruction::FILL_ARRAY_DATA: { |
| break; |
| } |
| |
| case Instruction::MOVE_RESULT_OBJECT: |
| is_object = true; |
| FALLTHROUGH_INTENDED; |
| case Instruction::MOVE_RESULT: { |
| int32_t register_index = instruction.VRegA_11x(); |
| InvokeDexCallingConventionVisitorARM64 convention; |
| if (!MoveLocation( |
| CreateNewRegisterLocation(register_index, previous_invoke_return_type_, next), |
| convention.GetReturnLocation(previous_invoke_return_type_), |
| previous_invoke_return_type_)) { |
| return false; |
| } |
| if (HitUnimplemented()) { |
| return false; |
| } |
| UpdateLocal(register_index, is_object); |
| return true; |
| } |
| |
| case Instruction::MOVE_RESULT_WIDE: { |
| break; |
| } |
| |
| case Instruction::CMP_LONG: { |
| break; |
| } |
| |
| case Instruction::CMPG_FLOAT: { |
| break; |
| } |
| |
| case Instruction::CMPG_DOUBLE: { |
| break; |
| } |
| |
| case Instruction::CMPL_FLOAT: { |
| break; |
| } |
| |
| case Instruction::CMPL_DOUBLE: { |
| break; |
| } |
| |
| case Instruction::NOP: |
| return true; |
| |
| case Instruction::IGET_OBJECT: |
| is_object = true; |
| FALLTHROUGH_INTENDED; |
| case Instruction::IGET: |
| case Instruction::IGET_WIDE: |
| case Instruction::IGET_BOOLEAN: |
| case Instruction::IGET_BYTE: |
| case Instruction::IGET_CHAR: |
| case Instruction::IGET_SHORT: { |
| uint32_t source_or_dest_reg = instruction.VRegA_22c(); |
| uint32_t obj_reg = instruction.VRegB_22c(); |
| uint16_t field_index = instruction.VRegC_22c(); |
| bool can_receiver_be_null = CanBeNull(obj_reg); |
| ArtField* field = nullptr; |
| { |
| ScopedObjectAccess soa(Thread::Current()); |
| field = ResolveFieldWithAccessChecks(soa.Self(), |
| dex_compilation_unit_.GetClassLinker(), |
| field_index, |
| method_, |
| /* is_static= */ false, |
| /* is_put= */ false, |
| /* resolve_field_type= */ 0u); |
| if (!CanGenerateCodeFor(field, can_receiver_be_null)) { |
| return false; |
| } |
| } |
| |
| if (can_receiver_be_null || is_object) { |
| // We need a frame in case the null check throws or there is a read |
| // barrier. |
| if (!EnsureHasFrame()) { |
| return false; |
| } |
| } |
| MemOperand mem = HeapOperand( |
| RegisterFrom(GetExistingRegisterLocation(obj_reg, DataType::Type::kReference), |
| DataType::Type::kReference), |
| field->GetOffset()); |
| if (HitUnimplemented()) { |
| return false; |
| } |
| if (!DoGet(mem, |
| field_index, |
| instruction.Opcode(), |
| source_or_dest_reg, |
| can_receiver_be_null, |
| is_object, |
| dex_pc, |
| next)) { |
| return false; |
| } |
| return true; |
| } |
| |
| case Instruction::IPUT_OBJECT: |
| is_object = true; |
| FALLTHROUGH_INTENDED; |
| case Instruction::IPUT: |
| case Instruction::IPUT_WIDE: |
| case Instruction::IPUT_BOOLEAN: |
| case Instruction::IPUT_BYTE: |
| case Instruction::IPUT_CHAR: |
| case Instruction::IPUT_SHORT: { |
| uint32_t source_reg = instruction.VRegA_22c(); |
| uint32_t obj_reg = instruction.VRegB_22c(); |
| uint16_t field_index = instruction.VRegC_22c(); |
| bool can_receiver_be_null = CanBeNull(obj_reg); |
| ArtField* field = nullptr; |
| { |
| ScopedObjectAccess soa(Thread::Current()); |
| field = ResolveFieldWithAccessChecks(soa.Self(), |
| dex_compilation_unit_.GetClassLinker(), |
| field_index, |
| method_, |
| /* is_static= */ false, |
| /* is_put= */ true, |
| /* resolve_field_type= */ is_object); |
| if (!CanGenerateCodeFor(field, can_receiver_be_null)) { |
| return false; |
| } |
| } |
| |
| if (can_receiver_be_null) { |
| // We need a frame in case the null check throws. |
| if (!EnsureHasFrame()) { |
| return false; |
| } |
| } |
| |
| Register holder = RegisterFrom( |
| GetExistingRegisterLocation(obj_reg, DataType::Type::kReference), |
| DataType::Type::kReference); |
| if (HitUnimplemented()) { |
| return false; |
| } |
| MemOperand mem = HeapOperand(holder, field->GetOffset()); |
| |
| // Need one temp if the stored value is a constant. |
| UseScratchRegisterScope temps(GetVIXLAssembler()); |
| Location src = vreg_locations_[source_reg]; |
| bool assigning_constant = false; |
| if (src.IsConstant()) { |
| assigning_constant = true; |
| if (src.GetConstant()->IsIntConstant() && |
| src.GetConstant()->AsIntConstant()->GetValue() == 0) { |
| src = Location::RegisterLocation(XZR); |
| } else { |
| src = Location::RegisterLocation(temps.AcquireW().GetCode()); |
| if (!MoveLocation(src, vreg_locations_[source_reg], DataType::Type::kInt32)) { |
| return false; |
| } |
| } |
| } else if (src.IsStackSlot() || src.IsDoubleStackSlot()) { |
| unimplemented_reason_ = "IPUTOnStackSlot"; |
| return false; |
| } |
| if (instruction.Opcode() == Instruction::IPUT_OBJECT) { |
| Register reg = WRegisterFrom(src); |
| { |
| // Ensure the pc position is recorded immediately after the store instruction. |
| EmissionCheckScope guard(GetVIXLAssembler(), kMaxMacroInstructionSizeInBytes); |
| __ Str(reg, mem); |
| if (can_receiver_be_null) { |
| RecordPcInfo(dex_pc); |
| } |
| } |
| // If we assign a constant (only null for iput-object), no need for the write |
| // barrier. |
| if (!assigning_constant) { |
| vixl::aarch64::Label exit; |
| __ Cbz(reg, &exit); |
| Register card = temps.AcquireX(); |
| Register temp = temps.AcquireW(); |
| __ Ldr(card, MemOperand(tr, Thread::CardTableOffset<kArm64PointerSize>().Int32Value())); |
| __ Lsr(temp, holder, gc::accounting::CardTable::kCardShift); |
| __ Strb(card, MemOperand(card, temp.X())); |
| __ Bind(&exit); |
| } |
| return true; |
| } |
| // Ensure the pc position is recorded immediately after the store instruction. |
| EmissionCheckScope guard(GetVIXLAssembler(), kMaxMacroInstructionSizeInBytes); |
| switch (instruction.Opcode()) { |
| case Instruction::IPUT_BOOLEAN: |
| case Instruction::IPUT_BYTE: { |
| __ Strb(WRegisterFrom(src), mem); |
| break; |
| } |
| case Instruction::IPUT_CHAR: |
| case Instruction::IPUT_SHORT: { |
| __ Strh(WRegisterFrom(src), mem); |
| break; |
| } |
| case Instruction::IPUT: { |
| if (src.IsFpuRegister()) { |
| __ Str(SRegisterFrom(src), mem); |
| } else { |
| __ Str(WRegisterFrom(src), mem); |
| } |
| break; |
| } |
| default: |
| unimplemented_reason_ = "UnimplementedIPut"; |
| return false; |
| } |
| if (can_receiver_be_null) { |
| RecordPcInfo(dex_pc); |
| } |
| return true; |
| } |
| |
| case Instruction::SGET_OBJECT: |
| is_object = true; |
| FALLTHROUGH_INTENDED; |
| case Instruction::SGET: |
| case Instruction::SGET_WIDE: |
| case Instruction::SGET_BOOLEAN: |
| case Instruction::SGET_BYTE: |
| case Instruction::SGET_CHAR: |
| case Instruction::SGET_SHORT: { |
| if (Runtime::Current()->IsAotCompiler()) { |
| unimplemented_reason_ = "AOTSGet"; |
| return false; |
| } |
| // We need a frame for the read barrier. |
| if (!EnsureHasFrame()) { |
| return false; |
| } |
| ArtField* field = nullptr; |
| uint16_t field_index = instruction.VRegB_21c(); |
| uint32_t source_or_dest_reg = instruction.VRegA_21c(); |
| UseScratchRegisterScope temps(GetVIXLAssembler()); |
| Register temp = temps.AcquireX(); |
| { |
| ScopedObjectAccess soa(Thread::Current()); |
| field = ResolveFieldWithAccessChecks(soa.Self(), |
| dex_compilation_unit_.GetClassLinker(), |
| field_index, |
| method_, |
| /* is_static= */ true, |
| /* is_put= */ false, |
| /* resolve_field_type= */ 0u); |
| if (!CanGenerateCodeFor(field, /* can_receiver_be_null= */ false)) { |
| return false; |
| } |
| Handle<mirror::Class> h_klass = handles_->NewHandle(field->GetDeclaringClass()); |
| if (!h_klass->IsVisiblyInitialized()) { |
| unimplemented_reason_ = "UninitializedSget"; |
| return false; |
| } |
| __ Ldr(temp.W(), jit_patches_.DeduplicateJitClassLiteral(h_klass->GetDexFile(), |
| h_klass->GetDexTypeIndex(), |
| h_klass, |
| code_generation_data_.get())); |
| } |
| __ Ldr(temp.W(), MemOperand(temp.X())); |
| DoReadBarrierOn(temp); |
| MemOperand mem = HeapOperand(temp.W(), field->GetOffset()); |
| if (!DoGet(mem, |
| field_index, |
| instruction.Opcode(), |
| source_or_dest_reg, |
| /* can_receiver_be_null= */ false, |
| is_object, |
| dex_pc, |
| next)) { |
| return false; |
| } |
| return true; |
| } |
| |
| case Instruction::SPUT: |
| case Instruction::SPUT_WIDE: |
| case Instruction::SPUT_OBJECT: |
| case Instruction::SPUT_BOOLEAN: |
| case Instruction::SPUT_BYTE: |
| case Instruction::SPUT_CHAR: |
| case Instruction::SPUT_SHORT: { |
| break; |
| } |
| |
| #define ARRAY_XX(kind, anticipated_type) \ |
| case Instruction::AGET##kind: { \ |
| break; \ |
| } \ |
| case Instruction::APUT##kind: { \ |
| break; \ |
| } |
| |
| ARRAY_XX(, DataType::Type::kInt32); |
| ARRAY_XX(_WIDE, DataType::Type::kInt64); |
| ARRAY_XX(_OBJECT, DataType::Type::kReference); |
| ARRAY_XX(_BOOLEAN, DataType::Type::kBool); |
| ARRAY_XX(_BYTE, DataType::Type::kInt8); |
| ARRAY_XX(_CHAR, DataType::Type::kUint16); |
| ARRAY_XX(_SHORT, DataType::Type::kInt16); |
| |
| case Instruction::ARRAY_LENGTH: { |
| break; |
| } |
| |
| case Instruction::CONST_STRING: { |
| dex::StringIndex string_index(instruction.VRegB_21c()); |
| return BuildLoadString(instruction.VRegA_21c(), string_index, next); |
| } |
| |
| case Instruction::CONST_STRING_JUMBO: { |
| dex::StringIndex string_index(instruction.VRegB_31c()); |
| return BuildLoadString(instruction.VRegA_31c(), string_index, next); |
| } |
| |
| case Instruction::CONST_CLASS: { |
| break; |
| } |
| |
| case Instruction::CONST_METHOD_HANDLE: { |
| break; |
| } |
| |
| case Instruction::CONST_METHOD_TYPE: { |
| break; |
| } |
| |
| case Instruction::MOVE_EXCEPTION: { |
| break; |
| } |
| |
| case Instruction::THROW: { |
| if (!EnsureHasFrame()) { |
| return false; |
| } |
| int32_t reg = instruction.VRegA_11x(); |
| InvokeRuntimeCallingConvention calling_convention; |
| if (!MoveLocation(LocationFrom(calling_convention.GetRegisterAt(0)), |
| vreg_locations_[reg], |
| DataType::Type::kReference)) { |
| return false; |
| } |
| InvokeRuntime(kQuickDeliverException, dex_pc); |
| return true; |
| } |
| |
| case Instruction::INSTANCE_OF: { |
| uint8_t destination = instruction.VRegA_22c(); |
| uint8_t reference = instruction.VRegB_22c(); |
| dex::TypeIndex type_index(instruction.VRegC_22c()); |
| return BuildInstanceOf(reference, destination, type_index, dex_pc); |
| } |
| |
| case Instruction::CHECK_CAST: { |
| uint8_t reference = instruction.VRegA_21c(); |
| dex::TypeIndex type_index(instruction.VRegB_21c()); |
| return BuildCheckCast(reference, type_index, dex_pc); |
| } |
| |
| case Instruction::MONITOR_ENTER: { |
| break; |
| } |
| |
| case Instruction::MONITOR_EXIT: { |
| break; |
| } |
| |
| case Instruction::SPARSE_SWITCH: |
| case Instruction::PACKED_SWITCH: { |
| break; |
| } |
| |
| case Instruction::UNUSED_3E ... Instruction::UNUSED_43: |
| case Instruction::UNUSED_73: |
| case Instruction::UNUSED_79: |
| case Instruction::UNUSED_7A: |
| case Instruction::UNUSED_E3 ... Instruction::UNUSED_F9: { |
| break; |
| } |
| } |
| unimplemented_reason_ = instruction.Name(); |
| return false; |
| } // NOLINT(readability/fn_size) |
| |
| bool FastCompilerARM64::Compile() { |
| if (!InitializeParameters()) { |
| DCHECK(HitUnimplemented()); |
| AbortCompilation(); |
| return false; |
| } |
| if (!ProcessInstructions()) { |
| DCHECK(HitUnimplemented()); |
| AbortCompilation(); |
| return false; |
| } |
| DCHECK(!HitUnimplemented()) << GetUnimplementedReason(); |
| |
| StackMapStream* stack_map_stream = code_generation_data_->GetStackMapStream(); |
| if (!has_frame_) { |
| stack_map_stream->BeginMethod(/* frame_size= */ 0u, |
| /* core_spill_mask= */ 0u, |
| /* fp_spill_mask= */ 0u, |
| GetCodeItemAccessor().RegistersSize(), |
| /* is_compiling_baseline= */ true, |
| /* is_debuggable= */ false); |
| } |
| |
| // Catch stack maps are pushed at the end. |
| for (auto pair : catch_stack_maps_) { |
| uint32_t dex_pc = pair.first; |
| uint32_t native_pc = pair.second; |
| std::vector<uint32_t> dex_pc_list_for_verification; |
| if (kIsDebugBuild) { |
| dex_pc_list_for_verification.push_back(dex_pc); |
| } |
| stack_map_stream->BeginStackMapEntry(dex_pc, |
| native_pc, |
| /* register_mask= */ 0, |
| /* sp_mask= */ nullptr, |
| StackMap::Kind::Catch, |
| /* needs_vreg_info= */ false, |
| dex_pc_list_for_verification); |
| stack_map_stream->EndStackMapEntry(); |
| } |
| stack_map_stream->EndMethod(assembler_.CodeSize()); |
| assembler_.FinalizeCode(); |
| |
| if (VLOG_IS_ON(jit)) { |
| // Dump the generated code |
| { |
| ScopedObjectAccess soa(Thread::Current()); |
| VLOG(jit) << "Dumping generated fast baseline code for " << method_->PrettyMethod(); |
| } |
| FILE* file = tmpfile(); |
| MacroAssembler* masm = GetVIXLAssembler(); |
| PrintDisassembler print_disasm(file); |
| vixl::aarch64::Instruction* dis_start = |
| masm->GetBuffer()->GetStartAddress<vixl::aarch64::Instruction*>(); |
| vixl::aarch64::Instruction* dis_end = |
| masm->GetBuffer()->GetEndAddress<vixl::aarch64::Instruction*>(); |
| print_disasm.DisassembleBuffer(dis_start, dis_end); |
| fseek(file, 0L, SEEK_SET); |
| char buffer[1024]; |
| const char* line; |
| while ((line = fgets(buffer, sizeof(buffer), file)) != nullptr) { |
| VLOG(jit) << std::string(line); |
| } |
| fclose(file); |
| } |
| return true; |
| } |
| |
| } // namespace arm64 |
| |
| std::unique_ptr<FastCompiler> FastCompiler::CompileARM64( |
| ArtMethod* method, |
| ArenaAllocator* allocator, |
| ArenaStack* arena_stack, |
| VariableSizedHandleScope* handles, |
| const CompilerOptions& compiler_options, |
| const DexCompilationUnit& dex_compilation_unit) { |
| if (!compiler_options.GetImplicitNullChecks() || |
| !compiler_options.GetImplicitStackOverflowChecks() || |
| kUseTableLookupReadBarrier || |
| !kReserveMarkingRegister || |
| kPoisonHeapReferences) { |
| // Configurations we don't support. |
| return nullptr; |
| } |
| std::unique_ptr<arm64::FastCompilerARM64> compiler(new arm64::FastCompilerARM64( |
| method, |
| allocator, |
| arena_stack, |
| handles, |
| compiler_options, |
| dex_compilation_unit)); |
| if (compiler->Compile()) { |
| return compiler; |
| } |
| VLOG(jit) << "Did not fast compile because of " << compiler->GetUnimplementedReason(); |
| return nullptr; |
| } |
| |
| } // namespace art |