[Tint] Validate partial eval of divby0

Validate against division by zero for cases where only the rhs is an
integral type and can be constant-evaluated.

Related CTS fixes: https://github.com/gpuweb/cts/pull/3873

Fixes: 42251274
Change-Id: I43498423a1826f0f16707587fc9c5f47b68dadaf
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/199776
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Commit-Queue: Natalie Chouinard <chouinard@google.com>
diff --git a/src/tint/lang/core/constant/eval_binary_op_test.cc b/src/tint/lang/core/constant/eval_binary_op_test.cc
index 520791d..812d633 100644
--- a/src/tint/lang/core/constant/eval_binary_op_test.cc
+++ b/src/tint/lang/core/constant/eval_binary_op_test.cc
@@ -454,9 +454,9 @@
         C(T{0}, T::Highest(), T{0}),
 
         // Divide by zero
-        E(T{123}, T{0}, error_msg(T{123}, T{0})),
-        E(T::Highest(), T{0}, error_msg(T::Highest(), T{0})),
-        E(T::Lowest(), T{0}, error_msg(T::Lowest(), T{0})),
+        E(T{123}, T{0}, "12:34 error: integer division by zero is invalid"),
+        E(T::Highest(), T{0}, "12:34 error: integer division by zero is invalid"),
+        E(T::Lowest(), T{0}, "12:34 error: integer division by zero is invalid"),
     };
 
     // Error on most negative divided by -1
@@ -526,9 +526,15 @@
         C(T{10}, T{10}, T{0}),  //
 
         // Error on divide by zero
-        E(T{123}, T{0}, error_msg(T{123}, T{0})),
-        E(T::Highest(), T{0}, error_msg(T::Highest(), T{0})),
-        E(T::Lowest(), T{0}, error_msg(T::Lowest(), T{0})),
+        E(T{123}, T{0},
+          IsIntegral<T> ? "12:34 error: integer division by zero is invalid"
+                        : error_msg(T{123}, T{0})),
+        E(T::Highest(), T{0},
+          IsIntegral<T> ? "12:34 error: integer division by zero is invalid"
+                        : error_msg(T::Highest(), T{0})),
+        E(T::Lowest(), T{0},
+          IsIntegral<T> ? "12:34 error: integer division by zero is invalid"
+                        : error_msg(T::Lowest(), T{0})),
     };
 
     if constexpr (IsIntegral<T>) {
@@ -1564,7 +1570,7 @@
     GlobalConst("result", binary);
 
     EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(), "12:34 error: '2 / 0' cannot be represented as 'abstract-int'");
+    EXPECT_EQ(r()->error(), "12:34 error: integer division by zero is invalid");
 }
 
 TEST_F(ConstEvalTest, ShortCircuit_And_Error_Binary) {
@@ -1608,7 +1614,7 @@
     GlobalConst("result", binary);
 
     EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(), "12:34 error: '2 / 0' cannot be represented as 'abstract-int'");
+    EXPECT_EQ(r()->error(), "12:34 error: integer division by zero is invalid");
 }
 
 TEST_F(ConstEvalTest, ShortCircuit_Or_Error_Binary) {
diff --git a/src/tint/lang/wgsl/resolver/validator.cc b/src/tint/lang/wgsl/resolver/validator.cc
index f8b75f1..c7fc01f 100644
--- a/src/tint/lang/wgsl/resolver/validator.cc
+++ b/src/tint/lang/wgsl/resolver/validator.cc
@@ -1698,6 +1698,20 @@
             }
             return true;
         }
+        case core::BinaryOp::kDivide:
+        case core::BinaryOp::kModulo: {
+            // Integer division by zero should be checked for the partial evaluation case (only rhs
+            // is const). FP division by zero is only invalid when the whole expression is
+            // constant-evaluated.
+            if (rhs->Type()->is_integer_scalar_or_vector() &&
+                rhs->Stage() == core::EvaluationStage::kConstant) {
+                if (rhs->ConstantValue()->AnyZero()) {
+                    AddError(node->source) << "integer division by zero is invalid";
+                    return false;
+                }
+            }
+            return true;
+        }
         default: {
             return true;
         }
