GLSL writer: validate all entry points with glslang.

Generate and validate all entry points individually.
This is required since GLSL has separate shader files, and
can only have a single "main" entry point.

Bug: tint:1217
Change-Id: Ie5cb510aaef3b7c8a7573f5fa9446815284afecb
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/61920
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Stephen White <senorblanco@chromium.org>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/.gitignore b/.gitignore
index 319c459..2b07d6f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,6 +16,7 @@
 testing
 third_party/cpplint
 third_party/binutils
+third_party/glslang
 third_party/googletest
 third_party/gpuweb-cts
 third_party/llvm-build
diff --git a/DEPS b/DEPS
index 54756ab..d6cdef7 100644
--- a/DEPS
+++ b/DEPS
@@ -9,6 +9,7 @@
   'build_revision': 'c6c4a4c3ae890f2c020a087c90fb8c0b8be2816a',
   'buildtools_revision': 'e3db55b4639f2a331af6f3708ca1fbd22322aac3',
   'clang_revision': 'eb5ab41f3801e2085208204fd71a490573d72dfd',
+  'glslang_revision': '4b7b86d568b40f4b076259dc2fc4cdd006340f34',
   'googletest_revision': '5c8ca58edfb304b2dd5e6061f83387470826dd87',
   'gpuweb_cts_revision': 'b0291fd966b55a5efc496772555b94842bde1085',
   'protobuf_revision': 'fde7cf7358ec7cd69e8db9be4f1fa6a5c431386a',
@@ -27,6 +28,9 @@
   'third_party/spirv-tools': Var('chromium_git') + Var('github') +
       '/KhronosGroup//SPIRV-Tools.git@' + Var('spirv_tools_revision'),
 
+  'third_party/glslang': Var('chromium_git') + Var('github') +
+      '/KhronosGroup/glslang.git@' + Var('glslang_revision'),
+
   # Dependencies required to use GN/Clang in standalone
   'build': Var('chromium_git') + '/chromium/src/build@' +
       Var('build_revision'),
diff --git a/build_overrides/glslang.gni b/build_overrides/glslang.gni
new file mode 100644
index 0000000..6345b33
--- /dev/null
+++ b/build_overrides/glslang.gni
@@ -0,0 +1,15 @@
+# Copyright 2021 The Dawn Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+glslang_spirv_tools_dir = "//third_party/spirv-tools"
diff --git a/samples/BUILD.gn b/samples/BUILD.gn
index 6253416..51be8be 100644
--- a/samples/BUILD.gn
+++ b/samples/BUILD.gn
@@ -25,6 +25,13 @@
     "${tint_spirv_tools_dir}/:spvtools_val",
   ]
 
+  if (tint_build_glsl_writer) {
+    deps += [
+      "${tint_root_dir}/third_party/glslang:glslang_default_resource_limits_sources",
+      "${tint_root_dir}/third_party/glslang:glslang_lib_sources",
+    ]
+  }
+
   configs += [
     "${tint_root_dir}/src:tint_common_config",
     "${tint_root_dir}/src:tint_config",
diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt
index b500040..5c6c6c9 100644
--- a/samples/CMakeLists.txt
+++ b/samples/CMakeLists.txt
@@ -25,4 +25,15 @@
   target_link_libraries(tint SPIRV-Tools)
 endif()
 
-
+if(${TINT_BUILD_GLSL_WRITER})
+  target_link_libraries(tint glslang)
+  target_link_libraries(tint glslang-default-resource-limits)
+  if(NOT MSVC)
+    target_compile_options(tint PRIVATE
+      -Wno-reserved-id-macro
+      -Wno-shadow-field-in-constructor
+      -Wno-shadow
+      -Wno-weak-vtables
+    )
+  endif()
+endif()
diff --git a/samples/main.cc b/samples/main.cc
index fee450f..900cb03 100644
--- a/samples/main.cc
+++ b/samples/main.cc
@@ -20,6 +20,11 @@
 #include <string>
 #include <vector>
 
+#if TINT_BUILD_GLSL_WRITER
+#include "StandAlone/ResourceLimits.h"
+#include "glslang/Public/ShaderLang.h"
+#endif
+
 #if TINT_BUILD_SPV_READER
 #include "spirv-tools/libspirv.hpp"
 #endif  // TINT_BUILD_SPV_READER
@@ -845,25 +850,65 @@
 #endif  // TINT_BUILD_HLSL_WRITER
 }
 
