Dawn: Split OwnedCompilationMessages to serializable and immutable parts

This CL split the OwnedCompilationMessages to a serializable part that
is used for holding the messages contents during parsing or from cache,
namely ParsedCompilationMessages, and an immutable and non-movable part
that holds the CompilationInfo, CompilationMessage, and
DawnCompilationMessageUtf16 objects with raw pointers to each other,
still named OwnedCompilationMessages.

Bug: 42240459, 402772740
Change-Id: Ic8c40b34ea8359cd6bfe25458e3f1e50e72044db
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/244114
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Loko Kung <lokokung@google.com>
Commit-Queue: Zhaoming Jiang <zhaoming.jiang@microsoft.com>
diff --git a/src/dawn/native/CompilationMessages.cpp b/src/dawn/native/CompilationMessages.cpp
index a3499ac..8ad160c 100644
--- a/src/dawn/native/CompilationMessages.cpp
+++ b/src/dawn/native/CompilationMessages.cpp
@@ -27,6 +27,8 @@
 
 #include "dawn/native/CompilationMessages.h"
 
+#include <utility>
+
 #include "dawn/common/Assert.h"
 #include "dawn/common/StringViewUtils.h"
 #include "dawn/native/dawn_platform.h"
@@ -83,43 +85,35 @@
     return numberOfUTF16CodeUnits;
 }
 
