| /* |
| * 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, ¤t_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_ |