Adds remaining setup logic to implement destroy in Device and ObjectBase.

- Renames some of the Device functions to be consistent with documentation
- Reverts change in https://dawn-review.googlesource.com/c/dawn/+/64820 for overloading mDevice == nullptr to determine if objects are alive because device is needed for error propagation. Instead, use list existence to determine if objects are alive
- Updates destroy api to return bool upwards in case we need to further process the extending objects
- Adds tracking functions in ObjectBase
- Adds final tag to all backend Device implementations
- Adds MoveInto LinkedList support to move list elements in O(1)

Bug: dawn:628
Change-Id: Iff70f4f7d55f46ee52d1bd0e02e3671819f2eed4
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/65861
Commit-Queue: Loko Kung <lokokung@google.com>
Reviewed-by: Austin Eng <enga@chromium.org>
diff --git a/docs/device_facilities.md b/docs/device_facilities.md
index 3a2384c..78b8ed5 100644
--- a/docs/device_facilities.md
+++ b/docs/device_facilities.md
@@ -43,7 +43,7 @@
 If an `Alive` device is destroyed, then a similar flow to `LoseForTesting happens`.
 
 All this ensures that during destruction or forceful disconnect of the device, it properly gets to the `Disconnected` state with no commands executing on the GPU.
-After disconnecting, frontend will call `backend::Device::ShutDownImpl` so that it can properly free driver objects.
+After disconnecting, frontend will call `backend::Device::DestroyImpl` so that it can properly free driver objects.
 
 ### Toggles
 
diff --git a/src/common/LinkedList.h b/src/common/LinkedList.h
index 9b89ad5..881aa82 100644
--- a/src/common/LinkedList.h
+++ b/src/common/LinkedList.h
@@ -6,6 +6,7 @@
 // modifications:
 //   - Added iterators for ranged based iterations
 //   - Added in list check before removing node to prevent segfault, now returns true iff removed
+//   - Added MoveInto functionality for moving list elements to another list
 
 #ifndef COMMON_LINKED_LIST_H
 #define COMMON_LINKED_LIST_H
@@ -89,6 +90,12 @@
 //    needs to glue on the "next" and "previous" pointers using
 //    some internal node type.
 
+// Forward declarations of the types in order for recursive referencing and friending.
+template <typename T>
+class LinkNode;
+template <typename T>
+class LinkedList;
+
 template <typename T>
 class LinkNode {
   public:
@@ -165,6 +172,7 @@
     }
 
   private:
+    friend class LinkedList<T>;
     LinkNode<T>* previous_;
     LinkNode<T>* next_;
 };
@@ -190,6 +198,20 @@
         e->InsertBefore(&root_);
     }
 
+    // Moves all elements (in order) of the list and appends them into |l| leaving the list empty.
+    void MoveInto(LinkedList<T>* l) {
+        if (empty()) {
+            return;
+        }
+        l->root_.previous_->next_ = root_.next_;
+        root_.next_->previous_ = l->root_.previous_;
+        l->root_.previous_ = root_.previous_;
+        root_.previous_->next_ = &l->root_;
+
+        root_.next_ = &root_;
+        root_.previous_ = &root_;
+    }
+
     LinkNode<T>* head() const {
         return root_.next();
     }
diff --git a/src/dawn_native/Device.cpp b/src/dawn_native/Device.cpp
index 59036ad..bc6f6a8 100644
--- a/src/dawn_native/Device.cpp
+++ b/src/dawn_native/Device.cpp
@@ -48,6 +48,7 @@
 #include "dawn_native/ValidationUtils_autogen.h"
 #include "dawn_platform/DawnPlatform.h"
 
+#include <array>
 #include <mutex>
 #include <unordered_set>
 
@@ -262,7 +263,30 @@
         return {};
     }
 
