blob: 493ea853e5e4e637f8688afbef8a4ca6926a6904 [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_H_
#define ART_RUNTIME_SUBTYPE_CHECK_H_
#include "subtype_check_bits_and_status.h"
#include "subtype_check_info.h"
#include "base/locks.h"
#include "mirror/class.h"
#include "runtime.h"
// Build flag for the bitstring subtype check runtime hooks.
constexpr bool kBitstringSubtypeCheckEnabled = false;
/**
* Any node in a tree can have its path (from the root to the node) represented as a string by
* concatenating the path of the parent to that of the current node.
*
* We can annotate each node with a `sibling-label` which is some value unique amongst all of the
* node's siblings. As a special case, the root is empty.
*
* (none)
* / | \
* A B C
* / \
* A’ B’
* |
* A’’
* |
* A’’’
* |
* A’’’’
*
* Given these sibling-labels, we can now encode the path from any node to the root by starting at
* the node and going up to the root, marking each node with this `path-label`. The special
* character $ means "end of path".
*
* $
* / | \
* A$ B$ C$
* / \
* A’A$ B’A$
* |
* A’’B’A$
* |
* A’’’A’’B’A$
* |
* A’’’’A’’B’A$
*
* Given the above `path-label` we can express if any two nodes are an offspring of the other
* through a O(1) expression:
*
* x <: y :=
* suffix(x, y) == y
*
* In the above example suffix(x,y) means the suffix of x that is as long as y (right-padded with
* $s if x is shorter than y) :
*
* suffix(x,y) := x(x.length - y.length .. 0]
* + repeat($, max(y.length - x.length, 0))
*
* A few generalities here to elaborate:
*
* - There can be at most D levels in the tree.
* - Each level L has an alphabet A, and the maximum number of
* nodes is determined by |A|
* - The alphabet A can be a subset, superset, equal, or unique with respect to the other alphabets
* without loss of generality. (In practice it would almost always be a subset of the previous
* level’s alphabet as we assume most classes have less children the deeper they are.)
* - The `sibling-label` doesn’t need to be stored as an explicit value. It can a temporary when
* visiting every immediate child of a node. Only the `path-label` needs to be actually stored for
* every node.
*
* The path can also be reversed, and use a prefix instead of a suffix to define the subchild
* relation.
*
* $
* / | \ \
* A$ B$ C$ D$
* / \
* AA’$ AB’$
* |
* AB’A’’$
* |
* AB’A’’A’’’$
* |
* AB’A’’A’’’A’’’’$
*
* x <: y :=
* prefix(x, y) == y
*
* prefix(x,y) := x[0 .. y.length)
* + repeat($, max(y.length - x.length, 0))
*
* In a dynamic tree, new nodes can be inserted at any time. This means if a minimal alphabet is
* selected to contain the initial tree hierarchy, later node insertions will be illegal because
* there is no more room to encode the path.
*
* In this simple example with an alphabet A,B,C and max level 1:
*
* Level
* 0: $
* / | \ \
* 1: A$ B$ C$ D$ (illegal)
* |
* 2: AA$ (illegal)
*
* Attempting to insert the sibling “D” at Level 1 would be illegal because the Alphabet(1) is
* {A,B,C} and inserting an extra node would mean the `sibling-label` is no longer unique.
* Attempting to insert “AA$” is illegal because the level 2 is more than the max level 1.
*
* One solution to this would be to revisit the entire graph, select a larger alphabet to that
* every `sibling-label` is unique, pick a larger max level count, and then store the updated
* `path-label` accordingly.
*
* The more common approach would instead be to select a set of alphabets and max levels statically,
* with large enough sizes, for example:
*
* Alphabets = {{A,B,C,D}, {A,B,C}, {A,B}, {A}}
* Max Levels = |Alphabets|
*
* Which would allow up to 4 levels with each successive level having 1 less max siblings.
*
* Attempting to insert a new node into the graph which does not fit into that level’s alphabet
* would be represented by re-using the `path-label` of the parent. Such a `path_label` would be
* considered truncated (because it would only have a prefix of the full path from the root to the
* node).
*
* Level
* 0: $
* / | \ \
* 1: A$ B$ C$ $ (same as parent)
* |
* 2: A$ (same as parent)
*
* The updated relation for offspring is then:
*
* x <: y :=
* if !truncated_path(y):
* return prefix(x, y) == y // O(1)
* else:
* return slow_check_is_offspring(x, y) // worse than O(1)
*
* (Example definition of truncated_path -- any semantically equivalent way to check that the
* sibling's `sibling-label` is not unique will do)
*
* truncated_path(y) :=
* return y == parent(y)
*
* (Example definition. Any slower-than-O(1) definition will do here. This is the traversing
* superclass hierarchy solution)
*
* slow_check_is_offspring(x, y) :=
* if not x: return false
* else: return x == y || recursive_is_offspring(parent(x), y)
*
* In which case slow_check_is_offspring is some non-O(1) way to check if x and is an offspring of y.
*
* In addition, note that it doesn’t matter if the "x" from above is a unique sibling or not; the
* relation will still be correct.
*
* ------------------------------------------------------------------------------------------------
*
* Leveraging truncated paths to minimize path lengths.
*
* As observed above, for any x <: y, it is sufficient to have a full path only for y,
* and x can be truncated (to its nearest ancestor's full path).
*
* We call a node that stores a full path "Assigned", and a node that stores a truncated path
* either "Initialized" or "Overflowed."
*
* "Initialized" means it is still possible to assign a full path to the node, and "Overflowed"
* means there is insufficient characters in the alphabet left.
*
* In this example, assume that we attempt to "Assign" all non-leafs if possible. Leafs
* always get truncated (as either Initialized or Overflowed).
*
* Alphabets = {{A,B,C,D}, {A,B}}
* Max Levels = |Alphabets|
*
* Level
* 0: $
* / | \ \ \
* 1: A$ B$ C$ D$ $ (Overflowed: Too wide)
* | |
* 2: AA$ C$ (Initialized)
* |
* 3: AA$ (Overflowed: Too deep)
*
* (All un-annotated nodes are "Assigned").
* Above, the node at level 3 becomes overflowed because it exceeds the max levels. The
* right-most node at level 1 becomes overflowed because there's no characters in the alphabet
* left in that level.
*
* The "C$" node is Initialized at level 2, but it can still be promoted to "Assigned" later on
* if we wanted to.
*
* In particular, this is the strategy we use in our implementation
* (SubtypeCheck::EnsureInitialized, SubtypeCheck::EnsureAssigned).
*
* Since the # of characters in our alphabet (BitString) is very limited, we want to avoid
* allocating a character to a node until its absolutely necessary.
*
* All node targets (in `src <: target`) get Assigned, and any parent of an Initialized
* node also gets Assigned.
*/
namespace art {
struct MockSubtypeCheck; // Forward declaration for testing.
// This class is using a template parameter to enable testability without losing performance.
// ClassPtr is almost always `mirror::Class*` or `ObjPtr<mirror::Class>`.
template <typename ClassPtr /* Pointer-like type to Class */>
struct SubtypeCheck {
// Force this class's SubtypeCheckInfo state into at least Initialized.
// As a side-effect, all parent classes also become Assigned|Overflowed.
//
// Cost: O(Depth(Class))
//
// Post-condition: State is >= Initialized.
// Returns: The precise SubtypeCheckInfo::State.
static SubtypeCheckInfo::State EnsureInitialized(ClassPtr klass)
REQUIRES(Locks::subtype_check_lock_)
REQUIRES_SHARED(Locks::mutator_lock_) {
return InitializeOrAssign(klass, /*assign=*/false).GetState();
}
// Force this class's SubtypeCheckInfo state into Assigned|Overflowed.
// As a side-effect, all parent classes also become Assigned|Overflowed.
//
// Cost: O(Depth(Class))
//
// Post-condition: State is Assigned|Overflowed.
// Returns: The precise SubtypeCheckInfo::State.
static SubtypeCheckInfo::State EnsureAssigned(ClassPtr klass)
REQUIRES(Locks::subtype_check_lock_)
REQUIRES_SHARED(Locks::mutator_lock_) {
return InitializeOrAssign(klass, /*assign=*/true).GetState();
}
// Resets the SubtypeCheckInfo into the Uninitialized state.
//
// Intended only for the AOT image writer.
// This is a static function to avoid calling klass.Depth(), which is unsupported
// in some portions of the image writer.
//
// Cost: O(1).
//
// Returns: A state that is always Uninitialized.
static SubtypeCheckInfo::State ForceUninitialize(ClassPtr klass)
REQUIRES(Locks::subtype_check_lock_)
REQUIRES_SHARED(Locks::mutator_lock_) {
// Trying to do this in a real runtime will break thread safety invariants
// of existing live objects in the class hierarchy.
// This is only safe as the last step when the classes are about to be
// written out as an image and IsSubClass is never used again.
DCHECK(Runtime::Current() == nullptr || Runtime::Current()->IsAotCompiler())
<< "This only makes sense when compiling an app image.";
// Directly read/write the class field here.
// As this method is used by image_writer on a copy,
// the Class* there is not a real class and using it for anything
// more complicated (e.g. ObjPtr or Depth call) will fail dchecks.
// OK. zero-initializing subtype_check_info_ puts us into the kUninitialized state.
SubtypeCheckBits scb_uninitialized = SubtypeCheckBits{};
WriteSubtypeCheckBits(klass, scb_uninitialized);
// Do not use "SubtypeCheckInfo" API here since that requires Depth()
// which would cause a dcheck failure.
return SubtypeCheckInfo::kUninitialized;
}
// Retrieve the state of this class's SubtypeCheckInfo.
//
// Cost: O(Depth(Class)).
//
// Returns: The precise SubtypeCheckInfo::State.
static SubtypeCheckInfo::State GetState(ClassPtr klass)
REQUIRES(Locks::subtype_check_lock_)
REQUIRES_SHARED(Locks::mutator_lock_) {
return GetSubtypeCheckInfo(klass).GetState();
}
// 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".
//
// Cost: O(Depth(Class)).
//
// Returns the encoded_src value. Must be >= Initialized (EnsureInitialized).
static BitString::StorageType GetEncodedPathToRootForSource(ClassPtr klass)
REQUIRES(Locks::subtype_check_lock_)
REQUIRES_SHARED(Locks::mutator_lock_) {
DCHECK_NE(SubtypeCheckInfo::kUninitialized, GetSubtypeCheckInfo(klass).GetState());
return GetSubtypeCheckInfo(klass).GetEncodedPathToRoot();
}
// 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".
//
// Cost: O(Depth(Class)).
//
// Returns the encoded_target value. Must be Assigned (EnsureAssigned).
static BitString::StorageType GetEncodedPathToRootForTarget(ClassPtr klass)
REQUIRES(Locks::subtype_check_lock_)
REQUIRES_SHARED(Locks::mutator_lock_) {
SubtypeCheckInfo sci = GetSubtypeCheckInfo(klass);
DCHECK_EQ(SubtypeCheckInfo::kAssigned, sci.GetState());
return sci.GetEncodedPathToRoot();
}
// Retrieve the path to root bitstring mask as a plain uintN_t value that is amenable to
// be used by a fast check "encoded_src & mask_target == encoded_target".
//
// Cost: O(Depth(Class)).
//
// Returns the mask_target value. Must be Assigned (EnsureAssigned).
static BitString::StorageType GetEncodedPathToRootMask(ClassPtr klass)
REQUIRES(Locks::subtype_check_lock_)
REQUIRES_SHARED(Locks::mutator_lock_) {
SubtypeCheckInfo sci = GetSubtypeCheckInfo(klass);
DCHECK_EQ(SubtypeCheckInfo::kAssigned, sci.GetState());
return sci.GetEncodedPathToRootMask();
}
// Is the source class a subclass of the target?
//
// The source state must be at least Initialized, and the target state
// must be Assigned, otherwise the result will return kUnknownSubtypeOf.
//
// See EnsureInitialized and EnsureAssigned. Ideally,
// EnsureInitialized will be called previously on all possible sources,
// and EnsureAssigned will be called previously on all possible targets.
//
// Runtime cost: O(Depth(Class)), but would be O(1) if depth was known.
//
// If the result is known, return kSubtypeOf or kNotSubtypeOf.
static SubtypeCheckInfo::Result IsSubtypeOf(ClassPtr source, ClassPtr target)
REQUIRES_SHARED(Locks::mutator_lock_) {
SubtypeCheckInfo sci = GetSubtypeCheckInfo(source);
SubtypeCheckInfo target_sci = GetSubtypeCheckInfo(target);
return sci.IsSubtypeOf(target_sci);
}
// Print SubtypeCheck bitstring and overflow to a stream (e.g. for oatdump).
static std::ostream& Dump(ClassPtr klass, std::ostream& os)
REQUIRES_SHARED(Locks::mutator_lock_) {
return os << GetSubtypeCheckInfo(klass);
}
static void WriteStatus(ClassPtr klass, ClassStatus status)
REQUIRES_SHARED(Locks::mutator_lock_) {
WriteStatusImpl(klass, status);
}
private:
static ClassPtr GetParentClass(ClassPtr klass)
REQUIRES_SHARED(Locks::mutator_lock_) {
DCHECK(klass->HasSuperClass());
return ClassPtr(klass->GetSuperClass());
}
static SubtypeCheckInfo InitializeOrAssign(ClassPtr klass, bool assign)
REQUIRES(Locks::subtype_check_lock_)
REQUIRES_SHARED(Locks::mutator_lock_) {
if (UNLIKELY(!klass->HasSuperClass())) {
// Object root always goes directly from Uninitialized -> Assigned.
const SubtypeCheckInfo root_sci = GetSubtypeCheckInfo(klass);
if (root_sci.GetState() != SubtypeCheckInfo::kUninitialized) {
return root_sci; // No change needed.
}
const SubtypeCheckInfo new_root_sci = root_sci.CreateRoot();
SetSubtypeCheckInfo(klass, new_root_sci);
// The object root is always in the Uninitialized|Assigned state.
DCHECK_EQ(SubtypeCheckInfo::kAssigned, GetSubtypeCheckInfo(klass).GetState())
<< "Invalid object root state, must be Assigned";
return new_root_sci;
}
// Force all ancestors to Assigned | Overflowed.
ClassPtr parent_klass = GetParentClass(klass);
size_t parent_depth = InitializeOrAssign(parent_klass, /*assign=*/true).GetDepth();
if (kIsDebugBuild) {
SubtypeCheckInfo::State parent_state = GetSubtypeCheckInfo(parent_klass).GetState();
DCHECK(parent_state == SubtypeCheckInfo::kAssigned ||
parent_state == SubtypeCheckInfo::kOverflowed)
<< "Expected parent Assigned|Overflowed, but was: " << parent_state;
}
// Read.
SubtypeCheckInfo sci = GetSubtypeCheckInfo(klass, parent_depth + 1u);
SubtypeCheckInfo parent_sci = GetSubtypeCheckInfo(parent_klass, parent_depth);
// Modify.
const SubtypeCheckInfo::State sci_state = sci.GetState();
// Skip doing any work if the state is already up-to-date.
// - assign == false -> Initialized or higher.
// - assign == true -> Assigned or higher.
if (sci_state == SubtypeCheckInfo::kUninitialized ||
(sci_state == SubtypeCheckInfo::kInitialized && assign)) {
// Copy parent path into the child.
//
// If assign==true, this also appends Parent.Next value to the end.
// Then the Parent.Next value is incremented to avoid allocating
// the same value again to another node.
sci = parent_sci.CreateChild(assign); // Note: Parent could be mutated.
} else {
// Nothing to do, already >= Initialized.
return sci;
}
// Post-condition: EnsureAssigned -> Assigned|Overflowed.
// Post-condition: EnsureInitialized -> Not Uninitialized.
DCHECK_NE(sci.GetState(), SubtypeCheckInfo::kUninitialized);
if (assign) {
DCHECK_NE(sci.GetState(), SubtypeCheckInfo::kInitialized);
}
// Write.
SetSubtypeCheckInfo(klass, sci); // self
SetSubtypeCheckInfo(parent_klass, parent_sci); // parent
return sci;
}
static SubtypeCheckBitsAndStatus ReadField(ClassPtr klass)
REQUIRES_SHARED(Locks::mutator_lock_) {
SubtypeCheckBitsAndStatus current_bits_and_status;
int32_t int32_data = klass->GetField32Volatile(klass->StatusOffset());
current_bits_and_status.int32_alias_ = int32_data;
if (kIsDebugBuild) {
SubtypeCheckBitsAndStatus tmp;
memcpy(&tmp, &int32_data, sizeof(tmp));
DCHECK_EQ(0, memcmp(&tmp, &current_bits_and_status, sizeof(tmp))) << int32_data;
}
return current_bits_and_status;
}
static void WriteSubtypeCheckBits(ClassPtr klass, const SubtypeCheckBits& new_bits)
REQUIRES(Locks::subtype_check_lock_)
REQUIRES_SHARED(Locks::mutator_lock_) {
// Use a "CAS" to write the SubtypeCheckBits in the class.
// Although we have exclusive access to the bitstrings, because
// ClassStatus and SubtypeCheckBits share the same word, another thread could
// potentially overwrite that word still.
SubtypeCheckBitsAndStatus new_value;
ClassStatus old_status;
SubtypeCheckBitsAndStatus full_old;
while (true) {
// TODO: Atomic compare-and-swap does not update the 'expected' parameter,
// so we have to read it as a separate step instead.
SubtypeCheckBitsAndStatus old_value = ReadField(klass);
{
SubtypeCheckBits old_bits = old_value.subtype_check_info_;
if (memcmp(&old_bits, &new_bits, sizeof(old_bits)) == 0) {
// Avoid dirtying memory when the data hasn't changed.
return;
}
}
full_old = old_value;
old_status = old_value.status_;
new_value = old_value;
new_value.subtype_check_info_ = new_bits;
if (kIsDebugBuild) {
int32_t int32_data = 0;
memcpy(&int32_data, &new_value, sizeof(int32_t));
DCHECK_EQ(int32_data, new_value.int32_alias_) << int32_data;
DCHECK_EQ(old_status, new_value.status_)
<< "full new: " << bit_cast<uint32_t>(new_value)
<< ", full old: " << bit_cast<uint32_t>(full_old);
}
if (CasFieldWeakSequentiallyConsistent32(klass,
klass->StatusOffset(),
old_value.int32_alias_,
new_value.int32_alias_)) {
break;
}
}
}
static void WriteStatusImpl(ClassPtr klass, ClassStatus status)
REQUIRES_SHARED(Locks::mutator_lock_) {
// Despite not having a lock annotation, this is done with mutual exclusion.
// See Class::SetStatus for more details.
SubtypeCheckBitsAndStatus new_value;
ClassStatus old_status;
while (true) {
// TODO: Atomic compare-and-swap does not update the 'expected' parameter,
// so we have to read it as a separate step instead.
SubtypeCheckBitsAndStatus old_value = ReadField(klass);
old_status = old_value.status_;
if (memcmp(&old_status, &status, sizeof(status)) == 0) {
// Avoid dirtying memory when the data hasn't changed.
return;
}
new_value = old_value;
new_value.status_ = status;
if (CasFieldWeakSequentiallyConsistent32(klass,
klass->StatusOffset(),
old_value.int32_alias_,
new_value.int32_alias_)) {
break;
}
}
}
static bool CasFieldWeakSequentiallyConsistent32(ClassPtr klass,
MemberOffset offset,
int32_t old_value,
int32_t new_value)
REQUIRES_SHARED(Locks::mutator_lock_) {
if (Runtime::Current() != nullptr && Runtime::Current()->IsActiveTransaction()) {
return klass->template CasField32</*kTransactionActive=*/true>(offset,
old_value,
new_value,
CASMode::kWeak,
std::memory_order_seq_cst);
} else {
return klass->template CasField32</*kTransactionActive=*/false>(offset,
old_value,
new_value,
CASMode::kWeak,
std::memory_order_seq_cst);
}
}
// Get the SubtypeCheckInfo for a klass. O(Depth(Class)) since
// it also requires calling klass->Depth.
//
// Anything calling this function will also be O(Depth(Class)).
static SubtypeCheckInfo GetSubtypeCheckInfo(ClassPtr klass)
REQUIRES_SHARED(Locks::mutator_lock_) {
return GetSubtypeCheckInfo(klass, klass->Depth());
}
// Get the SubtypeCheckInfo for a klass with known depth.
static SubtypeCheckInfo GetSubtypeCheckInfo(ClassPtr klass, size_t depth)
REQUIRES_SHARED(Locks::mutator_lock_) {
DCHECK_EQ(depth, klass->Depth());
SubtypeCheckBitsAndStatus current_bits_and_status = ReadField(klass);
const SubtypeCheckInfo current =
SubtypeCheckInfo::Create(current_bits_and_status.subtype_check_info_, depth);
return current;
}
static void SetSubtypeCheckInfo(ClassPtr klass, const SubtypeCheckInfo& new_sci)
REQUIRES(Locks::subtype_check_lock_)
REQUIRES_SHARED(Locks::mutator_lock_) {
SubtypeCheckBits new_bits = new_sci.GetSubtypeCheckBits();
WriteSubtypeCheckBits(klass, new_bits);
}
// Tests can inherit this class. Normal code should use static methods.
SubtypeCheck() = default;
SubtypeCheck(const SubtypeCheck& other) = default;
SubtypeCheck(SubtypeCheck&& other) = default;
~SubtypeCheck() = default;
friend struct MockSubtypeCheck;
};
} // namespace art
#endif // ART_RUNTIME_SUBTYPE_CHECK_H_