// 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 "compiler-core/slang-artifact-desc-util.h"
#include "compiler-core/slang-artifact-impl.h"
#include "compiler-core/slang-artifact-representation-impl.h"
#include "compiler-core/slang-command-line-args.h"
#include "compiler-core/slang-core-diagnostics.h"
#include "compiler-core/slang-name-convention-util.h"
#include "compiler-core/slang-source-embed-util.h"
#include "core/slang-castable.h"
#include "core/slang-char-util.h"
#include "core/slang-command-options-writer.h"
#include "core/slang-file-system.h"
#include "core/slang-hex-dump-util.h"
#include "core/slang-name-value.h"
#include "core/slang-platform.h"
#include "core/slang-stream.h"
#include "core/slang-string-slice-pool.h"
#include "core/slang-string-util.h"
#include "core/slang-type-text-util.h"
#include "slang-compiler-options.h"
#include "slang-compiler.h"
#include "slang-hlsl-to-vulkan-layout-options.h"
#include "slang-markdown.h"
#include "slang-profile.h"
#include "slang-repro.h"
#include "slang-rich-diagnostics.h"
#include "slang-serialize-ir.h"
#include "slang.h"

#ifdef _WIN32
#include <fcntl.h>
#include <io.h>
#endif
#include <limits>
#include <stdio.h>
#include <string.h>