-OwnedCompilationMessages::OwnedCompilationMessages() = default;
-
-OwnedCompilationMessages::~OwnedCompilationMessages() = default;
-
-void OwnedCompilationMessages::AddUnanchoredMessage(std::string_view message,
-                                                    wgpu::CompilationMessageType type) {
-    CompilationMessage m = {};
+void ParsedCompilationMessages::AddUnanchoredMessage(std::string_view message,
+                                                     wgpu::CompilationMessageType type) {
+    CompilationMessageContent m = {};
     m.message = message;
     m.type = type;
-    AddMessage(m);
-
-    mUtf16.push_back({});
+    AddMessage(std::move(m));
 }
 
-void OwnedCompilationMessages::AddMessageForTesting(std::string_view message,
-                                                    wgpu::CompilationMessageType type,
-                                                    uint64_t lineNum,
-                                                    uint64_t linePos,
-                                                    uint64_t offset,
-                                                    uint64_t length) {
-    CompilationMessage m = {};
-    m.message = message;
-    m.type = type;
-    m.lineNum = lineNum;
-    m.linePos = linePos;
-    m.offset = offset;
-    m.length = length;
-    AddMessage(m);
-
-    DawnCompilationMessageUtf16 utf16 = {};
-    utf16.linePos = linePos;
-    utf16.offset = offset;
-    utf16.length = length;
-    mUtf16.push_back(utf16);
+void ParsedCompilationMessages::AddMessageForTesting(std::string_view message,
+                                                     wgpu::CompilationMessageType type,
+                                                     uint64_t lineNum,
+                                                     uint64_t linePos,
+                                                     uint64_t offset,
+                                                     uint64_t length) {
+    AddMessage({{
+        .message = std::string(message),
+        .type = type,
+        .lineNum = lineNum,
+        .linePosInBytes = linePos,
+        .offsetInBytes = offset,
+        .lengthInBytes = length,
+        // Incorrect for non-ACSII strings
+        .linePosInUTF16 = linePos,
+        .offsetInUTF16 = offset,
+        .lengthInUTF16 = length,
+    }});
 }
 
-MaybeError OwnedCompilationMessages::AddMessage(const tint::diag::Diagnostic& diagnostic) {
+MaybeError ParsedCompilationMessages::AddMessage(const tint::diag::Diagnostic& diagnostic) {
     // Tint line and column values are 1-based.
     uint64_t lineNum = diagnostic.source.range.begin.line;
     uint64_t linePosInBytes = diagnostic.source.range.begin.column;
@@ -175,43 +169,26 @@
 
     std::string plainMessage = diagnostic.message.Plain();
 
-    CompilationMessage m = {};
-    m.message = std::string_view(plainMessage);
-    m.type = TintSeverityToMessageType(diagnostic.severity);
-    m.lineNum = lineNum;
-    m.linePos = linePosInBytes;
-    m.offset = offsetInBytes;
-    m.length = lengthInBytes;
-    AddMessage(m);
-
-    DawnCompilationMessageUtf16 utf16 = {};
-    utf16.linePos = linePosInUTF16;
-    utf16.offset = offsetInUTF16;
-    utf16.length = lengthInUTF16;
-    mUtf16.push_back(utf16);
+    AddMessage({{
+        .message = plainMessage,
+        .type = TintSeverityToMessageType(diagnostic.severity),
+        .lineNum = lineNum,
+        .linePosInBytes = linePosInBytes,
+        .offsetInBytes = offsetInBytes,
+        .lengthInBytes = lengthInBytes,
+        .linePosInUTF16 = linePosInUTF16,
+        .offsetInUTF16 = offsetInUTF16,
+        .lengthInUTF16 = lengthInUTF16,
+    }});
 
     return {};
 }
 
-void OwnedCompilationMessages::AddMessage(const CompilationMessage& message) {
-    // Cannot add messages after GetCompilationInfo has been called.
-    DAWN_ASSERT(!mCompilationInfo->has_value());
-
-    DAWN_ASSERT(message.nextInChain == nullptr);
-
-    mMessages.push_back(message);
-
-    // Own the contents of the message as it might be freed afterwards.
-    // Note that we use make_unique here as moving strings doesn't guarantee that the data pointer
-    // stays the same, for example if there's some small string optimization.
-    mMessageStrings.push_back(std::make_unique<std::string>(message.message));
-    mMessages.back().message = ToOutputStringView(*mMessageStrings.back());
+void ParsedCompilationMessages::AddMessage(CompilationMessageContent&& message) {
+    messages.push_back(message);
 }
 
-MaybeError OwnedCompilationMessages::AddMessages(const tint::diag::List& diagnostics) {
-    // Cannot add messages after GetCompilationInfo has been called.
-    DAWN_ASSERT(!mCompilationInfo->has_value());
-
+MaybeError ParsedCompilationMessages::AddMessages(const tint::diag::List& diagnostics) {
     for (const auto& diag : diagnostics) {
         DAWN_TRY(AddMessage(diag));
     }
@@ -221,49 +198,7 @@
     return {};
 }
 
-void OwnedCompilationMessages::ClearMessages() {
-    // Cannot clear messages after GetCompilationInfo has been called.
-    DAWN_ASSERT(!mCompilationInfo->has_value());
-
-    mMessageStrings.clear();
-    mMessages.clear();
-    mUtf16.clear();
-}
-
-const CompilationInfo* OwnedCompilationMessages::GetCompilationInfo() {
-    return mCompilationInfo.Use([&](auto info) {
-        if (info->has_value()) {
-            return &info->value();
-        }
-
-        // Append the UTF16 extension now.
-        DAWN_ASSERT(mMessages.size() == mUtf16.size());
-        for (size_t i = 0; i < mMessages.size(); i++) {
-            mMessages[i].nextInChain = &mUtf16[i];
-        }
-
-        (*info).emplace();
-        (*info)->messageCount = mMessages.size();
-        (*info)->messages = mMessages.data();
-        return &info->value();
-    });
-}
-
-const std::vector<std::string>& OwnedCompilationMessages::GetFormattedTintMessages() const {
-    return mFormattedTintMessages;
-}
-
-bool OwnedCompilationMessages::HasWarningsOrErrors() const {
-    for (const auto& message : mMessages) {
-        if (message.type == wgpu::CompilationMessageType::Error ||
-            message.type == wgpu::CompilationMessageType::Warning) {
-            return true;
-        }
-    }
-    return false;
-}
-
-void OwnedCompilationMessages::AddFormattedTintMessages(const tint::diag::List& diagnostics) {
+void ParsedCompilationMessages::AddFormattedTintMessages(const tint::diag::List& diagnostics) {
     tint::diag::List messageList;
     size_t warningCount = 0;
     size_t errorCount = 0;
@@ -304,7 +239,55 @@
     }
     t << "generated while compiling the shader:\n"
       << tint::diag::Formatter{style}.Format(messageList).Plain();
-    mFormattedTintMessages.push_back(t.str());
+    formattedTintMessages.push_back(t.str());
+}
+
+OwnedCompilationMessages::OwnedCompilationMessages(
+    ParsedCompilationMessages&& parsedCompilationMessagges)
+    : mMessageContents(parsedCompilationMessagges), mCompilationInfo() {
+    // Reserve the size of messages to avoid reallocations, so that we can use
+    // the raw pointer when adding each message.
+    mMessagesList.reserve(mMessageContents.messages.size());
+    mUtf16Messages.reserve(mMessageContents.messages.size());
+
+    // Build the CompilationMessage and DawnCompilationMessageUtf16 object for each message.
+    for (auto& m : mMessageContents.messages) {
+        if (m.type == wgpu::CompilationMessageType::Error ||
+            m.type == wgpu::CompilationMessageType::Warning) {
+            mHasWarningsOrErrors = true;
+        }
+
+        DawnCompilationMessageUtf16& utf16 = mUtf16Messages.emplace_back();
+        utf16.linePos = m.linePosInUTF16;
+        utf16.offset = m.offsetInUTF16;
+        utf16.length = m.lengthInUTF16;
+
+        CompilationMessage& message = mMessagesList.emplace_back();
+        message.message = ToOutputStringView(m.message);
+        message.type = m.type;
+        message.lineNum = m.lineNum;
+        message.linePos = m.linePosInBytes;
+        message.offset = m.offsetInBytes;
+        message.length = m.lengthInBytes;
+        // Points to the created DawnCompilationMessageUtf16, pointers would keep valid since
+        // mUtf16Messages has been reserved to the size of messages.
+        message.nextInChain = &utf16;
+    }
+
+    mCompilationInfo.messageCount = mMessagesList.size();
+    mCompilationInfo.messages = mMessagesList.data();
+}
+
+const CompilationInfo* OwnedCompilationMessages::GetCompilationInfo() const {
+    return &mCompilationInfo;
+}
+
+const std::vector<std::string>& OwnedCompilationMessages::GetFormattedTintMessages() const {
+    return mMessageContents.formattedTintMessages;
+}
+
+bool OwnedCompilationMessages::HasWarningsOrErrors() const {
+    return mHasWarningsOrErrors;
 }
 
 }  // namespace dawn::native
diff --git a/src/dawn/native/CompilationMessages.h b/src/dawn/native/CompilationMessages.h
index 78548f7..772590c 100644
--- a/src/dawn/native/CompilationMessages.h
+++ b/src/dawn/native/CompilationMessages.h
@@ -35,6 +35,7 @@
 #include "dawn/common/MutexProtected.h"
 #include "dawn/common/NonCopyable.h"
 #include "dawn/native/Error.h"
+#include "dawn/native/Serializable.h"
 #include "dawn/native/dawn_platform.h"
 
 namespace tint::diag {
@@ -46,41 +47,67 @@
 
 ResultOrError<uint64_t> CountUTF16CodeUnitsFromUTF8String(const std::string_view& utf8String);
 
-class OwnedCompilationMessages : public NonCopyable {
-  public:
-    OwnedCompilationMessages();
-    ~OwnedCompilationMessages();
+// CompilationMessageContent is serializable and holds the content of each compilation message.
+#define COMPILATION_MESSAGE_CONTENT_MEMBER(X) \
+    X(std::string, message)                   \
+    X(wgpu::CompilationMessageType, type)     \
+    X(uint64_t, lineNum)                      \
+    X(uint64_t, linePosInBytes)               \
+    X(uint64_t, offsetInBytes)                \
+    X(uint64_t, lengthInBytes)                \
+    X(uint64_t, linePosInUTF16)               \
+    X(uint64_t, offsetInUTF16)                \
+    X(uint64_t, lengthInUTF16)
+DAWN_SERIALIZABLE(struct, CompilationMessageContent, COMPILATION_MESSAGE_CONTENT_MEMBER){};
+#undef COMPILATION_MESSAGE_CONTENT_MEMBER
 
+// ParsedCompilationMessages holds the compilation messages generated by Tint or loaded from cache,
+// and is used to construct the OwnedCompilationMessages needed in ShaderModuleBase.
+#define PARSED_COMPILATION_MESSAGES_MEMBER(X)           \
+    X(std::vector<CompilationMessageContent>, messages) \
+    X(std::vector<std::string>, formattedTintMessages)
+DAWN_SERIALIZABLE(struct, ParsedCompilationMessages, PARSED_COMPILATION_MESSAGES_MEMBER) {
     // Adds a message on line 0 (before the first line).
-    void AddUnanchoredMessage(
-        std::string_view message,
-        wgpu::CompilationMessageType type = wgpu::CompilationMessageType::Info);
+    void AddUnanchoredMessage(std::string_view message, wgpu::CompilationMessageType type =
+                                                            wgpu::CompilationMessageType::Info);
     // For testing only. Uses the linePos/offset/length for both utf8 and utf16
     // (which is incorrect for non-ASCII strings).
     void AddMessageForTesting(
         std::string_view message,
         wgpu::CompilationMessageType type = wgpu::CompilationMessageType::Info,
-        uint64_t lineNum = 0,
-        uint64_t linePos = 0,
-        uint64_t offset = 0,
-        uint64_t length = 0);
+        uint64_t lineNum = 0, uint64_t linePos = 0, uint64_t offset = 0, uint64_t length = 0);
     MaybeError AddMessages(const tint::diag::List& diagnostics);
-    void ClearMessages();
 
     const CompilationInfo* GetCompilationInfo();
     const std::vector<std::string>& GetFormattedTintMessages() const;
+
+  private:
+    MaybeError AddMessage(const tint::diag::Diagnostic& diagnostic);
+    void AddMessage(CompilationMessageContent && message);
+    void AddFormattedTintMessages(const tint::diag::List& diagnostics);
+};
+#undef PARSED_COMPILATION_MESSAGES_MEMBER
+
+// Members of OwnedCompilationMessages hold the raw pointers to each other, so
+// OwnedCompilationMessages is non-movable. OwnedCompilationMessages is immutable after
+// construction, so it is thread-safe.
+class OwnedCompilationMessages : public NonMovable {
+  public:
+    // Create and store the required CompilationInfo and pointed CompilationMessage and
+    // DawnCompilationMessageUtf16 objects in constructor, and OwnedCompilationMessages will be
+    // read-only afterward.
+    explicit OwnedCompilationMessages(ParsedCompilationMessages&& parsedCompilationMessagges);
+
+    const CompilationInfo* GetCompilationInfo() const;
+    const std::vector<std::string>& GetFormattedTintMessages() const;
     bool HasWarningsOrErrors() const;
 
   private:
-    MaybeError AddMessage(const tint::diag::Diagnostic& diagnostic);
-    void AddMessage(const CompilationMessage& message);
-    void AddFormattedTintMessages(const tint::diag::List& diagnostics);
-
-    MutexProtected<std::optional<CompilationInfo>> mCompilationInfo = std::nullopt;
-    std::vector<std::unique_ptr<std::string>> mMessageStrings;
-    std::vector<CompilationMessage> mMessages;
-    std::vector<DawnCompilationMessageUtf16> mUtf16;
-    std::vector<std::string> mFormattedTintMessages;
+    const ParsedCompilationMessages mMessageContents;
+    bool mHasWarningsOrErrors = false;
+    CompilationInfo mCompilationInfo;
+    std::vector<DawnCompilationMessageUtf16> mUtf16Messages;
+    std::vector<CompilationMessage> mMessagesList;
 };
 
 }  // namespace dawn::native
diff --git a/src/dawn/native/Device.cpp b/src/dawn/native/Device.cpp
index b142d01..8baef75 100644
--- a/src/dawn/native/Device.cpp
+++ b/src/dawn/native/Device.cpp
@@ -1413,8 +1413,7 @@
     utils::TraceLabel label = utils::GetLabelForTrace(descriptor->label);
     TRACE_EVENT1(GetPlatform(), General, "DeviceBase::APICreateShaderModule", "label", label.label);
 
-    std::unique_ptr<OwnedCompilationMessages> compilationMessages(
-        std::make_unique<OwnedCompilationMessages>());
+    ParsedCompilationMessages compilationMessages;
     auto resultOrError =
         CreateShaderModule(descriptor, /*internalExtensions=*/{}, &compilationMessages);
 
@@ -1435,21 +1434,21 @@
     DAWN_ASSERT(consumedError);
     DAWN_ASSERT(result == nullptr);
     // The compilation messages should still be hold valid if shader module creation failed.
-    DAWN_ASSERT(compilationMessages != nullptr);
     // Move the compilation messages to the error shader module so the application can later
     // retrieve it with GetCompilationInfo.
-    result = ShaderModuleBase::MakeError(this, descriptor ? descriptor->label : nullptr,
-                                         std::move(compilationMessages));
+    result = ShaderModuleBase::MakeError(
+        this, descriptor ? descriptor->label : nullptr,
+        std::make_unique<OwnedCompilationMessages>(std::move(compilationMessages)));
     return ReturnToAPI(std::move(result));
 }
 
 ShaderModuleBase* DeviceBase::APICreateErrorShaderModule(const ShaderModuleDescriptor* descriptor,
                                                          StringView errorMessage) {
-    std::unique_ptr<OwnedCompilationMessages> compilationMessages(
-        std::make_unique<OwnedCompilationMessages>());
-    compilationMessages->AddUnanchoredMessage(errorMessage, wgpu::CompilationMessageType::Error);
+    ParsedCompilationMessages compilationMessages;
+    compilationMessages.AddUnanchoredMessage(errorMessage, wgpu::CompilationMessageType::Error);
     Ref<ShaderModuleBase> result = ShaderModuleBase::MakeError(
-        this, descriptor ? descriptor->label : nullptr, std::move(compilationMessages));
+        this, descriptor ? descriptor->label : nullptr,
+        std::make_unique<OwnedCompilationMessages>(std::move(compilationMessages)));
     auto log = result->GetCompilationLog();
 
     std::unique_ptr<ErrorData> errorData = DAWN_VALIDATION_ERROR(
@@ -2118,7 +2117,7 @@
 ResultOrError<Ref<ShaderModuleBase>> DeviceBase::CreateShaderModule(
     const ShaderModuleDescriptor* descriptor,
     const std::vector<tint::wgsl::Extension>& internalExtensions,
-    std::unique_ptr<OwnedCompilationMessages>* compilationMessages) {
+    ParsedCompilationMessages* compilationMessages) {
     DAWN_TRY(ValidateIsAlive());
 
     // Unpack and validate the descriptor chain before doing further validation or cache lookups.
@@ -2162,16 +2161,14 @@
 
     return GetOrCreate(
         mCaches->shaderModules, &blueprint, [&]() -> ResultOrError<Ref<ShaderModuleBase>> {
-            std::unique_ptr<OwnedCompilationMessages> inplaceCompilationMessages;
             // If the compilationMessages is nullptr, caller of this function assumes that the
             // shader creation will succeed and doesn't care the compilation messages. However we
             // still use a inplace compile messages to ensure every shader module in the cache have
             // a valid OwnedCompilationMessages.
+            ParsedCompilationMessages inplaceCompilationMessages;
             if (compilationMessages == nullptr) {
-                inplaceCompilationMessages = std::make_unique<OwnedCompilationMessages>();
                 compilationMessages = &inplaceCompilationMessages;
             }
-            auto* unownedMessages = compilationMessages->get();
 
             SCOPED_DAWN_HISTOGRAM_TIMER_MICROS(GetPlatform(), "CreateShaderModuleUS");
 
@@ -2181,13 +2178,16 @@
                 // Try to validate and parse the shader code, and if an error occurred return it
                 // without updating the cache.
                 DAWN_TRY(ParseShaderModule(this, unpacked, internalExtensions, &parseResult,
-                                           unownedMessages));
+                                           compilationMessages));
 
                 Ref<ShaderModuleBase> shaderModule;
                 // If created successfully, compilation messages are moved into the shader module.
+                DAWN_ASSERT(compilationMessages);
+                auto ownedCompilationMessages =
+                    std::make_unique<OwnedCompilationMessages>(std::move(*compilationMessages));
                 DAWN_TRY_ASSIGN(shaderModule,
                                 CreateShaderModuleImpl(unpacked, internalExtensions, &parseResult,
-                                                       compilationMessages));
+                                                       &ownedCompilationMessages));
                 shaderModule->SetContentHash(blueprintHash);
                 return shaderModule;
             }();
diff --git a/src/dawn/native/Device.h b/src/dawn/native/Device.h
index 27bf827..c5bc221 100644
--- a/src/dawn/native/Device.h
+++ b/src/dawn/native/Device.h
@@ -216,7 +216,7 @@
     ResultOrError<Ref<ShaderModuleBase>> CreateShaderModule(
         const ShaderModuleDescriptor* descriptor,
         const std::vector<tint::wgsl::Extension>& internalExtensions = {},
-        std::unique_ptr<OwnedCompilationMessages>* compilationMessages = nullptr);
+        ParsedCompilationMessages* compilationMessages = nullptr);
     ResultOrError<Ref<SwapChainBase>> CreateSwapChain(Surface* surface,
                                                       SwapChainBase* previousSwapChain,
                                                       const SurfaceConfiguration* config);
diff --git a/src/dawn/native/ShaderModule.cpp b/src/dawn/native/ShaderModule.cpp
index 190a907..ffbd580 100644
--- a/src/dawn/native/ShaderModule.cpp
+++ b/src/dawn/native/ShaderModule.cpp
@@ -379,7 +379,7 @@
 ResultOrError<tint::Program> ParseWGSL(const tint::Source::File* file,
                                        const tint::wgsl::AllowedFeatures& allowedFeatures,
                                        const std::vector<tint::wgsl::Extension>& internalExtensions,
-                                       OwnedCompilationMessages* outMessages) {
+                                       ParsedCompilationMessages* outMessages) {
     tint::wgsl::reader::Options options;
     options.allowed_features = allowedFeatures;
     options.allowed_features.extensions.insert(internalExtensions.begin(),
@@ -398,7 +398,7 @@
 #if TINT_BUILD_SPV_READER
 ResultOrError<tint::Program> ParseSPIRV(const std::vector<uint32_t>& spirv,
                                         const tint::wgsl::AllowedFeatures& allowedFeatures,
-                                        OwnedCompilationMessages* outMessages,
+                                        ParsedCompilationMessages* outMessages,
                                         const DawnShaderModuleSPIRVOptionsDescriptor* optionsDesc) {
     tint::spirv::reader::Options options;
     if (optionsDesc) {
@@ -1310,7 +1310,7 @@
                              const UnpackedPtr<ShaderModuleDescriptor>& descriptor,
                              const std::vector<tint::wgsl::Extension>& internalExtensions,
                              ShaderModuleParseResult* parseResult,
-                             OwnedCompilationMessages* outMessages) {
+                             ParsedCompilationMessages* outMessages) {
     DAWN_ASSERT(parseResult != nullptr);
 
     // We assume that the descriptor chain has already been validated.
@@ -1723,7 +1723,7 @@
     return {futureID};
 }
 
-OwnedCompilationMessages* ShaderModuleBase::GetCompilationMessages() const {
+const OwnedCompilationMessages* ShaderModuleBase::GetCompilationMessages() const {
     return mCompilationMessages.get();
 }
 
@@ -1743,6 +1743,11 @@
     return t.str();
 }
 
+void ShaderModuleBase::SetCompilationMessagesForTesting(
+    std::unique_ptr<OwnedCompilationMessages>* compilationMessages) {
+    mCompilationMessages = std::move(*compilationMessages);
+}
+
 MaybeError ShaderModuleBase::InitializeBase(
     ShaderModuleParseResult* parseResult,
     std::unique_ptr<OwnedCompilationMessages>* compilationMessages) {
diff --git a/src/dawn/native/ShaderModule.h b/src/dawn/native/ShaderModule.h
index 8b49bd3..6eee850 100644
--- a/src/dawn/native/ShaderModule.h
+++ b/src/dawn/native/ShaderModule.h
@@ -135,7 +135,7 @@
                              const UnpackedPtr<ShaderModuleDescriptor>& descriptor,
                              const std::vector<tint::wgsl::Extension>& internalExtensions,
                              ShaderModuleParseResult* parseResult,
-                             OwnedCompilationMessages* outMessages);
+                             ParsedCompilationMessages* outMessages);
 
 MaybeError ValidateCompatibilityWithPipelineLayout(DeviceBase* device,
                                                    const EntryPointMetadata& entryPoint,
@@ -385,8 +385,10 @@
 
     Future APIGetCompilationInfo(const WGPUCompilationInfoCallbackInfo& callbackInfo);
 
-    OwnedCompilationMessages* GetCompilationMessages() const;
+    const OwnedCompilationMessages* GetCompilationMessages() const;
     std::string GetCompilationLog() const;
+    void SetCompilationMessagesForTesting(
+        std::unique_ptr<OwnedCompilationMessages>* compilationMessages);
 
     // Return nullable tintProgram directly without any recreation, can be used for testing the
     // releasing/recreation behaviors.
@@ -432,7 +434,7 @@
     };
     MutexProtected<TintData> mTintData;
 
-    std::unique_ptr<OwnedCompilationMessages> mCompilationMessages;
+    std::unique_ptr<const OwnedCompilationMessages> mCompilationMessages;
 
     const std::vector<tint::wgsl::Extension> mInternalExtensions;
 };
diff --git a/src/dawn/native/d3d/ShaderUtils.cpp b/src/dawn/native/d3d/ShaderUtils.cpp
index a9b50b9..db399b3 100644
--- a/src/dawn/native/d3d/ShaderUtils.cpp
+++ b/src/dawn/native/d3d/ShaderUtils.cpp
@@ -51,7 +51,7 @@
                                            const tint::Program* program,
                                            const tint::ast::transform::DataMap& inputs,
                                            tint::ast::transform::DataMap* outputs,
-                                           OwnedCompilationMessages* outMessages) {
+                                           ParsedCompilationMessages* outMessages) {
     DAWN_ASSERT(program != nullptr);
     tint::ast::transform::DataMap transform_outputs;
     tint::Program result = transformManager->Run(*program, inputs, transform_outputs);
diff --git a/src/dawn/native/utils/WGPUHelpers.cpp b/src/dawn/native/utils/WGPUHelpers.cpp
index 5fb3353..8d1ea7b 100644
--- a/src/dawn/native/utils/WGPUHelpers.cpp
+++ b/src/dawn/native/utils/WGPUHelpers.cpp
@@ -56,8 +56,7 @@
     wgslDesc.code = source;
     ShaderModuleDescriptor descriptor;
     descriptor.nextInChain = &wgslDesc;
-    std::unique_ptr<OwnedCompilationMessages> compilationMessages =
-        std::make_unique<OwnedCompilationMessages>();
+    ParsedCompilationMessages compilationMessages;
     return device->CreateShaderModule(&descriptor, internalExtensions, &compilationMessages);
 }
 
diff --git a/src/dawn/tests/unittests/native/mocks/ShaderModuleMock.cpp b/src/dawn/tests/unittests/native/mocks/ShaderModuleMock.cpp
index 574f738..6a1659c 100644
--- a/src/dawn/tests/unittests/native/mocks/ShaderModuleMock.cpp
+++ b/src/dawn/tests/unittests/native/mocks/ShaderModuleMock.cpp
@@ -28,6 +28,7 @@
 #include "dawn/tests/unittests/native/mocks/ShaderModuleMock.h"
 
 #include <memory>
+#include <utility>
 
 #include "dawn/native/ChainUtils.h"
 
@@ -50,14 +51,14 @@
     DeviceMock* device,
     const UnpackedPtr<ShaderModuleDescriptor>& descriptor) {
     ShaderModuleParseResult parseResult;
-    std::unique_ptr<OwnedCompilationMessages> compilationMessages =
-        std::make_unique<OwnedCompilationMessages>();
-    ParseShaderModule(device, descriptor, {}, &parseResult, compilationMessages.get())
-        .AcquireSuccess();
+    ParsedCompilationMessages compilationMessages;
+    ParseShaderModule(device, descriptor, {}, &parseResult, &compilationMessages).AcquireSuccess();
+    auto ownedCompilationMessages =
+        std::make_unique<OwnedCompilationMessages>(std::move(compilationMessages));
 
     Ref<ShaderModuleMock> shaderModule =
         AcquireRef(new NiceMock<ShaderModuleMock>(device, descriptor));
-    shaderModule->InitializeBase(&parseResult, &compilationMessages).AcquireSuccess();
+    shaderModule->InitializeBase(&parseResult, &ownedCompilationMessages).AcquireSuccess();
     return shaderModule;
 }
 
