summaryrefslogtreecommitdiff
path: root/source/slang
diff options
context:
space:
mode:
Diffstat (limited to 'source/slang')
-rw-r--r--source/slang/check.cpp27
-rw-r--r--source/slang/compiler.cpp95
-rw-r--r--source/slang/compiler.h33
-rw-r--r--source/slang/diagnostic-defs.h36
-rw-r--r--source/slang/diagnostics.cpp32
-rw-r--r--source/slang/diagnostics.h7
-rw-r--r--source/slang/dxc-support.cpp2
-rw-r--r--source/slang/emit.cpp2
-rw-r--r--source/slang/options.cpp1030
-rw-r--r--source/slang/profile.h16
-rw-r--r--source/slang/slang.cpp33
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);
}