/* * Copyright (C) 2017 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_SUBTYPE_CHECK_INFO_H_ #define ART_RUNTIME_SUBTYPE_CHECK_INFO_H_ #include "base/bit_string.h" #include "subtype_check_bits.h" // Forward-declare for testing purposes. struct SubtypeCheckInfoTest; namespace art { /** * SubtypeCheckInfo is a logical label for the class SubtypeCheck data, which is necessary to * perform efficient O(1) subtype comparison checks. See also subtype_check.h * for the more general explanation of how the labels are used overall. * * For convenience, we also store the class depth within an SubtypeCheckInfo, since nearly all * calculations are dependent on knowing the depth of the class. * * A SubtypeCheckInfo logically has: * * Depth - How many levels up to the root (j.l.Object)? * * PathToRoot - Possibly truncated BitString that encodes path to root * * Next - The value a newly inserted Child would get appended to its path. * * Overflow - If this path can never become a full path. * * Depending on the values of the above, it can be in one of these logical states, * which are introduced in subtype_check.h: * * Transient States Terminal States * * +-----------------+ +--------------------+ +-------------------+ * | | | | | | * | Uninitialized | +---> Initialized | +---> Assigned | * | | | | | | * +--------+--------+ +---------+----------+ +-------------------+ * | | * | | * | | +-------------------+ * | +----------------> | * | | Overflowed | * +-----------------------------------------> | * +-------------------+ * * Invariants: * * Initialized => Parent >= Initialized * * Assigned => Parent == Assigned * * Overflowed => Parent == Overflowed || Parent.Next == Overflowed * * Thread-safety invariants: * * Initialized => Parent == Assigned * // For a class that has an Initialized bitstring, its superclass needs to have an * // Assigned bitstring since if its super class's bitstring is not Assigned yet, * // once it becomes Assigned, we cannot update its children's bitstrings to maintain * // all the tree invariants (below) atomically. * * -------------------------------------------------------------------------------------------- * Knowing these transitions above, we can more closely define the various terms and operations: * * Definitions: * (see also base/bit_string.h definitions). * * Depth := Distance(Root, Class) * Safe(Depth) := Min(Depth, MaxBitstringLen) * PathToRoot := Bitstring[0..Safe(Depth)) * Next := Bitstring[Depth] * OF ∈ {False, True} * TruncPath(D) := PathToRoot[0..D) * * Local Invariants: * * Uninitialized <=> StrLen(PathToRoot) == 0 * Next == 0 * OF == False * Initialized <=> StrLen(PathToRoot) < Depth * Next == 1 * OF == False * Assigned <=> StrLen(PathToRoot) == Depth * Next >= 1 * OF == False * Overflowed <=> OF == True * * Tree Invariants: * * Uninitialized => * forall child ∈ Children(Class): * child.State == Uninitialized * * Assigned => * forall child ∈ Children(Class): * Next > Child.PathToRoot[Child.Depth-1] * * ! Uninitialized => * forall ancestor ∈ Ancestors(Class): * TruncPath(ancestor.Depth) == ancestor.PathToRoot * forall unrelated ∈ (Classes - Ancestors(Class)) * s.t. unrelated.State == Assigned: * TruncPath(unrelated.Depth) != unrelated.PathToRoot * * Thread-safety invariants: * * Initialized <=> StrLen(PathToRoot) == Safe(Depth - 1) * // Initialized State corresponds to exactly 1 bitstring. * // Cannot transition from Initialized to Initialized. */ struct SubtypeCheckInfo { // See above documentation for possible state transitions. enum State { kUninitialized, kInitialized, kAssigned, kOverflowed }; // The result of a "src IsSubType target" check: enum Result { kUnknownSubtypeOf, // Not enough data. Operand states weren't high enough. kNotSubtypeOf, // Enough data. src is not a subchild of the target. kSubtypeOf // Enough data. src is a subchild of the target. }; // Get the raw depth. size_t GetDepth() const { return depth_; } // Chop off the depth, returning only the bitstring+of state. // (Used to store into memory, since storing the depth would be redundant.) SubtypeCheckBits GetSubtypeCheckBits() const { return bitstring_and_of_; } // Create from the depth and the bitstring+of state. // This is done for convenience to avoid passing in "depth" everywhere, // since our current state is almost always a function of depth. static SubtypeCheckInfo Create(SubtypeCheckBits compressed_value, size_t depth) { SubtypeCheckInfo io; io.depth_ = depth; io.bitstring_and_of_ = compressed_value; io.DcheckInvariants(); return io; } // Is this a subtype of the target? // // The current state must be at least Initialized, and the target state // must be Assigned, otherwise the result will return kUnknownSubtypeOf. // // Normally, return kSubtypeOf or kNotSubtypeOf. Result IsSubtypeOf(const SubtypeCheckInfo& target) { if (target.GetState() != SubtypeCheckInfo::kAssigned) { return Result::kUnknownSubtypeOf; } else if (GetState() == SubtypeCheckInfo::kUninitialized) { return Result::kUnknownSubtypeOf; } BitString::StorageType source_value = GetEncodedPathToRoot(); BitString::StorageType target_value = target.GetEncodedPathToRoot(); BitString::StorageType target_mask = target.GetEncodedPathToRootMask(); bool result = (source_value & target_mask) == (target_value); if (result) { DCHECK_EQ(GetPathToRoot().Truncate(target.GetSafeDepth()), target.GetPathToRoot()) << "Source: " << *this << ", Target: " << target; } else { DCHECK_NE(GetPathToRoot().Truncate(target.GetSafeDepth()), target.GetPathToRoot()) << "Source: " << *this << ", Target: " << target; } // Note: We could've also used shifts here, as described in subtype_check_bits.h, // but it doesn't make much of a difference in the Runtime since we aren't trying to optimize // for code size. return result ? Result::kSubtypeOf : Result::kNotSubtypeOf; } // Returns a new root SubtypeCheckInfo with a blank PathToRoot. // Post-condition: The return valued has an Assigned state. static SubtypeCheckInfo CreateRoot() { SubtypeCheckInfo io{}; io.depth_ = 0u; io.SetNext(io.GetNext() + 1u); // The root is always considered assigned once it is no longer Initialized. DCHECK_EQ(SubtypeCheckInfo::kAssigned, io.GetState()); return io; } // Copies the current PathToRoot into the child. // // If assign_next is true, then also assign a new SubtypeCheckInfo for a child by // assigning the current Next value to its PathToRoot[Depth] component. // Updates the current Next value as a side effect. // // Preconditions: State is either Assigned or Overflowed. // Returns: A new child >= Initialized state. SubtypeCheckInfo CreateChild(bool assign_next) { SubtypeCheckInfo child = *this; // Copy everything (path, next, of). child.depth_ = depth_ + 1u; // Must be Assigned or Overflowed in order to create a subchild. DCHECK(GetState() == kAssigned || GetState() == kOverflowed) << "Unexpected bitstring state: " << GetState(); // Begin transition to >= Initialized. // Always attempt to re-initialize Child's Next value. // Next must be non-0 to disambiguate it from Uninitialized. child.MaybeInitNext(); // Always clear the inherited Parent's next Value, i.e. the child's last path entry. OverwriteNextValueFromParent(/*inout*/&child, BitStringChar{}); // The state is now Initialized | Overflowed. DCHECK_NE(kAssigned, child.GetState()) << child.GetBitString(); DCHECK_NE(kUninitialized, child.GetState()) << child.GetBitString(); if (assign_next == false) { child.DcheckInvariants(); return child; } // Begin transition to >= Assigned. // Assign attempt. if (HasNext() && !bitstring_and_of_.overflow_) { BitStringChar next = GetNext(); if (next != next.MaximumValue()) { // The parent's "next" value is now the child's latest path element. OverwriteNextValueFromParent(/*inout*/&child, next); // Update self next value, so that future CreateChild calls // do not get the same path value. SetNext(next + 1u); } else { child.MarkOverflowed(); // Too wide. } } else { child.MarkOverflowed(); // Too deep, or parent was already overflowed. } // The state is now Assigned | Overflowed. DCHECK(child.GetState() == kAssigned || child.GetState() == kOverflowed); child.DcheckInvariants(); return child; } // Get the current state (Uninitialized, Initialized, Assigned, or Overflowed). // See the "SubtypeCheckInfo" documentation above which explains how a state is determined. State GetState() const { if (bitstring_and_of_.overflow_) { // Overflowed if and only if the OF bit was set. return kOverflowed; } if (GetBitString().IsEmpty()) { // Empty bitstring (all 0s) -> uninitialized. return kUninitialized; } // Either Assigned or Initialized. BitString path_to_root = GetPathToRoot(); DCHECK(!HasNext() || GetNext() != 0u) << "Expected (Assigned|Initialized) state to have >0 Next value: " << GetNext() << " path: " << path_to_root; if (path_to_root.Length() == depth_) { return kAssigned; } return kInitialized; } // Retrieve the path to root bitstring as a plain uintN_t value that is amenable to // be used by a fast check "encoded_src & mask_target == encoded_target". BitString::StorageType GetEncodedPathToRoot() const { BitString::StorageType data = static_cast(GetPathToRoot()); // Bit strings are logically in the least-significant memory. return data; } // Retrieve the path to root bitstring mask as a plain uintN_t that is amenable to // be used by a fast check "encoded_src & mask_target == encoded_target". BitString::StorageType GetEncodedPathToRootMask() const { size_t num_bitchars = GetSafeDepth(); size_t bitlength = BitString::GetBitLengthTotalAtPosition(num_bitchars); return MaskLeastSignificant(bitlength); } // Get the "Next" bitchar, assuming that there is one to get. BitStringChar GetNext() const { DCHECK(HasNext()); return GetBitString()[depth_]; } // Try to get the Next value, if there is one. // Returns: Whether or not there was a Next value. bool MaybeGetNext(/*out*/BitStringChar* next) const { DCHECK(next != nullptr); if (HasNext()) { *next = GetBitString()[depth_]; return true; } return false; } private: // Constructor intended for testing. Runs all invariant checks. SubtypeCheckInfo(BitString path_to_root, BitStringChar next, bool overflow, size_t depth) { SubtypeCheckBits iod; iod.bitstring_ = path_to_root; iod.overflow_ = overflow; bitstring_and_of_ = iod; depth_ = depth; // Len(Path-to-root) <= Depth. DCHECK_GE(depth_, path_to_root.Length()) << "Path was too long for the depth, path: " << path_to_root; bool did_overlap = false; if (HasNext()) { if (kIsDebugBuild) { did_overlap = (GetNext() != 0u); } SetNext(next); DCHECK_EQ(next, GetNext()); } // "Next" must be set before we can check the invariants. DcheckInvariants(); DCHECK(!did_overlap) << "Path to root overlapped with Next value, path: " << path_to_root; DCHECK_EQ(path_to_root, GetPathToRoot()); } // Factory intended for testing. Skips DcheckInvariants. static SubtypeCheckInfo MakeUnchecked(BitString bitstring, bool overflow, size_t depth) { SubtypeCheckBits iod; iod.bitstring_ = bitstring; iod.overflow_ = overflow; SubtypeCheckInfo io; io.depth_ = depth; io.bitstring_and_of_ = iod; return io; } void SetNext(BitStringChar next) { DCHECK(HasNext()); BitString bs = GetBitString(); bs.SetAt(depth_, next); SetBitString(bs); } void SetNextUnchecked(BitStringChar next) { BitString bs = GetBitString(); bs.SetAt(depth_, next); SetBitStringUnchecked(bs); } // If there is a next field, set it to 1. void MaybeInitNext() { if (HasNext()) { // Clearing out the "Next" value like this // is often an intermediate operation which temporarily // violates the invariants. Do not do the extra dchecks. SetNextUnchecked(BitStringChar{}); SetNextUnchecked(GetNext()+1u); } } BitString GetPathToRoot() const { size_t end = GetSafeDepth(); return GetBitString().Truncate(end); } bool HasNext() const { return depth_ < BitString::kCapacity; } void MarkOverflowed() { bitstring_and_of_.overflow_ = true; } static constexpr bool HasBitStringCharStorage(size_t idx) { return idx < BitString::kCapacity; } size_t GetSafeDepth() const { return GetSafeDepth(depth_); } // Get a "safe" depth, one that is truncated to the bitstring max capacity. // Using a value larger than this will cause undefined behavior. static size_t GetSafeDepth(size_t depth) { return std::min(depth, BitString::kCapacity); } BitString GetBitString() const { return bitstring_and_of_.bitstring_; } void SetBitString(const BitString& val) { SetBitStringUnchecked(val); DcheckInvariants(); } void SetBitStringUnchecked(const BitString& val) { bitstring_and_of_.bitstring_ = val; } void OverwriteNextValueFromParent(/*inout*/SubtypeCheckInfo* child, BitStringChar value) const { // Helper function for CreateChild. if (HasNext()) { // When we copied the "Next" value, it is now our // last path component in the child. // Always overwrite it with either a cleared value or the parent's Next value. BitString bs = child->GetBitString(); // Safe write. This.Next always occupies same slot as Child[Depth_]. DCHECK(child->HasBitStringCharStorage(depth_)); bs.SetAt(depth_, value); // The child is temporarily in a bad state until it is fixed up further. // Do not do the normal dchecks which do not allow transient badness. child->SetBitStringUnchecked(bs); } } void DcheckInvariants() const { if (kIsDebugBuild) { CHECK_GE(GetSafeDepth(depth_ + 1u), GetBitString().Length()) << "Bitstring too long for depth, bitstring: " << GetBitString() << ", depth: " << depth_; BitString path_to_root = GetPathToRoot(); // A 'null' (\0) character in path-to-root must be followed only // by other null characters. size_t i; for (i = 0; i < BitString::kCapacity; ++i) { BitStringChar bc = path_to_root[i]; if (bc == 0u) { break; } } // All characters following a 0 must also be 0. for (; i < BitString::kCapacity; ++i) { BitStringChar bc = path_to_root[i]; if (bc != 0u) { LOG(FATAL) << "Path to root had non-0s following 0s: " << path_to_root; } } // Trigger any dchecks in GetState. (void)GetState(); } } SubtypeCheckInfo() = default; size_t depth_; SubtypeCheckBits bitstring_and_of_; friend struct ::SubtypeCheckInfoTest; friend std::ostream& operator<<(std::ostream& os, const SubtypeCheckInfo& io); }; // Prints the SubtypeCheckInfo::State, e.g. "kUnitialized". inline std::ostream& operator<<(std::ostream& os, const SubtypeCheckInfo::State& state) { switch (state) { case SubtypeCheckInfo::kUninitialized: os << "kUninitialized"; break; case SubtypeCheckInfo::kInitialized: os << "kInitialized"; break; case SubtypeCheckInfo::kAssigned: os << "kAssigned"; break; case SubtypeCheckInfo::kOverflowed: os << "kOverflowed"; break; default: os << "(Invalid SubtypeCheckInfo::State " << static_cast(state) << ")"; } return os; } // Prints e.g. "SubtypeCheckInfo{BitString[1,2,3], depth: 3, of:1}" inline std::ostream& operator<<(std::ostream& os, const SubtypeCheckInfo& io) { os << "SubtypeCheckInfo{" << io.GetBitString() << ", " << "depth: " << io.depth_ << ", of:" << io.bitstring_and_of_.overflow_ << "}"; return os; } } // namespace art #endif // ART_RUNTIME_SUBTYPE_CHECK_INFO_H_