diff options
Diffstat (limited to 'source/slang')
| -rw-r--r-- | source/slang/check.cpp | 27 | ||||
| -rw-r--r-- | source/slang/compiler.cpp | 95 | ||||
| -rw-r--r-- | source/slang/compiler.h | 33 | ||||
| -rw-r--r-- | source/slang/diagnostic-defs.h | 36 | ||||
| -rw-r--r-- | source/slang/diagnostics.cpp | 32 | ||||
| -rw-r--r-- | source/slang/diagnostics.h | 7 | ||||
| -rw-r--r-- | source/slang/dxc-support.cpp | 2 | ||||
| -rw-r--r-- | source/slang/emit.cpp | 2 | ||||
| -rw-r--r-- | source/slang/options.cpp | 1030 | ||||
| -rw-r--r-- | source/slang/profile.h | 16 | ||||
| -rw-r--r-- | source/slang/slang.cpp | 33 |
11 files changed, 847 insertions, 466 deletions
diff --git a/source/slang/check.cpp b/source/slang/check.cpp index 278ba7b61..dbf2b294c 100644 --- a/source/slang/check.cpp +++ b/source/slang/check.cpp @@ -8427,10 +8427,19 @@ namespace Slang // that function is specific to the fragment profile/stage. // + auto sink = &entryPoint->compileRequest->mSink; + + // Every entry point needs to have a stage specified either via + // command-line/API options, or via an explicit `[shader("...")]` attribute. + // + if( entryPoint->getStage() == Stage::Unknown ) + { + sink->diagnose(entryPoint->decl, Diagnostics::entryPointHasNoStage, entryPoint->name); + } + if (entryPoint->getStage() == Stage::Hull) { auto translationUnit = entryPoint->getTranslationUnit(); - auto sink = &entryPoint->compileRequest->mSink; auto translationUnitSyntax = translationUnit->SyntaxNode; auto attr = entryPoint->decl->FindModifier<PatchConstantFuncAttribute>(); @@ -8554,6 +8563,22 @@ namespace Slang return; } + // If the entry point specifies a stage via a `[shader("...")]` attribute, + // then we might be able to infer a stage for the entry point request if + // it didn't have one, *or* issue a diagnostic if there is a mismatch. + // + if( auto entryPointAttribute = entryPointFuncDecl->FindModifier<EntryPointAttribute>() ) + { + if( entryPoint->getStage() == Stage::Unknown ) + { + entryPoint->profile.setStage(entryPointAttribute->stage); + } + else if( entryPointAttribute->stage != entryPoint->getStage() ) + { + sink->diagnose(entryPointFuncDecl, Diagnostics::specifiedStageDoesntMatchAttribute, entryPoint->name, entryPoint->getStage(), entryPointAttribute->stage); + } + } + // TODO: it is possible that the entry point was declared with // profile or target overloading. Is there anything that we need // to do at this point to filter out declarations that aren't diff --git a/source/slang/compiler.cpp b/source/slang/compiler.cpp index f16ad06b7..ea1b650a8 100644 --- a/source/slang/compiler.cpp +++ b/source/slang/compiler.cpp @@ -139,6 +139,19 @@ namespace Slang return Profile::Unknown; } + char const* Profile::getName() + { + switch( raw ) + { + default: + return "unknown"; + + #define PROFILE(TAG, NAME, STAGE, VERSION) case Profile::TAG: return #NAME; + #define PROFILE_ALIAS(TAG, DEF, NAME) /* empty */ + #include "profile-defs.h" + } + } + Stage findStageByName(String const& name) { static const struct @@ -268,7 +281,7 @@ namespace Slang } } - char const* GetHLSLProfileName(Profile profile) + String GetHLSLProfileName(Profile profile) { switch( profile.getFamily() ) { @@ -285,15 +298,56 @@ namespace Slang break; } - switch(profile.raw) + char const* stagePrefix = nullptr; + switch( profile.GetStage() ) { - #define PROFILE(TAG, NAME, STAGE, VERSION) case Profile::TAG: return #NAME; - #include "profile-defs.h" + default: + return "unknown"; + + #define CASE(NAME, PREFIX) case Stage::NAME: stagePrefix = #PREFIX; break + CASE(Vertex, vs); + CASE(Hull, hs); + CASE(Domain, ds); + CASE(Geometry, gs); + CASE(Fragment, ps); + CASE(Compute, cs); + + // Note: dxc requires a `lib` target for all + // ray tracing stages. + CASE(RayGeneration, lib); + CASE(Intersection, lib); + CASE(AnyHit, lib); + CASE(ClosestHit, lib); + CASE(Miss, lib); + CASE(Callable, lib); + #undef CASE + } + + char const* versionSuffix = nullptr; + switch(profile.GetVersion()) + { + #define CASE(TAG, SUFFIX) case ProfileVersion::TAG: versionSuffix = #SUFFIX; break + CASE(DX_4_0, _4_0); + CASE(DX_4_0_Level_9_0, _4_0_level_9_0); + CASE(DX_4_0_Level_9_1, _4_0_level_9_1); + CASE(DX_4_0_Level_9_3, _4_0_level_9_3); + CASE(DX_4_1, _4_1); + CASE(DX_5_0, _5_0); + CASE(DX_5_1, _5_1); + CASE(DX_6_0, _6_0); + CASE(DX_6_1, _6_1); + CASE(DX_6_2, _6_2); + CASE(DX_6_3, _6_3); + #undef CASE default: - // TODO: emit an error here! return "unknown"; } + + String result; + result.append(stagePrefix); + result.append(versionSuffix); + return result; } #if SLANG_ENABLE_DXBC_SUPPORT @@ -375,7 +429,7 @@ namespace Slang dxMacros, nullptr, getText(entryPoint->name).begin(), - GetHLSLProfileName(profile), + GetHLSLProfileName(profile).Buffer(), 0, 0, &codeBlob, @@ -787,13 +841,12 @@ String dissassembleDXILUsingDXC( } static void writeEntryPointResultToFile( - EntryPointRequest* entryPoint, - TargetRequest* targetReq, - UInt entryPointIndex) + EntryPointRequest* entryPoint, + String const& outputPath, + CompileResult const& result) { auto compileRequest = entryPoint->compileRequest; - auto outputPath = entryPoint->outputPath; - auto result = targetReq->entryPointResults[entryPointIndex]; + switch (result.format) { case ResultFormat::Text: @@ -837,12 +890,11 @@ String dissassembleDXILUsingDXC( } static void writeEntryPointResultToStandardOutput( - EntryPointRequest* entryPoint, - TargetRequest* targetReq, - UInt entryPointIndex) + EntryPointRequest* entryPoint, + TargetRequest* targetReq, + CompileResult const& result) { auto compileRequest = entryPoint->compileRequest; - auto& result = targetReq->entryPointResults[entryPointIndex]; switch (result.format) { @@ -924,28 +976,23 @@ String dissassembleDXILUsingDXC( TargetRequest* targetReq, UInt entryPointIndex) { + auto outputPath = targetReq->entryPointOutputPaths[entryPointIndex]; auto& result = targetReq->entryPointResults[entryPointIndex]; // Skip the case with no output if (result.format == ResultFormat::None) return; - if (entryPoint->outputPath.Length()) + if (outputPath.Length()) { - writeEntryPointResultToFile(entryPoint, targetReq, entryPointIndex); + writeEntryPointResultToFile(entryPoint, outputPath, result); } else { - writeEntryPointResultToStandardOutput(entryPoint, targetReq, entryPointIndex); + writeEntryPointResultToStandardOutput(entryPoint, targetReq, result); } } - void emitEntryPoints( - TargetRequest* /*targetReq*/) - { - - } - void generateOutputForTarget( TargetRequest* targetReq) { diff --git a/source/slang/compiler.h b/source/slang/compiler.h index de099c40b..c62daf228 100644 --- a/source/slang/compiler.h +++ b/source/slang/compiler.h @@ -144,23 +144,6 @@ namespace Slang // supposed to be defined in. int translationUnitIndex; - // The output path requested for this entry point. - // (only used when compiling from the command line) - // - // TODO: This should get dropped. When compiling from the - // command line, the user should be either: - // - // * Compiling a single entry point for a single target, so - // that only a single output path is needed for the whole request. - // - // * Compiling for a target that supports multiple entry points directly - // (e.g., a recent DXIL version), so that only one output file is needed. - // - // * Compiling to a slang module container, so that as many entry - // points and targets as needed can be specified. - // - String outputPath; - // The translation unit that this entry point came from TranslationUnitRequest* getTranslationUnit(); @@ -227,7 +210,12 @@ namespace Slang CompileRequest* compileRequest; CodeGenTarget target; SlangTargetFlags targetFlags = 0; - Slang::Profile targetProfile = Slang::Profile::Unknown; + Slang::Profile targetProfile = Slang::Profile(); + + // Requested output paths for each entry point. + // An empty string indices no output desired for + // the given entry point. + List<String> entryPointOutputPaths; // The resulting reflection layout information RefPtr<ProgramLayout> layout; @@ -240,9 +228,7 @@ namespace Slang // TypeLayouts created on the fly by reflection API Dictionary<Type*, RefPtr<TypeLayout>> typeLayouts; - /// The layout to use for matrices by default (row/column major) - MatrixLayoutMode defaultMatrixLayoutMode = kMatrixLayoutMode_ColumnMajor; - MatrixLayoutMode getDefaultMatrixLayoutMode() { return defaultMatrixLayoutMode; } + MatrixLayoutMode getDefaultMatrixLayoutMode(); }; // Compute the "effective" profile to use when outputting the given entry point @@ -324,8 +310,9 @@ namespace Slang // Types constructed by reflection API Dictionary<String, RefPtr<Type>> types; - // The code generation profile we've been asked to use. - Profile profile; + /// The layout to use for matrices by default (row/column major) + MatrixLayoutMode defaultMatrixLayoutMode = kMatrixLayoutMode_ColumnMajor; + MatrixLayoutMode getDefaultMatrixLayoutMode() { return defaultMatrixLayoutMode; } // Should we just pass the input to another compiler? PassThroughMode passThrough = PassThroughMode::None; diff --git a/source/slang/diagnostic-defs.h b/source/slang/diagnostic-defs.h index 89db9d0e3..a32dbb315 100644 --- a/source/slang/diagnostic-defs.h +++ b/source/slang/diagnostic-defs.h @@ -61,7 +61,6 @@ DIAGNOSTIC( 7, Error, noOutputPathSpecifiedForEntryPoint, DIAGNOSTIC( 8, Error, outputPathsImplyDifferentFormats, "the output paths '$0' and '$1' require different code-generation targets") -DIAGNOSTIC( 9, Error, cannotDeduceOutputFormatFromPath, "cannot deduce an output format from the output path '$0'") DIAGNOSTIC( 10, Error, explicitOutputPathsAndMultipleTargets, "canot use both explicit output paths ('-o') and multiple targets ('-target')") DIAGNOSTIC( 11, Error, glslIsNotSupported, "the Slang compiler does not support GLSL as a source language"); DIAGNOSTIC( 12, Error, cannotDeduceSourceLanguage, "can't deduce language for input file '$0'"); @@ -70,14 +69,36 @@ DIAGNOSTIC( 14, Error, unknownProfile, "unknown profile '$0'"); DIAGNOSTIC( 15, Error, unknownStage, "unknown stage '$0'"); DIAGNOSTIC( 16, Error, unknownPassThroughTarget, "unknown pass-through target '$0'"); DIAGNOSTIC( 17, Error, unknownCommandLineOption, "unknown command-line option '$0'"); -DIAGNOSTIC( 18, Error, noProfileSpecified, "no profile specified; use the '-profile <profile name>' option"); -DIAGNOSTIC( 19, Error, multipleEntryPointsNeedMulitpleProfiles, "when specifying multiple profiles on the command line, each '-entry' option must be preceded by a '-profile' option"); -DIAGNOSTIC( 20, Error, multipleTranslationUnitsNeedEntryPoints, "when using multiple translation units, entry points must be specified after their translation unit file(s)"); +DIAGNOSTIC( 20, Error, entryPointsNeedToBeAssociatedWithTranslationUnits, "when using multiple source files, entry points must be specified after their corresponding source file(s)"); DIAGNOSTIC( 21, Error, expectedArgumentForOption, "expected an argument for command-line option '$0'"); -DIAGNOSTIC( 22, Error, noStageSpecified, "no stage specified; use the '-stage <stage name>' option"); -DIAGNOSTIC( 23, Error, multipleEntryPointsNeedMulitpleStages, "when multiple entry points are specified on the command line, each '-entry' point must be given a stage by a preceding '-stage' option"); + DIAGNOSTIC( 24, Error, unknownLineDirectiveMode, "unknown '#line' directive mode '$0'"); +DIAGNOSTIC( 30, Error, sameStageSpecifiedMoreThanOnce, "the stage '$0' was specified more than once for entry point '$1'") +DIAGNOSTIC( 31, Error, conflictingStagesForEntryPoint, "conflicting stages have been specified for entry point '$0'") +DIAGNOSTIC( 32, Warning, explicitStageDoesntMatchImpliedStage, "the stage specified for entry point '$0' ('$1') does not match the stage implied by the source file name ('$2')") +DIAGNOSTIC( 33, Error, stageSpecificationIgnoredBecauseNoEntryPoints, "one or more stages were specified, but no entry points were specified with '-entry'") +DIAGNOSTIC( 34, Error, stageSpecificationIgnoredBecauseBeforeAllEntryPoints, "when compiling multiple entry points, any '-stage' options must follow the '-entry' option that they apply to") +DIAGNOSTIC( 35, Error, noStageSpecifiedInPassThroughMode, "no stage was specified for entry point '$0'; when using the '-pass-through' option, stages must be fully specified on the command line") + +DIAGNOSTIC( 40, Warning, sameProfileSpecifiedMoreThanOnce, "the '$0' was specified more than once for target '$0'") +DIAGNOSTIC( 41, Error, conflictingProfilesSpecifiedForTarget, "conflicting profiles have been specified for target '$0'") + +DIAGNOSTIC( 42, Error, profileSpecificationIgnoredBecauseNoTargets, "a '-profile' option was specified, but no target was specified with '-target'") +DIAGNOSTIC( 43, Error, profileSpecificationIgnoredBecauseBeforeAllTargets, "when using multiple targets, any '-profile' option must follow the '-target' it applies to") + +DIAGNOSTIC( 42, Error, targetFlagsIgnoredBecauseNoTargets, "target options were specified, but no target was specified with '-target'") +DIAGNOSTIC( 43, Error, targetFlagsIgnoredBecauseBeforeAllTargets, "when using multiple targets, any target options must follow the '-target' they apply to") + +DIAGNOSTIC( 50, Error, duplicateTargets, "the target '$0' has been specified more than once") + +DIAGNOSTIC( 60, Error, cannotDeduceOutputFormatFromPath, "cannot infer an output format from the output path '$0'") +DIAGNOSTIC( 61, Error, cannotMatchOutputFileToTarget, "no specified '-target' option matches the output path '$0', which implies the '$1' format") + +DIAGNOSTIC( 70, Error, cannotMatchOutputFileToEntryPoint, "the output path '$0' is not associated with any entry point; a '-o' option for a compiled kernel must follow the '-entry' option for its corresponding entry point") + +DIAGNOSTIC( 80, Error, duplicateOutputPathsForEntryPointAndTarget, "multiple output paths have been specified entry point '$0' on target '$1'") + // // 1xxxx - Lexical anaylsis // @@ -306,6 +327,9 @@ DIAGNOSTIC(38003, Error, entryPointSymbolNotAFunction, "entry point '$0' must be DIAGNOSTIC(38004, Error, entryPointTypeParameterNotFound, "no type found matching entry-point type parameter name '$0'") DIAGNOSTIC(38005, Error, entryPointTypeSymbolNotAType, "entry-point type parameter '$0' must be declared as a type") +DIAGNOSTIC(38006, Warning, specifiedStageDoesntMatchAttribute, "entry point '$0' being compiled for the '$1' stage has a '[shader(...)]' attribute that specifies the '$2' stage") +DIAGNOSTIC(38007, Error, entryPointHasNoStage, "no stage specified for entry point '$0'; use either a '[shader(\"name\")]' function attribute or the '-stage <name>' command-line option to specify a stage") + DIAGNOSTIC(38100, Error, typeDoesntImplementInterfaceRequirement, "type '$0' does not provide required interface member '$1'") DIAGNOSTIC(38101, Error, thisExpressionOutsideOfTypeDecl, "'this' expression can only be used in members of an aggregate type") DIAGNOSTIC(38102, Error, initializerNotInsideType, "an 'init' declaration is only allowed inside a type or 'extension' declaration") diff --git a/source/slang/diagnostics.cpp b/source/slang/diagnostics.cpp index 7271c04d3..f1d0b63f9 100644 --- a/source/slang/diagnostics.cpp +++ b/source/slang/diagnostics.cpp @@ -84,6 +84,38 @@ void printDiagnosticArg(StringBuilder& sb, Token const& token) sb << token.Content; } +void printDiagnosticArg(StringBuilder& sb, CodeGenTarget val) +{ + switch( val ) + { + default: + sb << "<unknown>"; + break; + +#define CASE(TAG, STR) case CodeGenTarget::TAG: sb << STR; break + CASE(GLSL, "glsl"); + CASE(HLSL, "hlsl"); + CASE(SPIRV, "spirv"); + CASE(SPIRVAssembly, "spriv-assembly"); + CASE(DXBytecode, "dxbc"); + CASE(DXBytecodeAssembly, "dxbc-assembly"); + CASE(DXIL, "dxil"); + CASE(DXILAssembly, "dxil-assembly"); +#undef CASE + } +} + +void printDiagnosticArg(StringBuilder& sb, Stage val) +{ + sb << getStageName(val); +} + +void printDiagnosticArg(StringBuilder& sb, ProfileVersion val) +{ + sb << Profile(val).getName(); +} + + SourceLoc const& getDiagnosticPos(SyntaxNode const* syntax) { return syntax->loc; diff --git a/source/slang/diagnostics.h b/source/slang/diagnostics.h index a12de03e4..945dc6c73 100644 --- a/source/slang/diagnostics.h +++ b/source/slang/diagnostics.h @@ -73,6 +73,10 @@ namespace Slang struct TypeExp; struct QualType; + enum class CodeGenTarget; + enum class Stage : SlangStage; + enum class ProfileVersion; + void printDiagnosticArg(StringBuilder& sb, char const* str); void printDiagnosticArg(StringBuilder& sb, int val); void printDiagnosticArg(StringBuilder& sb, UInt val); @@ -85,6 +89,9 @@ namespace Slang void printDiagnosticArg(StringBuilder& sb, QualType const& type); void printDiagnosticArg(StringBuilder& sb, TokenType tokenType); void printDiagnosticArg(StringBuilder& sb, Token const& token); + void printDiagnosticArg(StringBuilder& sb, CodeGenTarget val); + void printDiagnosticArg(StringBuilder& sb, Stage val); + void printDiagnosticArg(StringBuilder& sb, ProfileVersion val); template<typename T> void printDiagnosticArg(StringBuilder& sb, RefPtr<T> ptr) diff --git a/source/slang/dxc-support.cpp b/source/slang/dxc-support.cpp index b86330f20..98f099d4e 100644 --- a/source/slang/dxc-support.cpp +++ b/source/slang/dxc-support.cpp @@ -28,7 +28,7 @@ namespace Slang { - char const* GetHLSLProfileName(Profile profile); + String GetHLSLProfileName(Profile profile); String emitHLSLForEntryPoint( EntryPointRequest* entryPoint, TargetRequest* targetReq); diff --git a/source/slang/emit.cpp b/source/slang/emit.cpp index 1c02b64ef..f8d90bc2e 100644 --- a/source/slang/emit.cpp +++ b/source/slang/emit.cpp @@ -1969,7 +1969,7 @@ struct EmitVisitor // then types/variables defined in those modules should be emitted in // a way that is consistent with that layout... - auto matrixLayoutMode = targetReq->defaultMatrixLayoutMode; + auto matrixLayoutMode = targetReq->getDefaultMatrixLayoutMode(); switch(context->shared->target) { 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; } diff --git a/source/slang/profile.h b/source/slang/profile.h index 2967d5b09..cc142bc2a 100644 --- a/source/slang/profile.h +++ b/source/slang/profile.h @@ -52,7 +52,7 @@ namespace Slang struct Profile { typedef uint32_t RawVal; - enum : RawVal + enum RawEnum : RawVal { Unknown, @@ -62,9 +62,20 @@ namespace Slang }; Profile() {} - Profile(RawVal raw) + Profile(RawEnum raw) : raw(raw) {} + explicit Profile(RawVal raw) + : raw(raw) + {} + explicit Profile(Stage stage) + { + setStage(stage); + } + explicit Profile(ProfileVersion version) + { + setVersion(version); + } bool operator==(Profile const& other) const { return raw == other.raw; } bool operator!=(Profile const& other) const { return raw != other.raw; } @@ -84,6 +95,7 @@ namespace Slang ProfileFamily getFamily() const { return getProfileFamily(GetVersion()); } static Profile LookUp(char const* name); + char const* getName(); RawVal raw = Unknown; }; diff --git a/source/slang/slang.cpp b/source/slang/slang.cpp index 38fe88ef3..1729508f7 100644 --- a/source/slang/slang.cpp +++ b/source/slang/slang.cpp @@ -365,6 +365,17 @@ ComPtr<ISlangBlob> createRawBlob(void const* inData, size_t size) return ComPtr<ISlangBlob>(new RawBlob(inData, size)); } +// + +MatrixLayoutMode TargetRequest::getDefaultMatrixLayoutMode() +{ + return compileRequest->getDefaultMatrixLayoutMode(); +} + + + +// + SlangResult CompileRequest::loadFile(String const& path, ISlangBlob** outBlob) { return fileSystemExt->loadFile(path.Buffer(), outBlob); @@ -1211,7 +1222,7 @@ SLANG_API void spSetTargetProfile( SlangProfileID profile) { auto req = REQ(request); - req->targets[targetIndex]->targetProfile = profile; + req->targets[targetIndex]->targetProfile = Slang::Profile(profile); } SLANG_API void spSetTargetFlags( @@ -1223,13 +1234,21 @@ SLANG_API void spSetTargetFlags( req->targets[targetIndex]->targetFlags = flags; } +SLANG_API void spSetMatrixLayoutMode( + SlangCompileRequest* request, + SlangMatrixLayoutMode mode) +{ + auto req = REQ(request); + req->defaultMatrixLayoutMode = Slang::MatrixLayoutMode(mode); +} + SLANG_API void spSetTargetMatrixLayoutMode( SlangCompileRequest* request, int targetIndex, SlangMatrixLayoutMode mode) { - auto req = REQ(request); - req->targets[targetIndex]->defaultMatrixLayoutMode = Slang::MatrixLayoutMode(mode); + SLANG_UNUSED(targetIndex); + spSetMatrixLayoutMode(request, mode); } @@ -1417,7 +1436,7 @@ SLANG_API int spAddEntryPoint( SlangCompileRequest* request, int translationUnitIndex, char const* name, - SlangProfileID profile) + SlangStage stage) { if(!request) return -1; auto req = REQ(request); @@ -1428,7 +1447,7 @@ SLANG_API int spAddEntryPoint( return req->addEntryPoint( translationUnitIndex, name, - Slang::Profile(Slang::Profile::RawVal(profile)), + Slang::Profile(Slang::Stage(stage)), Slang::List<Slang::String>()); } @@ -1436,7 +1455,7 @@ SLANG_API int spAddEntryPointEx( SlangCompileRequest* request, int translationUnitIndex, char const* name, - SlangProfileID profile, + SlangStage stage, int genericParamTypeNameCount, char const ** genericParamTypeNames) { @@ -1451,7 +1470,7 @@ SLANG_API int spAddEntryPointEx( return req->addEntryPoint( translationUnitIndex, name, - Slang::Profile(Slang::Profile::RawVal(profile)), + Slang::Profile(Slang::Stage(stage)), typeNames); } |
