Add explanation of why RAW_PTR_EXCLUSIONs are safe.

Also remove one of them by using unique_ptr instead, requiring minor
changes to CommandAllocator/Iterator.

Bug: 335556942
Change-Id: Iab76d12b3cb8c759ab8acf6d41ef1733339f95e4
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/188040
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Loko Kung <lokokung@google.com>
diff --git a/src/dawn/common/LinkedList.h b/src/dawn/common/LinkedList.h
index ff89d47..16b9226 100644
--- a/src/dawn/common/LinkedList.h
+++ b/src/dawn/common/LinkedList.h
@@ -172,7 +172,9 @@
 
   private:
     friend class LinkedList<T>;
-    // RAW_PTR_EXCLUSION: Performance reasons (based on analysis of MotionMark).
+    // RAW_PTR_EXCLUSION: Linked lists are used and iterated a lot so these pointers are very hot.
+    // All accesses to the pointers are behind "safe" APIs such that it is not possible (in
+    // single-threaded code) to use them after they are freed.
     RAW_PTR_EXCLUSION LinkNode<T>* previous_ = nullptr;
     RAW_PTR_EXCLUSION LinkNode<T>* next_ = nullptr;
 };
@@ -235,7 +237,9 @@
     LinkNode<T>* operator*() const { return current_; }
 
   private:
-    // RAW_PTR_EXCLUSION: Performance reasons (based on analysis of MotionMark).
+    // RAW_PTR_EXCLUSION: Linked lists are used and iterated a lot so these pointers are very hot.
+    // All accesses to the pointers are behind "safe" APIs such that it is not possible (in
+    // single-threaded code) to use them after they are freed.
     RAW_PTR_EXCLUSION LinkNode<T>* current_ = nullptr;
     RAW_PTR_EXCLUSION LinkNode<T>* next_ = nullptr;
 };
diff --git a/src/dawn/common/MutexProtected.h b/src/dawn/common/MutexProtected.h
index c08e3a1..ab2e6fc 100644
--- a/src/dawn/common/MutexProtected.h
+++ b/src/dawn/common/MutexProtected.h
@@ -32,7 +32,9 @@
 #include <utility>
 
 #include "dawn/common/Mutex.h"
+#include "dawn/common/NonMovable.h"
 #include "dawn/common/Ref.h"
+#include "dawn/common/StackAllocated.h"
 #include "partition_alloc/pointers/raw_ptr_exclusion.h"
 
 namespace dawn {
@@ -69,7 +71,7 @@
 // Guard class is a wrapping class that gives access to a protected resource after acquiring the
 // lock related to it. For the lifetime of this class, the lock is held.
 template <typename T, typename Traits>
-class Guard {
+class Guard : public NonMovable, StackAllocated {
   public:
     // It's the programmer's burden to not save the pointer/reference and reuse it without the lock.
     auto* operator->() const { return Get(); }
@@ -92,7 +94,9 @@
     friend class MutexProtected<NonConstT, Guard>;
 
     typename Traits::LockType mLock;
-    // RAW_PTR_EXCLUSION: Performance reasons (based on analysis of MotionMark).
+    // RAW_PTR_EXCLUSION: This pointer is created/destroyed on each access to a MutexProtected.
+    // The pointer is always transiently used while the MutexProtected is in scope so it is
+    // unlikely to be used after it is freed.
     RAW_PTR_EXCLUSION T* mObj = nullptr;
 };
 
diff --git a/src/dawn/native/BindGroupTracker.h b/src/dawn/native/BindGroupTracker.h
index 7b95af1..318c979 100644
--- a/src/dawn/native/BindGroupTracker.h
+++ b/src/dawn/native/BindGroupTracker.h
@@ -126,7 +126,9 @@
     // |mPipelineLayout| is the current pipeline layout set on the command buffer.
     // |mLastAppliedPipelineLayout| is the last pipeline layout for which we applied changes
     // to the bind group bindings.
-    // RAW_PTR_EXCLUSION: Performance reasons (based on analysis of MotionMark).
+    // RAW_PTR_EXCLUSION: These pointers are very hot in command recording code and point at
+    // pipeline layouts referenced by the object graph of the CommandBuffer so they cannot be
+    // freed from underneath this class.
     RAW_PTR_EXCLUSION PipelineLayoutBase* mPipelineLayout = nullptr;
     RAW_PTR_EXCLUSION PipelineLayoutBase* mLastAppliedPipelineLayout = nullptr;
 };
diff --git a/src/dawn/native/CommandAllocator.cpp b/src/dawn/native/CommandAllocator.cpp
index f5d0cda..1fe51ee 100644
--- a/src/dawn/native/CommandAllocator.cpp
+++ b/src/dawn/native/CommandAllocator.cpp
@@ -91,7 +91,7 @@
         *commandId = detail::kEndOfBlock;
         return false;
     }
