LrtEntry* SmallLrtAllocator::Allocate(size_t size, std::string* error_msg) {
size_t index = GetIndex(size);
MutexLock lock(Thread::Current(), lock_);
size_t fill_from = index; while (fill_from != num_lrt_slots_ && free_lists_[fill_from] == nullptr) {
++fill_from;
} void* result = nullptr; if (fill_from != num_lrt_slots_) { // We found a slot with enough memory.
result = free_lists_[fill_from];
free_lists_[fill_from] = *reinterpret_cast<void**>(result);
} else { // We need to allocate a new page and split it into smaller pieces.
MemMap map = NewLRTMap(gPageSize, error_msg); if (!map.IsValid()) { return nullptr;
}
result = map.Begin();
shared_lrt_maps_.emplace_back(std::move(map));
} while (fill_from != index) {
--fill_from; // Store the second half of the current buffer in appropriate free list slot. void* mid = reinterpret_cast<uint8_t*>(result) + (kInitialLrtBytes << fill_from);
DCHECK(free_lists_[fill_from] == nullptr);
*reinterpret_cast<void**>(mid) = nullptr;
free_lists_[fill_from] = mid;
} // Clear the memory we return to the caller.
std::memset(result, 0, kInitialLrtBytes << index); returnreinterpret_cast<LrtEntry*>(result);
}
void SmallLrtAllocator::Deallocate(LrtEntry* unneeded, size_t size) {
size_t index = GetIndex(size);
MutexLock lock(Thread::Current(), lock_); while (index < num_lrt_slots_) { // Check if we can merge this free block with another block with the same size. void** other = reinterpret_cast<void**>( reinterpret_cast<uintptr_t>(unneeded) ^ (kInitialLrtBytes << index)); void** before = &free_lists_[index]; if (index + 1u == num_lrt_slots_ && *before == other && *other == nullptr) { // Do not unmap the page if we do not have other free blocks with index `num_lrt_slots_ - 1`. // (Keep at least one free block to avoid a situation where creating and destroying a single // thread with no local references would map and unmap a page in the `SmallLrtAllocator`.) break;
} while (*before != nullptr && *before != other) {
before = reinterpret_cast<void**>(*before);
} if (*before == nullptr) { break;
} // Remove `other` from the free list and merge it with the `unneeded` block.
DCHECK(*before == other);
*before = *reinterpret_cast<void**>(other);
++index;
unneeded = reinterpret_cast<LrtEntry*>( reinterpret_cast<uintptr_t>(unneeded) & reinterpret_cast<uintptr_t>(other));
} if (index == num_lrt_slots_) { // Free the entire page.
DCHECK(free_lists_[num_lrt_slots_ - 1u] != nullptr); auto match = [=](MemMap& map) { return unneeded == reinterpret_cast<LrtEntry*>(map.Begin()); }; auto it = std::find_if(shared_lrt_maps_.begin(), shared_lrt_maps_.end(), match);
DCHECK(it != shared_lrt_maps_.end());
shared_lrt_maps_.erase(it);
DCHECK(!shared_lrt_maps_.empty()); return;
}
*reinterpret_cast<void**>(unneeded) = free_lists_[index];
free_lists_[index] = unneeded;
}
inline uint32_t LocalReferenceTable::IncrementSerialNumber(LrtEntry* serial_number_entry) {
DCHECK_EQ(serial_number_entry, GetCheckJniSerialNumberEntry(serial_number_entry)); // The old serial number can be 0 if it was not used before. It can also be bits from the // representation of an object reference, or a link to the next free entry written in this // slot before enabling the CheckJNI. (Some gtests repeatedly enable and disable CheckJNI.)
uint32_t old_serial_number =
serial_number_entry->GetSerialNumberUnchecked() % kCheckJniEntriesPerReference;
uint32_t new_serial_number =
(old_serial_number + 1u) != kCheckJniEntriesPerReference ? old_serial_number + 1u : 1u;
DCHECK(IsValidSerialNumber(new_serial_number));
serial_number_entry->SetSerialNumber(new_serial_number); return new_serial_number;
}
// Removes an object. // // This method is not called when a local frame is popped; this is only used // for explicit single removals. // // If the entry is not at the top, we just add it to the free entry list. // If the entry is at the top, we pop it from the top and check if there are // free entries under it to remove in order to reduce the size of the table. // // Returns "false" if nothing was removed. bool LocalReferenceTable::Remove(IndirectRef iref) { if (kDebugLRT) {
LOG(INFO) << "+++ Remove: previous_state=" << previous_state_.top_index
<< " top_index=" << segment_state_.top_index;
}
IndirectRefKind kind = IndirectReferenceTable::GetIndirectRefKind(iref); if (UNLIKELY(kind != kLocal)) {
Thread* self = Thread::Current(); if (kind == kJniTransition) { if (self->IsJniTransitionReference(reinterpret_cast<jobject>(iref))) { // Transition references count as local but they cannot be deleted. // TODO: They could actually be cleared on the stack, except for the `jclass` // reference for static methods that points to the method's declaring class.
JNIEnvExt* env = self->GetJniEnv();
DCHECK(env != nullptr); if (env->IsCheckJniEnabled()) { constchar* msg = kDumpStackOnNonLocalReference
? "Attempt to remove non-JNI local reference, dumping thread"
: "Attempt to remove non-JNI local reference";
LOG(WARNING) << msg; if (kDumpStackOnNonLocalReference) {
self->Dump(LOG_STREAM(WARNING));
}
} returntrue;
}
} if (kDumpStackOnNonLocalReference && IsCheckJniEnabled()) { // Log the error message and stack. Repeat the message as FATAL later.
LOG(ERROR) << "Attempt to delete " << kind
<< " reference as local JNI reference, dumping stack";
self->Dump(LOG_STREAM(ERROR));
}
LOG(IsCheckJniEnabled() ? ERROR : FATAL)
<< "Attempt to delete " << kind << " reference as local JNI reference"; returnfalse;
}
if (entry_index < bottom_index) { // Wrong segment.
LOG(WARNING) << "Attempt to remove index outside index area (" << entry_index
<< " vs " << bottom_index << "-" << top_index << ")"; returnfalse;
}
if (UNLIKELY(IsCheckJniEnabled())) { // Ignore invalid references. CheckJNI should have aborted before passing this reference // to `LocalReferenceTable::Remove()` but gtests intercept the abort and proceed anyway.
std::string error_msg; if (!IsValidReference(iref, &error_msg)) {
LOG(WARNING) << "Attempt to remove invalid reference: " << error_msg; returnfalse;
}
}
DCHECK_LT(entry_index, top_index);
// Workaround for double `DeleteLocalRef` bug. b/298297411 if (entry->IsFree()) { // In debug build or with CheckJNI enabled, we would have detected this above.
LOG(ERROR) << "App error: `DeleteLocalRef()` on already deleted local ref. b/298297411"; returnfalse;
}
// Prune the free entry list if a segment with holes was popped before the `Remove()` call.
uint32_t first_free_index = GetFirstFreeIndex(); if (first_free_index != kFreeListEnd && first_free_index >= top_index) {
PrunePoppedFreeEntries([&](size_t index) { return GetEntry(index); });
}
// Check if we're removing the top entry (created with any CheckJNI setting). bool is_top_entry = false;
uint32_t prune_end = entry_index; if (GetCheckJniSerialNumberEntry(entry)->IsSerialNumber()) {
LrtEntry* serial_number_entry = GetCheckJniSerialNumberEntry(entry);
uint32_t serial_number = dchecked_integral_cast<uint32_t>(entry - serial_number_entry);
DCHECK_EQ(serial_number, serial_number_entry->GetSerialNumber());
prune_end = entry_index - serial_number;
is_top_entry = (prune_end == top_index - kCheckJniEntriesPerReference);
} else {
is_top_entry = (entry_index == top_index - 1u);
} if (is_top_entry) { // Top-most entry. Scan up and consume holes created with the current CheckJNI setting.
constexpr uint32_t kDeadLocalValue = 0xdead10c0;
entry->SetReference(reinterpret_cast32<mirror::Object*>(kDeadLocalValue));
// TODO: Maybe we should not prune free entries from the top of the segment // because it has quadratic worst-case complexity. We could still prune while // the first free list entry is at the top.
uint32_t prune_start = prune_end;
size_t prune_count; auto find_prune_range = [&](size_t chunk_size, auto is_prev_entry_free) { while (prune_start > bottom_index && is_prev_entry_free(prune_start)) {
prune_start -= chunk_size;
}
prune_count = (prune_end - prune_start) / chunk_size;
};
bool LocalReferenceTable::EnsureFreeCapacity(size_t free_capacity, std::string* error_msg) { // TODO: Pass `previous_state` so that we can check holes.
DCHECK_GE(free_capacity, static_cast<size_t>(1));
size_t top_index = segment_state_.top_index;
DCHECK_LE(top_index, max_entries_);
if (IsCheckJniEnabled()) { // High values lead to the maximum size check failing below. if (free_capacity >= std::numeric_limits<size_t>::max() / kCheckJniEntriesPerReference) {
free_capacity = std::numeric_limits<size_t>::max();
} else {
free_capacity *= kCheckJniEntriesPerReference;
}
}
// TODO: Include holes from the current segment in the calculation. if (free_capacity <= max_entries_ - top_index) { returntrue;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.