Transaction support for MethodType in dex cache.

Test: TransactionTest.ResolveMethodType
Test: m test-art-host-gtest
Test: testrunner.py --host --optimizing --interpreter
Bug: 191765508
Change-Id: I8c80e29fb84d50ca782d05db1bc4634fc002c726
diff --git a/runtime/mirror/dex_cache-inl.h b/runtime/mirror/dex_cache-inl.h
index 1ca4202..0553f4f 100644
--- a/runtime/mirror/dex_cache-inl.h
+++ b/runtime/mirror/dex_cache-inl.h
@@ -202,10 +202,27 @@
   DCHECK(resolved != nullptr);
   GetResolvedMethodTypes()[MethodTypeSlotIndex(proto_idx)].store(
       MethodTypeDexCachePair(resolved, proto_idx.index_), std::memory_order_relaxed);
+  Runtime* const runtime = Runtime::Current();
+  if (UNLIKELY(runtime->IsActiveTransaction())) {
+    DCHECK(runtime->IsAotCompiler());
+    runtime->RecordResolveMethodType(this, proto_idx);
+  }
   // TODO: Fine-grained marking, so that we don't need to go through all arrays in full.
   WriteBarrier::ForEveryFieldWrite(this);
 }
 
+inline void DexCache::ClearMethodType(dex::ProtoIndex proto_idx) {
+  DCHECK(Runtime::Current()->IsAotCompiler());
+  uint32_t slot_idx = MethodTypeSlotIndex(proto_idx);
+  MethodTypeDexCacheType* slot = &GetResolvedMethodTypes()[slot_idx];
+  // This is racy but should only be called from the transactional interpreter.
+  if (slot->load(std::memory_order_relaxed).index == proto_idx.index_) {
+    MethodTypeDexCachePair cleared(nullptr,
+                                   MethodTypeDexCachePair::InvalidIndexForSlot(proto_idx.index_));
+    slot->store(cleared, std::memory_order_relaxed);
+  }
+}
+
 inline CallSite* DexCache::GetResolvedCallSite(uint32_t call_site_idx) {
   DCHECK(Runtime::Current()->IsMethodHandlesEnabled());
   DCHECK_LT(call_site_idx, GetDexFile()->NumCallSiteIds());
diff --git a/runtime/mirror/dex_cache.h b/runtime/mirror/dex_cache.h
index ea52785..03ff221 100644
--- a/runtime/mirror/dex_cache.h
+++ b/runtime/mirror/dex_cache.h
@@ -316,6 +316,10 @@
   void SetResolvedMethodType(dex::ProtoIndex proto_idx, MethodType* resolved)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  // Clear a method type for proto_idx, used to undo method type resolution
+  // in aborted transactions to make sure the method type isn't kept live.
+  void ClearMethodType(dex::ProtoIndex proto_idx) REQUIRES_SHARED(Locks::mutator_lock_);
+
   CallSite* GetResolvedCallSite(uint32_t call_site_idx) REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Attempts to bind |call_site_idx| to the call site |resolved|. The
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index dc4acc0..25bfdc7 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -2759,6 +2759,13 @@
   GetTransaction()->RecordResolveString(dex_cache, string_idx);
 }
 
+void Runtime::RecordResolveMethodType(ObjPtr<mirror::DexCache> dex_cache,
+                                      dex::ProtoIndex proto_idx) const {
+  DCHECK(IsAotCompiler());
+  DCHECK(IsActiveTransaction());
+  GetTransaction()->RecordResolveMethodType(dex_cache, proto_idx);
+}
+
 void Runtime::SetFaultMessage(const std::string& message) {
   std::string* new_msg = new std::string(message);
   std::string* cur_msg = fault_message_.exchange(new_msg);
diff --git a/runtime/runtime.h b/runtime/runtime.h
index 1413b98..59f4d19 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -578,6 +578,8 @@
       REQUIRES(Locks::intern_table_lock_);
   void RecordResolveString(ObjPtr<mirror::DexCache> dex_cache, dex::StringIndex string_idx) const
       REQUIRES_SHARED(Locks::mutator_lock_);
+  void RecordResolveMethodType(ObjPtr<mirror::DexCache> dex_cache, dex::ProtoIndex proto_idx) const
+      REQUIRES_SHARED(Locks::mutator_lock_);
 
   void SetFaultMessage(const std::string& message);
 
diff --git a/runtime/transaction.cc b/runtime/transaction.cc
index 0293d23..0ad7a78 100644
--- a/runtime/transaction.cc
+++ b/runtime/transaction.cc
@@ -268,6 +268,15 @@
   resolve_string_logs_.emplace_back(dex_cache, string_idx);
 }
 