-    mCurrentPtr = AlignPtr(mBlocks[mCurrentBlock].block, alignof(uint32_t));
+    mCurrentPtr = AlignPtr(mBlocks[mCurrentBlock].block.get(), alignof(uint32_t));
     return NextCommandId(commandId);
 }
 
@@ -101,12 +101,9 @@
     if (mBlocks.empty()) {
         // This will case the first NextCommandId call to try to move to the next block and stop
         // the iteration immediately, without special casing the initialization.
-        mCurrentPtr = reinterpret_cast<uint8_t*>(&mEndOfBlock);
-        mBlocks.emplace_back();
-        mBlocks[0].size = sizeof(mEndOfBlock);
-        mBlocks[0].block = mCurrentPtr;
+        mCurrentPtr = reinterpret_cast<char*>(&mEndOfBlock);
     } else {
-        mCurrentPtr = AlignPtr(mBlocks[0].block, alignof(uint32_t));
+        mCurrentPtr = AlignPtr(mBlocks[0].block.get(), alignof(uint32_t));
     }
 }
 
@@ -115,17 +112,14 @@
         return;
     }
 
-    mCurrentPtr = reinterpret_cast<uint8_t*>(&mEndOfBlock);
-    for (BlockDef& block : mBlocks) {
-        free(block.block);
-    }
+    mCurrentPtr = reinterpret_cast<char*>(&mEndOfBlock);
     mBlocks.clear();
     Reset();
     DAWN_ASSERT(IsEmpty());
 }
 
 bool CommandIterator::IsEmpty() const {
-    return mBlocks[0].block == reinterpret_cast<const uint8_t*>(&mEndOfBlock);
+    return mBlocks.empty();
 }
 
 // Potential TODO(crbug.com/dawn/835):
@@ -172,15 +166,12 @@
 
 void CommandAllocator::Reset() {
     ResetPointers();
-    for (BlockDef& block : mBlocks) {
-        free(block.block);
-    }
     mBlocks.clear();
     mLastAllocationSize = kDefaultBaseAllocationSize;
 }
 
 bool CommandAllocator::IsEmpty() const {
-    return mCurrentPtr == reinterpret_cast<const uint8_t*>(&mPlaceholderEnum[0]);
+    return mCurrentPtr == reinterpret_cast<const char*>(&mPlaceholderSpace[0]);
 }
 
 CommandBlocks&& CommandAllocator::AcquireBlocks() {
@@ -194,9 +185,9 @@
     return std::move(mBlocks);
 }
 
-uint8_t* CommandAllocator::AllocateInNewBlock(uint32_t commandId,
-                                              size_t commandSize,
-                                              size_t commandAlignment) {
+char* CommandAllocator::AllocateInNewBlock(uint32_t commandId,
+                                           size_t commandSize,
+                                           size_t commandAlignment) {
     // When there is not enough space, we signal the kEndOfBlock, so that the iterator knows
     // to move to the next one. kEndOfBlock on the last block means the end of the commands.
     uint32_t* idAlloc = reinterpret_cast<uint32_t*>(mCurrentPtr);
@@ -221,20 +212,20 @@
     // Allocate blocks doubling sizes each time, to a maximum of 16k (or at least minimumSize).
     mLastAllocationSize = std::max(minimumSize, std::min(mLastAllocationSize * 2, size_t(16384)));
 
-    uint8_t* block = static_cast<uint8_t*>(malloc(mLastAllocationSize));
+    auto block = std::unique_ptr<char[]>(new (std::nothrow) char[mLastAllocationSize]);
     if (DAWN_UNLIKELY(block == nullptr)) {
         return false;
     }
 
-    mBlocks.push_back({mLastAllocationSize, block});
-    mCurrentPtr = AlignPtr(block, alignof(uint32_t));
-    mEndPtr = block + mLastAllocationSize;
+    mCurrentPtr = AlignPtr(block.get(), alignof(uint32_t));
+    mEndPtr = block.get() + mLastAllocationSize;
+    mBlocks.push_back({mLastAllocationSize, std::move(block)});
     return true;
 }
 
 void CommandAllocator::ResetPointers() {
-    mCurrentPtr = reinterpret_cast<uint8_t*>(&mPlaceholderEnum[0]);
-    mEndPtr = reinterpret_cast<uint8_t*>(&mPlaceholderEnum[1]);
+    mCurrentPtr = reinterpret_cast<char*>(&mPlaceholderSpace[0]);
+    mEndPtr = reinterpret_cast<char*>(&mPlaceholderSpace[1]);
 }
 
 }  // namespace dawn::native
