diff options
| author | jsmall-nvidia <jsmall@nvidia.com> | 2019-05-31 17:20:37 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-05-31 17:20:37 -0400 |
| commit | 6cbc3929a54d37bd23cb5efa8e3320ba02f78b2f (patch) | |
| tree | 5a23cb47782e9e2a77762c90dd35da1005eba8d0 /source/slang/slang-options.cpp | |
| parent | b81ff3ef968d1cc4e954b31a1812b3c391d17b02 (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.cpp | 1356 |
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); +} |
