Validate decorations from SPV_KHR_no_integer_wrap (#2271)

Validates NoSignedWrap, NoUnsignedWrap.

We are permissive by allowing any extended instruction.
diff --git a/source/val/validate_decorations.cpp b/source/val/validate_decorations.cpp
index 730b7ad..7f150aa 100644
--- a/source/val/validate_decorations.cpp
+++ b/source/val/validate_decorations.cpp
@@ -1180,6 +1180,36 @@
   return SPV_SUCCESS;
 }
 
+// Returns SPV_SUCCESS if validation rules are satisfied for NoSignedWrap or
+// NoUnsignedWrap decorations. Otherwise emits a diagnostic and returns
+// something other than SPV_SUCCESS. Assumes each decoration on a group has been
+// propagated down to the group members.
+spv_result_t CheckIntegerWrapDecoration(ValidationState_t& vstate,
+                                        const Instruction& inst,
+                                        const Decoration& decoration) {
+  switch (inst.opcode()) {
+    case SpvOpIAdd:
+    case SpvOpISub:
+    case SpvOpIMul:
+    case SpvOpShiftLeftLogical:
+    case SpvOpSNegate:
+      return SPV_SUCCESS;
+    case SpvOpExtInst:
+      // TODO(dneto): Only certain extended instructions allow these
+      // decorations.  For now allow anything.
+      return SPV_SUCCESS;
+    default:
+      break;
+  }
+
+  return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
+         << (decoration.dec_type() == SpvDecorationNoSignedWrap
+                 ? "NoSignedWrap"
+                 : "NoUnsignedWrap")
+         << " decoration may not be applied to "
+         << spvOpcodeString(inst.opcode());
+}
+
 #define PASS_OR_BAIL_AT_LINE(X, LINE)           \
   {                                             \
     spv_result_t e##LINE = (X);                 \
@@ -1216,6 +1246,10 @@
         case SpvDecorationUniform:
           PASS_OR_BAIL(CheckUniformDecoration(vstate, *inst, decoration));
           break;
+        case SpvDecorationNoSignedWrap:
+        case SpvDecorationNoUnsignedWrap:
+          PASS_OR_BAIL(CheckIntegerWrapDecoration(vstate, *inst, decoration));
+          break;
         default:
           break;
       }
diff --git a/test/val/val_decoration_test.cpp b/test/val/val_decoration_test.cpp
index 2f824c4..827ebf1 100644
--- a/test/val/val_decoration_test.cpp
+++ b/test/val/val_decoration_test.cpp
@@ -4810,6 +4810,282 @@
           "ID '2' decorated with both BufferBlock and Block is not allowed."));
 }
 
