// TODO This whole thing could be done lock & wait free (since we never remove anything from the // ids list). It's not clear this would be worthwile though.
staticbool CanUseIdArrays(ArtMethod* t) { // We cannot use ID arrays from the ClassExt object for obsolete and default conflict methods. The // ID arrays hold an ID corresponding to the methods in the methods_list. Obsolete methods aren't // in the method list. For default conflicting methods it is difficult to find the class that // contains the copied method, so we omit using ID arrays. For Default conflicting methods we // cannot use the canonical method because canonicalizing would return a method from one of the // interface classes. If we use that method ID and invoke it via the CallNonVirtual JNI interface, // it wouldn't throw the expected ICCE. return !(t->IsObsolete() || t->IsDefaultConflicting());
}
template <typename ArtType>
ObjPtr<mirror::PointerArray> GetIds(ObjPtr<mirror::Class> k, ArtType* t)
REQUIRES_SHARED(Locks::mutator_lock_) {
ObjPtr<mirror::Object> ret; if constexpr (std::is_same_v<ArtType, ArtField>) {
ret = t->IsStatic() ? k->GetStaticFieldIds() : k->GetInstanceFieldIds();
} else {
ret = CanUseIdArrays(t) ? k->GetMethodIds() : nullptr;
}
DCHECK(ret.IsNull() || ret->IsArrayInstance()) << "Should have bailed out early!"; if (kIsDebugBuild && !ret.IsNull()) { if (kRuntimePointerSize == PointerSize::k32) {
CHECK(ret->IsIntArray());
} else {
CHECK(ret->IsLongArray());
}
} return down_cast<mirror::PointerArray*>(ret.Ptr());
}
// Forces the appropriate id array to be present if possible. Returns true if allocation was // attempted but failed. template <typename ArtType> bool EnsureIdsArray(Thread* self, ObjPtr<mirror::Class> k, ArtType* t)
REQUIRES_SHARED(Locks::mutator_lock_);
template <> bool EnsureIdsArray(Thread* self, ObjPtr<mirror::Class> k, ArtField* field) {
ScopedExceptionStorage ses(self);
StackHandleScope<1> hs(self);
Handle<mirror::Class> h_k(hs.NewHandle(k)); if (Locks::mutator_lock_->IsExclusiveHeld(self)) { returnfalse;
} else { // NB This modifies the class to allocate the ClassExt and the ids array.
field->IsStatic() ? mirror::Class::EnsureStaticFieldIds(h_k)
: mirror::Class::EnsureInstanceFieldIds(h_k);
} if (self->IsExceptionPending()) {
self->AssertPendingOOMException();
ses.SuppressOldException("Failed to allocate maps for jmethodIDs. "); returntrue;
} returnfalse;
}
template <> bool EnsureIdsArray(Thread* self, ObjPtr<mirror::Class> k, ArtMethod* method) { if (!CanUseIdArrays(method)) { if (kTraceIds) {
LOG(INFO) << "jmethodID for Obsolete / Default conflicting method " << method->PrettyMethod()
<< " requested!";
} // No ids array for obsolete / default conflicting methods. Just do a linear scan. returnfalse;
}
StackHandleScope<1> hs(self);
Handle<mirror::Class> h_k(hs.NewHandle(k)); if (Locks::mutator_lock_->IsExclusiveHeld(self) || !Locks::mutator_lock_->IsSharedHeld(self)) { returnfalse;
} else { // NB This modifies the class to allocate the ClassExt and the ids array.
mirror::Class::EnsureMethodIds(h_k);
} if (self->IsExceptionPending()) {
self->AssertPendingOOMException(); returntrue;
} returnfalse;
}
// Checks if the field or method can use the ID array from class extension. template <typename ArtType> bool CanUseIdArrays(ReflectiveHandle<ArtType> t) REQUIRES_SHARED(Locks::mutator_lock_); template <> bool CanUseIdArrays([[maybe_unused]] ReflectiveHandle<ArtField> t) { returntrue;
} template <> bool CanUseIdArrays(ReflectiveHandle<ArtMethod> t) { return CanUseIdArrays(t.Get());
}
// Get the canonical (non-copied) version of the field or method. Only relevant for methods. template <typename ArtType>
ArtType* Canonicalize(ReflectiveHandle<ArtType> t) REQUIRES_SHARED(Locks::mutator_lock_); template <>
ArtField* Canonicalize(ReflectiveHandle<ArtField> t) { return t.Get();
} template <>
ArtMethod* Canonicalize(ReflectiveHandle<ArtMethod> t) { if (UNLIKELY(t->IsCopied())) { return t->GetCanonicalMethod();
} return t.Get();
}
}; // namespace
// We increment the id by 2 each time to allow us to use the LSB as a flag that the ID is an index // and not a pointer. This gives us 2**31 unique methods that can be addressed on 32-bit art, which // should be more than enough. template <>
uintptr_t JniIdManager::GetNextId<ArtField>(JniIdType type) {
DCHECK_EQ(type, JniIdType::kIndices);
uintptr_t res = next_field_id_;
next_field_id_ += 2;
CHECK_GT(next_field_id_, res) << "jfieldID Overflow"; return res;
}
template <>
size_t JniIdManager::GetLinearSearchStartId<ArtMethod>(ReflectiveHandle<ArtMethod> m) { if (CanUseIdArrays(m)) { // If we are searching because we couldn't allocate because of defer allocate scope, then we // should only look from deferred_allocation_method_id_start_. Once we exit the deferred scope // all these method ids will be updated to the id arrays in the respective ClassExt objects. return deferred_allocation_method_id_start_;
} else { // If we cannot use ID arrays, then the method can be anywhere in the list. return1;
}
}
// TODO need to fix races in here with visitors template <typename ArtType>
uintptr_t JniIdManager::EncodeGenericId(ReflectiveHandle<ArtType> t) {
static_assert(std::is_same_v<ArtType, ArtField> || std::is_same_v<ArtType, ArtMethod>, "Expected ArtField or ArtMethod");
Runtime* runtime = Runtime::Current();
JniIdType id_type = runtime->GetJniIdType();
DCHECK_NE(id_type, JniIdType::kPointer); if (t == nullptr) { return0u;
}
Thread* self = Thread::Current();
ScopedExceptionStorage ses(self);
DCHECK(!t->GetDeclaringClass().IsNull()) << "Null declaring class " << PrettyGeneric(t);
size_t off = -1; bool allocation_failure = false; // When we cannot use ID arrays, we just fallback to looking through the list to obtain the ID. // These are rare cases so shouldn't be a problem for performance. See CanUseIdArrays for more // information. if (CanUseIdArrays(t)) {
off = GetIdOffset(t->GetDeclaringClass(), Canonicalize(t), kRuntimePointerSize); // Here is the earliest point we can suspend.
allocation_failure = EnsureIdsArray(self, t->GetDeclaringClass(), t.Get());
} if (allocation_failure) {
self->AssertPendingOOMException();
ses.SuppressOldException("OOM exception while trying to allocate JNI ids."); return0u;
} elseif (ShouldReturnPointer(t->GetDeclaringClass(), t.Get())) { // TODO(mythria): Check why we return a pointer here instead of falling back // to the slow path of finding the ID by looping through the ID -> method // map. This seem incorrect. For example, if we are in ScopedEnableSuspendAllJniIdQueries // scope, we don't allocate ID arrays. We would then incorrectly return a // pointer here. return std::is_same_v<ArtType, ArtField>
? reinterpret_cast<uintptr_t>(EncodeArtField<false>(reinterpret_cast<ArtField*>(t.Get())))
: reinterpret_cast<uintptr_t>(
EncodeArtMethod<false>(reinterpret_cast<ArtMethod*>(t.Get())));
}
ObjPtr<mirror::Class> klass = t->GetDeclaringClass();
ObjPtr<mirror::PointerArray> ids(GetIds(klass, t.Get()));
uintptr_t cur_id = 0; if (!ids.IsNull()) {
DCHECK_GT(ids->GetLength(), static_cast<int32_t>(off)) << " is " << PrettyGeneric(t);
DCHECK_LE(0, static_cast<int32_t>(off)) << " is " << PrettyGeneric(t);
cur_id = ids->GetElementPtrSize<uintptr_t>(off, kRuntimePointerSize);
} if (cur_id != 0) { return cur_id;
}
WriterMutexLock mu(self, *Locks::jni_id_lock_);
ScopedAssertNoThreadSuspension sants("EncodeJniId critical section."); // Check the ids array for a racing id.
constexpr std::pair<size_t, size_t> counts {
std::is_same_v<ArtType, ArtField> ? 1 : 0,
std::is_same_v<ArtType, ArtField> ? 0 : 1,
};
StackReflectiveHandleScope<counts.first, counts.second> hs(self);
t = hs.NewHandle(Canonicalize(t)); if (!ids.IsNull()) { // It's possible we got suspended and structurally redefined during the EnsureIdsArray. We need // to get the information again.
ids = GetIds(klass, t.Get());
off = GetIdOffset(klass, Canonicalize(t), kRuntimePointerSize);
CHECK(!ids.IsNull());
cur_id = ids->GetElementPtrSize<uintptr_t>(off, kRuntimePointerSize); if (cur_id != 0) { // We were racing some other thread and lost. return cur_id;
}
} else { // We cannot allocate anything here or don't have an ids array (we might be an obsolete method).
DCHECK(!CanUseIdArrays(t) || deferred_allocation_refcount_ > 0u)
<< "deferred_allocation_refcount_: " << deferred_allocation_refcount_
<< " t: " << PrettyGeneric(t); // Check to see if we raced and lost to another thread. const std::vector<ArtType*>& vec = GetGenericMap<ArtType>(); bool found = false; // simple count-while.
size_t search_start_index = IdToIndex(GetLinearSearchStartId(t));
size_t index = std::count_if(vec.cbegin() + search_start_index,
vec.cend(),
[&found, &self, t](const ArtType* candidate) {
Locks::mutator_lock_->AssertSharedHeld(self);
found = found || candidate == t.Get(); return !found;
}) +
search_start_index; if (found) { // We were either racing some other thread and lost or this thread was asked to encode the // same method multiple times while holding the mutator lock.
DCHECK_EQ(vec[index], t.Get())
<< "Expected: " << PrettyGeneric(vec[index]) << " got " << PrettyGeneric(t)
<< " at index " << index << " (id: " << IndexToId(index) << ")."; return IndexToId(index);
}
}
cur_id = GetNextId<ArtType>(id_type);
DCHECK_EQ(cur_id % 2, 1u);
size_t cur_index = IdToIndex(cur_id);
std::vector<ArtType*>& vec = GetGenericMap<ArtType>();
vec.reserve(cur_index + 1);
vec.resize(std::max(vec.size(), cur_index + 1), nullptr);
vec[cur_index] = t.Get(); if (ids.IsNull()) { if (kIsDebugBuild && CanUseIdArrays(t)) {
CHECK_NE(deferred_allocation_refcount_, 0u)
<< "Failed to allocate ids array despite not being forbidden from doing so!";
Locks::mutator_lock_->AssertExclusiveHeld(self);
}
} else {
ids->SetElementPtrSize(off, reinterpret_cast<void*>(cur_id), kRuntimePointerSize);
} return cur_id;
}
void JniIdManager::Init(Thread* self) { // When compiling we don't want to have anything to do with any of this, which is fine since JNI // ids won't be created during AOT compilation. This also means we don't need to do any // complicated stuff with the image-writer. if (!Runtime::Current()->IsAotCompiler()) { // Allocate the marker
StackHandleScope<3> hs(self);
Handle<mirror::Object> marker_obj(
hs.NewHandle(GetClassRoot<mirror::Object>()->AllocObject(self)));
CHECK(!marker_obj.IsNull());
pointer_marker_ = GcRoot<mirror::Object>(marker_obj.Get()); // Manually mark class-ext as having all pointer-ids to avoid any annoying loops.
Handle<mirror::Class> class_ext_class(hs.NewHandle(GetClassRoot<mirror::ClassExt>()));
mirror::Class::EnsureExtDataPresent(class_ext_class, self);
Handle<mirror::ClassExt> class_ext_ext(hs.NewHandle(class_ext_class->GetExtData()));
class_ext_ext->SetIdsArraysForClassExtExtData(marker_obj.Get());
}
}
void JniIdManager::VisitReflectiveTargets(ReflectiveValueVisitor* rvv) {
art::WriterMutexLock mu(Thread::Current(), *Locks::jni_id_lock_); for (auto it = field_id_map_.begin(); it != field_id_map_.end(); ++it) {
ArtField* old_field = *it;
uintptr_t id = IndexToId(std::distance(field_id_map_.begin(), it));
ArtField* new_field =
rvv->VisitField(old_field, JniIdReflectiveSourceInfo(reinterpret_cast<jfieldID>(id))); if (old_field != new_field) {
*it = new_field;
ObjPtr<mirror::Class> old_class(old_field->GetDeclaringClass());
ObjPtr<mirror::Class> new_class(new_field->GetDeclaringClass());
ObjPtr<mirror::ClassExt> old_ext_data(old_class->GetExtData());
ObjPtr<mirror::ClassExt> new_ext_data(new_class->GetExtData()); if (!old_ext_data.IsNull()) {
CHECK(!old_ext_data->HasInstanceFieldPointerIdMarker() &&
!old_ext_data->HasStaticFieldPointerIdMarker())
<< old_class->PrettyClass(); // Clear the old field mapping.
size_t old_off = ArraySlice<ArtField>(old_class->GetFieldsPtr()).OffsetOf(old_field);
ObjPtr<mirror::PointerArray> array(old_field->IsStatic()
? old_ext_data->GetStaticJFieldIDsPointerArray()
: old_ext_data->GetInstanceJFieldIDsPointerArray()); if (!array.IsNull()) {
array->SetElementPtrSize(old_off, 0, kRuntimePointerSize);
}
} if (!new_ext_data.IsNull()) {
CHECK(!new_ext_data->HasInstanceFieldPointerIdMarker() &&
!new_ext_data->HasStaticFieldPointerIdMarker())
<< new_class->PrettyClass(); // Set the new field mapping.
size_t new_off = ArraySlice<ArtField>(new_class->GetFieldsPtr()).OffsetOf(new_field);
ObjPtr<mirror::PointerArray> array(new_field->IsStatic()
? new_ext_data->GetStaticJFieldIDsPointerArray()
: new_ext_data->GetInstanceJFieldIDsPointerArray()); if (!array.IsNull()) {
array->SetElementPtrSize(new_off, id, kRuntimePointerSize);
}
}
}
} for (auto it = method_id_map_.begin(); it != method_id_map_.end(); ++it) {
ArtMethod* old_method = *it;
uintptr_t id = IndexToId(std::distance(method_id_map_.begin(), it));
ArtMethod* new_method =
rvv->VisitMethod(old_method, JniIdReflectiveSourceInfo(reinterpret_cast<jmethodID>(id))); if (old_method != new_method) {
*it = new_method;
ObjPtr<mirror::Class> old_class(old_method->GetDeclaringClass());
ObjPtr<mirror::Class> new_class(new_method->GetDeclaringClass());
ObjPtr<mirror::ClassExt> old_ext_data(old_class->GetExtData());
ObjPtr<mirror::ClassExt> new_ext_data(new_class->GetExtData()); if (!old_ext_data.IsNull()) {
CHECK(!old_ext_data->HasMethodPointerIdMarker()) << old_class->PrettyClass(); // Clear the old method mapping.
size_t old_off = ArraySlice<ArtMethod>(old_class->GetMethodsPtr()).OffsetOf(old_method);
ObjPtr<mirror::PointerArray> old_methods(old_ext_data->GetJMethodIDsPointerArray()); if (!old_methods.IsNull()) {
old_methods->SetElementPtrSize(old_off, 0, kRuntimePointerSize);
}
} if (!new_ext_data.IsNull()) {
CHECK(!new_ext_data->HasMethodPointerIdMarker()) << new_class->PrettyClass(); // Set the new method mapping.
size_t new_off = ArraySlice<ArtMethod>(new_class->GetMethodsPtr()).OffsetOf(new_method);
ObjPtr<mirror::PointerArray> new_methods(new_ext_data->GetJMethodIDsPointerArray()); if (!new_methods.IsNull()) {
new_methods->SetElementPtrSize(new_off, id, kRuntimePointerSize);
}
}
}
}
}
// This whole defer system is an annoying requirement to allow us to generate IDs during heap-walks // such as those required for instrumentation tooling. // // The defer system works with the normal id-assignment routine to ensure that all the class-ext // data structures are eventually created and filled in. Basically how it works is the id-assignment // function will check to see if it has a strong mutator-lock. If it does not then it will try to // allocate the class-ext data structures normally and fail if it is unable to do so. In the case // where mutator-lock is being held exclusive no attempt to allocate will be made and the thread // will CHECK that allocations are being deferred (or that the method is obsolete, in which case // there is no class-ext to store the method->id map in). // // Once the thread is done holding the exclusive mutator-lock it will go back and fill-in the // class-ext data of all the methods that were added. We do this without the exclusive mutator-lock // on a copy of the maps before we decrement the deferred refcount. This ensures that any other // threads running at the same time know they need to perform a linear scan of the id-map. Since we // don't have the mutator-lock anymore other threads can allocate the class-ext data, meaning our // copy is fine. The only way additional methods could end up on the id-maps after our copy without // having class-ext data is if another thread picked up the exclusive mutator-lock and added another // defer, in which case that thread would fix-up the remaining ids. In this way we maintain eventual // consistency between the class-ext method/field->id maps and the JniIdManager id->method/field // maps. // // TODO It is possible that another thread to gain the mutator-lock and allocate new ids without // calling StartDefer. This is basically a race that we should try to catch but doing so is // rather difficult and since this defer system is only used in very rare circumstances unlikely to // be worth the trouble. void JniIdManager::StartDefer() {
Thread* self = Thread::Current();
WriterMutexLock mu(self, *Locks::jni_id_lock_); if (deferred_allocation_refcount_++ == 0) {
deferred_allocation_field_id_start_ = next_field_id_;
deferred_allocation_method_id_start_ = next_method_id_;
}
}
class JniIdDeferStackReflectiveScope : public BaseReflectiveHandleScope { public:
JniIdDeferStackReflectiveScope() REQUIRES_SHARED(art::Locks::mutator_lock_)
: BaseReflectiveHandleScope(), methods_(), fields_() {
PushScope(Thread::Current());
}
void JniIdManager::EndDefer() { // Fixup the method->id map.
Thread* self = Thread::Current(); auto set_id = [&](auto** t, uintptr_t id) REQUIRES_SHARED(Locks::mutator_lock_) { if (t == nullptr) { return;
} bool alloc_failure = EnsureIdsArray(self, (*t)->GetDeclaringClass(), *t);
ObjPtr<mirror::Class> klass((*t)->GetDeclaringClass());
size_t off = GetIdOffset(klass, (*t), kRuntimePointerSize);
ObjPtr<mirror::PointerArray> ids = GetIds(klass, (*t));
CHECK(!alloc_failure) << "Could not allocate jni ids array!"; if (ids.IsNull()) { return;
} if (kIsDebugBuild) {
uintptr_t old_id = ids->GetElementPtrSize<uintptr_t, kRuntimePointerSize>(off); if (old_id != 0) {
DCHECK_EQ(old_id, id);
}
}
ids->SetElementPtrSize(off, reinterpret_cast<void*>(id), kRuntimePointerSize);
}; // To ensure eventual consistency this depends on the fact that the method_id_map_ and // field_id_map_ are the ultimate source of truth and no id is ever reused to be valid. It also // relies on all threads always getting calling StartDefer if they are going to be allocating jni // ids while suspended. If a thread tries to do so while it doesn't have a scope we could miss // ids. // TODO We should use roles or something to verify that this requirement is not broken. // // If another thread comes along and adds more methods to the list after // copying either (1) the id-maps are already present for the method and everything is fine, (2) // the thread is not suspended and so can create the ext-data and id lists or, (3) the thread also // suspended everything and incremented the deferred_allocation_refcount_ so it will fix up new // ids when it finishes.
Locks::mutator_lock_->AssertNotExclusiveHeld(self);
Locks::mutator_lock_->AssertSharedHeld(self);
JniIdDeferStackReflectiveScope jidsrs;
uintptr_t method_start_id;
uintptr_t field_start_id;
{
ReaderMutexLock mu(self, *Locks::jni_id_lock_);
ScopedAssertNoThreadSuspension sants(__FUNCTION__);
jidsrs.Initialize(method_id_map_, field_id_map_);
method_start_id = deferred_allocation_method_id_start_;
field_start_id = deferred_allocation_field_id_start_;
}
for (size_t index = kIsDebugBuild ? 0 : IdToIndex(method_start_id); index < jidsrs.NumMethods();
++index) {
set_id(jidsrs.GetMethodPtr(index), IndexToId(index));
} for (size_t index = kIsDebugBuild ? 0 : IdToIndex(field_start_id); index < jidsrs.NumFields();
++index) {
set_id(jidsrs.GetFieldPtr(index), IndexToId(index));
}
WriterMutexLock mu(self, *Locks::jni_id_lock_);
DCHECK_GE(deferred_allocation_refcount_, 1u); if (--deferred_allocation_refcount_ == 0) {
deferred_allocation_field_id_start_ = 0;
deferred_allocation_method_id_start_ = 0;
}
}
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.