namespace Slang
{

static const char* const kStdinDisplayPath = "<stdin>";
static const char* const kStdinCommandLinePath = "-";
static const Index kMaxStdinBytes = Index(256) << 20;

namespace
{ // anonymous

enum class StdinSourceReadResult
{
    Success,
    CannotRead,
    TooLarge,
};

static StdinSourceReadResult _readStdinSource(FILE* input, Index maxBytes, List<Byte>& outSource)
{
    outSource.clear();

    if (!input)
        return StdinSourceReadResult::CannotRead;

#ifdef _WIN32
    const int previousMode = _setmode(_fileno(input), _O_BINARY);
    if (previousMode == -1)
        return StdinSourceReadResult::CannotRead;

    struct StdinModeGuard
    {
        FILE* input;
        int mode;
        ~StdinModeGuard() { _setmode(_fileno(input), mode); }
    } stdinModeGuard{input, previousMode};
#endif

    clearerr(input);

    // Use blocking stdio here. The process PipeStream path is optimized for polling child
    // process output and can spin while waiting for piped stdin.
    char buffer[16 * 1024];
    for (;;)
    {
        const size_t readByteCount = fread(buffer, 1, sizeof(buffer), input);
        if (readByteCount != 0)
        {
            const Index readCount = Index(readByteCount);
            if (outSource.getCount() > maxBytes || readCount > maxBytes - outSource.getCount())
            {
                outSource.clear();
                return StdinSourceReadResult::TooLarge;
            }
            outSource.addRange(reinterpret_cast<const Byte*>(buffer), readCount);
            continue;
        }

        if (ferror(input))
        {
            outSource.clear();
            return StdinSourceReadResult::CannotRead;
        }

        return StdinSourceReadResult::Success;
    }
}

// All of the options are given an unique enum
typedef CompilerOptionName OptionKind;

struct Option
{
    OptionKind optionKind;
    const char* name;
    const char* usage = nullptr;
    const char* description = nullptr;
    const char* displayName = nullptr;
    const CommandOptions::InputLink* links = nullptr;
    Count linkCount = 0;
};

enum class ValueCategory
{
    Compiler,
    Target,
    Language,
    FloatingPointMode,
    FloatingPointDenormalMode,
    ArchiveType,
    Stage,
    LineDirectiveMode,
    DebugInfoFormat,
    HelpStyle,
    OptimizationLevel,
    DebugLevel,
    FileSystemType,
    VulkanShift,
    SourceEmbedStyle,
    LanguageVersion,

    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(FloatingPointDenormalMode, FloatingPointDenormalMode)
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)
SLANG_GET_VALUE_CATEGORY(SourceEmbedStyle, SourceEmbedUtil::Style)
SLANG_GET_VALUE_CATEGORY(Language, SourceLanguage)

} // namespace

static void _addOptions(const ConstArrayView<Option>& options, CommandOptions& cmdOptions)
{
    for (auto& opt : options)
    {
        cmdOptions.add(
            opt.name,
            opt.usage,
            opt.description,
            CommandOptions::UserValue(opt.optionKind),
            opt.displayName,
            opt.links,
            opt.linkCount);
    }
}

void initCommandOptions(CommandOptions& options)
{
    typedef CommandOptions::Flag::Enum Flag;
    typedef CommandOptions::CategoryKind CategoryKind;
    typedef CommandOptions::UserValue UserValue;

    // Add all the option categories

    options.addCategory(CategoryKind::Option, "General", "General options");
    options.addCategory(CategoryKind::Option, "Target", "Target code generation options");
    options.addCategory(CategoryKind::Option, "Downstream", "Downstream compiler options");
    options.addCategory(
        CategoryKind::Option,
        "Debugging",
        "Compiler debugging/instrumentation options");
    options.addCategory(CategoryKind::Option, "Repro", "Slang repro system related");
    options.addCategory(
        CategoryKind::Option,
        "Experimental",
        "Experimental options (use at your own risk)");
    options.addCategory(
        CategoryKind::Option,
        "Internal",
        "Internal-use options (use at your own risk)");
    options.addCategory(
        CategoryKind::Option,
        "Deprecated",
        "Deprecated options (allowed but ignored; may be removed in future)");

    // Do the easy ones
    {
        options.addCategory(
            CategoryKind::Value,
            "compiler",
            "Downstream Compilers (aka Pass through)",
            UserValue(ValueCategory::Compiler));
        options.addValues(TypeTextUtil::getCompilerInfos());

        options.addCategory(
            CategoryKind::Value,
            "language",
            "Language",
            UserValue(ValueCategory::Language));
        options.addValues(TypeTextUtil::getLanguageInfos());

        options.addCategory(
            CategoryKind::Value,
            "language-version",
            "Language Version",
            UserValue(ValueCategory::LanguageVersion));
        options.addValues(TypeTextUtil::getLanguageVersionInfos());


        options.addCategory(
            CategoryKind::Value,
            "archive-type",
            "Archive Type",
            UserValue(ValueCategory::ArchiveType));
        options.addValues(TypeTextUtil::getArchiveTypeInfos());

        options.addCategory(
            CategoryKind::Value,
            "line-directive-mode",
            "Line Directive Mode",
            UserValue(ValueCategory::LineDirectiveMode));
        options.addValues(TypeTextUtil::getLineDirectiveInfos());

        options.addCategory(
            CategoryKind::Value,
            "debug-info-format",
            "Debug Info Format",
            UserValue(ValueCategory::DebugInfoFormat));
        options.addValues(TypeTextUtil::getDebugInfoFormatInfos());

        options.addCategory(
            CategoryKind::Value,
            "fp-mode",
            "Floating Point Mode",
            UserValue(ValueCategory::FloatingPointMode));
        options.addValues(TypeTextUtil::getFloatingPointModeInfos());

        options.addCategory(
            CategoryKind::Value,
            "fp-denormal-mode",
            "Floating Point Denormal Handling Mode",
            UserValue(ValueCategory::FloatingPointDenormalMode));
        options.addValues(TypeTextUtil::getFpDenormalModeInfos());

        options.addCategory(
            CategoryKind::Value,
            "help-style",
            "Help Style",
            UserValue(ValueCategory::HelpStyle));
        options.addValues(CommandOptionsWriter::getStyleInfos());

        options.addCategory(
            CategoryKind::Value,
            "optimization-level",
            "Optimization Level",
            UserValue(ValueCategory::OptimizationLevel));
        options.addValues(TypeTextUtil::getOptimizationLevelInfos());

        options.addCategory(
            CategoryKind::Value,
            "debug-level",
            "Debug Level",
            UserValue(ValueCategory::DebugLevel));
        options.addValues(TypeTextUtil::getDebugLevelInfos());

        options.addCategory(
            CategoryKind::Value,
            "file-system-type",
            "File System Type",
            UserValue(ValueCategory::FileSystemType));
        options.addValues(TypeTextUtil::getFileSystemTypeInfos());

        options.addCategory(
            CategoryKind::Value,
            "source-embed-style",
            "Source Embed Style",
            UserValue(ValueCategory::SourceEmbedStyle));
        options.addValues(SourceEmbedUtil::getStyleInfos());
    }

    /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! target !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

    {
        options
            .addCategory(CategoryKind::Value, "target", "Target", UserValue(ValueCategory::Target));
        for (auto opt : TypeTextUtil::getCompileTargetInfos())
        {
            options.addValue(opt.names, opt.description, UserValue(opt.target));
        }
    }

    /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! stage !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

    {
        options.addCategory(CategoryKind::Value, "stage", "Stage", UserValue(ValueCategory::Stage));
        List<NameValue> opts;
        for (auto& info : getStageInfos())
        {
            opts.add({ValueInt(info.stage), info.name});
        }
        options.addValuesWithAliases(opts.getArrayView());
    }

    /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! vulkan-shift !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

    {
        options.addCategory(
            CategoryKind::Value,
            "vulkan-shift",
            "Vulkan Shift",
            UserValue(ValueCategory::VulkanShift));
        options.addValues(HLSLToVulkanLayoutOptions::getKindInfos());
    }

    /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! capabilities !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

    {
        options.addCategory(
            CategoryKind::Value,
            "capability",
            "A capability describes an optional feature that a target may or "
            "may not support. When a -capability is specified, the compiler "
            "may assume that the target supports that capability, and generate "
            "code accordingly.");

        List<UnownedStringSlice> names;
        getCapabilityNames(names);

        // We'll just add to keep the list more simple...
        options.addValue("spirv_1_{ 0,1,2,3,4,5 }", "minimum supported SPIR - V version");

        for (auto name : names)
        {
            if (name.startsWith("__") || name.startsWith("spirv_1_") || name.startsWith("_") ||
                name == "Invalid")
            {
                continue;
            }
            else if (name.startsWith("GL_") || name.startsWith("SPV_") || name.startsWith("GLSL_"))
            {
                // We'll assume it is an extension..
                StringBuilder buf;
                buf << "enables the " << name << " extension";
                options.addValue(name, buf.getUnownedSlice());
            }
            else
            {
                options.addValue(name);
            }
        }
    }

    /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! extension !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

    {
        options.addCategory(
            CategoryKind::Value,
            "file-extension",
            "A <language>, <format>, and/or <stage> may be inferred from the "
            "extension of an input or -o path");

        // TODO(JS): It's concevable that these are enumerated via some other system
        // rather than just being listed here

        const CommandOptions::ValuePair pairs[] = {
            {"hlsl,fx", "hlsl"},
            {"dxbc", nullptr},
            {"dxbc-asm", "dxbc-assembly"},
            {"dxil", nullptr},
            {"dxil-asm", "dxil-assembly"},
            {"glsl", nullptr},
            {"vert", "glsl (vertex)"},
            {"frag", "glsl (fragment)"},
            {"geom", "glsl (geoemtry)"},
            {"tesc", "glsl (hull)"},
            {"tese", "glsl (domain)"},
            {"comp", "glsl (compute)"},
            {"mesh", "glsl (mesh)"},
            {"task", "glsl (amplification)"},
            {"slang", nullptr},
            {"spv", "SPIR-V"},
            {"spv-asm", "SPIR-V assembly"},
            {"c", nullptr},
            {"cpp,c++,cxx", "C++"},
            {"hpp", "C++ Header"},
            {"exe", "executable"},
            {"dll,so", "sharedlibrary/dll"},
            {"cu", "CUDA"},
            {"cuh", "CUDA Header"},
            {"ptx", "PTX"},
            {"obj,o", "object-code"},
            {"zip", "container"},
            {"slang-module,slang-library", "Slang Module/Library"},
            {"dir", "Container as a directory"},
        };
        options.addValues(pairs, SLANG_COUNT_OF(pairs));
    }

    /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! help-category !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

    {
        options.addCategory(
            CategoryKind::Value,
            "help-category",
            "Available help categories for the -h option");

        // Add all existing categories as valid help category values
        const auto& categories = options.getCategories();
        for (Index categoryIndex = 0; categoryIndex < categories.getCount(); ++categoryIndex)
        {
            const auto& category = categories[categoryIndex];
            options.addValue(category.name, category.description);
        }
    }


    /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! General !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

    options.setCategory("General");

    const Option generalOpts[] = {
        {OptionKind::MacroDefine,
         "-D?...",
         "-D<name>[=<value>], -D <name>[=<value>]",
         "Insert a preprocessor macro.\n"
         "The space between - D and <name> is optional. If no <value> is specified, Slang will "
         "define the macro with an empty value."},
        {OptionKind::DepFile,
         "-depfile",
         "-depfile <path>",
         "Save the source file dependency list in a file.\n"
         "Uses Makefile dependency syntax: <output>: <dep> <dep...>\n"
         "When no -o is given, - is used as the make target (output goes to stdout)."},
        {OptionKind::EntryPointName,
         "-entry",
         "-entry <name>",
         "Specify the name of an entry-point function.\n"
         "When compiling from a single file, this defaults to main if you specify a stage using "
         "-stage.\n"
         "Multiple -entry options may be used in a single invocation. "
         "When they do, the file associated with the entry point will be the first one found when "
         "searching to the left in the command line.\n"
         "If no -entry options are given, compiler will use [shader(...)] "
         "attributes to detect entry points."},
        {OptionKind::Specialize,
         "-specialize",
         "-specialize <typename>",
         "Specialize the last entrypoint with <typename>.\n"},
        {OptionKind::EmitIr,
         "-emit-ir",
         nullptr,
         "Emit IR typically as a '.slang-module' when outputting to a container."},
        {OptionKind::Help,
         "-h,-help,--help",
         "-h or -h <help-category>",
         "Print this message, or help in specified category."},
        {OptionKind::HelpStyle, "-help-style", "-help-style <help-style>", "Help formatting style"},
        {OptionKind::Include,
         "-I?...",
         "-I<path>, -I <path>",
         "Add a path to be used in resolving '#include' "
         "and 'import' operations."},
        {OptionKind::Language,
         "-lang",
         "-lang <language>",
         "Set the language for the following input files. Required when an input is '-' "
         "(standard input), because stdin has no file extension."},
        {OptionKind::MatrixLayoutColumn,
         "-matrix-layout-column-major",
         nullptr,
         "Set the default matrix layout to column-major."},
        {OptionKind::MatrixLayoutRow,
         "-matrix-layout-row-major",
         nullptr,
         "Set the default matrix layout to row-major."},
        {OptionKind::RestrictiveCapabilityCheck,
         "-restrictive-capability-check",
         nullptr,
         "Many capability warnings will become an error."},
        {OptionKind::IgnoreCapabilities,
         "-ignore-capabilities",
         nullptr,
         "Do not warn or error if capabilities are violated"},
        {OptionKind::MinimumSlangOptimization,
         "-minimum-slang-optimization",
         nullptr,
         "Perform minimum code optimization in Slang to favor compilation time."},
        {OptionKind::DisableNonEssentialValidations,
         "-disable-non-essential-validations",
         nullptr,
         "Disable non-essential IR validations such as use of uninitialized variables."},
        {OptionKind::DisableSourceMap,
         "-disable-source-map",
         nullptr,
         "Disable source mapping in the Obfuscation."},
        {OptionKind::ModuleName,
         "-module-name",
         "-module-name <name>",
         "Set the module name to use when compiling multiple .slang source files into a single "
         "module."},
        {OptionKind::Output,
         "-o",
         "-o <path>",
         "Specify a path where generated output should be written.\n"
         "Use `-` to write generated output to stdout. "
         "If no -target or -stage is specified, one may be inferred "
         "from file extension (see <file-extension>). "
         "If multiple -target options and a single -entry are present, each -o "
         "associates with the first -target to its left. "
         "Otherwise, if multiple -entry options are present, each -o associates "
         "with the first -entry to its left, and with the -target that matches "
         "the one inferred from <path>."},
        {OptionKind::Profile,
         "-profile",
         "-profile <profile>[+<capability>...]",
         "Specify the shader profile for code generation.\n"
         "Accepted profiles are:\n"
         "* sm_{4_0,4_1,5_0,5_1,6_0,6_1,6_2,6_3,6_4,6_5,6_6,6_7,6_8,6_9,6_10}\n"
         "* glsl_{150,330,400,410,420,430,440,450,460}\n"
         "Additional profiles that include -stage information:\n"
         "* {vs,hs,ds,gs,ps}_<version>\n"
         "See -capability for information on <capability>\n"
         "When multiple -target options are present, each -profile associates "
         "with the first -target to its left."},
        {OptionKind::Stage,
         "-stage",
         "-stage <stage>",
         "Specify the stage of an entry-point function.\n"
         "When multiple -entry options are present, each -stage associated with "
         "the first -entry to its left.\n"
         "May be omitted if entry-point function has a [shader(...)] attribute; "
         "otherwise required for each -entry option."},
        {OptionKind::Target,
         "-target",
         "-target <target>",
         "Specifies the format in which code should be generated."},
        {OptionKind::Version,
         "-v,-version",
         nullptr,
         "Display the build version. This is the contents of git describe --tags.\n"
         "It is typically only set from automated builds(such as distros available on github).A "
         "user build will by default be 'unknown'."},
        {OptionKind::LanguageVersion,
         "-std",
         "-std <language-version>",
         "Specifies the language standard that should be used."},
        {OptionKind::WarningsAsErrors,
         "-warnings-as-errors",
         "-warnings-as-errors all or -warnings-as-errors <id>[,<id>...]",
         "all - Treat all warnings as errors.\n"
         "<id>[,<id>...]: Treat specific warning ids as errors.\n"},
        {OptionKind::DisableWarnings,
         "-warnings-disable",
         "-warnings-disable <id>[,<id>...]",
         "Disable specific warning ids."},
        {OptionKind::WarningLevel,
         "-Wall,-Wextra,-Wpedantic",
         "-Wall | -Wextra | -Wpedantic",
         "Enable the corresponding group of warnings (additive). The groups are independent: "
         "-Wextra is on by default, while -Wall and -Wpedantic are off by default."},
        {OptionKind::EnableWarning, "-W...", "-W<id>", "Enable a warning with the specified id."},
        {OptionKind::DisableWarning, "-Wno-...", "-Wno-<id>", "Disable warning with <id>"},
        {OptionKind::DumpWarningDiagnostics,
         "-dump-warning-diagnostics",
         nullptr,
         "Dump to output list of warning diagnostic numeric and name ids."},
        {OptionKind::InputFilesRemain,
         "--",
         nullptr,
         "Treat the rest of the command line as input files. Use '-' once to read from standard "
         "input; -lang is required, stdin is limited to 256 MiB, and diagnostics use `<stdin>`."},
        {OptionKind::ReportDownstreamTime,
         "-report-downstream-time",
         nullptr,
         "Reports the time spent in the downstream compiler."},
        {OptionKind::ReportPerfBenchmark,
         "-report-perf-benchmark",
         nullptr,
         "Reports compiler performance benchmark results."},
        {OptionKind::ReportDetailedPerfBenchmark,
         "-report-detailed-perf-benchmark",
         nullptr,
         "Reports compiler performance benchmark results for each intermediate pass (implies "
         "-report-perf-benchmark)."},
        {OptionKind::ReportCheckpointIntermediates,
         "-report-checkpoint-intermediates",
         nullptr,
         "Reports information about checkpoint contexts used for reverse-mode automatic "
         "differentiation."},
        {OptionKind::TraceCoverage,
         "-trace-coverage",
         nullptr,
         "Instrument the shader with per-statement line coverage counters. "
         "When writing compiled output to a file, slangc also emits "
         "`<output>.coverage-manifest.json` mapping source coverage entries to counters."},
        {OptionKind::TraceFunctionCoverage,
         "-trace-function-coverage",
         nullptr,
         "Instrument the shader with per-function-entry coverage counters. "
         "Shares the synthesized `__slang_coverage` buffer and coverage metadata path."},
        {OptionKind::TraceBranchCoverage,
         "-trace-branch-coverage",
         nullptr,
         "Instrument the shader with per-branch-arm coverage counters for "
         "if/else, loop-condition, switch case/default arms, and switch no-match "
         "default paths. Expression-level short-circuit and ternary branches are "
         "not instrumented by this mode yet. "
         "Shares the synthesized `__slang_coverage` buffer and coverage metadata path."},
        {OptionKind::TraceCoverageBoolean,
         "-trace-coverage-boolean",
         nullptr,
         "Record boolean coverage instead of exact execution counts: each counter slot "
         "is written with 1 (via a plain non-atomic store) whenever its entry executes, "
         "rather than atomically incremented per execution. This removes all atomic "
         "contention, so coverage is dramatically faster and avoids the GPU watchdog "
         "timeouts that heavy per-execution counting can trigger, at the cost of exact "
         "counts (the counter is 0 or non-zero). Off by default. Ignored when no coverage "
         "mode is enabled."},
        {OptionKind::TraceCoverageBinding,
         "-trace-coverage-binding",
         "-trace-coverage-binding <index> <space>",
         "Bind the synthesized `__slang_coverage` buffer at an explicit "
         "(register index, space) instead of auto-allocating a slot. "
         "Useful when the host needs the binding fixed at compile time "
         "before any host metadata reads run. Implies `-trace-coverage`."},
        {OptionKind::TraceCoverageReservedSpace,
         "-trace-coverage-reserved-space",
         "-trace-coverage-reserved-space <space>",
         "Reserve a descriptor set when auto-allocating the "
         "synthesized `__slang_coverage` buffer. Use this when the host "
         "pipeline layout owns descriptor sets that are "
         "not visible in the compiled shader IR. Repeat for multiple spaces; "
         "duplicates are idempotent. Applies to Khronos descriptor-set targets."},
        {OptionKind::CoverageManifestOutput,
         "-coverage-manifest-output",
         "-coverage-manifest-output <path>",
         "Write shader coverage manifest metadata to an explicit JSON sidecar path. "
         "Use this when compiled output is written to stdout or when the build needs "
         "a stable manifest path instead of the default "
         "`<output>.coverage-manifest.json` sidecar. Requires at least one coverage tracing mode, "
         "is not supported for container outputs, and is valid only when exactly one compiled "
         "artifact carries coverage metadata. The path must not overlap any emitted artifact "
         "path."},
        {OptionKind::TraceCoverageCounterByteWidth,
         "-trace-coverage-counter-width",
         "-trace-coverage-counter-width <bits>",
         "Per-slot bit width of the synthesized `__slang_coverage` buffer. "
         "Accepts `64` (default) or `32`. uint64 counters effectively cannot wrap "
         "within any practical run; uint32 counters wrap silently at 2^32 hits per "
         "slot. Use `32` when targeting a runtime driver that does not support "
         "64-bit shader atomic add (notably MoltenVK on Apple Silicon, which "
         "exposes `shaderBufferInt64Atomics = false`). Metal targets (`metal`, "
         "`metallib`, `metallib-asm`): MSL provides no 64-bit atomic fetch-add, so "
         "counting-mode counters are capped to 32 bits; an explicitly requested "
         "`64` is capped with warning E45115. Boolean coverage "
         "(`-trace-coverage-boolean`) writes plain non-atomic stores and honors "
         "the requested width on all targets. "
         "Implies `-trace-coverage` is meaningful; ignored when no coverage mode "
         "is enabled."},
        {OptionKind::ReportDynamicDispatchSites,
         "-report-dynamic-dispatch-sites",
         nullptr,
         "Reports information about dynamic dispatch sites for interface calls."},
        {OptionKind::SkipSPIRVValidation,
         "-skip-spirv-validation",
         nullptr,
         "Skips spirv validation."},
        {OptionKind::SourceEmbedStyle,
         "-source-embed-style",
         "-source-embed-style <source-embed-style>",
         "If source embedding is enabled, defines the style used. When enabled (with any style "
         "other than `none`), "
         "will write compile results into embeddable source for the target language. "
         "If no output file is specified, the output is written to stdout. If an output file is "
         "specified "
         "it is written either to that file directly (if it is appropriate for the target "
         "language), "
         "or it will be output to the filename with an appropriate extension.\n\n"
         "Note for C/C++ with u16/u32/u64 types it is necessary to have \"#include <stdint.h>\" "
         "before the generated file.\n"},
        {OptionKind::SourceEmbedName,
         "-source-embed-name",
         "-source-embed-name <name>",
         "The name used as the basis for variables output for source embedding."},
        {OptionKind::SourceEmbedLanguage,
         "-source-embed-language",
         "-source-embed-language <language>",
         "The language to be used for source embedding. Defaults to C/C++. Currently only C/C++ "
         "are supported"},
        {OptionKind::DisableShortCircuit,
         "-disable-short-circuit",
         nullptr,
         "Disable short-circuiting for \"&&\" and \"||\" operations"},
        {OptionKind::UnscopedEnum,
         "-unscoped-enum",
         nullptr,
         "Treat enum types as unscoped by default. (Note: enum class remains scoped.)"},
        {OptionKind::PreserveParameters,
         "-preserve-params",
         nullptr,
         "Preserve all resource parameters in the output code, even if they are not used by the "
         "shader."},
        {OptionKind::TypeConformance,
         "-conformance",
         "-conformance <typeName>:<interfaceName>[=<sequentialID>]",
         "Include additional type conformance during linking for dynamic dispatch."},
        {OptionKind::EmitReflectionJSON,
         "-reflection-json",
         "-reflection-json <path>",
         "Emit reflection data in JSON format to a file."},
        {OptionKind::UseMSVCStyleBitfieldPacking,
         "-msvc-style-bitfield-packing",
         nullptr,
         "Pack bitfields according to MSVC rules (msb first, new field when underlying type size "
         "changes) rather than gcc-style (lsb first)"}};

    _addOptions(makeConstArrayView(generalOpts), options);

    /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Target !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

    options.setCategory("Target");

    StringBuilder vkShiftNames;
    {
        for (auto nameSlice : NameValueUtil::getNames(
                 NameValueUtil::NameKind::All,
                 HLSLToVulkanLayoutOptions::getKindInfos()))
        {
            // -fvk-{b|s|t|u}-shift
            vkShiftNames << "-fvk-" << nameSlice << "-shift,";
        }
        // remove last ,
        vkShiftNames.reduceLength(vkShiftNames.getLength() - 1);
    }

    static const CommandOptions::InputLink kVulkanBindShiftLinks[] = {
        {"DXC description",
         "https://github.com/Microsoft/DirectXShaderCompiler/blob/main/docs/"
         "SPIR-V.rst#implicit-binding-number-assignment"},
        {"GLSL wiki",
         "https://github.com/KhronosGroup/glslang/wiki/"
         "HLSL-FAQ#auto-mapped-binding-numbers"},
    };

    static const CommandOptions::InputLink kVulkanBindGlobalsLinks[] = {
        {"DXC description",
         "https://github.com/Microsoft/DirectXShaderCompiler/blob/main/docs/"
         "SPIR-V.rst#implicit-binding-number-assignment"},
    };

    const Option targetOpts[] = {
        {OptionKind::Capability,
         "-capability",
         "-capability <capability>[+<capability>...]",
         "Add optional capabilities to a code generation target. See Capabilities below."},
        {OptionKind::DefaultImageFormatUnknown,
         "-default-image-format-unknown",
         nullptr,
         "Set the format of R/W images with unspecified format to 'unknown'. Otherwise try to "
         "guess the format."},
        {OptionKind::DisableDynamicDispatch,
         "-disable-dynamic-dispatch",
         nullptr,
         "Disables generating dynamic dispatch code."},
        {OptionKind::DisableSpecialization,
         "-disable-specialization",
         nullptr,
         "Disables generics and specialization pass."},
        {OptionKind::FloatingPointMode,
         "-fp-mode,-floating-point-mode",
         "-fp-mode <fp-mode>, -floating-point-mode <fp-mode>",
         "Control floating point optimizations"},
        {OptionKind::DenormalModeFp16,
         "-denorm-mode-fp16",
         "-denorm-mode-fp16 <fp-denormal-mode>",
         "Control handling of 16-bit denormal floating point values in SPIR-V (any, preserve, "
         "ftz)"},
        {OptionKind::DenormalModeFp32,
         "-denorm-mode-fp32",
         "-denorm-mode-fp32 <fp-denormal-mode>",
         "Control handling of 32-bit denormal floating point values in SPIR-V and DXIL (any, "
         "preserve, ftz)"},
        {OptionKind::DenormalModeFp64,
         "-denorm-mode-fp64",
         "-denorm-mode-fp64 <fp-denormal-mode>",
         "Control handling of 64-bit denormal floating point values in SPIR-V (any, preserve, "
         "ftz)"},
        {OptionKind::DebugInformation,
         "-g...",
         "-g, -g<debug-info-format>, -g<debug-level>",
         "Include debug information in the generated code, where possible.\n"
         "<debug-level> is the amount of information, 0..3, unspecified means 2\n"
         "<debug-info-format> specifies a debugging info format\n"
         "It is valid to have multiple -g options, such as a <debug-level> and a "
         "<debug-info-format>"},
        {OptionKind::LineDirectiveMode,
         "-line-directive-mode",
         "-line-directive-mode <line-directive-mode>",
         "Sets how the `#line` directives should be produced. Available options are:\n"
         "If not specified, default behavior is to use C-style `#line` directives "
         "for HLSL and C/C++ output, and traditional GLSL-style `#line` directives "
         "for GLSL output."},
        {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,-fvk-use-scalar-layout",
         nullptr,
         "Make data accessed through ConstantBuffer, ParameterBlock, StructuredBuffer, "
         "ByteAddressBuffer and general pointers follow the 'scalar' layout when targeting GLSL or "
         "SPIRV."},
        {OptionKind::ForceDXLayout,
         "-fvk-use-dx-layout",
         nullptr,
         "Pack members using FXCs member packing rules when targeting GLSL or SPIRV."},
        {OptionKind::ForceCLayout,
         "-fvk-use-c-layout",
         nullptr,
         "Make data accessed through ConstantBuffer, ParameterBlock, StructuredBuffer, "
         "ByteAddressBuffer and general pointers follow the C/C++ structure layout rules "
         "when targeting SPIRV."},
        {OptionKind::VulkanBindShift,
         vkShiftNames.getBuffer(),
         "-fvk-<vulkan-shift>-shift <N> <space>",
         "For example '-fvk-b-shift <N> <space>' shifts by N the inferred binding numbers for all "
         "resources in 'b' registers of space <space>. "
         "For a resource 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>.",
         "-fvk-<vulkan-shift>-shift",
         kVulkanBindShiftLinks,
         SLANG_COUNT_OF(kVulkanBindShiftLinks)},
        {OptionKind::VulkanBindGlobals,
         "-fvk-bind-globals",
         "-fvk-bind-globals <N> <descriptor-set>",
         "Places the $Globals cbuffer at descriptor set <descriptor-set> and binding <N>.\n"
         "It lets you specify the descriptor for the source at a certain register.",
         nullptr,
         kVulkanBindGlobalsLinks,
         SLANG_COUNT_OF(kVulkanBindGlobalsLinks)},
        {OptionKind::VulkanInvertY,
         "-fvk-invert-y",
         nullptr,
         "Negates (additively inverts) SV_Position.y before writing to stage output."},
        {OptionKind::VulkanUseDxPositionW,
         "-fvk-use-dx-position-w",
         nullptr,
         "Reciprocates (multiplicatively inverts) SV_Position.w after reading from stage input. "
         "For use in fragment shaders only."},
        {OptionKind::VulkanUseEntryPointName,
         "-fvk-use-entrypoint-name",
         nullptr,
         "Uses the entrypoint name from the source instead of 'main' in the spirv output."},
        {OptionKind::VulkanUseGLLayout,
         "-fvk-use-gl-layout",
         nullptr,
         "Use std430 layout instead of D3D buffer layout for raw buffer load/stores."},
        {OptionKind::VulkanEmitReflection,
         "-fspv-reflect",
         nullptr,
         "Include reflection decorations in the resulting SPIRV for shader parameters."},
        {OptionKind::EnableEffectAnnotations,
         "-enable-effect-annotations",
         nullptr,
         "Enables support for legacy effect annotation syntax."},
        {OptionKind::EmitSpirvViaGLSL,
         "-emit-spirv-via-glsl",
         nullptr,
         "Generate SPIR-V output by compiling generated GLSL with glslang"},
        {OptionKind::EmitSpirvDirectly,
         "-emit-spirv-directly",
         nullptr,
         "Generate SPIR-V output directly (default)"},
        {OptionKind::SPIRVCoreGrammarJSON,
         "-spirv-core-grammar",
         nullptr,
         "A path to a specific spirv.core.grammar.json to use when generating SPIR-V output"},
        {OptionKind::IncompleteLibrary,
         "-incomplete-library",
         nullptr,
         "Allow generating code from incomplete libraries with unresolved external functions"},
        {OptionKind::BindlessSpaceIndex,
         "-bindless-space-index",
         "-bindless-space-index <index>",
         "Specify the space index for the system defined global bindless resource array."},
        {OptionKind::SPIRVResourceHeapStride,
         "-spirv-resource-heap-stride",
         "-spirv-resource-heap-stride <stride>",
         "Specify the byte stride for the resource descriptor heap when generating SPIRV with "
         "spvDescriptorHeapEXT. Defaults to 0, which will use OpConstantSizeOfEXT(ResourceType); "
         "for RaytracingAccelerationStructure entries, the 0 default emits a literal 8-byte "
         "ArrayStride for the uint64 device address elements. An explicit stride value still "
         "overrides these defaults; for acceleration-structure entries it must be at least 8 "
         "bytes."},
        {OptionKind::SPIRVSamplerHeapStride,
         "-spirv-sampler-heap-stride",
         "-spirv-sampler-heap-stride <stride>",
         "Specify the byte stride for the sampler descriptor heap when generating SPIRV with "
         "spvDescriptorHeapEXT. Defaults to 0, which will use OpConstantSizeOfEXT(OpTypeSampler)."},
        {OptionKind::SPIRVUnifiedDescriptorHeapStride,
         "-spirv-unified-descriptor-heap-stride",
         nullptr,
         "When generating SPIRV with spvDescriptorHeapEXT, emit each resource descriptor-heap "
         "runtime array's ArrayStride as the maximum of image and buffer descriptor sizes, so "
         "a single heap shared by buffers and images is indexed at the device's unified stride. "
         "Only affects the default OpConstantSizeOfEXT path (used when -spirv-resource-heap-stride "
         "is 0); mutually exclusive with a non-zero -spirv-resource-heap-stride (combining the two "
         "is an error). Does not affect the sampler heap or acceleration-structure entries."},
        {OptionKind::EmitSeparateDebug,
         "-separate-debug-info",
         nullptr,
         "Emit debug data to a separate file, and strip it from the main output file. By default, "
         "the debug file path is derived from the main `-o <path>` output as a fallback. Use "
         "`-separate-debug-info-output <path>` to override it or when the main artifact is written "
         "to stdout."},
        {OptionKind::SeparateDebugInfoOutput,
         "-separate-debug-info-output",
         "-separate-debug-info-output <path>",
         "Write separate debug information to an explicit sidecar path, overriding the fallback "
         "path derived from `-o <path>`. Requires `-separate-debug-info` and allows the main "
         "artifact to be written to stdout. Use `-` to write the separate debug information to "
         "stdout when the main artifact is written to a file."},
        {OptionKind::DebugInfoIncludeSource,
         "-debug-info-include-source",
         nullptr,
         "Embed the shader source text into the debug information, independently of the `-g` "
         "debug level. At `-g1` the source is embedded via the core SPIR-V `OpSource` "
         "instruction (no NonSemantic extension required); at `-g2`/`-g3` the source is already "
         "embedded so this is a no-op. Requires debug information: using it with `-g0`, or without "
         "any `-g` option, is an error. Only affects SPIR-V output."},
        {OptionKind::EmitCPUViaCPP,
         "-emit-cpu-via-cpp",
         nullptr,
         "Generate CPU targets using C++ (default)"},
        {OptionKind::EmitCPUViaLLVM,
         "-emit-cpu-via-llvm",
         nullptr,
         "Generate CPU targets using LLVM"},
        {OptionKind::LLVMTargetTriple,
         "-llvm-target-triple",
         "-llvm-target-triple <target triple>",
         "Sets the target triple for the LLVM target, enabling cross "
         "compilation. The default value is the host platform."},
        {OptionKind::LLVMCPU,
         "-llvm-cpu",
         "-llvm-cpu <cpu name>",
         "Sets the target CPU for the LLVM target, enabling the extensions and "
         "features of that CPU. The default value is \"generic\"."},
        {OptionKind::LLVMFeatures,
         "-llvm-features",
         "-llvm-features <a1,+enable,-disable,...>",
         "Sets a comma-separates list of architecture-specific features for the LLVM targets."},
    };

    _addOptions(makeConstArrayView(targetOpts), options);

    /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Downstream !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

    options.setCategory("Downstream");

    {
        auto namesList = NameValueUtil::getNames(
            NameValueUtil::NameKind::First,
            TypeTextUtil::getCompilerInfos());
        StringBuilder names;
        for (auto name : namesList)
        {
            names << "-" << name << "-path,";
        }
        // remove last ,
        names.reduceLength(names.getLength() - 1);

        options.add(
            names.getBuffer(),
            "-<compiler>-path <path>",
            "Specify path to a downstream <compiler> "
            "executable or library.\n",
            UserValue(OptionKind::CompilerPath),
            "-<compiler>-path");
    }

    {
        auto namesList = NameValueUtil::getNames(
            NameValueUtil::NameKind::First,
            TypeTextUtil::getCompilerInfos());
        StringBuilder names;
        for (auto name : namesList)
        {
            names << "-" << name << "-version,";
        }
        // remove last ,
        names.reduceLength(names.getLength() - 1);

        options.add(
            names.getBuffer(),
            "-<compiler>-version",
            "Print the version of the downstream <compiler> that Slang would load for that "
            "pass-through, then continue. Reports \"not found\" if the compiler cannot be "
            "located. Takes no value.\n",
            UserValue(OptionKind::CompilerVersion),
            "-<compiler>-version");
    }

    const Option downstreamOpts[] = {
        {OptionKind::DefaultDownstreamCompiler,
         "-default-downstream-compiler",
         "-default-downstream-compiler <language> <compiler>",
         "Set a default compiler for the given language. See -lang for the list of languages."},
        {OptionKind::DownstreamArgs,
         "-X...",
         "-X<compiler> <option> -X<compiler>... <options> -X.",
         "Pass arguments to downstream <compiler>. Just -X<compiler> passes just the next argument "
         "to the downstream compiler. -X<compiler>... options -X. will pass *all* of the options "
         "inbetween the opening -X and -X. to the downstream compiler."},
        {OptionKind::PassThrough,
         "-pass-through",
         "-pass-through <compiler>",
         "Pass the input through mostly unmodified to the "
         "existing compiler <compiler>.\n"
         "These are intended for debugging/testing purposes, when you want to be able to see what "
         "these existing compilers do with the \"same\" input and options"},
    };

    _addOptions(makeConstArrayView(downstreamOpts), options);

    /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Repro !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

    options.setCategory("Repro");

    const Option reproOpts[] = {
        {OptionKind::DumpReproOnError,
         "-dump-repro-on-error",
         nullptr,
         "Dump `.slang-repro` file on any compilation error."},
        {OptionKind::ExtractRepro,
         "-extract-repro",
         "-extract-repro <name>",
         "Extract the repro files into a folder."},
        {OptionKind::LoadReproDirectory,
         "-load-repro-directory",
         "-load-repro-directory <path>",
         "Use repro along specified path"},
        {OptionKind::LoadRepro, "-load-repro", "-load-repro <name>", "Load repro"},
        {OptionKind::ReproFileSystem,
         "-repro-file-system",
         "-repro-file-system <name>",
         "Use a repro as a file system"},
        {OptionKind::DumpRepro,
         "-dump-repro",
         nullptr,
         "Dump a `.slang-repro` file that can be used to reproduce "
         "a compilation on another machine.\n"},
        {OptionKind::ReproFallbackDirectory,
         "-repro-fallback-directory <path>",
         "Specify a directory to use if a file isn't found in a repro. Should be specified "
         "*before* any repro usage such as `load-repro`. \n"
         "There are two *special* directories: \n\n"
         " * 'none:' indicates no fallback, so if the file isn't found in the repro compliation "
         "will fail\n"
         " * 'default:' is the default (which is the OS file system)"}};

    _addOptions(makeConstArrayView(reproOpts), options);

    /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Debugging !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

    options.setCategory("Debugging");

    const Option debuggingOpts[] = {
        {OptionKind::DumpAst,
         "-dump-ast",
         nullptr,
         "Dump the AST to a .slang-ast file next to the input."},
        {OptionKind::DumpIntermediatePrefix,
         "-dump-intermediate-prefix",
         "-dump-intermediate-prefix <prefix>",
         "File name prefix for -dump-intermediates outputs, default is no prefix"},
        {OptionKind::DumpIntermediates,
         "-dump-intermediates",
         nullptr,
         "Dump intermediate outputs for debugging."},
        {OptionKind::DumpIr, "-dump-ir", nullptr, "Dump the IR after every pass for debugging."},
        {OptionKind::DumpIrIds,
         "-dump-ir-ids",
         nullptr,
         "Dump the IDs with -dump-ir (debug builds only)"},
        {OptionKind::PreprocessorOutput,
         "-E,-output-preprocessor",
         nullptr,
         "Output the preprocessing result and exit."},
        {OptionKind::NoCodeGen,
         "-no-codegen",
         nullptr,
         "Skip the code generation step, just check the code and generate layout."},
        {OptionKind::OutputIncludes,
         "-output-includes",
         nullptr,
         "Print the hierarchy of the processed source files."},
        {OptionKind::REMOVED_SerialIR,
         "-serial-ir",
         nullptr,
         "[REMOVED] Serialize the IR between front-end and back-end."},
        {OptionKind::SkipCodeGen, "-skip-codegen", nullptr, "Skip the code generation phase."},
        {OptionKind::ValidateIr,
         "-validate-ir",
         nullptr,
         "Validate the IR after select intermediate passes."},
        {OptionKind::ValidateIRDetailed,
         "-validate-ir-detailed",
         nullptr,
         "Perform debug validation on IR after each intermediate pass."},
        {OptionKind::DumpIRBefore,
         "-dump-ir-before",
         "-dump-ir-before <pass-names>",
         "Dump IR before specified pass, may be specified more than once"},
        {OptionKind::DumpIRAfter,
         "-dump-ir-after",
         "-dump-ir-after <pass-names>",
         "Dump IR after specified pass, may be specified more than once"},
        {OptionKind::VerbosePaths,
         "-verbose-paths",
         nullptr,
         "When displaying diagnostic output aim to display more detailed path information. "
         "In practice this is typically the complete 'canonical' path to the source file used."},
        {OptionKind::VerifyDebugSerialIr,
         "-verify-debug-serial-ir",
         nullptr,
         "Verify IR in the front-end."},
        {OptionKind::DumpModule, "-dump-module", nullptr, "Disassemble and print the module IR."},
        {OptionKind::GetModuleInfo,
         "-get-module-info",
         nullptr,
         "Print the name and version of a serialized IR Module"},
        {OptionKind::GetSupportedModuleVersions,
         "-get-supported-module-versions",
         nullptr,
         "Print the minimum and maximum module versions this compiler supports"}};
    _addOptions(makeConstArrayView(debuggingOpts), options);

    /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Experimental !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

    options.setCategory("Experimental");

    const Option experimentalOpts[] = {
        {OptionKind::FileSystem,
         "-file-system",
         "-file-system <file-system-type>",
         "Set the filesystem hook to use for a compile request."},
        {OptionKind::Heterogeneous,
         "-heterogeneous",
         nullptr,
         "Output heterogeneity-related code."},
        {OptionKind::NoMangle,
         "-no-mangle",
         nullptr,
         "Do as little mangling of names as possible."},
        {OptionKind::NoHLSLBinding,
         "-no-hlsl-binding",
         nullptr,
         "Do not include explicit parameter binding semantics in the output HLSL code,"
         "except for parameters that has explicit bindings in the input source."},
        {OptionKind::NoHLSLPackConstantBufferElements,
         "-no-hlsl-pack-constant-buffer-elements",
         nullptr,
         "Do not pack elements of constant buffers into structs in the output HLSL code."},
        {OptionKind::ValidateUniformity,
         "-validate-uniformity",
         nullptr,
         "Perform uniformity validation analysis."},
        {OptionKind::AllowGLSL, "-allow-glsl", nullptr, "Enable GLSL as an input language."},
        {OptionKind::EnableExperimentalPasses,
         "-enable-experimental-passes",
         nullptr,
         "Enable experimental compiler passes"},
        {OptionKind::EnableExperimentalDynamicDispatch,
         "-enable-experimental-dynamic-dispatch",
         nullptr,
         "Enable experimental dynamic dispatch features"},
        {OptionKind::EmbedDownstreamIR,
         "-embed-downstream-ir",
         nullptr,
         "Embed downstream IR into emitted slang IR"},
        {OptionKind::ExperimentalFeature,
         "-experimental-feature",
         nullptr,
         "Enable experimental language and module features"},
        {OptionKind::EnableRichDiagnostics,
         "-enable-experimental-rich-diagnostics",
         nullptr,
         "Enable experimental rich diagnostics with enhanced formatting and details"},
        {OptionKind::EnableMachineReadableDiagnostics,
         "-enable-machine-readable-diagnostics",
         nullptr,
         "Enable machine-readable diagnostic output in tab-separated format"},
        {OptionKind::DiagnosticColor,
         "-diagnostic-color",
         "-diagnostic-color <always|never|auto>",
         "Control colored diagnostic output (auto uses color if stderr is a tty)"},
    };
    _addOptions(makeConstArrayView(experimentalOpts), options);

    /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Internal !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

    options.setCategory("Internal");

    const Option internalOpts[] = {
        {OptionKind::ArchiveType,
         "-archive-type",
         "-archive-type <archive-type>",
         "Set the archive type for -save-core-module. Default is zip."},
        {OptionKind::CompileCoreModule,
         "-compile-core-module",
         nullptr,
         "Compile the core module from embedded sources. "
         "Will return a failure if there is already a core module available."},
        {OptionKind::Doc, "-doc", nullptr, "Write documentation for -compile-core-module"},
        {OptionKind::IrCompression,
         "-ir-compression",
         "-ir-compression <type>",
         "Set compression for IR and AST outputs.\n"
         "Accepted compression types: none, lite"},
        {OptionKind::LoadCoreModule,
         "-load-core-module",
         "-load-core-module <filename>",
         "Load the core module from file."},
        {OptionKind::ReferenceModule, "-r", "-r <name>", "reference module <name>"},
        {OptionKind::SaveCoreModule,
         "-save-core-module",
         "-save-core-module <filename>",
         "Save the core module to an archive file."},
        {OptionKind::SaveCoreModuleBinSource,
         "-save-core-module-bin-source",
         "-save-core-module-bin-source <filename>",
         "Same as -save-core-module but output "
         "the data as a C array.\n"},
        {OptionKind::SaveGLSLModuleBinSource,
         "-save-glsl-module-bin-source",
         "-save-glsl-module-bin-source <filename>",
         "Save the serialized glsl module "
         "as a C array.\n"},
        {OptionKind::TrackLiveness,
         "-track-liveness",
         nullptr,
         "Enable liveness tracking. Places SLANG_LIVE_START, and SLANG_LIVE_END in output source "
         "to indicate value liveness."},
        {OptionKind::LoopInversion,
         "-loop-inversion",
         nullptr,
         "Enable loop inversion in the code-gen optimization. Default is off"},
        {OptionKind::GenerateWholeProgram,
         "-whole-program",
         nullptr,
         "Generate code for all entry points in a single output (library mode)."},
    };
    _addOptions(makeConstArrayView(internalOpts), options);

    /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Deprecated !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

    options.setCategory("Deprecated");

    const Option deprecatedOpts[] = {
        {OptionKind::ParameterBlocksUseRegisterSpaces,
         "-parameter-blocks-use-register-spaces",
         nullptr,
         "Parameter blocks will use register spaces"},
        {OptionKind::ZeroInitialize,
         "-zero-initialize",
         nullptr,
         "Initialize all variables to zero."
         "Structs will set all struct-fields without an init expression to 0."
         "All variables will call their default constructor if not explicitly initialized as "
         "usual."},
    };
    _addOptions(makeConstArrayView(deprecatedOpts), options);

    // We can now check that the whole range is available. If this fails it means there
    // is an enum in the list that hasn't been setup as an option!
    SLANG_ASSERT(options.hasContiguousUserValueRange(
        CommandOptions::LookupKind::Option,
        UserValue(0),
        UserValue(OptionKind::CountOfParsableOptions)));
    SLANG_ASSERT(options.hasContiguousUserValueRange(
        CommandOptions::LookupKind::Category,
        UserValue(0),
        UserValue(ValueCategory::CountOf)));
}

SlangResult _addLibraryReference(
    EndToEndCompileRequest* req,
    String path,
    IArtifact* artifact,
    bool includeEntryPoint);

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

struct OptionsParser
{
    // 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;
    };