diff --git a/src/dawn/native/CommandAllocator.h b/src/dawn/native/CommandAllocator.h
index 1d9ddf1..9ad0db7 100644
--- a/src/dawn/native/CommandAllocator.h
+++ b/src/dawn/native/CommandAllocator.h
@@ -31,6 +31,7 @@
 #include <cstddef>
 #include <cstdint>
 #include <limits>
+#include <memory>
 #include <vector>
 
 #include "dawn/common/Assert.h"
@@ -71,8 +72,7 @@
 // and CommandIterator
 struct BlockDef {
     size_t size;
-    // RAW_PTR_EXCLUSION: Performance reasons (based on analysis of MotionMark).
-    RAW_PTR_EXCLUSION uint8_t* block = nullptr;
+    std::unique_ptr<char[]> block;
 };
 using CommandBlocks = std::vector<BlockDef>;
 
@@ -121,9 +121,10 @@
     bool IsEmpty() const;
 
     DAWN_FORCE_INLINE bool NextCommandId(uint32_t* commandId) {
-        uint8_t* idPtr = AlignPtr(mCurrentPtr, alignof(uint32_t));
-        DAWN_ASSERT(idPtr + sizeof(uint32_t) <=
-                    mBlocks[mCurrentBlock].block + mBlocks[mCurrentBlock].size);
+        char* idPtr = AlignPtr(mCurrentPtr, alignof(uint32_t));
+        DAWN_ASSERT(idPtr == reinterpret_cast<char*>(&mEndOfBlock) ||
+                    idPtr + sizeof(uint32_t) <=
+                        mBlocks[mCurrentBlock].block.get() + mBlocks[mCurrentBlock].size);
 
         uint32_t id = *reinterpret_cast<uint32_t*>(idPtr);
 
@@ -138,9 +139,9 @@
     bool NextCommandIdInNewBlock(uint32_t* commandId);
 
     DAWN_FORCE_INLINE void* NextCommand(size_t commandSize, size_t commandAlignment) {
-        uint8_t* commandPtr = AlignPtr(mCurrentPtr, commandAlignment);
+        char* commandPtr = AlignPtr(mCurrentPtr, commandAlignment);
         DAWN_ASSERT(commandPtr + sizeof(commandSize) <=
-                    mBlocks[mCurrentBlock].block + mBlocks[mCurrentBlock].size);
+                    mBlocks[mCurrentBlock].block.get() + mBlocks[mCurrentBlock].size);
 
         mCurrentPtr = commandPtr + commandSize;
         return commandPtr;
@@ -156,8 +157,9 @@
     }
 
     CommandBlocks mBlocks;
-    // RAW_PTR_EXCLUSION: Performance reasons (based on analysis of MotionMark).
-    RAW_PTR_EXCLUSION uint8_t* mCurrentPtr = nullptr;
+    // RAW_PTR_EXCLUSION: This is an extremely hot pointer during command iteration, but always
+    // points to at least a valid uint32_t, either inside a block, or at mEndOfBlock.
+    RAW_PTR_EXCLUSION char* mCurrentPtr = nullptr;
     size_t mCurrentBlock = 0;
     // Used to avoid a special case for empty iterators.
     uint32_t mEndOfBlock = detail::kEndOfBlock;
@@ -220,9 +222,9 @@
     friend CommandIterator;
     CommandBlocks&& AcquireBlocks();
 