+void Transaction::RecordResolveMethodType(ObjPtr<mirror::DexCache> dex_cache,
+                                          dex::ProtoIndex proto_idx) {
+  DCHECK(dex_cache != nullptr);
+  DCHECK_LT(proto_idx.index_, dex_cache->GetDexFile()->NumProtoIds());
+  MutexLock mu(Thread::Current(), log_lock_);
+  DCHECK(assert_no_new_records_reason_ == nullptr) << assert_no_new_records_reason_;
+  resolve_method_type_logs_.emplace_back(dex_cache, proto_idx);
+}
+
 void Transaction::RecordStrongStringInsertion(ObjPtr<mirror::String> s) {
   InternStringLog log(s, InternStringLog::kStrongString, InternStringLog::kInsert);
   LogInternedString(std::move(log));
@@ -306,6 +315,7 @@
   UndoArrayModifications();
   UndoInternStringTableModifications();
   UndoResolveStringModifications();
+  UndoResolveMethodTypeModifications();
   rolling_back_ = false;
 }
 
@@ -344,6 +354,13 @@
   resolve_string_logs_.clear();
 }
 
+void Transaction::UndoResolveMethodTypeModifications() {
+  for (ResolveMethodTypeLog& method_type_log : resolve_method_type_logs_) {
+    method_type_log.Undo();
+  }
+  resolve_method_type_logs_.clear();
+}
+
 void Transaction::VisitRoots(RootVisitor* visitor) {
   MutexLock mu(Thread::Current(), log_lock_);
   visitor->VisitRoot(reinterpret_cast<mirror::Object**>(&root_), RootInfo(kRootUnknown));
@@ -351,6 +368,7 @@
   VisitArrayLogs(visitor);
   VisitInternStringLogs(visitor);
   VisitResolveStringLogs(visitor);
+  VisitResolveMethodTypeLogs(visitor);
 }
 
 void Transaction::VisitObjectLogs(RootVisitor* visitor) {
@@ -420,6 +438,12 @@
   }
 }
 
+void Transaction::VisitResolveMethodTypeLogs(RootVisitor* visitor) {
+  for (ResolveMethodTypeLog& log : resolve_method_type_logs_) {
+    log.VisitRoots(visitor);
+  }
+}
+
 void Transaction::ObjectLog::LogBooleanValue(MemberOffset offset, uint8_t value, bool is_volatile) {
   LogValue(ObjectLog::kBoolean, offset, value, is_volatile);
 }
@@ -636,6 +660,22 @@
   dex_cache_.VisitRoot(visitor, RootInfo(kRootVMInternal));
 }
 
+void Transaction::ResolveMethodTypeLog::Undo() const {
+  dex_cache_.Read()->ClearMethodType(proto_idx_);
+}
+
+Transaction::ResolveMethodTypeLog::ResolveMethodTypeLog(ObjPtr<mirror::DexCache> dex_cache,
+                                                        dex::ProtoIndex proto_idx)
+    : dex_cache_(dex_cache),
+      proto_idx_(proto_idx) {
+  DCHECK(dex_cache != nullptr);
+  DCHECK_LT(proto_idx_.index_, dex_cache->GetDexFile()->NumProtoIds());
+}
+
+void Transaction::ResolveMethodTypeLog::VisitRoots(RootVisitor* visitor) {
+  dex_cache_.VisitRoot(visitor, RootInfo(kRootVMInternal));
+}
+
 Transaction::InternStringLog::InternStringLog(ObjPtr<mirror::String> s,
                                               StringKind kind,
                                               StringOp op)
diff --git a/runtime/transaction.h b/runtime/transaction.h
index 2fd0e2c..ece548c 100644
--- a/runtime/transaction.h
+++ b/runtime/transaction.h
@@ -130,6 +130,11 @@
       REQUIRES_SHARED(Locks::mutator_lock_)
       REQUIRES(!log_lock_);
 
+  // Record resolve method type.
+  void RecordResolveMethodType(ObjPtr<mirror::DexCache> dex_cache, dex::ProtoIndex proto_idx)
+      REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(!log_lock_);
+
   // Abort transaction by undoing all recorded changes.
   void Rollback()
       REQUIRES_SHARED(Locks::mutator_lock_)
@@ -275,6 +280,21 @@
     DISALLOW_COPY_AND_ASSIGN(ResolveStringLog);
   };
 
