Add validation for pipeline overrides value type error for i32/u32/f32

Follow WebIDL TypeError for float and EnforceRange. Generate
a validation error is the number is not representable.

Bug: dawn:1597
Change-Id: I9a683f65ed0bfadb936d5de358670b01a2036848
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/112920
Reviewed-by: Austin Eng <enga@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Shrek Shao <shrekshao@google.com>
diff --git a/src/dawn/common/Numeric.h b/src/dawn/common/Numeric.h
index f4b084b..6387f48 100644
--- a/src/dawn/common/Numeric.h
+++ b/src/dawn/common/Numeric.h
@@ -51,4 +51,18 @@
     return static_cast<Dst>(value);
 }
 
+template <typename T>
+bool IsDoubleValueRepresentable(double value) {
+    if constexpr (std::is_same_v<T, float> || std::is_integral_v<T>) {
+        // Following WebIDL 3.3.6.[EnforceRange] for integral
+        // Following WebIDL 3.2.5.float for float
+        // TODO(crbug.com/1396194): now follows what blink does but may need revisit.
+        constexpr double kLowest = static_cast<double>(std::numeric_limits<T>::lowest());
+        constexpr double kMax = static_cast<double>(std::numeric_limits<T>::max());
+        return kLowest <= value && value <= kMax;
+    } else {
+        static_assert(sizeof(T) != sizeof(T), "Unsupported type");
+    }
+}
+
 #endif  // SRC_DAWN_COMMON_NUMERIC_H_
diff --git a/src/dawn/native/ComputePipeline.cpp b/src/dawn/native/ComputePipeline.cpp
index ef84b2c..a4b669b 100644
--- a/src/dawn/native/ComputePipeline.cpp
+++ b/src/dawn/native/ComputePipeline.cpp
@@ -30,10 +30,13 @@
         DAWN_TRY(device->ValidateObject(descriptor->layout));
     }
 
-    return ValidateProgrammableStage(
-        device, descriptor->compute.module, descriptor->compute.entryPoint,
-        descriptor->compute.constantCount, descriptor->compute.constants, descriptor->layout,
-        SingleShaderStage::Compute);
+    DAWN_TRY_CONTEXT(ValidateProgrammableStage(
+                         device, descriptor->compute.module, descriptor->compute.entryPoint,
+                         descriptor->compute.constantCount, descriptor->compute.constants,
+                         descriptor->layout, SingleShaderStage::Compute),
+                     "validating compute stage (%s, entryPoint: %s).", descriptor->compute.module,
+                     descriptor->compute.entryPoint);
+    return {};
 }
 
 // ComputePipelineBase
diff --git a/src/dawn/native/Pipeline.cpp b/src/dawn/native/Pipeline.cpp
index 443ae3d..581472d 100644
--- a/src/dawn/native/Pipeline.cpp
+++ b/src/dawn/native/Pipeline.cpp
@@ -68,8 +68,38 @@
                         "Pipeline overridable constant \"%s\" not found in %s.", constants[i].key,
                         module);
         DAWN_INVALID_IF(!std::isfinite(constants[i].value),
-                        "Pipeline overridable constant \"%s\" with value (%f) is not finite",
-                        constants[i].key, constants[i].value);
+                        "Pipeline overridable constant \"%s\" with value (%f) is not finite in %s",
+                        constants[i].key, constants[i].value, module);
+
+        // Validate if constant value can be represented by the given scalar type in shader
+        auto type = metadata.overrides.at(constants[i].key).type;
+        switch (type) {
+            case EntryPointMetadata::Override::Type::Float32:
+                DAWN_INVALID_IF(!IsDoubleValueRepresentable<float>(constants[i].value),
+                                "Pipeline overridable constant \"%s\" with value (%f) is not "
+                                "representable in type (%s)",
+                                constants[i].key, constants[i].value, "f32");
+                break;
+            case EntryPointMetadata::Override::Type::Int32:
+                DAWN_INVALID_IF(!IsDoubleValueRepresentable<int32_t>(constants[i].value),
+                                "Pipeline overridable constant \"%s\" with value (%f) is not "
+                                "representable in type (%s)",
+                                constants[i].key, constants[i].value,
+                                type == EntryPointMetadata::Override::Type::Int32 ? "i32" : "b");
+                break;
+            case EntryPointMetadata::Override::Type::Uint32:
+                DAWN_INVALID_IF(!IsDoubleValueRepresentable<uint32_t>(constants[i].value),
+                                "Pipeline overridable constant \"%s\" with value (%f) is not "
+                                "representable in type (%s)",
+                                constants[i].key, constants[i].value, "u32");
+                break;
+            case EntryPointMetadata::Override::Type::Boolean:
+                // Conversion to boolean can't fail
+                // https://webidl.spec.whatwg.org/#es-boolean
+                break;
+            default:
+                UNREACHABLE();
+        }
 
         if (stageInitializedConstantIdentifiers.count(constants[i].key) == 0) {
             if (metadata.uninitializedOverrides.count(constants[i].key) > 0) {
@@ -79,8 +109,7 @@
         } else {
             // There are duplicate initializations
             return DAWN_VALIDATION_ERROR(
-                "Pipeline overridable constants \"%s\" is set more than once in %s",
-                constants[i].key, module);
+                "Pipeline overridable constants \"%s\" is set more than once", constants[i].key);
         }
     }
 