-    DAWN_FORCE_INLINE uint8_t* Allocate(uint32_t commandId,
-                                        size_t commandSize,
-                                        size_t commandAlignment) {
+    DAWN_FORCE_INLINE char* Allocate(uint32_t commandId,
+                                     size_t commandSize,
+                                     size_t commandAlignment) {
         DAWN_ASSERT(mCurrentPtr != nullptr);
         DAWN_ASSERT(mEndPtr != nullptr);
         DAWN_ASSERT(commandId != detail::kEndOfBlock);
@@ -250,7 +252,7 @@
             uint32_t* idAlloc = reinterpret_cast<uint32_t*>(mCurrentPtr);
             *idAlloc = commandId;
 
-            uint8_t* commandAlloc = AlignPtr(mCurrentPtr + sizeof(uint32_t), commandAlignment);
+            char* commandAlloc = AlignPtr(mCurrentPtr + sizeof(uint32_t), commandAlignment);
             mCurrentPtr = AlignPtr(commandAlloc + commandSize, alignof(uint32_t));
 
             return commandAlloc;
@@ -258,9 +260,9 @@
         return AllocateInNewBlock(commandId, commandSize, commandAlignment);
     }
 
-    uint8_t* AllocateInNewBlock(uint32_t commandId, size_t commandSize, size_t commandAlignment);
+    char* AllocateInNewBlock(uint32_t commandId, size_t commandSize, size_t commandAlignment);
 
-    DAWN_FORCE_INLINE uint8_t* AllocateData(size_t commandSize, size_t commandAlignment) {
+    DAWN_FORCE_INLINE char* AllocateData(size_t commandSize, size_t commandAlignment) {
         return Allocate(detail::kAdditionalData, commandSize, commandAlignment);
     }
 
@@ -274,14 +276,15 @@
     // Data used for the block range at initialization so that the first call to Allocate sees
     // there is not enough space and calls GetNewBlock. This avoids having to special case the
     // initialization in Allocate.
-    uint32_t mPlaceholderEnum[1] = {0};
+    uint32_t mPlaceholderSpace[1] = {0};
 
     // Pointers to the current range of allocation in the block. Guaranteed to allow for at
     // least one uint32_t if not nullptr, so that the special kEndOfBlock command id can always
     // be written. Nullptr iff the blocks were moved out.
-    // RAW_PTR_EXCLUSION: Performance reasons (based on analysis of MotionMark).
-    RAW_PTR_EXCLUSION uint8_t* mCurrentPtr = nullptr;
-    RAW_PTR_EXCLUSION uint8_t* mEndPtr = nullptr;
+    // RAW_PTR_EXCLUSION: These are extremely hot pointers during command allocation, but always
+    // set to a valid slice (either the placeholder space, or a real allocated block).
+    RAW_PTR_EXCLUSION char* mCurrentPtr = nullptr;
+    RAW_PTR_EXCLUSION char* mEndPtr = nullptr;
 };
 
 }  // namespace dawn::native
diff --git a/src/dawn/native/CommandBufferStateTracker.h b/src/dawn/native/CommandBufferStateTracker.h
index 78232b4..8476184 100644
--- a/src/dawn/native/CommandBufferStateTracker.h
+++ b/src/dawn/native/CommandBufferStateTracker.h
@@ -95,10 +95,6 @@
 
     ValidationAspects mAspects;
 
-    // RAW_PTR_EXCLUSION: Performance reasons (based on analysis of MotionMark).
-    RAW_PTR_EXCLUSION PerBindGroup<BindGroupBase*> mBindgroups = {};
-    PerBindGroup<std::vector<uint32_t>> mDynamicOffsets = {};
-
     VertexBufferMask mVertexBuffersUsed;
     PerVertexBuffer<uint64_t> mVertexBufferSizes = {};
 
@@ -107,7 +103,12 @@
     uint64_t mIndexBufferSize = 0;
     uint64_t mIndexBufferOffset = 0;
 
-    // RAW_PTR_EXCLUSION: Performance reasons (based on analysis of MotionMark).
+    // RAW_PTR_EXCLUSION: These pointers are very hot in command recording code and point at
+    // various objects referenced by the object graph of the CommandBuffer so they cannot be
+    // freed from underneath this class.
+    RAW_PTR_EXCLUSION PerBindGroup<BindGroupBase*> mBindgroups = {};
+    PerBindGroup<std::vector<uint32_t>> mDynamicOffsets = {};
+
     RAW_PTR_EXCLUSION PipelineLayoutBase* mLastPipelineLayout = nullptr;
     RAW_PTR_EXCLUSION PipelineBase* mLastPipeline = nullptr;
     RAW_PTR_EXCLUSION const RequiredBufferSizes* mMinBufferSizes = nullptr;