Add transactions for string resolve

Fixes a bug where resolved strings can be left in the dex cache after
a transaction is rolled back even though the interned string was
removed.

Added test in transaction_test.

Bug: 31239436

Test: test-art-host

Change-Id: I42c67bcefeae8db134cde34c480261f52db4102e
diff --git a/runtime/mirror/dex_cache-inl.h b/runtime/mirror/dex_cache-inl.h
index a3071b7..220979a 100644
--- a/runtime/mirror/dex_cache-inl.h
+++ b/runtime/mirror/dex_cache-inl.h
@@ -44,13 +44,29 @@
 
 inline void DexCache::SetResolvedString(uint32_t string_idx, mirror::String* resolved) {
   DCHECK_LT(string_idx % NumStrings(), NumStrings());
-  // TODO default transaction support.
-  StringDexCachePair idx_ptr;
-  idx_ptr.string_index = string_idx;
-  idx_ptr.string_pointer = GcRoot<String>(resolved);
-  GetStrings()[string_idx % NumStrings()].store(idx_ptr, std::memory_order_relaxed);
+  GetStrings()[string_idx % NumStrings()].store(
+      StringDexCachePair(resolved, string_idx),
+      std::memory_order_relaxed);
+  Runtime* const runtime = Runtime::Current();
+  if (UNLIKELY(runtime->IsActiveTransaction())) {
+    DCHECK(runtime->IsAotCompiler());
+    runtime->RecordResolveString(this, string_idx);
+  }
   // TODO: Fine-grained marking, so that we don't need to go through all arrays in full.
-  Runtime::Current()->GetHeap()->WriteBarrierEveryFieldOf(this);
+  runtime->GetHeap()->WriteBarrierEveryFieldOf(this);
+}
+
+inline void DexCache::ClearString(uint32_t string_idx) {
+  const uint32_t slot_idx = string_idx % NumStrings();
+  DCHECK(Runtime::Current()->IsAotCompiler());
+  StringDexCacheType* slot = &GetStrings()[slot_idx];
+  // This is racy but should only be called from the transactional interpreter.
+  if (slot->load(std::memory_order_relaxed).string_index == string_idx) {
+    StringDexCachePair cleared(
+        nullptr,
+        StringDexCachePair::InvalidStringIndexForSlot(slot_idx));
+    slot->store(cleared, std::memory_order_relaxed);
+  }
 }
 
 inline Class* DexCache::GetResolvedType(uint32_t type_idx) {
diff --git a/runtime/mirror/dex_cache.h b/runtime/mirror/dex_cache.h
index caf00c2..7d4021f 100644
--- a/runtime/mirror/dex_cache.h
+++ b/runtime/mirror/dex_cache.h
@@ -56,12 +56,20 @@
   // it's always non-null if the string id branch succeeds (except for the 0th string id).
   // Set the initial state for the 0th entry to be {0,1} which is guaranteed to fail
   // the lookup string id == stored id branch.
+  StringDexCachePair(String* string, uint32_t string_idx)
+      : string_pointer(string),
+        string_index(string_idx) {}
+  StringDexCachePair() = default;
+  StringDexCachePair(const StringDexCachePair&) = default;
+  StringDexCachePair& operator=(const StringDexCachePair&) = default;
+
   static void Initialize(StringDexCacheType* strings) {
     mirror::StringDexCachePair first_elem;
     first_elem.string_pointer = GcRoot<String>(nullptr);
-    first_elem.string_index = 1;
+    first_elem.string_index = InvalidStringIndexForSlot(0);
     strings[0].store(first_elem, std::memory_order_relaxed);
   }
+
   static GcRoot<String> LookupString(StringDexCacheType* dex_cache,
                                      uint32_t string_idx,
                                      uint32_t cache_size) {
@@ -71,10 +79,15 @@
     DCHECK(!index_string.string_pointer.IsNull());
     return index_string.string_pointer;
   }
+
+  static uint32_t InvalidStringIndexForSlot(uint32_t slot) {
+    // Since the cache size is a power of two, 0 will always map to slot 0.
+    // Use 1 for slot 0 and 0 for all other slots.
+    return (slot == 0) ? 1u : 0u;
+  }
 };
 using StringDexCacheType = std::atomic<StringDexCachePair>;
 
-
 // C++ mirror of java.lang.DexCache.
 class MANAGED DexCache FINAL : public Object {
  public:
@@ -164,6 +177,10 @@
   void SetResolvedString(uint32_t string_idx, mirror::String* resolved) ALWAYS_INLINE
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  // Clear a string for a string_idx, used to undo string intern transactions to make sure
+  // the string isn't kept live.
+  void ClearString(uint32_t string_idx) REQUIRES_SHARED(Locks::mutator_lock_);
+
   Class* GetResolvedType(uint32_t type_idx) REQUIRES_SHARED(Locks::mutator_lock_);
 
   void SetResolvedType(uint32_t type_idx, Class* resolved) REQUIRES_SHARED(Locks::mutator_lock_);