Impl Arena checkpoint.

and allow thread to use thread local scratch arena.

PiperOrigin-RevId: 680948868
Change-Id: Id740bc14007ce4fae8eb51ccdb1ab22f10b6ac15
diff --git a/src/tools/remote/src/main/cpp/testonly_output_service/memory.cc b/src/tools/remote/src/main/cpp/testonly_output_service/memory.cc
index bcc6133..19a0e43 100644
--- a/src/tools/remote/src/main/cpp/testonly_output_service/memory.cc
+++ b/src/tools/remote/src/main/cpp/testonly_output_service/memory.cc
@@ -42,6 +42,7 @@
 }
 
 void FreeArena(Arena *arena) {
+  assert(arena->temp_memory_count == 0 && "Temporary memory still in use");
   size_t reserved_size = arena->reserved - (uint8_t *)arena;
   ReleaseMemory(arena, reserved_size);
 }
@@ -73,3 +74,50 @@
   arena->top -= size;
   assert(arena->top >= GetArenaBase(arena));
 }
+
+TemporaryMemory BeginTemporaryMemory(Arena *arena) {
+  TemporaryMemory temp;
+  temp.arena = arena;
+  temp.top = arena->top;
+  ++arena->temp_memory_count;
+  return temp;
+}
+
+void EndTemporaryMempory(TemporaryMemory temp) {
+  Arena *arena = temp.arena;
+  assert(arena->temp_memory_count > 0 && temp.top <= arena->top);
+  PopArena(arena, arena->top - temp.top);
+  assert(arena->top == temp.top);
+  --arena->temp_memory_count;
+}
+
+constexpr size_t kScratchArenaCount = 2;
+thread_local Arena *t_scratch_arenas[kScratchArenaCount];
+
+Arena *GetScratchArena(Arena **conflicts, size_t count) {
+  Arena *result = 0;
+  for (size_t i = 0; i < kScratchArenaCount; ++i) {
+    Arena *candidate = t_scratch_arenas[i];
+    if (!candidate) {
+      result = AllocArena();
+      t_scratch_arenas[i] = result;
+      break;
+    }
+
+    bool conflict = false;
+    for (size_t j = 0; j < count; ++j) {
+      if (candidate == conflicts[j]) {
+        conflict = true;
+        break;
+      }
+    }
+
+    if (!conflict) {
+      result = candidate;
+      break;
+    }
+  }
+
+  assert(result && "No scratch arena available");
+  return result;
+}
diff --git a/src/tools/remote/src/main/cpp/testonly_output_service/memory.h b/src/tools/remote/src/main/cpp/testonly_output_service/memory.h
index 3a777f8..95f00ba 100644
--- a/src/tools/remote/src/main/cpp/testonly_output_service/memory.h
+++ b/src/tools/remote/src/main/cpp/testonly_output_service/memory.h
@@ -50,9 +50,10 @@
   //
   // Invariant: top <= committed <= reserved
   uint8_t *top;
+  uint32_t temp_memory_count;
 };
 
-Arena *AllocArena(size_t reserve_size);
+Arena *AllocArena(size_t reserve_size = GiB(1));
 void FreeArena(Arena *arena);
 
 // PushArena allocates a block of memory of the given size in the arena. The
@@ -63,4 +64,40 @@
 #define PushArray(arena, type, count) \
   (type *)PushArena(arena, sizeof(type) * (count))
 
+struct TemporaryMemory {
+  Arena *arena;
+  uint8_t *top;
+};
+
+// Begins temporary memory allocations for the arena. The returned
+// TemporaryMemory must be passed to EndTemporaryMemory() when the memory is no
+// longer needed.
+TemporaryMemory BeginTemporaryMemory(Arena *arena);
+// Ends the temporary memory allocations. All memory allocated since
+// BeginTemporaryMemory() was called is freed.
+void EndTemporaryMempory(TemporaryMemory temp);
+
+// Gets a thread-local arena for temporary use. Use `conflicts` to avoid
+// returning the same arena as a previous call to GetScratchArena().
+Arena *GetScratchArena(Arena **conflicts, size_t count);
+
+// Begins the use of a thread-local scratch arena. The returned TemporaryMemory
+// must be passed to EndScratch() when the memory is no longer needed.
+static inline TemporaryMemory BeginScratch(Arena **conflicts, size_t count) {
+  return BeginTemporaryMemory(GetScratchArena(conflicts, count));
+}
+
+static inline TemporaryMemory BeginScratch(Arena *conflict) {
+  if (conflict) {
+    return BeginTemporaryMemory(GetScratchArena(&conflict, 1));
+  } else {
+    return BeginTemporaryMemory(GetScratchArena(0, 0));
+  }
+}
+
+// Ends the use of a thread-local scratch arena.
+static inline void EndScratch(TemporaryMemory scratch) {
+  EndTemporaryMempory(scratch);
+}
+
 #endif  // BAZEL_SRC_TOOLS_REMOTE_SRC_MAIN_CPP_TESTONLY_OUTPUT_SERVICE_MEMORY_H_