+std::string MakeIntegerShader(
+    const std::string& decoration, const std::string& inst,
+    const std::string& extension =
+        "OpExtension \"SPV_KHR_no_integer_wrap_decoration\"") {
+  return R"(
+OpCapability Shader
+OpCapability Linkage
+)" + extension +
+         R"(
+%glsl = OpExtInstImport "GLSL.std.450"
+%opencl = OpExtInstImport "OpenCL.std"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpName %entry "entry"
+)" + decoration +
+         R"(
+    %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+     %int = OpTypeInt 32 1
+    %zero = OpConstantNull %int
+   %float = OpTypeFloat 32
+  %float0 = OpConstantNull %float
+    %main = OpFunction %void None %voidfn
+   %entry = OpLabel
+)" + inst +
+         R"(
+OpReturn
+OpFunctionEnd)";
+}
+
+// NoSignedWrap
+
+TEST_F(ValidateDecorations, NoSignedWrapOnTypeBad) {
+  std::string spirv = MakeIntegerShader("OpDecorate %void NoSignedWrap", "");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("NoSignedWrap decoration may not be applied to TypeVoid"));
+}
+
+TEST_F(ValidateDecorations, NoSignedWrapOnLabelBad) {
+  std::string spirv = MakeIntegerShader("OpDecorate %entry NoSignedWrap", "");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("NoSignedWrap decoration may not be applied to Label"));
+}
+
+TEST_F(ValidateDecorations, NoSignedWrapRequiresExtensionBad) {
+  std::string spirv = MakeIntegerShader("OpDecorate %val NoSignedWrap",
+                                        "%val = OpIAdd %int %zero %zero", "");
+
+  CompileSuccessfully(spirv);
+  EXPECT_NE(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("requires one of these extensions: "
+                        "SPV_KHR_no_integer_wrap_decoration"));
+}
+
+TEST_F(ValidateDecorations, NoSignedWrapIAddGood) {
+  std::string spirv = MakeIntegerShader("OpDecorate %val NoSignedWrap",
+                                        "%val = OpIAdd %int %zero %zero");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NoSignedWrapISubGood) {
+  std::string spirv = MakeIntegerShader("OpDecorate %val NoSignedWrap",
+                                        "%val = OpISub %int %zero %zero");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NoSignedWrapIMulGood) {
+  std::string spirv = MakeIntegerShader("OpDecorate %val NoSignedWrap",
+                                        "%val = OpIMul %int %zero %zero");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NoSignedWrapShiftLeftLogicalGood) {
+  std::string spirv =
+      MakeIntegerShader("OpDecorate %val NoSignedWrap",
+                        "%val = OpShiftLeftLogical %int %zero %zero");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NoSignedWrapSNegateGood) {
+  std::string spirv = MakeIntegerShader("OpDecorate %val NoSignedWrap",
+                                        "%val = OpSNegate %int %zero");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NoSignedWrapSRemBad) {
+  std::string spirv = MakeIntegerShader("OpDecorate %val NoSignedWrap",
+                                        "%val = OpSRem %int %zero %zero");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("NoSignedWrap decoration may not be applied to SRem"));
+}
+
+TEST_F(ValidateDecorations, NoSignedWrapFAddBad) {
+  std::string spirv = MakeIntegerShader("OpDecorate %val NoSignedWrap",
+                                        "%val = OpFAdd %float %float0 %float0");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("NoSignedWrap decoration may not be applied to FAdd"));
+}
+
+TEST_F(ValidateDecorations, NoSignedWrapExtInstOpenCLGood) {
+  std::string spirv =
+      MakeIntegerShader("OpDecorate %val NoSignedWrap",
+                        "%val = OpExtInst %int %opencl s_abs %zero");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NoSignedWrapExtInstGLSLGood) {
+  std::string spirv = MakeIntegerShader(
+      "OpDecorate %val NoSignedWrap", "%val = OpExtInst %int %glsl SAbs %zero");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+// TODO(dneto): For NoSignedWrap and NoUnsignedWrap, permit
+// "OpExtInst for instruction numbers specified in the extended
+// instruction-set specifications as accepting this decoration."
+
+// NoUnignedWrap
+
+TEST_F(ValidateDecorations, NoUnsignedWrapOnTypeBad) {
+  std::string spirv = MakeIntegerShader("OpDecorate %void NoUnsignedWrap", "");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("NoUnsignedWrap decoration may not be applied to TypeVoid"));
+}
+
+TEST_F(ValidateDecorations, NoUnsignedWrapOnLabelBad) {
+  std::string spirv = MakeIntegerShader("OpDecorate %entry NoUnsignedWrap", "");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("NoUnsignedWrap decoration may not be applied to Label"));
+}
+
+TEST_F(ValidateDecorations, NoUnsignedWrapRequiresExtensionBad) {
+  std::string spirv = MakeIntegerShader("OpDecorate %val NoUnsignedWrap",
+                                        "%val = OpIAdd %int %zero %zero", "");
+
+  CompileSuccessfully(spirv);
+  EXPECT_NE(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("requires one of these extensions: "
+                        "SPV_KHR_no_integer_wrap_decoration"));
+}
+
+TEST_F(ValidateDecorations, NoUnsignedWrapIAddGood) {
+  std::string spirv = MakeIntegerShader("OpDecorate %val NoUnsignedWrap",
+                                        "%val = OpIAdd %int %zero %zero");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NoUnsignedWrapISubGood) {
+  std::string spirv = MakeIntegerShader("OpDecorate %val NoUnsignedWrap",
+                                        "%val = OpISub %int %zero %zero");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NoUnsignedWrapIMulGood) {
+  std::string spirv = MakeIntegerShader("OpDecorate %val NoUnsignedWrap",
+                                        "%val = OpIMul %int %zero %zero");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NoUnsignedWrapShiftLeftLogicalGood) {
+  std::string spirv =
+      MakeIntegerShader("OpDecorate %val NoUnsignedWrap",
+                        "%val = OpShiftLeftLogical %int %zero %zero");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NoUnsignedWrapSNegateGood) {
+  std::string spirv = MakeIntegerShader("OpDecorate %val NoUnsignedWrap",
+                                        "%val = OpSNegate %int %zero");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NoUnsignedWrapSRemBad) {
+  std::string spirv = MakeIntegerShader("OpDecorate %val NoUnsignedWrap",
+                                        "%val = OpSRem %int %zero %zero");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("NoUnsignedWrap decoration may not be applied to SRem"));
+}
+
+TEST_F(ValidateDecorations, NoUnsignedWrapFAddBad) {
+  std::string spirv = MakeIntegerShader("OpDecorate %val NoUnsignedWrap",
+                                        "%val = OpFAdd %float %float0 %float0");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("NoUnsignedWrap decoration may not be applied to FAdd"));
+}
+
+TEST_F(ValidateDecorations, NoUnsignedWrapExtInstOpenCLGood) {
+  std::string spirv =
+      MakeIntegerShader("OpDecorate %val NoUnsignedWrap",
+                        "%val = OpExtInst %int %opencl s_abs %zero");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NoUnsignedWrapExtInstGLSLGood) {
+  std::string spirv =
+      MakeIntegerShader("OpDecorate %val NoUnsignedWrap",
+                        "%val = OpExtInst %int %glsl SAbs %zero");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+// TODO(dneto): For NoUnsignedWrap and NoUnsignedWrap, permit
+// "OpExtInst for instruction numbers specified in the extended
+// instruction-set specifications as accepting this decoration."
+
 TEST_F(ValidateDecorations, PSBAliasedRestrictPointerSuccess) {
   const std::string body = R"(
 OpCapability PhysicalStorageBufferAddressesEXT
@@ -4952,10 +5228,9 @@
 
   CompileSuccessfully(body.c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr(
-          "expected Aliased or Restrict for PhysicalStorageBufferEXT pointer"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected Aliased or Restrict for "
+                        "PhysicalStorageBufferEXT pointer"));
 }
 
 TEST_F(ValidateDecorations, PSBAliasedRestrictFunctionParamBoth) {