summaryrefslogtreecommitdiffstats
path: root/source
diff options
context:
space:
mode:
authorTim Foley <tfoley@nvidia.com>2017-07-25 16:21:23 -0700
committerTim Foley <tfoley@nvidia.com>2017-07-25 16:21:23 -0700
commit941b834f3ba15eba9bfa80eb370077ec9c845efd (patch)
tree7ca17a6655621a11eaa4806e0d9e11b011803dc5 /source
parent29fb1d781a763a8ca3369d03a75dbe1976571368 (diff)
Add a `-o` option to command-line `slangc`
Fixes #11 - This adds a `-o` command-line option for specifying an output file. - The code tries to be a bit smart, to glean an output format from a file extension, and also to associate multiple `-o` options with multiple `-entry` options if needed. - There is a restriction that all the output files need to agree on the code generation target. This is reasonable for now, but might be something to lift eventualy - There is a restriction that only one output file is allowed per entry point - Together with the previous item this means you can't output both a `.spv` and a `.spv.asm` in one pass, even though both should be possible - There is currently a restriction that output paths only apply to entry points - This means there is no way to output reflection JSON to a file with `-o` (but that is mostly just a debugging feature for now) - This also means we don't support any "container" formats that can encapsulate multiple compiled entry points
Diffstat (limited to 'source')
-rw-r--r--source/slang/compiler.cpp209
-rw-r--r--source/slang/compiler.h7
-rw-r--r--source/slang/diagnostic-defs.h12
-rw-r--r--source/slang/options.cpp235
-rw-r--r--source/slang/slang.cpp6
-rw-r--r--source/slangc/main.cpp8
6 files changed, 462 insertions, 15 deletions
diff --git a/source/slang/compiler.cpp b/source/slang/compiler.cpp
index 81f107bd8..6bb8abe7e 100644
--- a/source/slang/compiler.cpp
+++ b/source/slang/compiler.cpp
@@ -26,6 +26,9 @@
#include <d3dcompiler.h>
#endif
+#include <io.h>
+#include <fcntl.h>
+
#ifdef _MSC_VER
#pragma warning(disable: 4996)
#endif
@@ -318,7 +321,7 @@ namespace Slang
if (codeBlob)
{
char const* codeBegin = (char const*)codeBlob->GetBufferPointer();
- char const* codeEnd = codeBegin + codeBlob->GetBufferSize();
+ char const* codeEnd = codeBegin + codeBlob->GetBufferSize() - 1;
result.append(codeBegin, codeEnd);
codeBlob->Release();
}
@@ -592,6 +595,183 @@ namespace Slang
return result;
}
+ enum class OutputFileKind
+ {
+ Text,
+ Binary,
+ };
+
+ static void writeOutputFile(
+ CompileRequest* compileRequest,
+ FILE* file,
+ String const& path,
+ void const* data,
+ size_t size)
+ {
+ size_t count = fwrite(data, size, 1, file);
+ if (count != 1)
+ {
+ compileRequest->mSink.diagnose(
+ CodePosition(),
+ Diagnostics::cannotWriteOutputFile,
+ path);
+ }
+ }
+
+ static void writeOutputFile(
+ CompileRequest* compileRequest,
+ String const& path,
+ void const* data,
+ size_t size,
+ OutputFileKind kind)
+ {
+ FILE* file = fopen(
+ path.Buffer(),
+ kind == OutputFileKind::Binary ? "wb" : "w");
+ if (!file)
+ {
+ compileRequest->mSink.diagnose(
+ CodePosition(),
+ Diagnostics::cannotWriteOutputFile,
+ path);
+ return;
+ }
+
+ writeOutputFile(compileRequest, file, path, data, size);
+ fclose(file);
+ }
+
+ static void writeEntryPointResultToFile(
+ EntryPointRequest* entryPoint)
+ {
+ auto compileRequest = entryPoint->compileRequest;
+ auto outputPath = entryPoint->outputPath;
+ auto result = entryPoint->result;
+ switch (result.format)
+ {
+ case ResultFormat::Text:
+ {
+ auto text = result.outputString;
+ writeOutputFile(compileRequest,
+ outputPath,
+ text.begin(),
+ text.end() - text.begin(),
+ OutputFileKind::Text);
+ }
+ break;
+
+ case ResultFormat::Binary:
+ {
+ auto& data = result.outputBinary;
+ writeOutputFile(compileRequest,
+ outputPath,
+ data.begin(),
+ data.end() - data.begin(),
+ OutputFileKind::Binary);
+ }
+ break;
+
+ default:
+ SLANG_UNEXPECTED("unhandled output format");
+ break;
+ }
+
+ }
+
+ static void writeOutputToConsole(
+ CompileRequest*,
+ String const& text)
+ {
+ fwrite(
+ text.begin(),
+ text.end() - text.begin(),
+ 1,
+ stdout);
+ }
+
+ static void writeEntryPointResultToStandardOutput(
+ EntryPointRequest* entryPoint)
+ {
+ auto compileRequest = entryPoint->compileRequest;
+ auto result = entryPoint->result;
+
+ switch (result.format)
+ {
+ case ResultFormat::Text:
+ writeOutputToConsole(compileRequest, result.outputString);
+ break;
+
+ case ResultFormat::Binary:
+ {
+ auto& data = result.outputBinary;
+ int stdoutFileDesc = _fileno(stdout);
+ if (_isatty(stdoutFileDesc))
+ {
+ // Writing to console, so we need to generate text output.
+
+ switch (compileRequest->Target)
+ {
+ case CodeGenTarget::DXBytecode:
+ {
+ String assembly = dissassembleDXBC(compileRequest,
+ data.begin(),
+ data.end() - data.begin());
+ writeOutputToConsole(compileRequest, assembly);
+ }
+ break;
+
+ case CodeGenTarget::SPIRV:
+ {
+ String assembly = dissassembleSPIRV(compileRequest,
+ data.begin(),
+ data.end() - data.begin());
+ writeOutputToConsole(compileRequest, assembly);
+ }
+ break;
+
+ default:
+ SLANG_UNEXPECTED("unhandled output format");
+ return;
+ }
+ }
+ else
+ {
+ // Redirecting stdout to a file, so do the usual thing
+ _setmode(stdoutFileDesc, _O_BINARY);
+ writeOutputFile(
+ compileRequest,
+ stdout,
+ "stdout",
+ data.begin(),
+ data.end() - data.begin());
+ }
+ }
+ break;
+
+ default:
+ SLANG_UNEXPECTED("unhandled output format");
+ break;
+ }
+
+ }
+
+ static void writeEntryPointResult(
+ EntryPointRequest* entryPoint)
+ {
+ // Skip the case with no output
+ if (entryPoint->result.format == ResultFormat::None)
+ return;
+
+ if (entryPoint->outputPath.Length())
+ {
+ writeEntryPointResultToFile(entryPoint);
+ }
+ else
+ {
+ writeEntryPointResultToStandardOutput(entryPoint);
+ }
+ }
+
CompileResult emitTranslationUnitEntryPoints(
TranslationUnitRequest* translationUnit)
{
@@ -645,6 +825,7 @@ namespace Slang
// Allow for an "extra" target to verride things before we finish.
+ String extraResult;
switch (compileRequest->extraTarget)
{
case CodeGenTarget::ReflectionJSON:
@@ -661,11 +842,7 @@ namespace Slang
entryPoint->result = CompileResult();
}
- // HACK(tfoley): just print it out since that is what people probably expect.
- // TODO: need a way to control where output gets routed across all possible targets.
- fprintf(stdout, "%s", reflectionJSON.begin());
-
- return;
+ extraResult = reflectionJSON;
}
break;
@@ -673,6 +850,26 @@ namespace Slang
break;
}
+ // If we are in command-line mode, we might be expected to actually
+ // write output to one or more files here.
+
+ if (compileRequest->isCommandLineCompile)
+ {
+ switch (compileRequest->extraTarget)
+ {
+ case CodeGenTarget::ReflectionJSON:
+ fprintf(stdout, "%s", extraResult.begin());
+ break;
+
+ default:
+ for( auto entryPoint : compileRequest->entryPoints )
+ {
+ writeEntryPointResult(entryPoint);
+ }
+ break;
+ }
+ }
+
}
// Debug logic for dumping intermediate outputs
diff --git a/source/slang/compiler.h b/source/slang/compiler.h
index 09797664f..180fb027a 100644
--- a/source/slang/compiler.h
+++ b/source/slang/compiler.h
@@ -102,6 +102,10 @@ namespace Slang
// supposed to be defined in.
int translationUnitIndex;
+ // The output path requested for this entry point.
+ // (only used when compiling from the command line)
+ String outputPath;
+
// The resulting output for the enry point
//
// TODO: low-level code generation should be a distinct step
@@ -221,6 +225,9 @@ namespace Slang
// How should `#line` directives be emitted (if at all)?
LineDirectiveMode lineDirectiveMode = LineDirectiveMode::Default;
+ // Are we being driven by the command-line `slangc`, and should act accordingly?
+ bool isCommandLineCompile = false;
+
// Output stuff
DiagnosticSink mSink;
String mDiagnosticOutput;
diff --git a/source/slang/diagnostic-defs.h b/source/slang/diagnostic-defs.h
index 32385bd24..f0033b65b 100644
--- a/source/slang/diagnostic-defs.h
+++ b/source/slang/diagnostic-defs.h
@@ -49,6 +49,18 @@ DIAGNOSTIC( 2, Error, unsupportedCompilerMode, "unsupported compiler mode.")
DIAGNOSTIC( 4, Error, cannotWriteOutputFile, "cannot write output file '$0'.")
DIAGNOSTIC( 5, Error, failedToLoadDynamicLibrary, "failed to load dynamic library '$0'")
+DIAGNOSTIC( 6, Error, tooManyOutputPathsSpecified,
+ "$0 output paths specified, but only $1 entry points given")
+
+DIAGNOSTIC( 6, Error, noOutputPathSpecifiedForEntryPoint,
+ "no output path specified for entry point '$0' (the '-o' option for an entry point must precede the corresponding '-entry')")
+
+DIAGNOSTIC( 6, Error, outputPathsImplyDifferentFormats,
+ "the output paths '$0' and '$1' require different code-generation targets")
+
+DIAGNOSTIC( 6, Error, cannotDeduceOutputFormatFromPath,
+ "cannot deduce an output format from the output path '$0'")
+
//
// 1xxxx - Lexical anaylsis
//
diff --git a/source/slang/options.cpp b/source/slang/options.cpp
index d5f0f2f3c..49f33f58d 100644
--- a/source/slang/options.cpp
+++ b/source/slang/options.cpp
@@ -40,6 +40,8 @@ struct OptionsParser
SlangSession* session = nullptr;
SlangCompileRequest* compileRequest = nullptr;
+ Slang::CompileRequest* requestImpl = nullptr;
+
struct RawTranslationUnit
{
SlangSourceLanguage sourceLanguage;
@@ -55,6 +57,7 @@ struct OptionsParser
String name;
SlangProfileID profileID = SLANG_PROFILE_UNKNOWN;
int translationUnitIndex = -1;
+ int outputPathIndex = -1;
};
// Collect entry point names, so that we can associate them
@@ -73,8 +76,21 @@ struct OptionsParser
SlangProfileID currentProfileID = SLANG_PROFILE_UNKNOWN;
+ // How many times were `-profile` options given?
+ int profileOptionCount = 0;
+
SlangCompileFlags flags = 0;
+ struct RawOutputPath
+ {
+ String path;
+ SlangCompileTarget target;
+ };
+
+ List<RawOutputPath> rawOutputPaths;
+
+ SlangCompileTarget chosenTarget = SLANG_TARGET_NONE;
+
int addTranslationUnit(
SlangSourceLanguage language,
SlangProfileID implicitProfile = SLANG_PROFILE_UNKNOWN)
@@ -169,6 +185,52 @@ struct OptionsParser
}
}
+ void addOutputPath(
+ String const& path,
+ SlangCompileTarget target)
+ {
+ RawOutputPath rawOutputPath;
+ rawOutputPath.path = path;
+ rawOutputPath.target = target;
+
+ rawOutputPaths.Add(rawOutputPath);
+ }
+
+ void addOutputPath(char const* inPath)
+ {
+ String path = String(inPath);
+
+ if (!inPath) {}
+#define CASE(EXT, TARGET) \
+ else if(path.EndsWith(EXT)) do { addOutputPath(path, SLANG_##TARGET); } while(0)
+
+ CASE(".hlsl", HLSL);
+ CASE(".fx", HLSL);
+
+ CASE(".dxbc", DXBC);
+ CASE(".dxbc.asm", DXBC_ASM);
+
+ CASE(".glsl", GLSL);
+ CASE(".vert", GLSL);
+ CASE(".frag", GLSL);
+ CASE(".geom", GLSL);
+ CASE(".tesc", GLSL);
+ CASE(".tese", GLSL);
+ CASE(".comp", GLSL);
+
+ CASE(".spv", SPIRV);
+ CASE(".spv.asm", SPIRV_ASM);
+
+#undef CASE
+
+ else
+ {
+ // Allow an unknown-format `-o`, assuming we get a target format
+ // from another argument.
+ addOutputPath(path, SLANG_TARGET_UNKNOWN);
+ }
+ }
+
int parse(
int argc,
char const* const* argv)
@@ -244,6 +306,7 @@ struct OptionsParser
exit(1);
}
+ this->chosenTarget = target;
spSetCodeGenTarget(compileRequest, target);
}
// A "profile" specifies both a specific target stage and a general level
@@ -260,6 +323,7 @@ struct OptionsParser
else
{
currentProfileID = profileID;
+ profileOptionCount++;
}
}
else if (argStr == "-entry")
@@ -270,6 +334,10 @@ struct OptionsParser
entry.name = name;
entry.translationUnitIndex = currentTranslationUnitIndex;
+ int outputPathCount = (int) rawOutputPaths.Count();
+ int currentOutputPathIndex = outputPathCount - 1;
+ entry.outputPathIndex = currentOutputPathIndex;
+
// TODO(tfoley): Allow user to fold a specification of a profile into the entry-point name,
// for the case where they might be compiling multiple entry points in one invocation...
//
@@ -377,6 +445,16 @@ struct OptionsParser
compileRequest,
String(includeDirStr).begin());
}
+ //
+ // A `-o` option is used to specify a desired output file.
+ else if (argStr == "-o")
+ {
+ char const* outputPath = tryReadCommandLineArgumentRaw(
+ arg, &argCursor, argEnd);
+ if (!outputPath) continue;
+
+ addOutputPath(outputPath);
+ }
else if (argStr == "--")
{
// The `--` option causes us to stop trying to parse options,
@@ -472,23 +550,149 @@ struct OptionsParser
break;
}
- if( anyEntryPointWithoutProfile )
+ // Issue an error if there are entry points without a profile,
+ // and no profile was specified.
+ if( anyEntryPointWithoutProfile
+ && currentProfileID == SLANG_PROFILE_UNKNOWN)
{
- fprintf(stderr, "error: no profile specified; use the '-profile <profile name>' option");
+ fprintf(stderr, "error: no profile specified; use the '-profile <profile name>' option\n");
exit(1);
}
- // TODO: issue an error if we have multiple `-profile` options *and*
- // there are entry points that didn't get a profile.
+ // Issue an error if we have mulitple `-profile` options *and*
+ // there were entry points that didn't get a profile, *and*
+ // there we m
+ if (anyEntryPointWithoutProfile
+ && profileOptionCount > 1)
+ {
+ if (rawEntryPoints.Count() > 1)
+ {
+ fprintf(stderr, "error: when multiple entry points are specified, each must have a profile given (with '-profile') before the '-entry' option\n");
+ exit(1);
+ }
+ }
+ // TODO: need to issue an error on a `-profile` option that doesn't actually
+ // affect any entry point...
+
+ // Take the profile that was specified on the command line, and
+ // apply it to any entry points that don't already have a profile.
+ for( auto& e : rawEntryPoints )
+ {
+ if( e.profileID == SLANG_PROFILE_UNKNOWN )
+ {
+ e.profileID = currentProfileID;
+ }
+ }
+ }
+
+ // Did the user try to specify output path(s)?
+ if (rawOutputPaths.Count() != 0)
+ {
+ if (rawEntryPoints.Count() == 1 && rawOutputPaths.Count() == 1)
+ {
+ // There was exactly one entry point, and exactly one output path,
+ // so we can directly use that path for the entry point.
+ rawEntryPoints[0].outputPathIndex = 0;
+ }
+ else if (rawOutputPaths.Count() > rawEntryPoints.Count())
+ {
+ requestImpl->mSink.diagnose(
+ CodePosition(),
+ Diagnostics::tooManyOutputPathsSpecified,
+ rawOutputPaths.Count(),
+ rawEntryPoints.Count());
+ }
else
{
- for( auto& e : rawEntryPoints )
+ // If the user tried to apply explicit output paths, but there
+ // were any entry points that didn't pick up a path, that is
+ // an error:
+ for( auto& entryPoint : rawEntryPoints )
{
- if( e.profileID == SLANG_PROFILE_UNKNOWN )
+ if (entryPoint.outputPathIndex < 0)
{
- e.profileID = currentProfileID;
+ requestImpl->mSink.diagnose(
+ CodePosition(),
+ Diagnostics::noOutputPathSpecifiedForEntryPoint,
+ entryPoint.name);
+
+ // Don't emit this same error for other entry
+ // points, even if we have more
+ break;
}
}
}
+
+ // All of the output paths had better agree on the format
+ // they should provide.
+ switch (chosenTarget)
+ {
+ case SLANG_TARGET_NONE:
+ case SLANG_TARGET_UNKNOWN:
+ // No direct `-target` argument given, so try to infer
+ // a target from the entry points:
+ {
+ bool anyUnknownTargets = false;
+ for (auto rawOutputPath : rawOutputPaths)
+ {
+ if (rawOutputPath.target == SLANG_TARGET_UNKNOWN)
+ {
+ // This file didn't imply a target, and that
+ // needs to be an error:
+ requestImpl->mSink.diagnose(
+ CodePosition(),
+ Diagnostics::cannotDeduceOutputFormatFromPath,
+ rawOutputPath.path);
+
+ // Don't keep looking for errors
+ anyUnknownTargets = true;
+ break;
+ }
+ }
+
+ if (!anyUnknownTargets)
+ {
+ // Okay, all the files have explicit targets,
+ // so we will set the code generation target
+ // accordingly, and then ensure that all
+ // the other output paths specified (if any)
+ // are consistent with the chosen target.
+ //
+ auto target = rawOutputPaths[0].target;
+ spSetCodeGenTarget(
+ compileRequest,
+ target);
+
+ for (auto rawOutputPath : rawOutputPaths)
+ {
+ if (rawOutputPath.target != target)
+ {
+ // This file didn't imply a target, and that
+ // needs to be an error:
+ requestImpl->mSink.diagnose(
+ CodePosition(),
+ Diagnostics::outputPathsImplyDifferentFormats,
+ rawOutputPaths[0].path,
+ rawOutputPath.path);
+
+ // Don't keep looking for errors
+ break;
+ }
+ }
+ }
+
+ }
+ break;
+
+ default:
+ {
+ // An explicit target was given on the command-line.
+ // We will trust that the user knows what they are
+ // doing, even if one of the output files implies
+ // a different format.
+ }
+ break;
+
+ }
}
// Next, we want to make sure that entry points get attached to the appropriate translation
@@ -507,18 +711,27 @@ struct OptionsParser
if( anyEntryPointWithoutTranslationUnit && translationUnitCount != 1 )
{
- fprintf(stderr, "error: when using multiple translation units, entry points must be specified after their translation unit file(s)");
+ fprintf(stderr, "error: when using multiple translation units, entry points must be specified after their translation unit file(s)\n");
exit(1);
}
// Now place all those entry points where they belong
for( auto& entryPoint : rawEntryPoints )
{
- spAddEntryPoint(
+ int entryPointIndex = spAddEntryPoint(
compileRequest,
entryPoint.translationUnitIndex,
entryPoint.name.begin(),
entryPoint.profileID);
+
+ // If an output path was specified for the entry point,
+ // when we need to provide it here.
+ if (entryPoint.outputPathIndex >= 0)
+ {
+ auto rawOutputPath = rawOutputPaths[entryPoint.outputPathIndex];
+
+ requestImpl->entryPoints[entryPointIndex]->outputPath = rawOutputPath.path;
+ }
}
}
@@ -533,6 +746,9 @@ struct OptionsParser
}
#endif
+ if (requestImpl->mSink.GetErrorCount() != 0)
+ return 1;
+
return 0;
}
};
@@ -545,6 +761,7 @@ int parseOptions(
{
OptionsParser parser;
parser.compileRequest = compileRequest;
+ parser.requestImpl = (Slang::CompileRequest*) compileRequest;
return parser.parse(argc, argv);
}
diff --git a/source/slang/slang.cpp b/source/slang/slang.cpp
index 65f12fe59..bb83eb8af 100644
--- a/source/slang/slang.cpp
+++ b/source/slang/slang.cpp
@@ -684,6 +684,12 @@ SLANG_API void spSetLineDirectiveMode(
REQ(request)->lineDirectiveMode = Slang::LineDirectiveMode(mode);
}
+SLANG_API void spSetCommandLineCompilerMode(
+ SlangCompileRequest* request)
+{
+ REQ(request)->isCommandLineCompile = true;
+
+}
SLANG_API void spSetCodeGenTarget(
SlangCompileRequest* request,
diff --git a/source/slangc/main.cpp b/source/slangc/main.cpp
index a97c38685..6fb394cce 100644
--- a/source/slangc/main.cpp
+++ b/source/slangc/main.cpp
@@ -3,6 +3,8 @@
#define SLANG_DYNAMIC
#include "../slang.h"
+SLANG_API void spSetCommandLineCompilerMode(SlangCompileRequest* request);
+
#include "core/slang-io.h"
using namespace Slang;
@@ -32,6 +34,8 @@ int MAIN(int argc, char** argv)
SlangSession* session = spCreateSession(nullptr);
SlangCompileRequest* compileRequest = spCreateCompileRequest(session);
+ spSetCommandLineCompilerMode(compileRequest);
+
char const* appName = "slangc";
if(argc > 0) appName = argv[0];
@@ -62,6 +66,9 @@ int MAIN(int argc, char** argv)
exit(-1);
}
+#if 0
+ // Produce output as the command-line compiler driver should.
+
// Now dump the output from the compilation to stdout.
//
// TODO: Need a way to control where output goes so that
@@ -76,6 +83,7 @@ int MAIN(int argc, char** argv)
fputs(output, stdout);
}
fflush(stdout);
+#endif
// Now that we are done, clean up after ourselves