Add spvParseVulkanEnv (#3142)

This new API lets clients request a minimal spv_target_env value
that supports a given Vulkan and SPIR-V version, by a generic
numbering scheme (as already defined by Vulkan and SPIR-V specs).

This breaks a formal source dependency from Glslang to SPIRV-Tools.
When a new API is rolled out, such as Vulkan 1.2, Glslang currently
needs to reference a specific SPIRV-Tools enum by name.
diff --git a/include/spirv-tools/libspirv.h b/include/spirv-tools/libspirv.h
index e14e2e7..f6af78f 100644
--- a/include/spirv-tools/libspirv.h
+++ b/include/spirv-tools/libspirv.h
@@ -473,6 +473,19 @@
 // false and sets *env to SPV_ENV_UNIVERSAL_1_0.
 SPIRV_TOOLS_EXPORT bool spvParseTargetEnv(const char* s, spv_target_env* env);
 
+// Determines the target env value with the least features but which enables
+// the given Vulkan and SPIR-V versions. If such a target is supported, returns
+// true and writes the value to |env|, otherwise returns false.
+//
+// The Vulkan version is given as an unsigned 32-bit number as specified in
+// Vulkan section "29.2.1 Version Numbers": the major version number appears
+// in bits 22 to 21, and the minor version is in bits 12 to 21.  The SPIR-V
+// version is given in the SPIR-V version header word: major version in bits
+// 16 to 23, and minor version in bits 8 to 15.
+SPIRV_TOOLS_EXPORT bool spvParseVulkanEnv(uint32_t vulkan_ver,
+                                          uint32_t spirv_ver,
+                                          spv_target_env* env);
+
 // Creates a context object.  Returns null if env is invalid.
 SPIRV_TOOLS_EXPORT spv_context spvContextCreate(spv_target_env env);
 
diff --git a/source/spirv_target_env.cpp b/source/spirv_target_env.cpp
index c4730e8..e2ff99c 100644
--- a/source/spirv_target_env.cpp
+++ b/source/spirv_target_env.cpp
@@ -153,6 +153,34 @@
   return false;
 }
 