    // 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;
        List<String> specializationArgs;
        // State for tracking command-line errors
        bool conflictingStagesSet = false;
        bool redundantStageSet = false;
    };

    struct RawOutput
    {
        String path;
        CodeGenTarget impliedFormat = CodeGenTarget::Unknown;
        int targetIndex = -1;
        int entryPointIndex = -1;
        bool isWholeProgram = false;
    };

    struct RawTarget
    {
        CodeGenTarget format = CodeGenTarget::Unknown;
        int targetID = -1;
        CompilerOptionSet optionSet;

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

    struct PendingBuiltinModuleSave
    {
        slang::BuiltinModuleName moduleName = slang::BuiltinModuleName::Core;
        SlangArchiveType archiveType = SLANG_ARCHIVE_TYPE_RIFF_LZ4;
        String fileName;
        bool writeAsSourceBytes = false;
    };

    int addTranslationUnit(SlangSourceLanguage language, Stage impliedStage);

    void addInputSlangPath(String const& path);

    void addInputForeignShaderPath(
        String const& path,
        SlangSourceLanguage language,
        Stage impliedStage);

    static Profile::RawVal findGlslProfileFromPath(const String& path);

    SlangResult addInputStdin(SlangSourceLanguage sourceLanguage);

    SlangResult addInputPath(
        char const* inPath,
        SourceLanguage langOverride = SourceLanguage::Unknown);

    void addOutputPath(String const& path, CodeGenTarget impliedFormat);

    void addOutputPath(char const* inPath);

    // Adds a built-in module save request to be written after optional core-module compilation.
    SlangResult addPendingBuiltinModuleSave(
        slang::BuiltinModuleName moduleName,
        bool writeAsSourceBytes);

    // Writes deferred built-in module saves, compiling each requested module if needed.
    // For example, GLSL serialization after loading core reuses that archive.
    SlangResult writePendingBuiltinModuleSaves();
    RawEntryPoint* getCurrentEntryPoint();

    void setStage(RawEntryPoint* rawEntryPoint, Stage stage);

    RawTarget* getCurrentTarget();
    void setProfileVersion(RawTarget* rawTarget, ProfileVersion profileVersion);
    void setProfile(RawTarget* rawTarget, Profile profile);
    void addCapabilityAtom(RawTarget* rawTarget, CapabilityName atom);

    void setFloatingPointMode(RawTarget* rawTarget, FloatingPointMode mode);

    SlangResult parse(SlangCompileRequest* compileRequest, int argc, char const* const* argv);

    SlangResult _parse(int argc, char const* const* argv);

    static bool _passThroughRequiresStage(PassThroughMode passThrough);

    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)
    {
        CommandOptions::UserValue value;
        SLANG_RETURN_ON_FAIL(
            _getValue(ValueCategory(GetValueCategory<T>::Value), arg, name, value));
        ioValue = T(value);
        return SLANG_OK;
    }

    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);
    SlangResult _expectUInt(const CommandLineArg& arg, Int& outInt);


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

    void _appendUsageTitle(StringBuilder& out);
    void _appendMinimalUsage(StringBuilder& out);
    void _outputMinimalUsage();

    SlangResult addReferencedModule(String path, SourceLoc loc, bool includeEntryPoint);
    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);
    SlangResult _readStdin(List<Byte>& outSource);

    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;

    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;

    int m_translationUnitCount = 0;
    int m_currentTranslationUnitIndex = -1;

    bool m_stdinConsumed = false;
    bool m_hasLoadedRepro = false;
    bool m_compileCoreModule = false;
    slang::CompileCoreModuleFlags m_compileCoreModuleFlags = 0;
    List<PendingBuiltinModuleSave> m_pendingBuiltinModuleSaves;

    SlangArchiveType m_archiveType = SLANG_ARCHIVE_TYPE_RIFF_LZ4;

    List<RawOutput> m_rawOutputs;

    DiagnosticSink m_parseSink;
    DiagnosticSink* m_sink = nullptr;

    FrontEndCompileRequest* m_frontEndReq = nullptr;

    CommandLineReader m_reader;

    CommandOptionsWriter::Style m_helpStyle = CommandOptionsWriter::Style::Text;

    CommandOptions* m_cmdOptions = nullptr;
    CommandLineContext* m_cmdLineContext = nullptr;

    // Tracks the currently-being-parsed option name (e.g. "-line-directive-mode")
    // so that diagnostics like UnknownCommandLineValue can mention it.
    String m_currentOptionName;
};

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)
    {
        m_translationUnitCount++;
        m_slangTranslationUnitIndex =
            addTranslationUnit(SLANG_SOURCE_LANGUAGE_SLANG, Stage::Unknown);
    }

    m_compileRequest->addTranslationUnitSourceFile(
        m_rawTranslationUnits[m_slangTranslationUnitIndex].translationUnitID,
        path.begin());

    // Set the translation unit to be used by subsequent entry points
    m_currentTranslationUnitIndex = m_slangTranslationUnitIndex;
}