-    void DeviceBase::ShutDownBase() {
+    void DeviceBase::DestroyObjects() {
+        // List of object types in reverse "dependency" order so we can iterate and delete the
+        // objects safely starting at leaf objects. We define dependent here such that if B has
+        // a ref to A, then B depends on A. We therefore try to destroy B before destroying A. Note
+        // that this only considers the immediate frontend dependencies, while backend objects could
+        // add complications and extra dependencies.
+        // TODO(dawn/628) Add types into the array as they are implemented.
+        static constexpr std::array<ObjectType, 0> kObjectTypeDependencyOrder = {};
+
+        // We first move all objects out from the tracking list into a separate list so that we can
+        // avoid locking the same mutex twice. We can then iterate across the separate list to call
+        // the actual destroy function.
+        LinkedList<ApiObjectBase> objects;
+        for (ObjectType type : kObjectTypeDependencyOrder) {
+            ApiObjectList& objList = mObjectLists[type];
+            const std::lock_guard<std::mutex> lock(objList.mutex);
+            objList.objects.MoveInto(&objects);
+        }
+        for (LinkNode<ApiObjectBase>* node : objects) {
+            node->value()->DestroyApiObject();
+        }
+    }
+
+    void DeviceBase::Destroy() {
         // Skip handling device facilities if they haven't even been created (or failed doing so)
         if (mState != State::BeingCreated) {
             // Call all the callbacks immediately as the device is about to shut down.
@@ -304,8 +328,8 @@
         if (mState != State::BeingCreated) {
             // The GPU timeline is finished.
             // Tick the queue-related tasks since they should be complete. This must be done before
-            // ShutDownImpl() it may relinquish resources that will be freed by backends in the
-            // ShutDownImpl() call.
+            // DestroyImpl() it may relinquish resources that will be freed by backends in the
+            // DestroyImpl() call.
             mQueue->Tick(GetCompletedCommandSerial());
             // Call TickImpl once last time to clean up resources
             // Ignore errors so that we can continue with destruction
@@ -319,14 +343,15 @@
         mCallbackTaskManager = nullptr;
         mAsyncTaskManager = nullptr;
         mPersistentCache = nullptr;
-
         mEmptyBindGroupLayout = nullptr;
-
         mInternalPipelineStore = nullptr;
 
         AssumeCommandsComplete();
-        // Tell the backend that it can free all the objects now that the GPU timeline is empty.
-        ShutDownImpl();
+
+        // Now that the GPU timeline is empty, destroy all objects owned by the device, and then the
+        // backend device.
+        DestroyObjects();
+        DestroyImpl();
 
         mCaches = nullptr;
     }
@@ -499,6 +524,12 @@
         return mState != State::Alive;
     }
 
+    void DeviceBase::TrackObject(ApiObjectBase* object) {
+        ApiObjectList& objectList = mObjectLists[object->GetType()];
+        std::lock_guard<std::mutex> lock(objectList.mutex);
+        object->InsertBefore(objectList.objects.head());
+    }
+
     std::mutex* DeviceBase::GetObjectListMutex(ObjectType type) {
         return &mObjectLists[type].mutex;
     }
diff --git a/src/dawn_native/Device.h b/src/dawn_native/Device.h
index e0cb9fa..5d51096 100644
--- a/src/dawn_native/Device.h
+++ b/src/dawn_native/Device.h
@@ -298,6 +298,7 @@
         };
         State GetState() const;
         bool IsLost() const;
+        void TrackObject(ApiObjectBase* object);
         std::mutex* GetObjectListMutex(ObjectType type);
 
         std::vector<const char*> GetEnabledFeatures() const;
@@ -357,7 +358,8 @@
         void ForceSetToggle(Toggle toggle, bool isEnabled);
 
         MaybeError Initialize(QueueBase* defaultQueue);
-        void ShutDownBase();
+        void DestroyObjects();
+        void Destroy();
 
         // Incrememt mLastSubmittedSerial when we submit the next serial
         void IncrementLastSubmittedCommandSerial();
@@ -450,9 +452,9 @@
         ExecutionSerial mLastSubmittedSerial = ExecutionSerial(0);
         ExecutionSerial mFutureSerial = ExecutionSerial(0);
 
-        // ShutDownImpl is used to clean up and release resources used by device, does not wait for
+        // DestroyImpl is used to clean up and release resources used by device, does not wait for
         // GPU or check errors.
-        virtual void ShutDownImpl() = 0;
+        virtual void DestroyImpl() = 0;
 
         // WaitForIdleForDestruction waits for GPU to finish, checks errors and gets ready for
         // destruction. This is only used when properly destructing the device. For a real
diff --git a/src/dawn_native/ObjectBase.cpp b/src/dawn_native/ObjectBase.cpp
index e33722f..d458334 100644
--- a/src/dawn_native/ObjectBase.cpp
+++ b/src/dawn_native/ObjectBase.cpp
@@ -37,14 +37,6 @@
         return GetRefCountPayload() == kErrorPayload;
     }
 
