// slang-end-to-end-request.cpp
#include "slang-end-to-end-request.h"

#include "compiler-core/slang-pretty-writer.h"
#include "core/slang-memory-file-system.h"
#include "core/slang-performance-profiler.h"
#include "slang-check-impl.h"
#include "slang-compiler.h"
#include "slang-emit-dependency-file.h"
#include "slang-module-library.h"
#include "slang-options.h"
#include "slang-reflection-json.h"
#include "slang-repro.h"
#include "slang-rich-diagnostics.h"
#include "slang-serialize-container.h"

// TODO: The "artifact" system is a scourge.
#include "compiler-core/slang-artifact-associated-impl.h"
#include "compiler-core/slang-artifact-container-util.h"
#include "compiler-core/slang-artifact-desc-util.h"
#include "compiler-core/slang-artifact-impl.h"
#include "compiler-core/slang-artifact-util.h"
#include "slang-artifact-output-util.h"

namespace Slang
{

static bool _isCoverageTracingEnabled(CompilerOptionSet& optionSet)
{
    // Returns true only when one of the coverage trace mode booleans is set.
    // Binding and reserved-space options are inert without a coverage mode, so
    // they are intentionally not treated as enabling coverage tracing here.
    return optionSet.getBoolOption(CompilerOptionName::TraceCoverage) ||
           optionSet.getBoolOption(CompilerOptionName::TraceFunctionCoverage) ||
           optionSet.getBoolOption(CompilerOptionName::TraceBranchCoverage);
}

EndToEndCompileRequest::EndToEndCompileRequest(Session* session)
    : m_session(session), m_sink(nullptr, Lexer::sourceLocationLexer)
{
    RefPtr<ASTBuilder> astBuilder(
        new ASTBuilder(session->getASTBuilder(), "EndToEnd::Linkage::astBuilder"));
    m_linkage = new Linkage(session, astBuilder, session->getBuiltinLinkage());
    init();
}

EndToEndCompileRequest::EndToEndCompileRequest(Linkage* linkage)
    : m_session(linkage->getSessionImpl())
    , m_linkage(linkage)
    , m_sink(nullptr, Lexer::sourceLocationLexer)
{
    init();
}

void EndToEndCompileRequest::init()
{
    m_sink.setSourceManager(m_linkage->getSourceManager());

    m_writers = new StdWriters;

    // Set all the default writers
    for (int i = 0; i < int(WriterChannel::CountOf); ++i)
    {
        setWriter(WriterChannel(i), nullptr);
    }

    m_frontEndReq = new FrontEndCompileRequest(getLinkage(), m_writers, getSink());
}

EndToEndCompileRequest::~EndToEndCompileRequest()
{
    // Flush any writers associated with the request
    m_writers->flushWriters();

    m_linkage.setNull();
    m_frontEndReq.setNull();
}

SLANG_NO_THROW SlangResult SLANG_MCALL
EndToEndCompileRequest::queryInterface(SlangUUID const& uuid, void** outObject)
{
    if (uuid == EndToEndCompileRequest::getTypeGuid())
    {
        // Special case to cast directly into internal type
        // NOTE! No addref(!)
        *outObject = this;
        return SLANG_OK;
    }

    if (uuid == ISlangUnknown::getTypeGuid() || uuid == ICompileRequest::getTypeGuid())
    {
        addReference();
        *outObject = static_cast<slang::ICompileRequest*>(this);
        return SLANG_OK;
    }

    return SLANG_E_NO_INTERFACE;
}

// Try to infer a single common source language for a request
static SourceLanguage inferSourceLanguage(FrontEndCompileRequest* request)
{
    SourceLanguage language = SourceLanguage::Unknown;
    for (auto& translationUnit : request->translationUnits)
    {
        // Allow any other language to overide Slang as a choice
        if (language == SourceLanguage::Unknown || language == SourceLanguage::Slang)
        {
            language = translationUnit->sourceLanguage;
        }
        else if (language == translationUnit->sourceLanguage)
        {
            // same language as we currently have, so keep going
        }
        else
        {
            // we found a mismatch, so inference fails
            return SourceLanguage::Unknown;
        }
    }
    return language;
}

SlangResult EndToEndCompileRequest::executeActionsInner()
{
    SLANG_PROFILE_SECTION(endToEndActions);

    // If no code-generation target was specified, then try to infer one from the source language,
    // just to make sure we can do something reasonable when invoked from the command line.
    //
    // TODO: This logic should be moved into `options.cpp` or somewhere else
    // specific to the command-line tool.
    //
    if (getLinkage()->targets.getCount() == 0)
    {
        auto language = inferSourceLanguage(getFrontEndReq());
        switch (language)
        {
        case SourceLanguage::HLSL:
            getLinkage()->addTarget(CodeGenTarget::DXBytecode);
            break;

        case SourceLanguage::GLSL:
            getLinkage()->addTarget(CodeGenTarget::SPIRV);
            break;

        default:
            break;
        }
    }

    // Update compiler settings in target requests.
    for (auto target : getLinkage()->targets)
        target->getOptionSet().inheritFrom(getOptionSet());
    m_frontEndReq->optionSet = getOptionSet();

    // `-pass-through` bypasses the IR pipeline that emits coverage
    // instrumentation, so the two flags are mutually exclusive.
    // The check lives here (not in `OptionsParser::_parse`) so that
    // C++ API callers using `setPassThrough()` are also covered —
    // OptionsParser only runs for the slangc CLI path.
    auto& optionSet = getOptionSet();
    const bool coverageTracingEnabled = _isCoverageTracingEnabled(optionSet);

    if (m_passThrough != PassThroughMode::None && coverageTracingEnabled)
    {
        getSink()->diagnose(Diagnostics::CoveragePassThroughIncompatible{});
        return SLANG_FAIL;
    }

    const bool hasExplicitCoverageManifestPath = _hasExplicitCoverageManifestPath();
    if (hasExplicitCoverageManifestPath && !coverageTracingEnabled)
    {
        getSink()->diagnose(Diagnostics::CoverageManifestOutputWithoutCoverage{});
        return SLANG_FAIL;
    }
    if (hasExplicitCoverageManifestPath && m_containerFormat != ContainerFormat::None)
    {
        getSink()->diagnose(Diagnostics::CoverageManifestOutputWithContainer{});
        return SLANG_FAIL;
    }

    const bool hasExplicitSeparateDebugInfoOutputPath = _hasExplicitSeparateDebugInfoOutputPath();
    if (hasExplicitSeparateDebugInfoOutputPath && !optionSet.shouldEmitSeparateDebugInfo())
    {
        getSink()->diagnose(Diagnostics::SeparateDebugInfoOutputWithoutSeparateDebugInfo{});
        return SLANG_FAIL;
    }
    if (hasExplicitSeparateDebugInfoOutputPath && m_containerFormat != ContainerFormat::None)
    {
        getSink()->diagnose(Diagnostics::SeparateDebugInfoOutputWithContainer{});
        return SLANG_FAIL;
    }

    // We only do parsing and semantic checking if we *aren't* doing
    // a pass-through compilation.
    //
    if (m_passThrough == PassThroughMode::None)
    {
        SLANG_RETURN_ON_FAIL(getFrontEndReq()->executeActionsInner());
    }

    if (getOptionSet().getBoolOption(CompilerOptionName::PreprocessorOutput))
    {
        return SLANG_OK;
    }

    // If command line specifies to skip codegen, we exit here.
    // Note: this is a debugging option.
    //
    if (getOptionSet().getBoolOption(CompilerOptionName::SkipCodeGen))
    {
        // We will use the program (and matching layout information)
        // that was computed in the front-end for all subsequent
        // reflection queries, etc.
        //
        m_specializedGlobalComponentType = getUnspecializedGlobalComponentType();
        m_specializedGlobalAndEntryPointsComponentType =
            getUnspecializedGlobalAndEntryPointsComponentType();
        m_specializedEntryPoints = getFrontEndReq()->getUnspecializedEntryPoints();

        SLANG_RETURN_ON_FAIL(maybeCreateContainer());

        SLANG_RETURN_ON_FAIL(maybeWriteContainer(m_containerOutputPath));

        return SLANG_OK;
    }

    // If requested, attempt to compile the translation unit all the way down to the target
    // language(s) and stash the result blobs in IR.
    for (auto target : getLinkage()->targets)
    {
        SlangCompileTarget targetEnum = SlangCompileTarget(target->getTarget());
        if (target->getOptionSet().getBoolOption(CompilerOptionName::EmbedDownstreamIR))
        {
            auto frontEndReq = getFrontEndReq();

            for (auto translationUnit : frontEndReq->translationUnits)
            {
                SLANG_RETURN_ON_FAIL(
                    translationUnit->getModule()->precompileForTarget(targetEnum, nullptr));

                if (frontEndReq->optionSet.shouldDumpIR())
                {
                    DiagnosticSinkWriter writer(frontEndReq->getSink());

                    dumpIR(
                        translationUnit->getModule()->getIRModule(),
                        frontEndReq->m_irDumpOptions,
                        "PRECOMPILE_FOR_TARGET_COMPLETE_ALL",
                        frontEndReq->getSourceManager(),
                        &writer);

                    dumpIR(
                        translationUnit->getModule()->getIRModule()->getModuleInst(),
                        frontEndReq->m_irDumpOptions,
                        frontEndReq->getSourceManager(),
                        &writer);
                }
            }
        }
    }

    // If codegen is enabled, we need to move along to
    // apply any generic specialization that the user asked for.
    //
    if (m_passThrough == PassThroughMode::None)
    {
        m_specializedGlobalComponentType = createSpecializedGlobalComponentType(this);
        if (getSink()->getErrorCount() != 0)
            return SLANG_FAIL;

        m_specializedGlobalAndEntryPointsComponentType =
            createSpecializedGlobalAndEntryPointsComponentType(this, m_specializedEntryPoints);
        if (getSink()->getErrorCount() != 0)
            return SLANG_FAIL;

        // For each code generation target, we will generate specialized
        // parameter binding information (taking global generic
        // arguments into account at this time).
        //
        for (auto targetReq : getLinkage()->targets)
        {
            auto targetProgram =
                m_specializedGlobalAndEntryPointsComponentType->getTargetProgram(targetReq);
            targetProgram->getOrCreateLayout(getSink());
        }
        if (getSink()->getErrorCount() != 0)
            return SLANG_FAIL;
    }
    else
    {
        // We need to create dummy `EntryPoint` objects
        // to make sure that the logic in `generateOutput`
        // sees something worth processing.
        //
        List<RefPtr<ComponentType>> dummyEntryPoints;
        for (auto entryPointReq : getFrontEndReq()->getEntryPointReqs())
        {
            RefPtr<EntryPoint> dummyEntryPoint = EntryPoint::createDummyForPassThrough(
                getLinkage(),
                entryPointReq->getName(),
                entryPointReq->getProfile());

            dummyEntryPoints.add(dummyEntryPoint);
        }

        RefPtr<ComponentType> composedProgram =
            CompositeComponentType::create(getLinkage(), dummyEntryPoints);

        m_specializedGlobalComponentType = getUnspecializedGlobalComponentType();
        m_specializedGlobalAndEntryPointsComponentType = composedProgram;
        m_specializedEntryPoints = getFrontEndReq()->getUnspecializedEntryPoints();
    }

    // Generate output code, in whatever format was requested
    generateOutput();
    if (getSink()->getErrorCount() != 0)
        return SLANG_FAIL;

    return SLANG_OK;
}

// Act as expected of the API-based compiler
SlangResult EndToEndCompileRequest::executeActions()
{
    SlangResult res = executeActionsInner();

    m_diagnosticOutput = getSink()->outputBuffer.produceString();
    return res;
}


static ISlangWriter* _getDefaultWriter(WriterChannel chan)
{
    static FileWriter stdOut(stdout, WriterFlag::IsStatic | WriterFlag::IsUnowned);
    static FileWriter stdError(stderr, WriterFlag::IsStatic | WriterFlag::IsUnowned);
    static NullWriter nullWriter(WriterFlag::IsStatic | WriterFlag::IsConsole);

    switch (chan)
    {
    case WriterChannel::StdError:
        return &stdError;
    case WriterChannel::StdOutput:
        return &stdOut;
    case WriterChannel::Diagnostic:
        return &nullWriter;
    default:
        {
            SLANG_ASSERT(!"Unknown type");
            return &stdError;
        }
    }
}

void EndToEndCompileRequest::setWriter(WriterChannel chan, ISlangWriter* writer)
{
    // If the user passed in null, we will use the default writer on that channel
    m_writers->setWriter(SlangWriterChannel(chan), writer ? writer : _getDefaultWriter(chan));

    // For diagnostic output, if the user passes in nullptr, we set on m_sink.writer as that enables
    // buffering on DiagnosticSink
    if (chan == WriterChannel::Diagnostic)
    {
        m_sink.writer = writer;
    }
}

void EndToEndCompileRequest::writeArtifactToStandardOutput(
    IArtifact* artifact,
    DiagnosticSink* sink)
{
    // If it's host callable it's not available to write to output
    if (isDerivedFrom(artifact->getDesc().kind, ArtifactKind::HostCallable))
    {
        return;
    }

    auto session = getSession();
    ArtifactOutputUtil::maybeConvertAndWrite(
        session,
        artifact,
        sink,
        toSlice("stdout"),
        getWriter(WriterChannel::StdOutput));
}

String EndToEndCompileRequest::_getWholeProgramPath(TargetRequest* targetReq)
{
    RefPtr<EndToEndCompileRequest::TargetInfo> targetInfo;
    if (m_targetInfos.tryGetValue(targetReq, targetInfo))
    {
        return targetInfo->wholeTargetOutputPath;
    }
    return String();
}

String EndToEndCompileRequest::_getEntryPointPath(TargetRequest* targetReq, Index entryPointIndex)
{
    // It is possible that we are dynamically discovering entry
    // points (using `[shader(...)]` attributes), so that there
    // might be entry points added to the program that did not
    // get paths specified via command-line options.
    //
    RefPtr<EndToEndCompileRequest::TargetInfo> targetInfo;
    if (m_targetInfos.tryGetValue(targetReq, targetInfo))
    {
        String outputPath;
        if (targetInfo->entryPointOutputPaths.tryGetValue(entryPointIndex, outputPath))
        {
            return outputPath;
        }
    }

    return String();
}

void EndToEndCompileRequest::_collectExistingOutputArtifacts(
    List<ExistingOutputArtifact>& outArtifacts)
{
    outArtifacts.clear();

    auto linkage = getLinkage();
    auto program = getSpecializedGlobalAndEntryPointsComponentType();
    for (auto targetReq : linkage->targets)
    {
        auto targetProgram = program->getTargetProgram(targetReq);
        if (targetProgram->getOptionSet().getBoolOption(CompilerOptionName::GenerateWholeProgram))
        {
            if (auto artifact = targetProgram->getExistingWholeProgramResult())
            {
                ExistingOutputArtifact outputArtifact;
                outputArtifact.targetProgram = targetProgram;
                outputArtifact.path = _getWholeProgramPath(targetReq);
                outputArtifact.artifact = artifact;
                outArtifacts.add(outputArtifact);
            }
        }
        else
        {
            for (Index entryPointIndex = 0; entryPointIndex < program->getEntryPointCount();
                 ++entryPointIndex)
            {
                if (auto artifact = targetProgram->getExistingEntryPointResult(entryPointIndex))
                {
                    ExistingOutputArtifact outputArtifact;
                    outputArtifact.targetProgram = targetProgram;
                    outputArtifact.path = _getEntryPointPath(targetReq, entryPointIndex);
                    outputArtifact.artifact = artifact;
                    outArtifacts.add(outputArtifact);
                }
            }
        }
    }
}

static bool _isStdoutArtifactPath(const String& path)
{
    return path.getLength() == 0 || path == "-";
}

SlangResult EndToEndCompileRequest::_writeArtifact(const String& path, IArtifact* artifact)
{
    if (!_isStdoutArtifactPath(path))
    {
        SLANG_RETURN_ON_FAIL(ArtifactOutputUtil::writeToFile(artifact, getSink(), path));
    }
    else if (m_containerFormat == ContainerFormat::None)
    {
        // If we aren't writing to a container and we didn't write to a file, we can output to
        // standard output
        writeArtifactToStandardOutput(artifact, getSink());
    }
    return SLANG_OK;
}

String EndToEndCompileRequest::_getExplicitCoverageManifestPath()
{
    return getOptionSet().getStringOption(CompilerOptionName::CoverageManifestOutput);
}

bool EndToEndCompileRequest::_hasExplicitCoverageManifestPath()
{
    return _getExplicitCoverageManifestPath().getLength() != 0;
}

String EndToEndCompileRequest::_getExplicitSeparateDebugInfoOutputPath()
{
    return getOptionSet().getStringOption(CompilerOptionName::SeparateDebugInfoOutput);
}

bool EndToEndCompileRequest::_hasExplicitSeparateDebugInfoOutputPath()
{
    return _getExplicitSeparateDebugInfoOutputPath().getLength() != 0;
}

// If the artifact carries coverage tracing metadata, write its JSON
// manifest sidecar. By default slangc writes `<path>.coverage-manifest.json`
// alongside file outputs. `-coverage-manifest-output <path>` overrides
// that location and also works for stdout artifact output. Hosts can
// read this sidecar to attribute runtime counter values back to source
// entries. The JSON content is produced by the public
// `slang_writeCoverageManifestJson` API; both that API and this sidecar
// writer emit byte-identical output, so customers working in-process can
// pipe the API output through the same downstream tooling.
static slang::ICoverageTracingMetadata* _findCoverageTracingMetadata(IArtifact* artifact)
{
    if (!artifact)
        return nullptr;
    auto coverage = findAssociatedRepresentation<slang::ICoverageTracingMetadata>(artifact);
    // Return coverage metadata if either dimension has data. Future coverage
    // modes may record source entries without runtime counters, or runtime
    // counter allocation before all source-entry metadata is populated; either
    // non-empty dimension is worth persisting.
    return coverage && (coverage->getCounterCount() != 0 || coverage->getEntryCount() != 0)
               ? coverage
               : nullptr;
}

// Normalizes paths for preflight collision checks:
// - Existing paths compare by their canonical filesystem path.
// - Not-yet-created paths canonicalize their deepest existing ancestor, then
//   append the original non-existent suffix.
// - Paths with no canonicalizable ancestor fall back to absolutized, simplified
//   text. Equality after this transform implies the paths would alias the same
//   filesystem entity, modulo platform case-folding below.
static String _normalizeOutputPathForCompare(const String& path)
{
    if (path.getLength() == 0)
        return String();

    String absolutePath = path;
    if (!Path::isAbsolute(path))
    {
        String currentPath = Path::getCurrentPath();
        if (currentPath.getLength() != 0)
            absolutePath = Path::combine(currentPath, path);
    }
    absolutePath = Path::simplify(absolutePath);

    String canonicalPath;
    if (SLANG_SUCCEEDED(Path::getCanonical(absolutePath, canonicalPath)) &&
        canonicalPath.getLength() != 0)
    {
        return canonicalPath;
    }

    // Output files usually do not exist during preflight validation. Canonicalize
    // the deepest existing prefix so symlinked output directories still compare
    // correctly for not-yet-created artifacts and sidecars.
    String parentPath = Path::getParentDirectory(absolutePath);
    if (parentPath.getLength() == 0 || parentPath == absolutePath)
        return absolutePath;

    String canonicalParentPath = _normalizeOutputPathForCompare(parentPath);
    if (canonicalParentPath.getLength() == 0)
        return absolutePath;

    return Path::combine(canonicalParentPath, Path::getFileName(absolutePath));
}

static bool _areOutputPathsEquivalent(const String& left, const String& right)
{
    String normalizedLeft = _normalizeOutputPathForCompare(left);
    String normalizedRight = _normalizeOutputPathForCompare(right);
#if SLANG_WINDOWS_FAMILY
    return normalizedLeft.getUnownedSlice().caseInsensitiveEquals(
        normalizedRight.getUnownedSlice());
#else
    // Treat POSIX paths as case-sensitive even on case-folding mounts. The
    // worst case is a missed collision diagnostic on those mounts; Windows is
    // the platform where case aliases are expected and handled explicitly.
    return normalizedLeft == normalizedRight;
#endif
}

SlangResult EndToEndCompileRequest::_maybeWriteCoverageManifest(
    const String& path,
    IArtifact* artifact)
{
    auto coverage = _findCoverageTracingMetadata(artifact);
    if (!coverage)
        return SLANG_OK;

    const String explicitSidecarPath = _getExplicitCoverageManifestPath();
    String sidecarPath;
    if (explicitSidecarPath.getLength() != 0)
    {
        sidecarPath = explicitSidecarPath;
    }
    else
    {
        if (_isStdoutArtifactPath(path))
            return SLANG_OK;
        sidecarPath = path + ".coverage-manifest.json";
    }

    ComPtr<ISlangBlob> jsonBlob;
    SLANG_RETURN_ON_FAIL(slang_writeCoverageManifestJson(coverage, jsonBlob.writeRef()));
    const SlangResult writeResult =
        File::writeAllBytes(sidecarPath, jsonBlob->getBufferPointer(), jsonBlob->getBufferSize());
    if (SLANG_FAILED(writeResult))
    {
        getSink()->diagnose(Diagnostics::UnableToWriteFile{.path = sidecarPath});
    }
    return writeResult;
}

SlangResult EndToEndCompileRequest::_maybeWriteArtifact(const String& path, IArtifact* artifact)
{
    // We don't have to do anything if there is no artifact
    if (!artifact)
    {
        return SLANG_OK;
    }

    // If embedding is enabled...
    if (m_sourceEmbedStyle != SourceEmbedUtil::Style::None)
    {
        SourceEmbedUtil::Options options;

        options.style = m_sourceEmbedStyle;
        options.variableName = m_sourceEmbedName;
        options.language = (SlangSourceLanguage)m_sourceEmbedLanguage;

        ComPtr<IArtifact> embeddedArtifact;
        SLANG_RETURN_ON_FAIL(SourceEmbedUtil::createEmbedded(artifact, options, embeddedArtifact));

        if (!embeddedArtifact)
        {
            return SLANG_FAIL;
        }
        SLANG_RETURN_ON_FAIL(
            _writeArtifact(SourceEmbedUtil::getPath(path, options), embeddedArtifact));
        return SLANG_OK;
    }
    else
    {
        SLANG_RETURN_ON_FAIL(_writeArtifact(path, artifact));
    }

    return SLANG_OK;
}

// These helper functions are used by the -separate-debug-info command line
// arg to extract the associated artifact containing the debug SPIRV data
// and save it to a file with a .dbg.spv extension.
static String _getDebugSpvPath(const String& basePath)
{
    // Find the last occurrence of ".spv" at the end of the string.
    static const char ext[] = ".spv";
    static const char dbgExt[] = ".dbg.spv";
    Index extLen = 4;
    if (basePath.getLength() >= extLen && basePath.endsWith(ext))
    {
        // Replace the ".spv" extension with ".dbg.spv"
        String prefix = String(basePath.subString(0, basePath.getLength() - extLen));
        return prefix + dbgExt;
    }
    // If it doesn't end with .spv, just append .dbg.spv
    return basePath + dbgExt;
}

String EndToEndCompileRequest::_getSeparateDebugInfoOutputPath(
    TargetProgram* targetProgram,
    const String& path,
    IArtifact* artifact)
{
    if (!targetProgram->getOptionSet().shouldEmitSeparateDebugInfo())
        return String();

    const auto separateDebugInfoArtifact = getSeparateDbgArtifact(artifact);
    if (!separateDebugInfoArtifact)
        return String();

    const String explicitPath = _getExplicitSeparateDebugInfoOutputPath();
    if (explicitPath.getLength() != 0)
        return explicitPath;

    // Preserve the legacy behavior as a fallback: without an explicit debug path, derive the
    // sidecar location from the main `-o` output file. The separate-debug preflight runs before
    // this helper's coverage-preflight caller and rejects stdout paths that cannot be derived.
    SLANG_RELEASE_ASSERT(!_isStdoutArtifactPath(path));

    // The artifact's name may have been set to the debug build id hash, use
    // it as the filename if it exists.
    String separateDebugInfoOutputPath = separateDebugInfoArtifact->getName();
    if (separateDebugInfoOutputPath.getLength() == 0)
    {
        separateDebugInfoOutputPath = _getDebugSpvPath(path);
    }
    else
    {
        separateDebugInfoOutputPath.append(".dbg.spv");
    }
    return separateDebugInfoOutputPath;
}

SlangResult EndToEndCompileRequest::_validateSeparateDebugInfoOutputPaths()
{
    const String explicitPath = _getExplicitSeparateDebugInfoOutputPath();
    const bool hasExplicitPath = explicitPath.getLength() != 0;

    // This preflight has two modes. Fallback mode only rejects a debug-bearing stdout artifact,
    // because it has no main output path from which to derive a sidecar path. Explicit mode counts
    // debug artifacts and collects every other destination for the missing-data and collision
    // checks after the loop.
    List<String> otherOutputPaths;
    Index debugArtifactCount = 0;
    bool missingDerivedPath = false;

    List<ExistingOutputArtifact> outputArtifacts;
    _collectExistingOutputArtifacts(outputArtifacts);
    for (const auto& outputArtifact : outputArtifacts)
    {
        // With an explicit path, compare it against every other output, including outputs from
        // targets that do not themselves produce separate debug information.
        if (hasExplicitPath)
        {
            // Normalize both spellings of stdout so an explicit `-` sidecar cannot collide with an
            // implicit stdout main artifact.
            otherOutputPaths.add(
                _isStdoutArtifactPath(outputArtifact.path) ? String("-") : outputArtifact.path);

            if (_findCoverageTracingMetadata(outputArtifact.artifact))
            {
                String coveragePath = _getExplicitCoverageManifestPath();
                if (coveragePath.getLength() == 0 && !_isStdoutArtifactPath(outputArtifact.path))
                {
                    coveragePath = outputArtifact.path + ".coverage-manifest.json";
                }
                if (coveragePath.getLength() != 0)
                    otherOutputPaths.add(coveragePath);
            }
        }

        if (!getSeparateDbgArtifact(outputArtifact.artifact))
            continue;

        debugArtifactCount++;
        // In fallback mode, the only invalid shape is a debug-bearing stdout artifact because it
        // has no file path from which to derive the sidecar destination.
        if (!hasExplicitPath && _isStdoutArtifactPath(outputArtifact.path))
            missingDerivedPath = true;
    }

    if (missingDerivedPath)
    {
        getSink()->diagnose(Diagnostics::SeparateDebugInfoRequiresOutputPath{});
        return SLANG_FAIL;
    }

    if (!hasExplicitPath)
        return SLANG_OK;

    const String reflectionPath =
        getOptionSet().getStringOption(CompilerOptionName::EmitReflectionJSON);
    if (reflectionPath.getLength() != 0)
        otherOutputPaths.add(reflectionPath);
    if (m_dependencyOutputPath.getLength() != 0)
        otherOutputPaths.add(m_dependencyOutputPath);

    if (debugArtifactCount == 0)
    {
        getSink()->diagnose(
            Diagnostics::SeparateDebugInfoOutputWithoutDebugData{.path = explicitPath});
        return SLANG_FAIL;
    }
    if (debugArtifactCount > 1)
    {
        getSink()->diagnose(
            Diagnostics::SeparateDebugInfoOutputMultipleArtifacts{.path = explicitPath});
        return SLANG_FAIL;
    }

    for (const auto& otherPath : otherOutputPaths)
    {
        if (_areOutputPathsEquivalent(explicitPath, otherPath))
        {
            getSink()->diagnose(Diagnostics::SeparateDebugInfoOutputCollidesWithArtifact{
                .path = explicitPath,
                .otherPath = otherPath});
            return SLANG_FAIL;
        }
    }

    return SLANG_OK;
}

SlangResult EndToEndCompileRequest::_maybeWriteSeparateDebugInfoOutput(
    TargetProgram* targetProgram,
    const String& path,
    IArtifact* artifact)
{
    if (!targetProgram->getOptionSet().shouldEmitSeparateDebugInfo())
        return SLANG_OK;

    const auto separateDebugInfoArtifact = getSeparateDbgArtifact(artifact);
    // A separate debug-info artifact is currently produced only for SPIR-V targets. The warning
    // about unsupported targets is already issued during option parsing.
    if (!separateDebugInfoArtifact)
        return SLANG_OK;

    String separateDebugInfoOutputPath =
        _getSeparateDebugInfoOutputPath(targetProgram, path, artifact);
    // The separate-debug preflight runs before emission and rejects every shape for which this
    // artifact has no destination, including an underivable stdout fallback.
    SLANG_RELEASE_ASSERT(separateDebugInfoOutputPath.getLength() != 0);
    return _maybeWriteArtifact(separateDebugInfoOutputPath, separateDebugInfoArtifact);
}

SlangResult EndToEndCompileRequest::_validateCoverageManifestOutputPaths()
{
    const String explicitSidecarPath = _getExplicitCoverageManifestPath();
    if (explicitSidecarPath.getLength() == 0)
        return SLANG_OK;

    // Container output is rejected earlier by E45111 when the option is set.
    // Non-command-line requests do not use the slangc artifact-write path this
    // preflight protects.
    if (!m_isCommandLineCompile || m_containerFormat != ContainerFormat::None)
        return SLANG_OK;

    auto linkage = getLinkage();
    auto program = getSpecializedGlobalAndEntryPointsComponentType();

    List<String> emittedArtifactPaths;
    Index coverageArtifactCount = 0;

    // Records paths slangc will emit for this target/artifact pair and counts
    // coverage-bearing artifacts. The count rejects 0 or 2+ coverage artifacts;
    // the path list rejects explicit-sidecar collisions.
    auto recordArtifact =
        [&](TargetProgram* targetProgram, const String& artifactPath, IArtifact* artifact)
    {
        if (!artifact)
            return;
        // Stdout artifacts have no file path to collide with.
        if (artifactPath.getLength() != 0)
            emittedArtifactPaths.add(artifactPath);

        String dbgPath = _getSeparateDebugInfoOutputPath(targetProgram, artifactPath, artifact);
        // Targets that did not emit separate debug info have no debug path to collide with.
        if (dbgPath.getLength() != 0)
            emittedArtifactPaths.add(dbgPath);

        if (_findCoverageTracingMetadata(artifact))
            coverageArtifactCount++;
    };

    for (auto targetReq : linkage->targets)
    {
        auto targetProgram = program->getTargetProgram(targetReq);

        if (targetProgram->getOptionSet().getBoolOption(CompilerOptionName::GenerateWholeProgram))
        {
            recordArtifact(
                targetProgram,
                _getWholeProgramPath(targetReq),
                targetProgram->getExistingWholeProgramResult());
        }
        else
        {
            Index entryPointCount = program->getEntryPointCount();
            for (Index ee = 0; ee < entryPointCount; ++ee)
            {
                recordArtifact(
                    targetProgram,
                    _getEntryPointPath(targetReq, ee),
                    targetProgram->getExistingEntryPointResult(ee));
            }
        }
    }

    // Exactly one coverage-instrumented artifact is allowed to claim an
    // explicit sidecar path. Any other count is rejected before collision checks.
    if (coverageArtifactCount > 1)
    {
        getSink()->diagnose(
            Diagnostics::CoverageManifestOutputMultipleArtifacts{.path = explicitSidecarPath});
        return SLANG_FAIL;
    }

    if (coverageArtifactCount == 0)
    {
        getSink()->diagnose(
            Diagnostics::CoverageManifestOutputWithoutCoverageData{.path = explicitSidecarPath});
        return SLANG_FAIL;
    }

    for (const auto& artifactPath : emittedArtifactPaths)
    {
        if (_areOutputPathsEquivalent(explicitSidecarPath, artifactPath))
        {
            getSink()->diagnose(Diagnostics::CoverageManifestOutputCollidesWithArtifact{
                .path = explicitSidecarPath});
            return SLANG_FAIL;
        }
    }

    return SLANG_OK;
}

void EndToEndCompileRequest::generateOutput(TargetProgram* targetProgram)
{
    auto program = targetProgram->getProgram();

    // Generate target code any entry points that
    // have been requested for compilation.
    auto entryPointCount = program->getEntryPointCount();
    if (targetProgram->getOptionSet().getBoolOption(CompilerOptionName::GenerateWholeProgram))
    {
        targetProgram->_createWholeProgramResult(getSink(), this);
    }
    else
    {
        for (Index ii = 0; ii < entryPointCount; ++ii)
        {
            targetProgram->_createEntryPointResult(ii, getSink(), this);
        }
    }
}

bool _shouldWriteSourceLocs(Linkage* linkage)
{
    // If debug information or source manager are not avaiable we can't/shouldn't write out locs
    if (linkage->m_optionSet.getDebugInfoLevel() == DebugInfoLevel::None ||
        linkage->getSourceManager() == nullptr)
    {
        return false;
    }

    // Otherwise we do want to write out the locs
    return true;
}

SlangResult EndToEndCompileRequest::writeContainerToStream(Stream* stream)
{
    auto linkage = getLinkage();

    // Set up options
    SerialContainerUtil::WriteOptions options;

    // If debug information is enabled, enable writing out source locs
    if (_shouldWriteSourceLocs(linkage))
    {
        options.sourceManagerToUseWhenSerializingSourceLocs = linkage->getSourceManager();
    }

    SLANG_RETURN_ON_FAIL(SerialContainerUtil::write(this, options, stream));

    return SLANG_OK;
}

static IBoxValue<SourceMap>* _getObfuscatedSourceMap(TranslationUnitRequest* translationUnit)
{
    if (auto module = translationUnit->getModule())
    {
        if (auto irModule = module->getIRModule())
        {
            return irModule->getObfuscatedSourceMap();
        }
    }
    return nullptr;
}

SlangResult EndToEndCompileRequest::maybeCreateContainer()
{
    m_containerArtifact.setNull();

    List<ComPtr<IArtifact>> artifacts;

    auto linkage = getLinkage();

    auto program = getSpecializedGlobalAndEntryPointsComponentType();

    for (auto targetReq : linkage->targets)
    {
        auto targetProgram = program->getTargetProgram(targetReq);

        if (targetProgram->getOptionSet().getBoolOption(CompilerOptionName::GenerateWholeProgram))
        {
            if (auto artifact = targetProgram->getExistingWholeProgramResult())
            {
                if (!targetProgram->getOptionSet().getBoolOption(
                        CompilerOptionName::EmbedDownstreamIR))
                {
                    artifacts.add(ComPtr<IArtifact>(artifact));
                }
            }
        }
        else
        {
            Index entryPointCount = program->getEntryPointCount();
            for (Index ee = 0; ee < entryPointCount; ++ee)
            {
                if (auto artifact = targetProgram->getExistingEntryPointResult(ee))
                {
                    artifacts.add(ComPtr<IArtifact>(artifact));
                }
            }
        }
    }

    // If IR emitting is enabled, add IR to the artifacts
    if (m_emitIr && (m_containerFormat == ContainerFormat::SlangModule))
    {
        OwnedMemoryStream stream(FileAccess::Write);
        SlangResult res = writeContainerToStream(&stream);
        if (SLANG_FAILED(res))
        {
            getSink()->diagnose(Diagnostics::UnableToCreateModuleContainer{});
            return res;
        }

        // Need to turn into a blob
        List<uint8_t> blobData;
        stream.swapContents(blobData);

        auto containerBlob = ListBlob::moveCreate(blobData);

        auto irArtifact = Artifact::create(ArtifactDesc::make(
            Artifact::Kind::CompileBinary,
            ArtifactPayload::SlangIR,
            ArtifactStyle::Unknown));
        irArtifact->addRepresentationUnknown(containerBlob);

        // Add the IR artifact
        artifacts.add(irArtifact);
    }

    // If there is only one artifact we can use that as the container
    if (artifacts.getCount() == 1)
    {
        m_containerArtifact = artifacts[0];
    }
    else
    {
        m_containerArtifact = ArtifactUtil::createArtifact(
            ArtifactDesc::make(ArtifactKind::Container, ArtifactPayload::CompileResults));

        for (IArtifact* childArtifact : artifacts)
        {
            m_containerArtifact->addChild(childArtifact);
        }
    }

    // Get all of the source obfuscated source maps and add those
    if (m_containerArtifact)
    {
        auto frontEndReq = getFrontEndReq();

        for (auto translationUnit : frontEndReq->translationUnits)
        {
            // Hmmm do I have to therefore add a map for all translation units(!)
            // I guess this is okay in so far as an association can always be looked up by name
            if (auto sourceMap = _getObfuscatedSourceMap(translationUnit))
            {
                auto artifactDesc = ArtifactDesc::make(
                    ArtifactKind::Json,
                    ArtifactPayload::SourceMap,
                    ArtifactStyle::Obfuscated);

                // Create the source map artifact
                auto sourceMapArtifact =
                    Artifact::create(artifactDesc, sourceMap->get().m_file.getUnownedSlice());

                // Add the repesentation
                sourceMapArtifact->addRepresentation(sourceMap);

                // Associate with the container
                m_containerArtifact->addAssociated(sourceMapArtifact);
            }
        }
    }

    return SLANG_OK;
}

CompilerOptionSet& EndToEndCompileRequest::getTargetOptionSet(TargetRequest* req)
{
    return req->getOptionSet();
}

CompilerOptionSet& EndToEndCompileRequest::getTargetOptionSet(Index targetIndex)
{
    return m_linkage->targets[targetIndex]->getOptionSet();
}

SlangResult EndToEndCompileRequest::maybeWriteContainer(const String& fileName)
{
    // If there is no container, or filename, don't write anything
    if (fileName.getLength() == 0 || !m_containerArtifact)
    {
        return SLANG_OK;
    }

    // Filter the containerArtifact into things that can be written
    ComPtr<IArtifact> writeArtifact;
    SLANG_RETURN_ON_FAIL(ArtifactContainerUtil::filter(m_containerArtifact, writeArtifact));

    // Only write if there is something to write
    if (writeArtifact)
    {
        SLANG_RETURN_ON_FAIL(ArtifactContainerUtil::writeContainer(writeArtifact, fileName));
    }

    return SLANG_OK;
}

void EndToEndCompileRequest::generateOutput(ComponentType* program)
{
    // When dynamic dispatch is disabled, the program must
    // be fully specialized by now. So we check if we still
    // have unspecialized generic/existential parameters,
    // and report them as an error.
    //
    auto specializationParamCount = program->getSpecializationParamCount();
    if (getOptionSet().getBoolOption(CompilerOptionName::DisableDynamicDispatch) &&
        specializationParamCount != 0)
    {
        auto sink = getSink();

        for (Index ii = 0; ii < specializationParamCount; ++ii)
        {
            auto specializationParam = program->getSpecializationParam(ii);
            if (auto decl = as<Decl>(specializationParam.object))
            {
                sink->diagnose(Diagnostics::SpecializationParameterOfNameNotSpecialized{
                    .param = decl->getName()->text,
                    .location = specializationParam.loc});
            }
            else if (auto type = as<Type>(specializationParam.object))
            {
                sink->diagnose(Diagnostics::SpecializationParameterOfNameNotSpecialized{
                    .param = type ? type->toString() : "<unknown type>",
                    .location = specializationParam.loc});
            }
            else
            {
                sink->diagnose(Diagnostics::SpecializationParameterNotSpecialized{
                    .location = specializationParam.loc});
            }
        }

        return;
    }


    // Go through the code-generation targets that the user
    // has specified, and generate code for each of them.
    //
    auto linkage = getLinkage();
    for (auto targetReq : linkage->targets)
    {
        if (targetReq->getOptionSet().getBoolOption(CompilerOptionName::EmbedDownstreamIR))
            continue;

        auto targetProgram = program->getTargetProgram(targetReq);
        generateOutput(targetProgram);
    }
}

void EndToEndCompileRequest::generateOutput()
{
    SLANG_PROFILE;
    generateOutput(getSpecializedGlobalAndEntryPointsComponentType());

    // Do not emit any artifacts if code generation produced errors.
    if (getSink()->getErrorCount() != 0)
        return;

    // If we are in command-line mode, we might be expected to actually
    // write output to one or more files here.

    if (m_isCommandLineCompile && m_containerFormat == ContainerFormat::None)
    {
        if (SLANG_FAILED(_validateSeparateDebugInfoOutputPaths()))
            return;

        if (SLANG_FAILED(_validateCoverageManifestOutputPaths()))
            return;

        auto linkage = getLinkage();
        auto program = getSpecializedGlobalAndEntryPointsComponentType();

        for (auto targetReq : linkage->targets)
        {
            auto targetProgram = program->getTargetProgram(targetReq);

            if (targetProgram->getOptionSet().getBoolOption(
                    CompilerOptionName::GenerateWholeProgram))
            {
                if (const auto artifact = targetProgram->getExistingWholeProgramResult())
                {
                    const auto path = _getWholeProgramPath(targetReq);

                    _maybeWriteArtifact(path, artifact);

                    // If we are compiling separate debug info, check for the additional
                    // SPIRV artifact and write that if needed.
                    _maybeWriteSeparateDebugInfoOutput(targetProgram, path, artifact);

                    _maybeWriteCoverageManifest(path, artifact);
                }
            }
            else
            {
                Index entryPointCount = program->getEntryPointCount();
                for (Index ee = 0; ee < entryPointCount; ++ee)
                {
                    if (const auto artifact = targetProgram->getExistingEntryPointResult(ee))
                    {
                        const auto path = _getEntryPointPath(targetReq, ee);

                        _maybeWriteArtifact(path, artifact);

                        // If we are compiling separate debug info, check for the additional
                        // SPIRV artifact and write that if needed.
                        _maybeWriteSeparateDebugInfoOutput(targetProgram, path, artifact);

                        _maybeWriteCoverageManifest(path, artifact);
                    }
                }
            }
        }
    }

    // Maybe create the container
    maybeCreateContainer();

    // If it's a command line compile we may need to write the container to a file
    if (m_isCommandLineCompile)
    {
        // TODO(JS):
        // We could write the container into a source embedded format potentially

        maybeWriteContainer(m_containerOutputPath);

        writeDependencyFile(this);
    }
}

void EndToEndCompileRequest::setFileSystem(ISlangFileSystem* fileSystem)
{
    getLinkage()->setFileSystem(fileSystem);
}

void EndToEndCompileRequest::setCompileFlags(SlangCompileFlags flags)
{
    if (flags & SLANG_COMPILE_FLAG_NO_MANGLING)
        getOptionSet().set(CompilerOptionName::NoMangle, true);
    if (flags & SLANG_COMPILE_FLAG_NO_CODEGEN)
        getOptionSet().set(CompilerOptionName::SkipCodeGen, true);
    if (flags & SLANG_COMPILE_FLAG_OBFUSCATE)
        getOptionSet().set(CompilerOptionName::Obfuscate, true);
}

SlangCompileFlags EndToEndCompileRequest::getCompileFlags()
{
    SlangCompileFlags result = 0;
    if (getOptionSet().getBoolOption(CompilerOptionName::NoMangle))
        result |= SLANG_COMPILE_FLAG_NO_MANGLING;
    if (getOptionSet().getBoolOption(CompilerOptionName::SkipCodeGen))
        result |= SLANG_COMPILE_FLAG_NO_CODEGEN;
    if (getOptionSet().shouldObfuscateCode())
        result |= SLANG_COMPILE_FLAG_OBFUSCATE;
    return result;
}

void EndToEndCompileRequest::setDumpIntermediates(int enable)
{
    getOptionSet().set(CompilerOptionName::DumpIntermediates, enable);
}

void EndToEndCompileRequest::setTrackLiveness(bool v)
{
    getOptionSet().set(CompilerOptionName::TrackLiveness, v);
}

void EndToEndCompileRequest::setDumpIntermediatePrefix(const char* prefix)
{
    getOptionSet().set(CompilerOptionName::DumpIntermediatePrefix, String(prefix));
}

void EndToEndCompileRequest::setLineDirectiveMode(SlangLineDirectiveMode mode)
{
    getOptionSet().set(CompilerOptionName::LineDirectiveMode, mode);
}

void EndToEndCompileRequest::setCommandLineCompilerMode()
{
    m_isCommandLineCompile = true;

    // legacy slangc tool defaults to column major layout.
    if (!getOptionSet().hasOption(CompilerOptionName::MatrixLayoutRow))
        getOptionSet().setMatrixLayoutMode(kMatrixLayoutMode_ColumnMajor);
}

void EndToEndCompileRequest::_completeTargetRequest(UInt targetIndex)
{
    auto linkage = getLinkage();

    TargetRequest* targetRequest = linkage->targets[Index(targetIndex)];

    targetRequest->getOptionSet().inheritFrom(getLinkage()->m_optionSet);
    targetRequest->getOptionSet().inheritFrom(m_optionSetForDefaultTarget);
}

void EndToEndCompileRequest::setCodeGenTarget(SlangCompileTarget target)
{
    auto linkage = getLinkage();
    linkage->targets.clear();
    const auto targetIndex = linkage->addTarget(CodeGenTarget(target));
    SLANG_ASSERT(targetIndex == 0);
    _completeTargetRequest(0);
}

int EndToEndCompileRequest::addCodeGenTarget(SlangCompileTarget target)
{
    const auto targetIndex = getLinkage()->addTarget(CodeGenTarget(target));
    _completeTargetRequest(targetIndex);
    return int(targetIndex);
}

void EndToEndCompileRequest::setTargetProfile(int targetIndex, SlangProfileID profile)
{
    getTargetOptionSet(targetIndex).setProfile(Profile(profile));
}

void EndToEndCompileRequest::setTargetFlags(int targetIndex, SlangTargetFlags flags)
{
    getTargetOptionSet(targetIndex).setTargetFlags(flags);
}

void EndToEndCompileRequest::setTargetForceGLSLScalarBufferLayout(int targetIndex, bool value)
{
    getTargetOptionSet(targetIndex).set(CompilerOptionName::GLSLForceScalarLayout, value);
}

void EndToEndCompileRequest::setTargetForceDXLayout(int targetIndex, bool value)
{
    getTargetOptionSet(targetIndex).set(CompilerOptionName::ForceDXLayout, value);
}

void EndToEndCompileRequest::setTargetForceCLayout(int targetIndex, bool value)
{
    getTargetOptionSet(targetIndex).set(CompilerOptionName::ForceCLayout, value);
}

void EndToEndCompileRequest::setTargetFloatingPointMode(
    int targetIndex,
    SlangFloatingPointMode mode)
{
    getTargetOptionSet(targetIndex)
        .set(CompilerOptionName::FloatingPointMode, FloatingPointMode(mode));
}

void EndToEndCompileRequest::setMatrixLayoutMode(SlangMatrixLayoutMode mode)
{
    getOptionSet().setMatrixLayoutMode((MatrixLayoutMode)mode);
}

void EndToEndCompileRequest::setTargetMatrixLayoutMode(int targetIndex, SlangMatrixLayoutMode mode)
{
    getTargetOptionSet(targetIndex).setMatrixLayoutMode(MatrixLayoutMode(mode));
}

void EndToEndCompileRequest::setTargetGenerateWholeProgram(int targetIndex, bool value)
{
    getTargetOptionSet(targetIndex).set(CompilerOptionName::GenerateWholeProgram, value);
}

void EndToEndCompileRequest::setTargetEmbedDownstreamIR(int targetIndex, bool value)
{
    getTargetOptionSet(targetIndex).set(CompilerOptionName::EmbedDownstreamIR, value);
}

void EndToEndCompileRequest::setTargetLineDirectiveMode(
    SlangInt targetIndex,
    SlangLineDirectiveMode mode)
{
    getTargetOptionSet(targetIndex)
        .set(CompilerOptionName::LineDirectiveMode, LineDirectiveMode(mode));
}

void EndToEndCompileRequest::overrideDiagnosticSeverity(
    SlangInt messageID,
    SlangSeverity overrideSeverity)
{
    getSink()->overrideDiagnosticSeverity(int(messageID), Severity(overrideSeverity));
}

SlangDiagnosticFlags EndToEndCompileRequest::getDiagnosticFlags()
{
    DiagnosticSink::Flags sinkFlags = getSink()->getFlags();

    SlangDiagnosticFlags flags = 0;

    if (sinkFlags & DiagnosticSink::Flag::VerbosePath)
        flags |= SLANG_DIAGNOSTIC_FLAG_VERBOSE_PATHS;

    if (sinkFlags & DiagnosticSink::Flag::TreatWarningsAsErrors)
        flags |= SLANG_DIAGNOSTIC_FLAG_TREAT_WARNINGS_AS_ERRORS;

    return flags;
}

void EndToEndCompileRequest::setDiagnosticFlags(SlangDiagnosticFlags flags)
{
    DiagnosticSink::Flags sinkFlags = getSink()->getFlags();

    if (flags & SLANG_DIAGNOSTIC_FLAG_VERBOSE_PATHS)
        sinkFlags |= DiagnosticSink::Flag::VerbosePath;
    else
        sinkFlags &= ~DiagnosticSink::Flag::VerbosePath;

    if (flags & SLANG_DIAGNOSTIC_FLAG_TREAT_WARNINGS_AS_ERRORS)
        sinkFlags |= DiagnosticSink::Flag::TreatWarningsAsErrors;
    else
        sinkFlags &= ~DiagnosticSink::Flag::TreatWarningsAsErrors;

    getSink()->setFlags(sinkFlags);
}

SlangResult EndToEndCompileRequest::addTargetCapability(
    SlangInt targetIndex,
    SlangCapabilityID capability)
{
    auto& targets = getLinkage()->targets;
    if (targetIndex < 0 || targetIndex >= targets.getCount())
        return SLANG_E_INVALID_ARG;
    getTargetOptionSet(targetIndex).addCapabilityAtom(CapabilityName(capability));
    return SLANG_OK;
}

void EndToEndCompileRequest::setDebugInfoLevel(SlangDebugInfoLevel level)
{
    getOptionSet().set(CompilerOptionName::DebugInformation, DebugInfoLevel(level));
}

void EndToEndCompileRequest::setDebugInfoFormat(SlangDebugInfoFormat format)
{
    getOptionSet().set(CompilerOptionName::DebugInformationFormat, DebugInfoFormat(format));
}

void EndToEndCompileRequest::setOptimizationLevel(SlangOptimizationLevel level)
{
    getOptionSet().set(CompilerOptionName::Optimization, OptimizationLevel(level));
}

void EndToEndCompileRequest::setOutputContainerFormat(SlangContainerFormat format)
{
    m_containerFormat = ContainerFormat(format);
}

void EndToEndCompileRequest::setPassThrough(SlangPassThrough inPassThrough)
{
    m_passThrough = PassThroughMode(inPassThrough);
}

void EndToEndCompileRequest::setReportDownstreamTime(bool value)
{
    getOptionSet().set(CompilerOptionName::ReportDownstreamTime, value);
}

void EndToEndCompileRequest::setReportPerfBenchmark(bool value)
{
    getOptionSet().set(CompilerOptionName::ReportPerfBenchmark, value);
}

void EndToEndCompileRequest::setSkipSPIRVValidation(bool value)
{
    getOptionSet().set(CompilerOptionName::SkipSPIRVValidation, value);
}

void EndToEndCompileRequest::setTargetUseMinimumSlangOptimization(int targetIndex, bool value)
{
    getTargetOptionSet(targetIndex).set(CompilerOptionName::MinimumSlangOptimization, value);
}

void EndToEndCompileRequest::setIgnoreCapabilityCheck(bool value)
{
    getOptionSet().set(CompilerOptionName::IgnoreCapabilities, value);
}

void EndToEndCompileRequest::setDiagnosticCallback(
    SlangDiagnosticCallback callback,
    void const* userData)
{
    // Don't set IsConsole flag - the callback destination is unknown.
    // Command-line tools should handle TTY detection themselves and
    // set DiagnosticColor to ALWAYS or NEVER explicitly.
    ComPtr<ISlangWriter> writer(new CallbackWriter(callback, userData, 0));
    setWriter(WriterChannel::Diagnostic, writer);
}

void EndToEndCompileRequest::setWriter(SlangWriterChannel chan, ISlangWriter* writer)
{
    setWriter(WriterChannel(chan), writer);
}

ISlangWriter* EndToEndCompileRequest::getWriter(SlangWriterChannel chan)
{
    return getWriter(WriterChannel(chan));
}

void EndToEndCompileRequest::addSearchPath(const char* path)
{
    getOptionSet().addSearchPath(path);
}

void EndToEndCompileRequest::addPreprocessorDefine(const char* key, const char* value)
{
    getOptionSet().addPreprocessorDefine(key, value);
}

void EndToEndCompileRequest::setEnableEffectAnnotations(bool value)
{
    getOptionSet().set(CompilerOptionName::EnableEffectAnnotations, value);
}

char const* EndToEndCompileRequest::getDiagnosticOutput()
{
    return m_diagnosticOutput.begin();
}

SlangResult EndToEndCompileRequest::getDiagnosticOutputBlob(ISlangBlob** outBlob)
{
    if (!outBlob)
        return SLANG_E_INVALID_ARG;

    if (!m_diagnosticOutputBlob)
    {
        m_diagnosticOutputBlob = StringUtil::createStringBlob(m_diagnosticOutput);
    }

    ComPtr<ISlangBlob> resultBlob = m_diagnosticOutputBlob;
    *outBlob = resultBlob.detach();
    return SLANG_OK;
}

int EndToEndCompileRequest::addTranslationUnit(SlangSourceLanguage language, char const* inName)
{
    auto frontEndReq = getFrontEndReq();
    NamePool* namePool = frontEndReq->getNamePool();

    // Work out a module name. Can be nullptr if so will generate a name
    Name* moduleName = inName ? namePool->getName(inName) : frontEndReq->m_defaultModuleName;

    // If moduleName is nullptr a name will be generated
    return frontEndReq->addTranslationUnit(Slang::SourceLanguage(language), moduleName);
}

void EndToEndCompileRequest::setDefaultModuleName(const char* defaultModuleName)
{
    auto frontEndReq = getFrontEndReq();
    NamePool* namePool = frontEndReq->getNamePool();
    frontEndReq->m_defaultModuleName = namePool->getName(defaultModuleName);
}

SlangResult _addLibraryReference(
    EndToEndCompileRequest* req,
    ModuleLibrary* moduleLibrary,
    bool includeEntryPoint)
{
    FrontEndCompileRequest* frontEndRequest = req->getFrontEndReq();

    if (includeEntryPoint)
    {
        frontEndRequest->m_extraEntryPoints.addRange(
            moduleLibrary->m_entryPoints.getBuffer(),
            moduleLibrary->m_entryPoints.getCount());
    }

    for (auto m : moduleLibrary->m_modules)
    {
        RefPtr<TranslationUnitRequest> tu = new TranslationUnitRequest(frontEndRequest, m);
        frontEndRequest->translationUnits.add(tu);
        // For modules loaded for EndToEndCompileRequest,
        // we don't need the automatically discovered entrypoints.
        if (!includeEntryPoint)
            m->getEntryPoints().clear();
    }
    return SLANG_OK;
}

SlangResult _addLibraryReference(
    EndToEndCompileRequest* req,
    String path,
    IArtifact* artifact,
    bool includeEntryPoint)
{
    auto desc = artifact->getDesc();

    // TODO(JS):
    // This isn't perhaps the best way to handle this scenario, as IArtifact can
    // support lazy evaluation, with suitable hander.
    // For now we just read in and strip out the bits we want.
    if (isDerivedFrom(desc.kind, ArtifactKind::Container) &&
        isDerivedFrom(desc.payload, ArtifactPayload::CompileResults))
    {
        // We want to read as a file system
        ComPtr<IArtifact> container;

        SLANG_RETURN_ON_FAIL(ArtifactContainerUtil::readContainer(artifact, container));

        // Find the payload... It should be linkable
        if (!ArtifactDescUtil::isLinkable(container->getDesc()))
        {
            return SLANG_FAIL;
        }

        ComPtr<IModuleLibrary> libraryIntf;
        SLANG_RETURN_ON_FAIL(
            loadModuleLibrary(ArtifactKeep::Yes, container, path, req, libraryIntf));

        auto library = as<ModuleLibrary>(libraryIntf);

        // Look for source maps
        for (auto associated : container->getAssociated())
        {
            auto assocDesc = associated->getDesc();

            // If we find an obfuscated source map load it and associate
            if (isDerivedFrom(assocDesc.kind, ArtifactKind::Json) &&
                isDerivedFrom(assocDesc.payload, ArtifactPayload::SourceMap) &&
                isDerivedFrom(assocDesc.style, ArtifactStyle::Obfuscated))
            {
                ComPtr<ICastable> castable;
                SLANG_RETURN_ON_FAIL(associated->getOrCreateRepresentation(
                    SourceMap::getTypeGuid(),
                    ArtifactKeep::Yes,
                    castable.writeRef()));
                auto sourceMapBox = asBoxValue<SourceMap>(castable);
                SLANG_ASSERT(sourceMapBox);

                // TODO(JS):
                // There is perhaps (?) a risk here that we might copy the obfuscated map
                // into some output container. Currently that only happens for source maps
                // that are from translation units.
                //
                // On the other hand using "import" is a way that such source maps *would* be
                // copied into the output, and that is something that could be a vector
                // for leaking.
                //
                // That isn't a risk from -r though because, it doesn't create a translation
                // unit(s).
                for (auto module : library->m_modules)
                {
                    module->getIRModule()->setObfuscatedSourceMap(sourceMapBox);
                }

                // Look up the source file
                auto sourceManager = req->getSink()->getSourceManager();

                auto name = Path::getFileNameWithoutExt(associated->getName());

                if (name.getLength())
                {
                    // Note(tfoley): There is a subtle requirement here, that any
                    // source file `name` that might be searched for here *must*
                    // have been added to the `sourceManager` already, as a
                    // byproduct of debug source location information getting
                    // deserialized as part of the call to `loadModuleLibrary()` above.
                    //
                    // The implicit dependency is frustrating, and could potentially
                    // break if somehow the debug info chunk was stripped from a binary,
                    // while the source map was left in (which should be valid, even if
                    // it is unlikely to be what a user wants).
                    //
                    // Ideally the source map would either be made an integral part of
                    // the debug source location chunk, so they are loaded together,
                    // or the `SourceManager` would be adapted so that it can store
                    // registered source maps independent of whether or not the
                    // corresponding source file(s) have been loaded.

                    auto sourceFile = sourceManager->findSourceFileByPathRecursively(name);
                    SLANG_ASSERT(sourceFile);
                    sourceFile->setSourceMap(sourceMapBox, SourceMapKind::Obfuscated);
                }
            }
        }

        SLANG_RETURN_ON_FAIL(_addLibraryReference(req, library, includeEntryPoint));
        return SLANG_OK;
    }

    if (desc.kind == ArtifactKind::Library && desc.payload == ArtifactPayload::SlangIR)
    {
        ComPtr<IModuleLibrary> libraryIntf;

        SLANG_RETURN_ON_FAIL(
            loadModuleLibrary(ArtifactKeep::Yes, artifact, path, req, libraryIntf));

        auto library = as<ModuleLibrary>(libraryIntf);
        if (!library)
        {
            return SLANG_FAIL;
        }

        SLANG_RETURN_ON_FAIL(_addLibraryReference(req, library, includeEntryPoint));
        return SLANG_OK;
    }

    // TODO(JS):
    // Do we want to check the path exists?

    // Add to the m_libModules
    auto linkage = req->getLinkage();
    linkage->m_libModules.add(ComPtr<IArtifact>(artifact));

    return SLANG_OK;
}

SlangResult EndToEndCompileRequest::addLibraryReference(
    const char* basePath,
    const void* libData,
    size_t libDataSize)
{
    // We need to deserialize and add the modules
    ComPtr<IModuleLibrary> library;

    auto libBlob = RawBlob::create((const Byte*)libData, libDataSize);

    SLANG_RETURN_ON_FAIL(
        loadModuleLibrary(libBlob, (const Byte*)libData, libDataSize, basePath, this, library));

    // Create an artifact without any name (as one is not provided)
    auto artifact =
        Artifact::create(ArtifactDesc::make(ArtifactKind::Library, ArtifactPayload::SlangIR));
    artifact->addRepresentation(library);

    return _addLibraryReference(this, basePath, artifact, true);
}

void EndToEndCompileRequest::addTranslationUnitPreprocessorDefine(
    int translationUnitIndex,
    const char* key,
    const char* value)
{
    getFrontEndReq()->translationUnits[translationUnitIndex]->preprocessorDefinitions[key] = value;
}

void EndToEndCompileRequest::addTranslationUnitSourceFile(
    int translationUnitIndex,
    char const* path)
{
    auto frontEndReq = getFrontEndReq();
    if (!path)
        return;
    if (translationUnitIndex < 0)
        return;
    if (Index(translationUnitIndex) >= frontEndReq->translationUnits.getCount())
        return;

    frontEndReq->addTranslationUnitSourceFile(translationUnitIndex, path);
}

void EndToEndCompileRequest::addTranslationUnitSourceString(
    int translationUnitIndex,
    char const* path,
    char const* source)
{
    if (!source)
        return;
    addTranslationUnitSourceStringSpan(translationUnitIndex, path, source, source + strlen(source));
}

void EndToEndCompileRequest::addTranslationUnitSourceStringSpan(
    int translationUnitIndex,
    char const* path,
    char const* sourceBegin,
    char const* sourceEnd)
{
    auto frontEndReq = getFrontEndReq();
    if (!sourceBegin)
        return;
    if (translationUnitIndex < 0)
        return;
    if (Index(translationUnitIndex) >= frontEndReq->translationUnits.getCount())
        return;

    if (!path)
        path = "";

    const auto slice = UnownedStringSlice(sourceBegin, sourceEnd);

    auto blob = RawBlob::create(slice.begin(), slice.getLength());

    frontEndReq->addTranslationUnitSourceBlob(translationUnitIndex, path, blob);
}

void EndToEndCompileRequest::addTranslationUnitSourceBlob(
    int translationUnitIndex,
    char const* path,
    ISlangBlob* sourceBlob)
{
    auto frontEndReq = getFrontEndReq();
    if (!sourceBlob)
        return;
    if (translationUnitIndex < 0)
        return;
    if (Slang::Index(translationUnitIndex) >= frontEndReq->translationUnits.getCount())
        return;

    if (!path)
        path = "";

    frontEndReq->addTranslationUnitSourceBlob(translationUnitIndex, path, sourceBlob);
}


int EndToEndCompileRequest::addEntryPoint(
    int translationUnitIndex,
    char const* name,
    SlangStage stage)
{
    return addEntryPointEx(translationUnitIndex, name, stage, 0, nullptr);
}

int EndToEndCompileRequest::addEntryPointEx(
    int translationUnitIndex,
    char const* name,
    SlangStage stage,
    int genericParamTypeNameCount,
    char const** genericParamTypeNames)
{
    auto frontEndReq = getFrontEndReq();
    if (!name)
        return -1;
    if (translationUnitIndex < 0)
        return -1;
    if (Index(translationUnitIndex) >= frontEndReq->translationUnits.getCount())
        return -1;

    List<String> typeNames;
    for (int i = 0; i < genericParamTypeNameCount; i++)
        typeNames.add(genericParamTypeNames[i]);

    return addEntryPoint(translationUnitIndex, name, Profile(Stage(stage)), typeNames);
}

SlangResult EndToEndCompileRequest::setGlobalGenericArgs(
    int genericArgCount,
    char const** genericArgs)
{
    auto& argStrings = m_globalSpecializationArgStrings;
    argStrings.clear();
    for (int i = 0; i < genericArgCount; i++)
        argStrings.add(genericArgs[i]);

    return SLANG_OK;
}

SlangResult EndToEndCompileRequest::setTypeNameForGlobalExistentialTypeParam(
    int slotIndex,
    char const* typeName)
{
    if (slotIndex < 0)
        return SLANG_FAIL;
    if (!typeName)
        return SLANG_FAIL;

    auto& typeArgStrings = m_globalSpecializationArgStrings;
    if (Index(slotIndex) >= typeArgStrings.getCount())
        typeArgStrings.setCount(slotIndex + 1);
    typeArgStrings[slotIndex] = String(typeName);
    return SLANG_OK;
}

SlangResult EndToEndCompileRequest::setTypeNameForEntryPointExistentialTypeParam(
    int entryPointIndex,
    int slotIndex,
    char const* typeName)
{
    if (entryPointIndex < 0)
        return SLANG_FAIL;
    if (slotIndex < 0)
        return SLANG_FAIL;
    if (!typeName)
        return SLANG_FAIL;

    if (Index(entryPointIndex) >= m_entryPoints.getCount())
        return SLANG_FAIL;

    auto& entryPointInfo = m_entryPoints[entryPointIndex];
    auto& typeArgStrings = entryPointInfo.specializationArgStrings;
    if (Index(slotIndex) >= typeArgStrings.getCount())
        typeArgStrings.setCount(slotIndex + 1);
    typeArgStrings[slotIndex] = String(typeName);
    return SLANG_OK;
}

void EndToEndCompileRequest::setAllowGLSLInput(bool value)
{
    getOptionSet().set(CompilerOptionName::AllowGLSL, value);
}

SlangResult EndToEndCompileRequest::compile()
{
    SlangResult res = SLANG_FAIL;
    double downstreamStartTime = 0.0;
    double totalStartTime = 0.0;

    if (getOptionSet().getBoolOption(CompilerOptionName::ReportDownstreamTime))
    {
        getSession()->getCompilerElapsedTime(&totalStartTime, &downstreamStartTime);
        PerformanceProfiler::getProfiler()->clear();
    }
#if !defined(SLANG_DEBUG_INTERNAL_ERROR)
    // By default we'd like to catch as many internal errors as possible,
    // and report them to the user nicely (rather than just crash their
    // application). Internally Slang currently uses exceptions for this.
    //
    // TODO: Consider using `setjmp()`-style escape so that we can work
    // with applications that disable exceptions.
    //
    // TODO: Consider supporting Windows "Structured Exception Handling"
    // so that we can also recover from a wider class of crashes.

    try
    {
        SLANG_PROFILE_SECTION(compileInner);
        res = executeActions();
    }
    catch (const AbortCompilationException& e)
    {
        // This situation indicates a fatal (but not necessarily internal) error
        // that forced compilation to terminate. There should already have been
        // a diagnostic produced, so we don't need to add one here.
        if (getSink()->getErrorCount() == 0)
        {
            // If for some reason we didn't output any diagnostic, something is
            // going wrong, but we want to make sure we at least output something.
            getSink()->diagnose(Diagnostics::CompilationAbortedDueToException{
                .exceptionType = typeid(e).name(),
                .exceptionMessage = e.Message});
        }
    }
    catch (const Exception& e)
    {
        // The compiler failed due to an internal error that was detected.
        // We will print out information on the exception to help out the user
        // in either filing a bug, or locating what in their code created
        // a problem.
        getSink()->diagnose(Diagnostics::CompilationAbortedDueToException{
            .exceptionType = typeid(e).name(),
            .exceptionMessage = e.Message});
    }
    catch (...)
    {
        // The compiler failed due to some exception that wasn't a sublass of
        // `Exception`, so something really fishy is going on. We want to
        // let the user know that we messed up, so they know to blame Slang
        // and not some other component in their system.
        getSink()->diagnose(Diagnostics::CompilationAborted{});
    }
    m_diagnosticOutput = getSink()->outputBuffer.produceString();

#else
    // When debugging, we probably don't want to filter out any errors, since
    // we are probably trying to root-cause and *fix* those errors.
    {
        res = req->executeActions();
    }
#endif

    if (getOptionSet().getBoolOption(CompilerOptionName::ReportDownstreamTime))
    {
        double downstreamEndTime = 0;
        double totalEndTime = 0;
        getSession()->getCompilerElapsedTime(&totalEndTime, &downstreamEndTime);
        double downstreamTime = downstreamEndTime - downstreamStartTime;
        String downstreamTimeStr = String(downstreamTime, "%.2f");
        getSink()->diagnose(Diagnostics::DownstreamCompileTime{.time = downstreamTimeStr});
    }
    if (getOptionSet().getBoolOption(CompilerOptionName::ReportPerfBenchmark))
    {
        StringBuilder perfResult;
        PerformanceProfiler::getProfiler()->getResult(perfResult);
        perfResult << "\nType Dictionary Size: " << getSession()->m_typeDictionarySize << "\n";
        getSink()->diagnose(
            Diagnostics::PerformanceBenchmarkResult{.benchmarkOutput = perfResult.produceString()});
    }

    // Repro dump handling
    {
        auto dumpRepro = getOptionSet().getStringOption(CompilerOptionName::DumpRepro);
        auto dumpReproOnError = getOptionSet().getBoolOption(CompilerOptionName::DumpReproOnError);

        if (dumpRepro.getLength())
        {
            SlangResult saveRes = ReproUtil::saveState(this, dumpRepro);
            if (SLANG_FAILED(saveRes))
            {
                getSink()->diagnose(Diagnostics::UnableToWriteReproFile{.path = dumpRepro});
                return saveRes;
            }
        }
        else if (dumpReproOnError && SLANG_FAILED(res))
        {
            String reproFileName;
            SlangResult saveRes = SLANG_FAIL;

            RefPtr<Stream> stream;
            if (SLANG_SUCCEEDED(ReproUtil::findUniqueReproDumpStream(this, reproFileName, stream)))
            {
                saveRes = ReproUtil::saveState(this, stream);
            }

            if (SLANG_FAILED(saveRes))
            {
                getSink()->diagnose(Diagnostics::UnableToWriteReproFile{.path = reproFileName});
            }
        }
    }

    auto reflectionPath = getOptionSet().getStringOption(CompilerOptionName::EmitReflectionJSON);
    if (reflectionPath.getLength() != 0 && SLANG_SUCCEEDED(res))
    {
        auto reflection = this->getReflection();
        if (!reflection)
        {
            getSink()->diagnose(Diagnostics::CannotEmitReflectionWithoutTarget{});
            return SLANG_FAIL;
        }
        auto bufferWriter = PrettyWriter();
        emitReflectionJSON(this, reflection, bufferWriter);
        if (reflectionPath == "-")
        {
            auto builder = bufferWriter.getBuilder();
            StdWriters::getOut().write(builder.getBuffer(), builder.getLength());
        }
        else if (SLANG_FAILED(File::writeAllText(reflectionPath, bufferWriter.getBuilder())))
        {
            getSink()->diagnose(Diagnostics::UnableToWriteFile{.path = String(reflectionPath)});
        }
    }

    return res;
}

int EndToEndCompileRequest::getDependencyFileCount()
{
    auto frontEndReq = getFrontEndReq();
    auto program = frontEndReq->getGlobalAndEntryPointsComponentType();
    return (int)program->getFileDependencies().getCount();
}

char const* EndToEndCompileRequest::getDependencyFilePath(int index)
{
    auto frontEndReq = getFrontEndReq();
    auto program = frontEndReq->getGlobalAndEntryPointsComponentType();
    SourceFile* sourceFile = program->getFileDependencies()[index];
    return sourceFile->getPathInfo().hasFoundPath()
               ? sourceFile->getPathInfo().getMostUniqueIdentity().getBuffer()
               : "unknown";
}

int EndToEndCompileRequest::getTranslationUnitCount()
{
    return (int)getFrontEndReq()->translationUnits.getCount();
}

void const* EndToEndCompileRequest::getEntryPointCode(int entryPointIndex, size_t* outSize)
{
    // Zero the size initially, in case need to return nullptr for error.
    if (outSize)
    {
        *outSize = 0;
    }

    auto linkage = getLinkage();
    auto program = getSpecializedGlobalAndEntryPointsComponentType();

    // TODO: We should really accept a target index in this API
    Index targetIndex = 0;
    auto targetCount = linkage->targets.getCount();
    if (targetIndex >= targetCount)
        return nullptr;
    auto targetReq = linkage->targets[targetIndex];


    if (entryPointIndex < 0)
        return nullptr;
    if (Index(entryPointIndex) >= program->getEntryPointCount())
        return nullptr;
    auto entryPoint = program->getEntryPoint(entryPointIndex);

    auto targetProgram = program->getTargetProgram(targetReq);
    if (!targetProgram)
        return nullptr;
    IArtifact* artifact = targetProgram->getExistingEntryPointResult(entryPointIndex);
    if (!artifact)
    {
        return nullptr;
    }

    ComPtr<ISlangBlob> blob;
    SLANG_RETURN_NULL_ON_FAIL(artifact->loadBlob(ArtifactKeep::Yes, blob.writeRef()));

    if (outSize)
    {
        *outSize = blob->getBufferSize();
    }

    return (void*)blob->getBufferPointer();
}

SlangResult EndToEndCompileRequest::getCompileTimeProfile(
    ISlangProfiler** compileTimeProfile,
    bool shouldClear)
{
    if (compileTimeProfile == nullptr)
    {
        return SLANG_E_INVALID_ARG;
    }

    SlangProfiler* profiler = new SlangProfiler(PerformanceProfiler::getProfiler());

    if (shouldClear)
    {
        PerformanceProfiler::getProfiler()->clear();
    }

    ComPtr<ISlangProfiler> result(profiler);
    *compileTimeProfile = result.detach();
    return SLANG_OK;
}

static SlangResult _getEntryPointResult(
    EndToEndCompileRequest* req,
    int entryPointIndex,
    int targetIndex,
    ComPtr<IArtifact>& outArtifact)
{
    auto linkage = req->getLinkage();
    auto program = req->getSpecializedGlobalAndEntryPointsComponentType();

    Index targetCount = linkage->targets.getCount();
    if ((targetIndex < 0) || (targetIndex >= targetCount))
    {
        return SLANG_E_INVALID_ARG;
    }
    auto targetReq = linkage->targets[targetIndex];

    // Get the entry point count on the program, rather than (say) req->m_entryPoints.getCount()
    // because
    // 1) The entry point is fetched from the program anyway so must be consistent
    // 2) The req may not have all entry points (for example when an entry point is in a module)
    const Index entryPointCount = program->getEntryPointCount();

    if ((entryPointIndex < 0) || (entryPointIndex >= entryPointCount))
    {
        return SLANG_E_INVALID_ARG;
    }
    auto entryPointReq = program->getEntryPoint(entryPointIndex);

    auto targetProgram = program->getTargetProgram(targetReq);
    if (!targetProgram)
        return SLANG_FAIL;

    outArtifact = targetProgram->getExistingEntryPointResult(entryPointIndex);
    return SLANG_OK;
}

static SlangResult _getWholeProgramResult(
    EndToEndCompileRequest* req,
    int targetIndex,
    ComPtr<IArtifact>& outArtifact)
{
    auto linkage = req->getLinkage();
    auto program = req->getSpecializedGlobalAndEntryPointsComponentType();

    if (!program)
    {
        return SLANG_FAIL;
    }

    Index targetCount = linkage->targets.getCount();
    if ((targetIndex < 0) || (targetIndex >= targetCount))
    {
        return SLANG_E_INVALID_ARG;
    }
    auto targetReq = linkage->targets[targetIndex];

    auto targetProgram = program->getTargetProgram(targetReq);
    if (!targetProgram)
        return SLANG_FAIL;
    outArtifact = targetProgram->getExistingWholeProgramResult();
    if (!outArtifact)
        return SLANG_FAIL;
    return SLANG_OK;
}

SlangResult EndToEndCompileRequest::getEntryPointCodeBlob(
    int entryPointIndex,
    int targetIndex,
    ISlangBlob** outBlob)
{
    if (!outBlob)
        return SLANG_E_INVALID_ARG;
    ComPtr<IArtifact> artifact;
    SLANG_RETURN_ON_FAIL(_getEntryPointResult(this, entryPointIndex, targetIndex, artifact));
    SLANG_RETURN_ON_FAIL(artifact->loadBlob(ArtifactKeep::Yes, outBlob));

    return SLANG_OK;
}

SlangResult EndToEndCompileRequest::getEntryPointHostCallable(
    int entryPointIndex,
    int targetIndex,
    ISlangSharedLibrary** outSharedLibrary)
{
    if (!outSharedLibrary)
        return SLANG_E_INVALID_ARG;
    ComPtr<IArtifact> artifact;
    SLANG_RETURN_ON_FAIL(_getEntryPointResult(this, entryPointIndex, targetIndex, artifact));
    SLANG_RETURN_ON_FAIL(artifact->loadSharedLibrary(ArtifactKeep::Yes, outSharedLibrary));
    return SLANG_OK;
}

SlangResult EndToEndCompileRequest::getTargetCodeBlob(int targetIndex, ISlangBlob** outBlob)
{
    if (!outBlob)
        return SLANG_E_INVALID_ARG;

    ComPtr<IArtifact> artifact;
    SLANG_RETURN_ON_FAIL(_getWholeProgramResult(this, targetIndex, artifact));
    SLANG_RETURN_ON_FAIL(artifact->loadBlob(ArtifactKeep::Yes, outBlob));
    return SLANG_OK;
}

SlangResult EndToEndCompileRequest::getTargetHostCallable(
    int targetIndex,
    ISlangSharedLibrary** outSharedLibrary)
{
    if (!outSharedLibrary)
        return SLANG_E_INVALID_ARG;

    ComPtr<IArtifact> artifact;
    SLANG_RETURN_ON_FAIL(_getWholeProgramResult(this, targetIndex, artifact));
    SLANG_RETURN_ON_FAIL(artifact->loadSharedLibrary(ArtifactKeep::Yes, outSharedLibrary));
    return SLANG_OK;
}

char const* EndToEndCompileRequest::getEntryPointSource(int entryPointIndex)
{
    return (char const*)getEntryPointCode(entryPointIndex, nullptr);
}

ISlangMutableFileSystem* EndToEndCompileRequest::getCompileRequestResultAsFileSystem()
{
    if (!m_containerFileSystem)
    {
        if (m_containerArtifact)
        {
            ComPtr<ISlangMutableFileSystem> fileSystem(new MemoryFileSystem);

            // Filter the containerArtifact into things that can be written
            ComPtr<IArtifact> writeArtifact;
            if (SLANG_SUCCEEDED(
                    ArtifactContainerUtil::filter(m_containerArtifact, writeArtifact)) &&
                writeArtifact)
            {
                if (SLANG_SUCCEEDED(
                        ArtifactContainerUtil::writeContainer(writeArtifact, "", fileSystem)))
                {
                    m_containerFileSystem.swap(fileSystem);
                }
            }
        }
    }

    return m_containerFileSystem;
}

void const* EndToEndCompileRequest::getCompileRequestCode(size_t* outSize)
{
    if (m_containerArtifact)
    {
        ComPtr<ISlangBlob> containerBlob;
        if (SLANG_SUCCEEDED(
                m_containerArtifact->loadBlob(ArtifactKeep::Yes, containerBlob.writeRef())))
        {
            *outSize = containerBlob->getBufferSize();
            return containerBlob->getBufferPointer();
        }
    }

    // Container blob does not have any contents
    *outSize = 0;
    return nullptr;
}

SlangResult EndToEndCompileRequest::getContainerCode(ISlangBlob** outBlob)
{
    if (m_containerArtifact)
    {
        ComPtr<ISlangBlob> containerBlob;
        if (SLANG_SUCCEEDED(
                m_containerArtifact->loadBlob(ArtifactKeep::Yes, containerBlob.writeRef())))
        {
            *outBlob = containerBlob.detach();
            return SLANG_OK;
        }
    }
    return SLANG_FAIL;
}

SlangResult EndToEndCompileRequest::loadRepro(
    ISlangFileSystem* fileSystem,
    const void* data,
    size_t size)
{
    ComPtr<ISlangBlob> reproBlob;
    SLANG_RETURN_ON_FAIL(
        ReproUtil::loadState((const uint8_t*)data, size, getSink(), reproBlob.writeRef()));

    MemoryOffsetBase base;
    base.set(const_cast<void*>(reproBlob->getBufferPointer()), reproBlob->getBufferSize());

    ReproUtil::RequestState* requestState = const_cast<ReproUtil::RequestState*>(
        ReproUtil::getRequest(reproBlob->getBufferPointer(), reproBlob->getBufferSize()));

    SLANG_RETURN_ON_FAIL(ReproUtil::load(base, requestState, fileSystem, this));
    return SLANG_OK;
}

SlangResult EndToEndCompileRequest::saveRepro(ISlangBlob** outBlob)
{
    OwnedMemoryStream stream(FileAccess::Write);

    SLANG_RETURN_ON_FAIL(ReproUtil::saveState(this, &stream));

    // Put the content of the stream in the blob

    List<uint8_t> data;
    stream.swapContents(data);

    *outBlob = ListBlob::moveCreate(data).detach();
    return SLANG_OK;
}

SlangResult EndToEndCompileRequest::enableReproCapture()
{
    getLinkage()->setRequireCacheFileSystem(true);
    return SLANG_OK;
}

SlangResult EndToEndCompileRequest::processCommandLineArguments(
    char const* const* args,
    int argCount)
{
    return parseOptions(this, argCount, args);
}

SlangReflection* EndToEndCompileRequest::getReflection()
{
    auto linkage = getLinkage();
    auto program = getSpecializedGlobalAndEntryPointsComponentType();
    if (!(linkage && program))
        return nullptr;

    // Note(tfoley): The API signature doesn't let the client
    // specify which target they want to access reflection
    // information for, so for now we default to the first one.
    //
    // TODO: Add a new `spGetReflectionForTarget(req, targetIndex)`
    // so that we can do this better, and make it clear that
    // `spGetReflection()` is shorthand for `targetIndex == 0`.
    //
    Slang::Index targetIndex = 0;
    auto targetCount = linkage->targets.getCount();
    if (targetIndex >= targetCount)
        return nullptr;

    auto targetReq = linkage->targets[targetIndex];
    auto targetProgram = program->getTargetProgram(targetReq);
    if (!targetProgram)
        return nullptr;

    DiagnosticSink sink(linkage->getSourceManager(), Lexer::sourceLocationLexer);
    auto programLayout = targetProgram->getOrCreateLayout(&sink);

    return (SlangReflection*)programLayout;
}

SlangResult EndToEndCompileRequest::getProgram(slang::IComponentType** outProgram)
{
    auto program = getSpecializedGlobalComponentType();
    *outProgram = Slang::ComPtr<slang::IComponentType>(program).detach();
    return SLANG_OK;
}

SlangResult EndToEndCompileRequest::getProgramWithEntryPoints(slang::IComponentType** outProgram)
{
    auto program = getSpecializedGlobalAndEntryPointsComponentType();
    *outProgram = Slang::ComPtr<slang::IComponentType>(program).detach();
    return SLANG_OK;
}

SlangResult EndToEndCompileRequest::getModule(
    SlangInt translationUnitIndex,
    slang::IModule** outModule)
{
    auto module = getFrontEndReq()->getTranslationUnit(translationUnitIndex)->getModule();

    *outModule = Slang::ComPtr<slang::IModule>(module).detach();
    return SLANG_OK;
}

SlangResult EndToEndCompileRequest::getSession(slang::ISession** outSession)
{
    auto session = getLinkage();
    *outSession = Slang::ComPtr<slang::ISession>(session).detach();
    return SLANG_OK;
}

SlangResult EndToEndCompileRequest::getEntryPoint(
    SlangInt entryPointIndex,
    slang::IComponentType** outEntryPoint)
{
    auto entryPoint = getSpecializedEntryPointComponentType(entryPointIndex);
    *outEntryPoint = Slang::ComPtr<slang::IComponentType>(entryPoint).detach();
    return SLANG_OK;
}

SlangResult EndToEndCompileRequest::isParameterLocationUsed(
    Int entryPointIndex,
    Int targetIndex,
    SlangParameterCategory category,
    UInt spaceIndex,
    UInt registerIndex,
    bool& outUsed)
{
    if (!ShaderBindingRange::isUsageTracked((slang::ParameterCategory)category))
        return SLANG_E_NOT_AVAILABLE;

    ComPtr<IArtifact> artifact;
    if (SLANG_FAILED(_getEntryPointResult(
            this,
            static_cast<int>(entryPointIndex),
            static_cast<int>(targetIndex),
            artifact)))
        return SLANG_E_INVALID_ARG;

    if (!artifact)
        return SLANG_E_NOT_AVAILABLE;

    // Find a rep
    auto metadata = findAssociatedRepresentation<IArtifactPostEmitMetadata>(artifact);
    if (!metadata)
        return SLANG_E_NOT_AVAILABLE;

    return metadata->isParameterLocationUsed(category, spaceIndex, registerIndex, outUsed);
}

} // namespace Slang