void OptionsParser::addInputForeignShaderPath(
    String const& path,
    SlangSourceLanguage language,
    Stage impliedStage)
{
    m_translationUnitCount++;
    m_currentTranslationUnitIndex = addTranslationUnit(language, impliedStage);

    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[] = {
        {".vert", Profile::GLSL_Vertex},
        {".frag", Profile::GLSL_Fragment},
        {".geom", Profile::GLSL_Geometry},
        {".tesc", Profile::GLSL_TessControl},
        {".tese", Profile::GLSL_TessEval},
        {".comp", Profile::GLSL_Compute},
        {".mesh", Profile::GLSL_Mesh},
        {".task", Profile::GLSL_Task}};

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

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

    static const Entry entries[] = {
        {".slang.md", SLANG_SOURCE_LANGUAGE_SLANG, SLANG_STAGE_NONE},
        {".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},
        {".mesh", SLANG_SOURCE_LANGUAGE_GLSL, SLANG_STAGE_MESH},
        {".task", SLANG_SOURCE_LANGUAGE_GLSL, SLANG_STAGE_AMPLIFICATION},
        {".rgen", SLANG_SOURCE_LANGUAGE_GLSL, SLANG_STAGE_RAY_GENERATION},
        {".rint", SLANG_SOURCE_LANGUAGE_GLSL, SLANG_STAGE_INTERSECTION},
        {".rahit", SLANG_SOURCE_LANGUAGE_GLSL, SLANG_STAGE_ANY_HIT},
        {".rchit", SLANG_SOURCE_LANGUAGE_GLSL, SLANG_STAGE_CLOSEST_HIT},
        {".rmiss", SLANG_SOURCE_LANGUAGE_GLSL, SLANG_STAGE_MISS},
        {".rcall", SLANG_SOURCE_LANGUAGE_GLSL, SLANG_STAGE_CALLABLE},

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

        {".wgsl", SLANG_SOURCE_LANGUAGE_WGSL, SLANG_STAGE_NONE},
    };

    for (Index 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 OptionsParser::_readStdin(List<Byte>& outSource)
{
    const StdinSourceReadResult readResult = _readStdinSource(stdin, kMaxStdinBytes, outSource);

    if (readResult == StdinSourceReadResult::CannotRead)
        outSource.clear();

    switch (readResult)
    {
    case StdinSourceReadResult::Success:
        return SLANG_OK;
    case StdinSourceReadResult::TooLarge:
        m_sink->diagnose(Diagnostics::StdinInputTooLarge{});
        return SLANG_FAIL;
    case StdinSourceReadResult::CannotRead:
        m_sink->diagnose(Diagnostics::CannotReadFromStdin{});
        return SLANG_FAIL;
    }

    SLANG_UNREACHABLE("unexpected stdin read result");
}

SlangResult OptionsParser::addInputStdin(SlangSourceLanguage sourceLanguage)
{
    if (m_stdinConsumed)
    {
        m_sink->diagnose(Diagnostics::StdinInputAlreadyUsed{});
        return SLANG_FAIL;
    }

    if (sourceLanguage == SLANG_SOURCE_LANGUAGE_UNKNOWN)
    {
        m_sink->diagnose(Diagnostics::CannotDeduceSourceLanguage{.path = kStdinDisplayPath});
        return SLANG_FAIL;
    }

    m_stdinConsumed = true;

    List<Byte> source;
    SLANG_RETURN_ON_FAIL(_readStdin(source));

    if (sourceLanguage == SLANG_SOURCE_LANGUAGE_SLANG)
    {
        if (m_slangTranslationUnitIndex == -1)
        {
            m_translationUnitCount++;
            m_slangTranslationUnitIndex =
                addTranslationUnit(SLANG_SOURCE_LANGUAGE_SLANG, Stage::Unknown);
        }
        m_currentTranslationUnitIndex = m_slangTranslationUnitIndex;
    }
    else
    {
        m_translationUnitCount++;
        m_currentTranslationUnitIndex = addTranslationUnit(sourceLanguage, Stage::Unknown);
    }

    const char* sourceBegin =
        source.getCount() != 0 ? reinterpret_cast<const char*>(source.getBuffer()) : "";
    const char* sourceEnd = sourceBegin + source.getCount();
    m_compileRequest->addTranslationUnitSourceStringSpan(
        m_rawTranslationUnits[m_currentTranslationUnitIndex].translationUnitID,
        kStdinDisplayPath,
        sourceBegin,
        sourceEnd);

    return SLANG_OK;
}

SlangResult OptionsParser::addInputPath(char const* inPath, SourceLanguage langOverride)
{
    SlangSourceLanguage sourceLanguage = SlangSourceLanguage(langOverride);
    if (sourceLanguage == SLANG_SOURCE_LANGUAGE_UNKNOWN)
    {
        auto linkage = m_requestImpl->getLinkage();
        if (linkage->m_optionSet.hasOption(CompilerOptionName::Language))
        {
            sourceLanguage = linkage->m_optionSet.getEnumOption<SlangSourceLanguage>(
                CompilerOptionName::Language);
        }
    }

    if (strcmp(inPath, kStdinCommandLinePath) == 0)
        return addInputStdin(sourceLanguage);

    // look at the extension on the file name to determine
    // how we should handle it.
    String path = String(inPath);

    if (path.endsWith(".slang-module") || path.endsWith(".slang-lib"))
    {
        return addReferencedModule(path, SourceLoc(), false);
    }
    else if (
        path.endsWith(".slang") || hasLiterateFileExtension(path) ||
        langOverride == SourceLanguage::Slang)
    {
        // Plain old slang code
        addInputSlangPath(path);
        return SLANG_OK;
    }

    Stage impliedStage = Stage::Unknown;
    if (sourceLanguage == SLANG_SOURCE_LANGUAGE_UNKNOWN)
    {
        sourceLanguage = findSourceLanguageFromPath(path, impliedStage);
    }
    if (sourceLanguage == SLANG_SOURCE_LANGUAGE_UNKNOWN)
    {
        m_requestImpl->getSink()->diagnose(Diagnostics::CannotDeduceSourceLanguage{.path = inPath});
        return SLANG_FAIL;
    }

    addInputForeignShaderPath(path, sourceLanguage, impliedStage);

    return SLANG_OK;
}

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

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
        // We want to emit IR
        m_requestImpl->m_emitIr = true;

        // We want to write out in an artfact "container", that can hold multiple artifacts.
        m_compileRequest->setOutputContainerFormat(SLANG_CONTAINER_FORMAT_SLANG_MODULE);

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

SlangResult OptionsParser::addPendingBuiltinModuleSave(
    slang::BuiltinModuleName moduleName,
    bool writeAsSourceBytes)
{
    CommandLineArg fileName;
    SLANG_RETURN_ON_FAIL(m_reader.expectArg(fileName));

    PendingBuiltinModuleSave save;
    save.moduleName = moduleName;
    save.archiveType = m_archiveType;
    save.fileName = fileName.value;
    save.writeAsSourceBytes = writeAsSourceBytes;
    m_pendingBuiltinModuleSaves.add(save);
    return SLANG_OK;
}

SlangResult OptionsParser::writePendingBuiltinModuleSaves()
{
    for (const auto& save : m_pendingBuiltinModuleSaves)
    {
        ComPtr<ISlangBlob> blob;
        SlangResult saveResult =
            m_session->saveBuiltinModule(save.moduleName, save.archiveType, blob.writeRef());
        if (SLANG_FAILED(saveResult))
        {
            SLANG_RETURN_ON_FAIL(m_session->compileBuiltinModule(
                save.moduleName,
                save.moduleName == slang::BuiltinModuleName::Core ? m_compileCoreModuleFlags : 0));
            SLANG_RETURN_ON_FAIL(
                m_session->saveBuiltinModule(save.moduleName, save.archiveType, blob.writeRef()));
        }

        if (!save.writeAsSourceBytes)
        {
            SLANG_RETURN_ON_FAIL(File::writeAllBytes(
                save.fileName,
                blob->getBufferPointer(),
                blob->getBufferSize()));
            continue;
        }

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

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

        SLANG_RETURN_ON_FAIL(
            File::writeNativeText(save.fileName, builder.getBuffer(), builder.getLength()));
    }

    return SLANG_OK;
}

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)
    {
        rawEntryPoint->redundantStageSet = true;
        if (stage != rawEntryPoint->stage)
        {
            rawEntryPoint->conflictingStagesSet = true;
        }
    }
    rawEntryPoint->stage = stage;
}

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

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

        if (profileVersion != rawTarget->optionSet.getProfileVersion())
        {
            rawTarget->conflictingProfilesSet = true;
        }
    }
    rawTarget->optionSet.setProfileVersion(profileVersion);
}

void OptionsParser::setProfile(RawTarget* rawTarget, Profile profile)
{
    if (rawTarget->optionSet.getProfile() != Profile::Unknown)
    {
        rawTarget->redundantProfileSet = true;

        if (profile != rawTarget->optionSet.getProfile())
        {
            rawTarget->conflictingProfilesSet = true;
        }
    }
    rawTarget->optionSet.setProfile(profile);
}

void OptionsParser::addCapabilityAtom(RawTarget* rawTarget, CapabilityName atom)
{
    CapabilitySet capSet(atom);
    auto stageAtom = capSet.getUniquelyImpliedStageAtom();
    if (stageAtom != CapabilityAtom::Invalid)
    {
        Stage stage = getStageFromAtom(stageAtom);
        setStage(getCurrentEntryPoint(), stage);
    }
    rawTarget->optionSet.addCapabilityAtom(atom);
}

void OptionsParser::setFloatingPointMode(RawTarget* rawTarget, FloatingPointMode mode)
{
    rawTarget->optionSet.set(CompilerOptionName::FloatingPointMode, mode);
}

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

static SlangResult _loadReproBlobFromFile(
    const String& path,
    DiagnosticSink* sink,
    ISlangBlob** outBlob)
{
    RefPtr<FileStream> stream = new FileStream;
    SLANG_RETURN_ON_FAIL(
        stream->init(path, FileMode::Open, FileAccess::Read, FileShare::ReadWrite));

    List<Byte> streamData;
    auto readResult = StreamUtil::readAll(stream, streamData);
    if (SLANG_FAILED(readResult))
    {
        sink->diagnose(Diagnostics::UnableToReadRiff{});
        return readResult;
    }

    return ReproUtil::loadState(
        reinterpret_cast<const uint8_t*>(streamData.getBuffer()),
        streamData.getCount(),
        sink,
        outBlob);
}

static SlangResult _loadRepro(
    const String& path,
    DiagnosticSink* sink,
    EndToEndCompileRequest* request)
{
    ComPtr<ISlangBlob> reproBlob;
    SLANG_RETURN_ON_FAIL(_loadReproBlobFromFile(path, sink, reproBlob.writeRef()));

    auto requestState = const_cast<ReproUtil::RequestState*>(
        ReproUtil::getRequest(reproBlob->getBufferPointer(), reproBlob->getBufferSize()));
    MemoryOffsetBase base;
    base.set(const_cast<void*>(reproBlob->getBufferPointer()), reproBlob->getBufferSize());

    // If we can find a directory, that exists, we will set up a file system to load from that
    // directory
    ComPtr<ISlangFileSystem> optionalFileSystem;
    String dirPath;
    if (SLANG_SUCCEEDED(ReproUtil::calcDirectoryPathFromFilename(path, dirPath)))
    {
        SlangPathType pathType;
        if (SLANG_SUCCEEDED(Path::getPathType(dirPath, &pathType)) &&
            pathType == SLANG_PATH_TYPE_DIRECTORY)
        {
            optionalFileSystem = new RelativeFileSystem(OSFileSystem::getExtSingleton(), dirPath);
        }
    }

    SLANG_RETURN_ON_FAIL(ReproUtil::load(base, requestState, optionalFileSystem, request));

    return SLANG_OK;
}

SlangResult OptionsParser::_compileReproDirectory(
    SlangSession* session,
    EndToEndCompileRequest* originalRequest,
    const String& dir)
{
    auto stdOut = originalRequest->getWriter(WriterChannel::StdOutput);

    ReproPathVisitor visitor;
    Path::find(dir, nullptr, &visitor);

    for (auto filename : visitor.m_filenames)
    {
        // Create a fresh request
        ComPtr<slang::ICompileRequest> request;
        SLANG_ALLOW_DEPRECATED_BEGIN
        SLANG_RETURN_ON_FAIL(session->createCompileRequest(request.writeRef()));
        SLANG_ALLOW_DEPRECATED_END

        auto requestImpl = asInternal(request);

        // Copy over the fallback file system
        requestImpl->m_reproFallbackFileSystem = originalRequest->m_reproFallbackFileSystem;

        // Load the repro into it
        auto path = Path::combine(dir, filename);

        if (SLANG_FAILED(_loadRepro(path, m_sink, requestImpl)))
        {
            if (stdOut)
            {
                StringBuilder buf;
                buf << filename << " - Failed to load!\n";
            }
            continue;
        }

        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 OptionsParser::_dumpDiagnostics(Severity originalSeverity)
{
    // Get the diagnostics and dump them
    auto diagnosticsLookup = getDiagnosticsLookup();

    StringBuilder buf;

    for (const auto& diagnostic : diagnosticsLookup->getDiagnostics())
    {
        if (originalSeverity != Severity::Disable && diagnostic->severity != originalSeverity)
        {
            continue;
        }

        buf.clear();

        buf << diagnostic->id << " : ";
        NameConventionUtil::convert(
            NameStyle::Camel,
            UnownedStringSlice(diagnostic->name),
            NameConvention::LowerKabab,
            buf);
        buf << "\n";
        m_sink->diagnoseRaw(Severity::Note, buf.getUnownedSlice());
    }

    return SLANG_OK;
}

void OptionsParser::_appendUsageTitle(StringBuilder& out)
{
    out << "Usage: slangc [options...] [--] <input files>\n\n";
}

void OptionsParser::_outputMinimalUsage()
{
    // Output usage info
    StringBuilder buf;
    _appendMinimalUsage(buf);

    m_sink->diagnoseRaw(Severity::Note, buf.getUnownedSlice());
}

void OptionsParser::_appendMinimalUsage(StringBuilder& out)
{
    _appendUsageTitle(out);
    out << "For help: slangc -h\n";
}


SlangResult OptionsParser::_getValue(
    ValueCategory valueCategory,
    [[maybe_unused]] 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;
        }

        List<UnownedStringSlice> names;
        m_cmdOptions->getCategoryOptionNames(categoryIndex, names);

        StringBuilder buf;
        StringUtil::join(names.getBuffer(), names.getCount(), toSlice(", "), buf);

        m_sink->diagnose(Diagnostics::UnknownCommandLineValue{
            .option = m_currentOptionName,
            .validValues = buf});
        return SLANG_FAIL;
    }

    outValue = m_cmdOptions->getOptionAt(optionIndex).userValue;
    return SLANG_OK;
}

SlangResult OptionsParser::_getValue(
    ValueCategory valueCategory,
    const CommandLineArg& arg,
    CommandOptions::UserValue& outValue)
{
    return _getValue(valueCategory, arg, arg.value.getUnownedSlice(), outValue);
}

SlangResult OptionsParser::_getValue(
    const ConstArrayView<ValueCategory>& valueCategories,
    [[maybe_unused]] const CommandLineArg& arg,
    const UnownedStringSlice& name,
    ValueCategory& outCat,
    CommandOptions::UserValue& outValue)
{
    auto& cmdOptions = asInternal(m_session)->m_commandOptions;

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

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

    StringBuilder buf;
    StringUtil::join(names.getBuffer(), names.getCount(), toSlice(", "), buf);

    m_sink->diagnose(
        Diagnostics::UnknownCommandLineValue{.option = m_currentOptionName, .validValues = 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)))
    {
        m_sink->diagnose(Diagnostics::ExpectingAnInteger{
            .value = initArg.value,
            .location = arg.loc,
        });
        return SLANG_FAIL;
    }
    return SLANG_OK;
}

SlangResult OptionsParser::_expectUInt(const CommandLineArg& initArg, Int& outInt)
{
    SLANG_RETURN_ON_FAIL(_expectInt(initArg, outInt));
    if (outInt < 0)
    {
        m_sink->diagnose(Diagnostics::ExpectingAUnsignedInteger{
            .value = initArg.value,
            .parsedValue = outInt,
            .location = initArg.loc,
        });
        return SLANG_FAIL;
    }
    return SLANG_OK;
}

SlangResult createArtifactFromReferencedModule(
    String path,
    SourceLoc loc,
    DiagnosticSink* sink,
    IArtifact** outArtifact)
{
    SLANG_UNUSED(loc);
    auto desc = ArtifactDescUtil::getDescFromPath(path.getUnownedSlice());

    if (desc.kind == ArtifactKind::Unknown)
    {
        sink->diagnose(Diagnostics::UnknownLibraryKind{.kind = Path::getPathExt(path)});
        return SLANG_FAIL;
    }

    // If it's a GPU binary, then we'll assume it's a library
    if (ArtifactDescUtil::isGpuUsable(desc))
    {
        desc.kind = ArtifactKind::Library;
    }

    // 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(Diagnostics::KindNotLinkable{.kind = Path::getPathExt(path)});
        return SLANG_FAIL;
    }

    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)
    {
        // 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(Diagnostics::LibraryDoesNotExist{.path = path});
            return SLANG_FAIL;
        }
    }
    artifact->addRepresentation(fileRep);
    *outArtifact = artifact.detach();
    return SLANG_OK;
}