diff --git a/test/tint/statements/compound_assign/divide_by_zero.wgsl b/test/tint/statements/compound_assign/divide_by_zero.wgsl
index 7ff3454..5e340f9 100644
--- a/test/tint/statements/compound_assign/divide_by_zero.wgsl
+++ b/test/tint/statements/compound_assign/divide_by_zero.wgsl
@@ -2,8 +2,6 @@
 var<private> b : f32;
 
 fn foo(maybe_zero : i32) {
-  a /= 0;
-  a %= 0;
   a /= maybe_zero;
   a %= maybe_zero;
 
diff --git a/test/tint/statements/compound_assign/divide_by_zero.wgsl.expected.dxc.hlsl b/test/tint/statements/compound_assign/divide_by_zero.wgsl.expected.dxc.hlsl
index aeb066f..2f80743 100644
--- a/test/tint/statements/compound_assign/divide_by_zero.wgsl.expected.dxc.hlsl
+++ b/test/tint/statements/compound_assign/divide_by_zero.wgsl.expected.dxc.hlsl
@@ -28,8 +28,6 @@
 }
 
 void foo(int maybe_zero) {
-  a = tint_div(a, 0);
-  a = tint_mod(a, 0);
   a = tint_div(a, maybe_zero);
   a = tint_mod(a, maybe_zero);
   b = (b / 0.0f);
diff --git a/test/tint/statements/compound_assign/divide_by_zero.wgsl.expected.fxc.hlsl b/test/tint/statements/compound_assign/divide_by_zero.wgsl.expected.fxc.hlsl
index aeb066f..2f80743 100644
--- a/test/tint/statements/compound_assign/divide_by_zero.wgsl.expected.fxc.hlsl
+++ b/test/tint/statements/compound_assign/divide_by_zero.wgsl.expected.fxc.hlsl
@@ -28,8 +28,6 @@
 }
 
 void foo(int maybe_zero) {
-  a = tint_div(a, 0);
-  a = tint_mod(a, 0);
   a = tint_div(a, maybe_zero);
   a = tint_mod(a, maybe_zero);
   b = (b / 0.0f);
diff --git a/test/tint/statements/compound_assign/divide_by_zero.wgsl.expected.glsl b/test/tint/statements/compound_assign/divide_by_zero.wgsl.expected.glsl
index 2bd98b6..68edcef 100644
--- a/test/tint/statements/compound_assign/divide_by_zero.wgsl.expected.glsl
+++ b/test/tint/statements/compound_assign/divide_by_zero.wgsl.expected.glsl
@@ -25,8 +25,6 @@
 }
 
 void foo(int maybe_zero) {
-  a = tint_div(a, 0);
-  a = tint_mod(a, 0);
   a = tint_div(a, maybe_zero);
   a = tint_mod(a, maybe_zero);
   b = (b / 0.0f);
diff --git a/test/tint/statements/compound_assign/divide_by_zero.wgsl.expected.msl b/test/tint/statements/compound_assign/divide_by_zero.wgsl.expected.msl
index 47a5e29..8133694 100644
--- a/test/tint/statements/compound_assign/divide_by_zero.wgsl.expected.msl
+++ b/test/tint/statements/compound_assign/divide_by_zero.wgsl.expected.msl
@@ -20,8 +20,6 @@
 }
 
 void foo(int maybe_zero, thread tint_private_vars_struct* const tint_private_vars) {
-  (*(tint_private_vars)).a = tint_div((*(tint_private_vars)).a, 0);
-  (*(tint_private_vars)).a = tint_mod((*(tint_private_vars)).a, 0);
   (*(tint_private_vars)).a = tint_div((*(tint_private_vars)).a, maybe_zero);
   (*(tint_private_vars)).a = tint_mod((*(tint_private_vars)).a, maybe_zero);
   (*(tint_private_vars)).b = ((*(tint_private_vars)).b / 0.0f);
diff --git a/test/tint/statements/compound_assign/divide_by_zero.wgsl.expected.spvasm b/test/tint/statements/compound_assign/divide_by_zero.wgsl.expected.spvasm
index c7ab06b..97dcaa7 100644
--- a/test/tint/statements/compound_assign/divide_by_zero.wgsl.expected.spvasm
+++ b/test/tint/statements/compound_assign/divide_by_zero.wgsl.expected.spvasm
@@ -1,7 +1,7 @@
 ; SPIR-V
 ; Version: 1.3
 ; Generator: Google Tint Compiler; 0
-; Bound: 76
+; Bound: 72
 ; Schema: 0
                OpCapability Shader
                OpMemoryModel Logical GLSL450
@@ -85,30 +85,24 @@
  %maybe_zero = OpFunctionParameter %int
          %57 = OpLabel
          %59 = OpLoad %int %a
-         %58 = OpFunctionCall %int %tint_div %59 %4
+         %58 = OpFunctionCall %int %tint_div %59 %maybe_zero
                OpStore %a %58
          %61 = OpLoad %int %a
-         %60 = OpFunctionCall %int %tint_mod %61 %4
+         %60 = OpFunctionCall %int %tint_mod %61 %maybe_zero
                OpStore %a %60
-         %63 = OpLoad %int %a
-         %62 = OpFunctionCall %int %tint_div %63 %maybe_zero
-               OpStore %a %62
-         %65 = OpLoad %int %a
-         %64 = OpFunctionCall %int %tint_mod %65 %maybe_zero
-               OpStore %a %64
+         %62 = OpLoad %float %b
+         %63 = OpFDiv %float %62 %8
+               OpStore %b %63
+         %64 = OpLoad %float %b
+         %65 = OpFRem %float %64 %8
+               OpStore %b %65
          %66 = OpLoad %float %b
-         %67 = OpFDiv %float %66 %8
-               OpStore %b %67
-         %68 = OpLoad %float %b
-         %69 = OpFRem %float %68 %8
-               OpStore %b %69
-         %70 = OpLoad %float %b
-         %71 = OpConvertSToF %float %maybe_zero
-         %72 = OpFDiv %float %70 %71
-               OpStore %b %72
-         %73 = OpLoad %float %b
-         %74 = OpConvertSToF %float %maybe_zero
-         %75 = OpFRem %float %73 %74
-               OpStore %b %75
+         %67 = OpConvertSToF %float %maybe_zero
+         %68 = OpFDiv %float %66 %67
+               OpStore %b %68
+         %69 = OpLoad %float %b
+         %70 = OpConvertSToF %float %maybe_zero
+         %71 = OpFRem %float %69 %70
+               OpStore %b %71
                OpReturn
                OpFunctionEnd
diff --git a/test/tint/statements/compound_assign/divide_by_zero.wgsl.expected.wgsl b/test/tint/statements/compound_assign/divide_by_zero.wgsl.expected.wgsl
index 6a6bc58..2a63fc2 100644
--- a/test/tint/statements/compound_assign/divide_by_zero.wgsl.expected.wgsl
+++ b/test/tint/statements/compound_assign/divide_by_zero.wgsl.expected.wgsl
@@ -3,8 +3,6 @@
 var<private> b : f32;
 
 fn foo(maybe_zero : i32) {
-  a /= 0;
-  a %= 0;
   a /= maybe_zero;
   a %= maybe_zero;
   b /= 0.0;