Improve validation errors for ShaderModule

Bug: dawn:563
Change-Id: I3c0809742f87517456fd8a5f7645005af636fa75
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/65801
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
diff --git a/generator/templates/dawn_native/webgpu_absl_format.h b/generator/templates/dawn_native/webgpu_absl_format.h
index d9c6a9a..a327841 100644
--- a/generator/templates/dawn_native/webgpu_absl_format.h
+++ b/generator/templates/dawn_native/webgpu_absl_format.h
@@ -21,6 +21,11 @@
 
 namespace dawn_native {
 
+    // TODO(dawn:563):
+    //  - Split the file between autogenerated parts and manually written parts.
+    //  - Forward declare common Dawn enums and have AbslFormatConvert for them.
+    //  - Support AbslFormatConvert for Dawn's typed integers.
+
     //
     // Structs (Manually written)
     //
diff --git a/src/dawn_native/Device.cpp b/src/dawn_native/Device.cpp
index fc1c1a9..bfd0bdd 100644
--- a/src/dawn_native/Device.cpp
+++ b/src/dawn_native/Device.cpp
@@ -1405,8 +1405,9 @@
         ShaderModuleParseResult parseResult;
 
         if (IsValidationEnabled()) {
-            DAWN_TRY(ValidateShaderModuleDescriptor(this, descriptor, &parseResult,
-                                                    compilationMessages));
+            DAWN_TRY_CONTEXT(
+                ValidateShaderModuleDescriptor(this, descriptor, &parseResult, compilationMessages),
+                "validating %s", descriptor);
         }
 
         return GetOrCreateShaderModule(descriptor, &parseResult, compilationMessages);
diff --git a/src/dawn_native/ShaderModule.cpp b/src/dawn_native/ShaderModule.cpp
index 4c22706..4132d3c 100644
--- a/src/dawn_native/ShaderModule.cpp
+++ b/src/dawn_native/ShaderModule.cpp
@@ -33,29 +33,8 @@
 
 namespace dawn_native {
 
-    EntryPointMetadata::OverridableConstant::Type GetDawnOverridableConstantType(
-        tint::inspector::OverridableConstant::Type type) {
-        switch (type) {
-            case tint::inspector::OverridableConstant::Type::kBool:
-                return EntryPointMetadata::OverridableConstant::Type::Boolean;
-            case tint::inspector::OverridableConstant::Type::kFloat32:
-                return EntryPointMetadata::OverridableConstant::Type::Float32;
-            case tint::inspector::OverridableConstant::Type::kInt32:
-                return EntryPointMetadata::OverridableConstant::Type::Int32;
-            case tint::inspector::OverridableConstant::Type::kUint32:
-                return EntryPointMetadata::OverridableConstant::Type::Uint32;
-            default:
-                UNREACHABLE();
-        }
-    }
-
     namespace {
 
-        std::string GetShaderDeclarationString(BindGroupIndex group, BindingNumber binding) {
-            return absl::StrFormat("the shader module declaration at set %u, binding %u",
-                                   static_cast<uint32_t>(group), static_cast<uint32_t>(binding));
-        }
-
         tint::transform::VertexFormat ToTintVertexFormat(wgpu::VertexFormat format) {
             switch (format) {
                 case wgpu::VertexFormat::Uint8x2:
@@ -418,21 +397,32 @@
             UNREACHABLE();
         }
 
+        EntryPointMetadata::OverridableConstant::Type FromTintOverridableConstantType(
+            tint::inspector::OverridableConstant::Type type) {
+            switch (type) {
+                case tint::inspector::OverridableConstant::Type::kBool:
+                    return EntryPointMetadata::OverridableConstant::Type::Boolean;
+                case tint::inspector::OverridableConstant::Type::kFloat32:
+                    return EntryPointMetadata::OverridableConstant::Type::Float32;
+                case tint::inspector::OverridableConstant::Type::kInt32:
+                    return EntryPointMetadata::OverridableConstant::Type::Int32;
+                case tint::inspector::OverridableConstant::Type::kUint32:
+                    return EntryPointMetadata::OverridableConstant::Type::Uint32;
+                default:
+                    UNREACHABLE();
+            }
+        }
+
         ResultOrError<tint::Program> ParseWGSL(const tint::Source::File* file,
                                                OwnedCompilationMessages* outMessages) {
-            std::ostringstream errorStream;
-            errorStream << "Tint WGSL reader failure:" << std::endl;
-
             tint::Program program = tint::reader::wgsl::Parse(file);
             if (outMessages != nullptr) {
                 outMessages->AddMessages(program.Diagnostics());
             }
             if (!program.IsValid()) {
-                auto err = program.Diagnostics().str();
-                errorStream << "Parser: " << err << std::endl
-                            << "Shader: " << std::endl
-                            << file->content << std::endl;
-                return DAWN_VALIDATION_ERROR(errorStream.str().c_str());
+                return DAWN_FORMAT_VALIDATION_ERROR(
+                    "Tint WGSL reader failure:\nParser: %s\nShader:\n%s\n",
+                    program.Diagnostics().str(), file->content.data);
             }
 
             return std::move(program);
@@ -440,17 +430,13 @@
 
         ResultOrError<tint::Program> ParseSPIRV(const std::vector<uint32_t>& spirv,
                                                 OwnedCompilationMessages* outMessages) {
-            std::ostringstream errorStream;
-            errorStream << "Tint SPIRV reader failure:" << std::endl;
-
             tint::Program program = tint::reader::spirv::Parse(spirv);
             if (outMessages != nullptr) {
                 outMessages->AddMessages(program.Diagnostics());
             }
             if (!program.IsValid()) {
-                auto err = program.Diagnostics().str();
-                errorStream << "Parser: " << err << std::endl;
-                return DAWN_VALIDATION_ERROR(errorStream.str().c_str());
+                return DAWN_FORMAT_VALIDATION_ERROR("Tint SPIR-V reader failure:\nParser: %s\n",
+                                                    program.Diagnostics().str());
             }
 
             return std::move(program);
@@ -485,140 +471,138 @@
             return requiredBufferSizes;
         }
 
+        MaybeError ValidateCompatibilityOfSingleBindingWithLayout(
+            const DeviceBase* device,
+            const BindGroupLayoutBase* layout,
+            SingleShaderStage entryPointStage,
+            BindingNumber bindingNumber,
+            const ShaderBindingInfo& shaderInfo) {
+            const BindGroupLayoutBase::BindingMap& layoutBindings = layout->GetBindingMap();
+
+            const auto& bindingIt = layoutBindings.find(bindingNumber);
+            DAWN_INVALID_IF(bindingIt == layoutBindings.end(), "Binding doesn't exist in %s.",
+                            layout);
+
+            BindingIndex bindingIndex(bindingIt->second);
+            const BindingInfo& layoutInfo = layout->GetBindingInfo(bindingIndex);
+
+            // TODO(dawn:563): Provide info about the binding types.
+            DAWN_INVALID_IF(layoutInfo.bindingType != shaderInfo.bindingType,
+                            "Binding type (buffer vs. texture vs. sampler) doesn't match the type "
+                            "in the layout.");
+
+            // TODO(dawn:563): Provide info about the visibility.
+            DAWN_INVALID_IF(
+                (layoutInfo.visibility & StageBit(entryPointStage)) == 0,
+                "Entry point's stage is not in the binding visibility in the layout (%s)",
+                layoutInfo.visibility);
+
+            switch (layoutInfo.bindingType) {
+                case BindingInfoType::Texture: {
+                    DAWN_INVALID_IF(
+                        layoutInfo.texture.multisampled != shaderInfo.texture.multisampled,
+                        "Binding multisampled flag (%u) doesn't match the layout's multisampled "
+                        "flag (%u)",
+                        layoutInfo.texture.multisampled, shaderInfo.texture.multisampled);
+
+                    // TODO(dawn:563): Provide info about the sample types.
+                    DAWN_INVALID_IF((SampleTypeToSampleTypeBit(layoutInfo.texture.sampleType) &
+                                     shaderInfo.texture.compatibleSampleTypes) == 0,
+                                    "The sample type in the shader is not compatible with the "
+                                    "sample type of the layout.");
+
+                    DAWN_INVALID_IF(
+                        layoutInfo.texture.viewDimension != shaderInfo.texture.viewDimension,
+                        "The shader's binding dimension (%s) doesn't match the shader's binding "
+                        "dimension (%s).",
+                        layoutInfo.texture.viewDimension, shaderInfo.texture.viewDimension);
+                    break;
+                }
+
+                case BindingInfoType::StorageTexture: {
+                    ASSERT(layoutInfo.storageTexture.format != wgpu::TextureFormat::Undefined);
+                    ASSERT(shaderInfo.storageTexture.format != wgpu::TextureFormat::Undefined);
+
+                    DAWN_INVALID_IF(
+                        layoutInfo.storageTexture.access != shaderInfo.storageTexture.access,
+                        "The layout's binding access (%s) isn't compatible with the shader's "
+                        "binding access (%s).",
+                        layoutInfo.storageTexture.access, shaderInfo.storageTexture.access);
+
+                    DAWN_INVALID_IF(
+                        layoutInfo.storageTexture.format != shaderInfo.storageTexture.format,
+                        "The layout's binding format (%s) doesn't match the shader's binding "
+                        "format (%s).",
+                        layoutInfo.storageTexture.format, shaderInfo.storageTexture.format);
+
+                    DAWN_INVALID_IF(layoutInfo.storageTexture.viewDimension !=
+                                        shaderInfo.storageTexture.viewDimension,
+                                    "The layout's binding dimension (%s) doesn't match the "
+                                    "shader's binding dimension (%s).",
+                                    layoutInfo.storageTexture.viewDimension,
+                                    shaderInfo.storageTexture.viewDimension);
+                    break;
+                }
+
+                case BindingInfoType::ExternalTexture: {
+                    // Nothing to validate! (yet?)
+                    break;
+                }
+
+                case BindingInfoType::Buffer: {
+                    // Binding mismatch between shader and bind group is invalid. For example, a
+                    // writable binding in the shader with a readonly storage buffer in the bind
+                    // group layout is invalid. However, a readonly binding in the shader with a
+                    // writable storage buffer in the bind group layout is valid, a storage
+                    // binding in the shader with an internal storage buffer in the bind group
+                    // layout is also valid.
+                    bool validBindingConversion =
+                        (layoutInfo.buffer.type == wgpu::BufferBindingType::Storage &&
+                         shaderInfo.buffer.type == wgpu::BufferBindingType::ReadOnlyStorage) ||
+                        (layoutInfo.buffer.type == kInternalStorageBufferBinding &&
+                         shaderInfo.buffer.type == wgpu::BufferBindingType::Storage);
+
+                    DAWN_INVALID_IF(
+                        layoutInfo.buffer.type != shaderInfo.buffer.type && !validBindingConversion,
+                        "The buffer type in the shader (%s) is not compatible with the type in the "
+                        "layout (%s).",
+                        shaderInfo.buffer.type, layoutInfo.buffer.type);
+
+                    DAWN_INVALID_IF(
+                        layoutInfo.buffer.minBindingSize != 0 &&
+                            shaderInfo.buffer.minBindingSize > layoutInfo.buffer.minBindingSize,
+                        "The shader uses more bytes of the buffer (%u) than the layout's "
+                        "minBindingSize (%u).",
+                        shaderInfo.buffer.minBindingSize, layoutInfo.buffer.minBindingSize);
+                    break;
+                }
+
+                case BindingInfoType::Sampler:
+                    DAWN_INVALID_IF(
+                        (layoutInfo.sampler.type == wgpu::SamplerBindingType::Comparison) !=
+                            shaderInfo.sampler.isComparison,
+                        "The sampler type in the shader (comparison: %u) doesn't match the type in "
+                        "the layout (comparison: %u).",
+                        shaderInfo.sampler.isComparison,
+                        layoutInfo.sampler.type == wgpu::SamplerBindingType::Comparison);
+                    break;
+            }
+
+            return {};
+        }
         MaybeError ValidateCompatibilityWithBindGroupLayout(DeviceBase* device,
                                                             BindGroupIndex group,
                                                             const EntryPointMetadata& entryPoint,
                                                             const BindGroupLayoutBase* layout) {
-            const BindGroupLayoutBase::BindingMap& layoutBindings = layout->GetBindingMap();
-
             // Iterate over all bindings used by this group in the shader, and find the
             // corresponding binding in the BindGroupLayout, if it exists.
             for (const auto& it : entryPoint.bindings[group]) {
-                BindingNumber bindingNumber = it.first;
-                const ShaderBindingInfo& shaderInfo = it.second;
-
-                const auto& bindingIt = layoutBindings.find(bindingNumber);
-                if (bindingIt == layoutBindings.end()) {
-                    return DAWN_VALIDATION_ERROR("Missing bind group layout entry for " +
-                                                 GetShaderDeclarationString(group, bindingNumber));
-                }
-                BindingIndex bindingIndex(bindingIt->second);
-                const BindingInfo& layoutInfo = layout->GetBindingInfo(bindingIndex);
-
-                if (layoutInfo.bindingType != shaderInfo.bindingType) {
-                    return DAWN_VALIDATION_ERROR(
-                        "The binding type of the bind group layout entry conflicts " +
-                        GetShaderDeclarationString(group, bindingNumber));
-                }
-
-                if ((layoutInfo.visibility & StageBit(entryPoint.stage)) == 0) {
-                    return DAWN_VALIDATION_ERROR("The bind group layout entry for " +
-                                                 GetShaderDeclarationString(group, bindingNumber) +
-                                                 " is not visible for the shader stage");
-                }
-
-                switch (layoutInfo.bindingType) {
-                    case BindingInfoType::Texture: {
-                        if (layoutInfo.texture.multisampled != shaderInfo.texture.multisampled) {
-                            return DAWN_VALIDATION_ERROR(
-                                "The texture multisampled flag of the bind group layout entry is "
-                                "different from " +
-                                GetShaderDeclarationString(group, bindingNumber));
-                        }
-
-                        if ((SampleTypeToSampleTypeBit(layoutInfo.texture.sampleType) &
-                             shaderInfo.texture.compatibleSampleTypes) == 0) {
-                            return DAWN_VALIDATION_ERROR(
-                                "The texture sampleType of the bind group layout entry is "
-                                "not compatible with " +
-                                GetShaderDeclarationString(group, bindingNumber));
-                        }
-
-                        if (layoutInfo.texture.viewDimension != shaderInfo.texture.viewDimension) {
-                            return DAWN_VALIDATION_ERROR(
-                                "The texture viewDimension of the bind group layout entry is "
-                                "different "
-                                "from " +
-                                GetShaderDeclarationString(group, bindingNumber));
-                        }
-                        break;
-                    }
-
-                    case BindingInfoType::StorageTexture: {
-                        ASSERT(layoutInfo.storageTexture.format != wgpu::TextureFormat::Undefined);
-                        ASSERT(shaderInfo.storageTexture.format != wgpu::TextureFormat::Undefined);
-
-                        if (layoutInfo.storageTexture.access != shaderInfo.storageTexture.access) {
-                            return DAWN_VALIDATION_ERROR(
-                                "The storageTexture access mode of the bind group layout entry is "
-                                "different from " +
-                                GetShaderDeclarationString(group, bindingNumber));
-                        }
-
-                        if (layoutInfo.storageTexture.format != shaderInfo.storageTexture.format) {
-                            return DAWN_VALIDATION_ERROR(
-                                "The storageTexture format of the bind group layout entry is "
-                                "different from " +
-                                GetShaderDeclarationString(group, bindingNumber));
-                        }
-                        if (layoutInfo.storageTexture.viewDimension !=
-                            shaderInfo.storageTexture.viewDimension) {
-                            return DAWN_VALIDATION_ERROR(
-                                "The storageTexture viewDimension of the bind group layout entry "
-                                "is different from " +
-                                GetShaderDeclarationString(group, bindingNumber));
-                        }
-                        break;
-                    }
-
-                    case BindingInfoType::ExternalTexture: {
-                        if (shaderInfo.bindingType != BindingInfoType::ExternalTexture) {
-                            return DAWN_VALIDATION_ERROR(
-                                "The external texture bind group layout entry conflicts with " +
-                                GetShaderDeclarationString(group, bindingNumber));
-                        }
-                        break;
-                    }
-
-                    case BindingInfoType::Buffer: {
-                        // Binding mismatch between shader and bind group is invalid. For example, a
-                        // writable binding in the shader with a readonly storage buffer in the bind
-                        // group layout is invalid. However, a readonly binding in the shader with a
-                        // writable storage buffer in the bind group layout is valid, a storage
-                        // binding in the shader with an internal storage buffer in the bind group
-                        // layout is also valid.
-                        bool validBindingConversion =
-                            (layoutInfo.buffer.type == wgpu::BufferBindingType::Storage &&
-                             shaderInfo.buffer.type == wgpu::BufferBindingType::ReadOnlyStorage) ||
-                            (layoutInfo.buffer.type == kInternalStorageBufferBinding &&
-                             shaderInfo.buffer.type == wgpu::BufferBindingType::Storage);
-
-                        if (layoutInfo.buffer.type != shaderInfo.buffer.type &&
-                            !validBindingConversion) {
-                            return DAWN_VALIDATION_ERROR(
-                                "The buffer type of the bind group layout entry conflicts " +
-                                GetShaderDeclarationString(group, bindingNumber));
-                        }
-
-                        if (layoutInfo.buffer.minBindingSize != 0 &&
-                            shaderInfo.buffer.minBindingSize > layoutInfo.buffer.minBindingSize) {
-                            return DAWN_VALIDATION_ERROR(
-                                "The minimum buffer size of the bind group layout entry is smaller "
-                                "than " +
-                                GetShaderDeclarationString(group, bindingNumber));
-                        }
-                        break;
-                    }
-
-                    case BindingInfoType::Sampler:
-                        if ((layoutInfo.sampler.type == wgpu::SamplerBindingType::Comparison) !=
-                            shaderInfo.sampler.isComparison) {
-                            return DAWN_VALIDATION_ERROR(
-                                "The sampler type of the bind group layout entry is "
-                                "not compatible with " +
-                                GetShaderDeclarationString(group, bindingNumber));
-                        }
-                }
+                DAWN_TRY_CONTEXT(ValidateCompatibilityOfSingleBindingWithLayout(
+                                     device, layout, entryPoint.stage, it.first, it.second),
+                                 "validating that the entry-point's declaration for [[group(%u), "
+                                 "binding(%u)]] matches %s",
+                                 static_cast<uint32_t>(group), static_cast<uint32_t>(it.first),
+                                 layout);
             }
 
             return {};
@@ -630,16 +614,14 @@
             ASSERT(program->IsValid());
 
             EntryPointMetadataTable result;
-            std::ostringstream errorStream;
-            errorStream << "Tint Reflection failure:" << std::endl;
 
             tint::inspector::Inspector inspector(program);
             auto entryPoints = inspector.GetEntryPoints();
-            if (inspector.has_error()) {
-                errorStream << "Inspector: " << inspector.error() << std::endl;
-                return DAWN_VALIDATION_ERROR(errorStream.str().c_str());
-            }
+            DAWN_INVALID_IF(inspector.has_error(), "Tint Reflection failure: Inspector: %s\n",
+                            inspector.error());
 
+            // TODO(dawn:563): use DAWN_TRY_CONTEXT to output the name of the entry point we're
+            // reflecting.
             constexpr uint32_t kMaxInterStageShaderLocation = kMaxInterStageShaderVariables - 1;
             for (auto& entryPoint : entryPoints) {
                 ASSERT(result.count(entryPoint.name) == 0);
@@ -651,7 +633,7 @@
 
                     for (auto& c : entryPoint.overridable_constants) {
                         EntryPointMetadata::OverridableConstant constant = {
-                            name2Id.at(c.name), GetDawnOverridableConstantType(c.type)};
+                            name2Id.at(c.name), FromTintOverridableConstantType(c.type)};
                         metadata->overridableConstants[c.name] = constant;
                         // TODO(tint:1155) tint needs ways to differentiate whether a pipeline
                         // constant id is specified explicitly. Now we just store numeric id and
@@ -663,24 +645,14 @@
                 DAWN_TRY_ASSIGN(metadata->stage, TintPipelineStageToShaderStage(entryPoint.stage));
 
                 if (metadata->stage == SingleShaderStage::Compute) {
-                    if (entryPoint.workgroup_size_x > kMaxComputeWorkgroupSizeX) {
-                        errorStream << "Workgroup X dimension exceeds maximum allowed:"
-                                    << entryPoint.workgroup_size_x << " > "
-                                    << kMaxComputeWorkgroupSizeX;
-                        return DAWN_VALIDATION_ERROR(errorStream.str());
-                    }
-                    if (entryPoint.workgroup_size_y > kMaxComputeWorkgroupSizeY) {
-                        errorStream << "Workgroup Y dimension exceeds maximum allowed: "
-                                    << entryPoint.workgroup_size_y << " > "
-                                    << kMaxComputeWorkgroupSizeY;
-                        return DAWN_VALIDATION_ERROR(errorStream.str());
-                    }
-                    if (entryPoint.workgroup_size_z > kMaxComputeWorkgroupSizeZ) {
-                        errorStream << "Workgroup Z dimension exceeds maximum allowed: "
-                                    << entryPoint.workgroup_size_z << " > "
-                                    << kMaxComputeWorkgroupSizeZ;
-                        return DAWN_VALIDATION_ERROR(errorStream.str());
-                    }
+                    DAWN_INVALID_IF(entryPoint.workgroup_size_x > kMaxComputeWorkgroupSizeX ||
+                                        entryPoint.workgroup_size_y > kMaxComputeWorkgroupSizeY ||
+                                        entryPoint.workgroup_size_z > kMaxComputeWorkgroupSizeZ,
+                                    "Entry-point uses workgroup_size(%u, %u, %u) that exceeds the "
+                                    "maximum allowed (%u, %u, %u).",
+                                    entryPoint.workgroup_size_x, entryPoint.workgroup_size_y,
+                                    entryPoint.workgroup_size_z, kMaxComputeWorkgroupSizeX,
+                                    kMaxComputeWorkgroupSizeY, kMaxComputeWorkgroupSizeZ);
 
                     // Dimensions have already been validated against their individual limits above.
                     // This assertion ensures that the product of such limited dimensions cannot
@@ -689,23 +661,20 @@
                                           kMaxComputeWorkgroupSizeY * kMaxComputeWorkgroupSizeZ <=
                                       std::numeric_limits<uint32_t>::max(),
                                   "Per-dimension workgroup size limits are too high");
-                    uint32_t num_invocations = entryPoint.workgroup_size_x *
-                                               entryPoint.workgroup_size_y *
-                                               entryPoint.workgroup_size_z;
-                    if (num_invocations > kMaxComputeWorkgroupInvocations) {
-                        errorStream << "Number of workgroup invocations exceeds maximum allowed: "
-                                    << num_invocations << " > " << kMaxComputeWorkgroupInvocations;
-                        return DAWN_VALIDATION_ERROR(errorStream.str());
-                    }
+                    uint32_t numInvocations = entryPoint.workgroup_size_x *
+                                              entryPoint.workgroup_size_y *
+                                              entryPoint.workgroup_size_z;
+                    DAWN_INVALID_IF(numInvocations > kMaxComputeWorkgroupInvocations,
+                                    "The total number of workgroup invocations (%u) exceeds the "
+                                    "maximum allowed (%u).",
+                                    numInvocations, kMaxComputeWorkgroupInvocations);
 
-                    const size_t workgroup_storage_size =
+                    const size_t workgroupStorageSize =
                         inspector.GetWorkgroupStorageSize(entryPoint.name);
-                    if (workgroup_storage_size > kMaxComputeWorkgroupStorageSize) {
-                        errorStream << "Workgroup shared storage size for " << entryPoint.name
-                                    << " exceeds the maximum allowed: " << workgroup_storage_size
-                                    << " > " << kMaxComputeWorkgroupStorageSize;
-                        return DAWN_VALIDATION_ERROR(errorStream.str());
-                    }
+                    DAWN_INVALID_IF(workgroupStorageSize > kMaxComputeWorkgroupStorageSize,
+                                    "The total use of workgroup storage (%u bytes) is larger than "
+                                    "the maximum allowed (%u bytes).",
+                                    workgroupStorageSize, kMaxComputeWorkgroupStorageSize);
 
                     metadata->localWorkgroupSize.x = entryPoint.workgroup_size_x;
                     metadata->localWorkgroupSize.y = entryPoint.workgroup_size_y;
@@ -713,96 +682,98 @@
                 }
 
                 if (metadata->stage == SingleShaderStage::Vertex) {
-                    for (const auto& input_var : entryPoint.input_variables) {
-                        if (!input_var.has_location_decoration) {
-                            return DAWN_VALIDATION_ERROR(
-                                "Need Location decoration on Vertex input");
-                        }
-                        uint32_t unsanitizedLocation = input_var.location_decoration;
-                        if (DAWN_UNLIKELY(unsanitizedLocation >= kMaxVertexAttributes)) {
-                            std::stringstream ss;
-                            ss << "Attribute location (" << unsanitizedLocation << ") over limits";
-                            return DAWN_VALIDATION_ERROR(ss.str());
-                        }
+                    for (const auto& inputVar : entryPoint.input_variables) {
+                        DAWN_INVALID_IF(
+                            !inputVar.has_location_decoration,
+                            "Vertex input variable \"%s\" doesn't have a location decoration.",
+                            inputVar.name);
+
+                        uint32_t unsanitizedLocation = inputVar.location_decoration;
+                        DAWN_INVALID_IF(unsanitizedLocation >= kMaxVertexAttributes,
+                                        "Vertex input variable \"%s\" has a location (%u) that "
+                                        "exceeds the maximum (%u)",
+                                        inputVar.name, unsanitizedLocation, kMaxVertexAttributes);
                         VertexAttributeLocation location(static_cast<uint8_t>(unsanitizedLocation));
+
                         DAWN_TRY_ASSIGN(
                             metadata->vertexInputBaseTypes[location],
-                            TintComponentTypeToVertexFormatBaseType(input_var.component_type));
+                            TintComponentTypeToVertexFormatBaseType(inputVar.component_type));
                         metadata->usedVertexInputs.set(location);
                     }
 
-                    // [[position]] must be declared in a vertex shader.
+                    // [[position]] must be declared in a vertex shader but is not exposed as an
+                    // output variable by Tint so we directly add its components to the total.
                     uint32_t totalInterStageShaderComponents = 4;
-                    for (const auto& output_var : entryPoint.output_variables) {
-                        if (DAWN_UNLIKELY(!output_var.has_location_decoration)) {
-                            std::stringstream ss;
-                            ss << "Missing location qualifier on vertex output, "
-                               << output_var.name;
-                            return DAWN_VALIDATION_ERROR(ss.str());
-                        }
-                        uint32_t location = output_var.location_decoration;
-                        if (DAWN_UNLIKELY(location > kMaxInterStageShaderLocation)) {
-                            std::stringstream ss;
-                            ss << "Vertex output location (" << location << ") over limits";
-                            return DAWN_VALIDATION_ERROR(ss.str());
-                        }
+                    for (const auto& outputVar : entryPoint.output_variables) {
+                        DAWN_INVALID_IF(
+                            !outputVar.has_location_decoration,
+                            "Vertex ouput variable \"%s\" doesn't have a location decoration.",
+                            outputVar.name);
+
+                        uint32_t location = outputVar.location_decoration;
+                        DAWN_INVALID_IF(location > kMaxInterStageShaderLocation,
+                                        "Vertex output variable \"%s\" has a location (%u) that "
+                                        "exceeds the maximum (%u).",
+                                        outputVar.name, location, kMaxInterStageShaderLocation);
+
                         metadata->usedInterStageVariables.set(location);
                         DAWN_TRY_ASSIGN(
                             metadata->interStageVariables[location].baseType,
-                            TintComponentTypeToInterStageComponentType(output_var.component_type));
+                            TintComponentTypeToInterStageComponentType(outputVar.component_type));
                         DAWN_TRY_ASSIGN(metadata->interStageVariables[location].componentCount,
                                         TintCompositionTypeToInterStageComponentCount(
-                                            output_var.composition_type));
-                        DAWN_TRY_ASSIGN(metadata->interStageVariables[location].interpolationType,
-                                        TintInterpolationTypeToInterpolationType(
-                                            output_var.interpolation_type));
+                                            outputVar.composition_type));
+                        DAWN_TRY_ASSIGN(
+                            metadata->interStageVariables[location].interpolationType,
+                            TintInterpolationTypeToInterpolationType(outputVar.interpolation_type));
                         DAWN_TRY_ASSIGN(
                             metadata->interStageVariables[location].interpolationSampling,
                             TintInterpolationSamplingToInterpolationSamplingType(
-                                output_var.interpolation_sampling));
+                                outputVar.interpolation_sampling));
 
                         totalInterStageShaderComponents +=
                             metadata->interStageVariables[location].componentCount;
                     }
 
-                    if (DAWN_UNLIKELY(totalInterStageShaderComponents >
-                                      kMaxInterStageShaderComponents)) {
-                        return DAWN_VALIDATION_ERROR(
-                            "Total vertex output components count exceeds limits");
-                    }
+                    DAWN_INVALID_IF(
+                        totalInterStageShaderComponents > kMaxInterStageShaderComponents,
+                        "Total vertex output components count (%u) exceeds the maximum (%u).",
+                        totalInterStageShaderComponents, kMaxInterStageShaderComponents);
                 }
 
                 if (metadata->stage == SingleShaderStage::Fragment) {
                     uint32_t totalInterStageShaderComponents = 0;
-                    for (const auto& input_var : entryPoint.input_variables) {
-                        if (!input_var.has_location_decoration) {
-                            return DAWN_VALIDATION_ERROR(
-                                "Need location decoration on fragment input");
-                        }
-                        uint32_t location = input_var.location_decoration;
-                        if (DAWN_UNLIKELY(location > kMaxInterStageShaderLocation)) {
-                            std::stringstream ss;
-                            ss << "Fragment input location (" << location << ") over limits";
-                            return DAWN_VALIDATION_ERROR(ss.str());
-                        }
+                    for (const auto& inputVar : entryPoint.input_variables) {
+                        DAWN_INVALID_IF(
+                            !inputVar.has_location_decoration,
+                            "Fragment input variable \"%s\" doesn't have a location decoration.",
+                            inputVar.name);
+
+                        uint32_t location = inputVar.location_decoration;
+                        DAWN_INVALID_IF(location > kMaxInterStageShaderLocation,
+                                        "Fragment input variable \"%s\" has a location (%u) that "
+                                        "exceeds the maximum (%u).",
+                                        inputVar.name, location, kMaxInterStageShaderLocation);
+
                         metadata->usedInterStageVariables.set(location);
                         DAWN_TRY_ASSIGN(
                             metadata->interStageVariables[location].baseType,
-                            TintComponentTypeToInterStageComponentType(input_var.component_type));
+                            TintComponentTypeToInterStageComponentType(inputVar.component_type));
                         DAWN_TRY_ASSIGN(metadata->interStageVariables[location].componentCount,
                                         TintCompositionTypeToInterStageComponentCount(
-                                            input_var.composition_type));
+                                            inputVar.composition_type));
                         DAWN_TRY_ASSIGN(
                             metadata->interStageVariables[location].interpolationType,
-                            TintInterpolationTypeToInterpolationType(input_var.interpolation_type));
+                            TintInterpolationTypeToInterpolationType(inputVar.interpolation_type));
                         DAWN_TRY_ASSIGN(
                             metadata->interStageVariables[location].interpolationSampling,
                             TintInterpolationSamplingToInterpolationSamplingType(
-                                input_var.interpolation_sampling));
+                                inputVar.interpolation_sampling));
 
                         totalInterStageShaderComponents +=
                             metadata->interStageVariables[location].componentCount;
                     }
