Add -fentry-point to glslc

Updates to test infrastructure:
- TestStatus carries raw PlaceHolder objects

- FileShader placeholder now takes an optional assembly_substr.
  If present, that's a substring expected to be in the dissasembled
  output of an module generated from that placeholder's input.

- Add test mixin classes to check for substring in assembly
  output per shader input file.

- Pass the path to the spirv-dis to the tests, and store
  it in the test manager class.
diff --git a/CHANGES b/CHANGES
index db599b1..4cf6bd4 100644
--- a/CHANGES
+++ b/CHANGES
@@ -4,6 +4,8 @@
  - Support HLSL compilation, exposing functionality in Glslang.
    - Supported in C, C++ API
    - glslc accepts "-x hlsl", and assumes .hlsl files are HLSL.
+   - glslc accepts "-fentry-point=<name>" to set entry point name,
+     overriding default value "main".
 
 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 a7cdad4..a8f22c9 100644
--- a/glslc/README.asciidoc
+++ b/glslc/README.asciidoc
@@ -14,6 +14,7 @@
 glslc [-c|-S|-E]
       [-x ...] [-std=standard]
       [-fshader-stage=...]
+      [-fentry-point=...]
       [--target-env=...]
       [-g]
       [-O0|-Os]
@@ -150,6 +151,12 @@
 `-fshader-stage=`, since `-fshader-stage=` only affects the treatment of
 subsequent files.
 
+[[option-f-entry-point]]
+==== `-fentry-point=`
+
+`-fentry-point=<name>` lets you specify the entry point name.  This is only
+significant for HLSL compilation.  The default is "main".
+
 ==== `-std=`
 
 `-std=<value>` lets you specify a shader version and profile on the command