-    bool ObjectBase::IsAlive() const {
-        return mDevice != nullptr;
-    }
-
-    void ObjectBase::DestroyObject() {
-        mDevice = nullptr;
-    }
-
     ApiObjectBase::ApiObjectBase(DeviceBase* device, const char* label) : ObjectBase(device) {
         if (label) {
             mLabel = label;
@@ -58,6 +50,10 @@
         : ObjectBase(device) {
     }
 
+    ApiObjectBase::~ApiObjectBase() {
+        ASSERT(!IsAlive());
+    }
+
     void ApiObjectBase::APISetLabel(const char* label) {
         mLabel = label;
         SetLabelImpl();
@@ -70,4 +66,25 @@
     void ApiObjectBase::SetLabelImpl() {
     }
 
+    bool ApiObjectBase::IsAlive() const {
+        return IsInList();
+    }
+
+    void ApiObjectBase::TrackInDevice() {
+        ASSERT(GetDevice() != nullptr);
+        GetDevice()->TrackObject(this);
+    }
+
+    bool ApiObjectBase::DestroyApiObject() {
+        const std::lock_guard<std::mutex> lock(*GetDevice()->GetObjectListMutex(GetType()));
+        if (!RemoveFromList()) {
+            return false;
+        }
+        DestroyApiObjectImpl();
+        return true;
+    }
+
+    void ApiObjectBase::DestroyApiObjectImpl() {
+    }
+
 }  // namespace dawn_native
diff --git a/src/dawn_native/ObjectBase.h b/src/dawn_native/ObjectBase.h
index 17d32f8..8b14b77 100644
--- a/src/dawn_native/ObjectBase.h
+++ b/src/dawn_native/ObjectBase.h
@@ -35,12 +35,9 @@
 
         DeviceBase* GetDevice() const;
         bool IsError() const;
-        bool IsAlive() const;
-        void DestroyObject();
 
       private:
-        // Pointer to owning device, if nullptr, that means that the object is no longer alive or
-        // valid.
+        // Pointer to owning device.
         DeviceBase* mDevice;
     };
 
@@ -52,13 +49,29 @@
         ApiObjectBase(DeviceBase* device, LabelNotImplementedTag tag);
         ApiObjectBase(DeviceBase* device, const char* label);
         ApiObjectBase(DeviceBase* device, ErrorTag tag);
+        virtual ~ApiObjectBase() override;
 
         virtual ObjectType GetType() const = 0;
         const std::string& GetLabel() const;
 
+        // The ApiObjectBase is considered alive if it is tracked in a respective linked list owned
+        // by the owning device.
+        bool IsAlive() const;
+
+        // Allow virtual overriding of actual destroy call in order to allow for re-using of base
+        // destruction oerations. Classes that override this function should almost always call this
+        // class's implementation in the override. This needs to be public because it can be called
+        // from the device owning the object. Returns true iff destruction occurs. Upon any re-calls
+        // of the function it will return false to indicate no further operations should be taken.
+        virtual bool DestroyApiObject();
+
         // Dawn API
         void APISetLabel(const char* label);
 
+      protected:
+        void TrackInDevice();
+        virtual void DestroyApiObjectImpl();
+
       private:
         virtual void SetLabelImpl();
 
diff --git a/src/dawn_native/d3d12/DeviceD3D12.cpp b/src/dawn_native/d3d12/DeviceD3D12.cpp
index 3b96092..4e019c2 100644
--- a/src/dawn_native/d3d12/DeviceD3D12.cpp
+++ b/src/dawn_native/d3d12/DeviceD3D12.cpp
@@ -169,7 +169,7 @@
     }
 
     Device::~Device() {
-        ShutDownBase();
+        Destroy();
     }
 
     ID3D12Device* Device::GetD3D12Device() const {
@@ -601,7 +601,7 @@
         return DAWN_INTERNAL_ERROR(messages.str());
     }
 
