spirv-val: Add validation for 4-bit integer types (#6644)

Allow 4-bit integers with Int4TypeINTEL

Required for test in LLVM tree:
https://github.com/llvm/llvm-project/blob/6b0d268fe544b25fd1f82aad4e246f8a74e260ed/llvm/test/CodeGen/SPIRV/legalization/icmp_extended_int.ll

Extension ref:
https://github.com/KhronosGroup/SPIRV-Registry/blob/main/extensions/INTEL/SPV_INTEL_int4.asciidoc
diff --git a/source/val/validate_type.cpp b/source/val/validate_type.cpp
index 9ed0eab..ce8fc75 100644
--- a/source/val/validate_type.cpp
+++ b/source/val/validate_type.cpp
@@ -60,7 +60,15 @@
   // integers, respectively.
   auto num_bits = inst->GetOperandAs<const uint32_t>(1);
   if (num_bits != 32) {
-    if (num_bits == 8) {
+    if (num_bits == 4) {
+      if (_.HasCapability(spv::Capability::Int4TypeINTEL) ||
+          _.HasCapability(spv::Capability::ArbitraryPrecisionIntegersINTEL)) {
+        return SPV_SUCCESS;
+      }
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "Using a 4-bit integer type requires the Int4TypeINTEL "
+                "or ArbitraryPrecisionIntegersINTEL capability.";
+    } else if (num_bits == 8) {
       if (_.features().declare_int8_type) {
         return SPV_SUCCESS;
       }
diff --git a/test/val/val_data_test.cpp b/test/val/val_data_test.cpp
index 5b3e543..210e1d1 100644
--- a/test/val/val_data_test.cpp
+++ b/test/val/val_data_test.cpp
@@ -143,6 +143,9 @@
 std::string missing_cap_error =
     "requires the Vector16 or LongVectorEXT capability";
 std::string missing_int8_cap_error = "requires the Int8 capability";
+std::string missing_int4_cap_error =
+    "Using a 4-bit integer type requires the Int4TypeINTEL "
+    "or ArbitraryPrecisionIntegersINTEL capability.";
 std::string missing_int16_cap_error =
     "requires the Int16 capability,"
     " or an extension that explicitly enables 16-bit integers.";
@@ -298,6 +301,33 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()) << getDiagnosticString();
 }
 
+TEST_F(ValidateData, int4_bad) {
+  std::string str = header + "%2 = OpTypeInt 4 0";
+  CompileSuccessfully(str.c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(missing_int4_cap_error));
+}
+
+TEST_F(ValidateData, int4_with_arbitrary_precision_good) {
+  std::string str =
+      HeaderWith(
+          "ArbitraryPrecisionIntegersINTEL "
+          "OpExtension \"SPV_INTEL_arbitrary_precision_integers\"") +
+      " %2 = OpTypeInt 4 0";
+  CompileSuccessfully(str.c_str());
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()) << getDiagnosticString();
+}
+
+TEST_F(ValidateData, int4_signed_with_arbitrary_precision_good) {
+  std::string str =
+      HeaderWith(
+          "ArbitraryPrecisionIntegersINTEL "
+          "OpExtension \"SPV_INTEL_arbitrary_precision_integers\"") +
+      " %2 = OpTypeInt 4 1";
+  CompileSuccessfully(str.c_str());
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()) << getDiagnosticString();
+}
+
 TEST_F(ValidateData, int16_good) {
   std::string str = header_with_int16 + "%2 = OpTypeInt 16 1";
   CompileSuccessfully(str.c_str());