diff options
| author | jsmall-nvidia <jsmall@nvidia.com> | 2023-05-03 20:16:58 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-05-03 20:16:58 -0400 |
| commit | c9ef8d58a135061262fd321a82061d27dc733cf5 (patch) | |
| tree | 888bee92d8519fe80ec9d5dbb95c2dc8da985865 /source | |
| parent | bf2b05528d6edbbf1beb87552f447d29f92eee47 (diff) | |
HLSL->Vulkan binding support (#2865)
* WIP around VK shift binding.
* Refactor around options parsing.
* Remove needless passing around of sink.
* Some more tidying around OptionsParser.
* Handle vulkan shift parsing.
* Fix small issue around vk binding and "all".
* Fixing some small issues. Missing break.
* Split out VulkanLayoutOptions
* WIP binding taking into account HLSL->Vulkan options.
* First attempt at making binding work with HLSLVulkanOptions.
* VulkanLayoutOptions -> HLSLToVulkanLayoutOptions
* WIP with HLSL-Vulkan binding.
* Some more testing around vk-shift.
* Improvements around global binding.
More tests.
* Improve test coverage.
Improve checking for requirements around default space.
* Update command line options.
* Small fixes.
* Small fix in options reporting.
* Fix warning issue.
* Some fixes for isDefault for HLSLToVulkanLayoutOptions.
* Update hlsl-to-vulkan-shift output. The difference was due to default handling if shift isn't specified, and not being specified was not correctly tracked.
Diffstat (limited to 'source')
| -rw-r--r-- | source/compiler-core/slang-command-line-args.h | 16 | ||||
| -rw-r--r-- | source/compiler-core/slang-diagnostic-sink.h | 2 | ||||
| -rwxr-xr-x | source/slang/slang-compiler.h | 13 | ||||
| -rw-r--r-- | source/slang/slang-diagnostic-defs.h | 1 | ||||
| -rw-r--r-- | source/slang/slang-hlsl-to-vulkan-layout-options.cpp | 132 | ||||
| -rw-r--r-- | source/slang/slang-hlsl-to-vulkan-layout-options.h | 85 | ||||
| -rw-r--r-- | source/slang/slang-options.cpp | 3552 | ||||
| -rw-r--r-- | source/slang/slang-parameter-binding.cpp | 359 | ||||
| -rw-r--r-- | source/slang/slang.cpp | 14 |
9 files changed, 2383 insertions, 1791 deletions
diff --git a/source/compiler-core/slang-command-line-args.h b/source/compiler-core/slang-command-line-args.h index 31807cd48..b2bd48c61 100644 --- a/source/compiler-core/slang-command-line-args.h +++ b/source/compiler-core/slang-command-line-args.h @@ -109,17 +109,15 @@ struct CommandLineReader /// Set the current index void setIndex(Index index) { SLANG_ASSERT(index >= 0 && index <= m_args->getArgCount()); m_index = index; } + void init(CommandLineArgs* args, DiagnosticSink* sink) { m_args = args; m_sink = sink; m_index = 0; } + /// Set up reader with args - CommandLineReader(CommandLineArgs* args, DiagnosticSink* sink): - m_args(args), - m_index(0), - m_sink(sink) - { - } + CommandLineReader(CommandLineArgs* args, DiagnosticSink* sink) { init(args, sink); } + CommandLineReader() = default; - DiagnosticSink* m_sink; - CommandLineArgs* m_args; - Index m_index; + DiagnosticSink* m_sink = nullptr; + CommandLineArgs* m_args = nullptr; + Index m_index = 0; }; struct DownstreamArgs diff --git a/source/compiler-core/slang-diagnostic-sink.h b/source/compiler-core/slang-diagnostic-sink.h index 5131e5194..ebd43b456 100644 --- a/source/compiler-core/slang-diagnostic-sink.h +++ b/source/compiler-core/slang-diagnostic-sink.h @@ -255,7 +255,7 @@ public: /// Default Ctor DiagnosticSink(): m_sourceManager(nullptr), - m_sourceLocationLexer (nullptr) + m_sourceLocationLexer(nullptr) { } diff --git a/source/slang/slang-compiler.h b/source/slang/slang-compiler.h index 117c3f037..42fc3ca1f 100755 --- a/source/slang/slang-compiler.h +++ b/source/slang/slang-compiler.h @@ -25,6 +25,8 @@ #include "slang-syntax.h" #include "slang-content-assist-info.h" +#include "slang-hlsl-to-vulkan-layout-options.h" + #include "slang-serialize-ir-types.h" #include "../compiler-core/slang-artifact-representation-impl.h" @@ -1535,6 +1537,7 @@ namespace Slang class TargetRequest : public RefObject { public: + TargetRequest(Linkage* linkage, CodeGenTarget format); void addTargetFlags(SlangTargetFlags flags) @@ -1575,6 +1578,10 @@ namespace Slang return (targetFlags & SLANG_TARGET_FLAG_GENERATE_WHOLE_PROGRAM) != 0; } + void setHLSLToVulkanLayoutOptions(HLSLToVulkanLayoutOptions* opts); + + const HLSLToVulkanLayoutOptions* getHLSLToVulkanLayoutOptions() const { return hlslToVulkanLayoutOptions; } + bool shouldDumpIntermediates() { return dumpIntermediates; } void setTrackLiveness(bool enable) { enableLivenessTracking = enable; } @@ -1611,6 +1618,8 @@ namespace Slang bool dumpIntermediates = false; bool forceGLSLScalarBufferLayout = false; bool enableLivenessTracking = false; + + RefPtr<HLSLToVulkanLayoutOptions> hlslToVulkanLayoutOptions; ///< Optional vulkan layout options }; /// Are we generating code for a D3D API? @@ -1858,7 +1867,7 @@ namespace Slang Type* const* args, DiagnosticSink* sink); - /// Add a mew target and return its index. + /// Add a new target and return its index. UInt addTarget( CodeGenTarget target); @@ -2629,6 +2638,8 @@ namespace Slang virtual SLANG_NO_THROW void SLANG_MCALL setDiagnosticFlags(SlangDiagnosticFlags flags) SLANG_OVERRIDE; virtual SLANG_NO_THROW void SLANG_MCALL setDebugInfoFormat(SlangDebugInfoFormat format) SLANG_OVERRIDE; + void setHLSLToVulkanLayoutOptions(int targetIndex, HLSLToVulkanLayoutOptions* vulkanLayoutOptions); + EndToEndCompileRequest( Session* session); diff --git a/source/slang/slang-diagnostic-defs.h b/source/slang/slang-diagnostic-defs.h index a35c48a6a..e8d521630 100644 --- a/source/slang/slang-diagnostic-defs.h +++ b/source/slang/slang-diagnostic-defs.h @@ -85,6 +85,7 @@ DIAGNOSTIC( 32, Warning, explicitStageDoesntMatchImpliedStage, "the stage spe 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( 36, Error, expectingAnInteger, "expecting an integer value") 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'") diff --git a/source/slang/slang-hlsl-to-vulkan-layout-options.cpp b/source/slang/slang-hlsl-to-vulkan-layout-options.cpp new file mode 100644 index 000000000..e143762a1 --- /dev/null +++ b/source/slang/slang-hlsl-to-vulkan-layout-options.cpp @@ -0,0 +1,132 @@ +// slang-hlsl-to-vulkan-layout-options.cpp + +#include "slang-hlsl-to-vulkan-layout-options.h" + +namespace Slang { + +namespace { // anonymous + +typedef HLSLToVulkanLayoutOptions::Kind ShiftKind; + +/* {b|s|t|u} */ + +static NamesDescriptionValue s_vulkanShiftKinds[] = +{ + { ValueInt(ShiftKind::Buffer), "b", "Vulkan Buffer resource" }, + { ValueInt(ShiftKind::Sampler), "s", "Vulkan Sampler resource" }, + { ValueInt(ShiftKind::Texture), "t", "Vulkan Texture resource" }, + { ValueInt(ShiftKind::Uniform), "u", "Vulkan Uniform resource" }, +}; + +} // anonymous + +/* static */ConstArrayView<NamesDescriptionValue> HLSLToVulkanLayoutOptions::getKindInfos() +{ + return makeConstArrayView(s_vulkanShiftKinds); +} + +HLSLToVulkanLayoutOptions::HLSLToVulkanLayoutOptions() +{ + for (auto& shift : m_allShifts) + { + shift = kInvalidShift; + } +} + + + /// Set the the all option for the kind +void HLSLToVulkanLayoutOptions::setAllShift(Kind kind, Index shift) +{ + // We try to follow the convention, of the *last* entry set is the one used. + // If there a "all" set, we remove everything for the kind. + + // Find all the entries for the kind + List<Key> keys; + for (auto& pair : m_shifts) + { + if (pair.key.kind == kind) + { + keys.add(pair.key); + } + } + // Remove them all + for (auto& key : keys) + { + m_shifts.remove(key); + } + + m_allShifts[Index(kind)] = shift; +} + +void HLSLToVulkanLayoutOptions::setShift(Kind kind, Index set, Index shift) +{ + SLANG_ASSERT(shift != kInvalidShift); + + Key key{ kind, set }; + m_shifts.add(key, shift); +} + +Index HLSLToVulkanLayoutOptions::getShift(Kind kind, Index set) const +{ + if (auto ptr = m_shifts.tryGetValue(Key{ kind, set })) + { + return *ptr; + } + + return m_allShifts[Index(kind)]; +} + +bool HLSLToVulkanLayoutOptions::isDefault() const +{ + // If any all shift is set it's not default + for (auto shift : m_allShifts) + { + if (shift != kInvalidShift) + { + return false; + } + } + + // If any has a non zero shift, it's not default + for (auto& pair : m_shifts) + { + // We need a value that is non zero... + if (pair.value) + { + return false; + } + } + + // If either has been set it's not default + return m_globalsBinding < 0 && m_globalsBindingSet < 0; +} + +/* static */HLSLToVulkanLayoutOptions::Kind HLSLToVulkanLayoutOptions::getKind(slang::ParameterCategory param) +{ + typedef slang::ParameterCategory ParameterCategory; + + switch (param) + { + case ParameterCategory::Mixed: + { + // TODO(JS): + // Hmm, is this TextureSampler? + return Kind::Invalid; + } + case ParameterCategory::Uniform: + case ParameterCategory::ConstantBuffer: + { + return Kind::Uniform; + } + case ParameterCategory::ShaderResource: return Kind::Texture; + case ParameterCategory::UnorderedAccess: return Kind::Buffer; + case ParameterCategory::SamplerState: return Kind::Sampler; + + default: + { + return Kind::Invalid; + } + } +} + +} // namespace Slang diff --git a/source/slang/slang-hlsl-to-vulkan-layout-options.h b/source/slang/slang-hlsl-to-vulkan-layout-options.h new file mode 100644 index 000000000..9260fe91d --- /dev/null +++ b/source/slang/slang-hlsl-to-vulkan-layout-options.h @@ -0,0 +1,85 @@ +// slang-hlsl-to-vulkan-layout-options.h +#ifndef SLANG_HLSL_TO_VULKAN_LAYOUT_OPTIONS_H +#define SLANG_HLSL_TO_VULKAN_LAYOUT_OPTIONS_H + +#include "../core/slang-basic.h" +#include "../core/slang-name-value.h" + +namespace Slang +{ + +/* +For support features similar to described here.. + +https://github.com/microsoft/DirectXShaderCompiler/blob/main/docs/SPIR-V.rst#descriptors + +Options that allow for infering Vulkan bindings based on HLSL register bindings + */ +struct HLSLToVulkanLayoutOptions : public RefObject +{ +public: + + static const Index kInvalidShift = Index(0x80000000); + + // {b|s|t|u} + enum class Kind + { + Invalid = -1, + + Buffer, ///< Buffer + Sampler, ///< Sampler + Texture, ///< Texture + Uniform, ///< Uniform + + CountOf, + }; + + struct Key + { + typedef Key ThisType; + + bool operator==(const ThisType& rhs) const { return kind == rhs.kind && set == rhs.set; } + bool operator!=(const ThisType& rhs) const { return !(*this == rhs); } + + HashCode getHashCode() const { return combineHash(Slang::getHashCode(kind), Slang::getHashCode(set)); } + + Kind kind; ///< The kind this entry is for + Index set; ///< The set this shift is associated with + }; + + /// Set the the all option for the kind + void setAllShift(Kind kind, Index shift); + + /// Set the shift for kind/set + void setShift(Kind kind, Index set, Index shift); + + /// Get the shift. Returns kInvalidShift if no shift is found + Index getShift(Kind kind, Index set) const; + + /// Returns true if contains default information. If so it can in effect be ignored + bool isDefault() const; + + /// True as global binds set + bool hasGlobalsBinding() const { return m_globalsBinding >= 0 && m_globalsBindingSet >= 0; } + + /// Ctor + HLSLToVulkanLayoutOptions(); + + /// Get information about the different kinds + static ConstArrayView<NamesDescriptionValue> getKindInfos(); + + /// Given a paramCategory get the kind. Returns Kind::Invalid if not an applicable category + static Kind getKind(slang::ParameterCategory param); + + Index m_globalsBinding = -1; + Index m_globalsBindingSet = -1; + + Index m_allShifts[Count(Kind::CountOf)]; + + /// Maps a key to the amount of shift + Dictionary<Key, Index> m_shifts; +}; + +} // namespace Slang + +#endif diff --git a/source/slang/slang-options.cpp b/source/slang/slang-options.cpp index 353c6e57f..0c788a404 100644 --- a/source/slang/slang-options.cpp +++ b/source/slang/slang-options.cpp @@ -19,6 +19,7 @@ #include "slang-repro.h" #include "slang-serialize-ir.h" +#include "slang-hlsl-to-vulkan-layout-options.h" #include "../core/slang-castable.h" #include "../core/slang-file-system.h" @@ -81,6 +82,10 @@ enum class OptionKind LineDirectiveMode, Optimization, Obfuscate, + + VulkanBindShift, + VulkanBindGlobals, + GLSLForceScalarLayout, EnableEffectAnnotations, @@ -160,10 +165,25 @@ enum class ValueCategory OptimizationLevel, DebugLevel, FileSystemType, + VulkanShift, CountOf, }; +template <typename T> +struct GetValueCategory; + +#define SLANG_GET_VALUE_CATEGORY(cat, type) template <> struct GetValueCategory<type> { enum { Value = Index(ValueCategory::cat) }; }; + +SLANG_GET_VALUE_CATEGORY(Compiler, SlangPassThrough) +SLANG_GET_VALUE_CATEGORY(ArchiveType, SlangArchiveType) +SLANG_GET_VALUE_CATEGORY(LineDirectiveMode, SlangLineDirectiveMode) +SLANG_GET_VALUE_CATEGORY(FloatingPointMode, FloatingPointMode) +SLANG_GET_VALUE_CATEGORY(FileSystemType, TypeTextUtil::FileSystemType) +SLANG_GET_VALUE_CATEGORY(HelpStyle, CommandOptionsWriter::Style) +SLANG_GET_VALUE_CATEGORY(OptimizationLevel, SlangOptimizationLevel) +SLANG_GET_VALUE_CATEGORY(VulkanShift, HLSLToVulkanLayoutOptions::Kind) + } // anonymous static void _addOptions(const ConstArrayView<Option>& options, CommandOptions& cmdOptions) @@ -245,6 +265,13 @@ void initCommandOptions(CommandOptions& options) options.addValuesWithAliases(opts.getArrayView()); } + /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! vulkan-shift !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ + + { + options.addCategory(CategoryKind::Value, "vulkan-shift", "Vulkan Shift", UserValue(ValueCategory::VulkanShift)); + options.addValues(HLSLToVulkanLayoutOptions::getKindInfos()); + } + /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! capabilities !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ { @@ -294,21 +321,21 @@ void initCommandOptions(CommandOptions& options) const CommandOptions::ValuePair pairs[] = { {"hlsl,fx", "hlsl"}, - {"dxbc"}, + {"dxbc", nullptr}, {"dxbc-asm", "dxbc-assembly"}, - {"dxil"}, + {"dxil", nullptr}, {"dxil-asm", "dxil-assembly"}, - {"glsl"}, + {"glsl", nullptr}, {"vert", "glsl (vertex)"}, {"frag", "glsl (fragment)"}, {"geom", "glsl (geoemtry)"}, {"tesc", "glsl (hull)"}, {"tese", "glsl (domain)"}, {"comp", "glsl (compute)"}, - {"slang"}, + {"slang", nullptr}, {"spv", "SPIR-V"}, {"spv-asm", "SPIR-V assembly"}, - {"c"}, + {"c", nullptr}, {"cpp,c++,cxx", "C++"}, {"exe", "executable"}, {"dll,so", "sharedlibrary/dll"}, @@ -418,16 +445,37 @@ void initCommandOptions(CommandOptions& options) { OptionKind::Optimization, "-O...", "-O<optimization-level>", "Set the optimization level."}, { OptionKind::Obfuscate, "-obfuscate", nullptr, "Remove all source file information from outputs." }, { OptionKind::GLSLForceScalarLayout, - "-force-glsl-scalar-layout", - nullptr, + "-force-glsl-scalar-layout", nullptr, "Force using scalar block layout for uniform and shader storage buffers in GLSL output."}, + { OptionKind::VulkanBindGlobals, "-fvk-bind-globals", "-fvk-bind-globals <N> <descriptor-set>", + "Places the $Globals cbuffer at descriptor set <descriptor-set> and binding <N>. See HLSL global variables and Vulkan binding for explanation and examples." + }, { OptionKind::EnableEffectAnnotations, - "-enable-effect-annotations", + "-enable-effect-annotations", nullptr, "Enables support for legacy effect annotation syntax."}, }; _addOptions(makeConstArrayView(targetOpts), options); + { + StringBuilder names; + for (auto nameSlice : NameValueUtil::getNames(NameValueUtil::NameKind::All, HLSLToVulkanLayoutOptions::getKindInfos())) + { + // -fvk-{b|s|t|u}-shift + names << "-fvk-" << nameSlice << "-shift,"; + } + // remove last , + names.reduceLength(names.getLength() - 1); + options.add(names.getBuffer(), "-vk-<vulkan-shift>-shift <N> <space>", + "Shifts by N the inferred binding numbers for all resources in b-type registers of space <space>. " + "Specifically, for a resouce attached with :register(bX, <space>) but not [vk::binding(...)], " + "sets its Vulkan descriptor set to <space> and binding number to X + N. If you need to shift the " + "inferred binding numbers for more than one space, provide more than one such option. " + "If more than one such option is provided for the same space, the last one takes effect. " + "If you need to shift the inferred binding numbers for all sets, use 'all' as <space>.", + UserValue(OptionKind::VulkanBindShift)); + } + /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Downstream !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ options.setCategory("Downstream"); @@ -552,13 +600,22 @@ void initCommandOptions(CommandOptions& options) SlangResult _addLibraryReference(EndToEndCompileRequest* req, IArtifact* artifact); -struct OptionsParser +class ReproPathVisitor : public Slang::Path::Visitor { - SlangSession* session = nullptr; - SlangCompileRequest* compileRequest = nullptr; +public: + virtual void accept(Slang::Path::Type type, const Slang::UnownedStringSlice& filename) SLANG_OVERRIDE + { + if (type == Path::Type::File && Path::getPathExt(filename) == "slang-repro") + { + m_filenames.add(filename); + } + } - Slang::EndToEndCompileRequest* requestImpl = nullptr; + Slang::List<String> m_filenames; +}; +struct OptionsParser +{ // A "translation unit" represents one or more source files // that are processed as a single entity when it comes to // semantic checking. @@ -603,18 +660,7 @@ struct OptionsParser // int translationUnitID; }; - List<RawTranslationUnit> rawTranslationUnits; - - // If we already have a translation unit for Slang code, then this will give its index. - // If not, it will be `-1`. - int slangTranslationUnitIndex = -1; - - // The number of input files that have been specified - int inputPathCount = 0; - - int translationUnitCount = 0; - int currentTranslationUnitIndex= -1; - + // An entry point represents a function to be checked and possibly have // code generated in one of our translation units. An entry point // needs to have an associated stage, which might come via the @@ -632,22 +678,7 @@ struct OptionsParser bool conflictingStagesSet = false; bool redundantStageSet = false; }; - // - // We collect the entry points in a "raw" array so that we can - // possibly associate them with a stage or translation unit - // after the fact. - // - List<RawEntryPoint> rawEntryPoints; - - // In the case where we have only a single entry point, - // the entry point and its options might be specified out - // of order, so we will keep a single `RawEntryPoint` around - // and use it as the target for any state-setting options - // before the first "proper" entry point is specified. - RawEntryPoint defaultEntryPoint; - - SlangCompileFlags flags = 0; - + struct RawOutput { String path; @@ -656,8 +687,7 @@ struct OptionsParser int entryPointIndex = -1; bool isWholeProgram = false; }; - List<RawOutput> rawOutputs; - + struct RawTarget { CodeGenTarget format = CodeGenTarget::Unknown; @@ -673,1728 +703,1878 @@ struct OptionsParser bool redundantProfileSet = false; }; - List<RawTarget> rawTargets; + + int addTranslationUnit(SlangSourceLanguage language, Stage impliedStage); - RawTarget defaultTarget; + void addInputSlangPath(String const& path); - int addTranslationUnit( - SlangSourceLanguage language, - Stage impliedStage) - { - auto translationUnitIndex = rawTranslationUnits.getCount(); - auto translationUnitID = compileRequest->addTranslationUnit(language, nullptr); + void addInputForeignShaderPath( + String const& path, + SlangSourceLanguage language, + Stage impliedStage); - // As a sanity check: the API should be returning the same translation - // unit index as we maintain internally. This invariant would only - // be broken if we decide to support a mix of translation units specified - // via API, and ones specified via command-line arguments. - // - SLANG_RELEASE_ASSERT(Index(translationUnitID) == translationUnitIndex); + static Profile::RawVal findGlslProfileFromPath(const String& path); - RawTranslationUnit rawTranslationUnit; - rawTranslationUnit.sourceLanguage = language; - rawTranslationUnit.translationUnitID = translationUnitID; - rawTranslationUnit.impliedStage = impliedStage; + static SlangSourceLanguage findSourceLanguageFromPath(const String& path, Stage& outImpliedStage); - rawTranslationUnits.add(rawTranslationUnit); + SlangResult addInputPath(char const* inPath, SourceLanguage langOverride = SourceLanguage::Unknown); - return int(translationUnitIndex); - } + void addOutputPath(String const& path, CodeGenTarget impliedFormat); - void addInputSlangPath( - String const& path) - { - // All of the input .slang files will be grouped into a single logical translation unit, - // which we create lazily when the first .slang file is encountered. - if( slangTranslationUnitIndex == -1 ) - { - translationUnitCount++; - slangTranslationUnitIndex = addTranslationUnit(SLANG_SOURCE_LANGUAGE_SLANG, Stage::Unknown); - } + void addOutputPath(char const* inPath); + RawEntryPoint* getCurrentEntryPoint(); - compileRequest->addTranslationUnitSourceFile(rawTranslationUnits[slangTranslationUnitIndex].translationUnitID, path.begin()); + void setStage(RawEntryPoint* rawEntryPoint, Stage stage); - // Set the translation unit to be used by subsequent entry points - currentTranslationUnitIndex = slangTranslationUnitIndex; - } + RawTarget* getCurrentTarget(); + void setProfileVersion(RawTarget* rawTarget, ProfileVersion profileVersion); + void addCapabilityAtom(RawTarget* rawTarget, CapabilityAtom atom); + + void setFloatingPointMode(RawTarget* rawTarget, FloatingPointMode mode); + + SlangResult parse( + SlangCompileRequest* compileRequest, + int argc, + char const* const* argv); - void addInputForeignShaderPath( - String const& path, - SlangSourceLanguage language, - Stage impliedStage) - { - translationUnitCount++; - currentTranslationUnitIndex = addTranslationUnit(language, impliedStage); + SlangResult _parse( + int argc, + char const* const* argv); - compileRequest->addTranslationUnitSourceFile(rawTranslationUnits[currentTranslationUnitIndex].translationUnitID, path.begin()); - } + static bool _passThroughRequiresStage(PassThroughMode passThrough); - static Profile::RawVal findGlslProfileFromPath(const String& path) + + SlangResult _compileReproDirectory(SlangSession* session, EndToEndCompileRequest* originalRequest, const String& dir); + + // Pass Severity::Disabled to allow any original severity + SlangResult _overrideDiagnostics(const UnownedStringSlice& identifierList, Severity originalSeverity, Severity overrideSeverity); + + // Pass Severity::Disabled to allow any original severity + SlangResult _overrideDiagnostic(const UnownedStringSlice& identifier, Severity originalSeverity, Severity overrideSeverity); + + SlangResult _dumpDiagnostics(Severity originalSeverity); + + template <typename T> + SlangResult _getValue(const CommandLineArg& arg, const UnownedStringSlice& name, T& ioValue) { - struct Entry - { - const char* ext; - Profile::RawVal profileId; - }; + CommandOptions::UserValue value; + SLANG_RETURN_ON_FAIL(_getValue(ValueCategory(GetValueCategory<T>::Value), arg, name, value)); + ioValue = T(value); + return SLANG_OK; + } - static const Entry entries[] = - { - { ".frag", Profile::GLSL_Fragment }, - { ".geom", Profile::GLSL_Geometry }, - { ".tesc", Profile::GLSL_TessControl }, - { ".tese", Profile::GLSL_TessEval }, - { ".comp", Profile::GLSL_Compute } - }; + SlangResult _getValue(ValueCategory valueCategory, const CommandLineArg& arg, const UnownedStringSlice& name, CommandOptions::UserValue& outValue); + SlangResult _getValue(ValueCategory valueCategory, const CommandLineArg& arg, CommandOptions::UserValue& outValue); + SlangResult _getValue(const ConstArrayView<ValueCategory>& valueCategories, const CommandLineArg& arg, const UnownedStringSlice& name, ValueCategory& outCat, CommandOptions::UserValue& outValue); + + SlangResult _expectValue(ValueCategory valueCategory, CommandOptions::UserValue& outValue); + SlangResult _expectInt(const CommandLineArg& arg, Int& outInt); - for (Index i = 0; i < SLANG_COUNT_OF(entries); ++i) - { - const Entry& entry = entries[i]; - if (path.endsWith(entry.ext)) - { - return entry.profileId; - } - } - return Profile::Unknown; + template <typename T> + SlangResult _expectValue(T& ioValue) + { + CommandOptions::UserValue value; + SLANG_RETURN_ON_FAIL(_expectValue(ValueCategory(GetValueCategory<T>::Value), value)); + ioValue = T(value); + return SLANG_OK; } - static SlangSourceLanguage findSourceLanguageFromPath(const String& path, Stage& outImpliedStage) + void _appendUsageTitle(StringBuilder& out); + void _appendMinimalUsage(StringBuilder& out); + void _outputMinimalUsage(); + + SlangResult _parseReferenceModule(const CommandLineArg& arg); + SlangResult _parseReproFileSystem(const CommandLineArg& arg); + SlangResult _parseLoadRepro(const CommandLineArg& arg); + SlangResult _parseDebugInformation(const CommandLineArg& arg); + SlangResult _parseProfile(const CommandLineArg& arg); + SlangResult _parseHelp(const CommandLineArg& arg); + + SlangSession* m_session = nullptr; + SlangCompileRequest* m_compileRequest = nullptr; + + Slang::EndToEndCompileRequest* m_requestImpl = nullptr; + + List<RawTarget> m_rawTargets; + + RawTarget m_defaultTarget; + + // + // 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> m_rawEntryPoints; + + // In the case where we have only a single entry point, + // the entry point and its options might be specified out + // of order, so we will keep a single `RawEntryPoint` around + // and use it as the target for any state-setting options + // before the first "proper" entry point is specified. + RawEntryPoint m_defaultEntryPoint; + + SlangCompileFlags m_flags = 0; + + RefPtr<HLSLToVulkanLayoutOptions> m_hlslToVulkanLayoutOptions; + + List<RawTranslationUnit> m_rawTranslationUnits; + + // If we already have a translation unit for Slang code, then this will give its index. + // If not, it will be `-1`. + int m_slangTranslationUnitIndex = -1; + + // The number of input files that have been specified + int m_inputPathCount = 0; + + int m_translationUnitCount = 0; + int m_currentTranslationUnitIndex = -1; + + List<RawOutput> m_rawOutputs; + + DiagnosticSink m_parseSink; + DiagnosticSink* m_sink = nullptr; + + FrontEndCompileRequest* m_frontEndReq = nullptr; + + SlangMatrixLayoutMode m_defaultMatrixLayoutMode = SLANG_MATRIX_LAYOUT_MODE_UNKNOWN; + + // The default archive type is zip + SlangArchiveType m_archiveType = SLANG_ARCHIVE_TYPE_ZIP; + + bool m_compileStdLib = false; + slang::CompileStdLibFlags m_compileStdLibFlags = 0; + bool m_hasLoadedRepro = false; + + CommandLineReader m_reader; + + CommandOptionsWriter::Style m_helpStyle = CommandOptionsWriter::Style::Text; + + CommandOptions* m_cmdOptions = nullptr; + CommandLineContext* m_cmdLineContext = nullptr; +}; + +int OptionsParser::addTranslationUnit( + SlangSourceLanguage language, + Stage impliedStage) +{ + auto translationUnitIndex = m_rawTranslationUnits.getCount(); + auto translationUnitID = m_compileRequest->addTranslationUnit(language, nullptr); + + // As a sanity check: the API should be returning the same translation + // unit index as we maintain internally. This invariant would only + // be broken if we decide to support a mix of translation units specified + // via API, and ones specified via command-line arguments. + // + SLANG_RELEASE_ASSERT(Index(translationUnitID) == translationUnitIndex); + + RawTranslationUnit rawTranslationUnit; + rawTranslationUnit.sourceLanguage = language; + rawTranslationUnit.translationUnitID = translationUnitID; + rawTranslationUnit.impliedStage = impliedStage; + + m_rawTranslationUnits.add(rawTranslationUnit); + + return int(translationUnitIndex); +} + +void OptionsParser::addInputSlangPath( + String const& path) +{ + // All of the input .slang files will be grouped into a single logical translation unit, + // which we create lazily when the first .slang file is encountered. + if (m_slangTranslationUnitIndex == -1) { - struct Entry - { - const char* ext; - SlangSourceLanguage sourceLanguage; - SlangStage impliedStage; - }; + m_translationUnitCount++; + m_slangTranslationUnitIndex = addTranslationUnit(SLANG_SOURCE_LANGUAGE_SLANG, Stage::Unknown); + } - static const Entry entries[] = - { - { ".slang", SLANG_SOURCE_LANGUAGE_SLANG, SLANG_STAGE_NONE }, + m_compileRequest->addTranslationUnitSourceFile(m_rawTranslationUnits[m_slangTranslationUnitIndex].translationUnitID, path.begin()); - { ".hlsl", SLANG_SOURCE_LANGUAGE_HLSL, SLANG_STAGE_NONE }, - { ".fx", SLANG_SOURCE_LANGUAGE_HLSL, SLANG_STAGE_NONE }, + // Set the translation unit to be used by subsequent entry points + m_currentTranslationUnitIndex = m_slangTranslationUnitIndex; +} - { ".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 }, - { ".mesh", SLANG_SOURCE_LANGUAGE_GLSL, SLANG_STAGE_MESH }, - { ".task", SLANG_SOURCE_LANGUAGE_GLSL, SLANG_STAGE_AMPLIFICATION }, +void OptionsParser::addInputForeignShaderPath( + String const& path, + SlangSourceLanguage language, + Stage impliedStage) +{ + m_translationUnitCount++; + m_currentTranslationUnitIndex = addTranslationUnit(language, impliedStage); - { ".c", SLANG_SOURCE_LANGUAGE_C, SLANG_STAGE_NONE }, - { ".cpp", SLANG_SOURCE_LANGUAGE_CPP, SLANG_STAGE_NONE }, - { ".cu", SLANG_SOURCE_LANGUAGE_CUDA, SLANG_STAGE_NONE } + m_compileRequest->addTranslationUnitSourceFile(m_rawTranslationUnits[m_currentTranslationUnitIndex].translationUnitID, path.begin()); +} - }; +/* static */Profile::RawVal OptionsParser::findGlslProfileFromPath(const String& path) +{ + struct Entry + { + const char* ext; + Profile::RawVal profileId; + }; + + static const Entry entries[] = + { + { ".frag", Profile::GLSL_Fragment }, + { ".geom", Profile::GLSL_Geometry }, + { ".tesc", Profile::GLSL_TessControl }, + { ".tese", Profile::GLSL_TessEval }, + { ".comp", Profile::GLSL_Compute } + }; - for (Index i = 0; i < SLANG_COUNT_OF(entries); ++i) + for (Index i = 0; i < SLANG_COUNT_OF(entries); ++i) + { + const Entry& entry = entries[i]; + if (path.endsWith(entry.ext)) { - const Entry& entry = entries[i]; - if (path.endsWith(entry.ext)) - { - outImpliedStage = Stage(entry.impliedStage); - return entry.sourceLanguage; - } + return entry.profileId; } - return SLANG_SOURCE_LANGUAGE_UNKNOWN; } + return Profile::Unknown; +} - SlangResult addInputPath( - char const* inPath, - SourceLanguage langOverride = SourceLanguage::Unknown) +/* static */SlangSourceLanguage OptionsParser::findSourceLanguageFromPath(const String& path, Stage& outImpliedStage) +{ + struct Entry { - inputPathCount++; + const char* ext; + SlangSourceLanguage sourceLanguage; + SlangStage impliedStage; + }; - // look at the extension on the file name to determine - // how we should handle it. - String path = String(inPath); + static const Entry entries[] = + { + { ".slang", SLANG_SOURCE_LANGUAGE_SLANG, SLANG_STAGE_NONE }, - if( path.endsWith(".slang") || langOverride == SourceLanguage::Slang) - { - // Plain old slang code - addInputSlangPath(path); - return SLANG_OK; - } + { ".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 }, + { ".mesh", SLANG_SOURCE_LANGUAGE_GLSL, SLANG_STAGE_MESH }, + { ".task", SLANG_SOURCE_LANGUAGE_GLSL, SLANG_STAGE_AMPLIFICATION }, - Stage impliedStage = Stage::Unknown; - SlangSourceLanguage sourceLanguage = langOverride == SourceLanguage::Unknown ? findSourceLanguageFromPath(path, impliedStage) : SlangSourceLanguage(langOverride); + { ".c", SLANG_SOURCE_LANGUAGE_C, SLANG_STAGE_NONE }, + { ".cpp", SLANG_SOURCE_LANGUAGE_CPP, SLANG_STAGE_NONE }, + { ".cu", SLANG_SOURCE_LANGUAGE_CUDA, SLANG_STAGE_NONE } + + }; - if (sourceLanguage == SLANG_SOURCE_LANGUAGE_UNKNOWN) + for (Index i = 0; i < SLANG_COUNT_OF(entries); ++i) + { + const Entry& entry = entries[i]; + if (path.endsWith(entry.ext)) { - requestImpl->getSink()->diagnose(SourceLoc(), Diagnostics::cannotDeduceSourceLanguage, inPath); - return SLANG_FAIL; + outImpliedStage = Stage(entry.impliedStage); + return entry.sourceLanguage; } + } + return SLANG_SOURCE_LANGUAGE_UNKNOWN; +} - addInputForeignShaderPath(path, sourceLanguage, impliedStage); +SlangResult OptionsParser::addInputPath(char const* inPath, SourceLanguage langOverride ) +{ + m_inputPathCount++; + + // look at the extension on the file name to determine + // how we should handle it. + String path = String(inPath); + if (path.endsWith(".slang") || langOverride == SourceLanguage::Slang) + { + // Plain old slang code + addInputSlangPath(path); return SLANG_OK; } - void addOutputPath( - String const& path, - CodeGenTarget impliedFormat) + Stage impliedStage = Stage::Unknown; + SlangSourceLanguage sourceLanguage = langOverride == SourceLanguage::Unknown ? findSourceLanguageFromPath(path, impliedStage) : SlangSourceLanguage(langOverride); + + if (sourceLanguage == SLANG_SOURCE_LANGUAGE_UNKNOWN) { - RawOutput rawOutput; - rawOutput.path = path; - rawOutput.impliedFormat = impliedFormat; - rawOutputs.add(rawOutput); + m_requestImpl->getSink()->diagnose(SourceLoc(), Diagnostics::cannotDeduceSourceLanguage, inPath); + return SLANG_FAIL; } - void addOutputPath(char const* inPath) - { - String path = String(inPath); - String ext = Path::getPathExt(path); + addInputForeignShaderPath(path, sourceLanguage, impliedStage); - if (ext == toSlice("slang-module") || - ext == toSlice("slang-lib") || - ext == toSlice("dir") || - ext == toSlice("zip")) - { - // These extensions don't indicate a artifact container, just that we want to emit IR - if (ext == toSlice("slang-module") || - ext == toSlice("slang-lib")) - { - // We want to emit IR - requestImpl->m_emitIr = true; - } - else - { - // We want to write out in an artfact "container", that can hold multiple artifacts. - compileRequest->setOutputContainerFormat(SLANG_CONTAINER_FORMAT_SLANG_MODULE); - } + return SLANG_OK; +} + +void OptionsParser::addOutputPath( + String const& path, + CodeGenTarget impliedFormat) +{ + RawOutput rawOutput; + rawOutput.path = path; + rawOutput.impliedFormat = impliedFormat; + m_rawOutputs.add(rawOutput); +} - requestImpl->m_containerOutputPath = path; +void OptionsParser::addOutputPath(char const* inPath) +{ + String path = String(inPath); + String ext = Path::getPathExt(path); + + if (ext == toSlice("slang-module") || + ext == toSlice("slang-lib") || + ext == toSlice("dir") || + ext == toSlice("zip")) + { + // These extensions don't indicate a artifact container, just that we want to emit IR + if (ext == toSlice("slang-module") || + ext == toSlice("slang-lib")) + { + // We want to emit IR + m_requestImpl->m_emitIr = true; } else { - const SlangCompileTarget target = TypeTextUtil::findCompileTargetFromExtension(ext.getUnownedSlice()); - // If the target is not found the value returned is Unknown. This is okay because - // we allow an unknown-format `-o`, assuming we get a target format - // from another argument. - addOutputPath(path, CodeGenTarget(target)); + // We want to write out in an artfact "container", that can hold multiple artifacts. + m_compileRequest->setOutputContainerFormat(SLANG_CONTAINER_FORMAT_SLANG_MODULE); } - } - RawEntryPoint* getCurrentEntryPoint() + m_requestImpl->m_containerOutputPath = path; + } + else { - auto rawEntryPointCount = rawEntryPoints.getCount(); - return rawEntryPointCount ? &rawEntryPoints[rawEntryPointCount-1] : &defaultEntryPoint; + const SlangCompileTarget target = TypeTextUtil::findCompileTargetFromExtension(ext.getUnownedSlice()); + // If the target is not found the value returned is Unknown. This is okay because + // we allow an unknown-format `-o`, assuming we get a target format + // from another argument. + addOutputPath(path, CodeGenTarget(target)); } +} - void setStage(RawEntryPoint* rawEntryPoint, Stage stage) +OptionsParser::RawEntryPoint* OptionsParser::getCurrentEntryPoint() +{ + auto rawEntryPointCount = m_rawEntryPoints.getCount(); + return rawEntryPointCount ? &m_rawEntryPoints[rawEntryPointCount - 1] : &m_defaultEntryPoint; +} + +void OptionsParser::setStage(RawEntryPoint* rawEntryPoint, Stage stage) +{ + if (rawEntryPoint->stage != Stage::Unknown) { - if(rawEntryPoint->stage != Stage::Unknown) + rawEntryPoint->redundantStageSet = true; + if (stage != rawEntryPoint->stage) { - rawEntryPoint->redundantStageSet = true; - if( stage != rawEntryPoint->stage ) - { - rawEntryPoint->conflictingStagesSet = true; - } + rawEntryPoint->conflictingStagesSet = true; } - rawEntryPoint->stage = stage; } + rawEntryPoint->stage = stage; +} - RawTarget* getCurrentTarget() - { - auto rawTargetCount = rawTargets.getCount(); - return rawTargetCount ? &rawTargets[rawTargetCount-1] : &defaultTarget; - } +OptionsParser::RawTarget* OptionsParser::getCurrentTarget() +{ + auto rawTargetCount = m_rawTargets.getCount(); + return rawTargetCount ? &m_rawTargets[rawTargetCount - 1] : &m_defaultTarget; +} - void setProfileVersion(RawTarget* rawTarget, ProfileVersion profileVersion) +void OptionsParser::setProfileVersion(RawTarget* rawTarget, ProfileVersion profileVersion) +{ + if (rawTarget->profileVersion != ProfileVersion::Unknown) { - if(rawTarget->profileVersion != ProfileVersion::Unknown) - { - rawTarget->redundantProfileSet = true; + rawTarget->redundantProfileSet = true; - if(profileVersion != rawTarget->profileVersion) - { - rawTarget->conflictingProfilesSet = true; - } + if (profileVersion != rawTarget->profileVersion) + { + rawTarget->conflictingProfilesSet = true; } - rawTarget->profileVersion = profileVersion; } + rawTarget->profileVersion = profileVersion; +} - void addCapabilityAtom(RawTarget* rawTarget, CapabilityAtom atom) - { - rawTarget->capabilityAtoms.add(atom); - } +void OptionsParser::addCapabilityAtom(RawTarget* rawTarget, CapabilityAtom atom) +{ + rawTarget->capabilityAtoms.add(atom); +} - void setFloatingPointMode(RawTarget* rawTarget, FloatingPointMode mode) - { - rawTarget->floatingPointMode = mode; - } +void OptionsParser::setFloatingPointMode(RawTarget* rawTarget, FloatingPointMode mode) +{ + rawTarget->floatingPointMode = mode; +} - static bool _passThroughRequiresStage(PassThroughMode passThrough) +/* static */bool OptionsParser::_passThroughRequiresStage(PassThroughMode passThrough) +{ + switch (passThrough) { - switch (passThrough) + case PassThroughMode::Glslang: + case PassThroughMode::Dxc: + case PassThroughMode::Fxc: { - case PassThroughMode::Glslang: - case PassThroughMode::Dxc: - case PassThroughMode::Fxc: - { - return true; - } - default: - { - return false; - } + return true; + } + default: + { + return false; } } +} + +/* static */SlangResult OptionsParser::_compileReproDirectory(SlangSession* session, EndToEndCompileRequest* originalRequest, const String& dir) +{ + auto stdOut = originalRequest->getWriter(WriterChannel::StdOutput); + + ReproPathVisitor visitor; + Path::find(dir, nullptr, &visitor); - class ReproPathVisitor : public Slang::Path::Visitor + for (auto filename : visitor.m_filenames) { - public: - virtual void accept(Slang::Path::Type type, const Slang::UnownedStringSlice& filename) SLANG_OVERRIDE + auto path = Path::combine(dir, filename); + + ComPtr<slang::ICompileRequest> request; + SLANG_RETURN_ON_FAIL(session->createCompileRequest(request.writeRef())); + + auto requestImpl = asInternal(request); + + List<uint8_t> buffer; + SLANG_RETURN_ON_FAIL(ReproUtil::loadState(path, m_sink, buffer)); + + auto requestState = ReproUtil::getRequest(buffer); + MemoryOffsetBase base; + base.set(buffer.getBuffer(), buffer.getCount()); + + // If we can find a directory, that exists, we will set up a file system to load from that directory + ComPtr<ISlangFileSystem> fileSystem; + String dirPath; + if (SLANG_SUCCEEDED(ReproUtil::calcDirectoryPathFromFilename(path, dirPath))) { - if (type == Path::Type::File && Path::getPathExt(filename) == "slang-repro") + SlangPathType pathType; + if (SLANG_SUCCEEDED(Path::getPathType(dirPath, &pathType)) && pathType == SLANG_PATH_TYPE_DIRECTORY) { - m_filenames.add(filename); + fileSystem = new RelativeFileSystem(OSFileSystem::getExtSingleton(), dirPath); } } - Slang::List<String> m_filenames; - }; - - static SlangResult _compileReproDirectory(SlangSession* session, EndToEndCompileRequest* originalRequest, const String& dir, DiagnosticSink* sink) - { - auto stdOut = originalRequest->getWriter(WriterChannel::StdOutput); - - ReproPathVisitor visitor; - Path::find(dir, nullptr, &visitor); + SLANG_RETURN_ON_FAIL(ReproUtil::load(base, requestState, fileSystem, requestImpl)); - for (auto filename : visitor.m_filenames) + if (stdOut) { - auto path = Path::combine(dir, filename); + StringBuilder buf; + buf << filename << "\n"; + stdOut->write(buf.getBuffer(), buf.getLength()); + } - ComPtr<slang::ICompileRequest> request; - SLANG_RETURN_ON_FAIL(session->createCompileRequest(request.writeRef())); + StringBuilder bufs[Index(WriterChannel::CountOf)]; + ComPtr<ISlangWriter> writers[Index(WriterChannel::CountOf)]; + for (Index i = 0; i < Index(WriterChannel::CountOf); ++i) + { + writers[i] = new StringWriter(&bufs[0], 0); + requestImpl->setWriter(WriterChannel(i), writers[i]); + } - auto requestImpl = asInternal(request); + if (SLANG_FAILED(requestImpl->compile())) + { + const char failed[] = "FAILED!\n"; + stdOut->write(failed, SLANG_COUNT_OF(failed) - 1); - List<uint8_t> buffer; - SLANG_RETURN_ON_FAIL(ReproUtil::loadState(path, sink, buffer)); + const auto& diagnostics = bufs[Index(WriterChannel::Diagnostic)]; - auto requestState = ReproUtil::getRequest(buffer); - MemoryOffsetBase base; - base.set(buffer.getBuffer(), buffer.getCount()); + stdOut->write(diagnostics.getBuffer(), diagnostics.getLength()); - // If we can find a directory, that exists, we will set up a file system to load from that directory - ComPtr<ISlangFileSystem> fileSystem; - String dirPath; - if (SLANG_SUCCEEDED(ReproUtil::calcDirectoryPathFromFilename(path, dirPath))) - { - SlangPathType pathType; - if (SLANG_SUCCEEDED(Path::getPathType(dirPath, &pathType)) && pathType == SLANG_PATH_TYPE_DIRECTORY) - { - fileSystem = new RelativeFileSystem(OSFileSystem::getExtSingleton(), dirPath); - } - } - - SLANG_RETURN_ON_FAIL(ReproUtil::load(base, requestState, fileSystem, requestImpl)); + return SLANG_FAIL; + } + } - if (stdOut) - { - StringBuilder buf; - buf << filename << "\n"; - stdOut->write(buf.getBuffer(), buf.getLength()); - } + if (stdOut) + { + const char end[] = "(END)\n"; + stdOut->write(end, SLANG_COUNT_OF(end) - 1); + } - StringBuilder bufs[Index(WriterChannel::CountOf)]; - ComPtr<ISlangWriter> writers[Index(WriterChannel::CountOf)]; - for (Index i = 0; i < Index(WriterChannel::CountOf); ++i) - { - writers[i] = new StringWriter(&bufs[0], 0); - requestImpl->setWriter(WriterChannel(i), writers[i]); - } + return SLANG_OK; +} - if (SLANG_FAILED(requestImpl->compile())) - { - const char failed[] = "FAILED!\n"; - stdOut->write(failed, SLANG_COUNT_OF(failed) - 1); +SlangResult OptionsParser::_overrideDiagnostics(const UnownedStringSlice& identifierList, Severity originalSeverity, Severity overrideSeverity) +{ + List<UnownedStringSlice> slices; + StringUtil::split(identifierList, ',', slices); - const auto& diagnostics = bufs[Index(WriterChannel::Diagnostic)]; + for (const auto& slice : slices) + { + SLANG_RETURN_ON_FAIL(_overrideDiagnostic(slice, originalSeverity, overrideSeverity)); + } + return SLANG_OK; +} - stdOut->write(diagnostics.getBuffer(), diagnostics.getLength()); +SlangResult OptionsParser::_overrideDiagnostic(const UnownedStringSlice& identifier, Severity originalSeverity, Severity overrideSeverity) +{ + auto diagnosticsLookup = getDiagnosticsLookup(); - return SLANG_FAIL; - } - } + const DiagnosticInfo* diagnostic = nullptr; + Int diagnosticId = -1; - if (stdOut) + // If it starts with a digit we assume it a number + if (identifier.getLength() > 0 && (CharUtil::isDigit(identifier[0]) || identifier[0] == '-')) + { + if (SLANG_FAILED(StringUtil::parseInt(identifier, diagnosticId))) { - const char end[] = "(END)\n"; - stdOut->write(end, SLANG_COUNT_OF(end) - 1); + m_sink->diagnose(SourceLoc(), Diagnostics::unknownDiagnosticName, identifier); + return SLANG_FAIL; } - return SLANG_OK; + // If we use numbers, we don't worry if we can't find a diagnostic + // and silently ignore. This was the previous behavior, and perhaps + // provides a way to safely disable warnings, without worrying about + // the version of the compiler. + diagnostic = diagnosticsLookup->getDiagnosticById(diagnosticId); } - - // Pass Severity::Disabled to allow any original severity - SlangResult _overrideDiagnostics(const UnownedStringSlice& identifierList, Severity originalSeverity, Severity overrideSeverity, DiagnosticSink* sink) + else { - List<UnownedStringSlice> slices; - StringUtil::split(identifierList, ',', slices); - - for (const auto& slice : slices) + diagnostic = diagnosticsLookup->findDiagnosticByName(identifier); + if (!diagnostic) { - SLANG_RETURN_ON_FAIL(_overrideDiagnostic(slice, originalSeverity, overrideSeverity, sink)); + m_sink->diagnose(SourceLoc(), Diagnostics::unknownDiagnosticName, identifier); + return SLANG_FAIL; } - return SLANG_OK; + diagnosticId = diagnostic->id; } - // Pass Severity::Disabled to allow any original severity - SlangResult _overrideDiagnostic(const UnownedStringSlice& identifier, Severity originalSeverity, Severity overrideSeverity, DiagnosticSink* sink) + // If we are only allowing certain original severities check it's the right type + if (diagnostic && originalSeverity != Severity::Disable && diagnostic->severity != originalSeverity) { - auto diagnosticsLookup = getDiagnosticsLookup(); + // Strictly speaking the diagnostic name is known, but it's not the right severity + // to be converted from, so it is an 'unknown name' in the context of severity... + // Or perhaps we want another diagnostic + m_sink->diagnose(SourceLoc(), Diagnostics::unknownDiagnosticName, identifier); + return SLANG_FAIL; + } - const DiagnosticInfo* diagnostic = nullptr; - Int diagnosticId = -1; + // Override the diagnostic severity in the sink + m_requestImpl->getSink()->overrideDiagnosticSeverity(int(diagnosticId), overrideSeverity, diagnostic); - // If it starts with a digit we assume it a number - if (identifier.getLength() > 0 && (CharUtil::isDigit(identifier[0]) || identifier[0] == '-')) - { - if (SLANG_FAILED(StringUtil::parseInt(identifier, diagnosticId))) - { - sink->diagnose(SourceLoc(), Diagnostics::unknownDiagnosticName, identifier); - return SLANG_FAIL; - } + return SLANG_OK; +} - // If we use numbers, we don't worry if we can't find a diagnostic - // and silently ignore. This was the previous behavior, and perhaps - // provides a way to safely disable warnings, without worrying about - // the version of the compiler. - diagnostic = diagnosticsLookup->getDiagnosticById(diagnosticId); - } - else - { - diagnostic = diagnosticsLookup->findDiagnosticByName(identifier); - if (!diagnostic) - { - sink->diagnose(SourceLoc(), Diagnostics::unknownDiagnosticName, identifier); - return SLANG_FAIL; - } - diagnosticId = diagnostic->id; - } +SlangResult OptionsParser::_dumpDiagnostics(Severity originalSeverity) +{ + // Get the diagnostics and dump them + auto diagnosticsLookup = getDiagnosticsLookup(); + + StringBuilder buf; - // If we are only allowing certain original severities check it's the right type - if (diagnostic && originalSeverity != Severity::Disable && diagnostic->severity != originalSeverity) + for (const auto& diagnostic : diagnosticsLookup->getDiagnostics()) + { + if (originalSeverity != Severity::Disable && + diagnostic->severity != originalSeverity) { - // Strictly speaking the diagnostic name is known, but it's not the right severity - // to be converted from, so it is an 'unknown name' in the context of severity... - // Or perhaps we want another diagnostic - sink->diagnose(SourceLoc(), Diagnostics::unknownDiagnosticName, identifier); - return SLANG_FAIL; + continue; } - // Override the diagnostic severity in the sink - requestImpl->getSink()->overrideDiagnosticSeverity(int(diagnosticId), overrideSeverity, diagnostic); + buf.clear(); - return SLANG_OK; + buf << diagnostic->id << " : "; + NameConventionUtil::convert(NameStyle::Camel, UnownedStringSlice(diagnostic->name), NameConvention::LowerKabab, buf); + buf << "\n"; + m_sink->diagnoseRaw(Severity::Note, buf.getUnownedSlice()); } - SlangResult _dumpDiagnostics(Severity originalSeverity, DiagnosticSink* sink) - { - // Get the diagnostics and dump them - auto diagnosticsLookup = getDiagnosticsLookup(); + return SLANG_OK; +} - StringBuilder buf; +void OptionsParser::_appendUsageTitle(StringBuilder& out) +{ + out << "Usage: slangc [options...] [--] <input files>\n\n"; +} - for (const auto& diagnostic : diagnosticsLookup->getDiagnostics()) - { - if (originalSeverity != Severity::Disable && - diagnostic->severity != originalSeverity) - { - continue; - } +void OptionsParser::_outputMinimalUsage() +{ + // Output usage info + StringBuilder buf; + _appendMinimalUsage(buf); - buf.clear(); + m_sink->diagnoseRaw(Severity::Note, buf.getUnownedSlice()); +} - buf << diagnostic->id << " : "; - NameConventionUtil::convert(NameStyle::Camel, UnownedStringSlice(diagnostic->name), NameConvention::LowerKabab, buf); - buf << "\n"; - sink->diagnoseRaw(Severity::Note, buf.getUnownedSlice()); +void OptionsParser::_appendMinimalUsage(StringBuilder& out) +{ + _appendUsageTitle(out); + out << "For help: slangc -h\n"; +} + + +SlangResult OptionsParser::_getValue(ValueCategory valueCategory, const CommandLineArg& arg, const UnownedStringSlice& name, CommandOptions::UserValue& outValue) +{ + const auto optionIndex = m_cmdOptions->findOptionByCategoryUserValue(CommandOptions::UserValue(valueCategory), name); + if (optionIndex < 0) + { + const auto categoryIndex = m_cmdOptions->findCategoryByUserValue(CommandOptions::UserValue(valueCategory)); + SLANG_ASSERT(categoryIndex >= 0); + if (categoryIndex < 0) + { + return SLANG_FAIL; } - return SLANG_OK; + List<UnownedStringSlice> names; + m_cmdOptions->getCategoryOptionNames(categoryIndex, names); + + StringBuilder buf; + StringUtil::join(names.getBuffer(), names.getCount(), toSlice(", "), buf); + + m_sink->diagnose(arg.loc, Diagnostics::unknownCommandLineValue, buf); + return SLANG_FAIL; } - SlangResult _getValue(ValueCategory valueCategory, const CommandLineArg& arg, const UnownedStringSlice& name, DiagnosticSink* sink, CommandOptions::UserValue& outValue) - { - auto& cmdOptions = asInternal(session)->m_commandOptions; + outValue = m_cmdOptions->getOptionAt(optionIndex).userValue; + return SLANG_OK; +} - const auto optionIndex = cmdOptions.findOptionByCategoryUserValue(CommandOptions::UserValue(valueCategory), name); - if (optionIndex < 0) - { - const auto categoryIndex = cmdOptions.findCategoryByUserValue(CommandOptions::UserValue(valueCategory)); - SLANG_ASSERT(categoryIndex >= 0); - if (categoryIndex < 0) - { - return SLANG_FAIL; - } +SlangResult OptionsParser::_getValue(ValueCategory valueCategory, const CommandLineArg& arg, CommandOptions::UserValue& outValue) +{ + return _getValue(valueCategory, arg, arg.value.getUnownedSlice(), outValue); +} - List<UnownedStringSlice> names; - cmdOptions.getCategoryOptionNames(categoryIndex, names); +SlangResult OptionsParser::_getValue(const ConstArrayView<ValueCategory>& valueCategories, const CommandLineArg& arg, const UnownedStringSlice& name, ValueCategory& outCat, CommandOptions::UserValue& outValue) +{ + auto& cmdOptions = asInternal(m_session)->m_commandOptions; - StringBuilder buf; - StringUtil::join(names.getBuffer(), names.getCount(), toSlice(", "), buf); + for (auto valueCategory : valueCategories) + { + const auto optionIndex = cmdOptions.findOptionByCategoryUserValue(CommandOptions::UserValue(valueCategory), name); + if (optionIndex >= 0) + { + outCat = valueCategory; + outValue = cmdOptions.getOptionAt(optionIndex).userValue; + return SLANG_OK; + } + } - sink->diagnose(arg.loc, Diagnostics::unknownCommandLineValue, buf); + List<UnownedStringSlice> names; + for (auto valueCategory : valueCategories) + { + const auto categoryIndex = cmdOptions.findCategoryByUserValue(CommandOptions::UserValue(valueCategory)); + SLANG_ASSERT(categoryIndex >= 0); + if (categoryIndex < 0) + { return SLANG_FAIL; } - - outValue = cmdOptions.getOptionAt(optionIndex).userValue; - return SLANG_OK; + cmdOptions.appendCategoryOptionNames(categoryIndex, names); } - SlangResult _getValue(ValueCategory valueCategory, const CommandLineArg& arg, DiagnosticSink* sink, CommandOptions::UserValue& outValue) + StringBuilder buf; + StringUtil::join(names.getBuffer(), names.getCount(), toSlice(", "), buf); + + m_sink->diagnose(arg.loc, Diagnostics::unknownCommandLineValue, buf); + return SLANG_FAIL; +} + +SlangResult OptionsParser::_expectValue(ValueCategory valueCategory, CommandOptions::UserValue& outValue) +{ + CommandLineArg arg; + SLANG_RETURN_ON_FAIL(m_reader.expectArg(arg)); + SLANG_RETURN_ON_FAIL(_getValue(valueCategory, arg, outValue)); + return SLANG_OK; +} + +SlangResult OptionsParser::_expectInt(const CommandLineArg& initArg, Int& outInt) +{ + SLANG_UNUSED(initArg); + + CommandLineArg arg; + SLANG_RETURN_ON_FAIL(m_reader.expectArg(arg)); + + if (SLANG_FAILED(StringUtil::parseInt(arg.value.getUnownedSlice(), outInt))) { - return _getValue(valueCategory, arg, arg.value.getUnownedSlice(), sink, outValue); + m_sink->diagnose(arg.loc, Diagnostics::expectingAnInteger); + return SLANG_FAIL; } + return SLANG_OK; +} - SlangResult _getValue(const ConstArrayView<ValueCategory>& valueCategories, const CommandLineArg& arg, const UnownedStringSlice& name, DiagnosticSink* sink, ValueCategory& outCat, CommandOptions::UserValue& outValue) - { - auto& cmdOptions = asInternal(session)->m_commandOptions; +SlangResult OptionsParser::_parseReferenceModule(const CommandLineArg& arg) +{ + SLANG_UNUSED(arg); - for (auto valueCategory : valueCategories) - { - const auto optionIndex = cmdOptions.findOptionByCategoryUserValue(CommandOptions::UserValue(valueCategory), name); - if (optionIndex >= 0) - { - outCat = valueCategory; - outValue = cmdOptions.getOptionAt(optionIndex).userValue; - return SLANG_OK; - } - } + CommandLineArg referenceModuleName; + SLANG_RETURN_ON_FAIL(m_reader.expectArg(referenceModuleName)); - List<UnownedStringSlice> names; - for (auto valueCategory : valueCategories) - { - const auto categoryIndex = cmdOptions.findCategoryByUserValue(CommandOptions::UserValue(valueCategory)); - SLANG_ASSERT(categoryIndex >= 0); - if (categoryIndex < 0) - { - return SLANG_FAIL; - } - cmdOptions.appendCategoryOptionNames(categoryIndex, names); - } + const auto path = referenceModuleName.value; - StringBuilder buf; - StringUtil::join(names.getBuffer(), names.getCount(), toSlice(", "), buf); + auto desc = ArtifactDescUtil::getDescFromPath(path.getUnownedSlice()); - sink->diagnose(arg.loc, Diagnostics::unknownCommandLineValue, buf); + if (desc.kind == ArtifactKind::Unknown) + { + m_sink->diagnose(referenceModuleName.loc, Diagnostics::unknownLibraryKind, Path::getPathExt(path)); return SLANG_FAIL; } - SlangResult _expectValue(ValueCategory valueCategory, CommandLineReader& reader, DiagnosticSink* sink, CommandOptions::UserValue& outValue) + // If it's a GPU binary, then we'll assume it's a library + if (ArtifactDescUtil::isGpuUsable(desc)) { - CommandLineArg arg; - SLANG_RETURN_ON_FAIL(reader.expectArg(arg)); - SLANG_RETURN_ON_FAIL(_getValue(valueCategory, arg, sink, outValue)); - return SLANG_OK; + desc.kind = ArtifactKind::Library; } - void _appendUsageTitle(StringBuilder& out) + // If its a zip we'll *assume* its a zip holding compilation results + if (desc.kind == ArtifactKind::Zip) { - out << "Usage: slangc [options...] [--] <input files>\n\n"; + desc.payload = ArtifactPayload::CompileResults; } - void _appendMinimalUsage(StringBuilder& out) + + if (!ArtifactDescUtil::isLinkable(desc)) { - _appendUsageTitle(out); - out << "For help: slangc -h\n"; + m_sink->diagnose(referenceModuleName.loc, Diagnostics::kindNotLinkable, Path::getPathExt(path)); + return SLANG_FAIL; } - void _outputMinimalUsage(DiagnosticSink* sink) + + const String name = ArtifactDescUtil::getBaseNameFromPath(desc, path.getUnownedSlice()); + + // Create the artifact + auto artifact = Artifact::create(desc, name.getUnownedSlice()); + + // There is a problem here if I want to reference a library that is a 'system' library or is not directly a file + // In that case the path shouldn't be set and the name should completely define the library. + // Seeing as on all targets the baseName doesn't have an extension, and all library types do + // if the name doesn't have an extension we can assume there is no path to it. + + ComPtr<IOSFileArtifactRepresentation> fileRep; + if (Path::getPathExt(path).getLength() <= 0) { - // Output usage info - StringBuilder buf; - _appendMinimalUsage(buf); + // If there is no extension *assume* it is the name of a system level library + fileRep = new OSFileArtifactRepresentation(IOSFileArtifactRepresentation::Kind::NameOnly, path.getUnownedSlice(), nullptr); + } + else + { + fileRep = new OSFileArtifactRepresentation(IOSFileArtifactRepresentation::Kind::Reference, path.getUnownedSlice(), nullptr); + if (!fileRep->exists()) + { + m_sink->diagnose(referenceModuleName.loc, Diagnostics::libraryDoesNotExist, path); + return SLANG_FAIL; + } + } + artifact->addRepresentation(fileRep); + + SLANG_RETURN_ON_FAIL(_addLibraryReference(m_requestImpl, artifact)); + return SLANG_OK; +} + +SlangResult OptionsParser::_parseReproFileSystem(const CommandLineArg& arg) +{ + SLANG_UNUSED(arg); - sink->diagnoseRaw(Severity::Note, buf.getUnownedSlice()); + CommandLineArg reproName; + SLANG_RETURN_ON_FAIL(m_reader.expectArg(reproName)); + + List<uint8_t> buffer; + { + const Result res = ReproUtil::loadState(reproName.value, m_sink, buffer); + if (SLANG_FAILED(res)) + { + m_sink->diagnose(reproName.loc, Diagnostics::unableToReadFile, reproName.value); + return res; + } } - SlangResult parse( - int argc, - char const* const* argv) - { - - // Copy some state out of the current request, in case we've been called - // after some other initialization has been performed. - flags = requestImpl->getFrontEndReq()->compileFlags; - - DiagnosticSink* requestSink = requestImpl->getSink(); - - CommandLineContext* cmdLineContext = requestImpl->getLinkage()->m_downstreamArgs.getContext(); - - // Why create a new DiagnosticSink? - // We *don't* want the lexer that comes as default (it's for Slang source!) - // We may want to set flags that are different - // We will need to use a new sourceManager that will just last for this parse and will map locs to - // source lines. - // - // The *problem* is that we still need to communicate to the requestSink in some suitable way. - // - // 1) We could have some kind of scoping mechanism (and only one sink) - // 2) We could have a 'parent' diagnostic sink, that if we set we route output too - // 3) We use something like the ISlangWriter to always be the thing output too (this has problems because - // some code assumes the diagnostics are accessible as a string) - // - // The solution used here is to have DiagnosticsSink have a 'parent' that also gets diagnostics reported to. - - DiagnosticSink parseSink(cmdLineContext->getSourceManager(), nullptr); - + auto requestState = ReproUtil::getRequest(buffer); + MemoryOffsetBase base; + base.set(buffer.getBuffer(), buffer.getCount()); + + // If we can find a directory, that exists, we will set up a file system to load from that directory + ComPtr<ISlangFileSystem> dirFileSystem; + String dirPath; + if (SLANG_SUCCEEDED(ReproUtil::calcDirectoryPathFromFilename(reproName.value, dirPath))) + { + SlangPathType pathType; + if (SLANG_SUCCEEDED(Path::getPathType(dirPath, &pathType)) && pathType == SLANG_PATH_TYPE_DIRECTORY) { - parseSink.setFlags(requestSink->getFlags()); - // Allow HumaneLoc - it won't display much for command line parsing - just (1): - // Leaving allows for diagnostics to be compatible with other Slang diagnostic parsing. - //parseSink.resetFlag(DiagnosticSink::Flag::HumaneLoc); - parseSink.setFlag(DiagnosticSink::Flag::SourceLocationLine); + dirFileSystem = new RelativeFileSystem(OSFileSystem::getExtSingleton(), dirPath, true); } + } + + ComPtr<ISlangFileSystemExt> fileSystem; + SLANG_RETURN_ON_FAIL(ReproUtil::loadFileSystem(base, requestState, dirFileSystem, fileSystem)); + + auto cacheFileSystem = as<CacheFileSystem>(fileSystem); + SLANG_ASSERT(cacheFileSystem); + + // I might want to make the dir file system the fallback file system... + cacheFileSystem->setInnerFileSystem(dirFileSystem, cacheFileSystem->getUniqueIdentityMode(), cacheFileSystem->getPathStyle()); + + // Set as the file system + m_compileRequest->setFileSystem(fileSystem); + return SLANG_OK; +} - // All diagnostics will also be sent to requestSink - parseSink.setParentSink(requestSink); +SlangResult OptionsParser::_parseHelp(const CommandLineArg& arg) +{ + SLANG_UNUSED(arg); - DiagnosticSink* sink = &parseSink; + Index categoryIndex = -1; - // Set up the args - CommandLineArgs args(cmdLineContext); - // Converts input args into args in 'args'. - // Doing so will allocate some SourceLoc space from the CommandLineContext. - args.setArgs(argv, argc); + if (m_reader.hasArg()) + { + auto catArg = m_reader.getArgAndAdvance(); + categoryIndex = m_cmdOptions->findCategoryByCaseInsensitiveName(catArg.value.getUnownedSlice()); + if (categoryIndex < 0) { - auto linkage = requestImpl->getLinkage(); - // Before we do anything else lets strip out all of the downstream arguments. - SLANG_RETURN_ON_FAIL(linkage->m_downstreamArgs.stripDownstreamArgs(args, 0, sink)); + m_sink->diagnose(catArg.loc, Diagnostics::unknownHelpCategory); + return SLANG_FAIL; } + } - CommandLineReader reader(&args, sink); + CommandOptionsWriter::Options writerOptions; + writerOptions.style = m_helpStyle; - SlangMatrixLayoutMode defaultMatrixLayoutMode = SLANG_MATRIX_LAYOUT_MODE_UNKNOWN; + auto writer = CommandOptionsWriter::create(writerOptions); - // The default archive type is zip - SlangArchiveType archiveType = SLANG_ARCHIVE_TYPE_ZIP; + auto& buf = writer->getBuilder(); + + if (categoryIndex < 0) + { + // If it's the text style we can inject usage at the top + if (m_helpStyle == CommandOptionsWriter::Style::Text) + { + _appendUsageTitle(buf); + } + else + { + // NOTE! We need this preamble because if we have links, + // we have to make sure the first thing in markdown *isn't* <> + + buf << "# Slang Command Line Options\n\n"; + buf << "*Usage:*\n"; + buf << "```\n"; + buf << "slangc [options...] [--] <input files>\n\n"; + buf << "# For help\n"; + buf << "slangc -h\n\n"; + buf << "# To generate this file\n"; + buf << "slangc -help-style markdown -h\n"; + buf << "```\n"; + } + + writer->appendDescription(m_cmdOptions); + } + else + { + writer->appendDescriptionForCategory(m_cmdOptions, categoryIndex); + } + + m_sink->diagnoseRaw(Severity::Note, buf.getBuffer()); - bool compileStdLib = false; - slang::CompileStdLibFlags compileStdLibFlags = 0; - bool hasLoadedRepro = false; + return SLANG_OK; +} - // Get the options on the session - CommandOptions& options = asInternal(session)->m_commandOptions; - CommandOptionsWriter::Style helpStyle = CommandOptionsWriter::Style::Text; +SlangResult OptionsParser::_parseLoadRepro(const CommandLineArg& arg) +{ + SLANG_UNUSED(arg); - auto frontEndReq = requestImpl->getFrontEndReq(); + CommandLineArg reproName; + SLANG_RETURN_ON_FAIL(m_reader.expectArg(reproName)); - while (reader.hasArg()) + List<uint8_t> buffer; + { + const Result res = ReproUtil::loadState(reproName.value, m_sink, buffer); + if (SLANG_FAILED(res)) { - auto arg = reader.getArgAndAdvance(); - const auto& argValue = arg.value; + m_sink->diagnose(reproName.loc, Diagnostics::unableToReadFile, reproName.value); + return res; + } + } - // If it's not an option we assume it's a path - if (argValue[0] != '-') - { - SLANG_RETURN_ON_FAIL(addInputPath(argValue.getBuffer())); - continue; - } + auto requestState = ReproUtil::getRequest(buffer); + MemoryOffsetBase base; + base.set(buffer.getBuffer(), buffer.getCount()); - const Index optionIndex = options.findOptionByName(argValue.getUnownedSlice()); + // If we can find a directory, that exists, we will set up a file system to load from that directory + ComPtr<ISlangFileSystem> fileSystem; + String dirPath; + if (SLANG_SUCCEEDED(ReproUtil::calcDirectoryPathFromFilename(reproName.value, dirPath))) + { + SlangPathType pathType; + if (SLANG_SUCCEEDED(Path::getPathType(dirPath, &pathType)) && pathType == SLANG_PATH_TYPE_DIRECTORY) + { + fileSystem = new RelativeFileSystem(OSFileSystem::getExtSingleton(), dirPath); + } + } - if (optionIndex < 0) - { - sink->diagnose(arg.loc, Diagnostics::unknownCommandLineOption, argValue); - _outputMinimalUsage(sink); - return SLANG_FAIL; - } + SLANG_RETURN_ON_FAIL(ReproUtil::load(base, requestState, fileSystem, m_requestImpl)); - const auto optionKind = OptionKind(options.getOptionAt(optionIndex).userValue); + m_hasLoadedRepro = true; + return SLANG_OK; +} - switch (optionKind) - { - case OptionKind::NoMangle: flags |= SLANG_COMPILE_FLAG_NO_MANGLING; break; - case OptionKind::EmitIr: requestImpl->m_emitIr = true; break; - case OptionKind::LoadStdLib: - { - CommandLineArg fileName; - SLANG_RETURN_ON_FAIL(reader.expectArg(fileName)); - - // Load the file - ScopedAllocation contents; - SLANG_RETURN_ON_FAIL(File::readAllBytes(fileName.value, contents)); - SLANG_RETURN_ON_FAIL(session->loadStdLib(contents.getData(), contents.getSizeInBytes())); - break; - } - case OptionKind::CompileStdLib: compileStdLib = true; break; - case OptionKind::ArchiveType: - { - CommandOptions::UserValue value; - SLANG_RETURN_ON_FAIL(_expectValue(ValueCategory::ArchiveType, reader, sink, value)); - archiveType = SlangArchiveType(value); - break; - } - case OptionKind::SaveStdLib: - { - CommandLineArg fileName; - SLANG_RETURN_ON_FAIL(reader.expectArg(fileName)); +SlangResult OptionsParser::_parseDebugInformation(const CommandLineArg& arg) +{ + auto name = arg.value.getUnownedSlice().tail(2); - ComPtr<ISlangBlob> blob; + // Note: unlike with `-O` above, we have to consider that other + // options might have names that start with `-g` and so cannot + // just detect it as a prefix. + if (name.getLength() == 0) + { + // The default is standard + m_compileRequest->setDebugInfoLevel(SLANG_DEBUG_INFO_LEVEL_STANDARD); + } + else + { + CommandOptions::UserValue value; + ValueCategory valueCat; + ValueCategory valueCats[] = { ValueCategory::DebugLevel, ValueCategory::DebugInfoFormat }; + SLANG_RETURN_ON_FAIL(_getValue(makeConstArrayView(valueCats), arg, name, valueCat, value)); - SLANG_RETURN_ON_FAIL(session->saveStdLib(archiveType, blob.writeRef())); - SLANG_RETURN_ON_FAIL(File::writeAllBytes(fileName.value, blob->getBufferPointer(), blob->getBufferSize())); - break; - } - case OptionKind::SaveStdLibBinSource: - { - CommandLineArg fileName; - SLANG_RETURN_ON_FAIL(reader.expectArg(fileName)); + if (valueCat == ValueCategory::DebugLevel) + { + const auto level = (SlangDebugInfoLevel)value; + m_compileRequest->setDebugInfoLevel(level); + } + else + { + const auto debugFormat = (SlangDebugInfoFormat)value; + m_compileRequest->setDebugInfoFormat(debugFormat); + } + } + return SLANG_OK; +} - ComPtr<ISlangBlob> blob; - SLANG_RETURN_ON_FAIL(session->saveStdLib(archiveType, blob.writeRef())); +SlangResult OptionsParser::_parseProfile(const CommandLineArg& arg) +{ + SLANG_UNUSED(arg); - StringBuilder builder; - StringWriter writer(&builder, 0); + // 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. - SLANG_RETURN_ON_FAIL(HexDumpUtil::dumpSourceBytes((const uint8_t*)blob->getBufferPointer(), blob->getBufferSize(), 16, &writer)); + CommandLineArg operand; + SLANG_RETURN_ON_FAIL(m_reader.expectArg(operand)); - File::writeAllText(fileName.value, builder); - break; - } - case OptionKind::NoCodeGen: flags |= SLANG_COMPILE_FLAG_NO_CODEGEN; break; - case OptionKind::DumpIntermediates: compileRequest->setDumpIntermediates(true); break; - case OptionKind::DumpIrIds: - { - frontEndReq->m_irDumpOptions.flags |= IRDumpOptions::Flag::DumpDebugIds; - break; - } - case OptionKind::DumpIntermediatePrefix: - { - CommandLineArg prefix; - SLANG_RETURN_ON_FAIL(reader.expectArg(prefix)); - requestImpl->m_dumpIntermediatePrefix = prefix.value; - break; - } - case OptionKind::OutputIncludes: frontEndReq->outputIncludes = true; break; - case OptionKind::DumpIr: frontEndReq->shouldDumpIR = true; break; - case OptionKind::PreprocessorOutput: frontEndReq->outputPreprocessor = true; break; - case OptionKind::DumpAst: frontEndReq->shouldDumpAST = true; break; - case OptionKind::Doc: - { - // If compiling stdlib is enabled, will write out documentation - compileStdLibFlags |= slang::CompileStdLibFlag::WriteDocumentation; + // A a convenience, the `-profile` option supports an operand that consists + // of multiple tokens separated with `+`. The eventual goal is that each + // of these tokens will represent a capability that should be assumed to + // be present on the target. + // + List<UnownedStringSlice> slices; + StringUtil::split(operand.value.getUnownedSlice(), '+', slices); + Index sliceCount = slices.getCount(); - // Enable writing out documentation on the req - frontEndReq->shouldDocument = true; - break; - } - case OptionKind::DumpRepro: - { - CommandLineArg dumpRepro; - SLANG_RETURN_ON_FAIL(reader.expectArg(dumpRepro)); - requestImpl->m_dumpRepro = dumpRepro.value; - compileRequest->enableReproCapture(); - break; - } - case OptionKind::DumpReproOnError: requestImpl->m_dumpReproOnError = true; break; - case OptionKind::ExtractRepro: - { - CommandLineArg reproName; - SLANG_RETURN_ON_FAIL(reader.expectArg(reproName)); + // For now, we will require that the *first* capability in the list is + // special, and represents the traditional `Profile` to compile for in + // the existing Slang model. + // + UnownedStringSlice profileName = sliceCount >= 1 ? slices[0] : UnownedTerminatedStringSlice(""); - { - const Result res = ReproUtil::extractFilesToDirectory(reproName.value, sink); - if (SLANG_FAILED(res)) - { - sink->diagnose(reproName.loc, Diagnostics::unableExtractReproToDirectory, reproName.value); - return res; - } - } - break; - } - case OptionKind::ModuleName: - { - CommandLineArg moduleName; - SLANG_RETURN_ON_FAIL(reader.expectArg(moduleName)); + SlangProfileID profileID = SlangProfileID(Slang::Profile::lookUp(profileName).raw); + if (profileID == SLANG_PROFILE_UNKNOWN) + { + m_sink->diagnose(operand.loc, Diagnostics::unknownProfile, profileName); + return SLANG_FAIL; + } + else + { + auto profile = Profile(profileID); - compileRequest->setDefaultModuleName(moduleName.value.getBuffer()); - break; - } - case OptionKind::LoadRepro: - { - CommandLineArg reproName; - SLANG_RETURN_ON_FAIL(reader.expectArg(reproName)); + setProfileVersion(getCurrentTarget(), profile.getVersion()); - List<uint8_t> buffer; - { - const Result res = ReproUtil::loadState(reproName.value, sink, buffer); - if (SLANG_FAILED(res)) - { - sink->diagnose(reproName.loc, Diagnostics::unableToReadFile, reproName.value); - return res; - } - } + // A `-profile` option that also specifies a stage (e.g., `-profile vs_5_0`) + // should be treated like a composite (e.g., `-profile sm_5_0 -stage vertex`) + auto stage = profile.getStage(); + if (stage != Stage::Unknown) + { + setStage(getCurrentEntryPoint(), stage); + } + } - auto requestState = ReproUtil::getRequest(buffer); - MemoryOffsetBase base; - base.set(buffer.getBuffer(), buffer.getCount()); + // Any additional capability tokens will be assumed to represent `CapabilityAtom`s. + // Those atoms will need to be added to the supported capabilities of the target. + // + for (Index i = 1; i < sliceCount; ++i) + { + UnownedStringSlice atomName = slices[i]; + CapabilityAtom atom = findCapabilityAtom(atomName); + if (atom == CapabilityAtom::Invalid) + { + m_sink->diagnose(operand.loc, Diagnostics::unknownProfile, atomName); + return SLANG_FAIL; + } - // If we can find a directory, that exists, we will set up a file system to load from that directory - ComPtr<ISlangFileSystem> fileSystem; - String dirPath; - if (SLANG_SUCCEEDED(ReproUtil::calcDirectoryPathFromFilename(reproName.value, dirPath))) - { - SlangPathType pathType; - if (SLANG_SUCCEEDED(Path::getPathType(dirPath, &pathType)) && pathType == SLANG_PATH_TYPE_DIRECTORY) - { - fileSystem = new RelativeFileSystem(OSFileSystem::getExtSingleton(), dirPath); - } - } + addCapabilityAtom(getCurrentTarget(), atom); + } - SLANG_RETURN_ON_FAIL(ReproUtil::load(base, requestState, fileSystem, requestImpl)); + return SLANG_OK; +} - hasLoadedRepro = true; - break; - } - case OptionKind::LoadReproDirectory: - { - CommandLineArg reproDirectory; - SLANG_RETURN_ON_FAIL(reader.expectArg(reproDirectory)); +SlangResult OptionsParser::_parse( + int argc, + char const* const* argv) +{ + // Copy some state out of the current request, in case we've been called + // after some other initialization has been performed. + m_flags = m_requestImpl->getFrontEndReq()->compileFlags; - SLANG_RETURN_ON_FAIL(_compileReproDirectory(session, requestImpl, reproDirectory.value, sink)); - break; - } - case OptionKind::ReproFileSystem: - { - CommandLineArg reproName; - SLANG_RETURN_ON_FAIL(reader.expectArg(reproName)); + // Set up the args + CommandLineArgs args(m_cmdLineContext); + // Converts input args into args in 'args'. + // Doing so will allocate some SourceLoc space from the CommandLineContext. + args.setArgs(argv, argc); - List<uint8_t> buffer; - { - const Result res = ReproUtil::loadState(reproName.value, sink, buffer); - if (SLANG_FAILED(res)) - { - sink->diagnose(reproName.loc, Diagnostics::unableToReadFile, reproName.value); - return res; - } - } + { + auto linkage = m_requestImpl->getLinkage(); + // Before we do anything else lets strip out all of the downstream arguments. + SLANG_RETURN_ON_FAIL(linkage->m_downstreamArgs.stripDownstreamArgs(args, 0, m_sink)); + } - auto requestState = ReproUtil::getRequest(buffer); - MemoryOffsetBase base; - base.set(buffer.getBuffer(), buffer.getCount()); + m_reader.init(&args, m_sink); - // If we can find a directory, that exists, we will set up a file system to load from that directory - ComPtr<ISlangFileSystem> dirFileSystem; - String dirPath; - if (SLANG_SUCCEEDED(ReproUtil::calcDirectoryPathFromFilename(reproName.value, dirPath))) - { - SlangPathType pathType; - if (SLANG_SUCCEEDED(Path::getPathType(dirPath, &pathType)) && pathType == SLANG_PATH_TYPE_DIRECTORY) - { - dirFileSystem = new RelativeFileSystem(OSFileSystem::getExtSingleton(), dirPath, true); - } - } + while (m_reader.hasArg()) + { + auto arg = m_reader.getArgAndAdvance(); + const auto& argValue = arg.value; - ComPtr<ISlangFileSystemExt> fileSystem; - SLANG_RETURN_ON_FAIL(ReproUtil::loadFileSystem(base, requestState, dirFileSystem, fileSystem)); + // If it's not an option we assume it's a path + if (argValue[0] != '-') + { + SLANG_RETURN_ON_FAIL(addInputPath(argValue.getBuffer())); + continue; + } - auto cacheFileSystem = as<CacheFileSystem>(fileSystem); - SLANG_ASSERT(cacheFileSystem); + const Index optionIndex = m_cmdOptions->findOptionByName(argValue.getUnownedSlice()); - // I might want to make the dir file system the fallback file system... - cacheFileSystem->setInnerFileSystem(dirFileSystem, cacheFileSystem->getUniqueIdentityMode(), cacheFileSystem->getPathStyle()); + if (optionIndex < 0) + { + m_sink->diagnose(arg.loc, Diagnostics::unknownCommandLineOption, argValue); + _outputMinimalUsage(); + return SLANG_FAIL; + } - // Set as the file system - compileRequest->setFileSystem(fileSystem); - break; - } - case OptionKind::SerialIr: frontEndReq->useSerialIRBottleneck = true; break; - case OptionKind::DisableSpecialization: requestImpl->disableSpecialization = true; break; - case OptionKind::DisableDynamicDispatch: requestImpl->disableDynamicDispatch = true; break; - case OptionKind::TrackLiveness: requestImpl->setTrackLiveness(true); break; - case OptionKind::VerbosePaths: requestImpl->getSink()->setFlag(DiagnosticSink::Flag::VerbosePath); break; - case OptionKind::DumpWarningDiagnostics: _dumpDiagnostics(Severity::Warning, sink); break; - case OptionKind::WarningsAsErrors: - { - CommandLineArg operand; - SLANG_RETURN_ON_FAIL(reader.expectArg(operand)); + const auto optionKind = OptionKind(m_cmdOptions->getOptionAt(optionIndex).userValue); - if (operand.value == "all") - { - // TODO(JS): - // Perhaps there needs to be a way to disable this selectively. - requestImpl->getSink()->setFlag(DiagnosticSink::Flag::TreatWarningsAsErrors); - } - else - { - SLANG_RETURN_ON_FAIL(_overrideDiagnostics(operand.value.getUnownedSlice(), Severity::Warning, Severity::Error, sink)); - } - break; - } - case OptionKind::DisableWarnings: - { - CommandLineArg operand; - SLANG_RETURN_ON_FAIL(reader.expectArg(operand)); - SLANG_RETURN_ON_FAIL(_overrideDiagnostics(operand.value.getUnownedSlice(), Severity::Warning, Severity::Disable, sink)); - break; - } - case OptionKind::DisableWarning: - { - // 5 because -Wno- - auto name = argValue.getUnownedSlice().tail(5); - SLANG_RETURN_ON_FAIL(_overrideDiagnostic(name, Severity::Warning, Severity::Disable, sink)); - break; - } - case OptionKind::EnableWarning: - { - // 2 because -W - auto name = argValue.getUnownedSlice().tail(2); - // Enable the warning - SLANG_RETURN_ON_FAIL(_overrideDiagnostic(name, Severity::Warning, Severity::Warning, sink)); - break; - } - case OptionKind::VerifyDebugSerialIr: frontEndReq->verifyDebugSerialization = true; break; - case OptionKind::ValidateIr: frontEndReq->shouldValidateIR = true; break; - case OptionKind::SkipCodeGen: requestImpl->m_shouldSkipCodegen = true; break; - case OptionKind::ParameterBlocksUseRegisterSpaces: - { - getCurrentTarget()->targetFlags |= SLANG_TARGET_FLAG_PARAMETER_BLOCKS_USE_REGISTER_SPACES; - break; - } - case OptionKind::IrCompression: - { - CommandLineArg name; - SLANG_RETURN_ON_FAIL(reader.expectArg(name)); + switch (optionKind) + { + case OptionKind::NoMangle: m_flags |= SLANG_COMPILE_FLAG_NO_MANGLING; break; + case OptionKind::EmitIr: m_requestImpl->m_emitIr = true; break; + case OptionKind::LoadStdLib: + { + CommandLineArg fileName; + SLANG_RETURN_ON_FAIL(m_reader.expectArg(fileName)); - SLANG_RETURN_ON_FAIL(SerialParseUtil::parseCompressionType(name.value.getUnownedSlice(), requestImpl->getLinkage()->serialCompressionType)); - break; - } - case OptionKind::Target: - { - CommandLineArg name; - SLANG_RETURN_ON_FAIL(reader.expectArg(name)); + // Load the file + ScopedAllocation contents; + SLANG_RETURN_ON_FAIL(File::readAllBytes(fileName.value, contents)); + SLANG_RETURN_ON_FAIL(m_session->loadStdLib(contents.getData(), contents.getSizeInBytes())); + break; + } + case OptionKind::CompileStdLib: m_compileStdLib = true; break; + case OptionKind::ArchiveType: SLANG_RETURN_ON_FAIL(_expectValue(m_archiveType)); break; + case OptionKind::SaveStdLib: + { + CommandLineArg fileName; + SLANG_RETURN_ON_FAIL(m_reader.expectArg(fileName)); - const CodeGenTarget format = (CodeGenTarget)TypeTextUtil::findCompileTargetFromName(name.value.getUnownedSlice()); + ComPtr<ISlangBlob> blob; - if (format == CodeGenTarget::Unknown) - { - sink->diagnose(name.loc, Diagnostics::unknownCodeGenerationTarget, name.value); - return SLANG_FAIL; - } + SLANG_RETURN_ON_FAIL(m_session->saveStdLib(m_archiveType, blob.writeRef())); + SLANG_RETURN_ON_FAIL(File::writeAllBytes(fileName.value, blob->getBufferPointer(), blob->getBufferSize())); + break; + } + case OptionKind::SaveStdLibBinSource: + { + CommandLineArg fileName; + SLANG_RETURN_ON_FAIL(m_reader.expectArg(fileName)); - RawTarget rawTarget; - rawTarget.format = CodeGenTarget(format); + ComPtr<ISlangBlob> blob; - rawTargets.add(rawTarget); - break; - } - case OptionKind::Profile: - { - // 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. - - CommandLineArg operand; - SLANG_RETURN_ON_FAIL(reader.expectArg(operand)); - - // A a convenience, the `-profile` option supports an operand that consists - // of multiple tokens separated with `+`. The eventual goal is that each - // of these tokens will represent a capability that should be assumed to - // be present on the target. - // - List<UnownedStringSlice> slices; - StringUtil::split(operand.value.getUnownedSlice(), '+', slices); - Index sliceCount = slices.getCount(); - - // For now, we will require that the *first* capability in the list is - // special, and represents the traditional `Profile` to compile for in - // the existing Slang model. - // - UnownedStringSlice profileName = sliceCount >= 1 ? slices[0] : UnownedTerminatedStringSlice(""); - - SlangProfileID profileID = SlangProfileID(Slang::Profile::lookUp(profileName).raw); - if( profileID == SLANG_PROFILE_UNKNOWN ) - { - sink->diagnose(operand.loc, Diagnostics::unknownProfile, profileName); - return SLANG_FAIL; - } - else - { - auto profile = Profile(profileID); + SLANG_RETURN_ON_FAIL(m_session->saveStdLib(m_archiveType, blob.writeRef())); - setProfileVersion(getCurrentTarget(), profile.getVersion()); + StringBuilder builder; + StringWriter writer(&builder, 0); - // A `-profile` option that also specifies a stage (e.g., `-profile vs_5_0`) - // should be treated like a composite (e.g., `-profile sm_5_0 -stage vertex`) - auto stage = profile.getStage(); - if(stage != Stage::Unknown) - { - setStage(getCurrentEntryPoint(), stage); - } - } + SLANG_RETURN_ON_FAIL(HexDumpUtil::dumpSourceBytes((const uint8_t*)blob->getBufferPointer(), blob->getBufferSize(), 16, &writer)); - // Any additional capability tokens will be assumed to represent `CapabilityAtom`s. - // Those atoms will need to be added to the supported capabilities of the target. - // - for(Index i = 1; i < sliceCount; ++i) - { - UnownedStringSlice atomName = slices[i]; - CapabilityAtom atom = findCapabilityAtom(atomName); - if( atom == CapabilityAtom::Invalid ) - { - sink->diagnose(operand.loc, Diagnostics::unknownProfile, atomName); - return SLANG_FAIL; - } - - addCapabilityAtom(getCurrentTarget(), atom); - } + File::writeAllText(fileName.value, builder); + break; + } + case OptionKind::NoCodeGen: m_flags |= SLANG_COMPILE_FLAG_NO_CODEGEN; break; + case OptionKind::DumpIntermediates: m_compileRequest->setDumpIntermediates(true); break; + case OptionKind::DumpIrIds: + { + m_frontEndReq->m_irDumpOptions.flags |= IRDumpOptions::Flag::DumpDebugIds; + break; + } + case OptionKind::DumpIntermediatePrefix: + { + CommandLineArg prefix; + SLANG_RETURN_ON_FAIL(m_reader.expectArg(prefix)); + m_requestImpl->m_dumpIntermediatePrefix = prefix.value; + break; + } + case OptionKind::OutputIncludes: m_frontEndReq->outputIncludes = true; break; + case OptionKind::DumpIr: m_frontEndReq->shouldDumpIR = true; break; + case OptionKind::PreprocessorOutput: m_frontEndReq->outputPreprocessor = true; break; + case OptionKind::DumpAst: m_frontEndReq->shouldDumpAST = true; break; + case OptionKind::Doc: + { + // If compiling stdlib is enabled, will write out documentation + m_compileStdLibFlags |= slang::CompileStdLibFlag::WriteDocumentation; + + // Enable writing out documentation on the req + m_frontEndReq->shouldDocument = true; + break; + } + case OptionKind::DumpRepro: + { + CommandLineArg dumpRepro; + SLANG_RETURN_ON_FAIL(m_reader.expectArg(dumpRepro)); + m_requestImpl->m_dumpRepro = dumpRepro.value; + m_compileRequest->enableReproCapture(); + break; + } + case OptionKind::DumpReproOnError: m_requestImpl->m_dumpReproOnError = true; break; + case OptionKind::ExtractRepro: + { + CommandLineArg reproName; + SLANG_RETURN_ON_FAIL(m_reader.expectArg(reproName)); - break; - } - case OptionKind::Capability: { - // The `-capability` option is similar to `-profile` but does not set the actual profile - // for a target (it just adds capabilities). - // - // TODO: Once profiles are treated as capabilities themselves, it might be possible - // to treat `-profile` and `-capability` as aliases, although there might still be - // value in only allowing a single `-profile` option per target while still allowing - // zero or more `-capability` options. - - CommandLineArg operand; - SLANG_RETURN_ON_FAIL(reader.expectArg(operand)); - - List<UnownedStringSlice> slices; - StringUtil::split(operand.value.getUnownedSlice(), '+', slices); - Index sliceCount = slices.getCount(); - for(Index i = 0; i < sliceCount; ++i) + const Result res = ReproUtil::extractFilesToDirectory(reproName.value, m_sink); + if (SLANG_FAILED(res)) { - UnownedStringSlice atomName = slices[i]; - CapabilityAtom atom = findCapabilityAtom(atomName); - if( atom == CapabilityAtom::Invalid ) - { - sink->diagnose(operand.loc, Diagnostics::unknownProfile, atomName); - return SLANG_FAIL; - } - - addCapabilityAtom(getCurrentTarget(), atom); + m_sink->diagnose(reproName.loc, Diagnostics::unableExtractReproToDirectory, reproName.value); + return res; } - break; } - case OptionKind::Stage: - { - CommandLineArg name; - SLANG_RETURN_ON_FAIL(reader.expectArg(name)); + break; + } + case OptionKind::ModuleName: + { + CommandLineArg moduleName; + SLANG_RETURN_ON_FAIL(m_reader.expectArg(moduleName)); - Stage stage = findStageByName(name.value); - if( stage == Stage::Unknown ) - { - sink->diagnose(name.loc, Diagnostics::unknownStage, name.value); - return SLANG_FAIL; - } - else - { - setStage(getCurrentEntryPoint(), stage); - } - break; - } - case OptionKind::EntryPointName: - { - CommandLineArg name; - SLANG_RETURN_ON_FAIL(reader.expectArg(name)); + m_compileRequest->setDefaultModuleName(moduleName.value.getBuffer()); + break; + } + case OptionKind::LoadRepro: SLANG_RETURN_ON_FAIL(_parseLoadRepro(arg)); break; + case OptionKind::LoadReproDirectory: + { + CommandLineArg reproDirectory; + SLANG_RETURN_ON_FAIL(m_reader.expectArg(reproDirectory)); - RawEntryPoint rawEntryPoint; - rawEntryPoint.name = name.value; - rawEntryPoint.translationUnitIndex = currentTranslationUnitIndex; + SLANG_RETURN_ON_FAIL(_compileReproDirectory(m_session, m_requestImpl, reproDirectory.value)); + break; + } + case OptionKind::ReproFileSystem: SLANG_RETURN_ON_FAIL(_parseReproFileSystem(arg)); break; + case OptionKind::SerialIr: m_frontEndReq->useSerialIRBottleneck = true; break; + case OptionKind::DisableSpecialization: m_requestImpl->disableSpecialization = true; break; + case OptionKind::DisableDynamicDispatch: m_requestImpl->disableDynamicDispatch = true; break; + case OptionKind::TrackLiveness: m_requestImpl->setTrackLiveness(true); break; + case OptionKind::VerbosePaths: m_requestImpl->getSink()->setFlag(DiagnosticSink::Flag::VerbosePath); break; + case OptionKind::DumpWarningDiagnostics: _dumpDiagnostics(Severity::Warning); break; + case OptionKind::WarningsAsErrors: + { + CommandLineArg operand; + SLANG_RETURN_ON_FAIL(m_reader.expectArg(operand)); - rawEntryPoints.add(rawEntryPoint); - break; - } - case OptionKind::Language: + if (operand.value == "all") { - CommandLineArg name; - SLANG_RETURN_ON_FAIL(reader.expectArg(name)); - - const SourceLanguage sourceLanguage = (SourceLanguage)TypeTextUtil::findSourceLanguage(name.value.getUnownedSlice()); - - if (sourceLanguage == SourceLanguage::Unknown) - { - sink->diagnose(name.loc, Diagnostics::unknownSourceLanguage, name.value); - return SLANG_FAIL; - } - else - { - while (reader.hasArg() && !reader.peekValue().startsWith("-")) - { - SLANG_RETURN_ON_FAIL(addInputPath(reader.getValueAndAdvance().getBuffer(), sourceLanguage)); - } - } - break; + // TODO(JS): + // Perhaps there needs to be a way to disable this selectively. + m_requestImpl->getSink()->setFlag(DiagnosticSink::Flag::TreatWarningsAsErrors); } - case OptionKind::PassThrough: + else { - CommandLineArg name; - SLANG_RETURN_ON_FAIL(reader.expectArg(name)); - - SlangPassThrough passThrough = SLANG_PASS_THROUGH_NONE; - if (SLANG_FAILED(TypeTextUtil::findPassThrough(name.value.getUnownedSlice(), passThrough))) - { - sink->diagnose(name.loc, Diagnostics::unknownPassThroughTarget, name.value); - return SLANG_FAIL; - } - - compileRequest->setPassThrough(passThrough); - break; + SLANG_RETURN_ON_FAIL(_overrideDiagnostics(operand.value.getUnownedSlice(), Severity::Warning, Severity::Error)); } - case OptionKind::MacroDefine: - { - // The value to be defined might be part of the same option, as in: - // -DFOO - // or it might come separately, as in: - // -D FOO + break; + } + case OptionKind::DisableWarnings: + { + CommandLineArg operand; + SLANG_RETURN_ON_FAIL(m_reader.expectArg(operand)); + SLANG_RETURN_ON_FAIL(_overrideDiagnostics(operand.value.getUnownedSlice(), Severity::Warning, Severity::Disable)); + break; + } + case OptionKind::DisableWarning: + { + // 5 because -Wno- + auto name = argValue.getUnownedSlice().tail(5); + SLANG_RETURN_ON_FAIL(_overrideDiagnostic(name, Severity::Warning, Severity::Disable)); + break; + } + case OptionKind::EnableWarning: + { + // 2 because -W + auto name = argValue.getUnownedSlice().tail(2); + // Enable the warning + SLANG_RETURN_ON_FAIL(_overrideDiagnostic(name, Severity::Warning, Severity::Warning)); + break; + } + case OptionKind::VerifyDebugSerialIr: m_frontEndReq->verifyDebugSerialization = true; break; + case OptionKind::ValidateIr: m_frontEndReq->shouldValidateIR = true; break; + case OptionKind::SkipCodeGen: m_requestImpl->m_shouldSkipCodegen = true; break; + case OptionKind::ParameterBlocksUseRegisterSpaces: + { + getCurrentTarget()->targetFlags |= SLANG_TARGET_FLAG_PARAMETER_BLOCKS_USE_REGISTER_SPACES; + break; + } + case OptionKind::IrCompression: + { + CommandLineArg name; + SLANG_RETURN_ON_FAIL(m_reader.expectArg(name)); - UnownedStringSlice slice = argValue.getUnownedSlice().tail(2); + SLANG_RETURN_ON_FAIL(SerialParseUtil::parseCompressionType(name.value.getUnownedSlice(), m_requestImpl->getLinkage()->serialCompressionType)); + break; + } + case OptionKind::Target: + { + CommandLineArg name; + SLANG_RETURN_ON_FAIL(m_reader.expectArg(name)); - CommandLineArg nextArg; - if (slice.getLength() <= 0) - { - SLANG_RETURN_ON_FAIL(reader.expectArg(nextArg)); - slice = nextArg.value.getUnownedSlice(); - } + const CodeGenTarget format = (CodeGenTarget)TypeTextUtil::findCompileTargetFromName(name.value.getUnownedSlice()); - // The string that sets up the define can have an `=` between - // the name to be defined and its value, so we search for one. - const Index equalIndex = slice.indexOf('='); + if (format == CodeGenTarget::Unknown) + { + m_sink->diagnose(name.loc, Diagnostics::unknownCodeGenerationTarget, name.value); + return SLANG_FAIL; + } - // Now set the preprocessor define + RawTarget rawTarget; + rawTarget.format = CodeGenTarget(format); - if (equalIndex >= 0) - { - // If we found an `=`, we split the string... - compileRequest->addPreprocessorDefine(String(slice.head(equalIndex)).getBuffer(), String(slice.tail(equalIndex + 1)).getBuffer()); - } - else - { - // If there was no `=`, then just #define it to an empty string - compileRequest->addPreprocessorDefine(String(slice).getBuffer(), ""); - } - break; - } - case OptionKind::Include: - { - // The value to be defined might be part of the same option, as in: - // -IFOO - // or it might come separately, as in: - // -I FOO - // (see handling of `-D` above) - UnownedStringSlice slice = argValue.getUnownedSlice().tail(2); - - CommandLineArg nextArg; - if (slice.getLength() <= 0) - { - // Need to read another argument from the command line - SLANG_RETURN_ON_FAIL(reader.expectArg(nextArg)); - slice = nextArg.value.getUnownedSlice(); - } + m_rawTargets.add(rawTarget); + break; + } + case OptionKind::VulkanBindShift: + { + // -fvk-{b|s|t|u}-shift + const auto slice = arg.value.getUnownedSlice().subString(5, 1); + HLSLToVulkanLayoutOptions::Kind kind; + SLANG_RETURN_ON_FAIL(_getValue(arg, slice, kind)); - compileRequest->addSearchPath(String(slice).getBuffer()); - break; - } - case OptionKind::Output: + Int shift; + SLANG_RETURN_ON_FAIL(_expectInt(arg, shift)); + + if (m_reader.hasArg() && m_reader.peekArg().value == toSlice("all")) { - // - // A `-o` option is used to specify a desired output file. - CommandLineArg outputPath; - SLANG_RETURN_ON_FAIL(reader.expectArg(outputPath)); - - addOutputPath(outputPath.value.getBuffer()); - break; + m_reader.advance(); + m_hlslToVulkanLayoutOptions->setAllShift(kind, shift); } - case OptionKind::DepFile: + else { - CommandLineArg dependencyPath; - SLANG_RETURN_ON_FAIL(reader.expectArg(dependencyPath)); + Int set; + SLANG_RETURN_ON_FAIL(_expectInt(arg, set)); + m_hlslToVulkanLayoutOptions->setShift(kind, set, shift); + } + break; + } + case OptionKind::VulkanBindGlobals: + { + // -fvk-bind-globals N M + Int binding, bindingSet; + SLANG_RETURN_ON_FAIL(_expectInt(arg, binding)); + SLANG_RETURN_ON_FAIL(_expectInt(arg, bindingSet)); - if (requestImpl->m_dependencyOutputPath.getLength() == 0) - { - requestImpl->m_dependencyOutputPath = dependencyPath.value; - } - else + m_hlslToVulkanLayoutOptions->m_globalsBindingSet = Index(bindingSet); + m_hlslToVulkanLayoutOptions->m_globalsBinding = Index(binding); + break; + } + case OptionKind::Profile: SLANG_RETURN_ON_FAIL(_parseProfile(arg)); break; + case OptionKind::Capability: + { + // The `-capability` option is similar to `-profile` but does not set the actual profile + // for a target (it just adds capabilities). + // + // TODO: Once profiles are treated as capabilities themselves, it might be possible + // to treat `-profile` and `-capability` as aliases, although there might still be + // value in only allowing a single `-profile` option per target while still allowing + // zero or more `-capability` options. + + CommandLineArg operand; + SLANG_RETURN_ON_FAIL(m_reader.expectArg(operand)); + + List<UnownedStringSlice> slices; + StringUtil::split(operand.value.getUnownedSlice(), '+', slices); + Index sliceCount = slices.getCount(); + for (Index i = 0; i < sliceCount; ++i) + { + UnownedStringSlice atomName = slices[i]; + CapabilityAtom atom = findCapabilityAtom(atomName); + if (atom == CapabilityAtom::Invalid) { - sink->diagnose(dependencyPath.loc, Diagnostics::duplicateDependencyOutputPaths); + m_sink->diagnose(operand.loc, Diagnostics::unknownProfile, atomName); return SLANG_FAIL; } - break; - } - case OptionKind::MatrixLayoutRow: defaultMatrixLayoutMode = SlangMatrixLayoutMode(kMatrixLayoutMode_RowMajor); break; - case OptionKind::MatrixLayoutColumn: defaultMatrixLayoutMode = SlangMatrixLayoutMode(kMatrixLayoutMode_ColumnMajor); break; - case OptionKind::LineDirectiveMode: - { - CommandOptions::UserValue value; - SLANG_RETURN_ON_FAIL(_expectValue(ValueCategory::LineDirectiveMode, reader, sink, value)); - compileRequest->setLineDirectiveMode(SlangLineDirectiveMode(value)); - break; - } - case OptionKind::FloatingPointMode: - { - CommandOptions::UserValue value; - SLANG_RETURN_ON_FAIL(_expectValue(ValueCategory::FloatingPointMode, reader, sink, value)); - setFloatingPointMode(getCurrentTarget(), FloatingPointMode(value)); - break; + + addCapabilityAtom(getCurrentTarget(), atom); } - case OptionKind::GLSLForceScalarLayout: + break; + } + case OptionKind::Stage: + { + CommandLineArg name; + SLANG_RETURN_ON_FAIL(m_reader.expectArg(name)); + + Stage stage = findStageByName(name.value); + if (stage == Stage::Unknown) { - getCurrentTarget()->forceGLSLScalarLayout = true; - break; + m_sink->diagnose(name.loc, Diagnostics::unknownStage, name.value); + return SLANG_FAIL; } - case OptionKind::EnableEffectAnnotations: + else { - compileRequest->setEnableEffectAnnotations(true); - break; + setStage(getCurrentEntryPoint(), stage); } - case OptionKind::Optimization: - { - UnownedStringSlice levelSlice = argValue.getUnownedSlice().tail(2); - SlangOptimizationLevel level = SLANG_OPTIMIZATION_LEVEL_DEFAULT; + break; + } + case OptionKind::GLSLForceScalarLayout: + { + getCurrentTarget()->forceGLSLScalarLayout = true; + break; + } + case OptionKind::EnableEffectAnnotations: + { + m_compileRequest->setEnableEffectAnnotations(true); + break; + } + + case OptionKind::EntryPointName: + { + CommandLineArg name; + SLANG_RETURN_ON_FAIL(m_reader.expectArg(name)); - if (levelSlice.getLength()) - { - CommandOptions::UserValue value; - SLANG_RETURN_ON_FAIL(_getValue(ValueCategory::OptimizationLevel, arg, levelSlice, sink, value)); - level = SlangOptimizationLevel(value); - } + RawEntryPoint rawEntryPoint; + rawEntryPoint.name = name.value; + rawEntryPoint.translationUnitIndex = m_currentTranslationUnitIndex; - compileRequest->setOptimizationLevel(level); - break; - } - case OptionKind::DebugInformation: - { - auto name = argValue.getUnownedSlice().tail(2); + m_rawEntryPoints.add(rawEntryPoint); + break; + } + case OptionKind::Language: + { + CommandLineArg name; + SLANG_RETURN_ON_FAIL(m_reader.expectArg(name)); - // Note: unlike with `-O` above, we have to consider that other - // options might have names that start with `-g` and so cannot - // just detect it as a prefix. - if (name.getLength() == 0) - { - // The default is standard - compileRequest->setDebugInfoLevel(SLANG_DEBUG_INFO_LEVEL_STANDARD); - } - else - { - CommandOptions::UserValue value; - ValueCategory valueCat; - ValueCategory valueCats[] = { ValueCategory::DebugLevel, ValueCategory::DebugInfoFormat }; - SLANG_RETURN_ON_FAIL(_getValue(makeConstArrayView(valueCats), arg, name, sink, valueCat, value)); - - if (valueCat == ValueCategory::DebugLevel) - { - const auto level = (SlangDebugInfoLevel)value; - compileRequest->setDebugInfoLevel(level); - } - else - { - const auto debugFormat = (SlangDebugInfoFormat)value; - compileRequest->setDebugInfoFormat(debugFormat); - } - } - break; + const SourceLanguage sourceLanguage = (SourceLanguage)TypeTextUtil::findSourceLanguage(name.value.getUnownedSlice()); + + if (sourceLanguage == SourceLanguage::Unknown) + { + m_sink->diagnose(name.loc, Diagnostics::unknownSourceLanguage, name.value); + return SLANG_FAIL; } - case OptionKind::DefaultImageFormatUnknown: requestImpl->useUnknownImageFormatAsDefault = true; break; - case OptionKind::Obfuscate: requestImpl->getLinkage()->m_obfuscateCode = true; break; - case OptionKind::FileSystem: + else { - CommandOptions::UserValue value; - SLANG_RETURN_ON_FAIL(_expectValue(ValueCategory::FileSystemType, reader, sink, value)); - typedef TypeTextUtil::FileSystemType FileSystemType; - - switch (FileSystemType(value)) + while (m_reader.hasArg() && !m_reader.peekValue().startsWith("-")) { - case FileSystemType::Default: compileRequest->setFileSystem(nullptr); break; - case FileSystemType::LoadFile: compileRequest->setFileSystem(OSFileSystem::getLoadSingleton()); break; - case FileSystemType::Os: compileRequest->setFileSystem(OSFileSystem::getExtSingleton()); break; + SLANG_RETURN_ON_FAIL(addInputPath(m_reader.getValueAndAdvance().getBuffer(), sourceLanguage)); } - break; } - case OptionKind::ReferenceModule: + break; + } + case OptionKind::PassThrough: + { + CommandLineArg name; + SLANG_RETURN_ON_FAIL(m_reader.expectArg(name)); + + SlangPassThrough passThrough = SLANG_PASS_THROUGH_NONE; + if (SLANG_FAILED(TypeTextUtil::findPassThrough(name.value.getUnownedSlice(), passThrough))) { - CommandLineArg referenceModuleName; - SLANG_RETURN_ON_FAIL(reader.expectArg(referenceModuleName)); + m_sink->diagnose(name.loc, Diagnostics::unknownPassThroughTarget, name.value); + return SLANG_FAIL; + } - const auto path = referenceModuleName.value; + m_compileRequest->setPassThrough(passThrough); + break; + } + case OptionKind::MacroDefine: + { + // The value to be defined might be part of the same option, as in: + // -DFOO + // or it might come separately, as in: + // -D FOO - auto desc = ArtifactDescUtil::getDescFromPath(path.getUnownedSlice()); + UnownedStringSlice slice = argValue.getUnownedSlice().tail(2); - if (desc.kind == ArtifactKind::Unknown) - { - sink->diagnose(referenceModuleName.loc, Diagnostics::unknownLibraryKind, Path::getPathExt(path)); - return SLANG_FAIL; - } + CommandLineArg nextArg; + if (slice.getLength() <= 0) + { + SLANG_RETURN_ON_FAIL(m_reader.expectArg(nextArg)); + slice = nextArg.value.getUnownedSlice(); + } - // If it's a GPU binary, then we'll assume it's a library - if (ArtifactDescUtil::isGpuUsable(desc)) - { - desc.kind = ArtifactKind::Library; - } + // The string that sets up the define can have an `=` between + // the name to be defined and its value, so we search for one. + const Index equalIndex = slice.indexOf('='); - // If its a zip we'll *assume* its a zip holding compilation results - if (desc.kind == ArtifactKind::Zip) - { - desc.payload = ArtifactPayload::CompileResults; - } - - if (!ArtifactDescUtil::isLinkable(desc)) - { - sink->diagnose(referenceModuleName.loc, Diagnostics::kindNotLinkable, Path::getPathExt(path)); - return SLANG_FAIL; - } + // Now set the preprocessor define - const String name = ArtifactDescUtil::getBaseNameFromPath(desc, path.getUnownedSlice()); + if (equalIndex >= 0) + { + // If we found an `=`, we split the string... + m_compileRequest->addPreprocessorDefine(String(slice.head(equalIndex)).getBuffer(), String(slice.tail(equalIndex + 1)).getBuffer()); + } + else + { + // If there was no `=`, then just #define it to an empty string + m_compileRequest->addPreprocessorDefine(String(slice).getBuffer(), ""); + } + break; + } + case OptionKind::Include: + { + // The value to be defined might be part of the same option, as in: + // -IFOO + // or it might come separately, as in: + // -I FOO + // (see handling of `-D` above) + UnownedStringSlice slice = argValue.getUnownedSlice().tail(2); - // Create the artifact - auto artifact = Artifact::create(desc, name.getUnownedSlice()); + CommandLineArg nextArg; + if (slice.getLength() <= 0) + { + // Need to read another argument from the command line + SLANG_RETURN_ON_FAIL(m_reader.expectArg(nextArg)); + slice = nextArg.value.getUnownedSlice(); + } - // There is a problem here if I want to reference a library that is a 'system' library or is not directly a file - // In that case the path shouldn't be set and the name should completely define the library. - // Seeing as on all targets the baseName doesn't have an extension, and all library types do - // if the name doesn't have an extension we can assume there is no path to it. - - ComPtr<IOSFileArtifactRepresentation> fileRep; - if (Path::getPathExt(path).getLength() <= 0) - { - // If there is no extension *assume* it is the name of a system level library - fileRep = new OSFileArtifactRepresentation(IOSFileArtifactRepresentation::Kind::NameOnly, path.getUnownedSlice(), nullptr); - } - else - { - fileRep = new OSFileArtifactRepresentation(IOSFileArtifactRepresentation::Kind::Reference, path.getUnownedSlice(), nullptr); - if (!fileRep->exists()) - { - sink->diagnose(referenceModuleName.loc, Diagnostics::libraryDoesNotExist, path); - return SLANG_FAIL; - } - } - artifact->addRepresentation(fileRep); + m_compileRequest->addSearchPath(String(slice).getBuffer()); + break; + } + case OptionKind::Output: + { + // + // A `-o` option is used to specify a desired output file. + CommandLineArg outputPath; + SLANG_RETURN_ON_FAIL(m_reader.expectArg(outputPath)); - SLANG_RETURN_ON_FAIL(_addLibraryReference(requestImpl, artifact)); - break; + addOutputPath(outputPath.value.getBuffer()); + break; + } + case OptionKind::DepFile: + { + CommandLineArg dependencyPath; + SLANG_RETURN_ON_FAIL(m_reader.expectArg(dependencyPath)); + + if (m_requestImpl->m_dependencyOutputPath.getLength() == 0) + { + m_requestImpl->m_dependencyOutputPath = dependencyPath.value; } - case OptionKind::Version: + else { - sink->diagnoseRaw(Severity::Note, session->getBuildTagString()); - break; + m_sink->diagnose(dependencyPath.loc, Diagnostics::duplicateDependencyOutputPaths); + return SLANG_FAIL; } - case OptionKind::HelpStyle: + break; + } + case OptionKind::MatrixLayoutRow: m_defaultMatrixLayoutMode = SlangMatrixLayoutMode(kMatrixLayoutMode_RowMajor); break; + case OptionKind::MatrixLayoutColumn: m_defaultMatrixLayoutMode = SlangMatrixLayoutMode(kMatrixLayoutMode_ColumnMajor); break; + case OptionKind::LineDirectiveMode: + { + SlangLineDirectiveMode value; + SLANG_RETURN_ON_FAIL(_expectValue(value)); + m_compileRequest->setLineDirectiveMode(value); + break; + } + case OptionKind::FloatingPointMode: + { + FloatingPointMode value; + SLANG_RETURN_ON_FAIL(_expectValue(value)); + setFloatingPointMode(getCurrentTarget(), value); + break; + } + case OptionKind::Optimization: + { + UnownedStringSlice levelSlice = argValue.getUnownedSlice().tail(2); + SlangOptimizationLevel level = SLANG_OPTIMIZATION_LEVEL_DEFAULT; + + if (levelSlice.getLength()) { - CommandOptions::UserValue value; - SLANG_RETURN_ON_FAIL(_expectValue(ValueCategory::HelpStyle, reader, sink, value)); - helpStyle = CommandOptionsWriter::Style(value); - break; + SLANG_RETURN_ON_FAIL(_getValue(arg, levelSlice, level)); } - case OptionKind::Help: + + m_compileRequest->setOptimizationLevel(level); + break; + } + case OptionKind::DebugInformation: SLANG_RETURN_ON_FAIL(_parseDebugInformation(arg)); break; + case OptionKind::DefaultImageFormatUnknown: m_requestImpl->useUnknownImageFormatAsDefault = true; break; + case OptionKind::Obfuscate: m_requestImpl->getLinkage()->m_obfuscateCode = true; break; + case OptionKind::FileSystem: + { + typedef TypeTextUtil::FileSystemType FileSystemType; + FileSystemType value; + SLANG_RETURN_ON_FAIL(_expectValue(value)); + + switch (value) { - Index categoryIndex = -1; + case FileSystemType::Default: m_compileRequest->setFileSystem(nullptr); break; + case FileSystemType::LoadFile: m_compileRequest->setFileSystem(OSFileSystem::getLoadSingleton()); break; + case FileSystemType::Os: m_compileRequest->setFileSystem(OSFileSystem::getExtSingleton()); break; + } + break; + } + case OptionKind::ReferenceModule: SLANG_RETURN_ON_FAIL(_parseReferenceModule(arg)); break; + case OptionKind::Version: + { + m_sink->diagnoseRaw(Severity::Note, m_session->getBuildTagString()); + break; + } + case OptionKind::HelpStyle: SLANG_RETURN_ON_FAIL(_expectValue(m_helpStyle)); break; + case OptionKind::Help: + { + SLANG_RETURN_ON_FAIL(_parseHelp(arg)); - if (reader.hasArg()) - { - auto catArg = reader.getArgAndAdvance(); - - categoryIndex = options.findCategoryByCaseInsensitiveName(catArg.value.getUnownedSlice()); - if (categoryIndex < 0) - { - sink->diagnose(catArg.loc, Diagnostics::unknownHelpCategory); - return SLANG_FAIL; - } - } + // We retun an error so after this has successfully passed, we quit + return SLANG_FAIL; + } + case OptionKind::EmitSpirvDirectly: getCurrentTarget()->targetFlags |= SLANG_TARGET_FLAG_GENERATE_SPIRV_DIRECTLY; break; - CommandOptionsWriter::Options writerOptions; - writerOptions.style = helpStyle; + case OptionKind::DefaultDownstreamCompiler: + { + CommandLineArg sourceLanguageArg, compilerArg; + SLANG_RETURN_ON_FAIL(m_reader.expectArg(sourceLanguageArg)); + SLANG_RETURN_ON_FAIL(m_reader.expectArg(compilerArg)); - auto writer = CommandOptionsWriter::create(writerOptions); + SlangSourceLanguage sourceLanguage = TypeTextUtil::findSourceLanguage(sourceLanguageArg.value.getUnownedSlice()); + if (sourceLanguage == SLANG_SOURCE_LANGUAGE_UNKNOWN) + { + m_sink->diagnose(sourceLanguageArg.loc, Diagnostics::unknownSourceLanguage, sourceLanguageArg.value); + return SLANG_FAIL; + } - auto& buf = writer->getBuilder(); + SlangPassThrough compiler; + if (SLANG_FAILED(TypeTextUtil::findPassThrough(compilerArg.value.getUnownedSlice(), compiler))) + { + m_sink->diagnose(compilerArg.loc, Diagnostics::unknownPassThroughTarget, compilerArg.value); + return SLANG_FAIL; + } - if (categoryIndex < 0) - { - // If it's the text style we can inject usage at the top - if (helpStyle == CommandOptionsWriter::Style::Text) - { - _appendUsageTitle(buf); - } - else - { - // NOTE! We need this preamble because if we have links, - // we have to make sure the first thing in markdown *isn't* <> - - buf << "# Slang Command Line Options\n\n"; - buf << "*Usage:*\n"; - buf << "```\n"; - buf << "slangc [options...] [--] <input files>\n\n"; - buf << "# For help\n"; - buf << "slangc -h\n\n"; - buf << "# To generate this file\n"; - buf << "slangc -help-style markdown -h\n"; - buf << "```\n"; - } - - writer->appendDescription(&options); - } - else - { - writer->appendDescriptionForCategory(&options, categoryIndex); - } - - sink->diagnoseRaw(Severity::Note, buf.getBuffer()); - + if (SLANG_FAILED(m_session->setDefaultDownstreamCompiler(sourceLanguage, compiler))) + { + m_sink->diagnose(arg.loc, Diagnostics::unableToSetDefaultDownstreamCompiler, compilerArg.value, sourceLanguageArg.value); return SLANG_FAIL; } - case OptionKind::EmitSpirvDirectly: getCurrentTarget()->targetFlags |= SLANG_TARGET_FLAG_GENERATE_SPIRV_DIRECTLY; break; - - case OptionKind::DefaultDownstreamCompiler: + break; + } + case OptionKind::CompilerPath: + { + const Index index = argValue.lastIndexOf('-'); + if (index >= 0) { - CommandLineArg sourceLanguageArg, compilerArg; - SLANG_RETURN_ON_FAIL(reader.expectArg(sourceLanguageArg)); - SLANG_RETURN_ON_FAIL(reader.expectArg(compilerArg)); + CommandLineArg name; + SLANG_RETURN_ON_FAIL(m_reader.expectArg(name)); - SlangSourceLanguage sourceLanguage = TypeTextUtil::findSourceLanguage(sourceLanguageArg.value.getUnownedSlice()); - if (sourceLanguage == SLANG_SOURCE_LANGUAGE_UNKNOWN) - { - sink->diagnose(sourceLanguageArg.loc, Diagnostics::unknownSourceLanguage, sourceLanguageArg.value); - return SLANG_FAIL; - } + UnownedStringSlice passThroughSlice = argValue.getUnownedSlice().head(index).tail(1); - SlangPassThrough compiler; - if (SLANG_FAILED(TypeTextUtil::findPassThrough(compilerArg.value.getUnownedSlice(), compiler))) + // Skip the initial -, up to the last - + SlangPassThrough passThrough = SLANG_PASS_THROUGH_NONE; + if (SLANG_SUCCEEDED(TypeTextUtil::findPassThrough(passThroughSlice, passThrough))) { - sink->diagnose(compilerArg.loc, Diagnostics::unknownPassThroughTarget, compilerArg.value); - return SLANG_FAIL; + m_session->setDownstreamCompilerPath(passThrough, name.value.getBuffer()); + continue; } - - if (SLANG_FAILED(session->setDefaultDownstreamCompiler(sourceLanguage, compiler))) + else { - sink->diagnose(arg.loc, Diagnostics::unableToSetDefaultDownstreamCompiler, compilerArg.value, sourceLanguageArg.value); + m_sink->diagnose(arg.loc, Diagnostics::unknownDownstreamCompiler, passThroughSlice); return SLANG_FAIL; } - break; - } - case OptionKind::CompilerPath: - { - const Index index = argValue.lastIndexOf('-'); - if (index >= 0) - { - CommandLineArg name; - SLANG_RETURN_ON_FAIL(reader.expectArg(name)); - - UnownedStringSlice passThroughSlice = argValue.getUnownedSlice().head(index).tail(1); - - // Skip the initial -, up to the last - - SlangPassThrough passThrough = SLANG_PASS_THROUGH_NONE; - if (SLANG_SUCCEEDED(TypeTextUtil::findPassThrough(passThroughSlice, passThrough))) - { - session->setDownstreamCompilerPath(passThrough, name.value.getBuffer()); - continue; - } - else - { - sink->diagnose(arg.loc, Diagnostics::unknownDownstreamCompiler, passThroughSlice); - return SLANG_FAIL; - } - } - break; } - case OptionKind::InputFilesRemain: - { - // The `--` option causes us to stop trying to parse options, - // and treat the rest of the command line as input file names: - while (reader.hasArg()) - { - SLANG_RETURN_ON_FAIL(addInputPath(reader.getValueAndAdvance().getBuffer())); - } - break; - } - default: + break; + } + case OptionKind::InputFilesRemain: + { + // The `--` option causes us to stop trying to parse options, + // and treat the rest of the command line as input file names: + while (m_reader.hasArg()) { - // Hmmm, we looked up and produced a valid enum, but it wasn't handled in the switch... - sink->diagnose(arg.loc, Diagnostics::unknownCommandLineOption, argValue); - - _outputMinimalUsage(sink); - return SLANG_FAIL; + SLANG_RETURN_ON_FAIL(addInputPath(m_reader.getValueAndAdvance().getBuffer())); } + break; } - } + default: + { + // Hmmm, we looked up and produced a valid enum, but it wasn't handled in the switch... + m_sink->diagnose(arg.loc, Diagnostics::unknownCommandLineOption, argValue); - if (compileStdLib) - { - SLANG_RETURN_ON_FAIL(session->compileStdLib(compileStdLibFlags)); + _outputMinimalUsage(); + return SLANG_FAIL; + } } + } + + // If there are no layout settings, we don't need to carry this state + if (m_hlslToVulkanLayoutOptions->isDefault()) + { + m_hlslToVulkanLayoutOptions.setNull(); + } + + if (m_compileStdLib) + { + SLANG_RETURN_ON_FAIL(m_session->compileStdLib(m_compileStdLibFlags)); + } + + // TODO(JS): This is a restriction because of how setting of state works for load repro + // If a repro has been loaded, then many of the following options will overwrite + // what was set up. So for now they are ignored, and only parameters set as part + // of the loop work if they are after -load-repro + if (m_hasLoadedRepro) + { + return SLANG_OK; + } - // TODO(JS): This is a restriction because of how setting of state works for load repro - // If a repro has been loaded, then many of the following options will overwrite - // what was set up. So for now they are ignored, and only parameters set as part - // of the loop work if they are after -load-repro - if (hasLoadedRepro) + m_compileRequest->setCompileFlags(m_flags); + + // As a compatability feature, if the user didn't list any explicit entry + // point names, *and* they are compiling a single translation unit, *and* they + // have either specified a stage, or we can assume one from the naming + // of the translation unit, then we assume they wanted to compile a single + // entry point named `main`. + // + if (m_rawEntryPoints.getCount() == 0 + && m_rawTranslationUnits.getCount() == 1 + && (m_defaultEntryPoint.stage != Stage::Unknown + || m_rawTranslationUnits[0].impliedStage != Stage::Unknown)) + { + RawEntryPoint entry; + entry.name = "main"; + entry.translationUnitIndex = 0; + m_rawEntryPoints.add(entry); + } + + // If the user (manually or implicitly) specified only a single entry point, + // then we allow the associated stage to be specified either before or after + // the entry point. This means that if there is a stage attached + // to the "default" entry point, we should copy it over to the + // explicit one. + // + if (m_rawEntryPoints.getCount() == 1) + { + if (m_defaultEntryPoint.stage != Stage::Unknown) { - return SLANG_OK; + setStage(getCurrentEntryPoint(), m_defaultEntryPoint.stage); } - compileRequest->setCompileFlags(flags); - - // As a compatability feature, if the user didn't list any explicit entry - // point names, *and* they are compiling a single translation unit, *and* they - // have either specified a stage, or we can assume one from the naming - // of the translation unit, then we assume they wanted to compile a single - // entry point named `main`. - // - if(rawEntryPoints.getCount() == 0 - && rawTranslationUnits.getCount() == 1 - && (defaultEntryPoint.stage != Stage::Unknown - || rawTranslationUnits[0].impliedStage != Stage::Unknown)) - { - RawEntryPoint entry; - entry.name = "main"; - entry.translationUnitIndex = 0; - rawEntryPoints.add(entry); - } - - // If the user (manually or implicitly) specified only a single entry point, - // then we allow the associated stage to be specified either before or after - // the entry point. This means that if there is a stage attached - // to the "default" entry point, we should copy it over to the - // explicit one. + if (m_defaultEntryPoint.redundantStageSet) + getCurrentEntryPoint()->redundantStageSet = true; + if (m_defaultEntryPoint.conflictingStagesSet) + getCurrentEntryPoint()->conflictingStagesSet = true; + } + else + { + // If the "default" entry point has had a stage (or + // other state, if we add other per-entry-point state) + // specified, but there is more than one entry point, + // then that state doesn't apply to anything and we + // should issue an error to tell the user something + // funky is going on. // - if( rawEntryPoints.getCount() == 1 ) + if (m_defaultEntryPoint.stage != Stage::Unknown) { - if(defaultEntryPoint.stage != Stage::Unknown) + if (m_rawEntryPoints.getCount() == 0) { - setStage(getCurrentEntryPoint(), defaultEntryPoint.stage); + m_sink->diagnose(SourceLoc(), Diagnostics::stageSpecificationIgnoredBecauseNoEntryPoints); } - - if(defaultEntryPoint.redundantStageSet) - getCurrentEntryPoint()->redundantStageSet = true; - if(defaultEntryPoint.conflictingStagesSet) - getCurrentEntryPoint()->conflictingStagesSet = true; - } - else - { - // If the "default" entry point has had a stage (or - // other state, if we add other per-entry-point state) - // specified, but there is more than one entry point, - // then that state doesn't apply to anything and we - // should issue an error to tell the user something - // funky is going on. - // - if( defaultEntryPoint.stage != Stage::Unknown ) + else { - if( rawEntryPoints.getCount() == 0 ) - { - sink->diagnose(SourceLoc(), Diagnostics::stageSpecificationIgnoredBecauseNoEntryPoints); - } - else - { - sink->diagnose(SourceLoc(), Diagnostics::stageSpecificationIgnoredBecauseBeforeAllEntryPoints); - } + m_sink->diagnose(SourceLoc(), Diagnostics::stageSpecificationIgnoredBecauseBeforeAllEntryPoints); } } + } - // Slang requires that every explicit entry point indicate the translation - // unit it comes from. If there is only one translation unit specified, - // then implicitly all entry points come from it. - // - if(translationUnitCount == 1) + // 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 (m_translationUnitCount == 1) + { + for (auto& entryPoint : m_rawEntryPoints) { - for( auto& entryPoint : rawEntryPoints ) - { - entryPoint.translationUnitIndex = 0; - } + entryPoint.translationUnitIndex = 0; } - else + } + else + { + // Otherwise, we require that all entry points be specified after + // the translation unit to which tye belong. + bool anyEntryPointWithoutTranslationUnit = false; + for (auto& entryPoint : m_rawEntryPoints) { - // Otherwise, we require that all entry points be specified after - // the translation unit to which tye belong. - bool anyEntryPointWithoutTranslationUnit = false; - for( auto& entryPoint : rawEntryPoints ) - { - // Skip entry points that are already associated with a translation unit... - if( entryPoint.translationUnitIndex != -1 ) - continue; + // Skip entry points that are already associated with a translation unit... + if (entryPoint.translationUnitIndex != -1) + continue; - anyEntryPointWithoutTranslationUnit = true; - } - if( anyEntryPointWithoutTranslationUnit ) - { - sink->diagnose(SourceLoc(), Diagnostics::entryPointsNeedToBeAssociatedWithTranslationUnits); - return SLANG_FAIL; - } + anyEntryPointWithoutTranslationUnit = true; } - - // 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 ) + if (anyEntryPointWithoutTranslationUnit) { - // Skip entry points that already have a stage. - if(rawEntryPoint.stage != Stage::Unknown) - continue; + m_sink->diagnose(SourceLoc(), Diagnostics::entryPointsNeedToBeAssociatedWithTranslationUnits); + return SLANG_FAIL; + } + } - // Sanity check: don't process entry points with no associated translation unit. - if( rawEntryPoint.translationUnitIndex == -1 ) - continue; + // 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 : m_rawEntryPoints) + { + // Skip entry points that already have a stage. + if (rawEntryPoint.stage != Stage::Unknown) + continue; - auto impliedStage = rawTranslationUnits[rawEntryPoint.translationUnitIndex].impliedStage; - if(impliedStage != Stage::Unknown) - rawEntryPoint.stage = impliedStage; - } + // Sanity check: don't process entry points with no associated translation unit. + if (rawEntryPoint.translationUnitIndex == -1) + continue; - // 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. + auto impliedStage = m_rawTranslationUnits[rawEntryPoint.translationUnitIndex].impliedStage; + if (impliedStage != Stage::Unknown) + rawEntryPoint.stage = impliedStage; + } - // 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 ) + // 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 : m_rawEntryPoints) + { + if (rawEntryPoint.conflictingStagesSet) { - if( rawEntryPoint.conflictingStagesSet ) - { - sink->diagnose(SourceLoc(), Diagnostics::conflictingStagesForEntryPoint, rawEntryPoint.name); - } - else if( rawEntryPoint.redundantStageSet ) - { - sink->diagnose(SourceLoc(), Diagnostics::sameStageSpecifiedMoreThanOnce, rawEntryPoint.stage, rawEntryPoint.name); - } - else if( rawEntryPoint.translationUnitIndex != -1 ) + m_sink->diagnose(SourceLoc(), Diagnostics::conflictingStagesForEntryPoint, rawEntryPoint.name); + } + else if (rawEntryPoint.redundantStageSet) + { + m_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 = m_rawTranslationUnits[rawEntryPoint.translationUnitIndex]; + if (rawTranslationUnit.impliedStage != Stage::Unknown + && rawEntryPoint.stage != Stage::Unknown + && rawTranslationUnit.impliedStage != rawEntryPoint.stage) { - // As a quality-of-life feature, if the file name implies a particular - // stage, but the user manually specified something different for - // their entry point, give a warning in case they made a mistake. - - auto& rawTranslationUnit = rawTranslationUnits[rawEntryPoint.translationUnitIndex]; - if( rawTranslationUnit.impliedStage != Stage::Unknown - && rawEntryPoint.stage != Stage::Unknown - && rawTranslationUnit.impliedStage != rawEntryPoint.stage ) - { - sink->diagnose(SourceLoc(), Diagnostics::explicitStageDoesntMatchImpliedStage, rawEntryPoint.name, rawEntryPoint.stage, rawTranslationUnit.impliedStage); - } + m_sink->diagnose(SourceLoc(), Diagnostics::explicitStageDoesntMatchImpliedStage, rawEntryPoint.name, rawEntryPoint.stage, rawTranslationUnit.impliedStage); } } + } - // If the user is requesting code generation via pass-through, - // then any entry points they specify need to have a stage set, - // because fxc/dxc/glslang don't have a facility for taking - // a named entry point and pulling its stage from an attribute. - // - if(_passThroughRequiresStage(requestImpl->m_passThrough) ) + // 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 (_passThroughRequiresStage(m_requestImpl->m_passThrough)) + { + for (auto& rawEntryPoint : m_rawEntryPoints) { - for( auto& rawEntryPoint : rawEntryPoints ) + if (rawEntryPoint.stage == Stage::Unknown) { - if( rawEntryPoint.stage == Stage::Unknown ) - { - sink->diagnose(SourceLoc(), Diagnostics::noStageSpecifiedInPassThroughMode, rawEntryPoint.name); - } + m_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; + // We now have inferred enough information to add the + // entry points to our compile request. + // + for (auto& rawEntryPoint : m_rawEntryPoints) + { + if (rawEntryPoint.translationUnitIndex < 0) + continue; - auto translationUnitID = rawTranslationUnits[rawEntryPoint.translationUnitIndex].translationUnitID; + auto translationUnitID = m_rawTranslationUnits[rawEntryPoint.translationUnitIndex].translationUnitID; - int entryPointID = compileRequest->addEntryPoint( - translationUnitID, - rawEntryPoint.name.begin(), - SlangStage(rawEntryPoint.stage)); + int entryPointID = m_compileRequest->addEntryPoint( + translationUnitID, + rawEntryPoint.name.begin(), + SlangStage(rawEntryPoint.stage)); - rawEntryPoint.entryPointID = entryPointID; - } + rawEntryPoint.entryPointID = entryPointID; + } - // We are going to build a mapping from target formats to the - // target that handles that format. - Dictionary<CodeGenTarget, int> mapFormatToTargetIndex; + // We are going to build a mapping from target formats to the + // target that handles that format. + Dictionary<CodeGenTarget, int> mapFormatToTargetIndex; - // If there was no explicit `-target` specified, then we will look - // at the `-o` options to see what we can infer. - // - if(rawTargets.getCount() == 0) + // If there was no explicit `-target` specified, then we will look + // at the `-o` options to see what we can infer. + // + if (m_rawTargets.getCount() == 0) + { + // If there are no targets and no outputs + if (m_rawOutputs.getCount() == 0) { - // If there are no targets and no outputs - if (rawOutputs.getCount() == 0) + // And we have a container for output, then enable emitting SlangIR module + if (m_requestImpl->m_containerFormat != ContainerFormat::None) { - // And we have a container for output, then enable emitting SlangIR module - if (requestImpl->m_containerFormat != ContainerFormat::None) - { - requestImpl->m_emitIr = true; - } + m_requestImpl->m_emitIr = true; } - else + } + else + { + for (auto& rawOutput : m_rawOutputs) { - for(auto& rawOutput : rawOutputs) - { - // Some outputs don't imply a target format, and we shouldn't use those for inference. - auto impliedFormat = rawOutput.impliedFormat; - if( impliedFormat == CodeGenTarget::Unknown ) - continue; - - int targetIndex = 0; - if( !mapFormatToTargetIndex.tryGetValue(impliedFormat, targetIndex) ) - { - targetIndex = (int) rawTargets.getCount(); + // 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; - RawTarget rawTarget; - rawTarget.format = impliedFormat; - rawTargets.add(rawTarget); + int targetIndex = 0; + if (!mapFormatToTargetIndex.tryGetValue(impliedFormat, targetIndex)) + { + targetIndex = (int)m_rawTargets.getCount(); - mapFormatToTargetIndex[impliedFormat] = targetIndex; - } + RawTarget rawTarget; + rawTarget.format = impliedFormat; + m_rawTargets.add(rawTarget); - rawOutput.targetIndex = targetIndex; + mapFormatToTargetIndex[impliedFormat] = targetIndex; } + + rawOutput.targetIndex = targetIndex; } } - else + } + 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)m_rawTargets.getCount(); + for (int targetIndex = 0; targetIndex < targetCount; ++targetIndex) { - // If there were explicit targets, then we will use those, but still - // build up our mapping. We should object if the same target format - // is specified more than once (just because of the ambiguities - // it will create). - // - int targetCount = (int) rawTargets.getCount(); - for(int targetIndex = 0; targetIndex < targetCount; ++targetIndex) - { - auto format = rawTargets[targetIndex].format; + auto format = m_rawTargets[targetIndex].format; - if( mapFormatToTargetIndex.containsKey(format) ) - { - sink->diagnose(SourceLoc(), Diagnostics::duplicateTargets, format); - } - else - { - mapFormatToTargetIndex[format] = targetIndex; - } + if (mapFormatToTargetIndex.containsKey(format)) + { + m_sink->diagnose(SourceLoc(), Diagnostics::duplicateTargets, format); + } + else + { + mapFormatToTargetIndex[format] = targetIndex; } } + } - // If we weren't able to infer any targets from output paths (perhaps - // because there were no output paths), but there was a profile specified, - // then we can try to infer a target from the profile. + // If 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 (m_rawTargets.getCount() == 0 + && m_defaultTarget.profileVersion != ProfileVersion::Unknown + && !m_defaultTarget.conflictingProfilesSet) + { + // Let's see if the chosen profile allows us to infer + // the code gen target format that the user probably meant. // - if( rawTargets.getCount() == 0 - && defaultTarget.profileVersion != ProfileVersion::Unknown - && !defaultTarget.conflictingProfilesSet) + CodeGenTarget inferredFormat = CodeGenTarget::Unknown; + auto profileVersion = m_defaultTarget.profileVersion; + switch (Profile(profileVersion).getFamily()) { - // Let's see if the chosen profile allows us to infer - // the code gen target format that the user probably meant. - // - CodeGenTarget inferredFormat = CodeGenTarget::Unknown; - auto profileVersion = defaultTarget.profileVersion; - switch( Profile(profileVersion).getFamily() ) - { default: break; @@ -2415,7 +2595,7 @@ struct OptionsParser // cases should manually specify `-target dxil`. // case ProfileFamily::DX: - if( profileVersion >= ProfileVersion::DX_6_0 ) + if (profileVersion >= ProfileVersion::DX_6_0) { inferredFormat = CodeGenTarget::DXIL; } @@ -2424,225 +2604,227 @@ struct OptionsParser inferredFormat = CodeGenTarget::DXBytecode; } break; - } + } - if( inferredFormat != CodeGenTarget::Unknown ) - { - RawTarget rawTarget; - rawTarget.format = inferredFormat; - rawTargets.add(rawTarget); - } + if (inferredFormat != CodeGenTarget::Unknown) + { + RawTarget rawTarget; + rawTarget.format = inferredFormat; + m_rawTargets.add(rawTarget); } + } - // Similar to the case for entry points, if there is a single target, - // then we allow some of its options to come from the "default" - // target state. - if(rawTargets.getCount() == 1) + // 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 (m_rawTargets.getCount() == 1) + { + if (m_defaultTarget.profileVersion != ProfileVersion::Unknown) { - if(defaultTarget.profileVersion != ProfileVersion::Unknown) - { - setProfileVersion(getCurrentTarget(), defaultTarget.profileVersion); - } - for( auto atom : defaultTarget.capabilityAtoms ) - { - addCapabilityAtom(getCurrentTarget(), atom); - } + setProfileVersion(getCurrentTarget(), m_defaultTarget.profileVersion); + } + for (auto atom : m_defaultTarget.capabilityAtoms) + { + addCapabilityAtom(getCurrentTarget(), atom); + } - getCurrentTarget()->targetFlags |= defaultTarget.targetFlags; + getCurrentTarget()->targetFlags |= m_defaultTarget.targetFlags; - if( defaultTarget.floatingPointMode != FloatingPointMode::Default ) - { - setFloatingPointMode(getCurrentTarget(), defaultTarget.floatingPointMode); - } + if (m_defaultTarget.floatingPointMode != FloatingPointMode::Default) + { + setFloatingPointMode(getCurrentTarget(), m_defaultTarget.floatingPointMode); } - else + } + 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 (m_defaultTarget.profileVersion != ProfileVersion::Unknown) { - // If the "default" target has had a profile (or other state) - // specified, but there is != 1 taget, then that state doesn't - // apply to anythign and we should give the user an error. - // - if( defaultTarget.profileVersion != ProfileVersion::Unknown ) - { - if( rawTargets.getCount() == 0 ) - { - // This should only happen if there were multiple `-profile` options, - // so we didn't try to infer a target, or if the `-profile` option - // somehow didn't imply a target. - // - sink->diagnose(SourceLoc(), Diagnostics::profileSpecificationIgnoredBecauseNoTargets); - } - else - { - sink->diagnose(SourceLoc(), Diagnostics::profileSpecificationIgnoredBecauseBeforeAllTargets); - } - } - - if( defaultTarget.targetFlags ) + if (m_rawTargets.getCount() == 0) { - if( rawTargets.getCount() == 0 ) - { - sink->diagnose(SourceLoc(), Diagnostics::targetFlagsIgnoredBecauseNoTargets); - } - else - { - sink->diagnose(SourceLoc(), Diagnostics::targetFlagsIgnoredBecauseBeforeAllTargets); - } + // 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. + // + m_sink->diagnose(SourceLoc(), Diagnostics::profileSpecificationIgnoredBecauseNoTargets); } - - if( defaultTarget.floatingPointMode != FloatingPointMode::Default ) + else { - if( rawTargets.getCount() == 0 ) - { - sink->diagnose(SourceLoc(), Diagnostics::targetFlagsIgnoredBecauseNoTargets); - } - else - { - sink->diagnose(SourceLoc(), Diagnostics::targetFlagsIgnoredBecauseBeforeAllTargets); - } + m_sink->diagnose(SourceLoc(), Diagnostics::profileSpecificationIgnoredBecauseBeforeAllTargets); } - } - for(auto& rawTarget : rawTargets) + if (m_defaultTarget.targetFlags) { - if( rawTarget.conflictingProfilesSet ) + if (m_rawTargets.getCount() == 0) { - sink->diagnose(SourceLoc(), Diagnostics::conflictingProfilesSpecifiedForTarget, rawTarget.format); + m_sink->diagnose(SourceLoc(), Diagnostics::targetFlagsIgnoredBecauseNoTargets); } - else if( rawTarget.redundantProfileSet ) + else { - sink->diagnose(SourceLoc(), Diagnostics::sameProfileSpecifiedMoreThanOnce, rawTarget.profileVersion, rawTarget.format); + m_sink->diagnose(SourceLoc(), Diagnostics::targetFlagsIgnoredBecauseBeforeAllTargets); } } - // TODO: do we need to require that a target must have a profile specified, - // or will we continue to allow the profile to be inferred from the target? - - // We now have enough information to go ahead and declare the targets - // through the Slang API: - // - for(auto& rawTarget : rawTargets) + if (m_defaultTarget.floatingPointMode != FloatingPointMode::Default) { - int targetID = compileRequest->addCodeGenTarget(SlangCompileTarget(rawTarget.format)); - rawTarget.targetID = targetID; - - if( rawTarget.profileVersion != ProfileVersion::Unknown ) + if (m_rawTargets.getCount() == 0) { - compileRequest->setTargetProfile(targetID, SlangProfileID(Profile(rawTarget.profileVersion).raw)); + m_sink->diagnose(SourceLoc(), Diagnostics::targetFlagsIgnoredBecauseNoTargets); } - for( auto atom : rawTarget.capabilityAtoms ) + else { - requestImpl->addTargetCapability(targetID, SlangCapabilityID(atom)); + m_sink->diagnose(SourceLoc(), Diagnostics::targetFlagsIgnoredBecauseBeforeAllTargets); } + } - if( rawTarget.targetFlags ) - { - compileRequest->setTargetFlags(targetID, rawTarget.targetFlags); - } + } + for (auto& rawTarget : m_rawTargets) + { + if (rawTarget.conflictingProfilesSet) + { + m_sink->diagnose(SourceLoc(), Diagnostics::conflictingProfilesSpecifiedForTarget, rawTarget.format); + } + else if (rawTarget.redundantProfileSet) + { + m_sink->diagnose(SourceLoc(), Diagnostics::sameProfileSpecifiedMoreThanOnce, rawTarget.profileVersion, rawTarget.format); + } + } - if( rawTarget.floatingPointMode != FloatingPointMode::Default ) - { - compileRequest->setTargetFloatingPointMode(targetID, SlangFloatingPointMode(rawTarget.floatingPointMode)); - } - if (rawTarget.forceGLSLScalarLayout) - { - compileRequest->setTargetForceGLSLScalarBufferLayout(targetID, true); - } + // TODO: do we need to require that a target must have a profile specified, + // or will we continue to allow the profile to be inferred from the target? + + // We now have enough information to go ahead and declare the targets + // through the Slang API: + // + for (auto& rawTarget : m_rawTargets) + { + int targetID = m_compileRequest->addCodeGenTarget(SlangCompileTarget(rawTarget.format)); + rawTarget.targetID = targetID; + + m_requestImpl->setHLSLToVulkanLayoutOptions(targetID, m_hlslToVulkanLayoutOptions); + + if (rawTarget.profileVersion != ProfileVersion::Unknown) + { + m_compileRequest->setTargetProfile(targetID, SlangProfileID(Profile(rawTarget.profileVersion).raw)); + } + for (auto atom : rawTarget.capabilityAtoms) + { + m_requestImpl->addTargetCapability(targetID, SlangCapabilityID(atom)); } - if(defaultMatrixLayoutMode != SLANG_MATRIX_LAYOUT_MODE_UNKNOWN) + if (rawTarget.targetFlags) { - compileRequest->setMatrixLayoutMode(defaultMatrixLayoutMode); + m_compileRequest->setTargetFlags(targetID, rawTarget.targetFlags); } - // Next we need to sort out the output files specified with `-o`, and - // figure out which entry point and/or target they apply to. - // - // If there is only a single entry point, then that is automatically - // the entry point that should be associated with all outputs. - // - if( rawEntryPoints.getCount() == 1 ) + if (rawTarget.floatingPointMode != FloatingPointMode::Default) { - for( auto& rawOutput : rawOutputs ) - { - rawOutput.entryPointIndex = 0; - } + m_compileRequest->setTargetFloatingPointMode(targetID, SlangFloatingPointMode(rawTarget.floatingPointMode)); } - // - // Similarly, if there is only one target, then all outputs must - // implicitly appertain to that target. - // - if( rawTargets.getCount() == 1 ) + + if (rawTarget.forceGLSLScalarLayout) { - for( auto& rawOutput : rawOutputs ) - { - rawOutput.targetIndex = 0; - } + m_compileRequest->setTargetForceGLSLScalarBufferLayout(targetID, true); } + } - // If we don't have any raw outputs but do have a raw target, - // add an empty' rawOutput for certain targets where the expected behavior is obvious. - if (rawOutputs.getCount() == 0 && - rawTargets.getCount() == 1 && - (rawTargets[0].format == CodeGenTarget::HostCPPSource || - rawTargets[0].format == CodeGenTarget::PyTorchCppBinding || - rawTargets[0].format == CodeGenTarget::CUDASource || - ArtifactDescUtil::makeDescForCompileTarget(asExternal(rawTargets[0].format)).kind == ArtifactKind::HostCallable)) + if (m_defaultMatrixLayoutMode != SLANG_MATRIX_LAYOUT_MODE_UNKNOWN) + { + m_compileRequest->setMatrixLayoutMode(m_defaultMatrixLayoutMode); + } + + // Next we need to sort out the output files specified with `-o`, and + // figure out which entry point and/or target they apply to. + // + // If there is only a single entry point, then that is automatically + // the entry point that should be associated with all outputs. + // + if (m_rawEntryPoints.getCount() == 1) + { + for (auto& rawOutput : m_rawOutputs) + { + rawOutput.entryPointIndex = 0; + } + } + // + // Similarly, if there is only one target, then all outputs must + // implicitly appertain to that target. + // + if (m_rawTargets.getCount() == 1) + { + for (auto& rawOutput : m_rawOutputs) { - RawOutput rawOutput; - rawOutput.impliedFormat = rawTargets[0].format; rawOutput.targetIndex = 0; - rawOutputs.add(rawOutput); } + } - // Consider the output files specified via `-o` and try to figure - // out how to deal with them. - // - for(auto& rawOutput : rawOutputs) + // If we don't have any raw outputs but do have a raw target, + // add an empty' rawOutput for certain targets where the expected behavior is obvious. + if (m_rawOutputs.getCount() == 0 && + m_rawTargets.getCount() == 1 && + (m_rawTargets[0].format == CodeGenTarget::HostCPPSource || + m_rawTargets[0].format == CodeGenTarget::PyTorchCppBinding || + m_rawTargets[0].format == CodeGenTarget::CUDASource || + ArtifactDescUtil::makeDescForCompileTarget(asExternal(m_rawTargets[0].format)).kind == ArtifactKind::HostCallable)) + { + RawOutput rawOutput; + rawOutput.impliedFormat = m_rawTargets[0].format; + rawOutput.targetIndex = 0; + m_rawOutputs.add(rawOutput); + } + + // Consider the output files specified via `-o` and try to figure + // out how to deal with them. + // + for (auto& rawOutput : m_rawOutputs) + { + // For now, most output formats need to be tightly bound to + // both a target and an entry point. + + // If an output doesn't have a target associated with + // it, then search for the target with the matching format. + if (rawOutput.targetIndex == -1) { - // For now, most output formats need to be tightly bound to - // both a target and an entry point. + auto impliedFormat = rawOutput.impliedFormat; + int targetIndex = -1; - // If an output doesn't have a target associated with - // it, then search for the target with the matching format. - if( rawOutput.targetIndex == -1 ) + if (impliedFormat == CodeGenTarget::Unknown) { - 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); - } + // 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). + // + m_sink->diagnose(SourceLoc(), Diagnostics::cannotDeduceOutputFormatFromPath, rawOutput.path); + } + else if (mapFormatToTargetIndex.tryGetValue(rawOutput.impliedFormat, targetIndex)) + { + rawOutput.targetIndex = targetIndex; } + else + { + m_sink->diagnose(SourceLoc(), Diagnostics::cannotMatchOutputFileToTarget, rawOutput.path, rawOutput.impliedFormat); + } + } - // We won't do any searching to match an output file - // with an entry point, since the case of a single entry - // point was handled above, and the user is expected to - // follow the ordering rules when using multiple entry points. - if( rawOutput.entryPointIndex == -1 ) + // 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) + { + if (rawOutput.targetIndex != -1) { - if (rawOutput.targetIndex != -1 ) + auto outputFormat = m_rawTargets[rawOutput.targetIndex].format; + // Here we check whether the given output format supports multiple entry points + // When we add targets with support for multiple entry points, + // we should update this switch with those new formats + switch (outputFormat) { - auto outputFormat = rawTargets[rawOutput.targetIndex].format; - // Here we check whether the given output format supports multiple entry points - // When we add targets with support for multiple entry points, - // we should update this switch with those new formats - switch (outputFormat) - { case CodeGenTarget::CPPSource: case CodeGenTarget::PTX: case CodeGenTarget::CUDASource: @@ -2657,91 +2839,139 @@ struct OptionsParser rawOutput.isWholeProgram = true; break; default: - sink->diagnose(SourceLoc(), Diagnostics::cannotMatchOutputFileToEntryPoint, rawOutput.path); + m_sink->diagnose(SourceLoc(), Diagnostics::cannotMatchOutputFileToEntryPoint, rawOutput.path); break; - } } } } + } - // Now that we've diagnosed the output paths, we can add them - // to the compile request at the appropriate locations. - // - // We will consider the output files specified via `-o` and try to figure - // out how to deal with them. - // - for(auto& rawOutput : rawOutputs) + // Now that we've diagnosed the output paths, we can add them + // to the compile request at the appropriate locations. + // + // We will consider the output files specified via `-o` and try to figure + // out how to deal with them. + // + for (auto& rawOutput : m_rawOutputs) + { + if (rawOutput.targetIndex == -1) continue; + auto targetID = m_rawTargets[rawOutput.targetIndex].targetID; + auto target = m_requestImpl->getLinkage()->targets[targetID]; + RefPtr<EndToEndCompileRequest::TargetInfo> targetInfo; + if (!m_requestImpl->m_targetInfos.tryGetValue(target, targetInfo)) { - if(rawOutput.targetIndex == -1) continue; - auto targetID = rawTargets[rawOutput.targetIndex].targetID; - auto target = requestImpl->getLinkage()->targets[targetID]; - RefPtr<EndToEndCompileRequest::TargetInfo> targetInfo; - if( !requestImpl->m_targetInfos.tryGetValue(target, targetInfo) ) - { - targetInfo = new EndToEndCompileRequest::TargetInfo(); - requestImpl->m_targetInfos[target] = targetInfo; - } + targetInfo = new EndToEndCompileRequest::TargetInfo(); + m_requestImpl->m_targetInfos[target] = targetInfo; + } - if (rawOutput.isWholeProgram) + if (rawOutput.isWholeProgram) + { + if (targetInfo->wholeTargetOutputPath != "") { - if (targetInfo->wholeTargetOutputPath != "") - { - sink->diagnose(SourceLoc(), Diagnostics::duplicateOutputPathsForTarget, target->getTarget()); - } - else - { - target->addTargetFlags(SLANG_TARGET_FLAG_GENERATE_WHOLE_PROGRAM); - targetInfo->wholeTargetOutputPath = rawOutput.path; - } + m_sink->diagnose(SourceLoc(), Diagnostics::duplicateOutputPathsForTarget, target->getTarget()); } else { - if (rawOutput.entryPointIndex == -1) continue; + target->addTargetFlags(SLANG_TARGET_FLAG_GENERATE_WHOLE_PROGRAM); + targetInfo->wholeTargetOutputPath = rawOutput.path; + } + } + else + { + if (rawOutput.entryPointIndex == -1) continue; - Int entryPointID = rawEntryPoints[rawOutput.entryPointIndex].entryPointID; - auto entryPointReq = requestImpl->getFrontEndReq()->getEntryPointReqs()[entryPointID]; + Int entryPointID = m_rawEntryPoints[rawOutput.entryPointIndex].entryPointID; + auto entryPointReq = m_requestImpl->getFrontEndReq()->getEntryPointReqs()[entryPointID]; - //String outputPath; - if (targetInfo->entryPointOutputPaths.containsKey(entryPointID)) - { - sink->diagnose(SourceLoc(), Diagnostics::duplicateOutputPathsForEntryPointAndTarget, entryPointReq->getName(), target->getTarget()); - } - else - { - targetInfo->entryPointOutputPaths[entryPointID] = rawOutput.path; - } + //String outputPath; + if (targetInfo->entryPointOutputPaths.containsKey(entryPointID)) + { + m_sink->diagnose(SourceLoc(), Diagnostics::duplicateOutputPathsForEntryPointAndTarget, entryPointReq->getName(), target->getTarget()); + } + else + { + targetInfo->entryPointOutputPaths[entryPointID] = rawOutput.path; } } - - return (sink->getErrorCount() == 0) ? SLANG_OK : SLANG_FAIL; } -}; -SlangResult parseOptions( - SlangCompileRequest* inCompileRequest, - int argc, - char const* const* argv) + return (m_sink->getErrorCount() == 0) ? SLANG_OK : SLANG_FAIL; +} + +SlangResult OptionsParser::parse( + SlangCompileRequest* compileRequest, + int argc, + char const* const* argv) { - Slang::EndToEndCompileRequest* compileRequest = asInternal(inCompileRequest); + m_compileRequest = compileRequest; + + // Set up useful members + m_requestImpl = asInternal(compileRequest); - OptionsParser parser; - parser.compileRequest = inCompileRequest; - parser.requestImpl = compileRequest; - parser.session = asInternal(compileRequest->getSession()); + auto session = asInternal(m_requestImpl->getSession()); + + m_session = session; + m_frontEndReq = m_requestImpl->getFrontEndReq(); + + m_hlslToVulkanLayoutOptions = new HLSLToVulkanLayoutOptions; + + m_cmdOptions = &session->m_commandOptions; + + DiagnosticSink* requestSink = m_requestImpl->getSink(); + + m_cmdLineContext = m_requestImpl->getLinkage()->m_downstreamArgs.getContext(); - Result res = parser.parse(argc, argv); + // Why create a new DiagnosticSink? + // We *don't* want the lexer that comes as default (it's for Slang source!) + // We may want to set flags that are different + // We will need to use a new sourceManager that will just last for this parse and will map locs to + // source lines. + // + // The *problem* is that we still need to communicate to the requestSink in some suitable way. + // + // 1) We could have some kind of scoping mechanism (and only one sink) + // 2) We could have a 'parent' diagnostic sink, that if we set we route output too + // 3) We use something like the ISlangWriter to always be the thing output too (this has problems because + // some code assumes the diagnostics are accessible as a string) + // + // The solution used here is to have DiagnosticsSink have a 'parent' that also gets diagnostics reported to. + + m_parseSink.init(m_cmdLineContext->getSourceManager(), nullptr); + { + m_parseSink.setFlags(requestSink->getFlags()); + // Allow HumaneLoc - it won't display much for command line parsing - just (1): + // Leaving allows for diagnostics to be compatible with other Slang diagnostic parsing. + //parseSink.resetFlag(DiagnosticSink::Flag::HumaneLoc); + m_parseSink.setFlag(DiagnosticSink::Flag::SourceLocationLine); + } + + // All diagnostics will also be sent to requestSink + m_parseSink.setParentSink(requestSink); + m_sink = &m_parseSink; + + Result res = _parse(argc, argv); + + m_sink = nullptr; - DiagnosticSink* sink = compileRequest->getSink(); - if (sink->getErrorCount() > 0) + if (requestSink->getErrorCount() > 0) { // Put the errors in the diagnostic - compileRequest->m_diagnosticOutput = sink->outputBuffer.produceString(); + m_requestImpl->m_diagnosticOutput = requestSink->outputBuffer.produceString(); } return res; } +SlangResult parseOptions( + SlangCompileRequest* inCompileRequest, + int argc, + char const* const* argv) +{ + OptionsParser parser; + return parser.parse(inCompileRequest, argc, argv); +} + } // namespace Slang diff --git a/source/slang/slang-parameter-binding.cpp b/source/slang/slang-parameter-binding.cpp index 721d4204d..fbd11c7d0 100644 --- a/source/slang/slang-parameter-binding.cpp +++ b/source/slang/slang-parameter-binding.cpp @@ -332,6 +332,7 @@ struct EntryPointParameterBindingContext UsedRangeSet usedRangeSet; }; + // State that is shared during parameter binding, // across all translation units struct SharedParameterBindingContext @@ -476,7 +477,7 @@ LayoutResourceKind findRegisterClassFromName(UnownedStringSlice const& registerC break; case 5: - if( registerClassName == "space" ) + if( registerClassName == toSlice("space") ) { return LayoutResourceKind::RegisterSpace; } @@ -963,7 +964,7 @@ static void addExplicitParameterBindings_HLSL( } } -static void maybeDiagnoseMissingVulkanLayoutModifier( +static void _maybeDiagnoseMissingVulkanLayoutModifier( ParameterBindingContext* context, DeclRef<VarDeclBase> const& varDecl) { @@ -981,7 +982,6 @@ static void addExplicitParameterBindings_GLSL( RefPtr<ParameterInfo> parameterInfo, RefPtr<VarLayout> varLayout) { - // We only want to apply GLSL-style layout modifers // when compiling for a Khronos-related target. // @@ -1002,36 +1002,35 @@ static void addExplicitParameterBindings_GLSL( // TypeLayout::ResourceInfo* resInfo = nullptr; + TypeLayout::ResourceInfo* foundResInfo = nullptr; + LayoutSemanticInfo semanticInfo; semanticInfo.index = 0; semanticInfo.space = 0; - if( (resInfo = typeLayout->FindResourceInfo(LayoutResourceKind::DescriptorTableSlot)) != nullptr ) + + if( (foundResInfo = typeLayout->FindResourceInfo(LayoutResourceKind::DescriptorTableSlot)) != nullptr ) { // Try to find `binding` and `set` - auto attr = varDecl.getDecl()->findModifier<GLSLBindingAttribute>(); - if (!attr) + if (auto attr = varDecl.getDecl()->findModifier<GLSLBindingAttribute>()) { - maybeDiagnoseMissingVulkanLayoutModifier(context, varDecl); - return; + resInfo = foundResInfo; + semanticInfo.index = attr->binding; + semanticInfo.space = attr->set; } - semanticInfo.index = attr->binding; - semanticInfo.space = attr->set; } - else if( (resInfo = typeLayout->FindResourceInfo(LayoutResourceKind::RegisterSpace)) != nullptr ) + else if( (foundResInfo = typeLayout->FindResourceInfo(LayoutResourceKind::RegisterSpace)) != nullptr ) { // Try to find `set` - auto attr = varDecl.getDecl()->findModifier<GLSLBindingAttribute>(); - if (!attr) + if (auto attr = varDecl.getDecl()->findModifier<GLSLBindingAttribute>()) { - maybeDiagnoseMissingVulkanLayoutModifier(context, varDecl); - return; - } - if( attr->binding != 0) - { - getSink(context)->diagnose(attr, Diagnostics::wholeSpaceParameterRequiresZeroBinding, varDecl.getName(), attr->binding); + resInfo = foundResInfo; + if (attr->binding != 0) + { + getSink(context)->diagnose(attr, Diagnostics::wholeSpaceParameterRequiresZeroBinding, varDecl.getName(), attr->binding); + } + semanticInfo.index = attr->set; + semanticInfo.space = 0; } - semanticInfo.index = attr->set; - semanticInfo.space = 0; } else if( (resInfo = typeLayout->FindResourceInfo(LayoutResourceKind::SpecializationConstant)) != nullptr ) { @@ -1042,15 +1041,64 @@ static void addExplicitParameterBindings_GLSL( return; } - // If we didn't find any matches, then bail - if(!resInfo) + // if we found resInfo, we add the explicit binding + if (resInfo) + { + auto kind = resInfo->kind; + auto count = resInfo->count; + semanticInfo.kind = kind; + + addExplicitParameterBinding(context, parameterInfo, varDecl, semanticInfo, count); + return; + } + + // See if we can infer vulkan binding from HLSL if we have such options set + auto hlslToVulkanLayoutOptions = context->getTargetRequest()->getHLSLToVulkanLayoutOptions(); + + if (!hlslToVulkanLayoutOptions) + { + _maybeDiagnoseMissingVulkanLayoutModifier(context, varDecl); + return; + } + + // Do we have any vulkan shift settings + auto hlslRegSemantic = varDecl.getDecl()->findModifier<HLSLRegisterSemantic>(); + + if (hlslRegSemantic == nullptr) + { + // We don't have a HLSL binding, so we can't infer, so we can't assign an infered explicit binding return; + } + + // Get the HLSL binding info + const auto hlslInfo = ExtractLayoutSemanticInfo(context, hlslRegSemantic); + if (hlslInfo.kind != LayoutResourceKind::None) + { + // We need to map to the GLSL binding types + HLSLToVulkanLayoutOptions::Kind vulkanKind = HLSLToVulkanLayoutOptions::getKind(hlslInfo.kind); + if (vulkanKind != HLSLToVulkanLayoutOptions::Kind::Invalid) + { + const auto shift = hlslToVulkanLayoutOptions->getShift(vulkanKind, Index(hlslInfo.space)); + if (shift != HLSLToVulkanLayoutOptions::kInvalidShift) + { + const Index bindingIndex = Index(hlslInfo.index) + shift; - auto kind = resInfo->kind; - auto count = resInfo->count; - semanticInfo.kind = kind; + if (bindingIndex >= 0) + { + // Add for descriptor slot + resInfo = typeLayout->findOrAddResourceInfo(LayoutResourceKind::DescriptorTableSlot); + + semanticInfo.kind = resInfo->kind; + semanticInfo.index = UInt(bindingIndex); + semanticInfo.space = hlslInfo.space; + + const LayoutSize count = resInfo->count; - addExplicitParameterBinding(context, parameterInfo, varDecl, semanticInfo, count); + addExplicitParameterBinding(context, parameterInfo, varDecl, semanticInfo, count); + } + } + } + } } // Given a single parameter, collect whatever information we have on @@ -2281,7 +2329,7 @@ struct ScopeLayoutBuilder } - RefPtr<VarLayout> endLayout() + RefPtr<VarLayout> endLayout(VarLayout* inVarLayout = nullptr) { // Finish computing the layout for the ordindary data (if any). // @@ -2307,7 +2355,12 @@ struct ScopeLayoutBuilder // We now have a bunch of layout information, which we should // record into a suitable object that represents the scope - RefPtr<VarLayout> scopeVarLayout = new VarLayout(); + RefPtr<VarLayout> scopeVarLayout = inVarLayout; + if (!scopeVarLayout) + { + scopeVarLayout = new VarLayout(); + } + scopeVarLayout->typeLayout = scopeTypeLayout; if( auto pendingTypeLayout = scopeTypeLayout->pendingDataTypeLayout ) @@ -2445,19 +2498,15 @@ struct SimpleScopeLayoutBuilder : ScopeLayoutBuilder /// the resources required for a constant buffer in the appropriate /// target-specific fashion. /// -static ParameterBindingAndKindInfo maybeAllocateConstantBufferBinding( - ParameterBindingContext* context, - bool needConstantBuffer) +static ParameterBindingAndKindInfo _allocateConstantBufferBinding( + ParameterBindingContext* context) { - if( !needConstantBuffer ) return ParameterBindingAndKindInfo(); - UInt space = context->shared->defaultSpace; auto usedRangeSet = findUsedRangeSetForSpace(context, space); auto layoutInfo = context->getRulesFamily() ->getConstantBufferRules(context->getTargetRequest()) - ->GetObjectLayout( - ShaderParameterKind::ConstantBuffer); + ->GetObjectLayout(ShaderParameterKind::ConstantBuffer); ParameterBindingAndKindInfo info; info.kind = layoutInfo.kind; @@ -2467,6 +2516,32 @@ static ParameterBindingAndKindInfo maybeAllocateConstantBufferBinding( return info; } +static ParameterBindingAndKindInfo _assignConstantBufferBinding( + ParameterBindingContext* context, + VarLayout* varLayout, + UInt space, + UInt index) +{ + auto usedRangeSet = findUsedRangeSetForSpace(context, space); + + auto layoutInfo = context->getRulesFamily() + ->getConstantBufferRules(context->getTargetRequest()) + ->GetObjectLayout(ShaderParameterKind::ConstantBuffer); + + const Index count = Index(layoutInfo.size.getFiniteValue()); + + auto existingParam = usedRangeSet->usedResourceRanges[(int)layoutInfo.kind].Add(varLayout, index, index + count); + SLANG_UNUSED(existingParam); + SLANG_ASSERT(existingParam == nullptr); + + ParameterBindingAndKindInfo info; + info.kind = layoutInfo.kind; + info.count = count; + info.index = index; + info.space = space; + return info; +} + /// Remove resource usage from `typeLayout` that should only be stored per-entry-point. /// /// This is used when constructing the overall layout for an entry point, to make sure @@ -3374,6 +3449,92 @@ static void _completeBindings( _completeBindings(context, program, &counters); } +static bool _calcNeedsDefaultSpace(SharedParameterBindingContext& sharedContext) +{ + // Next we will look at the global-scope parameters and see if + // any of them requires a `register` or `binding` that will + // thus need to land in a default space. + // + for (auto& parameterInfo : sharedContext.parameters) + { + auto varLayout = parameterInfo->varLayout; + SLANG_RELEASE_ASSERT(varLayout); + + // For each parameter, we will look at each resource it consumes. + // + for (auto resInfo : varLayout->typeLayout->resourceInfos) + { + // We don't want to consider resource kinds for which + // the variable already has an (explicit) binding, since + // the space from the explicit binding will be used, so + // that a default space isn't needed. + // + if (parameterInfo->bindingInfo[resInfo.kind].count != 0) + continue; + + // We also want to exclude certain resource kinds from + // consideration, since parameters using those resource + // kinds wouldn't be allocated into the default space + // anyway. + // + switch (resInfo.kind) + { + case LayoutResourceKind::RegisterSpace: + case LayoutResourceKind::PushConstantBuffer: + continue; + case LayoutResourceKind::Uniform: + { + // If it's uniform, but we have globals binding defined, we don't need a default space for it + // as it will go in the global binding specified + if (auto hlslToVulkanOptions = sharedContext.getTargetRequest()->getHLSLToVulkanLayoutOptions()) + { + if (hlslToVulkanOptions->hasGlobalsBinding()) + { + continue; + } + } + break; + } + + default: + break; + } + + // Otherwise, we have a shader parameter that will need + // a default space or set to live in. + // + return true; + } + } + + // We also need a default space for any entry-point parameters + // that consume appropriate resource kinds. + // + for (auto& entryPoint : sharedContext.programLayout->entryPoints) + { + auto paramsLayout = entryPoint->parametersLayout; + for (auto resInfo : paramsLayout->resourceInfos) + { + switch (resInfo.kind) + { + default: + break; + + case LayoutResourceKind::RegisterSpace: + case LayoutResourceKind::VaryingInput: + case LayoutResourceKind::VaryingOutput: + case LayoutResourceKind::HitAttributes: + case LayoutResourceKind::RayPayload: + continue; + } + + return true; + } + } + + return false; +} + RefPtr<ProgramLayout> generateParameterBindings( TargetProgram* targetProgram, DiagnosticSink* sink) @@ -3536,6 +3697,31 @@ RefPtr<ProgramLayout> generateParameterBindings( } } + // Global constant buffer binding. + // It's initially invalid. (kind = None). + ParameterBindingAndKindInfo globalConstantBufferBinding; + + // We define this variable early, such that we can create and use when specifying + // a HLSLToVulkan based binding. It can if setup be used on endLayout, called later + RefPtr<VarLayout> globalScopeVarLayout; + + // If we have a space/binding assigned for use for globals in Vulkan, + // we can't use *that* as the default space, so we allocate if + if (auto vulkanOptions = targetReq->getHLSLToVulkanLayoutOptions()) + { + if (vulkanOptions->hasGlobalsBinding()) + { + // Create VarLayout which will be associated with the binding, and setup later + globalScopeVarLayout = new VarLayout; + + // Allocate the set + markSpaceUsed(&context, nullptr, vulkanOptions->m_globalsBindingSet); + + // Mark the use of this binding + globalConstantBufferBinding = _assignConstantBufferBinding(&context, globalScopeVarLayout, vulkanOptions->m_globalsBindingSet, vulkanOptions->m_globalsBinding); + } + } + // Once we have a canonical list of all the parameters, we can // detect if there are any global-scope parameters that make use // of `LayoutResourceKind::Uniform`, since such parameters would @@ -3550,10 +3736,14 @@ RefPtr<ProgramLayout> generateParameterBindings( // bool needDefaultConstantBuffer = false; + // If we have already setup a global constant buffer binding, we don't need a default one + // // On a CPU target, it's okay to have global scope parameters that use Uniform resources (because on CPU // all resources are 'Uniform') // TODO(JS): We'll just assume the same with CUDA target for now.. - if (!_isCPUTarget(targetReq->getTarget()) && !_isPTXTarget(targetReq->getTarget())) + if (globalConstantBufferBinding.kind == LayoutResourceKind::None && + !_isCPUTarget(targetReq->getTarget()) && + !_isPTXTarget(targetReq->getTarget())) { for( auto& parameterInfo : sharedContext.parameters ) { @@ -3577,81 +3767,9 @@ RefPtr<ProgramLayout> generateParameterBindings( // As a starting point, we will definitely need a "default" space if // we are creating a default constant buffer, since it should get // a binding in that "default" space. - // - bool needDefaultSpace = needDefaultConstantBuffer; - if (!needDefaultSpace) - { - // Next we will look at the global-scope parameters and see if - // any of them requires a `register` or `binding` that will - // thus need to land in a default space. - // - for (auto& parameterInfo : sharedContext.parameters) - { - auto varLayout = parameterInfo->varLayout; - SLANG_RELEASE_ASSERT(varLayout); - - // For each parameter, we will look at each resource it consumes. - // - for (auto resInfo : varLayout->typeLayout->resourceInfos) - { - // We don't want to consider resource kinds for which - // the variable already has an (explicit) binding, since - // the space from the explicit binding will be used, so - // that a default space isn't needed. - // - if( parameterInfo->bindingInfo[resInfo.kind].count != 0 ) - continue; - - // We also want to exclude certain resource kinds from - // consideration, since parameters using those resource - // kinds wouldn't be allocated into the default space - // anyway. - // - switch( resInfo.kind ) - { - case LayoutResourceKind::RegisterSpace: - case LayoutResourceKind::PushConstantBuffer: - continue; - - default: - break; - } - - // Otherwise, we have a shader parameter that will need - // a default space or set to live in. - // - needDefaultSpace = true; - break; - } - } - - // We also need a default space for any entry-point parameters - // that consume appropriate resource kinds. - // - for(auto& entryPoint : sharedContext.programLayout->entryPoints) - { - auto paramsLayout = entryPoint->parametersLayout; - for(auto resInfo : paramsLayout->resourceInfos ) - { - switch(resInfo.kind) - { - default: - break; - - case LayoutResourceKind::RegisterSpace: - case LayoutResourceKind::VaryingInput: - case LayoutResourceKind::VaryingOutput: - case LayoutResourceKind::HitAttributes: - case LayoutResourceKind::RayPayload: - continue; - } - - needDefaultSpace = true; - break; - } - } - } - + + const bool needDefaultSpace = needDefaultConstantBuffer || _calcNeedsDefaultSpace(sharedContext); + // If we need a space for default bindings, then allocate it here. if (needDefaultSpace) { @@ -3694,12 +3812,13 @@ RefPtr<ProgramLayout> generateParameterBindings( } // If there are any global-scope uniforms, then we need to - // allocate a constant-buffer binding for them here. - // - ParameterBindingAndKindInfo globalConstantBufferBinding = maybeAllocateConstantBufferBinding( - &context, - needDefaultConstantBuffer); - + // allocate a constant-buffer binding for them here, if hasn't already been + // assigned + if (globalConstantBufferBinding.kind == LayoutResourceKind::None && needDefaultConstantBuffer) + { + globalConstantBufferBinding = _allocateConstantBufferBinding(&context); + } + // Now that all of the explicit bindings have been dealt with // and we've also allocate any space/buffer that is required // for global-scope parameters, we will go through the @@ -3721,10 +3840,12 @@ RefPtr<ProgramLayout> generateParameterBindings( globalScopeLayoutBuilder.addParameter(parameterInfo); } - auto globalScopeVarLayout = globalScopeLayoutBuilder.endLayout(); + globalScopeVarLayout = globalScopeLayoutBuilder.endLayout(globalScopeVarLayout); + if( globalConstantBufferBinding.count != 0 ) { auto cbInfo = globalScopeVarLayout->findOrAddResourceInfo(globalConstantBufferBinding.kind); + cbInfo->space = globalConstantBufferBinding.space; cbInfo->index = globalConstantBufferBinding.index; } diff --git a/source/slang/slang.cpp b/source/slang/slang.cpp index ec5a7084e..a1c8f9b03 100644 --- a/source/slang/slang.cpp +++ b/source/slang/slang.cpp @@ -61,6 +61,7 @@ extern Slang::String get_slang_hlsl_prelude(); namespace Slang { + /* static */const BaseTypeInfo BaseTypeInfo::s_info[Index(BaseType::CountOf)] = { { 0, 0, uint8_t(BaseType::Void) }, @@ -1444,6 +1445,14 @@ MatrixLayoutMode TargetRequest::getDefaultMatrixLayoutMode() return linkage->getDefaultMatrixLayoutMode(); } +void TargetRequest::setHLSLToVulkanLayoutOptions(HLSLToVulkanLayoutOptions* opts) +{ + if (isKhronosTarget(this)) + { + hlslToVulkanLayoutOptions = opts; + } +} + void TargetRequest::addCapability(CapabilityAtom capability) { rawCapabilities.add(capability); @@ -4678,6 +4687,11 @@ void EndToEndCompileRequest::setTargetProfile(int targetIndex, SlangProfileID pr getLinkage()->targets[targetIndex]->setTargetProfile(Profile(profile)); } +void EndToEndCompileRequest::setHLSLToVulkanLayoutOptions(int targetIndex, HLSLToVulkanLayoutOptions* vulkanShiftOptions) +{ + getLinkage()->targets[targetIndex]->setHLSLToVulkanLayoutOptions(vulkanShiftOptions); +} + void EndToEndCompileRequest::setTargetFlags(int targetIndex, SlangTargetFlags flags) { getLinkage()->targets[targetIndex]->addTargetFlags(flags); |