+#define VULKAN_VER(MAJOR, MINOR) ((MAJOR << 22) | (MINOR << 12))
+#define SPIRV_VER(MAJOR, MINOR) ((MAJOR << 16) | (MINOR << 8))
+
+struct VulkanEnv {
+  spv_target_env vulkan_env;
+  uint32_t vulkan_ver;
+  uint32_t spirv_ver;
+};
+// Maps each Vulkan target environment enum to the Vulkan version, and the
+// maximum supported SPIR-V version for that Vulkan environment.
+// Keep this ordered from least capable to most capable.
+static const VulkanEnv ordered_vulkan_envs[] = {
+    {SPV_ENV_VULKAN_1_0, VULKAN_VER(1, 0), SPIRV_VER(1, 0)},
+    {SPV_ENV_VULKAN_1_1, VULKAN_VER(1, 1), SPIRV_VER(1, 3)},
+    {SPV_ENV_VULKAN_1_1_SPIRV_1_4, VULKAN_VER(1, 1), SPIRV_VER(1, 4)},
+    {SPV_ENV_VULKAN_1_2, VULKAN_VER(1, 2), SPIRV_VER(1, 5)}};
+
+bool spvParseVulkanEnv(uint32_t vulkan_ver, uint32_t spirv_ver,
+                       spv_target_env* env) {
+  for (auto triple : ordered_vulkan_envs) {
+    if (triple.vulkan_ver >= vulkan_ver && triple.spirv_ver >= spirv_ver) {
+      *env = triple.vulkan_env;
+      return true;
+    }
+  }
+  return false;
+}
+
 bool spvIsVulkanEnv(spv_target_env env) {
   switch (env) {
     case SPV_ENV_UNIVERSAL_1_0:
diff --git a/test/target_env_test.cpp b/test/target_env_test.cpp
index 5dc7415..9c86e2d 100644
--- a/test/target_env_test.cpp
+++ b/test/target_env_test.cpp
@@ -63,11 +63,13 @@
 
 using TargetParseTest = ::testing::TestWithParam<ParseCase>;
 
-TEST_P(TargetParseTest, InvalidTargetEnvProducesNull) {
+TEST_P(TargetParseTest, Samples) {
   spv_target_env env;
   bool parsed = spvParseTargetEnv(GetParam().input, &env);
   EXPECT_THAT(parsed, Eq(GetParam().success));
-  EXPECT_THAT(env, Eq(GetParam().env));
+  if (parsed) {
+    EXPECT_THAT(env, Eq(GetParam().env));
+  }
 }
 
 INSTANTIATE_TEST_SUITE_P(
@@ -103,5 +105,61 @@
         {"abc", false, SPV_ENV_UNIVERSAL_1_0},
     }));
 
+// A test case for parsing an environment string.
+struct ParseVulkanCase {
+  uint32_t vulkan;
+  uint32_t spirv;
+  bool success;        // Expect to successfully parse?
+  spv_target_env env;  // The parsed environment, if successful.
+};
+
+using TargetParseVulkanTest = ::testing::TestWithParam<ParseVulkanCase>;
+
+TEST_P(TargetParseVulkanTest, Samples) {
+  spv_target_env env;
+  bool parsed = spvParseVulkanEnv(GetParam().vulkan, GetParam().spirv, &env);
+  EXPECT_THAT(parsed, Eq(GetParam().success));
+  if (parsed) {
+    EXPECT_THAT(env, Eq(GetParam().env));
+  }
+}
+
+#define VK(MAJ, MIN) ((MAJ << 22) | (MIN << 12))
+#define SPV(MAJ, MIN) ((MAJ << 16) | (MIN << 8))
+INSTANTIATE_TEST_SUITE_P(
+    TargetVulkanParsing, TargetParseVulkanTest,
+    ValuesIn(std::vector<ParseVulkanCase>{
+        // Vulkan 1.0 cases
+        {VK(1, 0), SPV(1, 0), true, SPV_ENV_VULKAN_1_0},
+        {VK(1, 0), SPV(1, 1), true, SPV_ENV_VULKAN_1_1},
+        {VK(1, 0), SPV(1, 2), true, SPV_ENV_VULKAN_1_1},
+        {VK(1, 0), SPV(1, 3), true, SPV_ENV_VULKAN_1_1},
+        {VK(1, 0), SPV(1, 4), true, SPV_ENV_VULKAN_1_1_SPIRV_1_4},
+        {VK(1, 0), SPV(1, 5), true, SPV_ENV_VULKAN_1_2},
+        {VK(1, 0), SPV(1, 6), false, SPV_ENV_UNIVERSAL_1_0},
+        // Vulkan 1.1 cases
+        {VK(1, 1), SPV(1, 0), true, SPV_ENV_VULKAN_1_1},
+        {VK(1, 1), SPV(1, 1), true, SPV_ENV_VULKAN_1_1},
+        {VK(1, 1), SPV(1, 2), true, SPV_ENV_VULKAN_1_1},
+        {VK(1, 1), SPV(1, 3), true, SPV_ENV_VULKAN_1_1},
+        {VK(1, 1), SPV(1, 4), true, SPV_ENV_VULKAN_1_1_SPIRV_1_4},
+        {VK(1, 1), SPV(1, 5), true, SPV_ENV_VULKAN_1_2},
+        {VK(1, 1), SPV(1, 6), false, SPV_ENV_UNIVERSAL_1_0},
+        // Vulkan 1.2 cases
+        {VK(1, 2), SPV(1, 0), true, SPV_ENV_VULKAN_1_2},
+        {VK(1, 2), SPV(1, 1), true, SPV_ENV_VULKAN_1_2},
+        {VK(1, 2), SPV(1, 2), true, SPV_ENV_VULKAN_1_2},
+        {VK(1, 2), SPV(1, 3), true, SPV_ENV_VULKAN_1_2},
+        {VK(1, 2), SPV(1, 4), true, SPV_ENV_VULKAN_1_2},
+        {VK(1, 2), SPV(1, 5), true, SPV_ENV_VULKAN_1_2},
+        {VK(1, 2), SPV(1, 6), false, SPV_ENV_UNIVERSAL_1_0},
+        // Vulkan 1.3 cases
+        {VK(1, 3), SPV(1, 0), false, SPV_ENV_UNIVERSAL_1_0},
+        // Vulkan 2.0 cases
+        {VK(2, 0), SPV(1, 0), false, SPV_ENV_UNIVERSAL_1_0},
+        // Vulkan 99.0 cases
+        {VK(99, 0), SPV(1, 0), false, SPV_ENV_UNIVERSAL_1_0},
+    }));
+
 }  // namespace
 }  // namespace spvtools