Handle shaders without execution model in spread-volatile-semantics (#4766)

spread-volatile-semantics pass spreads Volatile semantics for builtin
variables used by certain execution models based on
VUID-StandaloneSpirv-VulkanMemoryModel-04678 and
VUID-StandaloneSpirv-VulkanMemoryModel-04679 (See "Standalone SPIR-V
Validation" section of Vulkan spec "Appendix A: Vulkan Environment for
SPIR-V"). Therefore, shaders without execution model (e.g., used only
for linkage) are not the target of the pass. This commit lets the pass
just return SuccessWithoutChange in that case.
diff --git a/source/opt/spread_volatile_semantics.cpp b/source/opt/spread_volatile_semantics.cpp
index 17a4c72..a1d3432 100644
--- a/source/opt/spread_volatile_semantics.cpp
+++ b/source/opt/spread_volatile_semantics.cpp
@@ -92,6 +92,10 @@
 }  // namespace
 
 Pass::Status SpreadVolatileSemantics::Process() {
+  if (HasNoExecutionModel()) {
+    return Status::SuccessWithoutChange;
+  }
+
   if (!HasOnlyEntryPointsAsFunctions(context(), get_module())) {
     return Status::Failure;
   }
diff --git a/source/opt/spread_volatile_semantics.h b/source/opt/spread_volatile_semantics.h
index 3d0a183..531a21d 100644
--- a/source/opt/spread_volatile_semantics.h
+++ b/source/opt/spread_volatile_semantics.h
@@ -35,6 +35,13 @@
   }
 
  private:
+  // Returns true if it does not have an execution model. Linkage shaders do not
+  // have an execution model.
+  bool HasNoExecutionModel() {
+    return get_module()->entry_points().empty() &&
+           context()->get_feature_mgr()->HasCapability(SpvCapabilityLinkage);
+  }
+
   // Iterates interface variables and spreads the Volatile semantics if it has
   // load instructions for the Volatile semantics.
   Pass::Status SpreadVolatileSemanticsToVariables(
diff --git a/test/opt/spread_volatile_semantics_test.cpp b/test/opt/spread_volatile_semantics_test.cpp
index 83b2dcf..fdabd92 100644
--- a/test/opt/spread_volatile_semantics_test.cpp
+++ b/test/opt/spread_volatile_semantics_test.cpp
@@ -1113,6 +1113,26 @@
   SinglePassRunAndMatch<SpreadVolatileSemantics>(text, true);
 }
 
+TEST_F(VolatileSpreadTest, SkipIfItHasNoExecutionModel) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%2 = OpTypeVoid
+%3 = OpTypeFunction %2
+%4 = OpFunction %2 None %3
+%5 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  Pass::Status status;
+  std::tie(std::ignore, status) =
+      SinglePassRunToBinary<SpreadVolatileSemantics>(text,
+                                                     /* skip_nop = */ false);
+  EXPECT_EQ(status, Pass::Status::SuccessWithoutChange);
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools