diff options
Diffstat (limited to 'source/slang/options.cpp')
| -rw-r--r-- | source/slang/options.cpp | 1030 |
1 files changed, 629 insertions, 401 deletions
diff --git a/source/slang/options.cpp b/source/slang/options.cpp index 944f66403..485b7d78b 100644 --- a/source/slang/options.cpp +++ b/source/slang/options.cpp @@ -43,74 +43,142 @@ struct OptionsParser Slang::CompileRequest* requestImpl = nullptr; + // 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; - SlangProfileID implicitProfile; - int translationUnitIndex; - }; - // Collect translation units so that we can futz with them later - List<RawTranslationUnit> rawTranslationUnits; + // 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; - struct RawEntryPoint - { - String name; - Profile profile; - int translationUnitIndex = -1; - int outputPathIndex = -1; + // 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; - // Collect entry point names, so that we can associate them - // with entry points later... - List<RawEntryPoint> rawEntryPoints; + // 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; - // If we already have a translation unit for Slang code, then this will give its index. - // If not, it will be `-1`. - int slangTranslationUnit = -1; - int translationUnitCount = 0; - int currentTranslationUnitIndex = -1; - - // The currently specified '-profile' and/or '-stage' options. - Profile currentProfile = Profile::Unknown; + 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 assocaited 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; - // How many times were `-profile` and '-stage' options given? - int profileOptionCount = 0; - int stageOptionCount = 0; + // 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; - SlangTargetFlags targetFlags = 0; - struct RawOutputPath + struct RawOutput { - String path; - SlangCompileTarget target; + String path; + CodeGenTarget impliedFormat = CodeGenTarget::Unknown; + int targetIndex = -1; + int entryPointIndex = -1; }; + List<RawOutput> rawOutputs; - List<RawOutputPath> rawOutputPaths; + struct RawTarget + { + CodeGenTarget format = CodeGenTarget::Unknown; + ProfileVersion profileVersion = ProfileVersion::Unknown; + SlangTargetFlags targetFlags = 0; + int targetID = -1; - SlangCompileTarget chosenTarget = SLANG_TARGET_NONE; + // State for tracking command-line errors + bool conflictingProfilesSet = false; + bool redundantProfileSet = false; + + }; + List<RawTarget> rawTargets; + + RawTarget defaultTarget; int addTranslationUnit( SlangSourceLanguage language, - SlangProfileID implicitProfile = SLANG_PROFILE_UNKNOWN) + Stage impliedStage) { - auto translationUnitIndex = spAddTranslationUnit(compileRequest, language, nullptr); + auto translationUnitIndex = rawTranslationUnits.Count(); + auto translationUnitID = spAddTranslationUnit(compileRequest, language, nullptr); - SLANG_RELEASE_ASSERT(UInt(translationUnitIndex) == rawTranslationUnits.Count()); + // 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(UInt(translationUnitID) == translationUnitIndex); RawTranslationUnit rawTranslationUnit; rawTranslationUnit.sourceLanguage = language; - rawTranslationUnit.implicitProfile = implicitProfile; - rawTranslationUnit.translationUnitIndex = translationUnitIndex; + rawTranslationUnit.translationUnitID = translationUnitID; + rawTranslationUnit.impliedStage = impliedStage; rawTranslationUnits.Add(rawTranslationUnit); - return translationUnitIndex; + return int(translationUnitIndex); } void addInputSlangPath( @@ -118,32 +186,32 @@ struct OptionsParser { // 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( slangTranslationUnit == -1 ) + if( slangTranslationUnitIndex == -1 ) { translationUnitCount++; - slangTranslationUnit = addTranslationUnit(SLANG_SOURCE_LANGUAGE_SLANG); + slangTranslationUnitIndex = addTranslationUnit(SLANG_SOURCE_LANGUAGE_SLANG, Stage::Unknown); } spAddTranslationUnitSourceFile( compileRequest, - slangTranslationUnit, + rawTranslationUnits[slangTranslationUnitIndex].translationUnitID, path.begin()); // Set the translation unit to be used by subsequent entry points - currentTranslationUnitIndex = slangTranslationUnit; + currentTranslationUnitIndex = slangTranslationUnitIndex; } void addInputForeignShaderPath( String const& path, SlangSourceLanguage language, - SlangProfileID implicitProfile = SLANG_PROFILE_UNKNOWN) + Stage impliedStage) { translationUnitCount++; - currentTranslationUnitIndex = addTranslationUnit(language, implicitProfile); + currentTranslationUnitIndex = addTranslationUnit(language, impliedStage); spAddTranslationUnitSourceFile( compileRequest, - currentTranslationUnitIndex, + rawTranslationUnits[currentTranslationUnitIndex].translationUnitID, path.begin()); } @@ -175,25 +243,39 @@ struct OptionsParser return Profile::Unknown; } - static SlangSourceLanguage findSourceLanguageFromPath(const String& path, SlangProfileID* profileOut) + static SlangSourceLanguage findSourceLanguageFromPath(const String& path, Stage& outImpliedStage) { - *profileOut = SLANG_PROFILE_UNKNOWN; - - if (path.EndsWith(".hlsl") || - path.EndsWith(".fx")) + struct Entry { - return SLANG_SOURCE_LANGUAGE_HLSL; - } - if (path.EndsWith(".glsl")) + const char* ext; + SlangSourceLanguage sourceLanguage; + SlangStage impliedStage; + }; + + static const Entry entries[] = { - return SLANG_SOURCE_LANGUAGE_GLSL; - } + { ".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 }, + }; - Profile::RawVal profile = findGlslProfileFromPath(path); - if (profile != Profile::Unknown) + for (int i = 0; i < SLANG_COUNT_OF(entries); ++i) { - *profileOut = SlangProfileID(profile); - return SLANG_SOURCE_LANGUAGE_GLSL; + const Entry& entry = entries[i]; + if (path.EndsWith(entry.ext)) + { + outImpliedStage = Stage(entry.impliedStage); + return entry.sourceLanguage; + } } return SLANG_SOURCE_LANGUAGE_UNKNOWN; } @@ -213,9 +295,9 @@ struct OptionsParser addInputSlangPath(path); return SLANG_OK; } - - SlangProfileID profileID; - SlangSourceLanguage sourceLanguage = findSourceLanguageFromPath(path, &profileID); + + Stage impliedStage = Stage::Unknown; + SlangSourceLanguage sourceLanguage = findSourceLanguageFromPath(path, impliedStage); if (sourceLanguage == SLANG_SOURCE_LANGUAGE_UNKNOWN) { @@ -223,20 +305,19 @@ struct OptionsParser return SLANG_FAIL; } - addInputForeignShaderPath(path, sourceLanguage, profileID); + addInputForeignShaderPath(path, sourceLanguage, impliedStage); return SLANG_OK; } void addOutputPath( - String const& path, - SlangCompileTarget target) + String const& path, + CodeGenTarget impliedFormat) { - RawOutputPath rawOutputPath; - rawOutputPath.path = path; - rawOutputPath.target = target; - - rawOutputPaths.Add(rawOutputPath); + RawOutput rawOutput; + rawOutput.path = path; + rawOutput.impliedFormat = impliedFormat; + rawOutputs.Add(rawOutput); } void addOutputPath(char const* inPath) @@ -245,7 +326,7 @@ struct OptionsParser if (!inPath) {} #define CASE(EXT, TARGET) \ - else if(path.EndsWith(EXT)) do { addOutputPath(path, SLANG_##TARGET); } while(0) + else if(path.EndsWith(EXT)) do { addOutputPath(path, CodeGenTarget(SLANG_##TARGET)); } while(0) CASE(".hlsl", HLSL); CASE(".fx", HLSL); @@ -253,6 +334,9 @@ struct OptionsParser 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); @@ -275,8 +359,47 @@ struct OptionsParser { // Allow an unknown-format `-o`, assuming we get a target format // from another argument. - addOutputPath(path, SLANG_TARGET_UNKNOWN); + addOutputPath(path, CodeGenTarget::Unknown); + } + } + + RawEntryPoint* getCurrentEntryPoint() + { + auto rawEntryPointCount = rawEntryPoints.Count(); + 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.Count(); + 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; } SlangResult parse( @@ -300,14 +423,6 @@ struct OptionsParser { String argStr = String(arg); - // The argument looks like an option, so try to parse it. -// if (argStr == "-outdir") -// outputDir = tryReadCommandLineArgument(arg, &argCursor, argEnd); -// if (argStr == "-out") -// options.outputName = tryReadCommandLineArgument(arg, &argCursor, argEnd); -// else if (argStr == "-symbo") -// options.SymbolToCompile = tryReadCommandLineArgument(arg, &argCursor, argEnd); - //else if(argStr == "-no-mangle" ) { flags |= SLANG_COMPILE_FLAG_NO_MANGLING; @@ -334,65 +449,45 @@ struct OptionsParser } else if(argStr == "-parameter-blocks-use-register-spaces" ) { - targetFlags |= SLANG_TARGET_FLAG_PARAMETER_BLOCKS_USE_REGISTER_SPACES; + getCurrentTarget()->targetFlags |= SLANG_TARGET_FLAG_PARAMETER_BLOCKS_USE_REGISTER_SPACES; } - else if (argStr == "-backend" || argStr == "-target") + else if (argStr == "-target") { String name; SLANG_RETURN_ON_FAIL(tryReadCommandLineArgument(sink, arg, &argCursor, argEnd, name)); - SlangCompileTarget target = SLANG_TARGET_UNKNOWN; + SlangCompileTarget format = SLANG_TARGET_UNKNOWN; - if (name == "glsl") - { - target = SLANG_GLSL; - } - else if (name == "glsl_vk") - { - target = SLANG_GLSL_VULKAN; - } -// else if (name == "glsl_vk_onedesc") -// { -// options.Target = CodeGenTarget::GLSL_Vulkan_OneDesc; -// } - else if (name == "hlsl") - { - target = SLANG_HLSL; - } - else if (name == "spriv") - { - target = SLANG_SPIRV; - } - else if (name == "dxbc") - { - target = SLANG_DXBC; - } - else if (name == "dxbc-assembly") - { - target = SLANG_DXBC_ASM; - } #define CASE(NAME, TARGET) \ - else if(name == #NAME) do { target = SLANG_##TARGET; } while(0) - - CASE(spirv, SPIRV); - CASE(spirv-assembly, SPIRV_ASM); - CASE(dxil, DXIL); - CASE(dxil-assembly, DXIL_ASM); - CASE(none, TARGET_NONE); + 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) #undef CASE - - else + /* else */ { sink->diagnose(SourceLoc(), Diagnostics::unknownCodeGenerationTarget, name); return SLANG_FAIL; } - this->chosenTarget = target; - spSetCodeGenTarget(compileRequest, target); + RawTarget rawTarget; + rawTarget.format = CodeGenTarget(format); + + rawTargets.Add(rawTarget); } - // A "profile" specifies both a specific target stage and a general level - // of capability required by the program. + // 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; @@ -406,16 +501,16 @@ struct OptionsParser } else { - auto newProfile = Profile(profileID); - currentProfile.setVersion(newProfile.GetVersion()); - profileOptionCount++; + 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`) - if(newProfile.GetStage() != Stage::Unknown) + auto stage = profile.GetStage(); + if(stage != Stage::Unknown) { - currentProfile.setStage(newProfile.GetStage()); - stageOptionCount++; + setStage(getCurrentEntryPoint(), stage); } } } @@ -432,8 +527,7 @@ struct OptionsParser } else { - currentProfile.setStage(stage); - stageOptionCount++; + setStage(getCurrentEntryPoint(), stage); } } else if (argStr == "-entry") @@ -441,38 +535,12 @@ struct OptionsParser String name; SLANG_RETURN_ON_FAIL(tryReadCommandLineArgument(sink, arg, &argCursor, argEnd, name)); - RawEntryPoint entry; - entry.name = name; - entry.translationUnitIndex = currentTranslationUnitIndex; - - int outputPathCount = (int) rawOutputPaths.Count(); - int currentOutputPathIndex = outputPathCount - 1; - entry.outputPathIndex = currentOutputPathIndex; - - entry.profile = currentProfile; + RawEntryPoint rawEntryPoint; + rawEntryPoint.name = name; + rawEntryPoint.translationUnitIndex = currentTranslationUnitIndex; - rawEntryPoints.Add(entry); + rawEntryPoints.Add(rawEntryPoint); } -#if 0 - else if (argStr == "-stage") - { - String name; - SLANG_RETURN_ON_FAIL(tryReadCommandLineArgument(sink, arg, &argCursor, argEnd, name)); - - StageTarget stage = StageTarget::Unknown; - if (name == "vertex") { stage = StageTarget::VertexShader; } - else if (name == "fragment") { stage = StageTarget::FragmentShader; } - else if (name == "hull") { stage = StageTarget::HullShader; } - else if (name == "domain") { stage = StageTarget::DomainShader; } - else if (name == "compute") { stage = StageTarget::ComputeShader; } - else - { - sink->diagnose(SourceLoc(), Diagnostics::unknownStage, name); - return SLANG_FAIL; - } - options.stage = stage; - } -#endif else if (argStr == "-pass-through") { String name; @@ -492,8 +560,6 @@ struct OptionsParser compileRequest, passThrough); } -// else if (argStr == "-genchoice") -// options.Mode = CompilerMode::GenerateChoice; else if (argStr[1] == 'D') { // The value to be defined might be part of the same option, as in: @@ -619,329 +685,491 @@ struct OptionsParser spSetCompileFlags(compileRequest, flags); - // TODO(tfoley): This kind of validation needs to wait until - // after all options have been specified for API usage -#if 0 - if (inputPathCount == 0) + // 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.Count() == 0 + && rawTranslationUnits.Count() == 1 + && (defaultEntryPoint.stage != Stage::Unknown + || rawTranslationUnits[0].impliedStage != Stage::Unknown)) { - fprintf(stderr, "error: no input file specified\n"); - return SLANG_E_INVALID_ARG; + RawEntryPoint entry; + entry.name = "main"; + entry.translationUnitIndex = 0; + rawEntryPoints.Add(entry); } - // No point in moving forward if there is nothing to compile - if( translationUnitCount == 0 ) + // 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.Count() == 1 ) + { + if(defaultEntryPoint.stage != Stage::Unknown) + { + setStage(getCurrentEntryPoint(), defaultEntryPoint.stage); + } + + if(defaultEntryPoint.redundantStageSet) + getCurrentEntryPoint()->redundantStageSet = true; + if(defaultEntryPoint.conflictingStagesSet) + getCurrentEntryPoint()->conflictingStagesSet = true; + } + else { - fprintf(stderr, "error: no compilation requested\n"); - return SLANG_FAIL; + // 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.Count() == 0 ) + { + sink->diagnose(SourceLoc(), Diagnostics::stageSpecificationIgnoredBecauseNoEntryPoints); + } + else + { + sink->diagnose(SourceLoc(), Diagnostics::stageSpecificationIgnoredBecauseBeforeAllEntryPoints); + } + } } -#endif - // If the user didn't list any explicit entry points, then we can - // try to infer one from the type of input file - if(rawEntryPoints.Count() == 0) + // 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 rawTranslationUnit : rawTranslationUnits) + 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 ) { - // Dont' add implicit entry points when compiling from Slang files, - // since Slang doesn't require entry points to be named on the - // command line. - if(rawTranslationUnit.sourceLanguage == SLANG_SOURCE_LANGUAGE_SLANG ) + // Skip entry points that are already associated with a translation unit... + if( entryPoint.translationUnitIndex != -1 ) continue; - // Use a default entry point name - char const* entryPointName = "main"; - - // Try to determine a profile and stage - - // If a profile and/or stage was specified on the command line, then we use it - Profile entryPointProfile = currentProfile; + anyEntryPointWithoutTranslationUnit = true; + } + if( anyEntryPointWithoutTranslationUnit ) + { + sink->diagnose(SourceLoc(), Diagnostics::entryPointsNeedToBeAssociatedWithTranslationUnits); + return SLANG_FAIL; + } + } - // Otherwise, check if the translation unit implied a profile - // (e.g., a `*.vert` file implies the `GLSL_Vertex` profile) - // - // TODO: most of this is just there to support GLSL files - // as input, which doesn't make sense when we don't support - // GLSL at all other than for pass-through. We should ditch - // as much of this complexity as possible. - // - if(entryPointProfile.raw == SLANG_PROFILE_UNKNOWN) - { - if(rawTranslationUnit.implicitProfile != SLANG_PROFILE_UNKNOWN) - { - entryPointProfile = rawTranslationUnit.implicitProfile; - } - } + // 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; - RawEntryPoint entry; - entry.name = entryPointName; - entry.translationUnitIndex = rawTranslationUnit.translationUnitIndex; - entry.profile = entryPointProfile; - rawEntryPoints.Add(entry); - } + auto impliedStage = rawTranslationUnits[rawEntryPoint.translationUnitIndex].impliedStage; + if(impliedStage != Stage::Unknown) + rawEntryPoint.stage = impliedStage; } - // For any entry points that were given without an explicit profile, we can now apply - // the profile that was given to them. - if( rawEntryPoints.Count() != 0 ) + // 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 ) { - bool anyEntryPointWithoutProfile = false; - bool anyEntryPointWithoutStage = false; - for( auto& entryPoint : rawEntryPoints ) + if( rawEntryPoint.conflictingStagesSet ) { - if(entryPoint.profile.GetStage() == Stage::Unknown) + 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 ) { - anyEntryPointWithoutStage = true; + sink->diagnose(SourceLoc(), Diagnostics::explicitStageDoesntMatchImpliedStage, rawEntryPoint.name, rawEntryPoint.stage, rawTranslationUnit.impliedStage); } + } + } - if(entryPoint.profile.getFamily() == ProfileFamily::Unknown) + // 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 ) { - anyEntryPointWithoutProfile = true; + 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)); - if(anyEntryPointWithoutStage) + 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.Count() == 0) + { + for(auto& rawOutput : rawOutputs) { - // If there are entry points that never got a stage specified, and - // the user used multiple `-profile` and `-stage` options to try - // and establish stages, then that is an error, because we can't - // infer a stage for whatever is left. - if(stageOptionCount > 1) + // 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) ) { - if (rawEntryPoints.Count() > 1) - { - sink->diagnose(SourceLoc(), Diagnostics::multipleEntryPointsNeedMulitpleStages); - return SLANG_E_INVALID_ARG; - } + targetIndex = (int) rawTargets.Count(); + + RawTarget rawTarget; + rawTarget.format = impliedFormat; + rawTargets.Add(rawTarget); + + mapFormatToTargetIndex[impliedFormat] = targetIndex; } - // If a stage never got specified, then that is an error. - if(currentProfile.GetStage() == Stage::Unknown) + 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.Count(); + for(int targetIndex = 0; targetIndex < targetCount; ++targetIndex) + { + auto format = rawTargets[targetIndex].format; + + if( mapFormatToTargetIndex.ContainsKey(format) ) { - sink->diagnose(SourceLoc(), Diagnostics::noStageSpecified); - return SLANG_E_INVALID_ARG; + sink->diagnose(SourceLoc(), Diagnostics::duplicateTargets, format); } - - // Otherwise, exactly one stage was specified on the command line, - // and that should implicitly apply to all the entry points. - for( auto& e : rawEntryPoints ) + else { - if(e.profile.GetStage() == Stage::Unknown) - { - e.profile.setStage(currentProfile.GetStage()); - } + mapFormatToTargetIndex[format] = targetIndex; } } + } - if(anyEntryPointWithoutProfile ) + // 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.Count() == 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() ) { - // If there are entry points that never got a profile specified, and - // the user used multiple `-profile` options to try and establish - // different profiles, then that is an error, because we can't - // infer a stage for whatever is left. - if(profileOptionCount > 1) - { - if (rawEntryPoints.Count() > 1) - { - sink->diagnose(SourceLoc(), Diagnostics::multipleEntryPointsNeedMulitpleProfiles); - return SLANG_E_INVALID_ARG; - } - } + 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; - // If a profile never got specified, then that is an error. - if(currentProfile.getFamily() == ProfileFamily::Unknown) + // 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 ) { - sink->diagnose(SourceLoc(), Diagnostics::noProfileSpecified); - return SLANG_E_INVALID_ARG; + inferredFormat = CodeGenTarget::DXIL; } - - // Otherwise, exactly one profile was specified on the command line, - // and that should implicitly apply to all the entry points. - for( auto& e : rawEntryPoints ) + else { - if(e.profile.getFamily() == ProfileFamily::Unknown) - { - e.profile.setVersion(currentProfile.GetVersion()); - } + inferredFormat = CodeGenTarget::DXBytecode; } + break; } - } - // If the user is requesting multiple targets, *and* is asking - // for direct output files for entry points, that is an error. - if (rawOutputPaths.Count() != 0 && requestImpl->targets.Count() > 1) - { - sink->diagnose(SourceLoc(), Diagnostics::explicitOutputPathsAndMultipleTargets); + if( inferredFormat != CodeGenTarget::Unknown ) + { + RawTarget rawTarget; + rawTarget.format = inferredFormat; + rawTargets.Add(rawTarget); + } } - // Did the user try to specify output path(s)? - if (rawOutputPaths.Count() != 0) + // 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.Count() == 1) { - 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()) + if(defaultTarget.profileVersion != ProfileVersion::Unknown) { - sink->diagnose(SourceLoc(), Diagnostics::tooManyOutputPathsSpecified, - rawOutputPaths.Count(), rawEntryPoints.Count()); + setProfileVersion(getCurrentTarget(), defaultTarget.profileVersion); } - else + + getCurrentTarget()->targetFlags |= defaultTarget.targetFlags; + } + 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 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( rawTargets.Count() == 0 ) { - if (entryPoint.outputPathIndex < 0) - { - sink->diagnose(SourceLoc(), Diagnostics::noOutputPathSpecifiedForEntryPoint, entryPoint.name); - - // Don't emit this same error for other entry - // points, even if we have more - break; - } + // 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); } } - // All of the output paths had better agree on the format - // they should provide. - switch (chosenTarget) + if( defaultTarget.targetFlags ) { - case SLANG_TARGET_NONE: - case SLANG_TARGET_UNKNOWN: - // No direct `-target` argument given, so try to infer - // a target from the entry points: + if( rawTargets.Count() == 0 ) { - 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: - sink->diagnose(SourceLoc(), Diagnostics::cannotDeduceOutputFormatFromPath, rawOutputPath.path); + sink->diagnose(SourceLoc(), Diagnostics::targetFlagsIgnoredBecauseNoTargets); + } + else + { + sink->diagnose(SourceLoc(), Diagnostics::targetFlagsIgnoredBecauseBeforeAllTargets); + } + } - // 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& rawTarget : rawTargets) + { + if(rawTarget.redundantProfileSet ) - for (auto rawOutputPath : rawOutputPaths) - { - if (rawOutputPath.target != target) - { - // This file didn't imply a target, and that - // needs to be an error: - sink->diagnose( - SourceLoc(), - Diagnostics::outputPathsImplyDifferentFormats, - rawOutputPaths[0].path, - rawOutputPath.path); - - // Don't keep looking for errors - break; - } - } - } + if( rawTarget.conflictingProfilesSet ) + { + sink->diagnose(SourceLoc(), Diagnostics::conflictingProfilesSpecifiedForTarget, rawTarget.format); + } + else if( rawTarget.redundantProfileSet ) + { + sink->diagnose(SourceLoc(), Diagnostics::sameProfileSpecifiedMoreThanOnce, rawTarget.profileVersion, rawTarget.format); + } + } - } - break; + // 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? - 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; + // 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 the user specified and per-compilation-target flags, make sure - // to apply them here. - if(targetFlags) + if(defaultMatrixLayoutMode != SLANG_MATRIX_LAYOUT_MODE_UNKNOWN) { - spSetTargetFlags(compileRequest, 0, targetFlags); + spSetMatrixLayoutMode(compileRequest, defaultMatrixLayoutMode); } - if(defaultMatrixLayoutMode != SLANG_MATRIX_LAYOUT_MODE_UNKNOWN) + // 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.Count() == 1 ) { - UInt targetCount = requestImpl->targets.Count(); - for(UInt tt = 0; tt < targetCount; ++tt) + for( auto& rawOutput : rawOutputs ) { - spSetTargetMatrixLayoutMode(compileRequest, int(tt), defaultMatrixLayoutMode); + rawOutput.entryPointIndex = 0; } } - - // Next, we want to make sure that entry points get attached to the appropriate translation - // unit that will provide them. + // + // Similarly, if there is only one target, then all outputs must + // implicitly appertain to that target. + // + if( rawTargets.Count() == 1 ) { - bool anyEntryPointWithoutTranslationUnit = false; - for( auto& entryPoint : rawEntryPoints ) + for( auto& rawOutput : rawOutputs ) { - // Skip entry points that are already associated with a translation unit... - if( entryPoint.translationUnitIndex != -1 ) - continue; - - anyEntryPointWithoutTranslationUnit = true; - entryPoint.translationUnitIndex = 0; + rawOutput.targetIndex = 0; } + } - if( anyEntryPointWithoutTranslationUnit && translationUnitCount != 1 ) + // 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 assocaited with + // it, then search for the target with the matching format. + if( rawOutput.targetIndex == -1 ) { - sink->diagnose(SourceLoc(), Diagnostics::multipleTranslationUnitsNeedEntryPoints); - return SLANG_FAIL; + 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); + } } - // Now place all those entry points where they belong - for( auto& entryPoint : rawEntryPoints ) + // 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 ) { - int entryPointIndex = spAddEntryPoint( - compileRequest, - entryPoint.translationUnitIndex, - entryPoint.name.begin(), - entryPoint.profile.raw); + sink->diagnose(SourceLoc(), Diagnostics::cannotMatchOutputFileToEntryPoint, rawOutput.path); + } + } - // 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]; + // Now that we've diagnosed the output paths, we can add them + // to the compile request at the appropriate locations. + // + // We start by allocating the arrays for per-entry-point output + // paths on each of the requested targets. + // + for(auto rawTarget : rawTargets) + { + auto targetID = rawTarget.targetID; + auto targetReq = requestImpl->targets[targetID]; - requestImpl->entryPoints[entryPointIndex]->outputPath = rawOutputPath.path; - } - } + targetReq->entryPointOutputPaths.SetSize(rawEntryPoints.Count()); } -#if 0 - // Automatically derive an output directory based on the first file specified. + // Consider the output files specified via `-o` and try to figure + // out how to deal with them. // - // TODO: require manual specification if there are multiple input files, in different directories - String fileName = options.translationUnits[0].sourceFilePaths[0]; - if (outputDir.Length() == 0) + for(auto& rawOutput : rawOutputs) { - outputDir = Path::GetDirectoryName(fileName); + if(rawOutput.targetIndex == -1) continue; + if(rawOutput.entryPointIndex == -1) continue; + + auto targetID = rawTargets[rawOutput.targetIndex].targetID; + auto entryPointID = rawEntryPoints[rawOutput.entryPointIndex].entryPointID; + + auto targetReq = requestImpl->targets[targetID]; + + if(targetReq->entryPointOutputPaths[entryPointID].Length()) + { + auto entryPointReq = requestImpl->entryPoints[entryPointID]; + sink->diagnose(SourceLoc(), Diagnostics::duplicateOutputPathsForEntryPointAndTarget, entryPointReq->name, targetReq->target); + } + else + { + targetReq->entryPointOutputPaths[entryPointID] = rawOutput.path; + } } -#endif return (sink->GetErrorCount() == 0) ? SLANG_OK : SLANG_FAIL; } |