diff --git a/src/dawn/tests/unittests/validation/ShaderModuleValidationTests.cpp b/src/dawn/tests/unittests/validation/ShaderModuleValidationTests.cpp
index 81f4e91..b241e0c 100644
--- a/src/dawn/tests/unittests/validation/ShaderModuleValidationTests.cpp
+++ b/src/dawn/tests/unittests/validation/ShaderModuleValidationTests.cpp
@@ -25,11 +25,14 @@
 // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
+#include <memory>
 #include <sstream>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include "dawn/common/Constants.h"
+#include "dawn/native/CompilationMessages.h"
 #include "dawn/native/ShaderModule.h"
 #include "dawn/tests/unittests/validation/ValidationTest.h"
 #include "dawn/utils/ComboRenderPipelineDescriptor.h"
@@ -316,13 +319,19 @@
         })");
 
     native::ShaderModuleBase* shaderModuleBase = native::FromAPI(shaderModule.Get());
-    native::OwnedCompilationMessages* messages = shaderModuleBase->GetCompilationMessages();
-    messages->ClearMessages();
-    messages->AddMessageForTesting("Info Message");
-    messages->AddMessageForTesting("Warning Message", wgpu::CompilationMessageType::Warning);
-    messages->AddMessageForTesting("Error Message", wgpu::CompilationMessageType::Error, 3, 4);
-    messages->AddMessageForTesting("Complete Message", wgpu::CompilationMessageType::Info, 3, 4, 5,
-                                   6);
+
+    // Build a list of messages to test.
+    native::ParsedCompilationMessages messages;
+    messages.AddMessageForTesting("Info Message");
+    messages.AddMessageForTesting("Warning Message", wgpu::CompilationMessageType::Warning);
+    messages.AddMessageForTesting("Error Message", wgpu::CompilationMessageType::Error, 3, 4);
+    messages.AddMessageForTesting("Complete Message", wgpu::CompilationMessageType::Info, 3, 4, 5,
+                                  6);
+    auto ownedMessages = std::make_unique<native::OwnedCompilationMessages>(std::move(messages));
+    // Set the messages on the shader module base.
+    shaderModuleBase->SetCompilationMessagesForTesting(&ownedMessages);
+    // Assert that the messages are set.
+    ASSERT_EQ(ownedMessages, nullptr);
 
     shaderModule.GetCompilationInfo(
         wgpu::CallbackMode::AllowSpontaneous,