Support SPV_KHR_8bit_storage

- Add asm/dis test for SPV_KHR_8bit_storage
- validator: SPV_KHR_8bit_storage capabilities enable declaration of 8bit int

TODO:
- validator: ban arithmetic on 8bit unless Int8 is enabled
  Covered by https://github.com/KhronosGroup/SPIRV-Tools/issues/1595
diff --git a/source/val/validation_state.cpp b/source/val/validation_state.cpp
index 137be8a..df74964 100644
--- a/source/val/validation_state.cpp
+++ b/source/val/validation_state.cpp
@@ -316,6 +316,12 @@
     case SpvCapabilityKernel:
       features_.group_ops_reduce_and_scans = true;
       break;
+    case SpvCapabilityInt8:
+    case SpvCapabilityStorageBuffer8BitAccess:
+    case SpvCapabilityUniformAndStorageBuffer8BitAccess:
+    case SpvCapabilityStoragePushConstant8:
+      features_.declare_int8_type = true;
+      break;
     case SpvCapabilityInt16:
       features_.declare_int16_type = true;
       break;
diff --git a/source/val/validation_state.h b/source/val/validation_state.h
index 3c57387..86669e2 100644
--- a/source/val/validation_state.h
+++ b/source/val/validation_state.h
@@ -74,6 +74,9 @@
 
     // Permit group oerations Reduce, InclusiveScan, ExclusiveScan
     bool group_ops_reduce_and_scans = false;
+
+    // Allow OpTypeInt with 8 bit width?
+    bool declare_int8_type = false;
   };
 
   ValidationState_t(const spv_const_context context,
diff --git a/source/validate_datarules.cpp b/source/validate_datarules.cpp
index 7d51eca..af0d53f 100644
--- a/source/validate_datarules.cpp
+++ b/source/validate_datarules.cpp
@@ -101,11 +101,12 @@
     return SPV_SUCCESS;
   }
   if (num_bits == 8) {
-    if (_.HasCapability(SpvCapabilityInt8)) {
+    if (_.features().declare_int8_type) {
       return SPV_SUCCESS;
     }
     return _.diag(SPV_ERROR_INVALID_DATA)
-           << "Using an 8-bit integer type requires the Int8 capability.";
+           << "Using an 8-bit integer type requires the Int8 capability,"
+              " or an extension that explicitly enables 16-bit integers.";
   }
   if (num_bits == 16) {
     if (_.features().declare_int16_type) {
diff --git a/test/enum_string_mapping_test.cpp b/test/enum_string_mapping_test.cpp
index f7f1ef5..ade61cb 100644
--- a/test/enum_string_mapping_test.cpp
+++ b/test/enum_string_mapping_test.cpp
@@ -87,6 +87,7 @@
         {Extension::kSPV_GOOGLE_decorate_string, "SPV_GOOGLE_decorate_string"},
         {Extension::kSPV_GOOGLE_hlsl_functionality1,
          "SPV_GOOGLE_hlsl_functionality1"},
+        {Extension::kSPV_KHR_8bit_storage, "SPV_KHR_8bit_storage"},
     })));
 
 INSTANTIATE_TEST_CASE_P(UnknownExtensions, UnknownExtensionTest,
diff --git a/test/text_to_binary.extension_test.cpp b/test/text_to_binary.extension_test.cpp
index 92ceb4a..14eeb0e 100644
--- a/test/text_to_binary.extension_test.cpp
+++ b/test/text_to_binary.extension_test.cpp
@@ -316,6 +316,26 @@
                                                  SpvBuiltInDeviceIndex})},
             })), );
 
+// SPV_KHR_8bit_storage
+
+INSTANTIATE_TEST_CASE_P(
+    SPV_KHR_8bit_storage, ExtensionRoundTripTest,
+    // We'll get coverage over operand tables by trying the universal
+    // environments, and at least one specific environment.
+    Combine(
+        ValuesIn(CommonVulkanEnvs()),
+        ValuesIn(std::vector<AssemblyCase>{
+            {"OpCapability StorageBuffer8BitAccess\n",
+             MakeInstruction(SpvOpCapability,
+                             {SpvCapabilityStorageBuffer8BitAccess})},
+            {"OpCapability UniformAndStorageBuffer8BitAccess\n",
+             MakeInstruction(SpvOpCapability,
+                             {SpvCapabilityUniformAndStorageBuffer8BitAccess})},
+            {"OpCapability StoragePushConstant8\n",
+             MakeInstruction(SpvOpCapability,
+                             {SpvCapabilityStoragePushConstant8})},
+        })), );
+
 // SPV_KHR_multiview
 
 INSTANTIATE_TEST_CASE_P(
diff --git a/test/val/val_data_test.cpp b/test/val/val_data_test.cpp
index 0c4218e..46e3b36 100644
--- a/test/val/val_data_test.cpp
+++ b/test/val/val_data_test.cpp
@@ -224,6 +224,33 @@
   EXPECT_THAT(getDiagnosticString(), HasSubstr(missing_int8_cap_error));
 }
 
+TEST_F(ValidateData, int8_with_storage_buffer_8bit_access_good) {
+  string str = HeaderWith(
+                   "StorageBuffer8BitAccess "
+                   "OpExtension \"SPV_KHR_8bit_storage\"") +
+               " %2 = OpTypeInt 8 0";
+  CompileSuccessfully(str.c_str());
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()) << getDiagnosticString();
+}
+
+TEST_F(ValidateData, int8_with_uniform_and_storage_buffer_8bit_access_good) {
+  string str = HeaderWith(
+                   "UniformAndStorageBuffer8BitAccess "
+                   "OpExtension \"SPV_KHR_8bit_storage\"") +
+               " %2 = OpTypeInt 8 0";
+  CompileSuccessfully(str.c_str());
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()) << getDiagnosticString();
+}
+
+TEST_F(ValidateData, int8_with_storage_push_constant_8_good) {
+  string str = HeaderWith(
+                   "StoragePushConstant8 "
+                   "OpExtension \"SPV_KHR_8bit_storage\"") +
+               " %2 = OpTypeInt 8 0";
+  CompileSuccessfully(str.c_str());
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()) << getDiagnosticString();
+}
+
 TEST_F(ValidateData, int16_good) {
   string str = header_with_int16 + "%2 = OpTypeInt 16 1";
   CompileSuccessfully(str.c_str());