summaryrefslogtreecommitdiffstats
path: root/source/slang/slang-options.cpp
diff options
context:
space:
mode:
authorjsmall-nvidia <jsmall@nvidia.com>2019-05-31 17:20:37 -0400
committerGitHub <noreply@github.com>2019-05-31 17:20:37 -0400
commit6cbc3929a54d37bd23cb5efa8e3320ba02f78b2f (patch)
tree5a23cb47782e9e2a77762c90dd35da1005eba8d0 /source/slang/slang-options.cpp
parentb81ff3ef968d1cc4e954b31a1812b3c391d17b02 (diff)
Use slang- prefix on slang compiler and core source (#973)
* Prefixing source files in source/slang with slang- * Prefix source in source/slang with slang- prefix. * Rename core source files with slang- prefix. * Update project files. * Fix problems from automatic merge.
Diffstat (limited to 'source/slang/slang-options.cpp')
-rw-r--r--source/slang/slang-options.cpp1356
1 files changed, 1356 insertions, 0 deletions
diff --git a/source/slang/slang-options.cpp b/source/slang/slang-options.cpp
new file mode 100644
index 000000000..85a2662bc
--- /dev/null
+++ b/source/slang/slang-options.cpp
@@ -0,0 +1,1356 @@
+// slang-options.cpp
+
+// Implementation of options parsing for `slangc` command line,
+// and also for API interface that takes command-line argument strings.
+
+#include "../../slang.h"
+
+#include "slang-compiler.h"
+#include "slang-profile.h"
+
+#include <assert.h>
+
+namespace Slang {
+
+SlangResult tryReadCommandLineArgumentRaw(DiagnosticSink* sink, char const* option, char const* const**ioCursor, char const* const*end, char const** argOut)
+{
+ *argOut = nullptr;
+ char const* const*& cursor = *ioCursor;
+ if (cursor == end)
+ {
+ sink->diagnose(SourceLoc(), Diagnostics::expectedArgumentForOption, option);
+ return SLANG_FAIL;
+ }
+ else
+ {
+ *argOut = *cursor++;
+ return SLANG_OK;
+ }
+}
+
+SlangResult tryReadCommandLineArgument(DiagnosticSink* sink, char const* option, char const* const**ioCursor, char const* const*end, String& argOut)
+{
+ const char* arg;
+ SLANG_RETURN_ON_FAIL(tryReadCommandLineArgumentRaw(sink, option, ioCursor, end, &arg));
+ argOut = arg;
+ return SLANG_OK;
+}
+
+struct OptionsParser
+{
+ SlangSession* session = nullptr;
+ SlangCompileRequest* compileRequest = nullptr;
+
+ Slang::EndToEndCompileRequest* requestImpl = nullptr;
+
+ Slang::RefPtr<Slang::ConfigurableSharedLibraryLoader> sharedLibraryLoader;
+
+ // A "translation unit" represents one or more source files
+ // that are processed as a single entity when it comes to
+ // semantic checking.
+ //
+ // For languages like HLSL, GLSL, and C, a translation unit
+ // is usually a single source file (which can then go on
+ // to `#include` other files into the same translation unit).
+ //
+ // For Slang, we support having multiple source files in
+ // a single translation unit, and indeed command-line `slangc`
+ // will always put all the source files into a single translation
+ // unit.
+ //
+ // We track information on the translation units that we
+ // create during options parsing, so that we can assocaite
+ // other entities with these translation units:
+ //
+ struct RawTranslationUnit
+ {
+ // What language is the translation unit using?
+ //
+ // Note: We do not support translation units that mix
+ // languages.
+ //
+ SlangSourceLanguage sourceLanguage;
+
+ // Certain naming conventions imply a stage for
+ // a file with only a single entry point, and in
+ // those cases we will try to infer the stage from
+ // the file when it is possible.
+ //
+ Stage impliedStage;
+
+ // We retain the Slang API level translation unit index,
+ // which we will call an "ID" inside the options parsing code.
+ //
+ // This will almost always be the index into the
+ // `rawTranslationUnits` array below, but could conceivably,
+ // be mismatched if we were parsing options for a compile
+ // request that already had some translation unit(s) added
+ // manually.
+ //
+ int translationUnitID;
+ };
+ List<RawTranslationUnit> rawTranslationUnits;
+
+ // If we already have a translation unit for Slang code, then this will give its index.
+ // If not, it will be `-1`.
+ int slangTranslationUnitIndex = -1;
+
+ // The number of input files that have been specified
+ int inputPathCount = 0;
+
+ int translationUnitCount = 0;
+ int currentTranslationUnitIndex= -1;
+
+ // An entry point represents a function to be checked and possibly have
+ // code generated in one of our translation units. An entry point
+ // needs to have an associated stage, which might come via the
+ // `-stage` command line option, or a `[shader("...")]` attribute
+ // in the source code.
+ //
+ struct RawEntryPoint
+ {
+ String name;
+ Stage stage = Stage::Unknown;
+ int translationUnitIndex = -1;
+ int entryPointID = -1;
+
+ // State for tracking command-line errors
+ bool conflictingStagesSet = false;
+ bool redundantStageSet = false;
+ };
+ //
+ // We collect the entry points in a "raw" array so that we can
+ // possibly associate them with a stage or translation unit
+ // after the fact.
+ //
+ List<RawEntryPoint> rawEntryPoints;
+
+ // In the case where we have only a single entry point,
+ // the entry point and its options might be specified out
+ // of order, so we will keep a single `RawEntryPoint` around
+ // and use it as the target for any state-setting options
+ // before the first "proper" entry point is specified.
+ RawEntryPoint defaultEntryPoint;
+
+ SlangCompileFlags flags = 0;
+
+ struct RawOutput
+ {
+ String path;
+ CodeGenTarget impliedFormat = CodeGenTarget::Unknown;
+ int targetIndex = -1;
+ int entryPointIndex = -1;
+ };
+ List<RawOutput> rawOutputs;
+
+ struct RawTarget
+ {
+ CodeGenTarget format = CodeGenTarget::Unknown;
+ ProfileVersion profileVersion = ProfileVersion::Unknown;
+ SlangTargetFlags targetFlags = 0;
+ int targetID = -1;
+ FloatingPointMode floatingPointMode = FloatingPointMode::Default;
+
+ // State for tracking command-line errors
+ bool conflictingProfilesSet = false;
+ bool redundantProfileSet = false;
+
+ };
+ List<RawTarget> rawTargets;
+
+ RawTarget defaultTarget;
+
+ void addSharedLibraryPath(SharedLibraryType libType, const String& path)
+ {
+ if (!sharedLibraryLoader)
+ {
+ sharedLibraryLoader = new ConfigurableSharedLibraryLoader;
+ }
+ sharedLibraryLoader->addEntry(libType, ConfigurableSharedLibraryLoader::changePath, path);
+ }
+
+ int addTranslationUnit(
+ SlangSourceLanguage language,
+ Stage impliedStage)
+ {
+ auto translationUnitIndex = rawTranslationUnits.getCount();
+ auto translationUnitID = spAddTranslationUnit(compileRequest, language, nullptr);
+
+ // As a sanity check: the API should be returning the same translation
+ // unit index as we maintain internally. This invariant would only
+ // be broken if we decide to support a mix of translation units specified
+ // via API, and ones specified via command-line arguments.
+ //
+ SLANG_RELEASE_ASSERT(Index(translationUnitID) == translationUnitIndex);
+
+ RawTranslationUnit rawTranslationUnit;
+ rawTranslationUnit.sourceLanguage = language;
+ rawTranslationUnit.translationUnitID = translationUnitID;
+ rawTranslationUnit.impliedStage = impliedStage;
+
+ rawTranslationUnits.add(rawTranslationUnit);
+
+ return int(translationUnitIndex);
+ }
+
+ void addInputSlangPath(
+ String const& path)
+ {
+ // All of the input .slang files will be grouped into a single logical translation unit,
+ // which we create lazily when the first .slang file is encountered.
+ if( slangTranslationUnitIndex == -1 )
+ {
+ translationUnitCount++;
+ slangTranslationUnitIndex = addTranslationUnit(SLANG_SOURCE_LANGUAGE_SLANG, Stage::Unknown);
+ }
+
+ spAddTranslationUnitSourceFile(
+ compileRequest,
+ rawTranslationUnits[slangTranslationUnitIndex].translationUnitID,
+ path.begin());
+
+ // Set the translation unit to be used by subsequent entry points
+ currentTranslationUnitIndex = slangTranslationUnitIndex;
+ }
+
+ void addInputForeignShaderPath(
+ String const& path,
+ SlangSourceLanguage language,
+ Stage impliedStage)
+ {
+ translationUnitCount++;
+ currentTranslationUnitIndex = addTranslationUnit(language, impliedStage);
+
+ spAddTranslationUnitSourceFile(
+ compileRequest,
+ rawTranslationUnits[currentTranslationUnitIndex].translationUnitID,
+ path.begin());
+ }
+
+ static Profile::RawVal findGlslProfileFromPath(const String& path)
+ {
+ struct Entry
+ {
+ const char* ext;
+ Profile::RawVal profileId;
+ };
+
+ static const Entry entries[] =
+ {
+ { ".frag", Profile::GLSL_Fragment },
+ { ".geom", Profile::GLSL_Geometry },
+ { ".tesc", Profile::GLSL_TessControl },
+ { ".tese", Profile::GLSL_TessEval },
+ { ".comp", Profile::GLSL_Compute }
+ };
+
+ for (int i = 0; i < SLANG_COUNT_OF(entries); ++i)
+ {
+ const Entry& entry = entries[i];
+ if (path.endsWith(entry.ext))
+ {
+ return entry.profileId;
+ }
+ }
+ return Profile::Unknown;
+ }
+
+ static SlangSourceLanguage findSourceLanguageFromPath(const String& path, Stage& outImpliedStage)
+ {
+ struct Entry
+ {
+ const char* ext;
+ SlangSourceLanguage sourceLanguage;
+ SlangStage impliedStage;
+ };
+
+ static const Entry entries[] =
+ {
+ { ".slang", SLANG_SOURCE_LANGUAGE_SLANG, SLANG_STAGE_NONE },
+
+ { ".hlsl", SLANG_SOURCE_LANGUAGE_HLSL, SLANG_STAGE_NONE },
+ { ".fx", SLANG_SOURCE_LANGUAGE_HLSL, SLANG_STAGE_NONE },
+
+ { ".glsl", SLANG_SOURCE_LANGUAGE_GLSL, SLANG_STAGE_NONE },
+ { ".vert", SLANG_SOURCE_LANGUAGE_GLSL, SLANG_STAGE_VERTEX },
+ { ".frag", SLANG_SOURCE_LANGUAGE_GLSL, SLANG_STAGE_FRAGMENT },
+ { ".geom", SLANG_SOURCE_LANGUAGE_GLSL, SLANG_STAGE_GEOMETRY },
+ { ".tesc", SLANG_SOURCE_LANGUAGE_GLSL, SLANG_STAGE_HULL },
+ { ".tese", SLANG_SOURCE_LANGUAGE_GLSL, SLANG_STAGE_DOMAIN },
+ { ".comp", SLANG_SOURCE_LANGUAGE_GLSL, SLANG_STAGE_COMPUTE },
+ };
+
+ for (int i = 0; i < SLANG_COUNT_OF(entries); ++i)
+ {
+ const Entry& entry = entries[i];
+ if (path.endsWith(entry.ext))
+ {
+ outImpliedStage = Stage(entry.impliedStage);
+ return entry.sourceLanguage;
+ }
+ }
+ return SLANG_SOURCE_LANGUAGE_UNKNOWN;
+ }
+
+ SlangResult addInputPath(
+ char const* inPath)
+ {
+ inputPathCount++;
+
+ // look at the extension on the file name to determine
+ // how we should handle it.
+ String path = String(inPath);
+
+ if( path.endsWith(".slang") )
+ {
+ // Plain old slang code
+ addInputSlangPath(path);
+ return SLANG_OK;
+ }
+
+ Stage impliedStage = Stage::Unknown;
+ SlangSourceLanguage sourceLanguage = findSourceLanguageFromPath(path, impliedStage);
+
+ if (sourceLanguage == SLANG_SOURCE_LANGUAGE_UNKNOWN)
+ {
+ requestImpl->getSink()->diagnose(SourceLoc(), Diagnostics::cannotDeduceSourceLanguage, inPath);
+ return SLANG_FAIL;
+ }
+
+ addInputForeignShaderPath(path, sourceLanguage, impliedStage);
+
+ return SLANG_OK;
+ }
+
+ void addOutputPath(
+ String const& path,
+ CodeGenTarget impliedFormat)
+ {
+ RawOutput rawOutput;
+ rawOutput.path = path;
+ rawOutput.impliedFormat = impliedFormat;
+ rawOutputs.add(rawOutput);
+ }
+
+ void addOutputPath(char const* inPath)
+ {
+ String path = String(inPath);
+
+ if (!inPath) {}
+#define CASE(EXT, TARGET) \
+ else if(path.endsWith(EXT)) do { addOutputPath(path, CodeGenTarget(SLANG_##TARGET)); } while(0)
+
+ CASE(".hlsl", HLSL);
+ CASE(".fx", HLSL);
+
+ CASE(".dxbc", DXBC);
+ CASE(".dxbc.asm", DXBC_ASM);
+
+ CASE(".dxil", DXIL);
+ CASE(".dxil.asm", DXIL_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);
+
+ CASE(".c", C_SOURCE);
+ CASE(".cpp", CPP_SOURCE);
+
+#undef CASE
+
+ else if (path.endsWith(".slang-module"))
+ {
+ spSetOutputContainerFormat(compileRequest, SLANG_CONTAINER_FORMAT_SLANG_MODULE);
+ requestImpl->containerOutputPath = path;
+ }
+ else
+ {
+ // Allow an unknown-format `-o`, assuming we get a target format
+ // from another argument.
+ addOutputPath(path, CodeGenTarget::Unknown);
+ }
+ }
+
+ RawEntryPoint* getCurrentEntryPoint()
+ {
+ auto rawEntryPointCount = rawEntryPoints.getCount();
+ return rawEntryPointCount ? &rawEntryPoints[rawEntryPointCount-1] : &defaultEntryPoint;
+ }
+
+ void setStage(RawEntryPoint* rawEntryPoint, Stage stage)
+ {
+ if(rawEntryPoint->stage != Stage::Unknown)
+ {
+ rawEntryPoint->redundantStageSet = true;
+ if( stage != rawEntryPoint->stage )
+ {
+ rawEntryPoint->conflictingStagesSet = true;
+ }
+ }
+ rawEntryPoint->stage = stage;
+ }
+
+ RawTarget* getCurrentTarget()
+ {
+ auto rawTargetCount = rawTargets.getCount();
+ return rawTargetCount ? &rawTargets[rawTargetCount-1] : &defaultTarget;
+ }
+
+ void setProfileVersion(RawTarget* rawTarget, ProfileVersion profileVersion)
+ {
+ if(rawTarget->profileVersion != ProfileVersion::Unknown)
+ {
+ rawTarget->redundantProfileSet = true;
+
+ if(profileVersion != rawTarget->profileVersion)
+ {
+ rawTarget->conflictingProfilesSet = true;
+ }
+ }
+ rawTarget->profileVersion = profileVersion;
+ }
+
+ void setFloatingPointMode(RawTarget* rawTarget, FloatingPointMode mode)
+ {
+ rawTarget->floatingPointMode = mode;
+ }
+
+ SlangResult parse(
+ int argc,
+ char const* const* argv)
+ {
+ // Copy some state out of the current request, in case we've been called
+ // after some other initialization has been performed.
+ flags = requestImpl->getFrontEndReq()->compileFlags;
+
+ DiagnosticSink* sink = requestImpl->getSink();
+
+ SlangMatrixLayoutMode defaultMatrixLayoutMode = SLANG_MATRIX_LAYOUT_MODE_UNKNOWN;
+
+ char const* const* argCursor = &argv[0];
+ char const* const* argEnd = &argv[argc];
+ while (argCursor != argEnd)
+ {
+ char const* arg = *argCursor++;
+ if (arg[0] == '-')
+ {
+ String argStr = String(arg);
+
+ if(argStr == "-no-mangle" )
+ {
+ flags |= SLANG_COMPILE_FLAG_NO_MANGLING;
+ }
+ else if (argStr == "-no-codegen")
+ {
+ flags |= SLANG_COMPILE_FLAG_NO_CODEGEN;
+ }
+ else if(argStr == "-dump-ir" )
+ {
+ requestImpl->getFrontEndReq()->shouldDumpIR = true;
+ requestImpl->getBackEndReq()->shouldDumpIR = true;
+ }
+ else if (argStr == "-serial-ir")
+ {
+ requestImpl->getFrontEndReq()->useSerialIRBottleneck = true;
+ }
+ else if (argStr == "-verbose-paths")
+ {
+ requestImpl->getSink()->flags |= DiagnosticSink::Flag::VerbosePath;
+ }
+ else if (argStr == "-verify-debug-serial-ir")
+ {
+ requestImpl->getFrontEndReq()->verifyDebugSerialization = true;
+ }
+ else if(argStr == "-validate-ir" )
+ {
+ requestImpl->getFrontEndReq()->shouldValidateIR = true;
+ requestImpl->getBackEndReq()->shouldValidateIR = true;
+ }
+ else if(argStr == "-skip-codegen" )
+ {
+ requestImpl->shouldSkipCodegen = true;
+ }
+ else if(argStr == "-parameter-blocks-use-register-spaces" )
+ {
+ getCurrentTarget()->targetFlags |= SLANG_TARGET_FLAG_PARAMETER_BLOCKS_USE_REGISTER_SPACES;
+ }
+ else if (argStr == "-target")
+ {
+ String name;
+ SLANG_RETURN_ON_FAIL(tryReadCommandLineArgument(sink, arg, &argCursor, argEnd, name));
+
+ SlangCompileTarget format = SLANG_TARGET_UNKNOWN;
+
+ #define CASE(NAME, TARGET) \
+ if(name == NAME) { format = SLANG_##TARGET; } else
+
+ CASE("hlsl", HLSL)
+ CASE("glsl", GLSL)
+ CASE("dxbc", DXBC)
+ CASE("dxbc-assembly", DXBC_ASM)
+ CASE("dxbc-asm", DXBC_ASM)
+ CASE("spirv", SPIRV)
+ CASE("spirv-assembly", SPIRV_ASM)
+ CASE("spirv-asm", SPIRV_ASM)
+ CASE("dxil", DXIL)
+ CASE("dxil-assembly", DXIL_ASM)
+ CASE("dxil-asm", DXIL_ASM)
+ CASE("c", C_SOURCE)
+ CASE("cpp", CPP_SOURCE)
+
+ #undef CASE
+ /* else */
+ {
+ sink->diagnose(SourceLoc(), Diagnostics::unknownCodeGenerationTarget, name);
+ return SLANG_FAIL;
+ }
+
+ RawTarget rawTarget;
+ rawTarget.format = CodeGenTarget(format);
+
+ rawTargets.add(rawTarget);
+ }
+ // A "profile" can specify both a general capability level for
+ // a target, and also (as a legacy/compatibility feature) a
+ // specific stage to use for an entry point.
+ else if (argStr == "-profile")
+ {
+ String name;
+ SLANG_RETURN_ON_FAIL(tryReadCommandLineArgument(sink, arg, &argCursor, argEnd, name));
+
+ SlangProfileID profileID = spFindProfile(session, name.begin());
+ if( profileID == SLANG_PROFILE_UNKNOWN )
+ {
+ sink->diagnose(SourceLoc(), Diagnostics::unknownProfile, name);
+ return SLANG_FAIL;
+ }
+ else
+ {
+ auto profile = Profile(profileID);
+
+ setProfileVersion(getCurrentTarget(), profile.GetVersion());
+
+ // A `-profile` option that also specifies a stage (e.g., `-profile vs_5_0`)
+ // should be treated like a composite (e.g., `-profile sm_5_0 -stage vertex`)
+ auto stage = profile.GetStage();
+ if(stage != Stage::Unknown)
+ {
+ setStage(getCurrentEntryPoint(), stage);
+ }
+ }
+ }
+ else if (argStr == "-stage")
+ {
+ String name;
+ SLANG_RETURN_ON_FAIL(tryReadCommandLineArgument(sink, arg, &argCursor, argEnd, name));
+
+ Stage stage = findStageByName(name);
+ if( stage == Stage::Unknown )
+ {
+ sink->diagnose(SourceLoc(), Diagnostics::unknownStage, name);
+ return SLANG_FAIL;
+ }
+ else
+ {
+ setStage(getCurrentEntryPoint(), stage);
+ }
+ }
+ else if (argStr == "-entry")
+ {
+ String name;
+ SLANG_RETURN_ON_FAIL(tryReadCommandLineArgument(sink, arg, &argCursor, argEnd, name));
+
+ RawEntryPoint rawEntryPoint;
+ rawEntryPoint.name = name;
+ rawEntryPoint.translationUnitIndex = currentTranslationUnitIndex;
+
+ rawEntryPoints.add(rawEntryPoint);
+ }
+ else if (argStr == "-pass-through")
+ {
+ String name;
+ SLANG_RETURN_ON_FAIL(tryReadCommandLineArgument(sink, arg, &argCursor, argEnd, name));
+
+ SlangPassThrough passThrough = SLANG_PASS_THROUGH_NONE;
+ if (name == "fxc") { passThrough = SLANG_PASS_THROUGH_FXC; }
+ else if (name == "dxc") { passThrough = SLANG_PASS_THROUGH_DXC; }
+ else if (name == "glslang") { passThrough = SLANG_PASS_THROUGH_GLSLANG; }
+ else
+ {
+ sink->diagnose(SourceLoc(), Diagnostics::unknownPassThroughTarget, name);
+ return SLANG_FAIL;
+ }
+
+ spSetPassThrough(
+ compileRequest,
+ passThrough);
+ }
+ else if (argStr == "-dxc-path")
+ {
+ String name;
+ SLANG_RETURN_ON_FAIL(tryReadCommandLineArgument(sink, arg, &argCursor, argEnd, name));
+ addSharedLibraryPath(SharedLibraryType::Dxc, name);
+ addSharedLibraryPath(SharedLibraryType::Dxil, name);
+ }
+ else if (argStr == "-glslang-path")
+ {
+ String name;
+ SLANG_RETURN_ON_FAIL(tryReadCommandLineArgument(sink, arg, &argCursor, argEnd, name));
+ addSharedLibraryPath(SharedLibraryType::Glslang, name);
+ }
+ else if (argStr == "-fxc-path")
+ {
+ String name;
+ SLANG_RETURN_ON_FAIL(tryReadCommandLineArgument(sink, arg, &argCursor, argEnd, name));
+ addSharedLibraryPath(SharedLibraryType::Fxc, name);
+ }
+ else if (argStr[1] == 'D')
+ {
+ // The value to be defined might be part of the same option, as in:
+ // -DFOO
+ // or it might come separately, as in:
+ // -D FOO
+ char const* defineStr = arg + 2;
+ if (defineStr[0] == 0)
+ {
+ // Need to read another argument from the command line
+ SLANG_RETURN_ON_FAIL(tryReadCommandLineArgumentRaw(sink, arg, &argCursor, argEnd, &defineStr));
+ }
+ // The string that sets up the define can have an `=` between
+ // the name to be defined and its value, so we search for one.
+ char const* eqPos = nullptr;
+ for(char const* dd = defineStr; *dd; ++dd)
+ {
+ if (*dd == '=')
+ {
+ eqPos = dd;
+ break;
+ }
+ }
+
+ // Now set the preprocessor define
+ //
+ if (eqPos)
+ {
+ // If we found an `=`, we split the string...
+
+ spAddPreprocessorDefine(
+ compileRequest,
+ String(defineStr, eqPos).begin(),
+ String(eqPos+1).begin());
+ }
+ else
+ {
+ // If there was no `=`, then just #define it to an empty string
+
+ spAddPreprocessorDefine(
+ compileRequest,
+ String(defineStr).begin(),
+ "");
+ }
+ }
+ else if (argStr[1] == 'I')
+ {
+ // The value to be defined might be part of the same option, as in:
+ // -IFOO
+ // or it might come separately, as in:
+ // -I FOO
+ // (see handling of `-D` above)
+ char const* includeDirStr = arg + 2;
+ if (includeDirStr[0] == 0)
+ {
+ // Need to read another argument from the command line
+ SLANG_RETURN_ON_FAIL(tryReadCommandLineArgumentRaw(sink, arg, &argCursor, argEnd, &includeDirStr));
+ }
+
+ spAddSearchPath(
+ compileRequest,
+ String(includeDirStr).begin());
+ }
+ //
+ // A `-o` option is used to specify a desired output file.
+ else if (argStr == "-o")
+ {
+ char const* outputPath = nullptr;
+ SLANG_RETURN_ON_FAIL(tryReadCommandLineArgumentRaw(sink, arg, &argCursor, argEnd, &outputPath));
+ if (!outputPath) continue;
+
+ addOutputPath(outputPath);
+ }
+ else if(argStr == "-matrix-layout-row-major")
+ {
+ defaultMatrixLayoutMode = kMatrixLayoutMode_RowMajor;
+ }
+ else if(argStr == "-matrix-layout-column-major")
+ {
+ defaultMatrixLayoutMode = kMatrixLayoutMode_ColumnMajor;
+ }
+ else if(argStr == "-line-directive-mode")
+ {
+ String name;
+ SLANG_RETURN_ON_FAIL(tryReadCommandLineArgument(sink, arg, &argCursor, argEnd, name));
+
+ SlangLineDirectiveMode mode = SLANG_LINE_DIRECTIVE_MODE_DEFAULT;
+ if(name == "none")
+ {
+ mode = SLANG_LINE_DIRECTIVE_MODE_NONE;
+ }
+ else
+ {
+ sink->diagnose(SourceLoc(), Diagnostics::unknownLineDirectiveMode, name);
+ return SLANG_FAIL;
+ }
+
+ spSetLineDirectiveMode(compileRequest, mode);
+
+ }
+ else if( argStr == "-fp-mode" || argStr == "-floating-point-mode" )
+ {
+ String name;
+ SLANG_RETURN_ON_FAIL(tryReadCommandLineArgument(sink, arg, &argCursor, argEnd, name));
+
+ FloatingPointMode mode = FloatingPointMode::Default;
+ if(name == "fast")
+ {
+ mode = FloatingPointMode::Fast;
+ }
+ else if(name == "precise")
+ {
+ mode = FloatingPointMode::Precise;
+ }
+ else
+ {
+ sink->diagnose(SourceLoc(), Diagnostics::unknownFloatingPointMode, name);
+ return SLANG_FAIL;
+ }
+
+ setFloatingPointMode(getCurrentTarget(), mode);
+ }
+ else if( argStr[1] == 'O' )
+ {
+ char const* name = arg + 2;
+ SlangOptimizationLevel level = SLANG_OPTIMIZATION_LEVEL_DEFAULT;
+
+ bool invalidOptimizationLevel = strlen(name) > 2;
+ switch( name[0] )
+ {
+ case '0': level = SLANG_OPTIMIZATION_LEVEL_NONE; break;
+ case '1': level = SLANG_OPTIMIZATION_LEVEL_DEFAULT; break;
+ case '2': level = SLANG_OPTIMIZATION_LEVEL_HIGH; break;
+ case '3': level = SLANG_OPTIMIZATION_LEVEL_MAXIMAL; break;
+ case 0 : level = SLANG_OPTIMIZATION_LEVEL_DEFAULT; break;
+ default:
+ invalidOptimizationLevel = true;
+ break;
+ }
+ if( invalidOptimizationLevel )
+ {
+ sink->diagnose(SourceLoc(), Diagnostics::unknownOptimiziationLevel, name);
+ return SLANG_FAIL;
+ }
+
+ spSetOptimizationLevel(compileRequest, level);
+ }
+
+ // Note: unlike with `-O` above, we have to consider that other
+ // options might have names that start with `-g` and so cannot
+ // just detect it as a prefix.
+ else if( argStr == "-g" || argStr == "-g2" )
+ {
+ spSetDebugInfoLevel(compileRequest, SLANG_DEBUG_INFO_LEVEL_STANDARD);
+ }
+ else if( argStr == "-g0" )
+ {
+ spSetDebugInfoLevel(compileRequest, SLANG_DEBUG_INFO_LEVEL_NONE);
+ }
+ else if( argStr == "-g1" )
+ {
+ spSetDebugInfoLevel(compileRequest, SLANG_DEBUG_INFO_LEVEL_MINIMAL);
+ }
+ else if( argStr == "-g3" )
+ {
+ spSetDebugInfoLevel(compileRequest, SLANG_DEBUG_INFO_LEVEL_MAXIMAL);
+ }
+ else if( argStr == "-default-image-format-unknown" )
+ {
+ requestImpl->getBackEndReq()->useUnknownImageFormatAsDefault = true;
+ }
+ else if (argStr == "--")
+ {
+ // The `--` option causes us to stop trying to parse options,
+ // and treat the rest of the command line as input file names:
+ while (argCursor != argEnd)
+ {
+ SLANG_RETURN_ON_FAIL(addInputPath(*argCursor++));
+ }
+ break;
+ }
+ else
+ {
+ sink->diagnose(SourceLoc(), Diagnostics::unknownCommandLineOption, argStr);
+ // TODO: print a usage message
+ return SLANG_FAIL;
+ }
+ }
+ else
+ {
+ SLANG_RETURN_ON_FAIL(addInputPath(arg));
+ }
+ }
+
+ spSetCompileFlags(compileRequest, flags);
+
+ // As a compatability feature, if the user didn't list any explicit entry
+ // point names, *and* they are compiling a single translation unit, *and* they
+ // have either specified a stage, or we can assume one from the naming
+ // of the translation unit, then we assume they wanted to compile a single
+ // entry point named `main`.
+ //
+ if(rawEntryPoints.getCount() == 0
+ && rawTranslationUnits.getCount() == 1
+ && (defaultEntryPoint.stage != Stage::Unknown
+ || rawTranslationUnits[0].impliedStage != Stage::Unknown))
+ {
+ RawEntryPoint entry;
+ entry.name = "main";
+ entry.translationUnitIndex = 0;
+ rawEntryPoints.add(entry);
+ }
+
+ // If the user (manually or implicitly) specified only a single entry point,
+ // then we allow the associated stage to be specified either before or after
+ // the entry point. This means that if there is a stage attached
+ // to the "default" entry point, we should copy it over to the
+ // explicit one.
+ //
+ if( rawEntryPoints.getCount() == 1 )
+ {
+ if(defaultEntryPoint.stage != Stage::Unknown)
+ {
+ setStage(getCurrentEntryPoint(), defaultEntryPoint.stage);
+ }
+
+ if(defaultEntryPoint.redundantStageSet)
+ getCurrentEntryPoint()->redundantStageSet = true;
+ if(defaultEntryPoint.conflictingStagesSet)
+ getCurrentEntryPoint()->conflictingStagesSet = true;
+ }
+ else
+ {
+ // If the "default" entry point has had a stage (or
+ // other state, if we add other per-entry-point state)
+ // specified, but there is more than one entry point,
+ // then that state doesn't apply to anything and we
+ // should issue an error to tell the user something
+ // funky is going on.
+ //
+ if( defaultEntryPoint.stage != Stage::Unknown )
+ {
+ if( rawEntryPoints.getCount() == 0 )
+ {
+ sink->diagnose(SourceLoc(), Diagnostics::stageSpecificationIgnoredBecauseNoEntryPoints);
+ }
+ else
+ {
+ sink->diagnose(SourceLoc(), Diagnostics::stageSpecificationIgnoredBecauseBeforeAllEntryPoints);
+ }
+ }
+ }
+
+ // Slang requires that every explicit entry point indicate the translation
+ // unit it comes from. If there is only one translation unit specified,
+ // then implicitly all entry points come from it.
+ //
+ if(translationUnitCount == 1)
+ {
+ for( auto& entryPoint : rawEntryPoints )
+ {
+ entryPoint.translationUnitIndex = 0;
+ }
+ }
+ else
+ {
+ // Otherwise, we require that all entry points be specified after
+ // the translation unit to which tye belong.
+ bool anyEntryPointWithoutTranslationUnit = false;
+ for( auto& entryPoint : rawEntryPoints )
+ {
+ // Skip entry points that are already associated with a translation unit...
+ if( entryPoint.translationUnitIndex != -1 )
+ continue;
+
+ anyEntryPointWithoutTranslationUnit = true;
+ }
+ if( anyEntryPointWithoutTranslationUnit )
+ {
+ sink->diagnose(SourceLoc(), Diagnostics::entryPointsNeedToBeAssociatedWithTranslationUnits);
+ return SLANG_FAIL;
+ }
+ }
+
+ // Now that entry points are associated with translation units,
+ // we can make one additional pass where if an entry point has
+ // no specified stage, but the nameing of its translation unit
+ // implies a stage, we will use that (a manual `-stage` annotation
+ // will always win out in such a case).
+ //
+ for( auto& rawEntryPoint : rawEntryPoints )
+ {
+ // Skip entry points that already have a stage.
+ if(rawEntryPoint.stage != Stage::Unknown)
+ continue;
+
+ // Sanity check: don't process entry points with no associated translation unit.
+ if( rawEntryPoint.translationUnitIndex == -1 )
+ continue;
+
+ auto impliedStage = rawTranslationUnits[rawEntryPoint.translationUnitIndex].impliedStage;
+ if(impliedStage != Stage::Unknown)
+ rawEntryPoint.stage = impliedStage;
+ }
+
+ // Note: it is possible that some entry points still won't have associated
+ // stages at this point, but we don't want to error out here, because
+ // those entry points might get stages later, as part of semantic checking,
+ // if the corresponding function has a `[shader("...")]` attribute.
+
+ // Now that we've tried to establish stages for entry points, we can
+ // issue diagnostics for cases where stages were set redundantly or
+ // in conflicting ways.
+ //
+ for( auto& rawEntryPoint : rawEntryPoints )
+ {
+ if( rawEntryPoint.conflictingStagesSet )
+ {
+ sink->diagnose(SourceLoc(), Diagnostics::conflictingStagesForEntryPoint, rawEntryPoint.name);
+ }
+ else if( rawEntryPoint.redundantStageSet )
+ {
+ sink->diagnose(SourceLoc(), Diagnostics::sameStageSpecifiedMoreThanOnce, rawEntryPoint.stage, rawEntryPoint.name);
+ }
+ else if( rawEntryPoint.translationUnitIndex != -1 )
+ {
+ // As a quality-of-life feature, if the file name implies a particular
+ // stage, but the user manually specified something different for
+ // their entry point, give a warning in case they made a mistake.
+
+ auto& rawTranslationUnit = rawTranslationUnits[rawEntryPoint.translationUnitIndex];
+ if( rawTranslationUnit.impliedStage != Stage::Unknown
+ && rawEntryPoint.stage != Stage::Unknown
+ && rawTranslationUnit.impliedStage != rawEntryPoint.stage )
+ {
+ sink->diagnose(SourceLoc(), Diagnostics::explicitStageDoesntMatchImpliedStage, rawEntryPoint.name, rawEntryPoint.stage, rawTranslationUnit.impliedStage);
+ }
+ }
+ }
+
+ // If the user is requesting code generation via pass-through,
+ // then any entry points they specify need to have a stage set,
+ // because fxc/dxc/glslang don't have a facility for taking
+ // a named entry point and pulling its stage from an attribute.
+ //
+ if( requestImpl->passThrough != PassThroughMode::None )
+ {
+ for( auto& rawEntryPoint : rawEntryPoints )
+ {
+ if( rawEntryPoint.stage == Stage::Unknown )
+ {
+ sink->diagnose(SourceLoc(), Diagnostics::noStageSpecifiedInPassThroughMode, rawEntryPoint.name);
+ }
+ }
+ }
+
+ // We now have inferred enough information to add the
+ // entry points to our compile request.
+ //
+ for( auto& rawEntryPoint : rawEntryPoints )
+ {
+ if(rawEntryPoint.translationUnitIndex < 0)
+ continue;
+
+ auto translationUnitID = rawTranslationUnits[rawEntryPoint.translationUnitIndex].translationUnitID;
+
+ int entryPointID = spAddEntryPoint(
+ compileRequest,
+ translationUnitID,
+ rawEntryPoint.name.begin(),
+ SlangStage(rawEntryPoint.stage));
+
+ rawEntryPoint.entryPointID = entryPointID;
+ }
+
+ // We are going to build a mapping from target formats to the
+ // target that handles that format.
+ Dictionary<CodeGenTarget, int> mapFormatToTargetIndex;
+
+ // If there was no explicit `-target` specified, then we will look
+ // at the `-o` options to see what we can infer.
+ //
+ if(rawTargets.getCount() == 0)
+ {
+ for(auto& rawOutput : rawOutputs)
+ {
+ // Some outputs don't imply a target format, and we shouldn't use those for inference.
+ auto impliedFormat = rawOutput.impliedFormat;
+ if( impliedFormat == CodeGenTarget::Unknown )
+ continue;
+
+ int targetIndex = 0;
+ if( !mapFormatToTargetIndex.TryGetValue(impliedFormat, targetIndex) )
+ {
+ targetIndex = (int) rawTargets.getCount();
+
+ RawTarget rawTarget;
+ rawTarget.format = impliedFormat;
+ rawTargets.add(rawTarget);
+
+ mapFormatToTargetIndex[impliedFormat] = targetIndex;
+ }
+
+ rawOutput.targetIndex = targetIndex;
+ }
+ }
+ else
+ {
+ // If there were explicit targets, then we will use those, but still
+ // build up our mapping. We should object if the same target format
+ // is specified more than once (just because of the ambiguities
+ // it will create).
+ //
+ int targetCount = (int) rawTargets.getCount();
+ for(int targetIndex = 0; targetIndex < targetCount; ++targetIndex)
+ {
+ auto format = rawTargets[targetIndex].format;
+
+ if( mapFormatToTargetIndex.ContainsKey(format) )
+ {
+ sink->diagnose(SourceLoc(), Diagnostics::duplicateTargets, format);
+ }
+ else
+ {
+ mapFormatToTargetIndex[format] = targetIndex;
+ }
+ }
+ }
+
+ // If we weren't able to infer any targets from output paths (perhaps
+ // because there were no output paths), but there was a profile specified,
+ // then we can try to infer a target from the profile.
+ //
+ if( rawTargets.getCount() == 0
+ && defaultTarget.profileVersion != ProfileVersion::Unknown
+ && !defaultTarget.conflictingProfilesSet)
+ {
+ // Let's see if the chosen profile allows us to infer
+ // the code gen target format that the user probably meant.
+ //
+ CodeGenTarget inferredFormat = CodeGenTarget::Unknown;
+ auto profileVersion = defaultTarget.profileVersion;
+ switch( Profile(profileVersion).getFamily() )
+ {
+ default:
+ break;
+
+ // For GLSL profile versions, we will assume SPIR-V
+ // is the output format the user intended.
+ case ProfileFamily::GLSL:
+ inferredFormat = CodeGenTarget::SPIRV;
+ break;
+
+ // For DX profile versions, we will assume that the
+ // user wants DXIL for Shader Model 6.0 and up,
+ // and DXBC for all earlier versions.
+ //
+ // Note: There is overlap where both DXBC and DXIL
+ // nominally support SM 5.1, but in general we
+ // expect users to prefer to make a clean break
+ // at SM 6.0. Anybody who cares about the overlap
+ // cases should manually specify `-target dxil`.
+ //
+ case ProfileFamily::DX:
+ if( profileVersion >= ProfileVersion::DX_6_0 )
+ {
+ inferredFormat = CodeGenTarget::DXIL;
+ }
+ else
+ {
+ inferredFormat = CodeGenTarget::DXBytecode;
+ }
+ break;
+ }
+
+ if( inferredFormat != CodeGenTarget::Unknown )
+ {
+ RawTarget rawTarget;
+ rawTarget.format = inferredFormat;
+ rawTargets.add(rawTarget);
+ }
+ }
+
+ // Similar to the case for entry points, if there is a single target,
+ // then we allow some of its options to come from the "default"
+ // target state.
+ if(rawTargets.getCount() == 1)
+ {
+ if(defaultTarget.profileVersion != ProfileVersion::Unknown)
+ {
+ setProfileVersion(getCurrentTarget(), defaultTarget.profileVersion);
+ }
+
+ getCurrentTarget()->targetFlags |= defaultTarget.targetFlags;
+
+ if( defaultTarget.floatingPointMode != FloatingPointMode::Default )
+ {
+ setFloatingPointMode(getCurrentTarget(), defaultTarget.floatingPointMode);
+ }
+ }
+ else
+ {
+ // If the "default" target has had a profile (or other state)
+ // specified, but there is != 1 taget, then that state doesn't
+ // apply to anythign and we should give the user an error.
+ //
+ if( defaultTarget.profileVersion != ProfileVersion::Unknown )
+ {
+ if( rawTargets.getCount() == 0 )
+ {
+ // This should only happen if there were multiple `-profile` options,
+ // so we didn't try to infer a target, or if the `-profile` option
+ // somehow didn't imply a target.
+ //
+ sink->diagnose(SourceLoc(), Diagnostics::profileSpecificationIgnoredBecauseNoTargets);
+ }
+ else
+ {
+ sink->diagnose(SourceLoc(), Diagnostics::profileSpecificationIgnoredBecauseBeforeAllTargets);
+ }
+ }
+
+ if( defaultTarget.targetFlags )
+ {
+ if( rawTargets.getCount() == 0 )
+ {
+ sink->diagnose(SourceLoc(), Diagnostics::targetFlagsIgnoredBecauseNoTargets);
+ }
+ else
+ {
+ sink->diagnose(SourceLoc(), Diagnostics::targetFlagsIgnoredBecauseBeforeAllTargets);
+ }
+ }
+
+ if( defaultTarget.floatingPointMode != FloatingPointMode::Default )
+ {
+ if( rawTargets.getCount() == 0 )
+ {
+ sink->diagnose(SourceLoc(), Diagnostics::targetFlagsIgnoredBecauseNoTargets);
+ }
+ else
+ {
+ sink->diagnose(SourceLoc(), Diagnostics::targetFlagsIgnoredBecauseBeforeAllTargets);
+ }
+ }
+
+ }
+
+ for(auto& rawTarget : rawTargets)
+ {
+ if( rawTarget.conflictingProfilesSet )
+ {
+ sink->diagnose(SourceLoc(), Diagnostics::conflictingProfilesSpecifiedForTarget, rawTarget.format);
+ }
+ else if( rawTarget.redundantProfileSet )
+ {
+ sink->diagnose(SourceLoc(), Diagnostics::sameProfileSpecifiedMoreThanOnce, rawTarget.profileVersion, rawTarget.format);
+ }
+ }
+
+ // TODO: do we need to require that a target must have a profile specified,
+ // or will we continue to allow the profile to be inferred from the target?
+
+ // We now have enough information to go ahead and declare the targets
+ // through the Slang API:
+ //
+ for(auto& rawTarget : rawTargets)
+ {
+ int targetID = spAddCodeGenTarget(compileRequest, SlangCompileTarget(rawTarget.format));
+ rawTarget.targetID = targetID;
+
+ if( rawTarget.profileVersion != ProfileVersion::Unknown )
+ {
+ spSetTargetProfile(compileRequest, targetID, Profile(rawTarget.profileVersion).raw);
+ }
+
+ if( rawTarget.targetFlags )
+ {
+ spSetTargetFlags(compileRequest, targetID, rawTarget.targetFlags);
+ }
+
+ if( rawTarget.floatingPointMode != FloatingPointMode::Default )
+ {
+ spSetTargetFloatingPointMode(compileRequest, targetID, SlangFloatingPointMode(rawTarget.floatingPointMode));
+ }
+ }
+
+ if(defaultMatrixLayoutMode != SLANG_MATRIX_LAYOUT_MODE_UNKNOWN)
+ {
+ spSetMatrixLayoutMode(compileRequest, defaultMatrixLayoutMode);
+ }
+
+ // Next we need to sort out the output files specified with `-o`, and
+ // figure out which entry point and/or target they apply to.
+ //
+ // If there is only a single entry point, then that is automatically
+ // the entry point that should be associated with all outputs.
+ //
+ if( rawEntryPoints.getCount() == 1 )
+ {
+ for( auto& rawOutput : rawOutputs )
+ {
+ rawOutput.entryPointIndex = 0;
+ }
+ }
+ //
+ // Similarly, if there is only one target, then all outputs must
+ // implicitly appertain to that target.
+ //
+ if( rawTargets.getCount() == 1 )
+ {
+ for( auto& rawOutput : rawOutputs )
+ {
+ rawOutput.targetIndex = 0;
+ }
+ }
+
+ // Consider the output files specified via `-o` and try to figure
+ // out how to deal with them.
+ //
+ for(auto& rawOutput : rawOutputs)
+ {
+ // For now, all output formats need to be tightly bound to
+ // both a target and an entry point (down the road we will
+ // need to support output formats that can store multiple
+ // entry points in one file).
+
+ // If an output doesn't have a target associated with
+ // it, then search for the target with the matching format.
+ if( rawOutput.targetIndex == -1 )
+ {
+ auto impliedFormat = rawOutput.impliedFormat;
+ int targetIndex = -1;
+
+ if(impliedFormat == CodeGenTarget::Unknown)
+ {
+ // If we hit this case, then it means that we need to pick the
+ // target to assocaite with this output based on its implied
+ // format, but the file path doesn't direclty imply a format
+ // (it doesn't have a suffix like `.spv` that tells us what to write).
+ //
+ sink->diagnose(SourceLoc(), Diagnostics::cannotDeduceOutputFormatFromPath, rawOutput.path);
+ }
+ else if( mapFormatToTargetIndex.TryGetValue(rawOutput.impliedFormat, targetIndex) )
+ {
+ rawOutput.targetIndex = targetIndex;
+ }
+ else
+ {
+ sink->diagnose(SourceLoc(), Diagnostics::cannotMatchOutputFileToTarget, rawOutput.path, rawOutput.impliedFormat);
+ }
+ }
+
+ // We won't do any searching to match an output file
+ // with an entry point, since the case of a single entry
+ // point was handled above, and the user is expected to
+ // follow the ordering rules when using multiple entry points.
+ //
+ if( rawOutput.entryPointIndex == -1 )
+ {
+ sink->diagnose(SourceLoc(), Diagnostics::cannotMatchOutputFileToEntryPoint, rawOutput.path);
+ }
+ }
+
+ // Now that we've diagnosed the output paths, we can add them
+ // to the compile request at the appropriate locations.
+ //
+ // We will consider the output files specified via `-o` and try to figure
+ // out how to deal with them.
+ //
+ for(auto& rawOutput : rawOutputs)
+ {
+ if(rawOutput.targetIndex == -1) continue;
+ if(rawOutput.entryPointIndex == -1) continue;
+
+ auto targetID = rawTargets[rawOutput.targetIndex].targetID;
+ Int entryPointID = rawEntryPoints[rawOutput.entryPointIndex].entryPointID;
+
+ auto target = requestImpl->getLinkage()->targets[targetID];
+ auto entryPointReq = requestImpl->getFrontEndReq()->getEntryPointReqs()[entryPointID];
+
+ RefPtr<EndToEndCompileRequest::TargetInfo> targetInfo;
+ if( !requestImpl->targetInfos.TryGetValue(target, targetInfo) )
+ {
+ targetInfo = new EndToEndCompileRequest::TargetInfo();
+ requestImpl->targetInfos[target] = targetInfo;
+ }
+
+ String outputPath;
+ if( targetInfo->entryPointOutputPaths.ContainsKey(entryPointID) )
+ {
+ sink->diagnose(SourceLoc(), Diagnostics::duplicateOutputPathsForEntryPointAndTarget, entryPointReq->getName(), target->getTarget());
+ }
+ else
+ {
+ targetInfo->entryPointOutputPaths[entryPointID] = rawOutput.path;
+ }
+ }
+
+ if (sharedLibraryLoader)
+ {
+ spSessionSetSharedLibraryLoader(session, sharedLibraryLoader);
+ }
+
+ return (sink->GetErrorCount() == 0) ? SLANG_OK : SLANG_FAIL;
+ }
+};
+
+
+SlangResult parseOptions(
+ SlangCompileRequest* compileRequestIn,
+ int argc,
+ char const* const* argv)
+{
+ Slang::EndToEndCompileRequest* compileRequest = (Slang::EndToEndCompileRequest*) compileRequestIn;
+
+ OptionsParser parser;
+ parser.compileRequest = compileRequestIn;
+ parser.requestImpl = compileRequest;
+ parser.session = (SlangSession*)compileRequest->getSession();
+
+ Result res = parser.parse(argc, argv);
+
+ DiagnosticSink* sink = compileRequest->getSink();
+ if (sink->GetErrorCount() > 0)
+ {
+ // Put the errors in the diagnostic
+ compileRequest->mDiagnosticOutput = sink->outputBuffer.ProduceString();
+ }
+
+ return res;
+}
+
+
+} // namespace Slang
+
+SLANG_API SlangResult spProcessCommandLineArguments(
+ SlangCompileRequest* request,
+ char const* const* args,
+ int argCount)
+{
+ return Slang::parseOptions(request, argCount, args);
+}