-    void Device::ShutDownImpl() {
+    void Device::DestroyImpl() {
         ASSERT(GetState() == State::Disconnected);
 
         // Immediately forget about all pending commands for the case where device is lost on its
diff --git a/src/dawn_native/d3d12/DeviceD3D12.h b/src/dawn_native/d3d12/DeviceD3D12.h
index 186e29e..03856be 100644
--- a/src/dawn_native/d3d12/DeviceD3D12.h
+++ b/src/dawn_native/d3d12/DeviceD3D12.h
@@ -39,7 +39,7 @@
     } while (0)
 
     // Definition of backend types
-    class Device : public DeviceBase {
+    class Device final : public DeviceBase {
       public:
         static ResultOrError<Device*> Create(Adapter* adapter, const DeviceDescriptor* descriptor);
         ~Device() override;
@@ -181,7 +181,7 @@
                                                WGPUCreateRenderPipelineAsyncCallback callback,
                                                void* userdata) override;
 
-        void ShutDownImpl() override;
+        void DestroyImpl() override;
         MaybeError WaitForIdleForDestruction() override;
 
         MaybeError CheckDebugLayerAndGenerateErrors();
diff --git a/src/dawn_native/metal/DeviceMTL.h b/src/dawn_native/metal/DeviceMTL.h
index e5232cb..5d16d8e 100644
--- a/src/dawn_native/metal/DeviceMTL.h
+++ b/src/dawn_native/metal/DeviceMTL.h
@@ -36,7 +36,7 @@
         struct KalmanInfo;
     }
 
-    class Device : public DeviceBase {
+    class Device final : public DeviceBase {
       public:
         static ResultOrError<Device*> Create(AdapterBase* adapter,
                                              NSPRef<id<MTLDevice>> mtlDevice,
@@ -122,7 +122,7 @@
                                                void* userdata) override;
 
         void InitTogglesFromDriver();
-        void ShutDownImpl() override;
+        void DestroyImpl() override;
         MaybeError WaitForIdleForDestruction() override;
         ResultOrError<ExecutionSerial> CheckAndUpdateCompletedSerials() override;
 
diff --git a/src/dawn_native/metal/DeviceMTL.mm b/src/dawn_native/metal/DeviceMTL.mm
index 67ce44b..acc351f 100644
--- a/src/dawn_native/metal/DeviceMTL.mm
+++ b/src/dawn_native/metal/DeviceMTL.mm
@@ -121,7 +121,7 @@
     }
 
     Device::~Device() {
-        ShutDownBase();
+        Destroy();
     }
 
     MaybeError Device::Initialize() {
@@ -483,7 +483,7 @@
         return {};
     }
 
-    void Device::ShutDownImpl() {
+    void Device::DestroyImpl() {
         ASSERT(GetState() == State::Disconnected);
 
         // Forget all pending commands.
diff --git a/src/dawn_native/null/DeviceNull.cpp b/src/dawn_native/null/DeviceNull.cpp
index ab6ec33..4f08b1b 100644
--- a/src/dawn_native/null/DeviceNull.cpp
+++ b/src/dawn_native/null/DeviceNull.cpp
@@ -87,7 +87,7 @@
     }
 
     Device::~Device() {
-        ShutDownBase();
+        Destroy();
     }
 
     MaybeError Device::Initialize() {
@@ -164,7 +164,7 @@
         return std::move(stagingBuffer);
     }
 
-    void Device::ShutDownImpl() {
+    void Device::DestroyImpl() {
         ASSERT(GetState() == State::Disconnected);
 
         // Clear pending operations before checking mMemoryUsage because some operations keep a
diff --git a/src/dawn_native/null/DeviceNull.h b/src/dawn_native/null/DeviceNull.h
index c51152d..be1c613 100644
--- a/src/dawn_native/null/DeviceNull.h
+++ b/src/dawn_native/null/DeviceNull.h
@@ -84,7 +84,7 @@
         virtual void Execute() = 0;
     };
 
-    class Device : public DeviceBase {
+    class Device final : public DeviceBase {
       public:
         static ResultOrError<Device*> Create(Adapter* adapter, const DeviceDescriptor* descriptor);
         ~Device() override;
@@ -156,7 +156,7 @@
 
         ResultOrError<ExecutionSerial> CheckAndUpdateCompletedSerials() override;
 
-        void ShutDownImpl() override;
+        void DestroyImpl() override;
         MaybeError WaitForIdleForDestruction() override;
 
         std::vector<std::unique_ptr<PendingOperation>> mPendingOperations;
diff --git a/src/dawn_native/opengl/DeviceGL.cpp b/src/dawn_native/opengl/DeviceGL.cpp
index ac8be45..aa2fa18 100644
--- a/src/dawn_native/opengl/DeviceGL.cpp
+++ b/src/dawn_native/opengl/DeviceGL.cpp
@@ -50,7 +50,7 @@
     }
 
     Device::~Device() {
-        ShutDownBase();
+        Destroy();
     }
 
     MaybeError Device::Initialize() {
@@ -291,7 +291,7 @@
         return DAWN_UNIMPLEMENTED_ERROR("Device unable to copy from staging buffer to texture.");
     }
 
-    void Device::ShutDownImpl() {
+    void Device::DestroyImpl() {
         ASSERT(GetState() == State::Disconnected);
     }
 
diff --git a/src/dawn_native/opengl/DeviceGL.h b/src/dawn_native/opengl/DeviceGL.h
index b259647..ee2ef46 100644
--- a/src/dawn_native/opengl/DeviceGL.h
+++ b/src/dawn_native/opengl/DeviceGL.h
@@ -35,7 +35,7 @@
 
 namespace dawn_native { namespace opengl {
 
-    class Device : public DeviceBase {
+    class Device final : public DeviceBase {
       public:
         static ResultOrError<Device*> Create(AdapterBase* adapter,
                                              const DeviceDescriptor* descriptor,
@@ -118,7 +118,7 @@
 
         void InitTogglesFromDriver();
         ResultOrError<ExecutionSerial> CheckAndUpdateCompletedSerials() override;
-        void ShutDownImpl() override;
+        void DestroyImpl() override;
         MaybeError WaitForIdleForDestruction() override;
 
         std::queue<std::pair<GLsync, ExecutionSerial>> mFencesInFlight;
diff --git a/src/dawn_native/vulkan/DeviceVk.cpp b/src/dawn_native/vulkan/DeviceVk.cpp
index 1834ae6..a30a20e 100644
--- a/src/dawn_native/vulkan/DeviceVk.cpp
+++ b/src/dawn_native/vulkan/DeviceVk.cpp
@@ -67,7 +67,7 @@
         // Two things are crucial if device initialization fails: the function pointers to destroy
         // objects, and the fence deleter that calls these functions. Do not do anything before
         // these two are set up, so that a failed initialization doesn't cause a crash in
-        // ShutDownImpl()
+        // DestroyImpl()
         {
             VkPhysicalDevice physicalDevice = ToBackend(GetAdapter())->GetPhysicalDevice();
 
@@ -100,7 +100,7 @@
     }
 
     Device::~Device() {
-        ShutDownBase();
+        Destroy();
     }
 
     ResultOrError<Ref<BindGroupBase>> Device::CreateBindGroupImpl(
@@ -912,7 +912,7 @@
         return {};
     }
 
-    void Device::ShutDownImpl() {
+    void Device::DestroyImpl() {
         ASSERT(GetState() == State::Disconnected);
 
         // We failed during initialization so early that we don't even have a VkDevice. There is
diff --git a/src/dawn_native/vulkan/DeviceVk.h b/src/dawn_native/vulkan/DeviceVk.h
index 447023f..3378bc8 100644
--- a/src/dawn_native/vulkan/DeviceVk.h
+++ b/src/dawn_native/vulkan/DeviceVk.h
@@ -40,7 +40,7 @@
     class RenderPassCache;
     class ResourceMemoryAllocator;
 
-    class Device : public DeviceBase {
+    class Device final : public DeviceBase {
       public:
         static ResultOrError<Device*> Create(Adapter* adapter, const DeviceDescriptor* descriptor);
         ~Device() override;
@@ -152,7 +152,7 @@
         void InitTogglesFromDriver();
         void ApplyDepth24PlusS8Toggle();
 
-        void ShutDownImpl() override;
+        void DestroyImpl() override;
         MaybeError WaitForIdleForDestruction() override;
 
         // To make it easier to use fn it is a public const member. However
diff --git a/src/tests/unittests/LinkedListTests.cpp b/src/tests/unittests/LinkedListTests.cpp
index 1a06d8c..72dd411 100644
--- a/src/tests/unittests/LinkedListTests.cpp
+++ b/src/tests/unittests/LinkedListTests.cpp
@@ -369,6 +369,51 @@
     EXPECT_FALSE(n.RemoveFromList());
 }
 
+TEST(LinkedList, MoveInto) {
+    LinkedList<Node> l1;
+    LinkedList<Node> l2;
+
+    Node n1(1);
+    Node n2(2);
+    l1.Append(&n1);
+    l2.Append(&n2);
+
+    l2.MoveInto(&l1);
+    const int expected[] = {1, 2};
+    ExpectListContents(l1, 2, expected);
+    EXPECT_TRUE(l2.empty());
+}
+
+TEST(LinkedList, MoveEmptyListInto) {
+    LinkedList<Node> l1;
+    LinkedList<Node> l2;
+
+    Node n1(1);
+    Node n2(2);
+    l1.Append(&n1);
+    l1.Append(&n2);
+
+    l2.MoveInto(&l1);
+    const int expected[] = {1, 2};
+    ExpectListContents(l1, 2, expected);
+    EXPECT_TRUE(l2.empty());
+}
+
+TEST(LinkedList, MoveIntoEmpty) {
+    LinkedList<Node> l1;
+    LinkedList<Node> l2;
+
+    Node n1(1);
+    Node n2(2);
+    l2.Append(&n1);
+    l2.Append(&n2);
+
+    l2.MoveInto(&l1);
+    const int expected[] = {1, 2};
+    ExpectListContents(l1, 2, expected);
+    EXPECT_TRUE(l2.empty());
+}
+
 TEST(LinkedList, RangeBasedModify) {
     LinkedList<Node> list;