/* * Copyright (C) 2022 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_JNI_LOCAL_REFERENCE_TABLE_H_ #define ART_RUNTIME_JNI_LOCAL_REFERENCE_TABLE_H_ #include #include #include #include #include #include "base/bit_field.h" #include "base/bit_utils.h" #include "base/casts.h" #include "base/dchecked_vector.h" #include "base/locks.h" #include "base/macros.h" #include "base/mem_map.h" #include "base/mutex.h" #include "gc_root.h" #include "indirect_reference_table.h" #include "mirror/object_reference.h" #include "obj_ptr.h" #include "offsets.h" namespace art HIDDEN { class RootInfo; namespace mirror { class Object; } // namespace mirror namespace jni { // Maintain a table of local JNI references. // // The table contains object references that are part of the GC root set. When an object is // added we return an `IndirectRef` that is not a valid pointer but can be used to find the // original value in O(1) time. Conversions to and from local JNI references are performed // on upcalls and downcalls as well as in JNI functions, so they need to be very fast. // // To be efficient for JNI local variable storage, we need to provide operations that allow us to // operate on segments of the table, where segments are pushed and popped as if on a stack. For // example, deletion of an entry should only succeed if it appears in the current segment, and we // want to be able to strip off the current segment quickly when a method returns. Additions to the // table must be made in the current segment even if space is available in an earlier area. // // A new segment is created when we call into native code from managed code, or when we handle // the JNI PushLocalFrame function. // // The GC must be able to scan the entire table quickly. // // In summary, these must be very fast: // - adding or removing a segment // - adding references (always adding to the current segment) // - converting a local reference back to an Object // These can be a little slower, but must still be pretty quick: // - removing individual references // - scanning the entire table straight through // // If there's more than one segment, we don't guarantee that the table will fill completely before // we fail due to lack of space. We do ensure that the current segment will pack tightly, which // should satisfy JNI requirements (e.g. EnsureLocalCapacity). // To get the desired behavior for JNI locals, we need to know the bottom and top of the current // "segment". When we call a native method or push a local frame, the current top index gets pushed // on, and serves as the new bottom. When we pop a frame off, the value from the stack becomes the // new top index, and the value stored in the previous frame becomes the new bottom. // // If we delete entries from the middle of the list, we will be left with "holes" which we track // with a singly-linked list, so that they can be reused quickly. After a segment has been removed, // we need to prune removed free entries from the front of this singly-linked list before we can // reuse a free entry from the current segment. This is linear in the number of entries removed // and may appear as a slow reference addition but this slow down is attributable to the previous // removals with a constant time per removal. // // Without CheckJNI, we aim for the fastest possible implementation, so there is no error checking // (in release build) and stale references can be erroneously used, especially after the same slot // has been reused for another reference which we cannot easily detect (even in debug build). // // With CheckJNI, we rotate the slots that we use based on a "serial number". // This increases the memory use but it allows for decent error detection. // // We allow switching between CheckJNI enabled and disabled but entries created with CheckJNI // disabled shall have weaker checking even after enabling CheckJNI and the switch can also // prevent reusing a hole that held a reference created with a different CheckJNI setting. // The state of the current segment contains the top index. struct LRTSegmentState { uint32_t top_index; }; // Use as initial value for "cookie", and when table has only one segment. static constexpr LRTSegmentState kLRTFirstSegment = { 0 }; // Each entry in the `LocalReferenceTable` can contain a null (initially or after a `Trim()`) // or reference, or it can be marked as free and hold the index of the next free entry. // If CheckJNI is (or was) enabled, some entries can contain serial numbers instead and // only one other entry in a CheckJNI chunk starting with a serial number is active. // // Valid bit patterns: // 33222222222211111111110000000000 // 10987654321098765432109876543210 // null: 00000000000000000000000000000000 // Only above the top index. // reference: <----- reference value ----->000 // See also `kObjectAlignment`. // free: <-------- next free --------->01 // serial number: <------ serial number ------->10 // CheckJNI entry. // Note that serial number entries can appear only as the first entry of a 16-byte aligned // chunk of four entries and the serial number in the range [1, 3] specifies which of the // other three entries in the chunk is currently used. class LrtEntry { public: void SetReference(ObjPtr ref) REQUIRES_SHARED(Locks::mutator_lock_); ObjPtr GetReference() REQUIRES_SHARED(Locks::mutator_lock_); bool IsNull() const { return root_.IsNull(); } void SetNextFree(uint32_t next_free) REQUIRES_SHARED(Locks::mutator_lock_); uint32_t GetNextFree() { DCHECK(IsFree()); DCHECK(!IsSerialNumber()); return NextFreeField::Decode(GetRawValue()); } bool IsFree() { return (GetRawValue() & (1u << kFlagFree)) != 0u; } void SetSerialNumber(uint32_t serial_number) REQUIRES_SHARED(Locks::mutator_lock_); uint32_t GetSerialNumber() { DCHECK(IsSerialNumber()); DCHECK(!IsFree()); return GetSerialNumberUnchecked(); } uint32_t GetSerialNumberUnchecked() { return SerialNumberField::Decode(GetRawValue()); } bool IsSerialNumber() { return (GetRawValue() & (1u << kFlagSerialNumber)) != 0u; } GcRoot* GetRootAddress() { return &root_; } static constexpr uint32_t FreeListEnd() { return MaxInt(kFieldNextFreeBits); } private: // Definitions of bit fields and flags. static constexpr size_t kFlagFree = 0u; static constexpr size_t kFlagSerialNumber = kFlagFree + 1u; static constexpr size_t kFieldNextFree = kFlagSerialNumber + 1u; static constexpr size_t kFieldNextFreeBits = BitSizeOf() - kFieldNextFree; using NextFreeField = BitField; using SerialNumberField = NextFreeField; static_assert(kObjectAlignment > (1u << kFlagFree)); static_assert(kObjectAlignment > (1u << kFlagSerialNumber)); void SetVRegValue(uint32_t value) REQUIRES_SHARED(Locks::mutator_lock_); uint32_t GetRawValue() { return root_.AddressWithoutBarrier()->AsVRegValue(); } // We record the contents as a `GcRoot<>` but it is an actual `GcRoot<>` only if it's below // the current segment's top index, it's not a "serial number" or inactive entry in a CheckJNI // chunk, and it's not marked as "free". Such entries are never null. GcRoot root_; }; static_assert(sizeof(LrtEntry) == sizeof(mirror::CompressedReference)); // Assert that the low bits of an `LrtEntry*` are sufficient for encoding the reference kind. static_assert(enum_cast(IndirectRefKind::kLastKind) < alignof(LrtEntry)); // We initially allocate local reference tables with a small number of entries, packing // multiple tables into a single page. If we need to expand, we double the capacity, // first allocating another chunk with the same number of entries as the first chunk // and then allocating twice as big chunk on each subsequent expansion. static constexpr size_t kInitialLrtBytes = 512; // Number of bytes in an initial local table. static constexpr size_t kSmallLrtEntries = kInitialLrtBytes / sizeof(LrtEntry); static_assert(IsPowerOfTwo(kInitialLrtBytes)); static_assert(kMinPageSize % kInitialLrtBytes == 0); static_assert(kInitialLrtBytes % sizeof(LrtEntry) == 0); // A minimal stopgap allocator for initial small local LRT tables. class SmallLrtAllocator { public: SmallLrtAllocator(); // Allocate a small block of `LrtEntries` for the `LocalReferenceTable` table. The `size` // must be a power of 2, at least `kSmallLrtEntries`, and requiring less than a page of memory. LrtEntry* Allocate(size_t size, std::string* error_msg) REQUIRES(!lock_); void Deallocate(LrtEntry* unneeded, size_t size) REQUIRES(!lock_); private: // Number of free lists in the allocator. #ifdef ART_PAGE_SIZE_AGNOSTIC const size_t num_lrt_slots_ = (WhichPowerOf2(gPageSize / kInitialLrtBytes)); #else static constexpr size_t num_lrt_slots_ = (WhichPowerOf2(gPageSize / kInitialLrtBytes)); #endif size_t GetIndex(size_t size); // Free lists of small chunks linked through the first word. dchecked_vector free_lists_; // Repository of MemMaps used for small LRT tables. dchecked_vector shared_lrt_maps_; Mutex lock_; // Level kGenericBottomLock; acquired before mem_map_lock_, which is a C++ mutex. }; class LocalReferenceTable { public: explicit LocalReferenceTable(bool check_jni); ~LocalReferenceTable(); // Set the CheckJNI enabled status. // Called only from the Zygote post-fork callback while the process is single-threaded. // Enabling CheckJNI reduces the number of entries that can be stored, thus invalidating // guarantees provided by a previous call to `EnsureFreeCapacity()`. void SetCheckJniEnabled(bool enabled); // Returns whether the CheckJNI is enabled for this `LocalReferenceTable`. bool IsCheckJniEnabled() const { return (free_entries_list_ & (1u << kFlagCheckJni)) != 0u; } // Initialize the `LocalReferenceTable`. // // Max_count is the requested minimum initial capacity (resizable). The actual initial // capacity can be higher to utilize all allocated memory. // // Returns true on success. // On failure, returns false and reports error in `*error_msg`. bool Initialize(size_t max_count, std::string* error_msg); // Add a new entry. The `obj` must be a valid non-null object reference. This function // will return null if an error happened (with an appropriate error message set). EXPORT IndirectRef Add(ObjPtr obj, std::string* error_msg) REQUIRES_SHARED(Locks::mutator_lock_); // Given an `IndirectRef` in the table, return the `Object` it refers to. // // This function may abort under error conditions in debug build. // In release builds, error conditions are unchecked and the function can // return old or invalid references from popped segments and deleted entries. ObjPtr Get(IndirectRef iref) const REQUIRES_SHARED(Locks::mutator_lock_) ALWAYS_INLINE; // Updates an existing indirect reference to point to a new object. // Used exclusively for updating `String` references after calling a `String` constructor. void Update(IndirectRef iref, ObjPtr obj) REQUIRES_SHARED(Locks::mutator_lock_); // Remove an existing entry. // // If the entry is not between the current top index and the bottom index // specified by the cookie, we don't remove anything. This is the behavior // required by JNI's DeleteLocalRef function. // // Returns "false" if nothing was removed. bool Remove(IndirectRef iref) REQUIRES_SHARED(Locks::mutator_lock_); void AssertEmpty(); void Dump(std::ostream& os) const REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!Locks::alloc_tracker_lock_); IndirectRefKind GetKind() const { return kLocal; } // Return the number of entries in the entire table. This includes holes, // and so may be larger than the actual number of "live" entries. // The value corresponds to the number of entries for the current CheckJNI setting // and may be wrong if there are entries created with a different CheckJNI setting. size_t Capacity() const { if (IsCheckJniEnabled()) { DCHECK_ALIGNED(segment_state_.top_index, kCheckJniEntriesPerReference); return segment_state_.top_index / kCheckJniEntriesPerReference; } else { return segment_state_.top_index; } } // Ensure that at least free_capacity elements are available, or return false. // Caller ensures free_capacity > 0. bool EnsureFreeCapacity(size_t free_capacity, std::string* error_msg) REQUIRES_SHARED(Locks::mutator_lock_); // See implementation of EnsureFreeCapacity. We'll only state here how much is trivially free, // without recovering holes. Thus this is a conservative estimate. size_t FreeCapacity() const; EXPORT void VisitRoots(RootVisitor* visitor, const RootInfo& root_info) REQUIRES_SHARED(Locks::mutator_lock_); LRTSegmentState PushFrame() { if (kDebugLRT) { LOG(INFO) << "+++ Push frame: previous state " << previous_state_.top_index << " -> " << segment_state_.top_index; } LRTSegmentState result = previous_state_; previous_state_ = segment_state_; return result; } void PopFrame(LRTSegmentState previous_state) { if (kDebugLRT) { LOG(INFO) << "+++ Pop frame: current state " << segment_state_.top_index << " -> " << previous_state_.top_index << ", previous state -> " << previous_state.top_index; } segment_state_ = previous_state_; previous_state_ = previous_state; } static MemberOffset PreviousStateOffset() { // Note: The `previous_state_` must be before any pointer-size-dependent members, so that // `MEMBER_OFFSET()` gives the correct value even for cross-compilation. return MemberOffset(OFFSETOF_MEMBER(LocalReferenceTable, previous_state_)); } static MemberOffset SegmentStateOffset() { // Note: The `segment_state_` must be before any pointer-size-dependent members, so that // `MEMBER_OFFSET()` gives the correct value even for cross-compilation. return MemberOffset(OFFSETOF_MEMBER(LocalReferenceTable, segment_state_)); } // Release pages past the end of the table that may have previously held references. void Trim() REQUIRES_SHARED(Locks::mutator_lock_); /* Reference validation for CheckJNI and debug build. */ bool IsValidReference(IndirectRef, /*out*/std::string* error_msg) const REQUIRES_SHARED(Locks::mutator_lock_); private: static constexpr bool kDebugLRT = false; // Flags and fields in the `free_entries_list_`. static constexpr size_t kFlagCheckJni = 0u; // Skip a bit to have the same value range for the "first free" as the "next free" in `LrtEntry`. static constexpr size_t kFlagPadding = kFlagCheckJni + 1u; static constexpr size_t kFieldFirstFree = kFlagPadding + 1u; static constexpr size_t kFieldFirstFreeSize = BitSizeOf() - kFieldFirstFree; using FirstFreeField = BitField; // The value of `FirstFreeField` in `free_entries_list_` indicating the end of the free list. static constexpr uint32_t kFreeListEnd = LrtEntry::FreeListEnd(); static_assert(kFreeListEnd == MaxInt(kFieldFirstFreeSize)); // The value of `free_entries_list_` indicating empty free list and disabled CheckJNI. static constexpr uint32_t kEmptyFreeListAndCheckJniDisabled = FirstFreeField::Update(kFreeListEnd, 0u); // kFlagCheckJni not set. // The number of entries per reference to detect obsolete reference uses with CheckJNI enabled. // The first entry serves as a serial number, one of the remaining entries can hold the actual // reference or the next free index. static constexpr size_t kCheckJniEntriesPerReference = 4u; static_assert(IsPowerOfTwo(kCheckJniEntriesPerReference)); // The maximum total table size we allow. static constexpr size_t kMaxTableSizeInBytes = 128 * MB; static_assert(IsPowerOfTwo(kMaxTableSizeInBytes)); static_assert(IsPowerOfTwo(sizeof(LrtEntry))); static constexpr size_t kMaxTableSize = kMaxTableSizeInBytes / sizeof(LrtEntry); static IndirectRef ToIndirectRef(LrtEntry* entry) { // The `IndirectRef` can be used to directly access the underlying `GcRoot<>`. DCHECK_EQ(reinterpret_cast*>(entry), entry->GetRootAddress()); return reinterpret_cast( reinterpret_cast(entry) | static_cast(kLocal)); } static LrtEntry* ToLrtEntry(IndirectRef iref) { DCHECK_EQ(IndirectReferenceTable::GetIndirectRefKind(iref), kLocal); return IndirectReferenceTable::ClearIndirectRefKind(iref); } static constexpr size_t GetTableSize(size_t table_index) { // First two tables have size `kSmallLrtEntries`, then it doubles for subsequent tables. return kSmallLrtEntries << (table_index != 0u ? table_index - 1u : 0u); } static constexpr size_t NumTablesForSize(size_t size) { DCHECK_GE(size, kSmallLrtEntries); DCHECK(IsPowerOfTwo(size)); return 1u + WhichPowerOf2(size / kSmallLrtEntries); } static size_t MaxSmallTables() { return NumTablesForSize(gPageSize / sizeof(LrtEntry)); } LrtEntry* GetEntry(size_t entry_index) const { DCHECK_LT(entry_index, max_entries_); if (LIKELY(small_table_ != nullptr)) { DCHECK_LT(entry_index, kSmallLrtEntries); DCHECK_EQ(max_entries_, kSmallLrtEntries); return &small_table_[entry_index]; } size_t table_start_index = (entry_index < kSmallLrtEntries) ? 0u : TruncToPowerOfTwo(entry_index); size_t table_index = (entry_index < kSmallLrtEntries) ? 0u : NumTablesForSize(table_start_index); LrtEntry* table = tables_[table_index]; return &table[entry_index - table_start_index]; } // Get the entry index for a local reference. Note that this may be higher than // the current segment state. Returns maximum uint32 value if the reference does not // point to one of the internal tables. uint32_t GetReferenceEntryIndex(IndirectRef iref) const; static LrtEntry* GetCheckJniSerialNumberEntry(LrtEntry* entry) { return AlignDown(entry, kCheckJniEntriesPerReference * sizeof(LrtEntry)); } static uint32_t IncrementSerialNumber(LrtEntry* serial_number_entry) REQUIRES_SHARED(Locks::mutator_lock_); static bool IsValidSerialNumber(uint32_t serial_number) { return serial_number != 0u && serial_number < kCheckJniEntriesPerReference; } // Debug mode check that the reference is valid. void DCheckValidReference(IndirectRef iref) const REQUIRES_SHARED(Locks::mutator_lock_); // Resize the backing table to be at least `new_size` elements long. The `new_size` // must be larger than the current size. After return max_entries_ >= new_size. bool Resize(size_t new_size, std::string* error_msg); // Extract the first free index from `free_entries_list_`. uint32_t GetFirstFreeIndex() const { return FirstFreeField::Decode(free_entries_list_); } // Remove popped free entries from the list. // Called only if `free_entries_list_` points to a popped entry. template void PrunePoppedFreeEntries(EntryGetter&& get_entry); // Helper template function for visiting roots. template void VisitRootsInternal(Visitor&& visitor) const REQUIRES_SHARED(Locks::mutator_lock_); /// semi-public - read/write by jni down calls. // These two members need to be before any pointer-size-dependent members, so that // `MEMBER_OFFSET()` gives the correct value even for cross-compilation. LRTSegmentState previous_state_; LRTSegmentState segment_state_; // The maximum number of entries (modulo resizing). uint32_t max_entries_; // The singly-linked list of free nodes. // We use entry indexes instead of pointers and `kFreeListEnd` instead of null indicates // the end of the list. See `LocalReferenceTable::GetEntry()` and `LrtEntry::GetNextFree(). // // We use the lowest bit to record whether CheckJNI is enabled. This helps us // check that the list is empty and CheckJNI is disabled in a single comparison. uint32_t free_entries_list_; // Individual tables. // As long as we have only one small table, we use `small_table_` to avoid an extra load // from another heap allocated location, otherwise we set it to null and use `tables_`. LrtEntry* small_table_; // For optimizing the fast-path. dchecked_vector tables_; // Mem maps where we store tables allocated directly with `MemMap` // rather than the `SmallLrtAllocator`. dchecked_vector table_mem_maps_; }; } // namespace jni } // namespace art #endif // ART_RUNTIME_JNI_LOCAL_REFERENCE_TABLE_H_