@@ -102,9 +131,9 @@
         }
 
         return DAWN_VALIDATION_ERROR(
-            "There are uninitialized pipeline overridable constants in shader module %s, their "
+            "There are uninitialized pipeline overridable constants, their "
             "identifiers:[%s]",
-            module, uninitializedConstantsArray);
+            uninitializedConstantsArray);
     }
 
     return {};
diff --git a/src/dawn/native/RenderPipeline.cpp b/src/dawn/native/RenderPipeline.cpp
index baa6c85..9b0ff96 100644
--- a/src/dawn/native/RenderPipeline.cpp
+++ b/src/dawn/native/RenderPipeline.cpp
@@ -128,7 +128,7 @@
     DAWN_TRY_CONTEXT(ValidateProgrammableStage(device, descriptor->module, descriptor->entryPoint,
                                                descriptor->constantCount, descriptor->constants,
                                                layout, SingleShaderStage::Vertex),
-                     "validating vertex stage (module: %s, entryPoint: %s).", descriptor->module,
+                     "validating vertex stage (%s, entryPoint: %s).", descriptor->module,
                      descriptor->entryPoint);
     const EntryPointMetadata& vertexMetadata =
         descriptor->module->GetEntryPoint(descriptor->entryPoint);
@@ -341,7 +341,7 @@
     DAWN_TRY_CONTEXT(ValidateProgrammableStage(device, descriptor->module, descriptor->entryPoint,
                                                descriptor->constantCount, descriptor->constants,
                                                layout, SingleShaderStage::Fragment),
-                     "validating fragment stage (module: %s, entryPoint: %s).", descriptor->module,
+                     "validating fragment stage (%s, entryPoint: %s).", descriptor->module,
                      descriptor->entryPoint);
 
     uint32_t maxColorAttachments = device->GetLimits().v1.maxColorAttachments;
diff --git a/src/dawn/native/ShaderModule.cpp b/src/dawn/native/ShaderModule.cpp
index b05f1b8..1ca7c7c 100644
--- a/src/dawn/native/ShaderModule.cpp
+++ b/src/dawn/native/ShaderModule.cpp
@@ -533,24 +533,8 @@
 
         for (auto& c : entryPoint.overrides) {
             auto id = name2Id.at(c.name);
-            OverrideScalar defaultValue;
-            if (c.is_initialized) {
-                // if it is initialized, the scalar must exist
-                const auto& scalar = id2Scalar.at(id);
-                if (scalar.IsBool()) {
-                    defaultValue.b = scalar.AsBool();
-                } else if (scalar.IsU32()) {
-                    defaultValue.u32 = scalar.AsU32();
-                } else if (scalar.IsI32()) {
-                    defaultValue.i32 = scalar.AsI32();
-                } else if (scalar.IsFloat()) {
-                    defaultValue.f32 = scalar.AsFloat();
-                } else {
-                    UNREACHABLE();
-                }
-            }
             EntryPointMetadata::Override override = {id, FromTintOverrideType(c.type),
-                                                     c.is_initialized, defaultValue};
+                                                     c.is_initialized};
 
             std::string identifier = c.is_id_specified ? std::to_string(override.id.value) : c.name;
             metadata->overrides[identifier] = override;
