blob: 08db77030e0c400540f1296b882c27ff01a3fc3d [file] [log] [blame]
/*
* 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<BitString::StorageType>(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<BitString::StorageType>(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<int>(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_