diff options
Diffstat (limited to 'runtime/oat/stack_map.h')
-rw-r--r-- | runtime/oat/stack_map.h | 604 |
1 files changed, 604 insertions, 0 deletions
diff --git a/runtime/oat/stack_map.h b/runtime/oat/stack_map.h new file mode 100644 index 0000000000..be7c11dfab --- /dev/null +++ b/runtime/oat/stack_map.h @@ -0,0 +1,604 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_RUNTIME_OAT_STACK_MAP_H_ +#define ART_RUNTIME_OAT_STACK_MAP_H_ + +#include <limits> + +#include "arch/instruction_set.h" +#include "base/array_ref.h" +#include "base/bit_memory_region.h" +#include "base/bit_table.h" +#include "base/bit_utils.h" +#include "base/globals.h" +#include "base/logging.h" +#include "base/memory_region.h" +#include "dex/dex_file_types.h" +#include "dex_register_location.h" +#include "quick/quick_method_frame_info.h" + +namespace art { + +namespace linker { +class CodeInfoTableDeduper; +} // namespace linker + +class OatQuickMethodHeader; +class VariableIndentationOutputStream; + +// Size of a frame slot, in bytes. This constant is a signed value, +// to please the compiler in arithmetic operations involving int32_t +// (signed) values. +static constexpr ssize_t kFrameSlotSize = 4; + +// The delta compression of dex register maps means we need to scan the stackmaps backwards. +// We compress the data in such a way so that there is an upper bound on the search distance. +// Max distance 0 means each stack map must be fully defined and no scanning back is allowed. +// If this value is changed, the oat file version should be incremented (for DCHECK to pass). +static constexpr size_t kMaxDexRegisterMapSearchDistance = 32; + +class ArtMethod; +class CodeInfo; +class Stats; + +std::ostream& operator<<(std::ostream& stream, const DexRegisterLocation& reg); + +// Information on Dex register locations for a specific PC. +// Effectively just a convenience wrapper for DexRegisterLocation vector. +// If the size is small enough, it keeps the data on the stack. +// TODO: Replace this with generic purpose "small-vector" implementation. +class DexRegisterMap { + public: + using iterator = DexRegisterLocation*; + using const_iterator = const DexRegisterLocation*; + + // Create map for given number of registers and initialize them to the given value. + DexRegisterMap(size_t count, DexRegisterLocation value) : count_(count), regs_small_{} { + if (count_ <= kSmallCount) { + std::fill_n(regs_small_.begin(), count, value); + } else { + regs_large_.resize(count, value); + } + } + + DexRegisterLocation* data() { + return count_ <= kSmallCount ? regs_small_.data() : regs_large_.data(); + } + const DexRegisterLocation* data() const { + return count_ <= kSmallCount ? regs_small_.data() : regs_large_.data(); + } + + iterator begin() { return data(); } + iterator end() { return data() + count_; } + const_iterator begin() const { return data(); } + const_iterator end() const { return data() + count_; } + size_t size() const { return count_; } + bool empty() const { return count_ == 0; } + + DexRegisterLocation& operator[](size_t index) { + DCHECK_LT(index, count_); + return data()[index]; + } + const DexRegisterLocation& operator[](size_t index) const { + DCHECK_LT(index, count_); + return data()[index]; + } + + size_t GetNumberOfLiveDexRegisters() const { + return std::count_if(begin(), end(), [](auto& loc) { return loc.IsLive(); }); + } + + bool HasAnyLiveDexRegisters() const { + return std::any_of(begin(), end(), [](auto& loc) { return loc.IsLive(); }); + } + + void Dump(VariableIndentationOutputStream* vios) const; + + private: + // Store the data inline if the number of registers is small to avoid memory allocations. + // If count_ <= kSmallCount, we use the regs_small_ array, and regs_large_ otherwise. + static constexpr size_t kSmallCount = 16; + size_t count_; + std::array<DexRegisterLocation, kSmallCount> regs_small_; + dchecked_vector<DexRegisterLocation> regs_large_; +}; + +/** + * A Stack Map holds compilation information for a specific PC necessary for: + * - Mapping it to a dex PC, + * - Knowing which stack entries are objects, + * - Knowing which registers hold objects, + * - Knowing the inlining information, + * - Knowing the values of dex registers. + */ +class StackMap : public BitTableAccessor<8> { + public: + enum Kind { + Default = -1, + Catch = 0, + OSR = 1, + Debug = 2, + }; + BIT_TABLE_HEADER(StackMap) + BIT_TABLE_COLUMN(0, Kind) + BIT_TABLE_COLUMN(1, PackedNativePc) + BIT_TABLE_COLUMN(2, DexPc) + BIT_TABLE_COLUMN(3, RegisterMaskIndex) + BIT_TABLE_COLUMN(4, StackMaskIndex) + BIT_TABLE_COLUMN(5, InlineInfoIndex) + BIT_TABLE_COLUMN(6, DexRegisterMaskIndex) + BIT_TABLE_COLUMN(7, DexRegisterMapIndex) + + ALWAYS_INLINE uint32_t GetNativePcOffset(InstructionSet instruction_set) const { + return UnpackNativePc(GetPackedNativePc(), instruction_set); + } + + ALWAYS_INLINE bool HasInlineInfo() const { + return HasInlineInfoIndex(); + } + + ALWAYS_INLINE bool HasDexRegisterMap() const { + return HasDexRegisterMapIndex(); + } + + static uint32_t PackNativePc(uint32_t native_pc, InstructionSet isa) { + DCHECK_ALIGNED_PARAM(native_pc, GetInstructionSetInstructionAlignment(isa)); + return native_pc / GetInstructionSetInstructionAlignment(isa); + } + + static uint32_t UnpackNativePc(uint32_t packed_native_pc, InstructionSet isa) { + uint32_t native_pc = packed_native_pc * GetInstructionSetInstructionAlignment(isa); + DCHECK_EQ(native_pc / GetInstructionSetInstructionAlignment(isa), packed_native_pc); + return native_pc; + } + + void Dump(VariableIndentationOutputStream* vios, + const CodeInfo& code_info, + uint32_t code_offset, + InstructionSet instruction_set) const; +}; + +/** + * Inline information for a specific PC. + * The row referenced from the StackMap holds information at depth 0. + * Following rows hold information for further depths. + */ +class InlineInfo : public BitTableAccessor<6> { + public: + BIT_TABLE_HEADER(InlineInfo) + BIT_TABLE_COLUMN(0, IsLast) // Determines if there are further rows for further depths. + BIT_TABLE_COLUMN(1, DexPc) + BIT_TABLE_COLUMN(2, MethodInfoIndex) + BIT_TABLE_COLUMN(3, ArtMethodHi) // High bits of ArtMethod*. + BIT_TABLE_COLUMN(4, ArtMethodLo) // Low bits of ArtMethod*. + BIT_TABLE_COLUMN(5, NumberOfDexRegisters) // Includes outer levels and the main method. + + static constexpr uint32_t kLast = -1; + static constexpr uint32_t kMore = 0; + + bool EncodesArtMethod() const { + return HasArtMethodLo(); + } + + ArtMethod* GetArtMethod() const { + uint64_t lo = GetArtMethodLo(); + uint64_t hi = GetArtMethodHi(); + return reinterpret_cast<ArtMethod*>((hi << 32) | lo); + } + + void Dump(VariableIndentationOutputStream* vios, + const CodeInfo& info, + const StackMap& stack_map) const; +}; + +class StackMask : public BitTableAccessor<1> { + public: + BIT_TABLE_HEADER(StackMask) + BIT_TABLE_COLUMN(0, Mask) +}; + +class DexRegisterMask : public BitTableAccessor<1> { + public: + BIT_TABLE_HEADER(DexRegisterMask) + BIT_TABLE_COLUMN(0, Mask) +}; + +class DexRegisterMapInfo : public BitTableAccessor<1> { + public: + BIT_TABLE_HEADER(DexRegisterMapInfo) + BIT_TABLE_COLUMN(0, CatalogueIndex) +}; + +class DexRegisterInfo : public BitTableAccessor<2> { + public: + BIT_TABLE_HEADER(DexRegisterInfo) + BIT_TABLE_COLUMN(0, Kind) + BIT_TABLE_COLUMN(1, PackedValue) + + ALWAYS_INLINE DexRegisterLocation GetLocation() const { + DexRegisterLocation::Kind kind = static_cast<DexRegisterLocation::Kind>(GetKind()); + return DexRegisterLocation(kind, UnpackValue(kind, GetPackedValue())); + } + + static uint32_t PackValue(DexRegisterLocation::Kind kind, uint32_t value) { + uint32_t packed_value = value; + if (kind == DexRegisterLocation::Kind::kInStack) { + DCHECK(IsAligned<kFrameSlotSize>(packed_value)); + packed_value /= kFrameSlotSize; + } + return packed_value; + } + + static uint32_t UnpackValue(DexRegisterLocation::Kind kind, uint32_t packed_value) { + uint32_t value = packed_value; + if (kind == DexRegisterLocation::Kind::kInStack) { + value *= kFrameSlotSize; + } + return value; + } +}; + +// Register masks tend to have many trailing zero bits (caller-saves are usually not encoded), +// therefore it is worth encoding the mask as value+shift. +class RegisterMask : public BitTableAccessor<2> { + public: + BIT_TABLE_HEADER(RegisterMask) + BIT_TABLE_COLUMN(0, Value) + BIT_TABLE_COLUMN(1, Shift) + + ALWAYS_INLINE uint32_t GetMask() const { + return GetValue() << GetShift(); + } +}; + +// Method indices are not very dedup friendly. +// Separating them greatly improves dedup efficiency of the other tables. +class MethodInfo : public BitTableAccessor<3> { + public: + BIT_TABLE_HEADER(MethodInfo) + BIT_TABLE_COLUMN(0, MethodIndex) + BIT_TABLE_COLUMN(1, DexFileIndexKind) + BIT_TABLE_COLUMN(2, DexFileIndex) + + static constexpr uint32_t kKindNonBCP = -1; + static constexpr uint32_t kKindBCP = 0; + + static constexpr uint32_t kSameDexFile = -1; +}; + +/** + * Wrapper around all compiler information collected for a method. + * See the Decode method at the end for the precise binary format. + */ +class CodeInfo { + public: + ALWAYS_INLINE CodeInfo() {} + ALWAYS_INLINE explicit CodeInfo(const uint8_t* data, size_t* num_read_bits = nullptr); + ALWAYS_INLINE explicit CodeInfo(const OatQuickMethodHeader* header); + + // The following methods decode only part of the data. + static CodeInfo DecodeGcMasksOnly(const OatQuickMethodHeader* header); + static CodeInfo DecodeInlineInfoOnly(const OatQuickMethodHeader* header); + + ALWAYS_INLINE static uint32_t DecodeCodeSize(const uint8_t* code_info_data) { + return DecodeHeaderOnly(code_info_data).code_size_; + } + + ALWAYS_INLINE static QuickMethodFrameInfo DecodeFrameInfo(const uint8_t* code_info_data) { + CodeInfo code_info = DecodeHeaderOnly(code_info_data); + return QuickMethodFrameInfo(code_info.packed_frame_size_ * kStackAlignment, + code_info.core_spill_mask_, + code_info.fp_spill_mask_); + } + + ALWAYS_INLINE static CodeInfo DecodeHeaderOnly(const uint8_t* code_info_data) { + CodeInfo code_info; + BitMemoryReader reader(code_info_data); + std::array<uint32_t, kNumHeaders> header = reader.ReadInterleavedVarints<kNumHeaders>(); + ForEachHeaderField([&code_info, &header](size_t i, auto member_pointer) { + code_info.*member_pointer = header[i]; + }); + return code_info; + } + + ALWAYS_INLINE const BitTable<StackMap>& GetStackMaps() const { + return stack_maps_; + } + + ALWAYS_INLINE StackMap GetStackMapAt(size_t index) const { + return stack_maps_.GetRow(index); + } + + BitMemoryRegion GetStackMask(size_t index) const { + return stack_masks_.GetBitMemoryRegion(index); + } + + BitMemoryRegion GetStackMaskOf(const StackMap& stack_map) const { + uint32_t index = stack_map.GetStackMaskIndex(); + return (index == StackMap::kNoValue) ? BitMemoryRegion() : GetStackMask(index); + } + + uint32_t GetRegisterMaskOf(const StackMap& stack_map) const { + uint32_t index = stack_map.GetRegisterMaskIndex(); + return (index == StackMap::kNoValue) ? 0 : register_masks_.GetRow(index).GetMask(); + } + + uint32_t GetNumberOfLocationCatalogEntries() const { + return dex_register_catalog_.NumRows(); + } + + ALWAYS_INLINE DexRegisterLocation GetDexRegisterCatalogEntry(size_t index) const { + return (index == StackMap::kNoValue) + ? DexRegisterLocation::None() + : dex_register_catalog_.GetRow(index).GetLocation(); + } + + bool HasInlineInfo() const { + return inline_infos_.NumRows() > 0; + } + + uint32_t GetNumberOfStackMaps() const { + return stack_maps_.NumRows(); + } + + MethodInfo GetMethodInfoOf(InlineInfo inline_info) const { + return method_infos_.GetRow(inline_info.GetMethodInfoIndex()); + } + + uint32_t GetMethodIndexOf(InlineInfo inline_info) const { + return GetMethodInfoOf(inline_info).GetMethodIndex(); + } + + // Returns the dex registers for `stack_map`, ignoring any inlined dex registers. + ALWAYS_INLINE DexRegisterMap GetDexRegisterMapOf(StackMap stack_map) const { + return GetDexRegisterMapOf(stack_map, /* first= */ 0, number_of_dex_registers_); + } + + // Returns the dex register map of `inline_info`, and just those registers. + ALWAYS_INLINE DexRegisterMap GetInlineDexRegisterMapOf(StackMap stack_map, + InlineInfo inline_info) const { + if (stack_map.HasDexRegisterMap()) { + DCHECK(stack_map.HasInlineInfoIndex()); + uint32_t depth = inline_info.Row() - stack_map.GetInlineInfoIndex(); + // The register counts are commutative and include all outer levels. + // This allows us to determine the range [first, last) in just two lookups. + // If we are at depth 0 (the first inlinee), the count from the main method is used. + uint32_t first = (depth == 0) + ? number_of_dex_registers_ + : inline_infos_.GetRow(inline_info.Row() - 1).GetNumberOfDexRegisters(); + uint32_t last = inline_info.GetNumberOfDexRegisters(); + return GetDexRegisterMapOf(stack_map, first, last); + } + return DexRegisterMap(0, DexRegisterLocation::None()); + } + + // Returns the dex register map of `stack_map` in the range the range [first, last). + ALWAYS_INLINE DexRegisterMap GetDexRegisterMapOf(StackMap stack_map, + uint32_t first, + uint32_t last) const { + if (stack_map.HasDexRegisterMap()) { + DCHECK_LE(first, last); + DexRegisterMap map(last - first, DexRegisterLocation::Invalid()); + DecodeDexRegisterMap(stack_map.Row(), first, &map); + return map; + } + return DexRegisterMap(0, DexRegisterLocation::None()); + } + + BitTableRange<InlineInfo> GetInlineInfosOf(StackMap stack_map) const { + uint32_t index = stack_map.GetInlineInfoIndex(); + if (index != StackMap::kNoValue) { + auto begin = inline_infos_.begin() + index; + auto end = begin; + while ((*end++).GetIsLast() == InlineInfo::kMore) { } + return BitTableRange<InlineInfo>(begin, end); + } else { + return BitTableRange<InlineInfo>(); + } + } + + StackMap GetStackMapForDexPc(uint32_t dex_pc) const { + for (StackMap stack_map : stack_maps_) { + if (stack_map.GetDexPc() == dex_pc && stack_map.GetKind() != StackMap::Kind::Debug) { + return stack_map; + } + } + return stack_maps_.GetInvalidRow(); + } + + StackMap GetCatchStackMapForDexPc(ArrayRef<const uint32_t> dex_pcs) const { + // Searches the stack map list backwards because catch stack maps are stored at the end. + for (size_t i = GetNumberOfStackMaps(); i > 0; --i) { + StackMap stack_map = GetStackMapAt(i - 1); + if (UNLIKELY(stack_map.GetKind() != StackMap::Kind::Catch)) { + // Early break since we should have catch stack maps only at the end. + if (kIsDebugBuild) { + for (size_t j = i - 1; j > 0; --j) { + DCHECK(GetStackMapAt(j - 1).GetKind() != StackMap::Kind::Catch); + } + } + break; + } + + // Both the handler dex_pc and all of the inline dex_pcs have to match i.e. we want dex_pcs to + // be [stack_map_dex_pc, inline_dex_pc_1, ..., inline_dex_pc_n]. + if (stack_map.GetDexPc() != dex_pcs.front()) { + continue; + } + + const BitTableRange<InlineInfo>& inline_infos = GetInlineInfosOf(stack_map); + if (inline_infos.size() == dex_pcs.size() - 1) { + bool matching_dex_pcs = true; + for (size_t inline_info_index = 0; inline_info_index < inline_infos.size(); + ++inline_info_index) { + if (inline_infos[inline_info_index].GetDexPc() != dex_pcs[inline_info_index + 1]) { + matching_dex_pcs = false; + break; + } + } + if (matching_dex_pcs) { + return stack_map; + } + } + } + return stack_maps_.GetInvalidRow(); + } + + StackMap GetOsrStackMapForDexPc(uint32_t dex_pc) const { + for (StackMap stack_map : stack_maps_) { + if (stack_map.GetDexPc() == dex_pc && stack_map.GetKind() == StackMap::Kind::OSR) { + return stack_map; + } + } + return stack_maps_.GetInvalidRow(); + } + + StackMap GetStackMapForNativePcOffset(uintptr_t pc, InstructionSet isa = kRuntimeISA) const; + + // Dump this CodeInfo object on `vios`. + // `code_offset` is the (absolute) native PC of the compiled method. + void Dump(VariableIndentationOutputStream* vios, + uint32_t code_offset, + bool verbose, + InstructionSet instruction_set) const; + + // Accumulate code info size statistics into the given Stats tree. + static void CollectSizeStats(const uint8_t* code_info, /*out*/ Stats& parent); + + template <uint32_t kFlag> + ALWAYS_INLINE static bool HasFlag(const uint8_t* code_info_data) { + // Fast path - read just the one specific bit from the header. + bool result; + uint8_t varint = (*code_info_data) & MaxInt<uint8_t>(kVarintBits); + if (LIKELY(varint <= kVarintMax)) { + result = (varint & kFlag) != 0; + } else { + DCHECK_EQ(varint, kVarintMax + 1); // Only up to 8 flags are supported for now. + constexpr uint32_t bit_offset = kNumHeaders * kVarintBits + WhichPowerOf2(kFlag); + result = (code_info_data[bit_offset / kBitsPerByte] & (1 << bit_offset % kBitsPerByte)) != 0; + } + // Slow path - dcheck that we got the correct result against the naive implementation. + BitMemoryReader reader(code_info_data); + DCHECK_EQ(result, (reader.ReadInterleavedVarints<kNumHeaders>()[0] & kFlag) != 0); + return result; + } + + ALWAYS_INLINE static bool HasInlineInfo(const uint8_t* code_info_data) { + return HasFlag<kHasInlineInfo>(code_info_data); + } + + ALWAYS_INLINE static bool HasShouldDeoptimizeFlag(const uint8_t* code_info_data) { + return HasFlag<kHasShouldDeoptimizeFlag>(code_info_data); + } + + ALWAYS_INLINE static bool IsBaseline(const uint8_t* code_info_data) { + return HasFlag<kIsBaseline>(code_info_data); + } + + ALWAYS_INLINE static bool IsDebuggable(const uint8_t* code_info_data) { + return HasFlag<kIsDebuggable>(code_info_data); + } + + uint32_t GetNumberOfDexRegisters() { + return number_of_dex_registers_; + } + + private: + // Scan backward to determine dex register locations at given stack map. + void DecodeDexRegisterMap(uint32_t stack_map_index, + uint32_t first_dex_register, + /*out*/ DexRegisterMap* map) const; + + template<typename DecodeCallback> // (size_t index, BitTable<...>*, BitMemoryRegion). + ALWAYS_INLINE CodeInfo(const uint8_t* data, size_t* num_read_bits, DecodeCallback callback); + + // Invokes the callback with index and member pointer of each header field. + template<typename Callback> + ALWAYS_INLINE static void ForEachHeaderField(Callback callback) { + size_t index = 0; + callback(index++, &CodeInfo::flags_); + callback(index++, &CodeInfo::code_size_); + callback(index++, &CodeInfo::packed_frame_size_); + callback(index++, &CodeInfo::core_spill_mask_); + callback(index++, &CodeInfo::fp_spill_mask_); + callback(index++, &CodeInfo::number_of_dex_registers_); + callback(index++, &CodeInfo::bit_table_flags_); + DCHECK_EQ(index, kNumHeaders); + } + + // Invokes the callback with index and member pointer of each BitTable field. + template<typename Callback> + ALWAYS_INLINE static void ForEachBitTableField(Callback callback) { + size_t index = 0; + callback(index++, &CodeInfo::stack_maps_); + callback(index++, &CodeInfo::register_masks_); + callback(index++, &CodeInfo::stack_masks_); + callback(index++, &CodeInfo::inline_infos_); + callback(index++, &CodeInfo::method_infos_); + callback(index++, &CodeInfo::dex_register_masks_); + callback(index++, &CodeInfo::dex_register_maps_); + callback(index++, &CodeInfo::dex_register_catalog_); + DCHECK_EQ(index, kNumBitTables); + } + + bool HasBitTable(size_t i) { return ((bit_table_flags_ >> i) & 1) != 0; } + bool IsBitTableDeduped(size_t i) { return ((bit_table_flags_ >> (kNumBitTables + i)) & 1) != 0; } + void SetBitTableDeduped(size_t i) { bit_table_flags_ |= 1 << (kNumBitTables + i); } + bool HasDedupedBitTables() { return (bit_table_flags_ >> kNumBitTables) != 0u; } + + // NB: The first three flags should be the most common ones. + // Maximum of 8 flags is supported right now (see the HasFlag method). + enum Flags { + kHasInlineInfo = 1 << 0, + kHasShouldDeoptimizeFlag = 1 << 1, + kIsBaseline = 1 << 2, + kIsDebuggable = 1 << 3, + }; + + // The CodeInfo starts with sequence of variable-length bit-encoded integers. + // (Please see kVarintMax for more details about encoding). + static constexpr size_t kNumHeaders = 7; + uint32_t flags_ = 0; + uint32_t code_size_ = 0; // The size of native PC range in bytes. + uint32_t packed_frame_size_ = 0; // Frame size in kStackAlignment units. + uint32_t core_spill_mask_ = 0; + uint32_t fp_spill_mask_ = 0; + uint32_t number_of_dex_registers_ = 0; + uint32_t bit_table_flags_ = 0; + + // The encoded bit-tables follow the header. Based on the above flags field, + // bit-tables might be omitted or replaced by relative bit-offset if deduped. + static constexpr size_t kNumBitTables = 8; + BitTable<StackMap> stack_maps_; + BitTable<RegisterMask> register_masks_; + BitTable<StackMask> stack_masks_; + BitTable<InlineInfo> inline_infos_; + BitTable<MethodInfo> method_infos_; + BitTable<DexRegisterMask> dex_register_masks_; + BitTable<DexRegisterMapInfo> dex_register_maps_; + BitTable<DexRegisterInfo> dex_register_catalog_; + + friend class linker::CodeInfoTableDeduper; + friend class StackMapStream; +}; + +#undef ELEMENT_BYTE_OFFSET_AFTER +#undef ELEMENT_BIT_OFFSET_AFTER + +} // namespace art + +#endif // ART_RUNTIME_OAT_STACK_MAP_H_ |