glslc: expose HLSL compilation support from Glslang

Support "-x hlsl", i.e. hlsl is a valid language specifier.

HLSL compilation ignores -std=... option

Files with .hlsl extension default to HLSL.
diff --git a/CHANGES b/CHANGES
index 1f9df68..db599b1 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,6 +1,9 @@
 Revision history for Shaderc
 
 v2016.2-dev 2016-10-12
+ - Support HLSL compilation, exposing functionality in Glslang.
+   - Supported in C, C++ API
+   - glslc accepts "-x hlsl", and assumes .hlsl files are HLSL.
 
 v2016.1 2016-10-12
  - C API for assembling now takes an options object
diff --git a/glslc/README.asciidoc b/glslc/README.asciidoc
index ba6375a..5a546f2 100644
--- a/glslc/README.asciidoc
+++ b/glslc/README.asciidoc
@@ -11,7 +11,7 @@
 
 ----
 glslc [-c|-S|-E]
-      [-x glsl] [-std=standard]
+      [-x ...] [-std=standard]
       [-fshader-stage=...]
       [--target-env=...]
       [-g]
@@ -158,7 +158,8 @@
 
 `-std=` behaves as follows:
 
-* `-std=` affects the version of all inputs passed to `glslc`.
+* `-std=` affects the version of all GLSL inputs passed to `glslc`.
+* `-std=` is ignored for HLSL inputs.
 * `-std=` overwrites `#version` directives in all input shaders, including those
   preceding the argument.
 * If a `-std=` argument specifies a different version from a `#version`
@@ -189,8 +190,9 @@
 
 ==== `-x`
 
-`-x` lets you specify the language of the input shader files. Right now, the
-only accepted argument is `glsl`.
+`-x` lets you specify the language of the input shader files. Valid languages
+are `glsl` and `hlsl`.  If the file extension is `hlsl` then the default language
+is HLSL.  Otherwise the default is 'glsl'.
 
 [[compilation-stage-selection-options]]
 === Compilation Stage Selection Options
diff --git a/glslc/src/file.h b/glslc/src/file.h
index 93d98ef..902b4cf 100644
--- a/glslc/src/file.h
+++ b/glslc/src/file.h
@@ -32,9 +32,13 @@
          extension == "tese" || extension == "geom" || extension == "comp";
 }
 
-// Returns true if the given file name has extension "glsl".
-inline bool IsGlslFile(const shaderc_util::string_piece& filename) {
-  return glslc::GetFileExtension(filename) == "glsl";
+// Returns the file extension if is either "glsl" or "hlsl", or an empty
+// string otherwise.
+inline std::string GetGlslOrHlslExtension(
+    const shaderc_util::string_piece& filename) {
+  auto extension = glslc::GetFileExtension(filename);
+  if ((extension == "glsl") || (extension == "hlsl")) return extension.str();
+  return "";
 }
 
 }  // namespace glslc