+#if TINT_BUILD_GLSL_WRITER
+EShLanguage pipeline_stage_to_esh_language(tint::ast::PipelineStage stage) {
+  switch (stage) {
+    case tint::ast::PipelineStage::kFragment:
+      return EShLangFragment;
+    case tint::ast::PipelineStage::kVertex:
+      return EShLangVertex;
+    case tint::ast::PipelineStage::kCompute:
+      return EShLangCompute;
+    default:
+      TINT_ASSERT(AST, false);
+      return EShLangVertex;
+  }
+}
+#endif
+
 /// Generate GLSL code for a program.
 /// @param program the program to generate
 /// @param options the options that Tint was invoked with
 /// @returns true on success
 bool GenerateGlsl(const tint::Program* program, const Options& options) {
 #if TINT_BUILD_GLSL_WRITER
+  if (options.validate) {
+    glslang::InitializeProcess();
+  }
   tint::writer::glsl::Options gen_options;
-  auto result = tint::writer::glsl::Generate(program, gen_options);
-  if (!result.success) {
-    PrintWGSL(std::cerr, *program);
-    std::cerr << "Failed to generate: " << result.error << std::endl;
-    return false;
-  }
+  tint::inspector::Inspector inspector(program);
+  for (auto& entry_point : inspector.GetEntryPoints()) {
+    auto result =
+        tint::writer::glsl::Generate(program, gen_options, entry_point.name);
+    if (!result.success) {
+      PrintWGSL(std::cerr, *program);
+      std::cerr << "Failed to generate: " << result.error << std::endl;
+      return false;
+    }
 
-  if (!WriteFile(options.output_file, "w", result.glsl)) {
-    return false;
-  }
+    if (!WriteFile(options.output_file, "w", result.glsl)) {
+      return false;
+    }
 
-  // TODO(senorblanco): implement GLSL validation
+    if (options.validate) {
+      for (auto entry_pt : result.entry_points) {
+        EShLanguage lang = pipeline_stage_to_esh_language(entry_pt.second);
+        glslang::TShader shader(lang);
+        const char* strings[1] = {result.glsl.c_str()};
+        int lengths[1] = {static_cast<int>(result.glsl.length())};
+        shader.setStringsWithLengths(strings, lengths, 1);
+        shader.setEntryPoint("main");
+        bool glslang_result =
+            shader.parse(&glslang::DefaultTBuiltInResource, 310, EEsProfile,
+                         false, false, EShMsgDefault);
+        if (!glslang_result) {
+          std::cerr << "Error parsing GLSL shader:\n"
+                    << shader.getInfoLog() << "\n"
+                    << shader.getInfoDebugLog() << "\n";
+        }
+      }
+    }
+  }
 
   return true;
 #else
diff --git a/src/transform/glsl.cc b/src/transform/glsl.cc
index 0457f9f..1023e4f 100644
--- a/src/transform/glsl.cc
+++ b/src/transform/glsl.cc
@@ -28,6 +28,7 @@
 #include "src/transform/pad_array_elements.h"
 #include "src/transform/promote_initializers_to_const_var.h"
 #include "src/transform/simplify.h"
+#include "src/transform/single_entry_point.h"
 #include "src/transform/zero_init_workgroup_memory.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::transform::Glsl);
@@ -69,6 +70,10 @@
   // variables directly.
   data.Add<CanonicalizeEntryPointIO::Config>(
       CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
+  if (cfg) {
+    manager.Add<SingleEntryPoint>();
+    data.Add<SingleEntryPoint::Config>(cfg->entry_point);
+  }
   auto out = manager.Run(in, data);
   if (!out.program.IsValid()) {
     return out;
@@ -94,7 +99,8 @@
                  ctx.dst->WorkgroupSize(1)});
 }
 