SlangResult OptionsParser::addReferencedModule(String path, SourceLoc loc, bool includeEntryPoint)
{
    ComPtr<IArtifact> artifact;
    SLANG_RETURN_ON_FAIL(
        createArtifactFromReferencedModule(path, loc, m_sink, artifact.writeRef()));

    SLANG_RETURN_ON_FAIL(_addLibraryReference(m_requestImpl, path, artifact, includeEntryPoint));
    for (Index i = m_rawTranslationUnits.getCount(); i < m_requestImpl->getTranslationUnitCount();
         i++)
    {
        RawTranslationUnit rawTU;
        rawTU.translationUnitID = (int)i;
        rawTU.impliedStage = Stage::Unknown;
        rawTU.sourceLanguage = SLANG_SOURCE_LANGUAGE_SLANG;
        m_rawTranslationUnits.add(rawTU);
    }
    m_currentTranslationUnitIndex = m_requestImpl->getTranslationUnitCount() - 1;
    m_slangTranslationUnitIndex = m_currentTranslationUnitIndex;
    return SLANG_OK;
}

SlangResult OptionsParser::_parseReferenceModule(const CommandLineArg& arg)
{
    SLANG_UNUSED(arg);

    CommandLineArg referenceModuleName;
    SLANG_RETURN_ON_FAIL(m_reader.expectArg(referenceModuleName));

    // Add the module to the request
    SLANG_RETURN_ON_FAIL(
        addReferencedModule(referenceModuleName.value, referenceModuleName.loc, true));

    // In addition to adding the module to the request, we also add to the options set, because
    // the same options parser is also used for IGlobalSession::parseCommandLineArguments, which
    // parses options via a dummy request that is destroyed once the command line options are
    // obtained. Therefore, also add the option here so that
    // IGlobalSession::parseCommandLineArguments can return them.
    m_requestImpl->getLinkage()->m_optionSet.add(
        CompilerOptionName::ReferenceModule,
        referenceModuleName.value);

    return SLANG_OK;
}

