https://github.com/shader-slang/slang
Raw File
Tip revision: d06a78d935b2743494d47ed5cd3f36e38ac9c5ac authored by Yong He on 04 February 2022, 03:17:30 UTC
Add gfx interop to allow more direct D3D12 usage scenarios. (#2117)
Tip revision: d06a78d
slang-options.cpp
// slang-options.cpp

// Implementation of options parsing for `slangc` command line,
// and also for API interface that takes command-line argument strings.

#include "slang-options.h"

#include "../../slang.h"

#include "slang-compiler.h"
#include "slang-profile.h"

#include "slang-repro.h"
#include "slang-serialize-ir.h"

#include "../core/slang-file-system.h"
#include "../core/slang-type-text-util.h"
#include "../core/slang-hex-dump-util.h"

#include "../compiler-core/slang-command-line-args.h"

#include <assert.h>

namespace Slang {

SlangResult _addLibraryReference(EndToEndCompileRequest* req, Stream* stream);

struct OptionsParser
{
    SlangSession*           session = nullptr;
    SlangCompileRequest*    compileRequest = nullptr;

    Slang::EndToEndCompileRequest*  requestImpl = nullptr;

    // A "translation unit" represents one or more source files
    // that are processed as a single entity when it comes to
    // semantic checking.
    //
    // For languages like HLSL, GLSL, and C, a translation unit
    // is usually a single source file (which can then go on
    // to `#include` other files into the same translation unit).
    //
    // For Slang, we support having multiple source files in
    // a single translation unit, and indeed command-line `slangc`
    // will always put all the source files into a single translation
    // unit.
    //
    // We track information on the translation units that we
    // create during options parsing, so that we can assocaite
    // other entities with these translation units:
    //
    struct RawTranslationUnit
    {
        // What language is the translation unit using?
        //
        // Note: We do not support translation units that mix
        // languages.
        //
        SlangSourceLanguage sourceLanguage;

        // Certain naming conventions imply a stage for
        // a file with only a single entry point, and in
        // those cases we will try to infer the stage from
        // the file when it is possible.
        //
        Stage impliedStage;

        // We retain the Slang API level translation unit index,
        // which we will call an "ID" inside the options parsing code.
        //
        // This will almost always be the index into the
        // `rawTranslationUnits` array below, but could conceivably,
        // be mismatched if we were parsing options for a compile
        // request that already had some translation unit(s) added
        // manually.
        //
        int                 translationUnitID;
    };
    List<RawTranslationUnit> rawTranslationUnits;

    // 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
    // `-stage` command line option, or a `[shader("...")]` attribute
    // in the source code.
    //
    struct RawEntryPoint
    {
        String  name;
        Stage   stage = Stage::Unknown;
        int     translationUnitIndex = -1;
        int     entryPointID = -1;

        // State for tracking command-line errors
        bool conflictingStagesSet = false;
        bool redundantStageSet = false;
    };
    //
    // We collect the entry points in a "raw" array so that we can
    // possibly associate them with a stage or translation unit
    // after the fact.
    //
    List<RawEntryPoint> rawEntryPoints;

    // 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;
        CodeGenTarget   impliedFormat = CodeGenTarget::Unknown;
        int             targetIndex = -1;
        int             entryPointIndex = -1;
        bool            isWholeProgram = false;
    };
    List<RawOutput> rawOutputs;

    struct RawTarget
    {
        CodeGenTarget       format = CodeGenTarget::Unknown;
        ProfileVersion      profileVersion = ProfileVersion::Unknown;
        SlangTargetFlags    targetFlags = 0;
        int                 targetID = -1;
        FloatingPointMode   floatingPointMode = FloatingPointMode::Default;

        List<CapabilityAtom> capabilityAtoms;

        // State for tracking command-line errors
        bool conflictingProfilesSet = false;
        bool redundantProfileSet = false;

    };
    List<RawTarget> rawTargets;

    RawTarget defaultTarget;

    int addTranslationUnit(
        SlangSourceLanguage language,
        Stage               impliedStage)
    {
        auto translationUnitIndex = rawTranslationUnits.getCount();
        auto translationUnitID = 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;

        rawTranslationUnits.add(rawTranslationUnit);

        return int(translationUnitIndex);
    }

    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);
        }

        compileRequest->addTranslationUnitSourceFile(rawTranslationUnits[slangTranslationUnitIndex].translationUnitID, path.begin());

        // Set the translation unit to be used by subsequent entry points
        currentTranslationUnitIndex = slangTranslationUnitIndex;
    }

    void addInputForeignShaderPath(
        String const&           path,
        SlangSourceLanguage     language,
        Stage                   impliedStage)
    {
        translationUnitCount++;
        currentTranslationUnitIndex = addTranslationUnit(language, impliedStage);

        compileRequest->addTranslationUnitSourceFile(rawTranslationUnits[currentTranslationUnitIndex].translationUnitID, path.begin());
    }

    static Profile::RawVal 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 (int i = 0; i < SLANG_COUNT_OF(entries); ++i)
        {
            const Entry& entry = entries[i];
            if (path.endsWith(entry.ext))
            {
                return entry.profileId;
            }
        }
        return Profile::Unknown;
    }

    static SlangSourceLanguage findSourceLanguageFromPath(const String& path, Stage& outImpliedStage)
    {
        struct Entry
        {
            const char*         ext;
            SlangSourceLanguage sourceLanguage;
            SlangStage          impliedStage;
        };

        static const Entry entries[] = 
        {
            { ".slang", SLANG_SOURCE_LANGUAGE_SLANG, SLANG_STAGE_NONE },

            { ".hlsl",  SLANG_SOURCE_LANGUAGE_HLSL, SLANG_STAGE_NONE },
            { ".fx",    SLANG_SOURCE_LANGUAGE_HLSL, SLANG_STAGE_NONE },

            { ".glsl", SLANG_SOURCE_LANGUAGE_GLSL,  SLANG_STAGE_NONE },
            { ".vert", SLANG_SOURCE_LANGUAGE_GLSL,  SLANG_STAGE_VERTEX },
            { ".frag", SLANG_SOURCE_LANGUAGE_GLSL,  SLANG_STAGE_FRAGMENT },
            { ".geom", SLANG_SOURCE_LANGUAGE_GLSL,  SLANG_STAGE_GEOMETRY },
            { ".tesc", SLANG_SOURCE_LANGUAGE_GLSL,  SLANG_STAGE_HULL },
            { ".tese", SLANG_SOURCE_LANGUAGE_GLSL,  SLANG_STAGE_DOMAIN },
            { ".comp", SLANG_SOURCE_LANGUAGE_GLSL,  SLANG_STAGE_COMPUTE },

            { ".c",    SLANG_SOURCE_LANGUAGE_C,     SLANG_STAGE_NONE },
            { ".cpp",  SLANG_SOURCE_LANGUAGE_CPP,   SLANG_STAGE_NONE },
            { ".cu",   SLANG_SOURCE_LANGUAGE_CUDA,  SLANG_STAGE_NONE }

        };

        for (int i = 0; i < SLANG_COUNT_OF(entries); ++i)
        {
            const Entry& entry = entries[i];
            if (path.endsWith(entry.ext))
            {
                outImpliedStage = Stage(entry.impliedStage);
                return entry.sourceLanguage;
            }
        }
        return SLANG_SOURCE_LANGUAGE_UNKNOWN;
    }

    SlangResult addInputPath(
        char const*  inPath,
        SourceLanguage langOverride = SourceLanguage::Unknown)
    {
        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;
        }

        Stage impliedStage = Stage::Unknown;
        SlangSourceLanguage sourceLanguage = langOverride == SourceLanguage::Unknown ? findSourceLanguageFromPath(path, impliedStage) : SlangSourceLanguage(langOverride);

        if (sourceLanguage == SLANG_SOURCE_LANGUAGE_UNKNOWN)
        {
            requestImpl->getSink()->diagnose(SourceLoc(), Diagnostics::cannotDeduceSourceLanguage, inPath);
            return SLANG_FAIL;
        }

        addInputForeignShaderPath(path, sourceLanguage, impliedStage);

        return SLANG_OK;
    }

    void addOutputPath(
        String const&   path,
        CodeGenTarget   impliedFormat)
    {
        RawOutput rawOutput;
        rawOutput.path = path;
        rawOutput.impliedFormat = impliedFormat;
        rawOutputs.add(rawOutput);
    }

    void addOutputPath(char const* inPath)
    {
        String path = String(inPath);
        String ext = Path::getPathExt(path);

        if (ext == "slang-module" || ext == "slang-lib")
        {
            compileRequest->setOutputContainerFormat(SLANG_CONTAINER_FORMAT_SLANG_MODULE);
            requestImpl->m_containerOutputPath = path;
        }
        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));
        }
    }

    RawEntryPoint* getCurrentEntryPoint()
    {
        auto rawEntryPointCount = rawEntryPoints.getCount();
        return rawEntryPointCount ? &rawEntryPoints[rawEntryPointCount-1] : &defaultEntryPoint;
    }

    void setStage(RawEntryPoint* rawEntryPoint, Stage stage)
    {
        if(rawEntryPoint->stage != Stage::Unknown)
        {
            rawEntryPoint->redundantStageSet = true;
            if( stage != rawEntryPoint->stage )
            {
                rawEntryPoint->conflictingStagesSet = true;
            }
        }
        rawEntryPoint->stage = stage;
    }

    RawTarget* getCurrentTarget()
    {
        auto rawTargetCount = rawTargets.getCount();
        return rawTargetCount ? &rawTargets[rawTargetCount-1] : &defaultTarget;
    }

    void setProfileVersion(RawTarget* rawTarget, ProfileVersion profileVersion)
    {
        if(rawTarget->profileVersion != ProfileVersion::Unknown)
        {
            rawTarget->redundantProfileSet = true;

            if(profileVersion != rawTarget->profileVersion)
            {
                rawTarget->conflictingProfilesSet = true;
            }
        }
        rawTarget->profileVersion = profileVersion;
    }

    void addCapabilityAtom(RawTarget* rawTarget, CapabilityAtom atom)
    {
        rawTarget->capabilityAtoms.add(atom);
    }

    void setFloatingPointMode(RawTarget* rawTarget, FloatingPointMode mode)
    {
        rawTarget->floatingPointMode = mode;
    }

    static bool _passThroughRequiresStage(PassThroughMode passThrough)
    {
        switch (passThrough)
        {
            case PassThroughMode::Glslang:
            case PassThroughMode::Dxc:
            case PassThroughMode::Fxc:
            {
                return true;
            }
            default:
            {
                return false;
            }
        }
    }

    class ReproPathVisitor : public Slang::Path::Visitor
    {
    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::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);

        for (auto filename : visitor.m_filenames)
        {
            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, 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)))
            {
                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));

            if (stdOut)
            {
                StringBuilder buf;
                buf << filename << "\n";
                stdOut->write(buf.getBuffer(), buf.getLength());
            }

            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]);
            }

            if (SLANG_FAILED(requestImpl->compile()))
            {
                const char failed[] = "FAILED!\n";
                stdOut->write(failed, SLANG_COUNT_OF(failed) - 1);

                const auto& diagnostics = bufs[Index(WriterChannel::Diagnostic)];

                stdOut->write(diagnostics.getBuffer(), diagnostics.getLength());

                return SLANG_FAIL;
            }
        }

        if (stdOut)
        {
            const char end[] = "(END)\n";
            stdOut->write(end, SLANG_COUNT_OF(end) - 1);
        }

        return SLANG_OK;
    }

    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);
        
        {
            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);
        }

        // All diagnostics will also be sent to requestSink
        parseSink.setParentSink(requestSink);

        DiagnosticSink* sink = &parseSink;

        // 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);

        {
            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));
        }

        CommandLineReader reader(&args, sink);

        SlangMatrixLayoutMode defaultMatrixLayoutMode = SLANG_MATRIX_LAYOUT_MODE_UNKNOWN;

        // The default archive type is zip
        SlangArchiveType archiveType = SLANG_ARCHIVE_TYPE_ZIP;

        bool compileStdLib = false;
        slang::CompileStdLibFlags compileStdLibFlags = 0;
        bool hasLoadedRepro = false;

        while (reader.hasArg())
        {
            auto arg = reader.getArgAndAdvance();
            const auto& argValue = arg.value;

            if (argValue[0] == '-')
            {
                if(argValue == "-no-mangle" )
                {
                    flags |= SLANG_COMPILE_FLAG_NO_MANGLING;
                }
                else if (argValue == "-load-stdlib")
                {
                    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()));
                }
                else if (argValue == "-compile-stdlib")
                {
                    compileStdLib = true;
                }
                else if (argValue == "-archive-type")
                {
                    CommandLineArg archiveTypeName;
                    SLANG_RETURN_ON_FAIL(reader.expectArg(archiveTypeName));

                    archiveType = TypeTextUtil::findArchiveType(archiveTypeName.value.getUnownedSlice());
                    if (archiveType == SLANG_ARCHIVE_TYPE_UNDEFINED)
                    {
                        sink->diagnose(archiveTypeName.loc, Diagnostics::unknownArchiveType, archiveTypeName.value);
                        return SLANG_FAIL;
                    }
                }
                else if (argValue == "-save-stdlib")
                {
                    CommandLineArg fileName;
                    SLANG_RETURN_ON_FAIL(reader.expectArg(fileName));

                    ComPtr<ISlangBlob> blob;

                    SLANG_RETURN_ON_FAIL(session->saveStdLib(archiveType, blob.writeRef()));
                    SLANG_RETURN_ON_FAIL(File::writeAllBytes(fileName.value, blob->getBufferPointer(), blob->getBufferSize()));
                }
                else if (argValue == "-save-stdlib-bin-source")
                {
                    CommandLineArg fileName;
                    SLANG_RETURN_ON_FAIL(reader.expectArg(fileName));

                    ComPtr<ISlangBlob> blob;

                    SLANG_RETURN_ON_FAIL(session->saveStdLib(archiveType, blob.writeRef()));

                    StringBuilder builder;
                    StringWriter writer(&builder, 0);

                    SLANG_RETURN_ON_FAIL(HexDumpUtil::dumpSourceBytes((const uint8_t*)blob->getBufferPointer(), blob->getBufferSize(), 16, &writer));

                    File::writeAllText(fileName.value, builder);
                }
                else if (argValue == "-no-codegen")
                {
                    flags |= SLANG_COMPILE_FLAG_NO_CODEGEN;
                }
                else if (argValue == "-dump-intermediates")
                {
                    compileRequest->setDumpIntermediates(true);
                }
                else if (argValue == "-dump-ir-ids")
                {
                    requestImpl->getFrontEndReq()->m_irDumpOptions.flags |= IRDumpOptions::Flag::DumpDebugIds;
                    requestImpl->getBackEndReq()->m_irDumpOptions.flags |= IRDumpOptions::Flag::DumpDebugIds;
                }
                else if (argValue == "-dump-intermediate-prefix")
                {
                    CommandLineArg prefix;
                    SLANG_RETURN_ON_FAIL(reader.expectArg(prefix));
                    requestImpl->getBackEndReq()->m_dumpIntermediatePrefix = prefix.value;
                }
                else if (argValue == "-output-includes")
                {
                    requestImpl->getFrontEndReq()->outputIncludes = true;
                }
                else if(argValue == "-dump-ir" )
                {
                    requestImpl->getFrontEndReq()->shouldDumpIR = true;
                    requestImpl->getBackEndReq()->shouldDumpIR = true;
                }
                else if (argValue == "-E" || argValue == "-output-preprocessor")
                {
                    requestImpl->getFrontEndReq()->outputPreprocessor = true;
                }
                else if (argValue == "-dump-ast")
                {
                    requestImpl->getFrontEndReq()->shouldDumpAST = true;
                }
                else if (argValue == "-doc")
                {
                    // If compiling stdlib is enabled, will write out documentation
                    compileStdLibFlags |= slang::CompileStdLibFlag::WriteDocumentation;

                    // Enable writing out documentation on the req
                    requestImpl->getFrontEndReq()->shouldDocument = true;
                }
                else if (argValue == "-dump-repro")
                {
                    CommandLineArg dumpRepro;
                    SLANG_RETURN_ON_FAIL(reader.expectArg(dumpRepro));
                    requestImpl->m_dumpRepro = dumpRepro.value;
                    compileRequest->enableReproCapture();
                }
                else if (argValue == "-dump-repro-on-error")
                {
                    requestImpl->m_dumpReproOnError = true;
                }
                else if (argValue == "-extract-repro")
                {
                    CommandLineArg reproName;
                    SLANG_RETURN_ON_FAIL(reader.expectArg(reproName));

                    {
                        const Result res = ReproUtil::extractFilesToDirectory(reproName.value, sink);
                        if (SLANG_FAILED(res))
                        {
                            sink->diagnose(reproName.loc, Diagnostics::unableExtractReproToDirectory, reproName.value);
                            return res;
                        }
                    }
                }
                else if (argValue == "-module-name")
                {
                    CommandLineArg moduleName;
                    SLANG_RETURN_ON_FAIL(reader.expectArg(moduleName));

                    compileRequest->setDefaultModuleName(moduleName.value.getBuffer());
                }
                else if(argValue == "-load-repro")
                {
                    CommandLineArg reproName;
                    SLANG_RETURN_ON_FAIL(reader.expectArg(reproName));

                    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 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(reproName.value, 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));

                    hasLoadedRepro = true;
                }
                else if (argValue == "-load-repro-directory")
                {
                    CommandLineArg reproDirectory;
                    SLANG_RETURN_ON_FAIL(reader.expectArg(reproDirectory));

                    SLANG_RETURN_ON_FAIL(_compileReproDirectory(session, requestImpl, reproDirectory.value, sink));
                }
                else if (argValue == "-repro-file-system")
                {
                    CommandLineArg reproName;
                    SLANG_RETURN_ON_FAIL(reader.expectArg(reproName));

                    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 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)
                        {
                            dirFileSystem = new RelativeFileSystem(OSFileSystem::getExtSingleton(), dirPath, true);
                        }
                    }

                    RefPtr<CacheFileSystem> cacheFileSystem;
                    SLANG_RETURN_ON_FAIL(ReproUtil::loadFileSystem(base, requestState, dirFileSystem, 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
                    compileRequest->setFileSystem(cacheFileSystem);
                }
                else if (argValue == "-serial-ir")
                {
                    requestImpl->getFrontEndReq()->useSerialIRBottleneck = true;
                }
                else if (argValue == "-disable-specialization")
                {
                    requestImpl->getBackEndReq()->disableSpecialization = true;
                }
                else if (argValue == "-disable-dynamic-dispatch")
                {
                    requestImpl->getBackEndReq()->disableDynamicDispatch = true;
                }
                else if (argValue == "-verbose-paths")
                {
                    requestImpl->getSink()->setFlag(DiagnosticSink::Flag::VerbosePath);
                }
                else if (argValue == "-verify-debug-serial-ir")
                {
                    requestImpl->getFrontEndReq()->verifyDebugSerialization = true;
                }
                else if(argValue == "-validate-ir" )
                {
                    requestImpl->getFrontEndReq()->shouldValidateIR = true;
                    requestImpl->getBackEndReq()->shouldValidateIR = true;
                }
                else if(argValue == "-skip-codegen" )
                {
                    requestImpl->m_shouldSkipCodegen = true;
                }
                else if(argValue == "-parameter-blocks-use-register-spaces" )
                {
                    getCurrentTarget()->targetFlags |= SLANG_TARGET_FLAG_PARAMETER_BLOCKS_USE_REGISTER_SPACES;
                }
                else if (argValue == "-ir-compression")
                {
                    CommandLineArg name;
                    SLANG_RETURN_ON_FAIL(reader.expectArg(name));

                    SLANG_RETURN_ON_FAIL(SerialParseUtil::parseCompressionType(name.value.getUnownedSlice(), requestImpl->getLinkage()->serialCompressionType));
                }
                else if (argValue == "-target")
                {
                    CommandLineArg name;
                    SLANG_RETURN_ON_FAIL(reader.expectArg(name));

                    const CodeGenTarget format = (CodeGenTarget)TypeTextUtil::findCompileTargetFromName(name.value.getUnownedSlice());

                    if (format == CodeGenTarget::Unknown)
                    {
                        sink->diagnose(name.loc, Diagnostics::unknownCodeGenerationTarget, name.value);
                        return SLANG_FAIL;
                    }

                    RawTarget rawTarget;
                    rawTarget.format = CodeGenTarget(format);

                    rawTargets.add(rawTarget);
                }
                // A "profile" can specify both a general capability level for
                // a target, and also (as a legacy/compatibility feature) a
                // specific stage to use for an entry point.
                else if (argValue == "-profile")
                {
                    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 = 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);

                        setProfileVersion(getCurrentTarget(), profile.getVersion());

                        // A `-profile` option that also specifies a stage (e.g., `-profile vs_5_0`)
                        // should be treated like a composite (e.g., `-profile sm_5_0 -stage vertex`)
                        auto stage = profile.getStage();
                        if(stage != Stage::Unknown)
                        {
                            setStage(getCurrentEntryPoint(), stage);
                        }
                    }

                    // 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);
                    }
                }
                else if( argValue == "-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)
                    {
                        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);
                    }
                }
                else if (argValue == "-stage")
                {
                    CommandLineArg name;
                    SLANG_RETURN_ON_FAIL(reader.expectArg(name));

                    Stage stage = findStageByName(name.value);
                    if( stage == Stage::Unknown )
                    {
                        sink->diagnose(name.loc, Diagnostics::unknownStage, name.value);
                        return SLANG_FAIL;
                    }
                    else
                    {
                        setStage(getCurrentEntryPoint(), stage);
                    }
                }
                else if (argValue == "-entry")
                {
                    CommandLineArg name;
                    SLANG_RETURN_ON_FAIL(reader.expectArg(name));

                    RawEntryPoint rawEntryPoint;
                    rawEntryPoint.name = name.value;
                    rawEntryPoint.translationUnitIndex = currentTranslationUnitIndex;

                    rawEntryPoints.add(rawEntryPoint);
                }
                else if (argValue == "-heterogeneous")
                {
                    requestImpl->getLinkage()->m_heterogeneous = true;
                }
                else if (argValue == "-lang")
                {
                    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));
                        }
                    }
                }
                else if (argValue == "-pass-through")
                {
                    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);
                }
                else if (argValue.getLength() >= 2 && argValue[1] == 'D')
                {
                    // The value to be defined might be part of the same option, as in:
                    //     -DFOO
                    // or it might come separately, as in:
                    //     -D FOO

                    UnownedStringSlice slice = argValue.getUnownedSlice().tail(2);

                    CommandLineArg nextArg;
                    if (slice.getLength() <= 0)
                    {
                        SLANG_RETURN_ON_FAIL(reader.expectArg(nextArg));
                        slice = nextArg.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('=');

                    // Now set the preprocessor define
              
                    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(), "");
                    }
                }
                else if (argValue.getLength() >= 2 && argValue[1] == 'I')
                {
                    // 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();
                    }

                    compileRequest->addSearchPath(String(slice).getBuffer());
                }
                //
                // A `-o` option is used to specify a desired output file.
                else if (argValue == "-o")
                {
                    CommandLineArg outputPath;
                    SLANG_RETURN_ON_FAIL(reader.expectArg(outputPath));

                    addOutputPath(outputPath.value.getBuffer());
                }
                else if(argValue == "-matrix-layout-row-major")
                {
                    defaultMatrixLayoutMode = kMatrixLayoutMode_RowMajor;
                }
                else if(argValue == "-matrix-layout-column-major")
                {
                    defaultMatrixLayoutMode = kMatrixLayoutMode_ColumnMajor;
                }
                else if(argValue == "-line-directive-mode")
                {
                    CommandLineArg name;
                    SLANG_RETURN_ON_FAIL(reader.expectArg(name));

                    SlangLineDirectiveMode mode = SLANG_LINE_DIRECTIVE_MODE_DEFAULT;
                    if(name.value == "none")
                    {
                        mode = SLANG_LINE_DIRECTIVE_MODE_NONE;
                    }
                    else
                    {
                        sink->diagnose(name.loc, Diagnostics::unknownLineDirectiveMode, name.value);
                        return SLANG_FAIL;
                    }

                    compileRequest->setLineDirectiveMode(mode);

                }
                else if( argValue == "-fp-mode" || argValue == "-floating-point-mode" )
                {
                    CommandLineArg name;
                    SLANG_RETURN_ON_FAIL(reader.expectArg(name));

                    FloatingPointMode mode = FloatingPointMode::Default;
                    if(name.value == "fast")
                    {
                        mode = FloatingPointMode::Fast;
                    }
                    else if(name.value == "precise")
                    {
                        mode = FloatingPointMode::Precise;
                    }
                    else
                    {
                        sink->diagnose(name.loc, Diagnostics::unknownFloatingPointMode, name.value);
                        return SLANG_FAIL;
                    }

                    setFloatingPointMode(getCurrentTarget(), mode);
                }
                else if( argValue.getLength() >= 2 && argValue[1] == 'O' )
                {
                    UnownedStringSlice levelSlice = argValue.getUnownedSlice().tail(2);
                    SlangOptimizationLevel level = SLANG_OPTIMIZATION_LEVEL_DEFAULT;

                    const char c = levelSlice.getLength() == 1 ? levelSlice[0] : 0;

                    switch (c)
                    {
                        case '0':   level = SLANG_OPTIMIZATION_LEVEL_NONE;      break;
                        case '1':   level = SLANG_OPTIMIZATION_LEVEL_DEFAULT;   break;
                        case '2':   level = SLANG_OPTIMIZATION_LEVEL_HIGH;      break;
                        case '3':   level = SLANG_OPTIMIZATION_LEVEL_MAXIMAL;   break;
                        default:
                        {
                            sink->diagnose(arg.loc, Diagnostics::unknownOptimiziationLevel, arg.value);
                            return SLANG_FAIL;
                        }
                    }
                 
                    compileRequest->setOptimizationLevel(level);
                }

                // 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.
                else if( argValue == "-g" || argValue == "-g2" )
                {
                    compileRequest->setDebugInfoLevel(SLANG_DEBUG_INFO_LEVEL_STANDARD);
                }
                else if( argValue == "-g0" )
                {
                    compileRequest->setDebugInfoLevel(SLANG_DEBUG_INFO_LEVEL_NONE);
                }
                else if( argValue == "-g1" )
                {
                    compileRequest->setDebugInfoLevel(SLANG_DEBUG_INFO_LEVEL_MINIMAL);
                }
                else if( argValue == "-g3" )
                {
                    compileRequest->setDebugInfoLevel(SLANG_DEBUG_INFO_LEVEL_MAXIMAL);
                }
                else if( argValue == "-default-image-format-unknown" )
                {
                    requestImpl->getBackEndReq()->useUnknownImageFormatAsDefault = true;
                }
                else if (argValue == "-obfuscate")
                {
                    requestImpl->getLinkage()->m_obfuscateCode = true;
                }
                else if (argValue == "-file-system")
                {
                    CommandLineArg name;
                    SLANG_RETURN_ON_FAIL(reader.expectArg(name));

                    if (name.value == "default")
                    {
                        compileRequest->setFileSystem(nullptr);
                    }
                    else if (name.value == "load-file")
                    {
                        // 'Simple' just implements loadFile interface, so will be wrapped with CacheFileSystem internally
                        compileRequest->setFileSystem(OSFileSystem::getLoadSingleton());
                    }
                    else if (name.value == "os")
                    {
                        // 'Immutable' implements the ISlangFileSystemExt interface - and will be used directly
                        compileRequest->setFileSystem(OSFileSystem::getExtSingleton());
                    }
                    else
                    {
                        sink->diagnose(name.loc, Diagnostics::unknownFileSystemOption, name.value);
                        return SLANG_FAIL;
                    }
                }
                else if (argValue == "-r")
                {
                    CommandLineArg referenceModuleName;
                    SLANG_RETURN_ON_FAIL(reader.expectArg(referenceModuleName));

                    // We need to deserialize and add the modules
                    FileStream fileStream;
                    SLANG_RETURN_ON_FAIL(fileStream.init(referenceModuleName.value, FileMode::Open, FileAccess::Read, FileShare::ReadWrite));

                    // TODO: probably near an error when we can't open the file?

                    _addLibraryReference(requestImpl, &fileStream);
                }
                else if (argValue == "-v")
                {
                    sink->diagnoseRaw(Severity::Note, session->getBuildTagString());
                }
                else if( argValue == "-emit-spirv-directly" )
                {
                    getCurrentTarget()->targetFlags |= SLANG_TARGET_FLAG_GENERATE_SPIRV_DIRECTLY;
                }
                else if (argValue == "-default-downstream-compiler")
                {
                    CommandLineArg sourceLanguageArg, compilerArg;
                    SLANG_RETURN_ON_FAIL(reader.expectArg(sourceLanguageArg));
                    SLANG_RETURN_ON_FAIL(reader.expectArg(compilerArg));

                    SlangSourceLanguage sourceLanguage = TypeTextUtil::findSourceLanguage(sourceLanguageArg.value.getUnownedSlice());
                    if (sourceLanguage == SLANG_SOURCE_LANGUAGE_UNKNOWN)
                    {
                        sink->diagnose(sourceLanguageArg.loc, Diagnostics::unknownSourceLanguage, sourceLanguageArg.value);
                        return SLANG_FAIL;
                    }

                    SlangPassThrough compiler;
                    if (SLANG_FAILED(TypeTextUtil::findPassThrough(compilerArg.value.getUnownedSlice(), compiler)))
                    {
                        sink->diagnose(compilerArg.loc, Diagnostics::unknownPassThroughTarget, compilerArg.value);
                        return SLANG_FAIL;
                    }

                    if (SLANG_FAILED(session->setDefaultDownstreamCompiler(sourceLanguage, compiler)))
                    {
                        sink->diagnose(arg.loc, Diagnostics::unableToSetDefaultDownstreamCompiler, compilerArg.value, sourceLanguageArg.value);
                        return SLANG_FAIL;
                    }
                }       
                else if (argValue == "--")
                {
                    // 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;
                }
                else
                {
                    if (argValue.endsWith("-path"))
                    {
                        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;
                            }
                        }
                    }

                    sink->diagnose(arg.loc, Diagnostics::unknownCommandLineOption, argValue);
                    // TODO: print a usage message
                    return SLANG_FAIL;
                }
            }
            else
            {
                SLANG_RETURN_ON_FAIL(addInputPath(argValue.getBuffer()));
            }
        }

        if (compileStdLib)
        {
            SLANG_RETURN_ON_FAIL(session->compileStdLib(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 (hasLoadedRepro)
        {
            return SLANG_OK;
        }

        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( rawEntryPoints.getCount() == 1 )
        {
            if(defaultEntryPoint.stage != Stage::Unknown)
            {
                setStage(getCurrentEntryPoint(), defaultEntryPoint.stage);
            }

            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 )
            {
                if( rawEntryPoints.getCount() == 0 )
                {
                    sink->diagnose(SourceLoc(), Diagnostics::stageSpecificationIgnoredBecauseNoEntryPoints);
                }
                else
                {
                    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)
        {
            for( auto& entryPoint : rawEntryPoints )
            {
                entryPoint.translationUnitIndex = 0;
            }
        }
        else
        {
            // Otherwise, we require that all entry points be specified after
            // the translation unit to which tye belong.
            bool anyEntryPointWithoutTranslationUnit = false;
            for( auto& entryPoint : rawEntryPoints )
            {
                // 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;
            }
        }

        // Now that entry points are associated with translation units,
        // we can make one additional pass where if an entry point has
        // no specified stage, but the nameing of its translation unit
        // implies a stage, we will use that (a manual `-stage` annotation
        // will always win out in such a case).
        //
        for( auto& rawEntryPoint : rawEntryPoints )
        {
            // Skip entry points that already have a stage.
            if(rawEntryPoint.stage != Stage::Unknown)
                continue;

            // Sanity check: don't process entry points with no associated translation unit.
            if( rawEntryPoint.translationUnitIndex == -1 )
                continue;

            auto impliedStage = rawTranslationUnits[rawEntryPoint.translationUnitIndex].impliedStage;
            if(impliedStage != Stage::Unknown)
                rawEntryPoint.stage = impliedStage;
        }

        // Note: it is possible that some entry points still won't have associated
        // stages at this point, but we don't want to error out here, because
        // those entry points might get stages later, as part of semantic checking,
        // if the corresponding function has a `[shader("...")]` attribute.

        // Now that we've tried to establish stages for entry points, we can
        // issue diagnostics for cases where stages were set redundantly or
        // in conflicting ways.
        //
        for( auto& rawEntryPoint : rawEntryPoints )
        {
            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 )
            {
                // 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);
                }
            }
        }

        // 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) )
        {
            for( auto& rawEntryPoint : rawEntryPoints )
            {
                if( rawEntryPoint.stage == Stage::Unknown )
                {
                    sink->diagnose(SourceLoc(), Diagnostics::noStageSpecifiedInPassThroughMode, rawEntryPoint.name);
                }
            }
        }

        // We now have inferred enough information to add the
        // entry points to our compile request.
        //
        for( auto& rawEntryPoint : rawEntryPoints )
        {
            if(rawEntryPoint.translationUnitIndex < 0)
                continue;

            auto translationUnitID = rawTranslationUnits[rawEntryPoint.translationUnitIndex].translationUnitID;

            int entryPointID = compileRequest->addEntryPoint(
                translationUnitID,
                rawEntryPoint.name.begin(),
                SlangStage(rawEntryPoint.stage));

            rawEntryPoint.entryPointID = entryPointID;
        }

        // We are going to build a mapping from target formats to the
        // target that handles that format.
        Dictionary<CodeGenTarget, int> mapFormatToTargetIndex;

        // If there was no explicit `-target` specified, then we will look
        // at the `-o` options to see what we can infer.
        //
        if(rawTargets.getCount() == 0)
        {
            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();

                    RawTarget rawTarget;
                    rawTarget.format = impliedFormat;
                    rawTargets.add(rawTarget);

                    mapFormatToTargetIndex[impliedFormat] = targetIndex;
                }

                rawOutput.targetIndex = targetIndex;
            }
        }
        else
        {
            // If there were explicit targets, then we will use those, but still
            // build up our mapping. We should object if the same target format
            // is specified more than once (just because of the ambiguities
            // it will create).
            //
            int targetCount = (int) rawTargets.getCount();
            for(int targetIndex = 0; targetIndex < targetCount; ++targetIndex)
            {
                auto format = rawTargets[targetIndex].format;

                if( mapFormatToTargetIndex.ContainsKey(format) )
                {
                    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( rawTargets.getCount() == 0
            && defaultTarget.profileVersion != ProfileVersion::Unknown
            && !defaultTarget.conflictingProfilesSet)
        {
            // Let's see if the chosen profile allows us to infer
            // the code gen target format that the user probably meant.
            //
            CodeGenTarget inferredFormat = CodeGenTarget::Unknown;
            auto profileVersion = defaultTarget.profileVersion;
            switch( Profile(profileVersion).getFamily() )
            {
            default:
                break;

                // For GLSL profile versions, we will assume SPIR-V
                // is the output format the user intended.
            case ProfileFamily::GLSL:
                inferredFormat = CodeGenTarget::SPIRV;
                break;

                // For DX profile versions, we will assume that the
                // user wants DXIL for Shader Model 6.0 and up,
                // and DXBC for all earlier versions.
                //
                // Note: There is overlap where both DXBC and DXIL
                // nominally support SM 5.1, but in general we
                // expect users to prefer to make a clean break
                // at SM 6.0. Anybody who cares about the overlap
                // cases should manually specify `-target dxil`.
                //
            case ProfileFamily::DX:
                if( profileVersion >= ProfileVersion::DX_6_0 )
                {
                    inferredFormat = CodeGenTarget::DXIL;
                }
                else
                {
                    inferredFormat = CodeGenTarget::DXBytecode;
                }
                break;
            }

            if( inferredFormat != CodeGenTarget::Unknown )
            {
                RawTarget rawTarget;
                rawTarget.format = inferredFormat;
                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)
        {
            if(defaultTarget.profileVersion != ProfileVersion::Unknown)
            {
                setProfileVersion(getCurrentTarget(), defaultTarget.profileVersion);
            }
            for( auto atom : defaultTarget.capabilityAtoms )
            {
                addCapabilityAtom(getCurrentTarget(), atom);
            }

            getCurrentTarget()->targetFlags |= defaultTarget.targetFlags;

            if( defaultTarget.floatingPointMode != FloatingPointMode::Default )
            {
                setFloatingPointMode(getCurrentTarget(), defaultTarget.floatingPointMode);
            }
        }
        else
        {
            // If the "default" target has had a profile (or other state)
            // specified, but there is != 1 taget, then that state doesn't
            // apply to anythign and we should give the user an error.
            //
            if( defaultTarget.profileVersion != ProfileVersion::Unknown )
            {
                if( 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( rawTargets.getCount() == 0 )
                {
                    sink->diagnose(SourceLoc(), Diagnostics::targetFlagsIgnoredBecauseNoTargets);
                }
                else
                {
                    sink->diagnose(SourceLoc(), Diagnostics::targetFlagsIgnoredBecauseBeforeAllTargets);
                }
            }

            if( defaultTarget.floatingPointMode != FloatingPointMode::Default )
            {
                if( rawTargets.getCount() == 0 )
                {
                    sink->diagnose(SourceLoc(), Diagnostics::targetFlagsIgnoredBecauseNoTargets);
                }
                else
                {
                    sink->diagnose(SourceLoc(), Diagnostics::targetFlagsIgnoredBecauseBeforeAllTargets);
                }
            }

        }

        for(auto& rawTarget : rawTargets)
        {
            if( rawTarget.conflictingProfilesSet )
            {
                sink->diagnose(SourceLoc(), Diagnostics::conflictingProfilesSpecifiedForTarget, rawTarget.format);
            }
            else if( rawTarget.redundantProfileSet )
            {
                sink->diagnose(SourceLoc(), Diagnostics::sameProfileSpecifiedMoreThanOnce, rawTarget.profileVersion, rawTarget.format);
            }
        }

        // 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)
        {
            int targetID = compileRequest->addCodeGenTarget(SlangCompileTarget(rawTarget.format));
            rawTarget.targetID = targetID;

            if( rawTarget.profileVersion != ProfileVersion::Unknown )
            {
                compileRequest->setTargetProfile(targetID, Profile(rawTarget.profileVersion).raw);
            }
            for( auto atom : rawTarget.capabilityAtoms )
            {
                requestImpl->addTargetCapability(targetID, SlangCapabilityID(atom));
            }

            if( rawTarget.targetFlags )
            {
                compileRequest->setTargetFlags(targetID, rawTarget.targetFlags);
            }

            if( rawTarget.floatingPointMode != FloatingPointMode::Default )
            {
                compileRequest->setTargetFloatingPointMode(targetID, SlangFloatingPointMode(rawTarget.floatingPointMode));
            }
        }

        if(defaultMatrixLayoutMode != SLANG_MATRIX_LAYOUT_MODE_UNKNOWN)
        {
            compileRequest->setMatrixLayoutMode(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( rawEntryPoints.getCount() == 1 )
        {
            for( auto& rawOutput : rawOutputs )
            {
                rawOutput.entryPointIndex = 0;
            }
        }
        //
        // Similarly, if there is only one target, then all outputs must
        // implicitly appertain to that target.
        //
        if( rawTargets.getCount() == 1 )
        {
            for( auto& rawOutput : rawOutputs )
            {
                rawOutput.targetIndex = 0;
            }
        }

        // Consider the output files specified via `-o` and try to figure
        // out how to deal with them.
        //
        for(auto& rawOutput : 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 )
            {
                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);
                }
            }

            // 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 )
                {
                    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:
                    case CodeGenTarget::HostCallable:
                    case CodeGenTarget::Executable:
                    case CodeGenTarget::SharedLibrary:
                        rawOutput.isWholeProgram = true;
                        break;
                    default:
                        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)
        {
            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;
            }

            if (rawOutput.isWholeProgram)
            {
                if (targetInfo->wholeTargetOutputPath != "")
                {
                    sink->diagnose(SourceLoc(), Diagnostics::duplicateOutputPathsForTarget, target->getTarget());
                }
                else
                {
                    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];

                //String outputPath;
                if (targetInfo->entryPointOutputPaths.ContainsKey(entryPointID))
                {
                    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)
{
    Slang::EndToEndCompileRequest* compileRequest = asInternal(inCompileRequest);

    OptionsParser parser;
    parser.compileRequest = inCompileRequest;
    parser.requestImpl = compileRequest;
    parser.session = asInternal(compileRequest->getSession());

    Result res = parser.parse(argc, argv);

    DiagnosticSink* sink = compileRequest->getSink();
    if (sink->getErrorCount() > 0)
    {
        // Put the errors in the diagnostic 
        compileRequest->m_diagnosticOutput = sink->outputBuffer.ProduceString();
    }

    return res;
}


} // namespace Slang


back to top