diff options
| author | Tim Foley <tfoleyNV@users.noreply.github.com> | 2018-10-29 14:44:39 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2018-10-29 14:44:39 -0700 |
| commit | 725985528f77ba939a5cddc71e5006fee7638465 (patch) | |
| tree | 9b5d4d90a02e38a7c564e6df1fa2944616cf7913 /source | |
| parent | 56c9de0ae0f0b37d0c5f50f2b39d6c18362642bb (diff) | |
Rework command-line options handling for entry points and targets (#697)
* Rework command-line options handling for entry points and targets
Overview:
* The biggest functionality change is that the implicit ordering constraints when multiple `-entry` options are reversed: any `-stage` option affects the `-entry` to its *left* instead of to its *right* as it used to. This is technically a breaking change, but I expect most users aren't using this feature.
* The options parsing tries to handle profile versions and stages as distinct data (rather than using the combined `Profile` type all over), and treats a `-profile` option that specifies both a profile version and a stage (e.g., `-profile ps_5_0`) as if it were sugar for both a `-profile` and a `-stage` (e.g., `-profile sm_5_0 -stage fragment`).
* We now technically handle multiple `-target` options in one invocation of `-slangc`, but do not advertise that fact in the documentation because it might be confusing for users. Similar to the relationship between `-stage` and `-entry`, any `-profile` option affects the most recent `-target` option unless there is only one `-target`.
* The logic for associating `-o` options with corresponding entry points and targets has been beefed up. The rule is that a `-o` option for a compiled kernel binds to the entry point to its left, unless there is only one entry point (just like for `-stage`). The associated target for a `-o` option is found via a search, however, because otherwise it would be impossible to specify `-o` options for both SPIR-V and DXIL in one pass.
* The handling of output paths for entry points in the internal compiler structures was changed, because previously it could only handle one output path per entry point (even when there are multiple targets). The new logic builds up a per-target mapping from an entry point to its desired output path (if any).
Details:
* Support for formatting profile versions, stages, and compile targets (formats) was added to diagnostic printing, so that we can make better error messages. This is fairly ad hoc, and it would be nice to have all of the string<->enum stuff be more data-driven throughout the codebase.
* Test cases were added for (almost) all of the error conditions in the current options validation. The main one that is missing is around specifying an `-entry` option before any source file when compiling multiple files. This is because the test runner is putting the source file name first on the command line automatically, so we can't reproduce that case.
* Several reflection-related tests now reflect entry points where they didn't before, because the logic for detecting when to infer a default `main` entry point have been made more loose
* On the dxc path, beefed up the handling of mapping from Slang `Profile`s to the coresponding string to use when invoking dxc.
* A bunch of tests cases were in violation of the newly imposed rules, so those needed to be cleaned up.
* There were also a bunch of test cases that had accidentally gotten "disabled" at some point because there were comparing output from `slangc` both with and without a `-pass-through` option, but that meant that any errors in command-line parsing produced the *same* error output in both the Slang and pass-through cases. This change updates `slang-test` to always expect a successful run for these tests, and then manually updates or disables the various test cases that are affected.
* When merging the updated test for matrix layout mode, I found that the new command-line logic was failing to propagate a matrix layout mode passed to `render-test` into the compiler. This was because the `-matrix-layout*` options were implemented as per-target, but the target was being set by API while the option came in via command line (passed through the API). It seems like we want matrix layout mode to be a global option anyway (rather than per-target), so I made that change here.
* Add missing expected output files
* A 64-bit fix
* Remove commented-out code noted in review
Diffstat (limited to 'source')
| -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); } |