SlangResult OptionsParser::_parseReproFileSystem(const CommandLineArg& arg)
{
    SLANG_UNUSED(arg);

    CommandLineArg reproName;
    SLANG_RETURN_ON_FAIL(m_reader.expectArg(reproName));

    ComPtr<ISlangBlob> reproBlob;
    {
        const Result res = _loadReproBlobFromFile(reproName.value, m_sink, reproBlob.writeRef());
        if (SLANG_FAILED(res))
        {
            m_sink->diagnose(
                Diagnostics::UnableToReadFile{.path = reproName.value, .location = reproName.loc});
            return res;
        }
    }

    auto requestState = const_cast<ReproUtil::RequestState*>(
        ReproUtil::getRequest(reproBlob->getBufferPointer(), reproBlob->getBufferSize()));
    MemoryOffsetBase base;
    base.set(const_cast<void*>(reproBlob->getBufferPointer()), reproBlob->getBufferSize());

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

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

SlangResult OptionsParser::_parseHelp(const CommandLineArg& arg)
{
    SLANG_UNUSED(arg);

    Index categoryIndex = -1;

    if (m_reader.hasArg())
    {
        auto catArg = m_reader.getArgAndAdvance();

        categoryIndex = m_cmdOptions->findCategoryByName(catArg.value.getUnownedSlice());

        if (categoryIndex < 0)
        {
            categoryIndex =
                m_cmdOptions->findCategoryByCaseInsensitiveName(catArg.value.getUnownedSlice());
        }

        if (categoryIndex < 0)
        {
            m_sink->diagnose(Diagnostics::UnknownHelpCategory{});
            return SLANG_FAIL;
        }
    }

    CommandOptionsWriter::Options writerOptions;
    writerOptions.style = m_helpStyle;

    auto writer = CommandOptionsWriter::create(writerOptions);

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

    // Write help text to stdout
    FileWriter stdoutWriter(stdout, WriterFlag::IsUnowned);
    stdoutWriter.write(buf.getBuffer(), buf.getLength());

    return SLANG_OK;
}

SlangResult OptionsParser::_parseLoadRepro(const CommandLineArg& arg)
{
    SLANG_UNUSED(arg);

    CommandLineArg reproName;
    SLANG_RETURN_ON_FAIL(m_reader.expectArg(reproName));

    if (SLANG_FAILED(_loadRepro(reproName.value, m_sink, m_requestImpl)))
    {
        m_sink->diagnose(
            Diagnostics::UnableToReadFile{.path = reproName.value, .location = reproName.loc});
        return SLANG_FAIL;
    }

    m_hasLoadedRepro = true;
    return SLANG_OK;
}

SlangResult OptionsParser::_parseDebugInformation(const CommandLineArg& arg)
{
    auto name = arg.value.getUnownedSlice().tail(2);

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

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


SlangResult OptionsParser::_parseProfile(const CommandLineArg& arg)
{
    SLANG_UNUSED(arg);

    // 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(m_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)
    {
        m_sink->diagnose(
            Diagnostics::UnknownProfile{.profile = profileName, .location = operand.loc});
        return SLANG_FAIL;
    }
    else
    {
        auto profile = Profile(profileID);

        setProfile(this->getCurrentTarget(), profile);

        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];
        CapabilityName atom = findCapabilityName(atomName);
        if (atom == CapabilityName::Invalid)
        {
            m_sink->diagnose(
                Diagnostics::UnknownProfile{.profile = atomName, .location = operand.loc});
            return SLANG_FAIL;
        }

        addCapabilityAtom(getCurrentTarget(), atom);
    }

    return SLANG_OK;
}

SlangResult OptionsParser::_parse(int argc, char const* const* argv)
{
    // 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);

    auto linkage = m_requestImpl->getLinkage();

    // Before we do anything else lets strip out all of the downstream arguments.
    DownstreamArgs downstreamArgs(m_cmdLineContext);


    SLANG_RETURN_ON_FAIL(downstreamArgs.stripDownstreamArgs(args, 0, m_sink));
    for (auto& entry : downstreamArgs.m_entries)
    {
        String serializedArgs = entry.args.serialize();
        CompilerOptionValue v;
        v.kind = CompilerOptionValueKind::String;
        v.stringValue = entry.name;
        v.stringValue2 = serializedArgs;
        linkage->m_optionSet.add(CompilerOptionName::DownstreamArgs, v);
    }

    m_reader.init(&args, m_sink);

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

        // If it's not an option we assume it's a path
        if (argValue[0] != '-')
        {
            SLANG_RETURN_ON_FAIL(addInputPath(argValue.getBuffer()));
            continue;
        }

        const Index optionIndex = m_cmdOptions->findOptionByName(argValue.getUnownedSlice());

        if (optionIndex < 0)
        {
            m_sink->diagnose(
                Diagnostics::UnknownCommandLineOption{.option = argValue, .location = arg.loc});
            _outputMinimalUsage();
            return SLANG_FAIL;
        }

        // Use the canonical first-listed name for the option rather than
        // the raw argument token. For prefix-style options like `-O3` /
        // `-g2`, `argValue` includes the suffix; the canonical name
        // (e.g. `-O`, `-g`) is what the user is supposed to recognise.
        m_currentOptionName = m_cmdOptions->getFirstNameForOption(optionIndex);

        const auto optionKind = OptionKind(m_cmdOptions->getOptionAt(optionIndex).userValue);

        switch (optionKind)
        {
        case OptionKind::NoMangle:
        case OptionKind::ValidateUniformity:
        case OptionKind::AllowGLSL:
        case OptionKind::EnableExperimentalPasses:
        case OptionKind::EnableExperimentalDynamicDispatch:
        case OptionKind::EmitIr:
        case OptionKind::DumpIntermediates:
        case OptionKind::DumpReproOnError:
        case OptionKind::ReportDownstreamTime:
        case OptionKind::ReportPerfBenchmark:
        case OptionKind::ReportCheckpointIntermediates:
        case OptionKind::ReportDynamicDispatchSites:
        case OptionKind::TraceCoverage:
        case OptionKind::TraceFunctionCoverage:
        case OptionKind::TraceBranchCoverage:
        case OptionKind::TraceCoverageBoolean:
        case OptionKind::SPIRVUnifiedDescriptorHeapStride:
        case OptionKind::DebugInfoIncludeSource:
        case OptionKind::SkipSPIRVValidation:
        case OptionKind::DisableSpecialization:
        case OptionKind::DisableDynamicDispatch:
        case OptionKind::TrackLiveness:
        case OptionKind::SkipCodeGen:
        case OptionKind::ParameterBlocksUseRegisterSpaces:
        case OptionKind::ValidateIr:
        case OptionKind::ValidateIRDetailed:
        case OptionKind::DumpIr:
        case OptionKind::VulkanInvertY:
        case OptionKind::VulkanUseDxPositionW:
        case OptionKind::VulkanUseEntryPointName:
        case OptionKind::VulkanUseGLLayout:
        case OptionKind::VulkanEmitReflection:
        case OptionKind::IgnoreCapabilities:
        case OptionKind::RestrictiveCapabilityCheck:
        case OptionKind::MinimumSlangOptimization:
        case OptionKind::DisableNonEssentialValidations:
        case OptionKind::DisableSourceMap:
        case OptionKind::DefaultImageFormatUnknown:
        case OptionKind::Obfuscate:
        case OptionKind::OutputIncludes:
        case OptionKind::PreprocessorOutput:
        case OptionKind::DumpAst:
        case OptionKind::IncompleteLibrary:
        case OptionKind::NoHLSLBinding:
        case OptionKind::NoHLSLPackConstantBufferElements:
        case OptionKind::LoopInversion:
        case OptionKind::UnscopedEnum:
        case OptionKind::PreserveParameters:
        case OptionKind::UseMSVCStyleBitfieldPacking:
        case OptionKind::ExperimentalFeature:
            linkage->m_optionSet.set(optionKind, true);
            break;
        case OptionKind::EnableRichDiagnostics:
            linkage->m_optionSet.set(optionKind, true);
            // Update the sink and all ancestor sinks so diagnostics emitted during option
            // parsing are properly formatted
            for (DiagnosticSink* sink = m_sink; sink; sink = sink->getParentSink())
                sink->setFlag(DiagnosticSink::Flag::AlwaysGenerateRichDiagnostics);
            break;
        case OptionKind::ReportDetailedPerfBenchmark:
            linkage->m_optionSet.set(optionKind, true);
            // -report-detailed-perf-benchmark implies -report-perf-benchmark
            linkage->m_optionSet.set(OptionKind::ReportPerfBenchmark, true);
            break;
        case OptionKind::EnableMachineReadableDiagnostics:
            linkage->m_optionSet.set(optionKind, true);
            // -enable-machine-readable-diagnostics implies -enable-experimental-rich-diagnostics
            linkage->m_optionSet.set(OptionKind::EnableRichDiagnostics, true);
            // Update the sink and all ancestor sinks so diagnostics emitted during option
            // parsing are properly formatted
            for (DiagnosticSink* sink = m_sink; sink; sink = sink->getParentSink())
            {
                sink->setFlag(DiagnosticSink::Flag::AlwaysGenerateRichDiagnostics);
                sink->setFlag(DiagnosticSink::Flag::MachineReadableDiagnostics);
            }
            break;
        case OptionKind::DiagnosticColor:
            {
                CommandLineArg colorArg;
                SLANG_RETURN_ON_FAIL(m_reader.expectArg(colorArg));
                SlangDiagnosticColor colorValue = SLANG_DIAGNOSTIC_COLOR_AUTO;
                if (colorArg.value == "always")
                    colorValue = SLANG_DIAGNOSTIC_COLOR_ALWAYS;
                else if (colorArg.value == "never")
                    colorValue = SLANG_DIAGNOSTIC_COLOR_NEVER;
                else if (colorArg.value == "auto")
                    colorValue = SLANG_DIAGNOSTIC_COLOR_AUTO;
                else
                {
                    m_sink->diagnose(Diagnostics::UnknownCommandLineValue{
                        .option = m_currentOptionName,
                        .validValues = "always, never, auto"});
                    return SLANG_FAIL;
                }
                linkage->m_optionSet.set(optionKind, (int)colorValue);
                // Update the sink and all ancestor sinks so colors work correctly
                for (DiagnosticSink* sink = m_sink; sink; sink = sink->getParentSink())
                    sink->setDiagnosticColorMode(colorValue);
                break;
            }
        case OptionKind::MatrixLayoutRow:
        case OptionKind::MatrixLayoutColumn:
            linkage->m_optionSet.setMatrixLayoutMode(
                (optionKind == OptionKind::MatrixLayoutRow)
                    ? MatrixLayoutMode::kMatrixLayoutMode_RowMajor
                    : MatrixLayoutMode::kMatrixLayoutMode_ColumnMajor);
            break;
        case OptionKind::ZeroInitialize:
            // Zero-initialize is now enabled by default, but accept the option for compatibility
            linkage->m_optionSet.set(optionKind, true);
            break;
        case OptionKind::NoCodeGen:
            linkage->m_optionSet.set(OptionKind::SkipCodeGen, true);
            break;
            break;
        case OptionKind::LoadCoreModule:
            {
                CommandLineArg fileName;
                SLANG_RETURN_ON_FAIL(m_reader.expectArg(fileName));

                // Load the file
                ScopedAllocation contents;
                SLANG_RETURN_ON_FAIL(File::readAllBytes(fileName.value, contents));
                SLANG_RETURN_ON_FAIL(
                    m_session->loadCoreModule(contents.getData(), contents.getSizeInBytes()));
                break;
            }
        case OptionKind::CompileCoreModule:
            m_compileCoreModule = true;
            break;
        case OptionKind::ArchiveType:
            {
                SLANG_RETURN_ON_FAIL(_expectValue(m_archiveType));
                break;
            }
        case OptionKind::SaveCoreModule:
            SLANG_RETURN_ON_FAIL(
                addPendingBuiltinModuleSave(slang::BuiltinModuleName::Core, false));
            break;
        case OptionKind::SaveCoreModuleBinSource:
        case OptionKind::SaveGLSLModuleBinSource:
            {
                const auto moduleName = optionKind == OptionKind::SaveCoreModuleBinSource
                                            ? slang::BuiltinModuleName::Core
                                            : slang::BuiltinModuleName::GLSL;
                SLANG_RETURN_ON_FAIL(addPendingBuiltinModuleSave(moduleName, 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));
                linkage->m_optionSet.set(CompilerOptionName::DumpIntermediatePrefix, prefix.value);
                break;
            }
        case OptionKind::Doc:
            {
                // When compiling the core module, it will write out a documentation.
                m_compileCoreModuleFlags |= slang::CompileCoreModuleFlag::WriteDocumentation;

                // Enable writing out documentation on the req
                linkage->m_optionSet.set(CompilerOptionName::Doc, true);
                break;
            }
        case OptionKind::DumpRepro:
            {
                CommandLineArg dumpRepro;
                SLANG_RETURN_ON_FAIL(m_reader.expectArg(dumpRepro));
                linkage->m_optionSet.set(OptionKind::DumpRepro, dumpRepro.value);
                m_compileRequest->enableReproCapture();
                break;
            }
        case OptionKind::ExtractRepro:
            {
                CommandLineArg reproName;
                SLANG_RETURN_ON_FAIL(m_reader.expectArg(reproName));

                {
                    const Result res = ReproUtil::extractFilesToDirectory(reproName.value, m_sink);
                    if (SLANG_FAILED(res))
                    {
                        m_sink->diagnose(
                            Diagnostics::UnableToExtractReproToDirectory{.path = reproName.value});
                        return res;
                    }
                }
                break;
            }
        case OptionKind::ModuleName:
            {
                CommandLineArg moduleName;
                SLANG_RETURN_ON_FAIL(m_reader.expectArg(moduleName));

                m_compileRequest->setDefaultModuleName(moduleName.value.getBuffer());
                break;
            }
        case OptionKind::DumpIRBefore:
            {
                CommandLineArg passNames;
                SLANG_RETURN_ON_FAIL(m_reader.expectArg(passNames));

                linkage->m_optionSet.add(CompilerOptionName::DumpIRBefore, passNames.value);
                break;
            }
        case OptionKind::DumpIRAfter:
            {
                CommandLineArg passNames;
                SLANG_RETURN_ON_FAIL(m_reader.expectArg(passNames));

                linkage->m_optionSet.add(CompilerOptionName::DumpIRAfter, passNames.value);
                break;
            }
        case OptionKind::LoadRepro:
            SLANG_RETURN_ON_FAIL(_parseLoadRepro(arg));
            break;
        case OptionKind::LoadReproDirectory:
            {
                CommandLineArg reproDirectory;
                SLANG_RETURN_ON_FAIL(m_reader.expectArg(reproDirectory));

                SLANG_RETURN_ON_FAIL(
                    _compileReproDirectory(m_session, m_requestImpl, reproDirectory.value));
                break;
            }
        case OptionKind::ReproFallbackDirectory:
            {
                CommandLineArg reproDirectory;
                SLANG_RETURN_ON_FAIL(m_reader.expectArg(reproDirectory));

                if (reproDirectory.value == toSlice("default:"))
                {
                    // The default is to use the OS file system
                    m_requestImpl->m_reproFallbackFileSystem = OSFileSystem::getExtSingleton();
                }
                else if (reproDirectory.value == toSlice("none:"))
                {
                    // None, means that there isn't a fallback
                    m_requestImpl->m_reproFallbackFileSystem.setNull();
                }
                else
                {
                    auto osFileSystem = OSFileSystem::getExtSingleton();

                    SlangPathType pathType;
                    if (SLANG_FAILED(osFileSystem->getPathType(
                            reproDirectory.value.getBuffer(),
                            &pathType)) ||
                        pathType != SLANG_PATH_TYPE_DIRECTORY)
                    {
                        return SLANG_FAIL;
                    }
                    // Make the fallback directory use a relative file system, to the specified
                    // directory
                    m_requestImpl->m_reproFallbackFileSystem =
                        new RelativeFileSystem(osFileSystem, reproDirectory.value);
                }
                break;
            }
        case OptionKind::ReproFileSystem:
            SLANG_RETURN_ON_FAIL(_parseReproFileSystem(arg));
            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));
                linkage->m_optionSet.add(
                    OptionKind::WarningsAsErrors,
                    operand.value.getUnownedSlice());
                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));
                linkage->m_optionSet.add(
                    OptionKind::DisableWarnings,
                    operand.value.getUnownedSlice());
                break;
            }
        case OptionKind::DisableWarning:
            {
                // 5 because -Wno-
                auto name = argValue.getUnownedSlice().tail(5);
                linkage->m_optionSet.add(OptionKind::DisableWarning, name);

                // SLANG_RETURN_ON_FAIL(_overrideDiagnostic(name, Severity::Warning,
                // Severity::Disable));
                break;
            }
        case OptionKind::EnableWarning:
            {
                // 2 because -W
                auto name = argValue.getUnownedSlice().tail(2);
                linkage->m_optionSet.add(OptionKind::EnableWarning, name);
                // Enable the warning
                // SLANG_RETURN_ON_FAIL(_overrideDiagnostic(name, Severity::Warning,
                // Severity::Warning));
                break;
            }
        case OptionKind::WarningLevel:
            {
                // The flag spelling (-Wall/-Wextra/-Wpedantic) selects which warning group to
                // enable. Exact-match options take priority over the -W<id> prefix, so these are
                // never confused with `-W<name>`.
                auto flag = argValue.getUnownedSlice();
                SlangWarningLevel level;
                if (flag == "-Wall")
                    level = SLANG_WARNING_LEVEL_ALL;
                else if (flag == "-Wextra")
                    level = SLANG_WARNING_LEVEL_EXTRA;
                else if (flag == "-Wpedantic")
                    level = SLANG_WARNING_LEVEL_PEDANTIC;
                else
                {
                    // Only the three exact flags above are registered for WarningLevel, so any
                    // other spelling reaching here is a wiring bug, not user input.
                    SLANG_UNEXPECTED("unhandled -W warning-level flag");
                }
                linkage->m_optionSet.add(OptionKind::WarningLevel, (int)level);
                break;
            }
        case OptionKind::VerifyDebugSerialIr:
            m_frontEndReq->verifyDebugSerialization = true;
            break;
        case OptionKind::IrCompression:
            {
                CommandLineArg name;
                SLANG_RETURN_ON_FAIL(m_reader.expectArg(name));
                // TODO: warn that this option is deprecated
                break;
            }
        case OptionKind::EmbedDownstreamIR:
            {
                getCurrentTarget()->optionSet.add(CompilerOptionName::EmbedDownstreamIR, true);
                break;
            }
        case OptionKind::Target:
            {
                CommandLineArg name;
                SLANG_RETURN_ON_FAIL(m_reader.expectArg(name));

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

                if (format == CodeGenTarget::Unknown)
                {
                    m_sink->diagnose(Diagnostics::UnknownCodeGenerationTarget{
                        .target = name.value,
                        .location = name.loc});
                    return SLANG_FAIL;
                }

                RawTarget rawTarget;
                rawTarget.format = CodeGenTarget(format);
                // Silently allow redundant targets if it is the same as the last specified target.
                if (m_rawTargets.getCount() != 0 &&
                    m_rawTargets.getLast().format == rawTarget.format)
                    break;
                m_rawTargets.add(rawTarget);
                break;
            }
        case OptionKind::VulkanBindShift:
            {
                // -fvk-{b|s|t|u}-shift <binding-shift> <set>
                const auto slice = arg.value.getUnownedSlice().subString(5, 1);
                HLSLToVulkanLayoutOptions::Kind kind = HLSLToVulkanLayoutOptions::Kind::Invalid;
                SLANG_RETURN_ON_FAIL(_getValue(arg, slice, kind));

                Int shift;
                SLANG_RETURN_ON_FAIL(_expectInt(arg, shift));

                if (m_reader.hasArg() && m_reader.peekArg().value == toSlice("all"))
                {
                    m_reader.advance();
                    linkage->m_optionSet.add(
                        CompilerOptionName::VulkanBindShiftAll,
                        (int)kind,
                        (int)shift);
                }
                else
                {
                    Int set;
                    SLANG_RETURN_ON_FAIL(_expectInt(arg, set));
                    linkage->m_optionSet.add(
                        CompilerOptionName::VulkanBindShift,
                        (uint8_t)kind,
                        (int)set,
                        (int)shift);
                }
                break;
            }
        case OptionKind::VulkanBindGlobals:
            {
                // -fvk-bind-globals <index> <set>
                Int binding, bindingSet;
                SLANG_RETURN_ON_FAIL(_expectInt(arg, binding));
                SLANG_RETURN_ON_FAIL(_expectInt(arg, bindingSet));
                linkage->m_optionSet.set(
                    OptionKind::VulkanBindGlobals,
                    (int)binding,
                    (int)bindingSet);
                break;
            }
        case OptionKind::TraceCoverageBinding:
            {
                // -trace-coverage-binding <index> <space>
                // Reject negative values up front so a typo or sentinel
                // (`-trace-coverage-binding -1 0`) produces a clear
                // diagnostic instead of silently degrading to auto-
                // allocation downstream.
                Int bindingIndex, bindingSpace;
                SLANG_RETURN_ON_FAIL(_expectUInt(arg, bindingIndex));
                SLANG_RETURN_ON_FAIL(_expectUInt(arg, bindingSpace));
                if (bindingIndex > std::numeric_limits<int>::max())
                {
                    m_sink->diagnose(Diagnostics::CoverageBindingOptionOutOfRange{
                        .option = arg.value,
                        .parsedValue = bindingIndex,
                        .location = arg.loc,
                    });
                    return SLANG_FAIL;
                }
                if (bindingSpace > std::numeric_limits<int>::max())
                {
                    m_sink->diagnose(Diagnostics::CoverageBindingOptionOutOfRange{
                        .option = arg.value,
                        .parsedValue = bindingSpace,
                        .location = arg.loc,
                    });
                    return SLANG_FAIL;
                }
                linkage->m_optionSet.set(
                    OptionKind::TraceCoverageBinding,
                    (int)bindingIndex,
                    (int)bindingSpace);
                // Implies -trace-coverage so users don't have to spell both.
                linkage->m_optionSet.set(OptionKind::TraceCoverage, true);
                break;
            }
        case OptionKind::TraceCoverageReservedSpace:
            {
                // -trace-coverage-reserved-space <space>
                Int bindingSpace;
                SLANG_RETURN_ON_FAIL(_expectUInt(arg, bindingSpace));
                if (bindingSpace > std::numeric_limits<int>::max())
                {
                    m_sink->diagnose(Diagnostics::CoverageBindingOptionOutOfRange{
                        .option = arg.value,
                        .parsedValue = bindingSpace,
                        .location = arg.loc,
                    });
                    return SLANG_FAIL;
                }
                linkage->m_optionSet.add(OptionKind::TraceCoverageReservedSpace, (int)bindingSpace);
                break;
            }
        case OptionKind::CoverageManifestOutput:
            {
                CommandLineArg outputPath;
                SLANG_RETURN_ON_FAIL(m_reader.expectArg(outputPath));
                linkage->m_optionSet.set(OptionKind::CoverageManifestOutput, outputPath.value);
                break;
            }
        case OptionKind::TraceCoverageCounterByteWidth:
            {
                // -trace-coverage-counter-width <bits>
                // Validate up front rather than at IR-pass time: this is a
                // user-facing CLI flag, not an internal state, so an invalid
                // value should produce a clear front-end diagnostic instead
                // of a downstream codegen surprise.
                Int widthBits;
                SLANG_RETURN_ON_FAIL(_expectUInt(arg, widthBits));
                if (widthBits != 32 && widthBits != 64)
                {
                    // `parsedValue` is intentionally the user's bit value
                    // (`widthBits`), not the byte width stored below: the
                    // diagnostic echoes what the user typed (32/64) so its
                    // "accepts only 32 or 64" message reads correctly. Do
                    // not "fix" this to the stored stride.
                    m_sink->diagnose(Diagnostics::CoverageCounterWidthInvalid{
                        .parsedValue = widthBits,
                        .location = arg.loc,
                    });
                    return SLANG_FAIL;
                }
                // Convert the user-facing bit width to the byte width (4 or
                // 8) stored on the option. This is the one place the unit
                // changes from bits (CLI) to bytes (option / IR pass /
                // metadata / `CoverageBufferInfo::elementByteWidth`); every
                // internal consumer reads bytes. The IR pass and metadata
                // writer can then treat the value as a stride directly.
                linkage->m_optionSet.set(
                    OptionKind::TraceCoverageCounterByteWidth,
                    (int)(widthBits / 8));
                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.

                // Don't treat zero args as an error.
                if (!m_reader.hasArg())
                    break;

                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];
                    CapabilityName atom = findCapabilityName(atomName);
                    if (atom == CapabilityName::Invalid)
                    {
                        m_sink->diagnose(Diagnostics::UnknownProfile{
                            .profile = atomName,
                            .location = operand.loc});
                        return SLANG_FAIL;
                    }

                    addCapabilityAtom(getCurrentTarget(), atom);
                }
                break;
            }
        case OptionKind::LanguageVersion:
            {
                CommandLineArg name;
                SLANG_RETURN_ON_FAIL(m_reader.expectArg(name));

                SlangLanguageVersion stdRevision =
                    TypeTextUtil::findLanguageVersion(name.value.getUnownedSlice());
                if (stdRevision == SLANG_LANGUAGE_VERSION_UNKNOWN)
                {
                    m_sink->diagnose(Diagnostics::UnknownLanguageVersion{
                        .version = name.value,
                        .location = name.loc});
                    return SLANG_FAIL;
                }
                else
                {
                    linkage->m_optionSet.add(OptionKind::LanguageVersion, stdRevision);
                }
                break;
            }
        case OptionKind::Stage:
            {
                CommandLineArg name;
                SLANG_RETURN_ON_FAIL(m_reader.expectArg(name));

                Stage stage = findStageByName(name.value);
                if (stage == Stage::Unknown)
                {
                    m_sink->diagnose(
                        Diagnostics::UnknownStage{.stageName = name.value, .location = name.loc});
                    return SLANG_FAIL;
                }
                else
                {
                    setStage(getCurrentEntryPoint(), stage);
                }
                break;
            }
        case OptionKind::GLSLForceScalarLayout:
            {
                getCurrentTarget()->optionSet.add(CompilerOptionName::GLSLForceScalarLayout, true);
                break;
            }
        case OptionKind::ForceDXLayout:
            {
                getCurrentTarget()->optionSet.add(CompilerOptionName::ForceDXLayout, true);
                break;
            }
        case OptionKind::ForceCLayout:
            {
                getCurrentTarget()->optionSet.add(CompilerOptionName::ForceCLayout, true);
                break;
            }
        case OptionKind::GenerateWholeProgram:
            {
                getCurrentTarget()->optionSet.add(CompilerOptionName::GenerateWholeProgram, true);
                break;
            }
        case OptionKind::EnableEffectAnnotations:
            {
                m_compileRequest->setEnableEffectAnnotations(true);
                break;
            }

        case OptionKind::EntryPointName:
            {
                CommandLineArg name;
                SLANG_RETURN_ON_FAIL(m_reader.expectArg(name));

                RawEntryPoint rawEntryPoint;
                rawEntryPoint.name = name.value;
                rawEntryPoint.translationUnitIndex = m_currentTranslationUnitIndex;
                // Silently allow duplicate entrypoints if it is the same as the last specified one.
                if (m_rawEntryPoints.getCount() != 0 &&
                    m_rawEntryPoints.getLast().name == rawEntryPoint.name)
                    break;
                m_rawEntryPoints.add(rawEntryPoint);
                break;
            }
        case OptionKind::Specialize:
            {
                for (;;)
                {
                    CommandLineArg name;
                    SLANG_RETURN_ON_FAIL(m_reader.expectArg(name));
                    if (m_rawEntryPoints.getCount() > 0)
                    {
                        auto& lastEntryPoint = m_rawEntryPoints.getLast();
                        lastEntryPoint.specializationArgs.add(name.value);
                    }
                    if (m_reader.hasArg() && m_reader.peekArg().value == ",")
                        m_reader.advance();
                    else
                        break;
                }
                break;
            }
        case OptionKind::Language:
            {
                CommandLineArg name;
                SLANG_RETURN_ON_FAIL(m_reader.expectArg(name));

                const SourceLanguage sourceLanguage =
                    (SourceLanguage)TypeTextUtil::findSourceLanguage(name.value.getUnownedSlice());

                if (sourceLanguage == SourceLanguage::Unknown)
                {
                    m_sink->diagnose(Diagnostics::UnknownSourceLanguage{
                        .language = name.value,
                        .location = name.loc});
                    return SLANG_FAIL;
                }
                else
                {
                    while (m_reader.hasArg() && !m_reader.peekValue().startsWith("-"))
                    {
                        SLANG_RETURN_ON_FAIL(addInputPath(
                            m_reader.getValueAndAdvance().getBuffer(),
                            sourceLanguage));
                    }
                }
                linkage->m_optionSet.add(CompilerOptionName::Language, (int)sourceLanguage);
                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)))
                {
                    m_sink->diagnose(Diagnostics::UnknownPassThroughTarget{
                        .target = name.value,
                        .location = name.loc});
                    return SLANG_FAIL;
                }

                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

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

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

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

                m_compileRequest->addSearchPath(String(slice).getBuffer());
                break;
            }
        case OptionKind::TypeConformance:
            {
                if (!m_reader.hasArg())
                    break;
                CommandLineArg operand;
                SLANG_RETURN_ON_FAIL(m_reader.expectArg(operand));
                auto unquoted =
                    StringEscapeUtil::maybeUnquoteCommandLineArg(operand.value.getUnownedSlice());
                linkage->m_optionSet.add(OptionKind::TypeConformance, unquoted);
                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));

                addOutputPath(outputPath.value.getBuffer());
                break;
            }
        case OptionKind::EmitReflectionJSON:
            {
                CommandLineArg outputPath;
                SLANG_RETURN_ON_FAIL(m_reader.expectArg(outputPath));

                linkage->m_optionSet.set(CompilerOptionName::EmitReflectionJSON, outputPath.value);
                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;
                }
                else
                {
                    m_sink->diagnose(Diagnostics::DuplicateDependencyOutputPaths{});
                    return SLANG_FAIL;
                }
                break;
            }
        case OptionKind::LineDirectiveMode:
            {
                SlangLineDirectiveMode value = SLANG_LINE_DIRECTIVE_MODE_DEFAULT;
                SLANG_RETURN_ON_FAIL(_expectValue(value));
                m_compileRequest->setLineDirectiveMode(value);
                break;
            }
        case OptionKind::FloatingPointMode:
            {
                FloatingPointMode value = FloatingPointMode::Default;
                SLANG_RETURN_ON_FAIL(_expectValue(value));
                setFloatingPointMode(getCurrentTarget(), value);
                break;
            }
        case OptionKind::DenormalModeFp16:
            {
                FloatingPointDenormalMode value = FloatingPointDenormalMode::Any;
                SLANG_RETURN_ON_FAIL(_expectValue(value));
                linkage->m_optionSet.set(CompilerOptionName::DenormalModeFp16, value);
                break;
            }
        case OptionKind::DenormalModeFp32:
            {
                FloatingPointDenormalMode value = FloatingPointDenormalMode::Any;
                SLANG_RETURN_ON_FAIL(_expectValue(value));
                linkage->m_optionSet.set(CompilerOptionName::DenormalModeFp32, value);
                break;
            }
        case OptionKind::DenormalModeFp64:
            {
                FloatingPointDenormalMode value = FloatingPointDenormalMode::Any;
                SLANG_RETURN_ON_FAIL(_expectValue(value));
                linkage->m_optionSet.set(CompilerOptionName::DenormalModeFp64, value);
                break;
            }
        case OptionKind::Optimization:
            {
                UnownedStringSlice levelSlice = argValue.getUnownedSlice().tail(2);
                SlangOptimizationLevel level = SLANG_OPTIMIZATION_LEVEL_DEFAULT;

                if (levelSlice.getLength())
                {
                    SLANG_RETURN_ON_FAIL(_getValue(arg, levelSlice, level));
                }

                m_compileRequest->setOptimizationLevel(level);
                break;
            }
        case OptionKind::DebugInformation:
            SLANG_RETURN_ON_FAIL(_parseDebugInformation(arg));
            break;
        case OptionKind::FileSystem:
            {
                typedef TypeTextUtil::FileSystemType FileSystemType;
                FileSystemType value = FileSystemType::Default;
                SLANG_RETURN_ON_FAIL(_expectValue(value));

                switch (value)
                {
                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:
            {
                StringBuilder versionStr;
                versionStr << m_session->getBuildTagString() << "\n";
                m_sink->diagnoseRaw(Severity::Note, versionStr.getUnownedSlice());
                break;
            }
        case OptionKind::HelpStyle:
            SLANG_RETURN_ON_FAIL(_expectValue(m_helpStyle));
            break;
        case OptionKind::Help:
            {
                SLANG_RETURN_ON_FAIL(_parseHelp(arg));
                return SLANG_OK;
            }
        case OptionKind::EmitSpirvViaGLSL:
        case OptionKind::EmitSpirvDirectly:
            {
                SlangEmitSpirvMethod selectMethod = (optionKind == OptionKind::EmitSpirvViaGLSL)
                                                        ? SLANG_EMIT_SPIRV_VIA_GLSL
                                                        : SLANG_EMIT_SPIRV_DIRECTLY;

                SlangEmitSpirvMethod currentMethod =
                    getCurrentTarget()->optionSet.getEnumOption<SlangEmitSpirvMethod>(
                        OptionKind::EmitSpirvMethod);
                // When both flag turns on, spirv-direcly mode will always take higher priority.
                // By default (value 0), spirv-via-glsl mode is used, and any input flag can
                // override the default value.
                if (selectMethod > currentMethod)
                {
                    getCurrentTarget()->optionSet.set(OptionKind::EmitSpirvMethod, selectMethod);
                }
            }
            break;
        case OptionKind::SPIRVCoreGrammarJSON:
            {
                CommandLineArg path;
                SLANG_RETURN_ON_FAIL(m_reader.expectArg(path));
                m_session->setSPIRVCoreGrammar(path.value.getBuffer());
            }
            break;

        case OptionKind::DefaultDownstreamCompiler:
            {
                CommandLineArg sourceLanguageArg, compilerArg;
                SLANG_RETURN_ON_FAIL(m_reader.expectArg(sourceLanguageArg));
                SLANG_RETURN_ON_FAIL(m_reader.expectArg(compilerArg));

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

                SlangPassThrough compiler;
                if (SLANG_FAILED(TypeTextUtil::findPassThrough(
                        compilerArg.value.getUnownedSlice(),
                        compiler)))
                {
                    m_sink->diagnose(Diagnostics::UnknownPassThroughTarget{
                        .target = compilerArg.value,
                        .location = compilerArg.loc});
                    return SLANG_FAIL;
                }

                if (SLANG_FAILED(m_session->setDefaultDownstreamCompiler(sourceLanguage, compiler)))
                {
                    m_sink->diagnose(Diagnostics::UnableToSetDefaultDownstreamCompiler{
                        .language = sourceLanguageArg.value,
                        .compiler = compilerArg.value});
                    return SLANG_FAIL;
                }
                break;
            }
        case OptionKind::CompilerPath:
            {
                const Index index = argValue.lastIndexOf('-');
                if (index >= 0)
                {
                    CommandLineArg name;
                    SLANG_RETURN_ON_FAIL(m_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)))
                    {
                        // Executable-based downstream compilers (Metal, GCC,
                        // Clang) locate the tool by combining the user-supplied
                        // directory with a fixed executable name.  Users often
                        // pass the full path to the executable instead (e.g.
                        // `-metal-path "C:\...\bin\metal.exe"`), which produces
                        // a doubled path.  Normalize to the parent directory for
                        // these compilers only.  Shared-library backends (DXC,
                        // FXC, NVRTC, etc.) already handle file-vs-directory
                        // disambiguation in their own locators and rely on the
                        // exact filename the user supplied.
                        String compilerPath = name.value;
                        if (passThrough == SLANG_PASS_THROUGH_METAL ||
                            passThrough == SLANG_PASS_THROUGH_CLANG ||
                            passThrough == SLANG_PASS_THROUGH_GCC ||
                            passThrough == SLANG_PASS_THROUGH_GENERIC_C_CPP)
                        {
                            SlangPathType pathType;
                            if (compilerPath.getLength() &&
                                SLANG_SUCCEEDED(Path::getPathType(compilerPath, &pathType)) &&
                                pathType == SLANG_PATH_TYPE_FILE)
                            {
                                String parentDir = Path::getParentDirectory(compilerPath);
                                if (parentDir.getLength())
                                    compilerPath = parentDir;
                            }
                        }
                        m_session->setDownstreamCompilerPath(passThrough, compilerPath.getBuffer());
                        continue;
                    }
                    else
                    {
                        m_sink->diagnose(Diagnostics::UnknownDownstreamCompiler{
                            .compiler = passThroughSlice,
                            .location = arg.loc});
                        return SLANG_FAIL;
                    }
                }
                break;
            }
        case OptionKind::CompilerVersion:
            {
                // `-<compiler>-version` is a print-and-continue query option. It has the same
                // "-<compiler>-..." shape as -<compiler>-path, but instead of consuming a value it
                // prints the version of the downstream compiler Slang would actually load for that
                // pass-through, then lets parsing continue (like -version). Recover <compiler> as
                // the text between the leading '-' and the trailing "-version", exactly as the
                // CompilerPath case recovers the name before "-path".
                const Index index = argValue.lastIndexOf('-');
                if (index >= 0)
                {
                    UnownedStringSlice passThroughSlice =
                        argValue.getUnownedSlice().head(index).tail(1);

                    SlangPassThrough passThrough = SLANG_PASS_THROUGH_NONE;
                    if (SLANG_FAILED(TypeTextUtil::findPassThrough(passThroughSlice, passThrough)))
                    {
                        m_sink->diagnose(Diagnostics::UnknownDownstreamCompiler{
                            .compiler = passThroughSlice,
                            .location = arg.loc});
                        return SLANG_FAIL;
                    }

                    // getDownstreamCompilerVersion shares the same lazy-discovery funnel used
                    // during compilation, so the reported version is the library that would
                    // actually be used for this pass-through (it honors -<compiler>-path and the
                    // standard search order). It returns SLANG_OK once the compiler is located and
                    // loaded -- major/minor are then valid, and a loaded-but-versionless compiler
                    // such as glslang reports 0.0 -- and SLANG_E_NOT_FOUND when it cannot be
                    // loaded (e.g. the toolchain is not installed).
                    int major = 0;
                    int minor = 0;
                    StringBuilder versionStr;
                    versionStr << passThroughSlice << " version: ";
                    if (SLANG_SUCCEEDED(
                            m_session->getDownstreamCompilerVersion(passThrough, &major, &minor)))
                    {
                        versionStr << major << "." << minor;
                    }
                    else
                    {
                        versionStr << "not found";
                    }
                    versionStr << "\n";
                    m_sink->diagnoseRaw(Severity::Note, versionStr.getUnownedSlice());
                }
                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())
                {
                    SLANG_RETURN_ON_FAIL(addInputPath(m_reader.getValueAndAdvance().getBuffer()));
                }
                break;
            }
        case OptionKind::SourceEmbedStyle:
            {
                SLANG_RETURN_ON_FAIL(_expectValue(m_requestImpl->m_sourceEmbedStyle));
                break;
            }
        case OptionKind::SourceEmbedName:
            {
                CommandLineArg name;
                SLANG_RETURN_ON_FAIL(m_reader.expectArg(name));
                m_requestImpl->m_sourceEmbedName = name.value;
                break;
            }
        case OptionKind::SourceEmbedLanguage:
            {
                SLANG_RETURN_ON_FAIL(_expectValue(m_requestImpl->m_sourceEmbedLanguage));

                if (!SourceEmbedUtil::isSupported(
                        (SlangSourceLanguage)m_requestImpl->m_sourceEmbedLanguage))
                {
                    m_sink->diagnose(Diagnostics::UnhandledLanguageForSourceEmbedding{});
                    return SLANG_FAIL;
                }

                break;
            }
        case OptionKind::DisableShortCircuit:
            {
                linkage->m_optionSet.add(OptionKind::DisableShortCircuit, true);
                break;
            }
        case OptionKind::BindlessSpaceIndex:
        case OptionKind::SPIRVSamplerHeapStride:
        case OptionKind::SPIRVResourceHeapStride:
            {
                Int index = 0;
                SLANG_RETURN_ON_FAIL(_expectUInt(arg, index));
                linkage->m_optionSet.add(optionKind, (int)index);
                break;
            }
        case OptionKind::DumpModule:
            {
                CommandLineArg fileName;
                SLANG_RETURN_ON_FAIL(m_reader.expectArg(fileName));
                auto desc = slang::SessionDesc();
                ComPtr<slang::ISession> session;
                m_session->createSession(desc, session.writeRef());
                ComPtr<slang::IBlob> diagnostics;

                // Coerce Slang to load from the given file, without letting it automatically
                // choose .slang-module files over .slang files.
                // First try to load as source string, and fall back to loading as an IR Blob.
                // Avoid guessing based on filename or inspecting the file contents.
                FileStream file;
                if (SLANG_FAILED(file.init(
                        fileName.value,
                        FileMode::Open,
                        FileAccess::Read,
                        FileShare::None)))
                {
                    m_sink->diagnose(
                        Diagnostics::CannotOpenFile{.path = fileName.value, .location = arg.loc});
                    return SLANG_FAIL;
                }

                List<uint8_t> buffer;
                file.seek(SeekOrigin::End, 0);
                const Int64 size = file.getPosition();
                buffer.setCount(size + 1);
                file.seek(SeekOrigin::Start, 0);
                SLANG_RETURN_ON_FAIL(file.readExactly(buffer.getBuffer(), (size_t)size));
                buffer[size] = 0;
                file.close();

                ComPtr<slang::IModule> module;
                module = session->loadModuleFromSourceString(
                    "module",
                    "path",
                    (const char*)buffer.getBuffer(),
                    diagnostics.writeRef());
                if (!module)
                {
                    // Load buffer as an IR blob
                    ComPtr<slang::IBlob> blob;
                    blob = RawBlob::create(buffer.getBuffer(), size);

                    module = session->loadModuleFromIRBlob(
                        "module",
                        "path",
                        blob,
                        diagnostics.writeRef());
                }

                if (module)
                {
                    ComPtr<slang::IBlob> disassemblyBlob;
                    if (SLANG_FAILED(module->disassemble(disassemblyBlob.writeRef())))
                    {
                        m_sink->diagnose(Diagnostics::CannotDisassemble{
                            .target = fileName.value,
                            .location = arg.loc});
                        return SLANG_FAIL;
                    }
                    else
                    {
                        // success, print out the disassembly in a way that slang-test can read
                        m_sink->diagnoseRaw(
                            Severity::Note,
                            (const char*)disassemblyBlob->getBufferPointer());
                    }
                }
                else
                {
                    if (diagnostics)
                    {
                        m_sink->diagnoseRaw(
                            Severity::Error,
                            (const char*)diagnostics->getBufferPointer());
                    }
                    return SLANG_FAIL;
                }


                break;
            }
        case OptionKind::GetModuleInfo:
            {
                CommandLineArg fileName;
                SLANG_RETURN_ON_FAIL(m_reader.expectArg(fileName));
                auto desc = slang::SessionDesc();
                ComPtr<slang::ISession> session;
                m_session->createSession(desc, session.writeRef());
                ComPtr<slang::IBlob> diagnostics;

                FileStream file;
                if (SLANG_FAILED(file.init(
                        fileName.value,
                        FileMode::Open,
                        FileAccess::Read,
                        FileShare::None)))
                {
                    m_sink->diagnose(
                        Diagnostics::CannotOpenFile{.path = fileName.value, .location = arg.loc});
                    return SLANG_FAIL;
                }

                List<uint8_t> buffer;
                file.seek(SeekOrigin::End, 0);
                const Int64 size = file.getPosition();
                buffer.setCount(size + 1);
                file.seek(SeekOrigin::Start, 0);
                SLANG_RETURN_ON_FAIL(file.readExactly(buffer.getBuffer(), (size_t)size));
                buffer[size] = 0;
                file.close();

                ComPtr<slang::IModule> module;
                // Load buffer as an IR blob
                ComPtr<slang::IBlob> blob;
                blob = RawBlob::create(buffer.getBuffer(), size);

                const char* moduleName = nullptr;
                const char* moduleCompilerVersion = nullptr;
                SlangInt moduleVersion = ~0;
                SLANG_RETURN_ON_FAIL(session->loadModuleInfoFromIRBlob(
                    blob,
                    moduleVersion,
                    moduleCompilerVersion,
                    moduleName));

                char infoBuffer[512];
                snprintf(
                    infoBuffer,
                    sizeof(infoBuffer),
                    "Module Name: %s\n"
                    "Module Version: %lld\n"
                    "Compiler Version: %s\n",
                    moduleName ? moduleName : "null",
                    static_cast<long long>(moduleVersion),
                    moduleCompilerVersion ? moduleCompilerVersion : "null");

                m_sink->diagnoseRaw(Severity::Note, infoBuffer);

                break;
            }
        case OptionKind::GetSupportedModuleVersions:
            {
                char infoBuffer[512];
                snprintf(
                    infoBuffer,
                    sizeof(infoBuffer),
                    "Minimum supported version: %lu\n"
                    "Maximum supported version: %lu\n",
                    (unsigned long)IRModule::k_minSupportedModuleVersion,
                    (unsigned long)IRModule::k_maxSupportedModuleVersion);

                m_sink->diagnoseRaw(Severity::Note, infoBuffer);

                break;
            }
        case OptionKind::EmitSeparateDebug:
            {
                // This will emit a separate debug file, containing all debug info in
                // a .dbg.spv file. The main output SPIRV will have all debug info stripped.
                linkage->m_optionSet.set(OptionKind::EmitSeparateDebug, true);
                break;
            }
        case OptionKind::SeparateDebugInfoOutput:
            {
                CommandLineArg outputPath;
                SLANG_RETURN_ON_FAIL(m_reader.expectArg(outputPath));
                linkage->m_optionSet.set(OptionKind::SeparateDebugInfoOutput, outputPath.value);
                break;
            }
        case OptionKind::EmitCPUViaCPP:
        case OptionKind::EmitCPUViaLLVM:
            {
                SlangEmitCPUMethod selectMethod = (optionKind == OptionKind::EmitCPUViaCPP)
                                                      ? SLANG_EMIT_CPU_VIA_CPP
                                                      : SLANG_EMIT_CPU_VIA_LLVM;

                getCurrentTarget()->optionSet.set(OptionKind::EmitCPUMethod, selectMethod);
            }
            break;
        case OptionKind::LLVMTargetTriple:
            {
                CommandLineArg targetTriple;
                SLANG_RETURN_ON_FAIL(m_reader.expectArg(targetTriple));
                linkage->m_optionSet.set(CompilerOptionName::LLVMTargetTriple, targetTriple.value);
                break;
            }
        case OptionKind::LLVMCPU:
            {
                CommandLineArg cpuName;
                SLANG_RETURN_ON_FAIL(m_reader.expectArg(cpuName));
                linkage->m_optionSet.set(CompilerOptionName::LLVMCPU, cpuName.value);
                break;
            }
        case OptionKind::LLVMFeatures:
            {
                CommandLineArg features;
                SLANG_RETURN_ON_FAIL(m_reader.expectArg(features));
                linkage->m_optionSet.set(CompilerOptionName::LLVMFeatures, features.value);
                break;
            }
        default:
            {
                // Hmmm, we looked up and produced a valid enum, but it wasn't handled in the
                // switch...
                m_sink->diagnose(
                    Diagnostics::UnknownCommandLineOption{.option = argValue, .location = arg.loc});

                _outputMinimalUsage();
                return SLANG_FAIL;
            }
        }
    }

    // Apply diagnostic sink settings early so that any diagnostics emitted during
    // option post-processing (e.g., entry point validation) use the correct settings
    // such as rich diagnostics and machine-readable output.
    applySettingsToDiagnosticSink(m_requestImpl->getSink(), m_sink, linkage->m_optionSet);

    if (m_compileCoreModule)
    {
        SLANG_RETURN_ON_FAIL(m_session->compileCoreModule(m_compileCoreModuleFlags));
    }
    SLANG_RETURN_ON_FAIL(writePendingBuiltinModuleSaves());

    // 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)
    {
        // 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)
            {
                setStage(getCurrentEntryPoint(), m_defaultEntryPoint.stage);
            }

            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 (m_defaultEntryPoint.stage != Stage::Unknown)
            {
                if (m_rawEntryPoints.getCount() == 0)
                {
                    m_sink->diagnose(Diagnostics::StageSpecificationIgnoredBecauseNoEntryPoints{});
                }
                else
                {
                    m_sink->diagnose(
                        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 (m_translationUnitCount == 1)
        {
            for (auto& entryPoint : m_rawEntryPoints)
            {
                entryPoint.translationUnitIndex = 0;
            }
        }
        else if (
            m_frontEndReq->additionalLoadedModules &&
            m_frontEndReq->additionalLoadedModules->getCount() == 0)
        {
            // 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)
            {
                // Skip entry points that are already associated with a translation unit...
                if (entryPoint.translationUnitIndex != -1)
                    continue;

                anyEntryPointWithoutTranslationUnit = true;
            }
            if (anyEntryPointWithoutTranslationUnit)
            {
                m_sink->diagnose(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 : m_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 =
                m_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 : m_rawEntryPoints)
        {
            if (rawEntryPoint.conflictingStagesSet)
            {
                m_sink->diagnose(
                    Diagnostics::ConflictingStagesForEntryPoint{.entryPoint = rawEntryPoint.name});
            }
            else if (rawEntryPoint.redundantStageSet)
            {
                m_sink->diagnose(Diagnostics::SameStageSpecifiedMoreThanOnce{
                    .stage = getStageName(rawEntryPoint.stage),
                    .entryPoint = 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)
                {
                    m_sink->diagnose(Diagnostics::ExplicitStageDoesntMatchImpliedStage{
                        .entryPoint = rawEntryPoint.name,
                        .specifiedStage = getStageName(rawEntryPoint.stage),
                        .impliedStage = getStageName(rawTranslationUnit.impliedStage)});
                }
            }
        }

        // (`-trace-coverage` + `-pass-through` rejection moved to
        // `EndToEndCompileRequest::executeActionsInner` so that C++
        // API callers using `setPassThrough()` are also covered;
        // this option-parser path is CLI-only.)

        // 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)
            {
                if (rawEntryPoint.stage == Stage::Unknown)
                {
                    m_sink->diagnose(Diagnostics::NoStageSpecifiedInPassThroughMode{
                        .entryPoint = rawEntryPoint.name});
                }
            }
        }

        // 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 =
                m_rawTranslationUnits[rawEntryPoint.translationUnitIndex].translationUnitID;

            List<const char*> specializationArgs;
            for (auto& arg : rawEntryPoint.specializationArgs)
                specializationArgs.add(arg.getBuffer());

            int entryPointID = m_compileRequest->addEntryPointEx(
                translationUnitID,
                rawEntryPoint.name.begin(),
                SlangStage(rawEntryPoint.stage),
                (int)specializationArgs.getCount(),
                specializationArgs.getBuffer());

            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 (m_rawTargets.getCount() == 0)
        {
            // If there are no targets and no outputs
            if (m_rawOutputs.getCount() == 0)
            {
                m_requestImpl->m_emitIr = true;
            }
            else
            {
                for (auto& rawOutput : m_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)m_rawTargets.getCount();

                        RawTarget rawTarget;
                        rawTarget.format = impliedFormat;
                        m_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)m_rawTargets.getCount();
            for (int targetIndex = 0; targetIndex < targetCount; ++targetIndex)
            {
                auto format = m_rawTargets[targetIndex].format;

                if (mapFormatToTargetIndex.containsKey(format))
                {
                    m_sink->diagnose(Diagnostics::DuplicateTargets{
                        .target = TypeTextUtil::getCompileTargetName(SlangCompileTarget(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 (m_rawTargets.getCount() == 0 &&
            m_defaultTarget.optionSet.getProfileVersion() != 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.
            //
            CodeGenTarget inferredFormat = CodeGenTarget::Unknown;
            auto profileVersion = m_defaultTarget.optionSet.getProfileVersion();
            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;
                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.
        auto defaultTargetFloatingPointMode = m_defaultTarget.optionSet.getFloatingPointMode();

        if (m_rawTargets.getCount() == 1)
        {
            m_rawTargets[0].optionSet.overrideWith(m_defaultTarget.optionSet);
        }
        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.optionSet.getProfileVersion() != ProfileVersion::Unknown)
            {
                if (m_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.
                    //
                    m_sink->diagnose(Diagnostics::ProfileSpecificationIgnoredBecauseNoTargets{});
                }
                else
                {
                    m_sink->diagnose(
                        Diagnostics::ProfileSpecificationIgnoredBecauseBeforeAllTargets{});
                }
            }

            if (defaultTargetFloatingPointMode != FloatingPointMode::Default)
            {
                if (m_rawTargets.getCount() == 0)
                {
                    m_sink->diagnose(Diagnostics::TargetFlagsIgnoredBecauseNoTargets{});
                }
                else
                {
                    m_sink->diagnose(Diagnostics::TargetFlagsIgnoredBecauseBeforeAllTargets{});
                }
            }
        }
        for (auto& rawTarget : m_rawTargets)
        {
            if (rawTarget.conflictingProfilesSet)
            {
                m_sink->diagnose(Diagnostics::ConflictingProfilesSpecifiedForTarget{
                    .target =
                        TypeTextUtil::getCompileTargetName(SlangCompileTarget(rawTarget.format))});
            }
            else if (rawTarget.redundantProfileSet)
            {
                m_sink->diagnose(Diagnostics::SameProfileSpecifiedMoreThanOnce{
                    .profile = Profile(rawTarget.optionSet.getProfileVersion()).getName(),
                    .target =
                        TypeTextUtil::getCompileTargetName(SlangCompileTarget(rawTarget.format))});
            }

            // Reject a `-capability` that raises the emitted version above what an explicit
            // `-profile` pins. Only run when the profile's family matches the target's version
            // family: a cross-family profile (e.g. `glsl_450` on a SPIR-V target) contributes
            // version atoms that would misread as a pin, wrongly flagging `-profile
            // glsl_450+spirv_1_4`.
            Profile profile = rawTarget.optionSet.getProfile();
            CapabilityAtom targetVersionFamily = CapabilityAtom::Invalid;
            switch (profile.getFamily())
            {
            case ProfileFamily::SPIRV:
                // `isSPIRV`, not `isKhronosTarget`: the GLSL text target emits no SPIR-V version.
                if (isSPIRV(rawTarget.format))
                    targetVersionFamily = CapabilityAtom::_spirv_1_0;
                break;
            case ProfileFamily::METAL:
                if (isMetalTarget(rawTarget.format))
                    targetVersionFamily = CapabilityAtom::metallib_2_3;
                break;
            case ProfileFamily::DX:
                if (isD3DTarget(rawTarget.format))
                    targetVersionFamily = CapabilityAtom::_sm_4_0;
                break;
            case ProfileFamily::GLSL:
                if (rawTarget.format == CodeGenTarget::GLSL)
                    targetVersionFamily = CapabilityAtom::_GLSL_130;
                break;
            default:
                break;
            }
            if (!rawTarget.conflictingProfilesSet && targetVersionFamily != CapabilityAtom::Invalid)
            {
                // Collect the explicit `-capability` atoms, then ask once whether — folded in the
                // way `getTargetCaps()` folds them — they raise the emitted target version above
                // what the profile pins.
                List<CapabilityName> requestedCapabilities;
                for (auto atomVal : rawTarget.optionSet.getArray(CompilerOptionName::Capability))
                {
                    CapabilityName capabilityName = CapabilityName::Invalid;
                    switch (atomVal.kind)
                    {
                    case CompilerOptionValueKind::Int:
                        capabilityName = CapabilityName(atomVal.intValue);
                        break;
                    case CompilerOptionValueKind::String:
                        capabilityName = findCapabilityName(atomVal.stringValue.getUnownedSlice());
                        break;
                    }
                    if (capabilityName != CapabilityName::Invalid)
                        requestedCapabilities.add(capabilityName);
                }

                if (doRequestedCapabilitiesRaiseTargetVersionAboveProfile(
                        profile.getCapabilityName(),
                        requestedCapabilities,
                        targetVersionFamily))
                {
                    m_sink->diagnose(Diagnostics::ConflictingExplicitCapabilityAndProfile{
                        .profile = profile.getName()});
                }
            }
        }

        // `-spirv-unified-descriptor-heap-stride` and an explicit non-zero
        // `-spirv-resource-heap-stride` express contradictory strides for the same resource heap,
        // so reject the combination here, as soon as the options are parsed, rather than waiting
        // until SPIR-V emission. An explicit stride of 0 selects the default `OpConstantSizeOfEXT`
        // path that the unified option modifies and is therefore not a conflict.
        if (linkage->m_optionSet.getBoolOption(
                CompilerOptionName::SPIRVUnifiedDescriptorHeapStride) &&
            linkage->m_optionSet.getIntOption(CompilerOptionName::SPIRVResourceHeapStride) != 0)
        {
            m_sink->diagnose(Diagnostics::SpirvConflictingDescriptorHeapStrideOptions{});
        }

        // `-debug-info-include-source` embeds the source into debug information, so it needs
        // debug information to exist. At `DebugInfoLevel::None` there is no debug scaffolding to
        // attach the source to, so reject the combination here rather than silently ignoring it.
        // Note `getDebugInfoLevel()` cannot distinguish an explicit `-g0` from the default (no
        // `-g`), which also resolves to `None`; both cases are a conflict for this flag.
        if (linkage->m_optionSet.shouldIncludeSourceInDebugInfo() &&
            linkage->m_optionSet.getDebugInfoLevel() == DebugInfoLevel::None)
        {
            m_sink->diagnose(Diagnostics::DebugInfoIncludeSourceRequiresDebugInfo{});
        }

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

            if (rawTarget.optionSet.getProfileVersion() != ProfileVersion::Unknown)
            {
                m_compileRequest->setTargetProfile(
                    targetID,
                    SlangProfileID(Profile(rawTarget.optionSet.getProfileVersion()).raw));
            }
            for (auto atom : rawTarget.optionSet.getArray(CompilerOptionName::Capability))
            {
                m_requestImpl->addTargetCapability(targetID, SlangCapabilityID(atom.intValue));
            }

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

            if (rawTarget.optionSet.shouldUseScalarLayout())
            {
                m_compileRequest->setTargetForceGLSLScalarBufferLayout(targetID, true);
            }

            if (rawTarget.optionSet.shouldUseDXLayout())
            {
                m_compileRequest->setTargetForceDXLayout(targetID, true);
            }

            if (rawTarget.optionSet.shouldUseCLayout())
            {
                m_compileRequest->setTargetForceCLayout(targetID, true);
            }

            if (rawTarget.optionSet.getBoolOption(CompilerOptionName::GenerateWholeProgram))
            {
                m_compileRequest->setTargetGenerateWholeProgram(targetID, true);
            }

            if (rawTarget.optionSet.getBoolOption(CompilerOptionName::EmbedDownstreamIR))
            {
                m_compileRequest->setTargetEmbedDownstreamIR(targetID, true);
            }
        }

        // 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.targetIndex = 0;
            }
        }

        // 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 ||
             m_rawTargets[0].format == CodeGenTarget::CUDAHeader ||
             m_rawTargets[0].format == CodeGenTarget::SPIRV ||
             m_rawTargets[0].format == CodeGenTarget::SPIRVAssembly ||
             m_rawTargets[0].format == CodeGenTarget::Metal ||
             m_rawTargets[0].format == CodeGenTarget::MetalLib ||
             m_rawTargets[0].format == CodeGenTarget::MetalLibAssembly ||
             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)
            {
                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).
                    //
                    m_sink->diagnose(
                        Diagnostics::CannotDeduceOutputFormatFromPath{.path = rawOutput.path});
                }
                else if (mapFormatToTargetIndex.tryGetValue(rawOutput.impliedFormat, targetIndex))
                {
                    rawOutput.targetIndex = targetIndex;
                }
                else
                {
                    m_sink->diagnose(Diagnostics::CannotMatchOutputFileToTarget{
                        .path = rawOutput.path,
                        .format = TypeTextUtil::getCompileTargetName(
                            SlangCompileTarget(rawOutput.impliedFormat))});
                }
            }

            if (rawOutput.targetIndex != -1 &&
                m_rawTargets[rawOutput.targetIndex].optionSet.getBoolOption(
                    CompilerOptionName::GenerateWholeProgram))
            {
                // `-whole-program` emits one target-level artifact, even when the command line
                // names a single entry point. Keep `-o` aligned with the artifact that
                // `generateOutput()` will actually write.
                rawOutput.isWholeProgram = true;
            }

            // 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 = 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)
                    {
                    case CodeGenTarget::CPPSource:
                    case CodeGenTarget::CPPHeader:
                    case CodeGenTarget::PTX:
                    case CodeGenTarget::CUDASource:
                    case CodeGenTarget::CUDAHeader:

                    case CodeGenTarget::HostHostCallable:
                    case CodeGenTarget::ShaderHostCallable:
                    case CodeGenTarget::HostExecutable:
                    case CodeGenTarget::ShaderSharedLibrary:
                    case CodeGenTarget::HostSharedLibrary:
                    case CodeGenTarget::PyTorchCppBinding:
                    case CodeGenTarget::DXIL:
                    case CodeGenTarget::MetalLib:
                    case CodeGenTarget::MetalLibAssembly:
                    case CodeGenTarget::Metal:
                    case CodeGenTarget::WGSL:
                    case CodeGenTarget::HostVM:
                    case CodeGenTarget::HostObjectCode:
                    case CodeGenTarget::ShaderObjectCode:
                    case CodeGenTarget::HostLLVMIR:
                    case CodeGenTarget::ShaderLLVMIR:
                        rawOutput.isWholeProgram = true;
                        break;
                    case CodeGenTarget::SPIRV:
                    case CodeGenTarget::SPIRVAssembly:
                        if (getCurrentTarget()->optionSet.shouldEmitSPIRVDirectly())
                        {
                            rawOutput.isWholeProgram = true;
                            break;
                        }
                        else if (m_rawEntryPoints.getCount() != 0)
                        {
                            rawOutput.entryPointIndex = (int)m_rawEntryPoints.getCount() - 1;
                            break;
                        }
                        [[fallthrough]];
                    default:
                        if (rawOutput.path.getLength() != 0)
                        {
                            m_sink->diagnose(Diagnostics::CannotMatchOutputFileToEntryPoint{
                                .path = 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 : 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))
        {
            targetInfo = new EndToEndCompileRequest::TargetInfo();
            m_requestImpl->m_targetInfos[target] = targetInfo;
        }
        target->getOptionSet().overrideWith(m_rawTargets[rawOutput.targetIndex].optionSet);
        if (rawOutput.isWholeProgram)
        {
            if (targetInfo->wholeTargetOutputPath != "")
            {
                m_sink->diagnose(Diagnostics::DuplicateOutputPathsForTarget{
                    .target = TypeTextUtil::getCompileTargetName(
                        SlangCompileTarget(target->getTarget()))});
            }
            else
            {
                target->getOptionSet().addTargetFlags(SLANG_TARGET_FLAG_GENERATE_WHOLE_PROGRAM);
                targetInfo->wholeTargetOutputPath = rawOutput.path;
            }
        }
        else
        {
            if (rawOutput.entryPointIndex == -1)
                continue;

            auto entryPoint = m_rawEntryPoints[rawOutput.entryPointIndex];
            Int entryPointID = entryPoint.entryPointID;
            if (entryPointID == -1)
            {
                m_sink->diagnose(Diagnostics::EntryPointFunctionNotFound{.name = entryPoint.name});
                continue;
            }
            auto entryPointReq = m_requestImpl->getFrontEndReq()->getEntryPointReqs()[entryPointID];

            // String outputPath;
            if (targetInfo->entryPointOutputPaths.containsKey(entryPointID))
            {
                m_sink->diagnose(Diagnostics::DuplicateOutputPathsForEntryPointAndTarget{
                    .entryPoint = entryPointReq->getName(),
                    .target = TypeTextUtil::getCompileTargetName(
                        SlangCompileTarget(target->getTarget()))});
            }
            else
            {
                targetInfo->entryPointOutputPaths[entryPointID] = rawOutput.path;
            }
        }
    }


    // Copy all settings from linkage to targets.
    for (auto target : linkage->targets)
    {
        target->getOptionSet().inheritFrom(linkage->m_optionSet);

        // If there is no target specified in command line, we should inherit the default target
        // options.
        if (m_rawTargets.getCount() == 0)
        {
            target->getOptionSet().inheritFrom(m_defaultTarget.optionSet);
        }
    }

    // If there are no targets specified in command line, and addCodeGenTarget() is not called
    // yet, the options for the default target will be gone after option parsing. We
    // should save the option for the future use when addCodeGenTarget() is called.
    if ((linkage->targets.getCount() == 0) && (m_rawTargets.getCount() == 0))
    {
        m_requestImpl->m_optionSetForDefaultTarget = m_defaultTarget.optionSet;
    }

    applySettingsToDiagnosticSink(m_requestImpl->getSink(), m_sink, linkage->m_optionSet);

    // Warn about separate debug info being used with unsupported targets
    if (linkage->m_optionSet.shouldEmitSeparateDebugInfo())
    {
        // Check all targets to see if any use separate debug info with an unsupported target
        for (const auto& rawTarget : m_rawTargets)
        {
            if (rawTarget.format != CodeGenTarget::SPIRV)
            {
                // Get the target name for the warning message
                UnownedStringSlice targetName =
                    TypeTextUtil::getCompileTargetName(asExternal(rawTarget.format));
                m_sink->diagnose(
                    Diagnostics::SeparateDebugInfoUnsupportedForTarget{.target = targetName});
            }
        }

        // Also check the default target if no explicit targets were specified
        if (m_rawTargets.getCount() == 0)
        {
            if (m_defaultTarget.format != CodeGenTarget::SPIRV)
            {
                // Get the target name for the warning message
                UnownedStringSlice targetName =
                    TypeTextUtil::getCompileTargetName(asExternal(m_defaultTarget.format));
                m_sink->diagnose(
                    Diagnostics::SeparateDebugInfoUnsupportedForTarget{.target = targetName});
            }
        }
    }

    // `-debug-info-include-source` only affects the SPIR-V core `OpSource` path. The producer
    // change that carries source content into the IR runs once for all targets, so warn (rather
    // than silently do nothing) when it is requested for a non-SPIR-V target, mirroring the
    // `-separate-debug-info` precedent above.
    if (linkage->m_optionSet.shouldIncludeSourceInDebugInfo())
    {
        // `isSPIRV` covers both the `spirv` and `spirv-asm` targets, which share the SPIR-V
        // emitter that honors this option.
        for (const auto& rawTarget : m_rawTargets)
        {
            if (!isSPIRV(rawTarget.format))
            {
                UnownedStringSlice targetName =
                    TypeTextUtil::getCompileTargetName(asExternal(rawTarget.format));
                m_sink->diagnose(
                    Diagnostics::DebugInfoIncludeSourceUnsupportedForTarget{.target = targetName});
            }
        }

        if (m_rawTargets.getCount() == 0 && !isSPIRV(m_defaultTarget.format))
        {
            UnownedStringSlice targetName =
                TypeTextUtil::getCompileTargetName(asExternal(m_defaultTarget.format));
            m_sink->diagnose(
                Diagnostics::DebugInfoIncludeSourceUnsupportedForTarget{.target = targetName});
        }
    }

    return (m_sink->getErrorCount() == 0) ? SLANG_OK : SLANG_FAIL;
}

SlangResult OptionsParser::parse(
    SlangCompileRequest* compileRequest,
    int argc,
    char const* const* argv)
{
    m_compileRequest = compileRequest;

    // Set up useful members
    m_requestImpl = asInternal(compileRequest);

    auto session = asInternal(m_requestImpl->getSession());

    m_session = session;
    m_frontEndReq = m_requestImpl->getFrontEndReq();

    m_cmdOptions = &session->m_commandOptions;
    m_cmdLineContext = m_requestImpl->getLinkage()->m_cmdLineContext.get();

    DiagnosticSink* requestSink = m_requestImpl->getSink();

    // 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);
        // Copy color and unicode settings from the request sink
        m_parseSink.setDiagnosticColorMode(requestSink->getDiagnosticColorMode());
        m_parseSink.setEnableUnicode(requestSink->getEnableUnicode());
    }

    // All diagnostics will also be sent to requestSink
    m_parseSink.setParentSink(requestSink);
    m_sink = &m_parseSink;

    Result res = _parse(argc, argv);

    m_sink = nullptr;

    if (m_parseSink.getErrorCount() > 0)
    {
        // Put the errors in the diagnostic
        m_requestImpl->m_diagnosticOutput = m_parseSink.outputBuffer.produceString();
    }

    return res;
}

SlangResult parseOptions(SlangCompileRequest* inCompileRequest, int argc, char const* const* argv)
{
    OptionsParser parser;
    return parser.parse(inCompileRequest, argc, argv);
}


} // namespace Slang
