writer/spirv: Support declaring workgroup variables with 0 initializer

This patch adds an option to declare the workgroup variables with zero
initializer in Build() instead of transform::ZeroInitWorkgroupMemory
in Sanitize(). This option will be enabled when the Vulkan extension
VK_KHR_zero_initialize_workgroup_memory is enabled on the API side.

BUG=dawn:1302

Change-Id: Ia580df98ec161ec6f2d3099a01dbedb8bf848bf2
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/82580
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@chromium.org>
Commit-Queue: Ben Clayton <bclayton@chromium.org>
diff --git a/src/tint/writer/spirv/builder.cc b/src/tint/writer/spirv/builder.cc
index 0f0623e..dd5a52d 100644
--- a/src/tint/writer/spirv/builder.cc
+++ b/src/tint/writer/spirv/builder.cc
@@ -300,8 +300,10 @@
 
 Builder::AccessorInfo::~AccessorInfo() {}
 
-Builder::Builder(const Program* program)
-    : builder_(ProgramBuilder::Wrap(program)), scope_stack_({}) {}
+Builder::Builder(const Program* program, bool zero_initialize_workgroup_memory)
+    : builder_(ProgramBuilder::Wrap(program)),
+      scope_stack_({}),
+      zero_initialize_workgroup_memory_(zero_initialize_workgroup_memory) {}
 
 Builder::~Builder() = default;
 
@@ -861,8 +863,13 @@
     if (!type->Is<sem::Sampler>()) {
       // If we don't have a constructor and we're an Output or Private
       // variable, then WGSL requires that we zero-initialize.
+      // If we're a Workgroup variable, and the
+      // VK_KHR_zero_initialize_workgroup_memory extension is enabled, we should
+      // also zero-initialize.
       if (sem->StorageClass() == ast::StorageClass::kPrivate ||
-          sem->StorageClass() == ast::StorageClass::kOutput) {
+          sem->StorageClass() == ast::StorageClass::kOutput ||
+          (zero_initialize_workgroup_memory_ &&
+           sem->StorageClass() == ast::StorageClass::kWorkgroup)) {
         init_id = GenerateConstantNullIfNeeded(type);
         if (init_id == 0) {
           return 0;
diff --git a/src/tint/writer/spirv/builder.h b/src/tint/writer/spirv/builder.h
index c7eea0d..96924bc 100644
--- a/src/tint/writer/spirv/builder.h
+++ b/src/tint/writer/spirv/builder.h
@@ -91,7 +91,10 @@
 
   /// Constructor
   /// @param program the program
-  explicit Builder(const Program* program);
+  /// @param zero_initialize_workgroup_memory `true` to initialize all the
+  /// variables in the Workgroup storage class with OpConstantNull
+  Builder(const Program* program,
+          bool zero_initialize_workgroup_memory = false);
   ~Builder();
 
   /// Generates the SPIR-V instructions for the given program
@@ -624,6 +627,7 @@
   std::vector<uint32_t> continue_stack_;
   std::unordered_set<uint32_t> capability_set_;
   bool has_overridable_workgroup_size_ = false;
+  bool zero_initialize_workgroup_memory_ = false;
 
   struct ContinuingInfo {
     ContinuingInfo(const ast::Statement* last_statement,
diff --git a/src/tint/writer/spirv/builder_global_variable_test.cc b/src/tint/writer/spirv/builder_global_variable_test.cc
index 8fa7e75..d2b6eca 100644
--- a/src/tint/writer/spirv/builder_global_variable_test.cc
+++ b/src/tint/writer/spirv/builder_global_variable_test.cc
@@ -622,6 +622,51 @@
 )");
 }
 
+TEST_F(BuilderTest, GlobalVar_WorkgroupWithZeroInit) {
+  auto* type_scalar = ty.i32();
+  auto* var_scalar = Global("a", type_scalar, ast::StorageClass::kWorkgroup);
+
+  auto* type_array = ty.array<f32, 16>();
+  auto* var_array = Global("b", type_array, ast::StorageClass::kWorkgroup);
+
+  auto* type_struct = Structure("C",
+                                {
+                                    Member("a", ty.i32()),
+                                    Member("b", ty.i32()),
+                                },
+                                {create<ast::StructBlockAttribute>()});
+  auto* var_struct =
+      Global("c", ty.Of(type_struct), ast::StorageClass::kWorkgroup);
+
+  program = std::make_unique<Program>(std::move(*this));
+
+  constexpr bool kZeroInitializeWorkgroupMemory = true;
+  std::unique_ptr<spirv::Builder> b = std::make_unique<spirv::Builder>(
+      program.get(), kZeroInitializeWorkgroupMemory);
+
+  EXPECT_TRUE(b->GenerateGlobalVariable(var_scalar)) << b->error();
+  EXPECT_TRUE(b->GenerateGlobalVariable(var_array)) << b->error();
+  EXPECT_TRUE(b->GenerateGlobalVariable(var_struct)) << b->error();
+  ASSERT_FALSE(b->has_error()) << b->error();
+
+  EXPECT_EQ(DumpInstructions(b->types()), R"(%3 = OpTypeInt 32 1
+%2 = OpTypePointer Workgroup %3
+%4 = OpConstantNull %3
+%1 = OpVariable %2 Workgroup %4
+%8 = OpTypeFloat 32
+%9 = OpTypeInt 32 0
+%10 = OpConstant %9 16
+%7 = OpTypeArray %8 %10
+%6 = OpTypePointer Workgroup %7
+%11 = OpConstantNull %7
+%5 = OpVariable %6 Workgroup %11
+%14 = OpTypeStruct %3 %3
+%13 = OpTypePointer Workgroup %14
+%15 = OpConstantNull %14
+%12 = OpVariable %13 Workgroup %15
+)");
+}
+
 }  // namespace
 }  // namespace spirv
 }  // namespace writer