diff --git a/glslc/src/file_compiler.cc b/glslc/src/file_compiler.cc
index 1aed39d..5469736 100644
--- a/glslc/src/file_compiler.cc
+++ b/glslc/src/file_compiler.cc
@@ -62,16 +62,14 @@
 }  // anonymous namespace
 
 namespace glslc {
-bool FileCompiler::CompileShaderFile(const std::string& input_file,
-                                     shaderc_shader_kind shader_stage,
-                                     shaderc_source_language lang) {
+bool FileCompiler::CompileShaderFile(const InputFileSpec& input_file) {
   std::vector<char> input_data;
-  std::string path = input_file;
+  std::string path = input_file.name;
   if (!shaderc_util::ReadFile(path, &input_data)) {
     return false;
   }
 
-  std::string output_name = GetOutputFileName(input_file);
+  std::string output_name = GetOutputFileName(input_file.name);
 
   std::ofstream potential_file_stream;
   std::ostream* output_stream =
@@ -80,7 +78,7 @@
     // An error message has already been emitted to the stderr stream.
     return false;
   }
-  string_piece error_file_name = input_file;
+  string_piece error_file_name = input_file.name;
 
   if (error_file_name == "-") {
     // If the input file was stdin, we want to output errors as <stdin>.
@@ -100,12 +98,12 @@
   const auto& used_source_files = includer->file_path_trace();
   options_.SetIncluder(std::move(includer));
 
-  if (shader_stage == shaderc_spirv_assembly) {
+  if (input_file.stage == shaderc_spirv_assembly) {
     // Only act if the requested target is SPIR-V binary.
     if (output_type_ == OutputType::SpirvBinary) {
       const auto result =
           compiler_.AssembleToSpv(source_string.data(), source_string.size());
-      return EmitCompiledResult(result, input_file, error_file_name,
+      return EmitCompiledResult(result, input_file.name, error_file_name,
                                 used_source_files, output_stream);
     } else {
       return true;
@@ -115,28 +113,30 @@
   // 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);
+  options_.SetSourceLanguage(input_file.language);
 
   switch (output_type_) {
     case OutputType::SpirvBinary: {
       const auto result = compiler_.CompileGlslToSpv(
-          source_string.data(), source_string.size(), shader_stage,
-          error_file_name.data(), options_);
-      return EmitCompiledResult(result, input_file, error_file_name,
+          source_string.data(), source_string.size(), input_file.stage,
+          error_file_name.data(), input_file.entry_point_name.c_str(),
+          options_);
+      return EmitCompiledResult(result, input_file.name, error_file_name,
                                 used_source_files, output_stream);
     }
     case OutputType::SpirvAssemblyText: {
       const auto result = compiler_.CompileGlslToSpvAssembly(
-          source_string.data(), source_string.size(), shader_stage,
-          error_file_name.data(), options_);
-      return EmitCompiledResult(result, input_file, error_file_name,
+          source_string.data(), source_string.size(), input_file.stage,
+          error_file_name.data(), input_file.entry_point_name.c_str(),
+          options_);
+      return EmitCompiledResult(result, input_file.name, error_file_name,
                                 used_source_files, output_stream);
     }
     case OutputType::PreprocessedText: {
       const auto result = compiler_.PreprocessGlsl(
-          source_string.data(), source_string.size(), shader_stage,
+          source_string.data(), source_string.size(), input_file.stage,
           error_file_name.data(), options_);
-      return EmitCompiledResult(result, input_file, error_file_name,
+      return EmitCompiledResult(result, input_file.name, error_file_name,
                                 used_source_files, output_stream);
     }
   }
diff --git a/glslc/src/file_compiler.h b/glslc/src/file_compiler.h
index 4d8cb35..2f45e92 100644
--- a/glslc/src/file_compiler.h
+++ b/glslc/src/file_compiler.h
@@ -25,6 +25,14 @@
 
 namespace glslc {
 
+// Describes an input file to be compiled.
+struct InputFileSpec {
+  std::string name;
+  shaderc_shader_kind stage;
+  shaderc_source_language language;
+  std::string entry_point_name;
+};
+
 // Context for managing compilation of source GLSL files into destination
 // SPIR-V files or preprocessed output.
 class FileCompiler {
@@ -46,10 +54,11 @@
         total_warnings_(0),
         total_errors_(0) {}
 
-  // Compiles a shader received in input_file, returning true on success and
-  // false otherwise. If force_shader_stage is not shaderc_glsl_infer_source or
-  // any default shader stage then the given shader_stage will be used,
-  // otherwise it will be determined from the source or the file type.
+  // Compiles a shader received as specified by input_file, returning true
+  // on success and false otherwise. If force_shader_stage is not
+  // shaderc_glsl_infer_source or any default shader stage then the given
+  // shader_stage will be used, otherwise it will be determined from the source
+  // or the file type.
   //
   // Places the compilation output into a new file whose name is derived from
   // input_file according to the rules from glslc/README.asciidoc.
@@ -59,9 +68,7 @@
   //
   // 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_source_language lang);
+  bool CompileShaderFile(const InputFileSpec& input_file);
 
   // Adds a directory to be searched when processing #include directives.
   //
diff --git a/glslc/src/main.cc b/glslc/src/main.cc
index 193bc7f..571e65f 100644
--- a/glslc/src/main.cc
+++ b/glslc/src/main.cc
@@ -33,13 +33,6 @@
 
 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
@@ -57,6 +50,9 @@
                     Treat subsequent input files as having stage <stage>.
                     Valid stages are vertex, fragment, tesscontrol, tesseval,
                     geometry, and compute.
+  -fentry-point=<name>
+                    Specify the entry point name for HLSL compilation, for
+                    all subsequent source files.  Default is "main".
   -g                Generate source-level debug information.
                     Currently this option has no effect.
   --help            Display available options.
@@ -120,11 +116,12 @@
 }  // anonymous namespace
 
 int main(int argc, char** argv) {
-  std::vector<InputFileSpec> input_files;
+  std::vector<glslc::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;
+  std::string current_entry_point_name("main");
   glslc::FileCompiler compiler;
   bool success = true;
   bool has_stdin_input = false;
@@ -156,6 +153,9 @@
                   << std::endl;
         return 1;
       }
+    } else if (arg.starts_with("-fentry-point=")) {
+      current_entry_point_name =
+          arg.substr(std::strlen("-fentry-point=")).str();
     } else if (arg.starts_with("-std=")) {
       const string_piece standard = arg.substr(std::strlen("-std="));
       int version;
@@ -358,11 +358,11 @@
       // 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(InputFileSpec{
+      input_files.emplace_back(glslc::InputFileSpec{
           arg.str(), (current_fshader_stage == shaderc_glsl_infer_from_source
                           ? glslc::DeduceDefaultShaderKindFromFileName(arg)
                           : current_fshader_stage),
-          language});
+          language, current_entry_point_name});
     }
   }
 
@@ -371,8 +371,7 @@
   if (!success) return 1;
 
   for (const auto& input_file : input_files) {
-    success &= compiler.CompileShaderFile(input_file.name, input_file.stage,
-                                          input_file.language);
+    success &= compiler.CompileShaderFile(input_file);
   }
 
   compiler.OutputMessages();
diff --git a/glslc/test/CMakeLists.txt b/glslc/test/CMakeLists.txt
index bb82a70..cca9314 100644
--- a/glslc/test/CMakeLists.txt
+++ b/glslc/test/CMakeLists.txt
@@ -5,5 +5,6 @@
   add_test(NAME glslc_tests
     COMMAND ${PYTHON_EXE}
     ${CMAKE_CURRENT_SOURCE_DIR}/glslc_test_framework.py
-    $<TARGET_FILE:glslc_exe> --test-dir ${CMAKE_CURRENT_SOURCE_DIR})
+    $<TARGET_FILE:glslc_exe> $<TARGET_FILE:spirv-dis>
+    --test-dir ${CMAKE_CURRENT_SOURCE_DIR})
 endif()
diff --git a/glslc/test/expect.py b/glslc/test/expect.py
index 00e31d4..cfd0524 100644
--- a/glslc/test/expect.py
+++ b/glslc/test/expect.py
@@ -21,6 +21,7 @@
 import difflib
 import os
 import re
+import subprocess
 from glslc_test_framework import GlslCTest
 
 
@@ -212,6 +213,33 @@
         return True, ''
 
 
+class ValidObjectFileWithAssemblySubstr(SuccessfulReturn, CorrectObjectFilePreamble):
+    """Mixin class for checking that every input file generates a valid object
+    file following the object file naming rule, there is no output on
+    stdout/stderr, and the disassmbly contains a specified substring per input."""
+
+    def check_object_file_disassembly(self, status):
+        for an_input in status.inputs:
+            object_filename = get_object_filename(an_input.filename)
+            obj_file = str(os.path.join(status.directory, object_filename))
+            success, message = self.verify_object_file_preamble(obj_file)
+            if not success:
+                return False, message
+            cmd = [status.test_manager.disassembler_path, '--no-color', obj_file]
+            process = subprocess.Popen(
+                args=cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+                stderr=subprocess.PIPE, cwd=status.directory)
+            output = process.communicate(None)
+            disassembly = output[0]
+            if not isinstance(an_input.assembly_substr, str):
+                return False, "Missing assembly_substr member"
+            if an_input.assembly_substr not in disassembly:
+                return False, ('Incorrect disassembly output:\n{asm}\n'
+                    'Expected substring not found:\n{exp}'.format(
+                    asm=disassembly, exp=an_input.assembly_substr))
+        return True, ''
+
+
 class ValidNamedObjectFile(SuccessfulReturn, CorrectObjectFilePreamble):
     """Mixin class for checking that a list of object files with the given
     names are correctly generated, and there is no output on stdout/stderr.
@@ -280,6 +308,32 @@
         return True, ''
 
 
+class ValidAssemblyFileWithSubstr(ValidAssemblyFile):
+    """Mixin class for checking that every input file generates a valid assembly
+    file following the assembly file naming rule, there is no output on
+    stdout/stderr, and all assembly files have the given substring specified
+    by expected_assembly_substr.
+
+    To mix in this class, subclasses need to provde expected_assembly_substr
+    as the expected substring.
+    """
+
+    def check_assembly_with_substr(self, status):
+        for input_filename in status.input_filenames:
+            assembly_filename = get_assembly_filename(input_filename)
+            success, message = self.verify_assembly_file_preamble(
+                os.path.join(status.directory, assembly_filename))
+            if not success:
+                return False, message
+            with open(assembly_filename, 'r') as f:
+                content = f.read()
+                if self.expected_assembly_substr not in convert_to_unix_line_endings(content):
+                   return False, ('Incorrect assembly output:\n{asm}\n'
+                                  'Expected substring not found:\n{exp}'.format(
+                                  asm=content, exp=self.expected_assembly_substr))
+        return True, ''
+
+
 class ValidNamedAssemblyFile(SuccessfulReturn, CorrectAssemblyFilePreamble):
     """Mixin class for checking that a list of assembly files with the given
     names are correctly generated, and there is no output on stdout/stderr.
diff --git a/glslc/test/expect_nosetest.py b/glslc/test/expect_nosetest.py
index dc33e4d..567b0a0 100644
--- a/glslc/test/expect_nosetest.py
+++ b/glslc/test/expect_nosetest.py
@@ -41,20 +41,20 @@
 
 def nosetest_stdout_match_regex_has_match():
     test = TestStdoutMatchADotC()
-    status = TestStatus(returncode=0, stdout='0abc1', stderr=None,
-                        directory=None, input_filenames=None)
+    status = TestStatus(test_manager=None, returncode=0, stdout='0abc1',
+                        stderr=None, directory=None, inputs=None, input_filenames=None)
     assert_true(test.check_stdout_match(status)[0])
 
 
 def nosetest_stdout_match_regex_no_match():
     test = TestStdoutMatchADotC()
-    status = TestStatus(returncode=0, stdout='ab', stderr=None,
-                        directory=None, input_filenames=None)
+    status = TestStatus(test_manager=None, returncode=0, stdout='ab',
+                        stderr=None, directory=None, inputs=None, input_filenames=None)
     assert_false(test.check_stdout_match(status)[0])
 
 
 def nosetest_stdout_match_regex_empty_stdout():
     test = TestStdoutMatchADotC()
-    status = TestStatus(returncode=0, stdout='', stderr=None,
-                        directory=None, input_filenames=None)
+    status = TestStatus(test_manager=None, returncode=0, stdout='', stderr=None,
+                        directory=None, inputs=None, input_filenames=None)
     assert_false(test.check_stdout_match(status)[0])
diff --git a/glslc/test/glslc_test_framework.py b/glslc/test/glslc_test_framework.py
index 9454a66..6bf43a7 100755
--- a/glslc/test/glslc_test_framework.py
+++ b/glslc/test/glslc_test_framework.py
@@ -28,7 +28,7 @@
      The transformed list elements are then supplied as glslc arguments.
   3. If the environment member variable exists, its write() method will be
      invoked.
-  4. All expected_* member varibles will be inspected and all placeholders in
+  4. All expected_* member variables will be inspected and all placeholders in
      them will be expanded by calling instantiate_for_expectation() on those
      placeholders. After placeholder expansion, if the expected_* variable is
      a list, its element will be joined together with '' to form a single
@@ -141,12 +141,15 @@
 class TestStatus:
     """A struct for holding run status of a test case."""
 
-    def __init__(self, returncode, stdout, stderr, directory, input_filenames):
+    def __init__(self, test_manager, returncode, stdout, stderr, directory, inputs, input_filenames):
+        self.test_manager = test_manager
         self.returncode = returncode
         self.stdout = stdout
         self.stderr = stderr
         # temporary directory where the test runs
         self.directory = directory
+        # List of inputs, as PlaceHolder objects.
+        self.inputs = inputs
         # the names of input shader files (potentially including paths)
         self.input_filenames = input_filenames
 
@@ -198,8 +201,9 @@
 class TestManager:
     """Manages and runs a set of tests."""
 
-    def __init__(self, executable_path):
+    def __init__(self, executable_path, disassembler_path):
         self.executable_path = executable_path
+        self.disassembler_path = disassembler_path
         self.num_successes = 0
         self.num_failures = 0
         self.num_tests = 0
@@ -236,7 +240,8 @@
     def __init__(self, test, test_manager):
         self.test = test
         self.test_manager = test_manager
-        self.file_shaders = []  # filenames of shader files
+        self.inputs = []  # inputs, as PlaceHolder objects.
+        self.file_shaders = []  # filenames of shader files.
         self.stdin_shader = None  # text to be passed to glslc as stdin
 
     def setUp(self):
@@ -250,8 +255,8 @@
             if isinstance(arg, PlaceHolder) else arg
             for arg in self.test.glslc_args]
         # Get all shader files' names
-        self.file_shaders = [
-            arg.filename for arg in glslc_args if isinstance(arg, PlaceHolder)]
+        self.inputs = [arg for arg in glslc_args if isinstance(arg, PlaceHolder)]
+        self.file_shaders = [arg.filename for arg in self.inputs]
 
         if 'environment' in get_all_variables(self.test):
             self.test.environment.write(self.directory)
@@ -294,8 +299,9 @@
                 cwd=self.directory)
             output = process.communicate(self.stdin_shader)
             test_status = TestStatus(
+                self.test_manager,
                 process.returncode, output[0], output[1],
-                self.directory, self.file_shaders)
+                self.directory, self.inputs, self.file_shaders)
             run_results = [getattr(self.test, test_method)(test_status)
                            for test_method in get_all_test_methods(
                                self.test.__class__)]
@@ -313,6 +319,8 @@
     parser = argparse.ArgumentParser()
     parser.add_argument('glslc', metavar='path/to/glslc', type=str, nargs=1,
                         help='Path to glslc')
+    parser.add_argument('spirvdis', metavar='path/to/glslc', type=str, nargs=1,
+                        help='Path to spirv-dis')
     parser.add_argument('--leave-output', action='store_const', const=1,
                         help='Do not clean up temporary directories')
     parser.add_argument('--test-dir', nargs=1,
@@ -322,7 +330,7 @@
     root_dir = os.getcwd()
     if args.test_dir:
         root_dir = args.test_dir[0]
-    manager = TestManager(args.glslc[0])
+    manager = TestManager(args.glslc[0], args.spirvdis[0])
     if args.leave_output:
         manager.leave_output = True
     for root, _, filenames in os.walk(root_dir):
diff --git a/glslc/test/option_dash_x.py b/glslc/test/option_dash_x.py
index 43b8b96..3645340 100644
--- a/glslc/test/option_dash_x.py
+++ b/glslc/test/option_dash_x.py
@@ -51,14 +51,6 @@
 
 
 @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."""
 
diff --git a/glslc/test/option_fentry_point.py b/glslc/test/option_fentry_point.py
new file mode 100644
index 0000000..5d8f96f
--- /dev/null
+++ b/glslc/test/option_fentry_point.py
@@ -0,0 +1,109 @@
+# Copyright 2016 The Shaderc Authors. All rights reserved.
+#
+# 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.
+
+import expect
+from glslc_test_framework import inside_glslc_testsuite
+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); }"
+HLSL_VERTEX_SHADER_WITH_MAIN = "float4 main() : SV_POSITION { return float4(1.0); }"
+HLSL_VERTEX_SHADER_WITH_FOOBAR = "float4 Foobar() : SV_POSITION { return float4(1.0); }"
+
+# Expected assembly code within certain shaders.
+ASSEMBLY_ENTRY_POINT = "OpEntryPoint Vertex %EntryPoint \"EntryPoint\""
+ASSEMBLY_MAIN = "OpEntryPoint Vertex %main \"main\""
+ASSEMBLY_FOOBAR = "OpEntryPoint Vertex %Foobar \"Foobar\""
+
+
+@inside_glslc_testsuite('OptionFEntryPoint')
+class TestEntryPointDefaultsToMainForGlsl(expect.ValidAssemblyFileWithSubstr):
+    """Tests that entry point name defaults to "main" in a GLSL shader."""
+
+    shader = FileShader(GLSL_VERTEX_SHADER, '.vert')
+    glslc_args = ['-S', shader]
+    expected_assembly_substr = ASSEMBLY_MAIN
+
+
+@inside_glslc_testsuite('OptionFEntryPoint')
+class TestEntryPointDefaultsToMainForHlsl(expect.ValidAssemblyFileWithSubstr):
+    """Tests that entry point name defaults to "main" in an HLSL shader."""
+
+    shader = FileShader(HLSL_VERTEX_SHADER_WITH_MAIN, '.vert')
+    glslc_args = ['-x', 'hlsl', '-S', shader]
+    expected_assembly_substr = ASSEMBLY_MAIN
+
+
+@inside_glslc_testsuite('OptionFEntryPoint')
+class TestFEntryPointMainOnGlslShader(expect.ValidAssemblyFileWithSubstr):
+    """Tests -fentry-point=main with a GLSL shader."""
+
+    shader = FileShader(GLSL_VERTEX_SHADER, '.vert')
+    glslc_args = ['-fentry-point=main', '-S', shader]
+    expected_assembly_substr = ASSEMBLY_MAIN
+
+
+@inside_glslc_testsuite('OptionFEntryPoint')
+class TestFEntryPointMainOnHlslShader(expect.ValidAssemblyFileWithSubstr):
+    """Tests -x hlsl on an HLSL shader with -fentry-point=main and -S."""
+
+    shader = FileShader(HLSL_VERTEX_SHADER, '.vert')
+    glslc_args = ['-x', 'hlsl', '-fentry-point=main', '-S', shader]
+    expected_assembly_substr = ASSEMBLY_MAIN
+
+
+@inside_glslc_testsuite('OptionFEntryPoint')
+class TestFEntryPointSpecifiedOnHlslShaderInDisassembly(expect.ValidObjectFileWithAssemblySubstr):
+    """Tests -x hlsl on an HLSL shader with -fentry-point=EntryPoint."""
+
+    shader = FileShader(HLSL_VERTEX_SHADER, '.vert', assembly_substr=ASSEMBLY_ENTRY_POINT)
+    glslc_args = ['-x', 'hlsl', '-fentry-point=EntryPoint', '-c', shader]
+
+
+@inside_glslc_testsuite('OptionFEntryPoint')
+class TestFEntryPointAffectsSubsequentShaderFiles(expect.ValidObjectFileWithAssemblySubstr):
+    """Tests -x hlsl affects several subsequent shader source files."""
+
+    shader1 = FileShader(HLSL_VERTEX_SHADER, '.vert', assembly_substr=ASSEMBLY_ENTRY_POINT)
+    shader2 = FileShader(HLSL_VERTEX_SHADER, '.vert', assembly_substr=ASSEMBLY_ENTRY_POINT)
+    glslc_args = ['-x', 'hlsl', '-fentry-point=EntryPoint', '-c', shader1, shader2]
+
+
+@inside_glslc_testsuite('OptionFEntryPoint')
+class TestFEntryPointOverridesItself(expect.ValidObjectFileWithAssemblySubstr):
+    """Tests that a later -fentry-point option overrides an earlier use."""
+
+    shader = FileShader(HLSL_VERTEX_SHADER, '.vert', assembly_substr=ASSEMBLY_ENTRY_POINT)
+    glslc_args = ['-x', 'hlsl', '-fentry-point=foobar', '-fentry-point=EntryPoint',
+                  '-c', shader]
+
+
+@inside_glslc_testsuite('OptionFEntryPoint')
+class TestFEntryPointDefaultAndTwoOthers(expect.ValidObjectFileWithAssemblySubstr):
+    """Tests three shaders with different entry point names. The first uses "main"
+    with default entry point processing, and the remaining shaders get their
+    own -fentry-point argument."""
+
+    shaderMain = FileShader(HLSL_VERTEX_SHADER_WITH_MAIN, '.vert',
+                            assembly_substr=ASSEMBLY_MAIN)
+    shaderEntryPoint = FileShader(HLSL_VERTEX_SHADER, '.vert',
+                                  assembly_substr=ASSEMBLY_ENTRY_POINT)
+    shaderFoobar = FileShader(HLSL_VERTEX_SHADER_WITH_FOOBAR, '.vert',
+                              assembly_substr=ASSEMBLY_FOOBAR)
+    glslc_args = ['-x', 'hlsl', '-c', shaderMain,
+                  '-fentry-point=EntryPoint', shaderEntryPoint,
+                  '-fentry-point=Foobar', shaderFoobar]
diff --git a/glslc/test/parameter_tests.py b/glslc/test/parameter_tests.py
index ff450b1..cbb6f11 100644
--- a/glslc/test/parameter_tests.py
+++ b/glslc/test/parameter_tests.py
@@ -61,6 +61,9 @@
                     Treat subsequent input files as having stage <stage>.
                     Valid stages are vertex, fragment, tesscontrol, tesseval,
                     geometry, and compute.
+  -fentry-point=<name>
+                    Specify the entry point name for HLSL compilation, for
+                    all subsequent source files.  Default is "main".
   -g                Generate source-level debug information.
                     Currently this option has no effect.
   --help            Display available options.
diff --git a/glslc/test/placeholder.py b/glslc/test/placeholder.py
index 9c5bcfc..8a701bf 100644
--- a/glslc/test/placeholder.py
+++ b/glslc/test/placeholder.py
@@ -59,12 +59,15 @@
 class FileShader(PlaceHolder):
     """Stands for a shader whose source code is in a file."""
 
-    def __init__(self, source, suffix):
+    def __init__(self, source, suffix, assembly_substr=None):
         assert isinstance(source, str)
         assert isinstance(suffix, str)
         self.source = source
         self.suffix = suffix
         self.filename = None
+        # If provided, this is a substring which is expected to be in
+        # the disassembly of the module generated from this input file.
+        self.assembly_substr = assembly_substr
 
     def instantiate_for_glslc_args(self, testcase):
         """Creates a temporary file and writes the source into it.