+
                     if (entryPoint.front_facing_used) {
                         totalInterStageShaderComponents += 1;
                     }
@@ -815,32 +786,34 @@
                     if (entryPoint.input_position_used) {
                         totalInterStageShaderComponents += 4;
                     }
-                    if (totalInterStageShaderComponents > kMaxInterStageShaderComponents) {
-                        return DAWN_VALIDATION_ERROR(
-                            "Total fragment input components count exceeds limits");
-                    }
 
-                    for (const auto& output_var : entryPoint.output_variables) {
-                        if (!output_var.has_location_decoration) {
-                            return DAWN_VALIDATION_ERROR(
-                                "Need location decoration on fragment output");
-                        }
+                    DAWN_INVALID_IF(
+                        totalInterStageShaderComponents > kMaxInterStageShaderComponents,
+                        "Total fragment input components count (%u) exceeds the maximum (%u).",
+                        totalInterStageShaderComponents, kMaxInterStageShaderComponents);
 
-                        uint32_t unsanitizedAttachment = output_var.location_decoration;
-                        if (unsanitizedAttachment >= kMaxColorAttachments) {
-                            return DAWN_VALIDATION_ERROR(
-                                "Fragment output index must be less than max number of color "
-                                "attachments");
-                        }
+                    for (const auto& outputVar : entryPoint.output_variables) {
+                        DAWN_INVALID_IF(
+                            !outputVar.has_location_decoration,
+                            "Fragment input variable \"%s\" doesn't have a location decoration.",
+                            outputVar.name);
+
+                        uint32_t unsanitizedAttachment = outputVar.location_decoration;
+                        DAWN_INVALID_IF(unsanitizedAttachment >= kMaxColorAttachments,
+                                        "Fragment output variable \"%s\" has a location (%u) that "
+                                        "exceeds the maximum (%u).",
+                                        outputVar.name, unsanitizedAttachment,
+                                        kMaxColorAttachments);
                         ColorAttachmentIndex attachment(
                             static_cast<uint8_t>(unsanitizedAttachment));
+
                         DAWN_TRY_ASSIGN(
                             metadata->fragmentOutputVariables[attachment].baseType,
-                            TintComponentTypeToTextureComponentType(output_var.component_type));
+                            TintComponentTypeToTextureComponentType(outputVar.component_type));
                         uint32_t componentCount;
                         DAWN_TRY_ASSIGN(componentCount,
                                         TintCompositionTypeToInterStageComponentCount(
-                                            output_var.composition_type));
+                                            outputVar.composition_type));
                         // componentCount should be no larger than 4u
                         ASSERT(componentCount <= 4u);
                         metadata->fragmentOutputVariables[attachment].componentCount =