diff --git a/src/tint/writer/spirv/generator.cc b/src/tint/writer/spirv/generator.cc
index 0f6f6f8..4b3b72e 100644
--- a/src/tint/writer/spirv/generator.cc
+++ b/src/tint/writer/spirv/generator.cc
@@ -28,8 +28,11 @@
   Result result;
 
   // Sanitize the program.
+  bool disable_workgroup_init_in_sanitizer =
+      options.disable_workgroup_init ||
+      options.use_zero_initialize_workgroup_memory_extension;
   auto sanitized_result = Sanitize(program, options.emit_vertex_point_size,
-                                   options.disable_workgroup_init);
+                                   disable_workgroup_init_in_sanitizer);
   if (!sanitized_result.program.IsValid()) {
     result.success = false;
     result.error = sanitized_result.program.Diagnostics().str();
@@ -37,7 +40,11 @@
   }
 
   // Generate the SPIR-V code.
-  auto builder = std::make_unique<Builder>(&sanitized_result.program);
+  bool zero_initialize_workgroup_memory =
+      !options.disable_workgroup_init &&
+      options.use_zero_initialize_workgroup_memory_extension;
+  auto builder = std::make_unique<Builder>(&sanitized_result.program,
+                                           zero_initialize_workgroup_memory);
   auto writer = std::make_unique<BinaryWriter>();
   if (!builder->Build()) {
     result.success = false;
diff --git a/src/tint/writer/spirv/generator.h b/src/tint/writer/spirv/generator.h
index 3642a0f..7cf9654 100644
--- a/src/tint/writer/spirv/generator.h
+++ b/src/tint/writer/spirv/generator.h
@@ -41,6 +41,10 @@
 
   /// Set to `true` to disable workgroup memory zero initialization
   bool disable_workgroup_init = false;
+
+  /// Set to `true` to initialize workgroup memory with OpConstantNull when
+  /// VK_KHR_zero_initialize_workgroup_memory is enabled.
+  bool use_zero_initialize_workgroup_memory_extension = false;
 };
 
 /// The result produced when generating SPIR-V.