diff --git a/glslc/src/file_compiler.cc b/glslc/src/file_compiler.cc
index 311fddf..1aed39d 100644
--- a/glslc/src/file_compiler.cc
+++ b/glslc/src/file_compiler.cc
@@ -63,7 +63,8 @@
 
 namespace glslc {
 bool FileCompiler::CompileShaderFile(const std::string& input_file,
-                                     shaderc_shader_kind shader_stage) {
+                                     shaderc_shader_kind shader_stage,
+                                     shaderc_source_language lang) {
   std::vector<char> input_data;
   std::string path = input_file;
   if (!shaderc_util::ReadFile(path, &input_data)) {
@@ -111,6 +112,11 @@
     }
   }
 
+  // Set the language.  Since we only use the options object in this
+  // method, then it's ok to always set it without resetting it after
+  // compilation.  A subsequent compilation will set it again anyway.
+  options_.SetSourceLanguage(lang);
+
   switch (output_type_) {
     case OutputType::SpirvBinary: {
       const auto result = compiler_.CompileGlslToSpv(
@@ -152,11 +158,12 @@
   // Handle the error message for failing to deduce the shader kind.
   if (result.GetCompilationStatus() ==
       shaderc_compilation_status_invalid_stage) {
-    if (IsGlslFile(error_file_name)) {
+    auto glsl_or_hlsl_extension = GetGlslOrHlslExtension(error_file_name);
+    if (glsl_or_hlsl_extension != "") {
       std::cerr << "glslc: error: "
                 << "'" << error_file_name << "': "
-                << ".glsl file encountered but no -fshader-stage "
-                   "specified ahead";
+                << "." << glsl_or_hlsl_extension
+                << " file encountered but no -fshader-stage specified ahead";
     } else if (error_file_name == "<stdin>") {
       std::cerr
           << "glslc: error: '-': -fshader-stage required when input is from "
diff --git a/glslc/src/file_compiler.h b/glslc/src/file_compiler.h
index 6b077a1..4d8cb35 100644
--- a/glslc/src/file_compiler.h
+++ b/glslc/src/file_compiler.h
@@ -60,7 +60,8 @@
   // Any errors/warnings found in the shader source will be output to std::cerr
   // and increment the counts reported by OutputMessages().
   bool CompileShaderFile(const std::string& input_file,
-                         shaderc_shader_kind shader_stage);
+                         shaderc_shader_kind shader_stage,
+                         shaderc_source_language lang);
 
   // Adds a directory to be searched when processing #include directives.
   //
diff --git a/glslc/src/file_test.cc b/glslc/src/file_test.cc
index f242621..d9e075b 100644
--- a/glslc/src/file_test.cc
+++ b/glslc/src/file_test.cc
@@ -14,14 +14,15 @@
 
 #include "file.h"
 
-#include <gtest/gtest.h>
+#include <gmock/gmock.h>
 
 namespace {
 
 using glslc::GetFileExtension;
 using glslc::IsStageFile;
-using glslc::IsGlslFile;
+using glslc::GetGlslOrHlslExtension;
 using shaderc_util::string_piece;
+using testing::Eq;
 
 class FileExtensionTest : public testing::Test {
  protected:
@@ -36,7 +37,10 @@
   string_piece geom_ext = "shader.geom";
   string_piece comp_ext = "shader.comp";
   string_piece glsl_ext = "shader.glsl";
+  string_piece hlsl_ext = "shader.hlsl";
   string_piece multi_dot = "shader.some..ext";
+  string_piece both_hg_ext = "shader.hlsl.glsl";
+  string_piece both_gh_ext = "shader.glsl.hlsl";
 };
 
 TEST_F(FileExtensionTest, GetFileExtension) {
@@ -52,21 +56,26 @@
   EXPECT_EQ("comp", GetFileExtension(comp_ext));
   EXPECT_EQ("glsl", GetFileExtension(glsl_ext));
   EXPECT_EQ("ext", GetFileExtension(multi_dot));
+  EXPECT_EQ("glsl", GetFileExtension(both_hg_ext));
+  EXPECT_EQ("hlsl", GetFileExtension(both_gh_ext));
 }
 
-TEST_F(FileExtensionTest, IsGlslFile) {
-  EXPECT_FALSE(IsGlslFile(empty));
-  EXPECT_FALSE(IsGlslFile(dot));
-  EXPECT_FALSE(IsGlslFile(no_ext));
-  EXPECT_FALSE(IsGlslFile(trailing_dot));
-  EXPECT_FALSE(IsGlslFile(vert_ext));
-  EXPECT_FALSE(IsGlslFile(frag_ext));
-  EXPECT_FALSE(IsGlslFile(tesc_ext));
-  EXPECT_FALSE(IsGlslFile(tese_ext));
-  EXPECT_FALSE(IsGlslFile(geom_ext));
-  EXPECT_FALSE(IsGlslFile(comp_ext));
-  EXPECT_TRUE(IsGlslFile(glsl_ext));
-  EXPECT_FALSE(IsGlslFile(multi_dot));
+TEST_F(FileExtensionTest, GetGlslOrHlslExtension) {
+  EXPECT_THAT(GetGlslOrHlslExtension(empty), Eq(""));
+  EXPECT_THAT(GetGlslOrHlslExtension(dot), Eq(""));
+  EXPECT_THAT(GetGlslOrHlslExtension(no_ext), Eq(""));
+  EXPECT_THAT(GetGlslOrHlslExtension(trailing_dot), Eq(""));
+  EXPECT_THAT(GetGlslOrHlslExtension(vert_ext), Eq(""));
+  EXPECT_THAT(GetGlslOrHlslExtension(frag_ext), Eq(""));
+  EXPECT_THAT(GetGlslOrHlslExtension(tesc_ext), Eq(""));
+  EXPECT_THAT(GetGlslOrHlslExtension(tese_ext), Eq(""));
+  EXPECT_THAT(GetGlslOrHlslExtension(geom_ext), Eq(""));
+  EXPECT_THAT(GetGlslOrHlslExtension(comp_ext), Eq(""));
+  EXPECT_THAT(GetGlslOrHlslExtension(glsl_ext), Eq("glsl"));
+  EXPECT_THAT(GetGlslOrHlslExtension(hlsl_ext), Eq("hlsl"));
+  EXPECT_THAT(GetGlslOrHlslExtension(multi_dot), Eq(""));
+  EXPECT_THAT(GetGlslOrHlslExtension(both_hg_ext), Eq("glsl"));
+  EXPECT_THAT(GetGlslOrHlslExtension(both_gh_ext), Eq("hlsl"));
 }
 
 TEST_F(FileExtensionTest, IsStageFile) {
diff --git a/glslc/src/main.cc b/glslc/src/main.cc
index 782e3ce..193bc7f 100644
--- a/glslc/src/main.cc
+++ b/glslc/src/main.cc
@@ -22,6 +22,7 @@
 #include <utility>
 
 #include "libshaderc_util/string_piece.h"
+#include "shaderc/shaderc.h"
 #include "spirv-tools/libspirv.h"
 
 #include "file.h"
@@ -32,6 +33,13 @@
 
 namespace {
 
+// Describes an input file to be compiled.
+struct InputFileSpec {
+  std::string name;
+  shaderc_shader_kind stage;
+  shaderc_source_language language;
+};
+
 // Prints the help message.
 void PrintHelp(std::ostream* out) {
   *out << R"(glslc - Compile shaders into SPIR-V
@@ -56,9 +64,9 @@
   -I <value>        Add directory to include search path.
   -o <file>         Write output to <file>.
                     A file name of '-' represents standard output.
-  -std=<value>      Version and profile for input files. Possible values
+  -std=<value>      Version and profile for GLSL input files. Possible values
                     are concatenations of version and profile, e.g. 310es,
-                    450core, etc.
+                    450core, etc.  Ignored for HLSL files.
   -mfmt=<format>    Output SPIR-V binary code using the selected format. This
                     option may be specified only when the compilation output is
                     in SPIR-V binary code form. Available options include bin, c
@@ -77,7 +85,9 @@
   -w                Suppresses all warning messages.
   -Werror           Treat all warnings as errors.
   -x <language>     Treat subsequent input files as having type <language>.
-                    The only supported language is glsl.
+                    Valid languages are: glsl, hlsl.
+                    For files ending in .hlsl the default is hlsl.
+                    Otherwise the default is glsl.
 )";
 }
 
@@ -110,8 +120,11 @@
 }  // anonymous namespace
 
 int main(int argc, char** argv) {
-  std::vector<std::pair<std::string, shaderc_shader_kind>> input_files;
+  std::vector<InputFileSpec> input_files;
   shaderc_shader_kind current_fshader_stage = shaderc_glsl_infer_from_source;
+  bool source_language_forced = false;
+  shaderc_source_language current_source_language =
+      shaderc_source_language_glsl;
   glslc::FileCompiler compiler;
   bool success = true;
   bool has_stdin_input = false;
@@ -196,11 +209,16 @@
             << std::endl;
         success = false;
       } else {
-        if (option_arg != "glsl") {
+        if (option_arg == "glsl") {
+          current_source_language = shaderc_source_language_glsl;
+        } else if (option_arg == "hlsl") {
+          current_source_language = shaderc_source_language_hlsl;
+        } else {
           std::cerr << "glslc: error: language not recognized: '" << option_arg
                     << "'" << std::endl;
           return 1;
         }
+        source_language_forced = true;
       }
     } else if (arg == "-c") {
       compiler.SetIndividualCompilationFlag();
@@ -329,15 +347,22 @@
         has_stdin_input = true;
       }
 
+      const auto language = source_language_forced
+                                ? current_source_language
+                                : ((glslc::GetFileExtension(arg) == "hlsl")
+                                       ? shaderc_source_language_hlsl
+                                       : shaderc_source_language_glsl);
+
       // If current_fshader_stage is shaderc_glsl_infer_from_source, that means
       // we didn't set forced shader kinds (otherwise an error should have
       // already been emitted before). So we should deduce the shader kind
       // from the file name. If current_fshader_stage is specifed to one of
       // the forced shader kinds, use that for the following compilation.
-      input_files.emplace_back(
-          arg.str(), current_fshader_stage == shaderc_glsl_infer_from_source
-                         ? glslc::DeduceDefaultShaderKindFromFileName(arg)
-                         : current_fshader_stage);
+      input_files.emplace_back(InputFileSpec{
+          arg.str(), (current_fshader_stage == shaderc_glsl_infer_from_source
+                          ? glslc::DeduceDefaultShaderKindFromFileName(arg)
+                          : current_fshader_stage),
+          language});
     }
   }
 
@@ -346,10 +371,8 @@
   if (!success) return 1;
 
   for (const auto& input_file : input_files) {
-    const std::string& name = input_file.first;
-    const shaderc_shader_kind stage = input_file.second;
-
-    success &= compiler.CompileShaderFile(name, stage);
+    success &= compiler.CompileShaderFile(input_file.name, input_file.stage,
+                                          input_file.language);
   }
 
   compiler.OutputMessages();
diff --git a/glslc/test/option_dash_x.py b/glslc/test/option_dash_x.py
index 5e2c5ee..43b8b96 100644
--- a/glslc/test/option_dash_x.py
+++ b/glslc/test/option_dash_x.py
@@ -17,6 +17,10 @@
 from placeholder import FileShader
 
 MINIMAL_SHADER = "#version 140\nvoid main(){}"
+# This one is valid GLSL but not valid HLSL.
+GLSL_VERTEX_SHADER = "#version 140\nvoid main(){ gl_Position = vec4(1.0);}"
+# This one is valid HLSL but not valid GLSL.
+HLSL_VERTEX_SHADER = "float4 EntryPoint() : SV_POSITION { return float4(1.0); }"
 
 @inside_glslc_testsuite('OptionDashX')
 class TestDashXNoArg(expect.ErrorMessage):
@@ -29,14 +33,41 @@
 
 
 @inside_glslc_testsuite('OptionDashX')
-class TestDashXGlsl(expect.ValidObjectFile):
-    """Tests -x glsl."""
+class TestDashXGlslOnGlslShader(expect.ValidObjectFile):
+    """Tests -x glsl on a GLSL shader."""
 
-    shader = FileShader(MINIMAL_SHADER, '.vert')
+    shader = FileShader(GLSL_VERTEX_SHADER, '.vert')
     glslc_args = ['-x', 'glsl', '-c', shader]
 
 
 @inside_glslc_testsuite('OptionDashX')
+class TestDashXGlslOnHlslShader(expect.ErrorMessageSubstr):
+    """Tests -x glsl on an HLSL shader."""
+
+    shader = FileShader(HLSL_VERTEX_SHADER, '.vert')
+    glslc_args = ['-x', 'glsl', '-c', shader]
+    expected_error_substr = ["error: #version: Desktop shaders for Vulkan SPIR-V"
+                             " require version 140 or higher\n"]
+
+
+@inside_glslc_testsuite('OptionDashX')
+class TestDashXHlslOnHlslShader(expect.ValidObjectFile):
+    """Tests -x hlsl on an HLSL shader."""
+
+    shader = FileShader(HLSL_VERTEX_SHADER, '.vert')
+    glslc_args = ['-x', 'hlsl', '-c', shader]
+
+
+@inside_glslc_testsuite('OptionDashX')
+class TestDashXHlslOnGlslShader(expect.ErrorMessageSubstr):
+    """Tests -x hlsl on a GLSL shader."""
+
+    shader = FileShader(GLSL_VERTEX_SHADER, '.vert')
+    glslc_args = ['-x', 'hlsl', '-c', shader]
+    expected_error_substr = ["error: 'vec4' : no matching overloaded function found\n"]
+
+
+@inside_glslc_testsuite('OptionDashX')
 class TestDashXWrongParam(expect.ErrorMessage):
     """Tests -x with wrong parameter."""
 
@@ -47,13 +78,25 @@
 
 @inside_glslc_testsuite('OptionDashX')
 class TestMultipleDashX(expect.ValidObjectFile):
-    """Tests that multiple -x glsl works."""
+    """Tests that multiple -x works with a single language."""
 
-    shader = FileShader(MINIMAL_SHADER, '.vert')
+    shader = FileShader(GLSL_VERTEX_SHADER, '.vert')
     glslc_args = ['-c', '-x', 'glsl', '-x', 'glsl', shader, '-x', 'glsl']
 
 
 @inside_glslc_testsuite('OptionDashX')
+class TestMultipleDashXMixedLanguages(expect.ValidObjectFile):
+    """Tests that multiple -x works with different languages."""
+
+    glsl_shader = FileShader(GLSL_VERTEX_SHADER, '.vert')
+    hlsl_shader = FileShader(HLSL_VERTEX_SHADER, '.vert')
+    glslc_args = ['-c', '-x', 'hlsl', hlsl_shader,
+                  '-x', 'glsl', glsl_shader,
+                  '-x', 'hlsl', hlsl_shader,
+                  '-x', 'glsl', glsl_shader]
+
+
+@inside_glslc_testsuite('OptionDashX')
 class TestMultipleDashXCorrectWrong(expect.ErrorMessage):
     """Tests -x glsl -x [wrong-language]."""
 
diff --git a/glslc/test/option_shader_stage.py b/glslc/test/option_shader_stage.py
index 8e0bcbb..5114173 100644
--- a/glslc/test/option_shader_stage.py
+++ b/glslc/test/option_shader_stage.py
@@ -24,6 +24,10 @@
     }"""
 
 
+def simple_hlsl_vertex_shader():
+    return """float4 EntryPoint() : SV_POSITION { return float4(1.0); } """
+
+
 def simple_fragment_shader():
     return """#version 310 es
     void main() {
@@ -66,6 +70,14 @@
 
 
 @inside_glslc_testsuite('OptionShaderStage')
+class TestShaderStageWithHlslExtension(expect.ValidObjectFile):
+    """Tests -fshader-stage with .hlsl extension."""
+
+    shader = FileShader(simple_hlsl_vertex_shader(), '.hlsl')
+    glslc_args = ['-c', '-fshader-stage=vertex', shader]
+
+
+@inside_glslc_testsuite('OptionShaderStage')
 class TestShaderStageWithKnownExtension(expect.ValidObjectFile):
     """Tests -fshader-stage with known extension."""
 
@@ -188,6 +200,18 @@
 
 
 @inside_glslc_testsuite('OptionShaderStage')
+class TestShaderStageHlslExtensionMissingShaderStage(expect.ErrorMessage):
+    """Tests that missing -fshader-stage for .hlsl extension results in
+    an error."""
+
+    shader = FileShader(simple_hlsl_vertex_shader(), '.hlsl')
+    glslc_args = ['-c', '-x', 'hlsl', shader]
+    expected_error = [
+        "glslc: error: '", shader,
+        "': .hlsl file encountered but no -fshader-stage specified ahead\n"]
+
+
+@inside_glslc_testsuite('OptionShaderStage')
 class TestShaderStageUnknownExtensionMissingShaderStage(expect.ErrorMessage):
     """Tests that missing -fshader-stage for unknown extension results in
     an error."""
diff --git a/glslc/test/option_std.py b/glslc/test/option_std.py
index 9ad762b..865a4d2 100644
--- a/glslc/test/option_std.py
+++ b/glslc/test/option_std.py
@@ -28,6 +28,10 @@
     return 'void main() { int temp = gl_SampleID; }'
 
 
+def hlsl_compute_shader_with_barriers():
+    return 'void Entry() { AllMemoryBarrierWithGroupSync(); }'
+
+
 @inside_glslc_testsuite('OptionStd')
 class TestStdNoArg(expect.ErrorMessage):
     """Tests -std alone."""
@@ -72,6 +76,15 @@
 
 
 @inside_glslc_testsuite('OptionStd')
+class TestStdIgnoredInHlsl(expect.ValidObjectFile):
+    """Tests HLSL compilation ignores -std."""
+
+    # Compute shaders are not available in OpenGL 150
+    shader = FileShader(hlsl_compute_shader_with_barriers(), '.comp')
+    glslc_args = ['-c', '-x', 'hlsl', '-std=150', shader]
+
+
+@inside_glslc_testsuite('OptionStd')
 class TestMissingVersionAndWrongStd(expect.ErrorMessage):
     """Tests missing #version and wrong -std results in errors."""
 
diff --git a/glslc/test/parameter_tests.py b/glslc/test/parameter_tests.py
index 235b609..ff450b1 100644
--- a/glslc/test/parameter_tests.py
+++ b/glslc/test/parameter_tests.py
@@ -68,9 +68,9 @@
   -I <value>        Add directory to include search path.
   -o <file>         Write output to <file>.
                     A file name of '-' represents standard output.
-  -std=<value>      Version and profile for input files. Possible values
+  -std=<value>      Version and profile for GLSL input files. Possible values
                     are concatenations of version and profile, e.g. 310es,
-                    450core, etc.
+                    450core, etc.  Ignored for HLSL files.
   -mfmt=<format>    Output SPIR-V binary code using the selected format. This
                     option may be specified only when the compilation output is
                     in SPIR-V binary code form. Available options include bin, c
@@ -89,7 +89,9 @@
   -w                Suppresses all warning messages.
   -Werror           Treat all warnings as errors.
   -x <language>     Treat subsequent input files as having type <language>.
-                    The only supported language is glsl.
+                    Valid languages are: glsl, hlsl.
+                    For files ending in .hlsl the default is hlsl.
+                    Otherwise the default is glsl.
 '''
 
     expected_stderr = ''
diff --git a/libshaderc/src/shaderc_test.cc b/libshaderc/src/shaderc_test.cc
index df8a5cb..280a5d1 100644
--- a/libshaderc/src/shaderc_test.cc
+++ b/libshaderc/src/shaderc_test.cc
@@ -1293,7 +1293,7 @@
 }
 
 const char kGlslVertexShader[] =
-      "#version 140\nvoid main(){ gl_Position = vec4(0);}";
+    "#version 140\nvoid main(){ gl_Position = vec4(0);}";
 
 const char kHlslVertexShader[] =
     "float4 EntryPoint(uint index : SV_VERTEXID) : SV_POSITION\n"
@@ -1302,34 +1302,29 @@
 TEST_F(CompileStringTest, LangGlslOnGlslVertexSucceeds) {
   shaderc_compile_options_set_source_language(options_.get(),
                                               shaderc_source_language_glsl);
-  EXPECT_TRUE(CompilationSuccess(kGlslVertexShader,
-                                 shaderc_glsl_vertex_shader,
+  EXPECT_TRUE(CompilationSuccess(kGlslVertexShader, shaderc_glsl_vertex_shader,
                                  options_.get()));
 }
 
 TEST_F(CompileStringTest, LangGlslOnHlslVertexFails) {
   shaderc_compile_options_set_source_language(options_.get(),
                                               shaderc_source_language_glsl);
-  EXPECT_FALSE(CompilationSuccess(kHlslVertexShader,
-                                  shaderc_glsl_vertex_shader,
+  EXPECT_FALSE(CompilationSuccess(kHlslVertexShader, shaderc_glsl_vertex_shader,
                                   options_.get()));
 }
 
 TEST_F(CompileStringTest, LangHlslOnGlslVertexFails) {
   shaderc_compile_options_set_source_language(options_.get(),
                                               shaderc_source_language_hlsl);
-  EXPECT_FALSE(CompilationSuccess(kGlslVertexShader,
-                                  shaderc_glsl_vertex_shader,
+  EXPECT_FALSE(CompilationSuccess(kGlslVertexShader, shaderc_glsl_vertex_shader,
                                   options_.get()));
 }
 
 TEST_F(CompileStringTest, LangHlslOnHlslVertexSucceeds) {
   shaderc_compile_options_set_source_language(options_.get(),
                                               shaderc_source_language_hlsl);
-  EXPECT_TRUE(CompilationSuccess(kHlslVertexShader,
-                                 shaderc_glsl_vertex_shader,
+  EXPECT_TRUE(CompilationSuccess(kHlslVertexShader, shaderc_glsl_vertex_shader,
                                  options_.get()));
 }
 
-
 }  // anonymous namespace