// Binary encoding of 2^32 for type double. static int64_t constexpr k2Pow32EncodingForDouble = INT64_C(0x41F0000000000000); // Binary encoding of 2^31 for type double. static int64_t constexpr k2Pow31EncodingForDouble = INT64_C(0x41E0000000000000);
// Minimum value for a primitive integer. static int32_t constexpr kPrimIntMin = 0x80000000; // Minimum value for a primitive long. static int64_t constexpr kPrimLongMin = INT64_C(0x8000000000000000);
// Maximum value for a primitive integer. static int32_t constexpr kPrimIntMax = 0x7fffffff; // Maximum value for a primitive long. static int64_t constexpr kPrimLongMax = INT64_C(0x7fffffffffffffff);
// Returns true if the native code generated for this slow path is identical to // the code generated for the slow path for provided `instruction`. virtualbool EmitsSameNativeCodeAsSlowPathForInstruction(
[[maybe_unused]] const HInstruction* instruction,
[[maybe_unused]] const CodeGenerator* codegen) const { returnfalse;
}
// Save live core and floating-point caller-save registers and // update the stack mask in `locations` for registers holding object // references. virtualvoid SaveLiveRegisters(CodeGenerator* codegen, LocationSummary* locations); // Restore live core and floating-point caller-save registers. virtualvoid RestoreLiveRegisters(CodeGenerator* codegen, LocationSummary* locations);
// The current index for core registers.
uint32_t gp_index_ = 0u; // The current index for floating-point registers.
uint32_t float_index_ = 0u; // The current stack index.
uint32_t stack_index_ = 0u;
// Get FP register width in bytes for spilling/restoring in the slow paths. // // Note: In SIMD graphs this should return SIMD register width as all FP and SIMD registers // alias and live SIMD registers are forced to be spilled in full size in the slow paths. virtual size_t GetSlowPathFPWidth() const { // Default implementation. return GetCalleePreservedFPWidth();
}
// Get FP register width required to be preserved by the target ABI. virtual size_t GetCalleePreservedFPWidth() const = 0;
// Get the size of the target SIMD register in bytes. virtual size_t GetSIMDRegisterWidth() const = 0; virtualbool HasOverlappingFPVecRegisters() const { returnfalse; }
virtual uintptr_t GetAddressOf(HBasicBlock* block) = 0; void InitializeCodeGeneration(size_t number_of_spill_slots,
size_t maximum_safepoint_spill_size,
size_t number_of_out_slots, const ArenaVector<HBasicBlock*>& block_order); // Backends can override this as necessary. For most, no special alignment is required. virtual uint32_t GetPreferredSlotsAlignment() const { return1; }
virtualvoid ComputeSpillMask() {
spilled_registers_ = allocated_registers_.Intersect(callee_saves_);
DCHECK_NE(spilled_registers_.GetCoreRegisterSet(), 0u)
<< "At least the return address register must be saved";
}
virtualvoid DumpCoreRegister(std::ostream& stream, int reg) const = 0; virtualvoid DumpFloatingPointRegister(std::ostream& stream, int reg) const = 0; virtualvoid DumpVectorRegister(std::ostream& stream, int reg) const; // Default: unreachable.
// Saves the register in the stack. Returns the size taken on stack. virtual size_t SaveCoreRegister(size_t stack_index, uint32_t reg_id) = 0; // Restores the register from the stack. Returns the size taken on stack. virtual size_t RestoreCoreRegister(size_t stack_index, uint32_t reg_id) = 0;
// Returns true if `invoke` is an implemented intrinsic in this codegen's arch. bool IsImplementedIntrinsic(HInvoke* invoke) const { return invoke->IsIntrinsic() &&
!unimplemented_intrinsics_[static_cast<size_t>(invoke->GetIntrinsic())];
}
RegisterSet GetSlowPathSpills(LocationSummary* locations) const {
DCHECK(locations->OnlyCallsOnSlowPath() ||
(locations->Intrinsified() && locations->CallsOnMainAndSlowPath() &&
!locations->HasCustomSlowPathCallingConvention())); const RegisterSet& live_registers = *locations->GetLiveRegisters(); if (locations->HasCustomSlowPathCallingConvention()) { // Save only the live registers that the custom calling convention wants us to save. return live_registers.Intersect(locations->GetCustomSlowPathCallerSaves());
} else { // Default ABI, we need to spill non-callee-save live registers. return live_registers.Subtract(callee_saves_);
}
}
// For stack overflow checks and native-debug-info entries without dex register // mapping i.e. start of basic block or at frame entry. void RecordPcInfoForFrameOrBlockEntry(uint32_t dex_pc = 0);
// Record native to dex mapping for a suspend point. // The native_pc is used from Assembler::CodePosition. // // Note: As Assembler::CodePosition is target dependent, it does not guarantee the exact native_pc // for the instruction. If the exact native_pc is required it must be provided explicitly. void RecordPcInfo(HInstruction* instruction,
SlowPathCode* slow_path = nullptr, bool native_debug_info = false);
// Record native to dex mapping for a suspend point. Required by runtime. // Do not use directly. Use the method above. void RecordPcInfo(HInstruction* instruction,
uint32_t dex_pc,
uint32_t native_pc,
SlowPathCode* slow_path = nullptr, bool native_debug_info = false);
// Check whether we have already recorded mapping at this PC. bool HasStackMapAtCurrentPc();
// Record extra stack maps if we support native debugging. // // ARM specific behaviour: The recorded native PC might be a branch over pools to instructions // corresponding the dex PC. void MaybeRecordNativeDebugInfoForBlockEntry(uint32_t dex_pc); void MaybeRecordNativeDebugInfo(HInstruction* instruction,
uint32_t dex_pc,
SlowPathCode* slow_path = nullptr);
// Records a stack map which the runtime might use to set catch phi values // during exception delivery. // TODO: Replace with a catch-entering instruction that records the environment. void RecordCatchBlockInfo();
// Returns true if we should check the GC card for consistency purposes. bool ShouldCheckGCCard(DataType::Type type,
HInstruction* value,
WriteBarrierKind write_barrier_kind) const;
// Get the ScopedArenaAllocator used for codegen memory allocation.
ScopedArenaAllocator* GetScopedAllocator();
// Clears the spill slots taken by loop phis in the `LocationSummary` of the // suspend check. This is called when the code generator generates code // for the suspend check at the back edge (instead of where the suspend check // is, which is the loop entry). At this point, the spill slots for the phis // have not been written to. void ClearSpillSlotsFromLoopPhisInStackMap(HSuspendCheck* suspend_check,
HParallelMove* spills) const;
// Helper that returns the offset of the array's length field. // Note: Besides the normal arrays, we also use the HArrayLength for // accessing the String's `count` field in String intrinsics. static uint32_t GetArrayLengthOffset(HArrayLength* array_length);
// Helper that returns the offset of the array's data. // Note: Besides the normal arrays, we also use the HArrayGet for // accessing the String's `value` field in String intrinsics. static uint32_t GetArrayDataOffset(HArrayGet* array_get);
bool InstanceOfNeedsReadBarrier(HInstanceOf* instance_of) { // Used only for `kExactCheck`, `kAbstractClassCheck`, `kClassHierarchyCheck`, // `kArrayObjectCheck` and `kInterfaceCheck`.
DCHECK(instance_of->GetTypeCheckKind() == TypeCheckKind::kExactCheck ||
instance_of->GetTypeCheckKind() == TypeCheckKind::kAbstractClassCheck ||
instance_of->GetTypeCheckKind() == TypeCheckKind::kClassHierarchyCheck ||
instance_of->GetTypeCheckKind() == TypeCheckKind::kArrayObjectCheck ||
instance_of->GetTypeCheckKind() == TypeCheckKind::kInterfaceCheck)
<< instance_of->GetTypeCheckKind(); // If the target class is in the boot or app image, it's non-moveable and it doesn't matter // if we compare it with a from-space or to-space reference, the result is the same. // It's OK to traverse a class hierarchy jumping between from-space and to-space. if (EmitBakerReadBarrier()) {
HInstruction* target_class = instance_of->GetTargetClass(); if (target_class->IsLoadClass()) { return !target_class->AsLoadClass()->IsInImage();
} else {
DCHECK(target_class->IsFieldAccess()); // This could be more precise. Assuming the worst. returntrue;
}
} returnfalse;
}
bool IsTypeCheckSlowPathFatal(HCheckCast* check_cast) { switch (check_cast->GetTypeCheckKind()) { case TypeCheckKind::kExactCheck: case TypeCheckKind::kAbstractClassCheck: case TypeCheckKind::kClassHierarchyCheck: case TypeCheckKind::kArrayObjectCheck: case TypeCheckKind::kInterfaceCheck: {
DCHECK(check_cast->GetTargetClass()->IsLoadClass()); bool needs_read_barrier =
EmitReadBarrier() && !check_cast->GetTargetClass()->AsLoadClass()->IsInImage(); // We do not emit read barriers for HCheckCast, so we can get false negatives // and the slow path shall re-check and simply return if the cast is actually OK. return !needs_read_barrier;
} case TypeCheckKind::kArrayCheck: case TypeCheckKind::kUnresolvedCheck: returnfalse; case TypeCheckKind::kBitstringCheck: returntrue;
}
LOG(FATAL) << "Unreachable";
UNREACHABLE();
}
LocationSummary::CallKind GetCheckCastCallKind(HCheckCast* check_cast) { return (IsTypeCheckSlowPathFatal(check_cast) && !check_cast->CanThrowIntoCatchBlock())
? LocationSummary::kNoCall // In fact, call on a fatal (non-returning) slow path.
: LocationSummary::kCallOnSlowPath;
}
staticbool StoreNeedsWriteBarrier(DataType::Type type, HInstruction* value) { // Check that null value is not represented as an integer constant.
DCHECK_IMPLIES(type == DataType::Type::kReference, !value->IsIntConstant()); return type == DataType::Type::kReference && !value->IsNullConstant();
}
// If we are compiling a graph with the WBE pass enabled, we want to honor the WriteBarrierKind // set during the WBE pass. bool StoreNeedsWriteBarrier(DataType::Type type,
HInstruction* value,
WriteBarrierKind write_barrier_kind) const;
// Performs checks pertaining to an InvokeRuntime call. void ValidateInvokeRuntime(QuickEntrypointEnum entrypoint,
HInstruction* instruction,
SlowPathCode* slow_path);
// Performs checks pertaining to an InvokeRuntimeWithoutRecordingPcInfo call. staticvoid ValidateInvokeRuntimeWithoutRecordingPcInfo(HInstruction* instruction,
SlowPathCode* slow_path);
// Type consistency check, used only in debug builds. staticbool CheckTypeConsistency(HInstruction* instruction);
// Tells whether the stack frame of the compiled method is // considered "empty", that is either actually having a size of zero, // or just containing the saved return address register. bool HasEmptyFrame() const { return GetFrameSize() == (CallPushesPC() ? GetWordSize() : 0);
}
// Check if the desired_string_load_kind is supported. If it is, return it, // otherwise return a fall-back kind that should be used instead. virtual HLoadString::LoadKind GetSupportedLoadStringKind(
HLoadString::LoadKind desired_string_load_kind) = 0;
// Check if the desired_class_load_kind is supported. If it is, return it, // otherwise return a fall-back kind that should be used instead. virtual HLoadClass::LoadKind GetSupportedLoadClassKind(
HLoadClass::LoadKind desired_class_load_kind) = 0;
// Check if the desired_dispatch_info is supported. If it is, return it, // otherwise return a fall-back info that should be used instead. virtual HInvokeStaticOrDirect::DispatchInfo GetSupportedInvokeStaticOrDirectDispatch( const HInvokeStaticOrDirect::DispatchInfo& desired_dispatch_info,
ArtMethod* method) = 0;
// Generate a call to a static or direct method. virtualvoid GenerateStaticOrDirectCall(
HInvokeStaticOrDirect* invoke, Location temp, SlowPathCode* slow_path = nullptr) = 0; // Generate a call to a virtual method. virtualvoid GenerateVirtualCall(
HInvokeVirtual* invoke, Location temp, SlowPathCode* slow_path = nullptr) = 0;
// Copy the result of a call into the given target. virtualvoid MoveFromReturnRegister(Location trg, DataType::Type type) = 0;
// Returns true if `invoke` is an intrinsic with code generation that it is truly there and // call-free (not unimplemented, no bail on instruction features, or call on slow path). // // TODO: Avoid wasting Arena memory. This is done by calling the locations builder on the // instruction and clearing out the locations once result is known. We assume this call only has // creating locations as side effects! virtualbool IsIntrinsicCallFree(HInvoke* invoke) const = 0;
protected: // Patch info used for recording locations of required linker patches and their targets, // i.e. target method, string, type or code identified by their dex file and index, // or boot image .data.img.rel.ro entries identified by the boot image offset. template <typename LabelType> struct PatchInfo {
PatchInfo(const DexFile* dex_file, uint32_t off_or_idx)
: target_dex_file(dex_file), offset_or_index(off_or_idx), label() { }
// Target dex file or null for boot image .data.img.rel.ro patches. const DexFile* target_dex_file; // Either the boot image offset (to write to .data.img.rel.ro) or string/type/method index.
uint32_t offset_or_index; // Label for the instruction to patch.
LabelType label;
};
template <typename RegType> static uint32_t ComputeRegisterMask(const RegType* registers, size_t length) {
uint32_t mask = 0; for (size_t i = 0, e = length; i < e; ++i) {
mask |= (1 << registers[i]);
} return mask;
}
// Returns the location of the first spilled entry for floating point registers, // relative to the stack pointer.
uint32_t GetFpuSpillStart() const { return GetFrameSize() - FrameEntrySpillSize();
}
// Arm64 has its own type for a label, so we need to templatize these methods // to share the logic.
template <typename LabelType>
LabelType* CommonInitializeLabels() { // We use raw array allocations instead of ArenaVector<> because Labels are // non-constructible and non-movable and as such cannot be held in a vector.
size_t size = GetGraph()->GetBlocks().size();
LabelType* labels =
GetGraph()->GetAllocator()->AllocArray<LabelType>(size, kArenaAllocCodeGenerator); for (size_t i = 0; i != size; ++i) { new(labels + i) LabelType();
} return labels;
}
// Registers that cannot be allocated. Codegens should set this up in the constructor.
RegisterSet blocked_registers_;
// Registers that were allocated during linear scan.
RegisterSet allocated_registers_;
// Registers spilled to the method's frame.
RegisterSet spilled_registers_;
// Bitmask of data types that need a register pair. // Codegens should set this up in the constructor if any data type requires a register pair.
uint32_t data_types_requiring_register_pair_;
// The current slow-path that we're generating code for.
SlowPathCode* current_slow_path_;
// The current block index in `block_order_` of the block // we are generating code for.
size_t current_block_index_;
// Whether the method is a leaf method. bool is_leaf_;
// Whether an instruction in the graph accesses the current method. // TODO: Rename: this actually indicates that some instruction in the method // needs the environment including a valid stack frame. bool requires_current_method_;
// The CodeGenerationData contains a ScopedArenaAllocator intended for reusing the // ArenaStack memory allocated in previous passes instead of adding to the memory // held by the ArenaAllocator. This ScopedArenaAllocator is created in // CodeGenerator::Compile() and remains alive until the CodeGenerator is destroyed.
std::unique_ptr<CodeGenerationData> code_generation_data_;
// Which intrinsics we don't have handcrafted code for.
art::ArrayRef<constbool> unimplemented_intrinsics_;
C GetRegisterAt(size_t index) const {
DCHECK_LT(index, number_of_registers_); return registers_[index];
}
F GetFpuRegisterAt(size_t index) const {
DCHECK_LT(index, number_of_fpu_registers_); return fpu_registers_[index];
}
size_t GetStackOffsetOf(size_t index) const { // We still reserve the space for parameters passed by registers. // Add space for the method pointer. returnstatic_cast<size_t>(pointer_size_) + index * kVRegSize;
}
/** *AtemplatedclassSlowPathGeneratorwithatemplatedmethodNewSlowPath() *thatcanbeusedbyanycodegeneratortoshareequivalentslow-pathswith *theobjectiveofreducinggeneratedcodesize. * *InstructionType:instructionthatrequiresSlowPathCodeType *SlowPathCodeType:subclassofSlowPathCode,withconstructorSlowPathCodeType(InstructionType*)
*/ template <typename InstructionType> class SlowPathGenerator {
static_assert(std::is_base_of<HInstruction, InstructionType>::value, "InstructionType is not a subclass of art::HInstruction");
// Creates and adds a new slow-path, if needed, or returns existing one otherwise. // Templating the method (rather than the whole class) on the slow-path type enables // keeping this code at a generic, non architecture-specific place. // // NOTE: This approach assumes each InstructionType only generates one SlowPathCodeType. // To relax this requirement, we would need some RTTI on the stored slow-paths, // or template the class as a whole on SlowPathType. template <typename SlowPathCodeType>
SlowPathCodeType* NewSlowPath(InstructionType* instruction) {
static_assert(std::is_base_of<SlowPathCode, SlowPathCodeType>::value, "SlowPathCodeType is not a subclass of art::SlowPathCode");
static_assert(std::is_constructible<SlowPathCodeType, InstructionType*>::value, "SlowPathCodeType is not constructible from InstructionType*"); // Iterate over potential candidates for sharing. Currently, only same-typed // slow-paths with exactly the same dex-pc are viable candidates. // TODO: pass dex-pc/slow-path-type to run-time to allow even more sharing? const uint32_t dex_pc = instruction->GetDexPc(); auto iter = slow_path_map_.find(dex_pc); if (iter != slow_path_map_.end()) { const ArenaVector<std::pair<InstructionType*, SlowPathCode*>>& candidates = iter->second; for (constauto& it : candidates) {
InstructionType* other_instruction = it.first;
SlowPathCodeType* other_slow_path = down_cast<SlowPathCodeType*>(it.second); // Determine if the instructions allow for slow-path sharing. if (other_slow_path->EmitsSameNativeCodeAsSlowPathForInstruction(instruction, codegen_) &&
HaveSameStackMap(instruction, other_instruction)) { // Can share: reuse existing one. return other_slow_path;
}
}
} else { // First time this dex-pc is seen.
iter = slow_path_map_.Put(dex_pc,
{{}, {graph_->GetAllocator()->Adapter(kArenaAllocSlowPaths)}});
} // Cannot share: create and add new slow-path for this particular dex-pc.
SlowPathCodeType* slow_path = new (codegen_->GetScopedAllocator()) SlowPathCodeType(instruction);
iter->second.emplace_back(std::make_pair(instruction, slow_path));
codegen_->AddSlowPath(slow_path); return slow_path;
}
private: // Tests if both instructions have the same stack map. This ensures the interpreter // will find exactly the same dex-registers at the same entries. bool HaveSameStackMap(const InstructionType* i1, const InstructionType* i2) const {
DCHECK(i1->HasEnvironment());
DCHECK(i2->HasEnvironment()); // We conservatively test if the two instructions find exactly the same instructions // and location in each dex-register. This guarantees they will have the same stack map.
HEnvironment* e1 = i1->GetEnvironment();
HEnvironment* e2 = i2->GetEnvironment(); if (e1->GetParent() != e2->GetParent() || e1->Size() != e2->Size()) { returnfalse;
} for (size_t i = 0, sz = e1->Size(); i < sz; ++i) { if (e1->GetInstructionAt(i) != e2->GetInstructionAt(i) ||
!e1->GetLocationAt(i).Equals(e2->GetLocationAt(i))) { returnfalse;
}
} returntrue;
}
// Map from dex-pc to vector of already existing instruction/slow-path pairs.
ArenaSafeMap<uint32_t, ArenaVector<std::pair<InstructionType*, SlowPathCode*>>> slow_path_map_;
DISALLOW_COPY_AND_ASSIGN(SlowPathGenerator);
};
class InstructionCodeGenerator : public HGraphVisitor { public:
InstructionCodeGenerator(HGraph* graph, CodeGenerator* codegen)
: HGraphVisitor(graph),
deopt_slow_paths_(graph, codegen) {}
protected: // Add slow-path generator for each instruction/slow-path combination that desires sharing. // TODO: under current regime, only deopt sharing make sense; extend later.
SlowPathGenerator<HDeoptimize> deopt_slow_paths_;
};
// Tests if slow-paths for both instructions save the same set of live registers. // // Although, in general, the implementation of a slow-path depends on its type and target // architecture, it typically includes the following: // * Saving live caller-save registers (all or those required by the custom slow-path calling // convention) — preamble. // * Preparing arguments for and calling the corresponding runtime entry point. // // For such slow-paths, this method can be used to check that they have the same preamble, // which is a required condition for deduplication. inlinebool HaveSameSlowPathSavedRegisters(const HInstruction* i1, const HInstruction* i2, const CodeGenerator* codegen) { // CodeGenerator::GetSlowPathSpills returns the set of live registers that will be saved // in an instruction's slow-path (unrelated to the save location). Currently, concrete // implementations of SlowPathCode::SaveLiveRegisters also use this method.
RegisterSet spills1 = codegen->GetSlowPathSpills(i1->GetLocations());
RegisterSet spills2 = codegen->GetSlowPathSpills(i2->GetLocations()); return spills1.GetCoreRegisterSet() == spills2.GetCoreRegisterSet() &&
spills1.GetFpuRegisterSet() == spills2.GetFpuRegisterSet();
}
// Returns true if both instructions are Deoptimize and have the same kind and slow-path spills. inlinebool IsDeoptWithSameKindAndSlowPathSavedRegisters(const HInstruction* i1, const HInstruction* i2, const CodeGenerator* codegen) { if (!i1->IsDeoptimize() || !i2->IsDeoptimize()) { returnfalse;
} // Check that slow-paths have the same argument-preparation part. if (i1->AsDeoptimize()->GetDeoptimizationKind() != i2->AsDeoptimize()->GetDeoptimizationKind()) { returnfalse;
} // Check that slow-paths have the same preamble. return HaveSameSlowPathSavedRegisters(i1, i2, codegen);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.