DiscardableHandle Implementation

This change adds a DiscardableHandle class, which acts as the primary
synchronization mechanism for the GPU Discardable Memory system.

This is the first in a sequence of CLs which implement the GPU
Discardable Memory system. This class is currently only created/used by
unit test code.

BUG=706456
CQ_INCLUDE_TRYBOTS=master.tryserver.chromium.android:android_optional_gpu_tests_rel;master.tryserver.chromium.linux:linux_optional_gpu_tests_rel;master.tryserver.chromium.mac:mac_optional_gpu_tests_rel;master.tryserver.chromium.win:win_optional_gpu_tests_rel

Review-Url: https://codereview.chromium.org/2732223004
Cr-Commit-Position: refs/heads/master@{#460610}
diff --git a/gpu/BUILD.gn b/gpu/BUILD.gn
index 82c9e9a..6bad429 100644
--- a/gpu/BUILD.gn
+++ b/gpu/BUILD.gn
@@ -234,6 +234,7 @@
     "command_buffer/common/command_buffer_mock.h",
     "command_buffer/common/command_buffer_shared_test.cc",
     "command_buffer/common/debug_marker_manager_unittest.cc",
+    "command_buffer/common/discardable_handle_unittest.cc",
     "command_buffer/common/gles2_cmd_format_test.cc",
     "command_buffer/common/gles2_cmd_format_test_autogen.h",
     "command_buffer/common/gles2_cmd_utils_unittest.cc",
diff --git a/gpu/command_buffer/common/BUILD.gn b/gpu/command_buffer/common/BUILD.gn
index 3f9e147..4b69e04e 100644
--- a/gpu/command_buffer/common/BUILD.gn
+++ b/gpu/command_buffer/common/BUILD.gn
@@ -37,6 +37,8 @@
     "constants.h",
     "debug_marker_manager.cc",
     "debug_marker_manager.h",
+    "discardable_handle.cc",
+    "discardable_handle.h",
     "gles2_cmd_format.cc",
     "gles2_cmd_format.h",
     "gles2_cmd_format_autogen.h",
diff --git a/gpu/command_buffer/common/discardable_handle.cc b/gpu/command_buffer/common/discardable_handle.cc
new file mode 100644
index 0000000..987ad82
--- /dev/null
+++ b/gpu/command_buffer/common/discardable_handle.cc
@@ -0,0 +1,117 @@
+// Copyright (c) 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gpu/command_buffer/common/discardable_handle.h"
+
+#include "base/atomicops.h"
+#include "gpu/command_buffer/common/buffer.h"
+
+namespace gpu {
+namespace {
+const int32_t kHandleDeleted = 0;
+const int32_t kHandleUnlocked = 1;
+const int32_t kHandleLockedStart = 2;
+
+}  // namespace
+
+DiscardableHandleBase::DiscardableHandleBase(scoped_refptr<Buffer> buffer,
+                                             uint32_t byte_offset,
+                                             int32_t shm_id)
+    : buffer_(std::move(buffer)), byte_offset_(byte_offset), shm_id_(shm_id) {}
+
+DiscardableHandleBase::DiscardableHandleBase(
+    const DiscardableHandleBase& other) = default;
+DiscardableHandleBase::DiscardableHandleBase(DiscardableHandleBase&& other) =
+    default;
+DiscardableHandleBase::~DiscardableHandleBase() = default;
+DiscardableHandleBase& DiscardableHandleBase::operator=(
+    const DiscardableHandleBase& other) = default;
+DiscardableHandleBase& DiscardableHandleBase::operator=(
+    DiscardableHandleBase&& other) = default;
+
+bool DiscardableHandleBase::IsLockedForTesting() {
+  return kHandleLockedStart <= base::subtle::NoBarrier_Load(AsAtomic());
+}
+
+bool DiscardableHandleBase::IsDeletedForTesting() {
+  return kHandleDeleted == base::subtle::NoBarrier_Load(AsAtomic());
+}
+
+volatile base::subtle::Atomic32* DiscardableHandleBase::AsAtomic() const {
+  return reinterpret_cast<volatile base::subtle::Atomic32*>(
+      buffer_->GetDataAddress(byte_offset_, sizeof(base::subtle::Atomic32)));
+}
+
+ClientDiscardableHandle::ClientDiscardableHandle(scoped_refptr<Buffer> buffer,
+                                                 uint32_t byte_offset,
+                                                 int32_t shm_id)
+    : DiscardableHandleBase(std::move(buffer), byte_offset, shm_id) {
+  // Handle always starts locked.
+  base::subtle::NoBarrier_Store(AsAtomic(), kHandleLockedStart);
+}
+
+ClientDiscardableHandle::ClientDiscardableHandle(
+    const ClientDiscardableHandle& other) = default;
+ClientDiscardableHandle::ClientDiscardableHandle(
+    ClientDiscardableHandle&& other) = default;
+ClientDiscardableHandle& ClientDiscardableHandle::operator=(
+    const ClientDiscardableHandle& other) = default;
+ClientDiscardableHandle& ClientDiscardableHandle::operator=(
+    ClientDiscardableHandle&& other) = default;
+
+bool ClientDiscardableHandle::Lock() {
+  while (true) {
+    base::subtle::Atomic32 current_value =
+        base::subtle::NoBarrier_Load(AsAtomic());
+    if (current_value == kHandleDeleted) {
+      // Once a handle is deleted, it cannot be modified further.
+      return false;
+    }
+    base::subtle::Atomic32 new_value = current_value + 1;
+    // No barrier is needed, as any commands which depend on this operation
+    // will flow over the command buffer, which ensures a memory barrier
+    // between here and where these commands are executed on the GPU process.
+    base::subtle::Atomic32 previous_value =
+        base::subtle::NoBarrier_CompareAndSwap(AsAtomic(), current_value,
+                                               new_value);
+    if (current_value == previous_value) {
+      return true;
+    }
+  }
+}
+
+bool ClientDiscardableHandle::CanBeReUsed() const {
+  return kHandleDeleted == base::subtle::Acquire_Load(AsAtomic());
+}
+
+ServiceDiscardableHandle::ServiceDiscardableHandle(scoped_refptr<Buffer> buffer,
+                                                   uint32_t byte_offset,
+                                                   int32_t shm_id)
+    : DiscardableHandleBase(std::move(buffer), byte_offset, shm_id) {}
+
+ServiceDiscardableHandle::ServiceDiscardableHandle(
+    const ServiceDiscardableHandle& other) = default;
+ServiceDiscardableHandle::ServiceDiscardableHandle(
+    ServiceDiscardableHandle&& other) = default;
+ServiceDiscardableHandle& ServiceDiscardableHandle::operator=(
+    const ServiceDiscardableHandle& other) = default;
+ServiceDiscardableHandle& ServiceDiscardableHandle::operator=(
+    ServiceDiscardableHandle&& other) = default;
+
+void ServiceDiscardableHandle::Unlock() {
+  // No barrier is needed as all GPU process access happens on a single thread,
+  // and communication of dependent data between the GPU process and the
+  // renderer process happens across the command buffer and includes barriers.
+  base::subtle::NoBarrier_AtomicIncrement(AsAtomic(), -1);
+}
+
+bool ServiceDiscardableHandle::Delete() {
+  // No barrier is needed as all GPU process access happens on a single thread,
+  // and communication of dependent data between the GPU process and the
+  // renderer process happens across the command buffer and includes barriers.
+  return kHandleUnlocked == base::subtle::NoBarrier_CompareAndSwap(
+                                AsAtomic(), kHandleUnlocked, kHandleDeleted);
+}
+
+}  // namespace gpu
diff --git a/gpu/command_buffer/common/discardable_handle.h b/gpu/command_buffer/common/discardable_handle.h
new file mode 100644
index 0000000..a3b1769
--- /dev/null
+++ b/gpu/command_buffer/common/discardable_handle.h
@@ -0,0 +1,105 @@
+// Copyright (c) 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef GPU_COMMAND_BUFFER_COMMON_DISCARDABLE_HANDLE_H_
+#define GPU_COMMAND_BUFFER_COMMON_DISCARDABLE_HANDLE_H_
+
+#include "base/memory/ref_counted.h"
+#include "gpu/gpu_export.h"
+
+namespace gpu {
+
+class Buffer;
+
+// DiscardableHandleBase is the base class for the discardable handle
+// implementation. In order to facilitate transfering handles across the
+// command buffer, DiscardableHandleBase is backed by a gpu::Buffer and an
+// offset into that buffer. It uses a single uint32_t of data at the given
+// offset.
+//
+// DiscardableHandleBase is never used directly, but is instead modified by the
+// Client/ServiceDiscardableHandle subclasses. These subclasses implement the
+// Lock/Unlock/Delete functionality, making it explicit which operations occur
+// in which process.
+//
+// Via these subclasses, a discardable handle can be transitioned between one
+// of three states:
+//  ╔════════════╗         ╔════════════╗         ╔═══════════╗
+//  ║   Locked   ║ ──────> ║  Unlocked  ║ ──────> ║  Deleted  ║
+//  ╚════════════╝         ╚════════════╝         ╚═══════════╝
+//         └───────────<──────────┘
+//
+// Note that a handle can be locked multiple times, and stores a lock-count.
+class GPU_EXPORT DiscardableHandleBase {
+ public:
+  int32_t shm_id() const { return shm_id_; }
+  uint32_t byte_offset() const { return byte_offset_; }
+
+  // Test only functions.
+  bool IsLockedForTesting();
+  bool IsDeletedForTesting();
+
+ protected:
+  DiscardableHandleBase(scoped_refptr<Buffer> buffer,
+                        uint32_t byte_offset,
+                        int32_t shm_id);
+  DiscardableHandleBase(const DiscardableHandleBase& other);
+  DiscardableHandleBase(DiscardableHandleBase&& other);
+  DiscardableHandleBase& operator=(const DiscardableHandleBase& other);
+  DiscardableHandleBase& operator=(DiscardableHandleBase&& other);
+  ~DiscardableHandleBase();
+
+  volatile base::subtle::Atomic32* AsAtomic() const;
+
+ private:
+  scoped_refptr<Buffer> buffer_;
+  uint32_t byte_offset_ = 0;
+  uint32_t shm_id_ = 0;
+};
+
+// ClientDiscardableHandle enables the instantiation of a new discardable
+// handle (via the constructor), and can Lock an existing handle.
+class GPU_EXPORT ClientDiscardableHandle : public DiscardableHandleBase {
+ public:
+  ClientDiscardableHandle(scoped_refptr<Buffer> buffer,
+                          uint32_t byte_offset,
+                          int32_t shm_id);
+  ClientDiscardableHandle(const ClientDiscardableHandle& other);
+  ClientDiscardableHandle(ClientDiscardableHandle&& other);
+  ClientDiscardableHandle& operator=(const ClientDiscardableHandle& other);
+  ClientDiscardableHandle& operator=(ClientDiscardableHandle&& other);
+
+  // Tries to lock the handle. Returns true if successfully locked. Returns
+  // false if the handle has already been deleted on the service.
+  bool Lock();
+
+  // Returns true if the handle has been deleted on service side and can be
+  // re-used on the client.
+  bool CanBeReUsed() const;
+};
+
+// ServiceDiscardableHandle can wrap an existing handle (via the constructor),
+// and can unlock and delete this handle.
+class GPU_EXPORT ServiceDiscardableHandle : public DiscardableHandleBase {
+ public:
+  ServiceDiscardableHandle(scoped_refptr<Buffer> buffer,
+                           uint32_t byte_offset,
+                           int32_t shm_id);
+  ServiceDiscardableHandle(const ServiceDiscardableHandle& other);
+  ServiceDiscardableHandle(ServiceDiscardableHandle&& other);
+  ServiceDiscardableHandle& operator=(const ServiceDiscardableHandle& other);
+  ServiceDiscardableHandle& operator=(ServiceDiscardableHandle&& other);
+
+  // Unlocks the handle. This should always be paired with a client-side call
+  // to lock, or with a new handle, which starts locked.
+  void Unlock();
+
+  // Tries to delete the handle. Returns true if successfully deleted. Returns
+  // false if the handle is locked client-side and cannot be deleted.
+  bool Delete();
+};
+
+}  // namespace gpu
+
+#endif  // GPU_COMMAND_BUFFER_COMMON_DISCARDABLE_HANDLE_H_
diff --git a/gpu/command_buffer/common/discardable_handle_unittest.cc b/gpu/command_buffer/common/discardable_handle_unittest.cc
new file mode 100644
index 0000000..8bb01ae
--- /dev/null
+++ b/gpu/command_buffer/common/discardable_handle_unittest.cc
@@ -0,0 +1,129 @@
+// Copyright (c) 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gpu/command_buffer/common/discardable_handle.h"
+#include "gpu/command_buffer/common/buffer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace gpu {
+namespace {
+
+scoped_refptr<Buffer> MakeBufferForTesting(size_t num_handles) {
+  size_t size = sizeof(base::subtle::Atomic32) * num_handles;
+  std::unique_ptr<base::SharedMemory> shared_mem(new base::SharedMemory);
+  shared_mem->CreateAndMapAnonymous(size);
+  return MakeBufferFromSharedMemory(std::move(shared_mem), size);
+}
+
+}  // namespace
+
+TEST(DiscardableHandleTest, BasicUsage) {
+  scoped_refptr<Buffer> buffer = MakeBufferForTesting(1);
+
+  uint32_t byte_offset = 0;
+  int32_t shm_id = 1;
+  ClientDiscardableHandle client_handle(buffer, byte_offset, shm_id);
+  EXPECT_EQ(client_handle.shm_id(), shm_id);
+  EXPECT_TRUE(client_handle.IsLockedForTesting());
+
+  ServiceDiscardableHandle service_handle(buffer, byte_offset, shm_id);
+  EXPECT_EQ(service_handle.shm_id(), shm_id);
+  EXPECT_TRUE(service_handle.IsLockedForTesting());
+
+  EXPECT_FALSE(service_handle.Delete());
+  EXPECT_FALSE(service_handle.IsDeletedForTesting());
+  EXPECT_FALSE(client_handle.CanBeReUsed());
+
+  service_handle.Unlock();
+  EXPECT_FALSE(service_handle.IsLockedForTesting());
+  EXPECT_FALSE(client_handle.IsLockedForTesting());
+
+  EXPECT_TRUE(client_handle.Lock());
+  EXPECT_TRUE(client_handle.IsLockedForTesting());
+  EXPECT_TRUE(service_handle.IsLockedForTesting());
+
+  service_handle.Unlock();
+  EXPECT_FALSE(service_handle.IsLockedForTesting());
+  EXPECT_FALSE(client_handle.IsLockedForTesting());
+
+  EXPECT_TRUE(service_handle.Delete());
+  EXPECT_TRUE(service_handle.IsDeletedForTesting());
+  EXPECT_TRUE(client_handle.CanBeReUsed());
+  EXPECT_FALSE(service_handle.IsLockedForTesting());
+  EXPECT_FALSE(client_handle.IsLockedForTesting());
+
+  EXPECT_FALSE(client_handle.Lock());
+  EXPECT_FALSE(service_handle.IsLockedForTesting());
+  EXPECT_FALSE(client_handle.IsLockedForTesting());
+  EXPECT_TRUE(service_handle.IsDeletedForTesting());
+  EXPECT_TRUE(client_handle.IsDeletedForTesting());
+}
+
+TEST(DiscardableHandleTest, MultiLock) {
+  scoped_refptr<Buffer> buffer = MakeBufferForTesting(1);
+
+  uint32_t byte_offset = 0;
+  int32_t shm_id = 1;
+  ClientDiscardableHandle client_handle(buffer, byte_offset, shm_id);
+  EXPECT_EQ(client_handle.shm_id(), shm_id);
+  EXPECT_TRUE(client_handle.IsLockedForTesting());
+
+  ServiceDiscardableHandle service_handle(buffer, byte_offset, shm_id);
+  EXPECT_EQ(service_handle.shm_id(), shm_id);
+  EXPECT_TRUE(service_handle.IsLockedForTesting());
+
+  for (int i = 1; i < 10; ++i) {
+    EXPECT_TRUE(client_handle.IsLockedForTesting());
+    EXPECT_TRUE(service_handle.IsLockedForTesting());
+    EXPECT_TRUE(client_handle.Lock());
+  }
+
+  for (int i = 0; i < 10; ++i) {
+    EXPECT_TRUE(client_handle.IsLockedForTesting());
+    EXPECT_TRUE(service_handle.IsLockedForTesting());
+    service_handle.Unlock();
+  }
+
+  EXPECT_FALSE(client_handle.IsLockedForTesting());
+  EXPECT_FALSE(service_handle.IsLockedForTesting());
+}
+
+TEST(DiscardableHandleTest, Suballocations) {
+  static const int32_t num_elements = 10;
+  scoped_refptr<Buffer> buffer = MakeBufferForTesting(num_elements);
+
+  std::vector<ClientDiscardableHandle> client_handles;
+  std::vector<ServiceDiscardableHandle> service_handles;
+  for (int32_t i = 0; i < num_elements; ++i) {
+    client_handles.emplace_back(buffer, sizeof(base::subtle::Atomic32) * i,
+                                i + 1);
+    EXPECT_EQ(client_handles[i].shm_id(), i + 1);
+    EXPECT_TRUE(client_handles[i].IsLockedForTesting());
+
+    service_handles.emplace_back(buffer, sizeof(base::subtle::Atomic32) * i,
+                                 i + 1);
+    EXPECT_EQ(service_handles[i].shm_id(), i + 1);
+    EXPECT_TRUE(service_handles[i].IsLockedForTesting());
+  }
+
+  for (int32_t i = 0; i < num_elements; i += 2) {
+    service_handles[i].Unlock();
+  }
+
+  for (int32_t i = 1; i < num_elements; i += 2) {
+    client_handles[i].Lock();
+  }
+
+  for (int32_t i = 0; i < num_elements; ++i) {
+    if (i % 2) {
+      EXPECT_TRUE(client_handles[i].IsLockedForTesting());
+      EXPECT_TRUE(service_handles[i].IsLockedForTesting());
+    } else {
+      EXPECT_FALSE(client_handles[i].IsLockedForTesting());
+      EXPECT_FALSE(service_handles[i].IsLockedForTesting());
+    }
+  }
+}
+
+}  // namespace gpu