diff --git a/src/dawn/native/ShaderModule.h b/src/dawn/native/ShaderModule.h
index a1f34ac..af63e85 100644
--- a/src/dawn/native/ShaderModule.h
+++ b/src/dawn/native/ShaderModule.h
@@ -227,11 +227,6 @@
         // Then it is required for the pipeline stage to have a constant record to initialize a
         // value
         bool isInitialized;
-
-        // Store the default initialized value in shader
-        // This is used by metal backend as the function_constant does not have dafault values
-        // Initialized when isInitialized == true
-        OverrideScalar defaultValue;
     };
 
     using OverridesMap = std::unordered_map<std::string, Override>;
diff --git a/src/dawn/tests/unittests/validation/OverridableConstantsValidationTests.cpp b/src/dawn/tests/unittests/validation/OverridableConstantsValidationTests.cpp
index f7c061f..542bb06 100644
--- a/src/dawn/tests/unittests/validation/OverridableConstantsValidationTests.cpp
+++ b/src/dawn/tests/unittests/validation/OverridableConstantsValidationTests.cpp
@@ -223,31 +223,72 @@
 TEST_F(ComputePipelineOverridableConstantsValidationTest, InvalidValue) {
     SetUpShadersWithDefaultValueConstants();
     {
-        // Error:: NaN
+        // Error: NaN
         std::vector<wgpu::ConstantEntry> constants{{nullptr, "c3", std::nan("")}};
         ASSERT_DEVICE_ERROR(TestCreatePipeline(constants));
     }
     {
-        // Error:: -NaN
+        // Error: -NaN
         std::vector<wgpu::ConstantEntry> constants{{nullptr, "c3", -std::nan("")}};
         ASSERT_DEVICE_ERROR(TestCreatePipeline(constants));
     }
     {
-        // Error:: Inf
+        // Error: Inf
         std::vector<wgpu::ConstantEntry> constants{
             {nullptr, "c3", std::numeric_limits<double>::infinity()}};
         ASSERT_DEVICE_ERROR(TestCreatePipeline(constants));
     }
     {
-        // Error:: -Inf
+        // Error: -Inf
         std::vector<wgpu::ConstantEntry> constants{
             {nullptr, "c3", -std::numeric_limits<double>::infinity()}};
         ASSERT_DEVICE_ERROR(TestCreatePipeline(constants));
     }
     {
-        // Valid:: Max
+        // Valid: Max
         std::vector<wgpu::ConstantEntry> constants{
             {nullptr, "c3", std::numeric_limits<float>::max()}};
         TestCreatePipeline(constants);
     }
+    {
+        // Valid: Lowest
+        std::vector<wgpu::ConstantEntry> constants{
+            {nullptr, "c3", std::numeric_limits<float>::lowest()}};
+        TestCreatePipeline(constants);
+    }
+}
+
+// Test that values that are not representable by WGSL type i32/u32/f16/f32
+TEST_F(ComputePipelineOverridableConstantsValidationTest, OutofRangeValue) {
+    SetUpShadersWithDefaultValueConstants();
+    {
+        // Error: 1.79769e+308 cannot be represented by f32
+        std::vector<wgpu::ConstantEntry> constants{
+            {nullptr, "c3", std::numeric_limits<double>::max()}};
+        ASSERT_DEVICE_ERROR(TestCreatePipeline(constants));
+    }
+    {
+        // Error: i32 out of range
+        std::vector<wgpu::ConstantEntry> constants{
+            {nullptr, "c5", static_cast<double>(std::numeric_limits<int32_t>::max()) + 1.0}};
+        ASSERT_DEVICE_ERROR(TestCreatePipeline(constants));
+    }
+    {
+        // Error: i32 out of range (negative)
+        std::vector<wgpu::ConstantEntry> constants{
+            {nullptr, "c5", static_cast<double>(std::numeric_limits<int32_t>::lowest()) - 1.0}};
+        ASSERT_DEVICE_ERROR(TestCreatePipeline(constants));
+    }
+    {
+        // Error: u32 out of range
+        std::vector<wgpu::ConstantEntry> constants{
+            {nullptr, "c8", static_cast<double>(std::numeric_limits<uint32_t>::max()) + 1.0}};
+        ASSERT_DEVICE_ERROR(TestCreatePipeline(constants));
+    }
+    {
+        // Valid: conversion to boolean can't fail
+        std::vector<wgpu::ConstantEntry> constants{
+            {nullptr, "c0", static_cast<double>(std::numeric_limits<int32_t>::max()) + 1.0}};
+        TestCreatePipeline(constants);
+    }
 }