+  class ResolveMethodTypeLog : public ValueObject {
+   public:
+    ResolveMethodTypeLog(ObjPtr<mirror::DexCache> dex_cache, dex::ProtoIndex proto_idx);
+
+    void Undo() const REQUIRES_SHARED(Locks::mutator_lock_);
+
+    void VisitRoots(RootVisitor* visitor) REQUIRES_SHARED(Locks::mutator_lock_);
+
+   private:
+    GcRoot<mirror::DexCache> dex_cache_;
+    const dex::ProtoIndex proto_idx_;
+
+    DISALLOW_COPY_AND_ASSIGN(ResolveMethodTypeLog);
+  };
+
   void LogInternedString(InternStringLog&& log)
       REQUIRES(Locks::intern_table_lock_)
       REQUIRES(!log_lock_);
@@ -292,6 +312,9 @@
   void UndoResolveStringModifications()
       REQUIRES(log_lock_)
       REQUIRES_SHARED(Locks::mutator_lock_);
+  void UndoResolveMethodTypeModifications()
+      REQUIRES(log_lock_)
+      REQUIRES_SHARED(Locks::mutator_lock_);
 
   void VisitObjectLogs(RootVisitor* visitor)
       REQUIRES(log_lock_)
@@ -305,6 +328,9 @@
   void VisitResolveStringLogs(RootVisitor* visitor)
       REQUIRES(log_lock_)
       REQUIRES_SHARED(Locks::mutator_lock_);
+  void VisitResolveMethodTypeLogs(RootVisitor* visitor)
+      REQUIRES(log_lock_)
+      REQUIRES_SHARED(Locks::mutator_lock_);
 
   const std::string& GetAbortMessage() REQUIRES(!log_lock_);
 
@@ -313,6 +339,7 @@
   std::map<mirror::Array*, ArrayLog> array_logs_  GUARDED_BY(log_lock_);
   std::list<InternStringLog> intern_string_logs_ GUARDED_BY(log_lock_);
   std::list<ResolveStringLog> resolve_string_logs_ GUARDED_BY(log_lock_);
+  std::list<ResolveMethodTypeLog> resolve_method_type_logs_ GUARDED_BY(log_lock_);
   bool aborted_ GUARDED_BY(log_lock_);
   bool rolling_back_;  // Single thread, no race.
   gc::Heap* const heap_;
diff --git a/runtime/transaction_test.cc b/runtime/transaction_test.cc
index f606d02..4c2c4c2 100644
--- a/runtime/transaction_test.cc
+++ b/runtime/transaction_test.cc
@@ -531,6 +531,39 @@
   ASSERT_FALSE(soa.Self()->IsExceptionPending());
 }
 
+// Tests rolling back resolved method types in dex cache.
+TEST_F(TransactionTest, ResolveMethodType) {
+  ScopedObjectAccess soa(Thread::Current());
+  StackHandleScope<3> hs(soa.Self());
+  Handle<mirror::ClassLoader> class_loader(
+      hs.NewHandle(soa.Decode<mirror::ClassLoader>(LoadDex("Transaction"))));
+  ASSERT_TRUE(class_loader != nullptr);
+
+  Handle<mirror::Class> h_klass(
+      hs.NewHandle(class_linker_->FindClass(soa.Self(), "LTransaction;", class_loader)));
+  ASSERT_TRUE(h_klass != nullptr);
+
+  Handle<mirror::DexCache> h_dex_cache(hs.NewHandle(h_klass->GetDexCache()));
+  ASSERT_TRUE(h_dex_cache != nullptr);
+  const DexFile* const dex_file = h_dex_cache->GetDexFile();
+  ASSERT_TRUE(dex_file != nullptr);
+
+  ASSERT_NE(dex_file->NumProtoIds(), 0u);
+  dex::ProtoIndex proto_index(0u);
+  ASSERT_TRUE(h_dex_cache->GetResolvedMethodType(proto_index) == nullptr);
+
+  // Do the transaction, then roll back.
+  EnterTransactionMode();
+  ObjPtr<mirror::MethodType> method_type =
+      class_linker_->ResolveMethodType(soa.Self(), proto_index, h_dex_cache, class_loader);
+  ASSERT_TRUE(method_type != nullptr);
+  // Make sure the method type was recorded in the dex cache.
+  ASSERT_TRUE(h_dex_cache->GetResolvedMethodType(proto_index) == method_type);
+  RollbackAndExitTransactionMode();
+  // Check that the method type was removed from the dex cache.
+  ASSERT_TRUE(h_dex_cache->GetResolvedMethodType(proto_index) == nullptr);
+}
+
 // Tests successful class initialization without class initializer.
 TEST_F(TransactionTest, EmptyClass) {
   ScopedObjectAccess soa(Thread::Current());