-Glsl::Config::Config(bool disable_wi) : disable_workgroup_init(disable_wi) {}
+Glsl::Config::Config(const std::string& ep, bool disable_wi)
+    : entry_point(ep), disable_workgroup_init(disable_wi) {}
 Glsl::Config::Config(const Config&) = default;
 Glsl::Config::~Config() = default;
 
diff --git a/src/transform/glsl.h b/src/transform/glsl.h
index 56d8de9..27ec91f 100644
--- a/src/transform/glsl.h
+++ b/src/transform/glsl.h
@@ -15,6 +15,8 @@
 #ifndef SRC_TRANSFORM_GLSL_H_
 #define SRC_TRANSFORM_GLSL_H_
 
+#include <string>
+
 #include "src/transform/transform.h"
 
 namespace tint {
@@ -32,9 +34,11 @@
   /// Configuration options for the Glsl sanitizer transform.
   struct Config : public Castable<Data, transform::Data> {
     /// Constructor
+    /// @param entry_point the root entry point function to generate
     /// @param disable_workgroup_init `true` to disable workgroup memory zero
     ///        initialization
-    explicit Config(bool disable_workgroup_init = false);
+    explicit Config(const std::string& entry_point,
+                    bool disable_workgroup_init = false);
 
     /// Copy constructor
     Config(const Config&);
@@ -42,6 +46,9 @@
     /// Destructor
     ~Config() override;
 
+    /// GLSL generator wraps a single entry point in a main() function.
+    std::string entry_point;
+
     /// Set to `true` to disable workgroup memory zero initialization
     bool disable_workgroup_init = false;
   };
diff --git a/src/writer/glsl/generator.cc b/src/writer/glsl/generator.cc
index 6f6bbaa..a03e7ca 100644
--- a/src/writer/glsl/generator.cc
+++ b/src/writer/glsl/generator.cc
@@ -25,12 +25,16 @@
 Result::~Result() = default;
 Result::Result(const Result&) = default;
 
-Result Generate(const Program* program, const Options&) {
+Result Generate(const Program* program,
+                const Options&,
+                const std::string& entry_point) {
   Result result;
 
   // Run the GLSL sanitizer.
+  transform::DataMap data;
+  data.Add<transform::Glsl::Config>(entry_point);
   transform::Glsl sanitizer;
-  auto output = sanitizer.Run(program);
+  auto output = sanitizer.Run(program, data);
   if (!output.program.IsValid()) {
     result.success = false;
     result.error = output.program.Diagnostics().str();
diff --git a/src/writer/glsl/generator.h b/src/writer/glsl/generator.h
index 6aaf20f..6dad736 100644
--- a/src/writer/glsl/generator.h
+++ b/src/writer/glsl/generator.h
@@ -67,7 +67,9 @@
 /// @param program the program to translate to GLSL
 /// @param options the configuration options to use when generating GLSL
 /// @returns the resulting GLSL and supplementary information
-Result Generate(const Program* program, const Options& options);
+Result Generate(const Program* program,
+                const Options& options,
+                const std::string& entry_point);
 
 }  // namespace glsl
 }  // namespace writer
diff --git a/third_party/CMakeLists.txt b/third_party/CMakeLists.txt
index d5ff3f4..f54ed0a 100644
--- a/third_party/CMakeLists.txt
+++ b/third_party/CMakeLists.txt
@@ -39,3 +39,8 @@
     add_subdirectory("${TINT_THIRD_PARTY_DIR}/spirv-tools" "${CMAKE_BINARY_DIR}/third_party/spirv-tools" EXCLUDE_FROM_ALL)
   endif()
 endif()
+
+if(${TINT_BUILD_GLSL_WRITER})
+  set(SPIRV-Headers_SOURCE_DIR "${TINT_THIRD_PARTY_DIR}/glslang")
+  add_subdirectory("${TINT_THIRD_PARTY_DIR}/glslang" "${CMAKE_BINARY_DIR}/third_party/glslang" EXCLUDE_FROM_ALL)
+endif()