@@ -851,17 +824,20 @@
 
                 for (const tint::inspector::ResourceBinding& resource :
                      inspector.GetResourceBindings(entryPoint.name)) {
+                    DAWN_INVALID_IF(resource.bind_group >= kMaxBindGroups,
+                                    "The entry-point uses a binding with a group decoration (%u) "
+                                    "that exceeds the maximum (%u).",
+                                    resource.bind_group, kMaxBindGroups);
+
                     BindingNumber bindingNumber(resource.binding);
                     BindGroupIndex bindGroupIndex(resource.bind_group);
-                    if (bindGroupIndex >= kMaxBindGroupsTyped) {
-                        return DAWN_VALIDATION_ERROR("Shader has bind group index over limits");
-                    }
 
                     const auto& it = metadata->bindings[bindGroupIndex].emplace(
                         bindingNumber, ShaderBindingInfo{});
-                    if (!it.second) {
-                        return DAWN_VALIDATION_ERROR("Shader has duplicate bindings");
-                    }
+                    DAWN_INVALID_IF(
+                        !it.second,
+                        "Entry-point has a duplicate binding for (group:%u, binding:%u).",
+                        resource.binding, resource.bind_group);
 
                     ShaderBindingInfo* info = &it.first->second;
                     info->bindingType = TintResourceTypeToBindingInfoType(resource.resource_type);
@@ -974,9 +950,9 @@
         ASSERT(parseResult != nullptr);
 
         const ChainedStruct* chainedDescriptor = descriptor->nextInChain;
-        if (chainedDescriptor == nullptr) {
-            return DAWN_VALIDATION_ERROR("Shader module descriptor missing chained descriptor");
-        }
+        DAWN_INVALID_IF(chainedDescriptor == nullptr,
+                        "Shader module descriptor missing chained descriptor");
+
         // For now only a single SPIRV or WGSL subdescriptor is allowed.
         DAWN_TRY(ValidateSingleSType(chainedDescriptor, wgpu::SType::ShaderModuleSPIRVDescriptor,
                                      wgpu::SType::ShaderModuleWGSLDescriptor));
@@ -999,12 +975,7 @@
 
             tint::writer::wgsl::Options options;
             auto result = tint::writer::wgsl::Generate(&program, options);
-            if (!result.success) {
-                std::ostringstream errorStream;
-                errorStream << "Tint WGSL failure:" << std::endl;
-                errorStream << "Generator: " << result.error << std::endl;
-                return DAWN_VALIDATION_ERROR(errorStream.str().c_str());
-            }
+            DAWN_INVALID_IF(!result.success, "Tint WGSL failure: Generator: %s", result.error);
 
             newWgslCode = std::move(result.wgsl);
             newWgslDesc.source = newWgslCode.c_str();
@@ -1014,9 +985,8 @@
         }
 
         if (spirvDesc) {
-            if (device->IsToggleEnabled(Toggle::DisallowSpirv)) {
-                return DAWN_VALIDATION_ERROR("SPIR-V is disallowed.");
-            }
+            DAWN_INVALID_IF(device->IsToggleEnabled(Toggle::DisallowSpirv),
+                            "SPIR-V is disallowed.");
 
             std::vector<uint32_t> spirv(spirvDesc->code, spirvDesc->code + spirvDesc->codeSize);
             tint::Program program;
@@ -1060,10 +1030,8 @@
         if (outMessages != nullptr) {
             outMessages->AddMessages(output.program.Diagnostics());
         }
-        if (!output.program.IsValid()) {
-            std::string err = "Tint program failure: " + output.program.Diagnostics().str();
-            return DAWN_VALIDATION_ERROR(err.c_str());
-        }
+        DAWN_INVALID_IF(!output.program.IsValid(), "Tint program failure: %s\n",
+                        output.program.Diagnostics().str());
         if (outputs != nullptr) {
             *outputs = std::move(output.data);
         }
@@ -1107,17 +1075,17 @@
                                                        const EntryPointMetadata& entryPoint,
                                                        const PipelineLayoutBase* layout) {
         for (BindGroupIndex group : IterateBitSet(layout->GetBindGroupLayoutsMask())) {
-            DAWN_TRY(ValidateCompatibilityWithBindGroupLayout(device, group, entryPoint,
-                                                              layout->GetBindGroupLayout(group)));
+            DAWN_TRY_CONTEXT(ValidateCompatibilityWithBindGroupLayout(
+                                 device, group, entryPoint, layout->GetBindGroupLayout(group)),
+                             "validating the entry-point's compatibility for group %u with %s",
+                             static_cast<uint32_t>(group), layout->GetBindGroupLayout(group));
         }
 
         for (BindGroupIndex group : IterateBitSet(~layout->GetBindGroupLayoutsMask())) {
-            if (entryPoint.bindings[group].size() > 0) {
-                std::ostringstream ostream;
-                ostream << "No bind group layout entry matches the declaration set "
-                        << static_cast<uint32_t>(group) << " in the shader module";
-                return DAWN_VALIDATION_ERROR(ostream.str());
-            }
+            DAWN_INVALID_IF(entryPoint.bindings[group].size() > 0,
+                            "The entry-point uses bindings in group %u but %s doesn't have a "
+                            "BindGroupLayout for this index",
+                            static_cast<uint32_t>(group), layout);
         }
 
         // Validate that filtering samplers are not used with unfilterable textures.
@@ -1150,11 +1118,15 @@
                    textureInfo.texture.sampleType != wgpu::TextureSampleType::Uint &&
                    textureInfo.texture.sampleType != wgpu::TextureSampleType::Sint);
 
-            if (textureInfo.texture.sampleType == wgpu::TextureSampleType::UnfilterableFloat) {
-                return DAWN_VALIDATION_ERROR(
-                    "unfilterable-float texture bindings cannot be sampled with a "
-                    "filtering sampler");
-            }
+            DAWN_INVALID_IF(
+                textureInfo.texture.sampleType == wgpu::TextureSampleType::UnfilterableFloat,
+                "Texture binding (group:%u, binding:%u) is %s but used statically with a sampler "
+                "(group:%u, binding:%u) that's %s",
+                static_cast<uint32_t>(pair.texture.group),
+                static_cast<uint32_t>(pair.texture.binding),
+                wgpu::TextureSampleType::UnfilterableFloat,
+                static_cast<uint32_t>(pair.sampler.group),
+                static_cast<uint32_t>(pair.sampler.binding), wgpu::SamplerBindingType::Filtering);
         }
 
         return {};