// slang-emit.cpp

#include "compiler-core/slang-artifact-associated-impl.h"
#include "compiler-core/slang-artifact-desc-util.h"
#include "compiler-core/slang-artifact-impl.h"
#include "compiler-core/slang-artifact-util.h"
#include "compiler-core/slang-name.h"
#include "compiler-core/slang-slice-allocator.h"
#include "core/slang-castable.h"
#include "core/slang-performance-profiler.h"
#include "core/slang-type-text-util.h"
#include "core/slang-writer.h"
#include "slang-capability.h"
#include "slang-check-out-of-bound-access.h"
#include "slang-emit-c-like.h"
#include "slang-emit-cpp.h"
#include "slang-emit-cuda.h"
#include "slang-emit-glsl.h"
#include "slang-emit-hlsl.h"
#include "slang-emit-llvm.h"
#include "slang-emit-metal.h"
#include "slang-emit-slang.h"
#include "slang-emit-source-writer.h"
#include "slang-emit-torch.h"
#include "slang-emit-vm.h"
#include "slang-emit-wgsl.h"
#include "slang-ir-any-value-inference.h"
#include "slang-ir-any-value-marshalling.h"
#include "slang-ir-autodiff.h"
#include "slang-ir-bind-existentials.h"
#include "slang-ir-byte-address-legalize.h"
#include "slang-ir-check-optional-none-usage.h"
#include "slang-ir-check-recursion.h"
#include "slang-ir-check-shader-parameter-type.h"
#include "slang-ir-check-unsupported-inst.h"
#include "slang-ir-cleanup-void.h"
#include "slang-ir-collect-global-uniforms.h"
#include "slang-ir-com-interface.h"
#include "slang-ir-coverage-instrument.h"
#include "slang-ir-cuda-immutable-load.h"
#include "slang-ir-dce.h"
#include "slang-ir-defer-buffer-load.h"
#include "slang-ir-defunctionalization.h"
#include "slang-ir-detect-uninitialized-resources.h"
#include "slang-ir-dll-export.h"
#include "slang-ir-dll-import.h"
#include "slang-ir-early-raytracing-intrinsic-simplification.h"
#include "slang-ir-eliminate-multilevel-break.h"
#include "slang-ir-eliminate-phis.h"
#include "slang-ir-entry-point-decorations.h"
#include "slang-ir-entry-point-raw-ptr-params.h"
#include "slang-ir-entry-point-uniforms.h"
#include "slang-ir-explicit-global-context.h"
#include "slang-ir-explicit-global-init.h"
#include "slang-ir-fix-entrypoint-callsite.h"
#include "slang-ir-float-non-uniform-resource-index.h"
#include "slang-ir-fuse-satcoop.h"
#include "slang-ir-glsl-legalize.h"
#include "slang-ir-glsl-liveness.h"
#include "slang-ir-hlsl-legalize.h"
#include "slang-ir-inline.h"
#include "slang-ir-insts.h"
#include "slang-ir-late-require-capability.h"
#include "slang-ir-layout.h"
#include "slang-ir-legalize-array-return-type.h"
#include "slang-ir-legalize-binary-operator.h"
#include "slang-ir-legalize-composite-select.h"
#include "slang-ir-legalize-empty-array.h"
#include "slang-ir-legalize-global-values.h"
#include "slang-ir-legalize-image-subscript.h"
#include "slang-ir-legalize-matrix-types.h"
#include "slang-ir-legalize-mesh-outputs.h"
#include "slang-ir-legalize-uniform-buffer-load.h"
#include "slang-ir-legalize-varying-params.h"
#include "slang-ir-legalize-vector-types.h"
#include "slang-ir-link.h"
#include "slang-ir-liveness.h"
#include "slang-ir-loop-unroll.h"
#include "slang-ir-lower-append-consume-structured-buffer.h"
#include "slang-ir-lower-binding-query.h"
#include "slang-ir-lower-bit-cast.h"
#include "slang-ir-lower-buffer-element-type.h"
#include "slang-ir-lower-combined-texture-sampler.h"
#include "slang-ir-lower-conditional-type.h"
#include "slang-ir-lower-coopvec.h"
#include "slang-ir-lower-cpu-resource-types.h"
#include "slang-ir-lower-dynamic-dispatch-insts.h"
#include "slang-ir-lower-dynamic-resource-heap.h"
#include "slang-ir-lower-enum-type.h"
#include "slang-ir-lower-glsl-ssbo-types.h"
#include "slang-ir-lower-l-value-cast.h"
#include "slang-ir-lower-matrix-swizzle-store.h"
#include "slang-ir-lower-optional-type.h"
#include "slang-ir-lower-reinterpret.h"
#include "slang-ir-lower-result-type.h"
#include "slang-ir-lower-tuple-types.h"
#include "slang-ir-metadata.h"
#include "slang-ir-metal-legalize.h"
#include "slang-ir-missing-return.h"
#include "slang-ir-optix-entry-point-uniforms.h"
#include "slang-ir-pytorch-cpp-binding.h"
#include "slang-ir-redundancy-removal.h"
#include "slang-ir-resolve-texture-format.h"
#include "slang-ir-resolve-varying-input-ref.h"
#include "slang-ir-restructure-scoping.h"
#include "slang-ir-restructure.h"
#include "slang-ir-sccp.h"
#include "slang-ir-simplify-for-emit.h"
#include "slang-ir-specialize-address-space.h"
#include "slang-ir-specialize-arrays.h"
#include "slang-ir-specialize-buffer-load-arg.h"
#include "slang-ir-specialize-matrix-layout.h"
#include "slang-ir-specialize-resources.h"
#include "slang-ir-specialize-stage-switch.h"
#include "slang-ir-specialize.h"
#include "slang-ir-ssa-simplification.h"
#include "slang-ir-ssa.h"
#include "slang-ir-string-hash.h"
#include "slang-ir-strip-debug-info.h"
#include "slang-ir-strip-default-construct.h"
#include "slang-ir-strip-legalization-insts.h"
#include "slang-ir-synthesize-active-mask.h"
#include "slang-ir-transform-params-to-constref.h"
#include "slang-ir-translate-global-varying-var.h"
#include "slang-ir-translate.h"
#include "slang-ir-typeflow-specialize.h"
#include "slang-ir-undo-param-copy.h"
#include "slang-ir-uniformity.h"
#include "slang-ir-user-type-hint.h"
#include "slang-ir-validate.h"
#include "slang-ir-variable-scope-correction.h"
#include "slang-ir-vk-invert-y.h"
#include "slang-ir-wgsl-legalize.h"
#include "slang-ir-wrap-cbuffer-element.h"
#include "slang-ir-wrap-structured-buffers.h"
#include "slang-legalize-types.h"
#include "slang-lower-to-ir.h"
#include "slang-mangle.h"
#include "slang-pass-wrapper.h"
#include "slang-rich-diagnostics.h"
#include "slang-syntax.h"
#include "slang-type-layout.h"
#include "slang-visitor.h"
#include "slang-vm-bytecode.h"

#include <limits>
Slang::String get_slang_cpp_host_prelude();
Slang::String get_slang_torch_prelude();

namespace Slang
{

EntryPointLayout* findEntryPointLayout(ProgramLayout* programLayout, EntryPoint* entryPoint)
{
    // TODO: This function shouldn't need to exist, and it
    // somewhat hampers the capabilities of the compiler (e.g.,
    // it isn't supported to have a single program contain
    // two different "instances" of the same entry point).
    //
    // Code that cares about layouts should be looking up
    // the entry point layout by index on a `ProgramLayout`,
    // knowing that those indices will align with the order
    // of entry points on the `ComponentType` for the program.

    for (auto entryPointLayout : programLayout->entryPoints)
    {
        if (entryPointLayout->entryPoint.getName() != entryPoint->getName())
            continue;

        // TODO: We need to be careful about this check, since it relies on
        // the profile information in the layout matching that in the request.
        //
        // What we really seem to want here is some dictionary mapping the
        // `EntryPoint` directly to the `EntryPointLayout`, and maybe
        // that is precisely what we should build...
        //
        if (entryPointLayout->profile != entryPoint->getProfile())
            continue;

        return entryPointLayout;
    }

    return nullptr;
}

/// Given a layout computed for a scope, get the layout to use when lookup up variables.
///
/// A scope (such as the global scope of a program) groups its
/// parameters into a pseudo-`struct` type for layout purposes,
/// and in some cases that type will in turn be wrapped in a
/// `ConstantBuffer` type to indicate that the parameters needed
/// an implicit constant buffer to be allocated.
///
/// This function "unwraps" the type layout to find the structure
/// type layout that must be stored inside.
///
StructTypeLayout* getScopeStructLayout(ScopeLayout* scopeLayout)
{
    auto scopeTypeLayout = scopeLayout->parametersLayout->typeLayout;

    if (auto constantBufferTypeLayout = as<ParameterGroupTypeLayout>(scopeTypeLayout))
    {
        scopeTypeLayout = constantBufferTypeLayout->offsetElementTypeLayout;
    }

    if (auto structTypeLayout = as<StructTypeLayout>(scopeTypeLayout))
    {
        return structTypeLayout;
    }

    SLANG_UNEXPECTED("uhandled global-scope binding layout");
    return nullptr;
}

/// Given a layout computed for a program, get the layout to use when lookup up variables.
///
/// This is just an alias of `getScopeStructLayout`.
///
StructTypeLayout* getGlobalStructLayout(ProgramLayout* programLayout)
{
    return getScopeStructLayout(programLayout);
}


static void reportCheckpointIntermediates(
    CodeGenContext* codeGenContext,
    DiagnosticSink* sink,
    IRModule* irModule)
{
    // Report checkpointing information by consuming ReportCheckpointStore marker instructions.
    // Each ReportCheckpointStore has 3 operands:
    //   0: storedType - the IRType of the value being checkpointed
    //   1: originalFunc - the function that the checkpoint is associated with
    //   2: storeRef - weak reference to the store/address (becomes poison if optimized out)
    TargetRequest* targetReq = codeGenContext->getTargetReq();
    SourceManager* sourceManager = sink->getSourceManager();

    SourceWriter typeWriter(sourceManager, LineDirectiveMode::None, nullptr);

    CLikeSourceEmitter::Desc description;
    description.codeGenContext = codeGenContext;
    description.sourceWriter = &typeWriter;

    CPPSourceEmitter emitter(description);

    // Collect all ReportCheckpointStore instructions, grouped by original function.
    struct CheckpointEntry
    {
        IRType* storedType;
        SourceLoc sourceLoc;
    };
    Dictionary<IRInst*, List<CheckpointEntry>> funcToEntries;
    List<IRInst*> reportInstsToRemove;

    for (auto globalInst : irModule->getGlobalInsts())
    {
        auto func = as<IRFunc>(globalInst);
        if (!func)
        {
            if (auto generic = as<IRGeneric>(globalInst))
                func = as<IRFunc>(findGenericReturnVal(generic));
        }
        if (!func)
            continue;

        for (auto block : func->getBlocks())
        {
            for (auto inst : block->getChildren())
            {
                if (inst->getOp() == kIROp_ReportCheckpointStore)
                {
                    reportInstsToRemove.add(inst);

                    // If the store was optimized out (storeRef became poison),
                    // skip reporting this entry.
                    auto storeRef = inst->getOperand(2);
                    if (storeRef->getOp() == kIROp_Poison)
                        continue;

                    auto storedType = (IRType*)inst->getOperand(0);
                    auto originalFunc = inst->getOperand(1);
                    CheckpointEntry entry;
                    entry.storedType = storedType;
                    entry.sourceLoc = inst->sourceLoc;
                    funcToEntries[originalFunc].add(entry);
                }
            }
        }
    }

    int nonEmptyFuncs = 0;
    for (auto& [originalFunc, entries] : funcToEntries)
    {
        IRSizeAndAlignment totalSize = {};
        List<CheckpointEntry> nonZeroEntries;
        for (auto& entry : entries)
        {
            IRSizeAndAlignment entrySize;
            getNaturalSizeAndAlignment(targetReq, entry.storedType, &entrySize);
            if (entrySize.size > 0)
            {
                totalSize.size += entrySize.size;
                nonZeroEntries.add(entry);
            }
        }

        if (totalSize.size == 0)
            continue;

        Diagnostics::ReportCheckpointIntermediates diagnostic;
        diagnostic.size = (int64_t)totalSize.size;
        diagnostic.func = originalFunc;
        diagnostic.location = originalFunc->sourceLoc;

        // Group entries by (sourceLoc, typeName) to deduplicate repeats from
        // loop unrolling etc.
        struct GroupKey
        {
            SourceLoc loc;
            String typeName;
            bool operator==(const GroupKey& other) const
            {
                return loc == other.loc && typeName == other.typeName;
            }
            HashCode getHashCode() const
            {
                return combineHash(Slang::getHashCode(loc.getRaw()), Slang::getHashCode(typeName));
            }
        };
        struct GroupValue
        {
            int64_t varSize;
            int64_t count;
        };
        Dictionary<GroupKey, GroupValue> grouped;
        List<GroupKey> groupOrder;

        for (auto& entry : nonZeroEntries)
        {
            IRSizeAndAlignment entrySize;
            getNaturalSizeAndAlignment(targetReq, entry.storedType, &entrySize);

            typeWriter.clearContent();
            emitter.emitType(entry.storedType);

            GroupKey key;
            key.loc = entry.sourceLoc;
            key.typeName = typeWriter.getContent();

            if (auto* existing = grouped.tryGetValue(key))
            {
                existing->count++;
            }
            else
            {
                grouped[key] = GroupValue{(int64_t)entrySize.size, 1};
                groupOrder.add(key);
            }
        }

        for (auto& key : groupOrder)
        {
            auto& val = grouped[key];
            if (val.count == 1)
            {
                Diagnostics::ReportCheckpointIntermediates::Single s;
                s.singleSize = val.varSize;
                s.singleTypeName = key.typeName;
                s.singleLocation = key.loc;
                diagnostic.singles.add(s);
            }
            else
            {
                Diagnostics::ReportCheckpointIntermediates::Multi m;
                m.multiCount = val.count;
                m.multiSize = val.varSize;
                m.multiTypeName = key.typeName;
                m.multiLocation = key.loc;
                diagnostic.multis.add(m);
            }
        }

        sink->diagnose(diagnostic);
        nonEmptyFuncs++;
    }

    if (nonEmptyFuncs == 0)
        sink->diagnose(Diagnostics::ReportCheckpointNone{});

    // Remove all consumed ReportCheckpointStore instructions.
    for (auto inst : reportInstsToRemove)
        inst->removeAndDeallocate();
}

struct LinkingAndOptimizationOptions
{
    bool shouldLegalizeExistentialAndResourceTypes = true;
    CLikeSourceEmitter* sourceEmitter = nullptr;
};

// Scan the IR module and determine which lowering/legalization passes are needed based
// on the instructions we see.
//
void calcRequiredLoweringPassSet(
    RequiredLoweringPassSet& result,
    CodeGenContext* codeGenContext,
    IRInst* inst)
{
    // The autodiff finalization passes (finalizeAutoDiffPass / lowerDiffTypeInfoInsts in
    // linkAndOptimizeIR) lower or strip these constructs. They can appear in modules that
    // never call fwd_diff/bwd_diff (e.g. direct DifferentialPair use, or a no_diff type),
    // so mark autodiff here too; otherwise those passes would be skipped and autodiff IR
    // would survive into emission.
    //
    // These stay as base-class checks (not switch cases) because each spans several
    // concrete opcodes; `as<Base>` keeps matching if a new leaf op is added under the
    // base later. Single-opcode autodiff insts live in the switch below.
    if (as<IRTranslateBase>(inst) || as<IRTranslatedTypeBase>(inst) ||
        as<IRDifferentialPairTypeBase>(inst) || as<IRMakeDifferentialPairBase>(inst) ||
        as<IRDifferentialPairGetDifferentialBase>(inst) ||
        as<IRDifferentialPairGetPrimalBase>(inst))
    {
        result.autodiff = true;
    }
    // no_diff is an attribute payload, not a distinct opcode, so it needs findAttr.
    if (auto attrType = as<IRAttributedType>(inst))
    {
        if (attrType->findAttr<IRNoDiffAttr>())
            result.autodiff = true;
    }

    switch (inst->getOp())
    {
    case kIROp_DebugValue:
    case kIROp_DebugVar:
    case kIROp_DebugLine:
    case kIROp_DebugLocationDecoration:
    case kIROp_DebugSource:
    case kIROp_DebugInlinedAt:
    case kIROp_DebugScope:
    case kIROp_DebugNoScope:
    case kIROp_DebugFunction:
    case kIROp_DebugBuildIdentifier:
    case kIROp_DebugCompilationUnit:
        result.debugInfo = true;
        break;
    case kIROp_ResultType:
        result.resultType = true;
        break;
    case kIROp_OptionalType:
        result.optionalType = true;
        break;
    case kIROp_ConditionalType:
        result.conditionalType = true;
        break;
    case kIROp_EnumType:
    // The enum-cast ops are lowered by the same `lowerEnumType` pass as the type
    // itself, so flag `enumType` on them too. Constant folding can eliminate the
    // last live `IREnumType` while leaving a degenerate cast behind (e.g. an
    // enum-typed local holding a constant folds to `CastEnumToInt(1 : UInt)`);
    // flagging only the type would then skip the pass and strand the cast at
    // emit (#12048). `CastIntToEnum`/`EnumCast` produce an enum-typed result, so a
    // surviving one keeps its `IREnumType` alive and the `kIROp_EnumType` arm
    // already covers it; they are listed here for parity with `lowerEnumType`'s
    // handled set. The `Constexpr*` cast variants are intentionally excluded: they
    // arise only in constant `IntVal` contexts (`emitConstexprCast`, from
    // `visitTypeCastIntVal`), never in runtime value flow that reaches emit, and
    // `lowerEnumType` has no case for them.
    case kIROp_CastEnumToInt:
    case kIROp_CastIntToEnum:
    case kIROp_EnumCast:
        result.enumType = true;
        break;
    case kIROp_TextureType:
        if (!isKhronosTarget(codeGenContext->getTargetReq()))
        {
            if (auto texType = as<IRTextureType>(inst))
            {
                auto isCombined = texType->getIsCombinedInst();
                if (auto isCombinedVal = as<IRIntLit>(isCombined))
                {
                    if (isCombinedVal->getValue() != 0)
                    {
                        result.combinedTextureSamplers = true;
                    }
                }
                else
                {
                    result.combinedTextureSamplers = true;
                }
            }
        }
        break;
    case kIROp_PseudoPtrType:
    case kIROp_BoundInterfaceType:
    case kIROp_BindExistentialsType:
        result.generics = true;
        result.existentialTypeLayout = true;
        break;
    case kIROp_GetRegisterIndex:
    case kIROp_GetRegisterSpace:
        result.bindingQuery = true;
        break;
    case kIROp_Annotation:
    case kIROp_DetachDerivative:
    case kIROp_BackwardDifferentiate:
    case kIROp_ForwardDifferentiate:
    case kIROp_DiffTypeInfo:
        result.autodiff = true;
        break;
    case kIROp_VerticesType:
    case kIROp_IndicesType:
    case kIROp_PrimitivesType:
        result.meshOutput = true;
        break;
    case kIROp_CreateExistentialObject:
    case kIROp_MakeExistential:
    case kIROp_ExtractExistentialType:
    case kIROp_ExtractExistentialValue:
    case kIROp_ExtractExistentialWitnessTable:
    case kIROp_WrapExistential:
    case kIROp_LookupWitnessMethod:
        result.generics = true;
        break;
    case kIROp_Specialize:
        {
            auto specInst = as<IRSpecialize>(inst);
            if (!findAnyTargetIntrinsicDecoration(getResolvedInstForDecorations(specInst)))
                result.generics = true;
        }
        break;
    case kIROp_Reinterpret:
        result.reinterpret = true;
        break;
    case kIROp_BitCast:
        result.bitcast = true;
        break;
    case kIROp_AutoPyBindCudaDecoration:
        result.derivativePyBindWrapper = true;
        break;
    case kIROp_Param:
        if (as<IRFuncType>(inst->getDataType()))
            result.higherOrderFunc = true;
        break;
    case kIROp_GlobalInputDecoration:
    case kIROp_GlobalOutputDecoration:
    case kIROp_GetWorkGroupSize:
        result.globalVaryingVar = true;
        break;
    case kIROp_BindExistentialSlotsDecoration:
        result.bindExistential = true;
        result.generics = true;
        result.existentialTypeLayout = true;
        break;
    case kIROp_GLSLShaderStorageBufferType:
        result.glslSSBO = true;
        break;
    case kIROp_ByteAddressBufferLoad:
    case kIROp_ByteAddressBufferStore:
    case kIROp_HLSLRWByteAddressBufferType:
    case kIROp_HLSLByteAddressBufferType:
        result.byteAddressBuffer = true;
        break;
    case kIROp_HLSLAppendStructuredBufferType:
    case kIROp_HLSLConsumeStructuredBufferType:
        result.appendConsumeStructuredBuffer = true;
        break;
    case kIROp_DynamicResourceType:
        result.dynamicResource = true;
        break;
    case kIROp_GetDynamicResourceHeap:
        result.dynamicResourceHeap = true;
        break;
    case kIROp_CastUIntToUntypedResourceHandle:
    case kIROp_CastUntypedResourceHandleToUInt:
    case kIROp_CastUIntToUntypedSamplerHandle:
    case kIROp_CastUntypedSamplerHandleToUInt:
    case kIROp_UntypedResourceHandleType:
    case kIROp_UntypedSamplerHandleType:
        result.untypedResourceHandle = true;
        break;
    case kIROp_ResolveVaryingInputRef:
        result.resolveVaryingInputRef = true;
        break;
    case kIROp_GetCurrentStage:
        result.specializeStageSwitch = true;
        break;
    case kIROp_MissingReturn:
        result.missingReturn = true;
        break;
    case kIROp_MatrixSwizzleStore:
        result.matrixSwizzleStore = true;
        break;
    case kIROp_Select:
        if (!isScalarOrVectorType(inst->getFullType()))
            result.nonVectorCompositeSelect = true;
        break;
    case kIROp_IncrementCoverageCounter:
    case kIROp_IncrementFunctionCoverageCounter:
    case kIROp_IncrementBranchCoverageCounter:
        result.coverageTracing = true;
        break;
    case kIROp_GetEnumBarrierMemoryTypeFlags:
    case kIROp_GetEnumBarrierSemanticFlags:
        result.barrierFlagValidation = true;
        break;
    case kIROp_TaggedUnionType:
    case kIROp_MakeTaggedUnion:
    case kIROp_GetTagFromTaggedUnion:
    case kIROp_GetTypeTagFromTaggedUnion:
    case kIROp_GetValueFromTaggedUnion:
    case kIROp_CastInterfaceToTaggedUnionPtr:
        result.taggedUnion = true;
        result.untaggedUnion = true;
        result.tagOps = true;
        result.tagType = true;
        break;
    case kIROp_AssumeAddress:
        result.assumeAddress = true;
        break;
    case kIROp_UntaggedUnionType:
    case kIROp_NoneTypeElement:
        result.untaggedUnion = true;
        break;
    case kIROp_GetTagOfElementInSet:
    case kIROp_GetTagForSuperSet:
    case kIROp_GetTagForSubSet:
    case kIROp_GetTagForMappedSet:
        result.tagOps = true;
        result.tagType = true;
        break;
    case kIROp_SetTagType:
        result.tagType = true;
        break;
    case kIROp_InOutImplicitCast:
    case kIROp_OutImplicitCast:
        result.lValueCast = true;
        break;
    case kIROp_SumVectorElements:
    case kIROp_SumMatrixElements:
        result.sumVectorMatrix = true;
        break;
    case kIROp_LateRequireCapability:
        result.lateRequireCapability = true;
        break;
    }
    if (!result.generics || !result.existentialTypeLayout)
    {
        // If any instruction has an interface type, we need to run
        // the generics lowering pass.
        auto type = as<IRType>(inst) ? inst : inst->getDataType();
        for (;;)
        {
            if (auto ptrType = as<IRPtrTypeBase>(type))
                type = ptrType->getValueType();
            else
                break;
        }
        if (type && type->getOp() == kIROp_InterfaceType)
        {
            result.generics = true;
            result.existentialTypeLayout = true;
        }
    }
    for (auto child : inst->getDecorationsAndChildren())
    {
        calcRequiredLoweringPassSet(
            codeGenContext->getRequiredLoweringPassSet(),
            codeGenContext,
            child);
    }
}

bool checkStaticAssert(IRInst* inst, DiagnosticSink* sink)
{
    switch (inst->getOp())
    {
    case kIROp_StaticAssert:
        {
            IRInst* condi = inst->getOperand(0);
            if (auto condiLit = as<IRBoolLit>(condi))
            {
                if (!condiLit->getValue())
                {
                    IRInst* msg = inst->getOperand(1);
                    if (auto msgLit = as<IRStringLit>(msg))
                    {
                        sink->diagnose(Diagnostics::StaticAssertionFailure{
                            .message = String(msgLit->getStringSlice()),
                            .location = inst->sourceLoc,
                        });
                    }
                    else
                    {
                        sink->diagnose(Diagnostics::StaticAssertionFailureWithoutMessage{
                            .location = inst->sourceLoc,
                        });
                    }
                    diagnoseCallStack(inst, sink);
                }
            }
            else
            {
                sink->diagnose(Diagnostics::StaticAssertionConditionNotConstant{
                    .location = condi->sourceLoc,
                });
            }

            return true;
        }
    }

    List<IRInst*> removeList;
    for (auto child : inst->getChildren())
    {
        if (checkStaticAssert(child, sink))
            removeList.add(child);
    }
    for (auto child : removeList)
    {
        child->removeAndDeallocate();
    }

    return false;
}

static void unexportNonEmbeddableIR(IRModule* irModule, CodeGenTarget target)
{
    for (auto inst : irModule->getGlobalInsts())
    {
        if (inst->getOp() == kIROp_Func)
        {
            bool remove = false;
            if (target == CodeGenTarget::HLSL)
            {
                // DXIL does not permit HLSLStructureBufferType in exported functions
                // or sadly Matrices (https://github.com/shader-slang/slang/issues/4880)
                auto type = as<IRFuncType>(inst->getFullType());
                auto argCount = type->getOperandCount();
                for (UInt aa = 0; aa < argCount; ++aa)
                {
                    auto operand = type->getOperand(aa);
                    if (operand->getOp() == kIROp_HLSLStructuredBufferType ||
                        operand->getOp() == kIROp_MatrixType)
                    {
                        remove = true;
                        break;
                    }
                }
            }
            else if (target == CodeGenTarget::SPIRV)
            {
                // SPIR-V does not allow exporting entry points
                if (inst->findDecoration<IREntryPointDecoration>())
                {
                    remove = true;
                }
            }
            if (remove)
            {
                if (auto dec = inst->findDecoration<IRPublicDecoration>())
                {
                    dec->removeAndDeallocate();
                }
                if (auto dec = inst->findDecoration<IRDownstreamModuleExportDecoration>())
                {
                    dec->removeAndDeallocate();
                }
            }
        }
    }
}

// Add DenormPreserve and DenormFlushToZero decorations to all entry point functions
static void addDenormalModeDecorations(IRModule* irModule, CodeGenContext* codeGenContext)
{
    auto optionSet = codeGenContext->getTargetProgram()->getOptionSet();

    // Only add decorations if we have floating point denormal handling mode options set
    auto denormalModeFp16 = optionSet.getDenormalModeFp16();
    auto denormalModeFp32 = optionSet.getDenormalModeFp32();
    auto denormalModeFp64 = optionSet.getDenormalModeFp64();

    if (denormalModeFp16 == FloatingPointDenormalMode::Any &&
        denormalModeFp32 == FloatingPointDenormalMode::Any &&
        denormalModeFp64 == FloatingPointDenormalMode::Any)
        return;

    IRBuilder builder(irModule);

    // Apply floating point denormal handling mode decorations to all entry point functions
    for (auto inst : irModule->getGlobalInsts())
    {
        IRFunc* func = nullptr;

        // Check if this is a direct function
        if (auto directFunc = as<IRFunc>(inst))
        {
            func = directFunc;
        }
        // Check if this is a generic that contains an entry point function
        else if (auto generic = as<IRGeneric>(inst))
        {
            if (auto innerFunc = as<IRFunc>(findGenericReturnVal(generic)))
            {
                func = innerFunc;
            }
        }

        if (!func)
            continue;

        // Check if this is an entry point function
        auto entryPoint = func->findDecoration<IREntryPointDecoration>();
        if (!entryPoint)
            continue;

        // Handle FP16 denormal handling mode
        auto width16 = builder.getIntValue(builder.getUIntType(), 16);
        if (denormalModeFp16 == FloatingPointDenormalMode::Preserve)
        {
            builder.addFpDenormalPreserveDecoration(func, width16);
        }
        else if (denormalModeFp16 == FloatingPointDenormalMode::FlushToZero)
        {
            builder.addFpDenormalFlushToZeroDecoration(func, width16);
        }

        // Handle FP32 denormal handling mode
        auto width32 = builder.getIntValue(builder.getUIntType(), 32);
        if (denormalModeFp32 == FloatingPointDenormalMode::Preserve)
        {
            builder.addFpDenormalPreserveDecoration(func, width32);
        }
        else if (denormalModeFp32 == FloatingPointDenormalMode::FlushToZero)
        {
            builder.addFpDenormalFlushToZeroDecoration(func, width32);
        }

        // Handle FP64 denormal handling mode
        auto width64 = builder.getIntValue(builder.getUIntType(), 64);
        if (denormalModeFp64 == FloatingPointDenormalMode::Preserve)
        {
            builder.addFpDenormalPreserveDecoration(func, width64);
        }
        else if (denormalModeFp64 == FloatingPointDenormalMode::FlushToZero)
        {
            builder.addFpDenormalFlushToZeroDecoration(func, width64);
        }
    }
}

// Helper function to convert a 20 byte SHA1 to a hexadecimal string,
// needed for the build identifier instruction.
String getBuildIdentifierString(ComponentType* component)
{
    ComPtr<ISlangBlob> hashBlob;
    component->getEntryPointHash(0, 0, hashBlob.writeRef());

    const uint8_t* data = reinterpret_cast<const uint8_t*>(hashBlob->getBufferPointer());
    size_t size = hashBlob->getBufferSize();
    StringBuilder sb;
    for (size_t i = 0; i < size; ++i)
        sb << StringUtil::makeStringWithFormat("%02x", data[i]);
    return sb.produceString();
}

// Lower DiffTypeInfo instructions to MakeTuple.
// DiffTypeInfo is a hoistable instruction that holds witness tables for differential type info.
// After specialization, it can be safely converted to a regular MakeTuple.
void lowerDiffTypeInfoInsts(IRModule* module)
{
    List<IRInst*> instsToReplace;
    for (auto globalInst : module->getGlobalInsts())
    {
        if (globalInst->getOp() == kIROp_DiffTypeInfo)
        {
            instsToReplace.add(globalInst);
        }
    }

    IRBuilder builder(module);
    for (auto inst : instsToReplace)
    {
        builder.setInsertBefore(inst);
        List<IRInst*> args;
        for (UInt i = 0; i < inst->getOperandCount(); i++)
        {
            args.add(inst->getOperand(i));
        }
        auto tuple =
            builder.emitMakeTuple(inst->getFullType(), (UInt)args.getCount(), args.getBuffer());
        inst->replaceUsesWith(tuple);
        inst->removeAndDeallocate();
    }
}

void lowerSumVectorMatrixInsts(IRModule* module)
{
    struct LowerSumVectorMatrixPass : InstPassBase
    {
        LowerSumVectorMatrixPass(IRModule* module)
            : InstPassBase(module)
        {
        }
        void processModule()
        {
            processInstsOfType<IRSumVectorElements>(
                kIROp_SumVectorElements,
                [&](IRSumVectorElements* sumInst)
                {
                    auto vectorOperand = sumInst->getOperand(0);
                    IRBuilder builder(module);
                    builder.setInsertBefore(sumInst);
                    auto vectorType = as<IRVectorType>(vectorOperand->getDataType());
                    auto vectorSize = as<IRIntLit>(vectorType->getElementCount())->getValue();
                    auto vectorElemType = vectorType->getElementType();

                    IRInst* result = nullptr;
                    for (auto ii = 0; ii < vectorSize; ii++)
                    {
                        auto element =
                            builder.emitElementExtract(vectorOperand, builder.getIntValue(ii));
                        if (ii == 0)
                            result = element;
                        else
                            result = builder.emitAdd(vectorElemType, result, element);
                    }

                    SLANG_ASSERT(result);

                    sumInst->replaceUsesWith(result);
                    sumInst->removeAndDeallocate();
                });

            processInstsOfType<IRSumMatrixElements>(
                kIROp_SumMatrixElements,
                [&](IRSumMatrixElements* sumInst)
                {
                    auto matrixOperand = sumInst->getOperand(0);
                    IRBuilder builder(module);
                    builder.setInsertBefore(sumInst);
                    auto matrixType = as<IRMatrixType>(matrixOperand->getDataType());
                    auto matrixColCount = as<IRIntLit>(matrixType->getColumnCount())->getValue();
                    auto matrixRowCount = as<IRIntLit>(matrixType->getRowCount())->getValue();
                    auto matrixElemType = matrixType->getElementType();

                    IRInst* result = nullptr;
                    for (auto ii = 0; ii < matrixRowCount; ii++)
                    {
                        for (auto jj = 0; jj < matrixColCount; jj++)
                        {
                            auto element = builder.emitElementExtract(
                                builder.emitElementExtract(matrixOperand, builder.getIntValue(ii)),
                                builder.getIntValue(jj));
                            if (ii == 0 && jj == 0)
                                result = element;
                            else
                                result = builder.emitAdd(matrixElemType, result, element);
                        }
                    }
                    SLANG_ASSERT(result);

                    sumInst->replaceUsesWith(result);
                    sumInst->removeAndDeallocate();
                });
        }
    };

    LowerSumVectorMatrixPass pass(module);
    pass.processModule();
}

void removeWeakUseInsts(IRModule* module)
{
    List<IRInst*> weakUseInsts;
    for (auto inst : module->getModuleInst()->getGlobalInsts())
    {
        if (inst->getOp() == kIROp_WeakUse)
            weakUseInsts.add(inst);
    }

    for (auto weakUse : weakUseInsts)
    {
        weakUse->removeAndDeallocate();
    }
}

Result linkAndOptimizeIR(
    CodeGenContext* codeGenContext,
    LinkingAndOptimizationOptions const& options,
    LinkedIR& outLinkedIR)
{
    SLANG_PROFILE;

    // This lambda is here so that we can select the correct overload for our parameters, without it
    // the overload deduction fails for passes which have overloads not taking an IRModule*
#define SLANG_PASS(passFunc, ...)                                                          \
    wrapPass(                                                                              \
        codeGenContext,                                                                    \
        #passFunc,                                                                         \
        [](auto* m, auto&&... a) { return passFunc(m, std::forward<decltype(a)>(a)...); }, \
        irModule,                                                                          \
        ##__VA_ARGS__)

    auto session = codeGenContext->getSession();
    auto sink = codeGenContext->getSink();
    auto target = codeGenContext->getTargetFormat();
    auto targetRequest = codeGenContext->getTargetReq();
    auto targetProgram = codeGenContext->getTargetProgram();
    auto targetCompilerOptions = targetRequest->getOptionSet();

    // Get the artifact desc for the target
    const auto artifactDesc = ArtifactDescUtil::makeDescForCompileTarget(asExternal(target));

    // We start out by performing "linking" at the level of the IR.
    // This step will create a fresh IR module to be used for
    // code generation, and will copy in any IR definitions that
    // the desired entry point requires. Along the way it will
    // resolve references to imported/exported symbols across
    // modules, and also select between the definitions of
    // any "profile-overloaded" symbols.
    //
    outLinkedIR = linkIR(codeGenContext);
    auto irModule = outLinkedIR.module;
    auto irEntryPoints = outLinkedIR.entryPoints;

    // If our linking step resulted in errors, abort. We can't assume that
    // our IR is complete.
    //
    if (sink->getErrorCount() != 0)
        return SLANG_FAIL;

    // Create the post-emit metadata object up-front so that IR passes
    // that need to record reportable data (e.g. `instrumentCoverage`'s
    // source-entry mapping) can write into it directly. `collectMetadata`
    // later fills in binding / exported-function fields.
    auto metadata = new ArtifactPostEmitMetadata;
    outLinkedIR.metadata = metadata;

    // For now, only emit the debug build identifier if separate debug info is enabled
    // and only if there are targets.
    // TODO: We will ultimately need to change this to always emit the instruction.
    if (targetCompilerOptions.shouldEmitSeparateDebugInfo())
    {
        // Build identifier is a hash of the source code and compile options.
        String buildIdentifier = getBuildIdentifierString(codeGenContext->getProgram());
        int buildIdentifierFlags = 0;
        IRBuilder builder(irModule);
        builder.setInsertInto(irModule->getModuleInst());
        builder.emitDebugBuildIdentifier(buildIdentifier.getUnownedSlice(), buildIdentifierFlags);
    }

    validateIRModuleIfEnabled(codeGenContext, irModule);

    // Scan the IR module and determine which lowering/legalization passes are needed.
    RequiredLoweringPassSet& requiredLoweringPassSet = codeGenContext->getRequiredLoweringPassSet();
    requiredLoweringPassSet = {};
    calcRequiredLoweringPassSet(requiredLoweringPassSet, codeGenContext, irModule->getModuleInst());

    if (requiredLoweringPassSet.assumeAddress)
    {
        bool validate = !isCPUTarget(targetRequest) && !isCUDATarget(targetRequest);
        SLANG_PASS(validateAndRemoveAssumeAddress, validate, sink);
    }

    // If the user specified the flag that they want us to dump
    // IR, then do it here, for the target-specific, but
    // un-specialized IR.

    // Debug info is added by the front-end. If the target cannot express debug info, or if the user
    // specifies -g0, we need to stripped them out now to allow more optimization and cleanups.
    if (requiredLoweringPassSet.debugInfo &&
        (targetCompilerOptions.getDebugInfoLevel() == DebugInfoLevel::None))
        SLANG_PASS(stripDebugInfo);

    if (!isKhronosTarget(targetRequest) && requiredLoweringPassSet.glslSSBO)
        SLANG_PASS(lowerGLSLShaderStorageBufferObjectsToStructuredBuffers, sink);

    SLANG_PASS(translateEntryPointInParamToBorrow, sink);

    // Replace any global constants with their values.
    //
    SLANG_PASS(replaceGlobalConstants);
    validateIRModuleIfEnabled(codeGenContext, irModule);


    // When there are top-level existential-type parameters
    // to the shader, we need to take the side-band information
    // on how the existential "slots" were bound to concrete
    // types, and use it to introduce additional explicit
    // shader parameters for those slots, to be wired up to
    // use sites.
    //
    if (requiredLoweringPassSet.bindExistential)
        SLANG_PASS(bindExistentialSlots, sink);
    validateIRModuleIfEnabled(codeGenContext, irModule);

    // Now that we've linked the IR code, any layout/binding
    // information has been attached to shader parameters
    // and entry points. Now we are safe to make transformations
    // that might move code without worrying about losing
    // the connection between a parameter and its layout.

    // One example of a transformation that needs to wait until
    // we have layout information is the step where we collect
    // any global-scope shader parameters with ordinary/uniform
    // type into an aggregate `struct`, and then (optionally)
    // wrap that `struct` up in a constant buffer.
    //
    // This step allows shaders to declare parameters of ordinary
    // type as globals in the input file, while ensuring that
    // downstream passes for graphics APIs like Vulkan and D3D
    // can assume that all ordinary/uniform data is strictly
    // passed using constant buffers.
    //
    // Shader coverage instrumentation. The pass synthesizes
    // `__slang_coverage` as an `IRGlobalParam` directly in the
    // linked program IR, extends the program-scope var layout, and
    // rewrites coverage marker ops to atomic adds. Runs BEFORE
    // `collectGlobalUniformParameters` so the synthesized buffer
    // gets packed into `GlobalParams` alongside user globals on
    // targets that pack ordinary uniforms (CPU, CUDA).
    //
    // Coverage markers carry source position on their built-in `sourceLoc`,
    // so this pass is independent of debug-info state. It writes its
    // source-entry mapping into `metadata`, exposed to hosts via
    // ICoverageTracingMetadata.
    // Placement options are read OUTSIDE the gate below, and the bindless
    // index is fully validated here -- value kind, value range, and target
    // support alike.
    //
    // The gate is `requiredLoweringPassSet.coverageTracing`, derived from
    // coverage marker ops present in the IR, so a module with nothing
    // instrumentable never opens it. Validating the bindless index inside
    // would mean an empty or declaration-only translation unit accepted an
    // unsupported option set in total silence: the user asks for the shared
    // descriptor array, gets no diagnostic, and finds out from a
    // pipeline-layout mismatch at runtime.
    //
    // `-trace-coverage-binding` gets no equivalent checks here -- its values
    // are consumed by `instrumentCoverage` inside the gate, and an
    // unsatisfiable binding surfaces there. It is read alongside the index
    // only because the two describe one placement and are cheaper to read
    // together than to split across the gate.
    int explicitBinding = -1;
    int explicitSpace = -1;
    int bindlessIndex = -1;
    // One option set, read both here and inside the gate below.
    auto& coverageOpts = codeGenContext->getTargetReq()->getOptionSet();
    {
        if (auto values =
                coverageOpts.options.tryGetValue(CompilerOptionName::TraceCoverageBinding))
        {
            if (values->getCount() > 0)
            {
                explicitBinding = (int)(*values)[0].intValue;
                explicitSpace = (int)(*values)[0].intValue2;
            }
        }
        // `-trace-coverage-bindless-index <index>`. -1 leaves the ordinary
        // single-buffer form; >= 0 selects the unbounded-descriptor-array
        // form. WHERE the array lives is a separate decision that stays with
        // `-trace-coverage-binding` (or auto-allocation), because the host's
        // descriptor set layout is the host's to choose and the compiler
        // cannot see it.
        if (auto values =
                coverageOpts.options.tryGetValue(CompilerOptionName::TraceCoverageBindlessIndex))
        {
            if (values->getCount() > 0)
            {
                // A host setting this through the API can supply any value
                // kind. Reading `intValue` off a string-valued entry would
                // yield 0 and quietly select array element 0, so reject the
                // wrong kind rather than acting on a value that was never
                // meant as an index. Matches the reserved-space handling
                // below.
                //
                // Reported as an internal "unexpected" diagnostic rather
                // than a registered one, unlike the range check just below:
                // a wrong value *kind* means the caller mis-built the option
                // entry itself, which no CLI input can produce and which the
                // option's own type contract already forbids. A negative
                // index is a well-formed option carrying an out-of-range
                // value, so it gets a user-facing code (E45117).
                if ((*values)[0].kind != CompilerOptionValueKind::Int)
                {
                    if (sink)
                    {
                        SLANG_DIAGNOSE_UNEXPECTED(
                            sink,
                            SourceLoc(),
                            "TraceCoverageBindlessIndex option value must be an integer");
                    }
                    return SLANG_FAIL;
                }
                // The CLI parser rejects negatives, but a host setting this
                // through the API bypasses it, and a negative index would
                // silently fall back to the single-buffer form -- one binding
                // per shader, the opposite of what the caller asked for.
                int requestedIndex = (int)(*values)[0].intValue;
                if (requestedIndex < 0)
                {
                    if (sink)
                        sink->diagnose(Diagnostics::CoverageBindlessNegativeIndex{});
                    return SLANG_FAIL;
                }
                bindlessIndex = requestedIndex;
            }
        }
        // Target support is validated here rather than inside
        // `instrumentCoverage` for the same reason the value checks are: the
        // pass only runs when `requiredLoweringPassSet.coverageTracing` is
        // set, and that flag is derived from coverage marker ops present in
        // the IR. A module with nothing instrumentable in it -- an empty or
        // declaration-only translation unit -- never opens that gate, so a
        // request for the bindless form on a target that cannot express it
        // would compile clean and report nothing. The host would then learn
        // it did not get the shared binding it asked for from a
        // pipeline-layout mismatch at runtime, which is precisely the failure
        // this option exists to remove.
        if (bindlessIndex >= 0 && !isKhronosTarget(targetRequest))
        {
            if (sink)
                sink->diagnose(Diagnostics::CoverageBindlessTargetNotSupported{});
            return SLANG_FAIL;
        }
    }

    if (requiredLoweringPassSet.coverageTracing)
    {
        List<int> reservedSpaces;
        if (auto values =
                coverageOpts.options.tryGetValue(CompilerOptionName::TraceCoverageReservedSpace))
        {
            for (auto value : *values)
            {
                if (value.kind != CompilerOptionValueKind::Int)
                {
                    if (sink)
                    {
                        SLANG_DIAGNOSE_UNEXPECTED(
                            sink,
                            SourceLoc(),
                            "TraceCoverageReservedSpace option value must be an integer");
                    }
                    return SLANG_FAIL;
                }
                if (value.intValue < 0 || value.intValue > std::numeric_limits<int>::max())
                {
                    if (sink)
                    {
                        sink->diagnose(Diagnostics::CoverageBindingOptionOutOfRange{
                            .option = "-trace-coverage-reserved-space",
                            .parsedValue = value.intValue,
                        });
                    }
                    return SLANG_FAIL;
                }
                reservedSpaces.add((int)value.intValue);
            }
        }
        // Default to uint64. Customers opt down to uint32 (4 bytes per
        // slot) only when targeting a runtime driver without 64-bit
        // shader-atomic-add support — notably MoltenVK on Apple Silicon,
        // where Vulkan reports `shaderBufferInt64Atomics = false`. The
        // compiler can't see the runtime driver, so the choice is the
        // caller's responsibility.
        int counterByteWidth = kDefaultCoverageCounterByteWidth;
        bool hasExplicitCounterByteWidth = false;
        if (auto values =
                coverageOpts.options.tryGetValue(CompilerOptionName::TraceCoverageCounterByteWidth))
        {
            if (values->getCount() > 0)
            {
                counterByteWidth = (int)(*values)[0].intValue;
                hasExplicitCounterByteWidth = true;
            }
        }
        // Validate the byte width on the API path. The CLI parser
        // (`slang-options.cpp`) already validates the user-facing bit
        // width and stores 4 or 8, but a host that sets the
        // `TraceCoverageCounterByteWidth` option directly bypasses that
        // check. Only 4 and 8 have a synthesizable element type; rather
        // than silently coercing anything else to uint32 (which would
        // hide a caller's misconfiguration — e.g. forwarding bits 32/64
        // instead of bytes 4/8), fail loudly here, matching the CLI's
        // `E45113` guarantee.
        if (counterByteWidth != 4 && counterByteWidth != 8)
        {
            sink->diagnose(Diagnostics::CoverageCounterWidthBytesInvalid{
                .byteWidth = counterByteWidth,
            });
            return SLANG_FAIL;
        }
        // Metal cannot execute 64-bit counting-mode coverage: MSL provides no
        // 64-bit atomic fetch-add (its `_valid_fetch_add_type` constraint
        // rejects `device atomic_ulong*`), so the Metal compiler fails every
        // counter increment with "no matching function for call to
        // 'atomic_fetch_add_explicit'". Cap counting-mode counters to 4 bytes
        // for Metal targets (`metal`, `metallib`, `metallib-asm`). Unlike the
        // validation block above, which rejects out-of-contract widths loudly
        // because they indicate a caller bug, this cap adjusts a *valid* width
        // to a platform limitation: the uncapped default (8) is capped
        // silently, and an explicitly requested 8 is capped with warning
        // E45115 so a caller who spelled out `-trace-coverage-counter-width
        // 64` learns their choice was not honored.
        // Boolean mode (`-trace-coverage-boolean`) is exempt: it writes plain
        // non-atomic stores (`*slot = 1`), which MSL accepts at either width —
        // verified against the Metal compiler — so the requested width is
        // honored there.
        bool coverageBoolean = false;
        if (auto values =
                coverageOpts.options.tryGetValue(CompilerOptionName::TraceCoverageBoolean))
        {
            if (values->getCount() > 0)
                coverageBoolean = (*values)[0].intValue != 0;
        }
        if (isMetalTarget(targetRequest) && counterByteWidth > 4 && !coverageBoolean)
        {
            if (hasExplicitCounterByteWidth)
                sink->diagnose(Diagnostics::CoverageCounterWidthCappedForMetal{});
            counterByteWidth = 4;
        }
        SLANG_PASS(
            instrumentCoverage,
            sink,
            codeGenContext->shouldTraceAnyCoverage(),
            explicitBinding,
            explicitSpace,
            reservedSpaces.getBuffer(),
            (int)reservedSpaces.getCount(),
            counterByteWidth,
            coverageBoolean,
            bindlessIndex,
            targetRequest,
            outLinkedIR.globalScopeVarLayout,
            *metadata);
        validateIRModuleIfEnabled(codeGenContext, irModule);
    }

    SLANG_PASS(collectGlobalUniformParameters, outLinkedIR.globalScopeVarLayout, target);
    validateIRModuleIfEnabled(codeGenContext, irModule);

    SLANG_PASS(checkEntryPointDecorations, target, sink);

    // Add floating point denormal handling mode decorations to entry point functions based on
    // compiler options. This is done post-linking to ensure all entry points from linked
    // modules are processed.
    SLANG_PASS(addDenormalModeDecorations, codeGenContext);
    validateIRModuleIfEnabled(codeGenContext, irModule);

    // Another transformation that needed to wait until we
    // had layout information on parameters is to take uniform
    // parameters of a shader entry point and move them into
    // the global scope instead.
    //
    // TODO: We should skip this step for CUDA targets.
    // (NM): we actually do need to do this step for OptiX based CUDA targets
    //
    {
        CollectEntryPointUniformParamsOptions passOptions;
        passOptions.targetReq = targetRequest;
        switch (target)
        {
        case CodeGenTarget::HostCPPSource:
        case CodeGenTarget::HostVM:
        case CodeGenTarget::HostLLVMIR:
        case CodeGenTarget::HostObjectCode:
        case CodeGenTarget::HostHostCallable:
            break;
        case CodeGenTarget::CUDASource:
        case CodeGenTarget::CUDAHeader:
            SLANG_PASS(collectOptiXEntryPointUniformParams);
            validateIRModuleIfEnabled(codeGenContext, irModule);
            break;

        case CodeGenTarget::CPPSource:
        case CodeGenTarget::CPPHeader:
        case CodeGenTarget::ShaderLLVMIR:
        case CodeGenTarget::ShaderObjectCode:
        case CodeGenTarget::ShaderHostCallable:
            passOptions.alwaysCreateCollectedParam = true;
            [[fallthrough]];
        default:
            SLANG_PASS(collectEntryPointUniformParams, passOptions);
            validateIRModuleIfEnabled(codeGenContext, irModule);
            break;
        }
    }

    switch (target)
    {
    default:
        SLANG_PASS(moveEntryPointUniformParamsToGlobalScope);
        validateIRModuleIfEnabled(codeGenContext, irModule);
        break;
    case CodeGenTarget::HostCPPSource:
    case CodeGenTarget::CPPSource:
    case CodeGenTarget::CPPHeader:
    case CodeGenTarget::CUDASource:
    case CodeGenTarget::CUDAHeader:
    case CodeGenTarget::HostVM:
    case CodeGenTarget::HostLLVMIR:
    case CodeGenTarget::HostObjectCode:
    case CodeGenTarget::HostHostCallable:
    case CodeGenTarget::ShaderLLVMIR:
    case CodeGenTarget::ShaderObjectCode:
    case CodeGenTarget::ShaderHostCallable:
        break;
    }

    switch (target)
    {
    case CodeGenTarget::CUDASource:
    case CodeGenTarget::CUDAHeader:
    case CodeGenTarget::PyTorchCppBinding:
        break;

    default:
        SLANG_PASS(removeTorchAndCUDAEntryPoints);
        break;
    }

    validateIRModuleIfEnabled(codeGenContext, irModule);

    if (requiredLoweringPassSet.coverageTracing)
    {
        SLANG_PASS(
            finalizeCoverageInstrumentationMetadata,
            sink,
            codeGenContext->shouldTraceAnyCoverage(),
            outLinkedIR.globalScopeVarLayout,
            targetRequest,
            *metadata);
    }

    // Lower all the LValue implict casts (used for out/inout/ref scenarios)
    //
    // #11917: Gated on `lValueCast` to skip this whole-module walk when no
    // out/inout implicit-cast IR is present. `kIROp_InOutImplicitCast` and
    // `kIROp_OutImplicitCast` are produced only by the front end
    // (`emitInOutImplicitCast`/`emitOutImplicitCast` in slang-lower-to-ir.cpp),
    // so any instance is present before the `calcRequiredLoweringPassSet` scan
    // that governs this call site; no pass in between synthesizes them, so the
    // flag cannot be a false-negative.
    if (requiredLoweringPassSet.lValueCast)
        SLANG_PASS(lowerLValueCast, targetProgram);

    // Lower enum types early since enums and enum casts may appear in
    // specialization & not resolving them here would block specialization.
    //
    if (requiredLoweringPassSet.enumType)
        SLANG_PASS(lowerEnumType, sink);

    IRSimplificationOptions defaultIRSimplificationOptions =
        IRSimplificationOptions::getDefault(targetProgram);
    IRSimplificationOptions fastIRSimplificationOptions =
        IRSimplificationOptions::getFast(targetProgram);
    IRDeadCodeEliminationOptions deadCodeEliminationOptions = IRDeadCodeEliminationOptions();
    fastIRSimplificationOptions.minimalOptimization =
        defaultIRSimplificationOptions.minimalOptimization;
    deadCodeEliminationOptions.useFastAnalysis = fastIRSimplificationOptions.minimalOptimization;
    deadCodeEliminationOptions.keepGlobalParamsAlive =
        targetProgram->getOptionSet().getBoolOption(CompilerOptionName::PreserveParameters);

    SLANG_PASS(simplifyIR, targetProgram, defaultIRSimplificationOptions, sink);

    if (targetProgram->getOptionSet().getBoolOption(CompilerOptionName::ValidateUniformity))
    {
        SLANG_PASS(validateUniformity, sink);
        if (sink->getErrorCount() != 0)
            return SLANG_FAIL;
    }

    // Fill in default matrix layout into matrix types that left layout unspecified.
    SLANG_PASS(specializeMatrixLayout, targetProgram);

    // It's important that this takes place before defunctionalization as we
    // want to be able to easily discover the cooperate and fallback funcitons
    // being passed to saturated_cooperation
    if (!targetProgram->getOptionSet().shouldPerformMinimumOptimizations())
        SLANG_PASS(fuseCallsToSaturatedCooperation);

    switch (target)
    {
    case CodeGenTarget::CUDASource:
    case CodeGenTarget::CUDAHeader:
    case CodeGenTarget::PyTorchCppBinding:
        {
            // Generate any requested derivative wrappers
            if (requiredLoweringPassSet.derivativePyBindWrapper)
                SLANG_PASS(generateDerivativeWrappers, sink);
            break;
        }
    default:
        break;
    }

    if (requiredLoweringPassSet.autodiff)
    {
        // Generate warnings for potentially incorrect or badly-performing autodiff patterns.
        SLANG_PASS(checkAutodiffPatterns, targetProgram, sink);
    }

    // Next, we need to ensure that the code we emit for
    // the target doesn't contain any operations that would
    // be illegal on the target platform. For example,
    // none of our target supports generics, or interfaces,
    // so we need to specialize those away.
    //
    // Simplification of existential-based and generics-based
    // code may each open up opportunities for the other, so
    // the relevant specialization transformations are handled in a
    // single pass that looks for all simplification opportunities.
    //

    // Diagnose circular interface conformances before specialization.
    // Circular conformance IR (e.g. a struct implementing IFoo that contains
    // an IFoo field) produces IR that the translation/specialization passes
    // cannot handle. Detecting and reporting this early avoids crashes in
    // downstream passes.
    SLANG_PASS(diagnoseCircularConformances, sink);
    if (sink->getErrorCount() != 0)
        return SLANG_FAIL;

    if (!codeGenContext->isSpecializationDisabled())
    {
        SpecializationOptions specOptions;
        specOptions.lowerWitnessLookups = true;
        specOptions.reportDynamicDispatchSites = codeGenContext->shouldReportDynamicDispatchSites();
        SLANG_PASS(specializeModule, targetProgram, codeGenContext->getSink(), specOptions);
    }

    if (sink->getErrorCount() != 0)
        return SLANG_FAIL;

    if (requiredLoweringPassSet.higherOrderFunc)
    {
        SLANG_PASS(specializeHigherOrderParameters, codeGenContext);
    }

    // finalizeAutoDiffPass walks the whole module and builds an AutoDiffSharedContext. It
    // only has work to do when the module contains autodiff constructs, which
    // calcRequiredLoweringPassSet records in requiredLoweringPassSet.autodiff (covering
    // direct DifferentialPair / no_diff use, not just fwd_diff/bwd_diff). Skip it for
    // modules with none so they don't pay the per-compile cost.
    //
    // Even a module with no autodiff constructs still links in the core-module autodiff
    // builtins (types marked [__AutoDiffBuiltin], e.g. NullDifferential), which carry
    // Export/HLSLExport/KeepAlive decorations that keep them alive through DCE. Those
    // must still be stripped so the eliminateDeadCode pass below can drop the unused
    // builtins. stripAutoDiffDecorations removes those pins (along with the other
    // autodiff-only transient decorations) and needs no AutoDiffSharedContext, so run it
    // directly on the skip path.
    if (requiredLoweringPassSet.autodiff)
    {
        SLANG_PASS(finalizeAutoDiffPass, targetProgram);
    }
    else
    {
        SLANG_PASS(stripAutoDiffDecorations);
    }
    if (requiredLoweringPassSet.matrixSwizzleStore)
        SLANG_PASS(lowerMatrixSwizzleStores);
    SLANG_PASS(eliminateDeadCode, deadCodeEliminationOptions);

    SLANG_PASS(finalizeSpecialization);

    // Lower DiffTypeInfo instructions to MakeTuple.
    // This must happen after specialization since DiffTypeInfo is hoistable.
    // DiffTypeInfo originates from autodiff (calcRequiredLoweringPassSet marks it in
    // requiredLoweringPassSet.autodiff), so skip the whole-module walk when the module
    // contains no autodiff constructs.
    if (requiredLoweringPassSet.autodiff)
        lowerDiffTypeInfoInsts(irModule);

    if (requiredLoweringPassSet.conditionalType)
        SLANG_PASS(lowerConditionalType, sink);

    if (requiredLoweringPassSet.optionalType)
        SLANG_PASS(lowerReinterpretOptional, targetProgram, sink);

    // Check for accessing .value on an Optional that is always none.
    // This must run after simplifyIR (which eliminates dead branches that
    // might access a none value) but before lowerOptionalType (which removes
    // IRMakeOptionalNone instructions).
    if (targetProgram->getOptionSet().shouldRunNonEssentialValidation())
        SLANG_PASS(checkForOptionalNoneUsage, sink);

    if (requiredLoweringPassSet.optionalType)
        SLANG_PASS(lowerOptionalType, sink);

    // Lower `Result<T,E>` types into ordinary struct types. This must happen
    // after specialization, since otherwise incompatible copies of the lowered
    // result structure are generated.
    //
    // This pass depends on getting accurate results from getAnyValueSize(),
    // and must therefore run after lowering Optional types.
    if (requiredLoweringPassSet.resultType)
        SLANG_PASS(lowerResultType, targetProgram, sink);

    if (requiredLoweringPassSet.nonVectorCompositeSelect)
    {
        switch (target)
        {
        case CodeGenTarget::HLSL:
            SLANG_PASS(legalizeNonVectorCompositeSelect);
            break;
        default:
            break;
        }
    }

    switch (target)
    {
    case CodeGenTarget::CPPSource:
    case CodeGenTarget::CPPHeader:
    case CodeGenTarget::HostCPPSource:
        {
            SLANG_PASS(lowerComInterfaces, artifactDesc.style, sink);
            SLANG_PASS(generateDllImportFuncs, codeGenContext->getTargetProgram(), sink);
            SLANG_PASS(generateDllExportFuncs, sink);
            break;
        }
    default:
        break;
    }

    calcRequiredLoweringPassSet(requiredLoweringPassSet, codeGenContext, irModule->getModuleInst());

    switch (target)
    {
    case CodeGenTarget::PyTorchCppBinding:
        SLANG_PASS(generateHostFunctionsForAutoBindCuda, sink);
        SLANG_PASS(lowerBuiltinTypesForKernelEntryPoints, sink);
        SLANG_PASS(generatePyTorchCppBinding, sink);
        SLANG_PASS(handleAutoBindNames);
        break;
    case CodeGenTarget::CUDASource:
    case CodeGenTarget::CUDAHeader:
        SLANG_PASS(lowerBuiltinTypesForKernelEntryPoints, sink);
        SLANG_PASS(removeTorchKernels);
        SLANG_PASS(handleAutoBindNames);
        break;
    default:
        break;
    }

    SLANG_PASS(detectUninitializedResources, sink);

    if (codeGenContext->removeAvailableInDownstreamIR)
    {
        SLANG_PASS(removeAvailableInDownstreamModuleDecorations, target);
    }

    if (targetProgram->getOptionSet().shouldRunNonEssentialValidation())
    {
        SLANG_PASS(checkForRecursiveTypes, sink);
        SLANG_PASS(checkForRecursiveFunctions, codeGenContext->getTargetReq(), sink);
        SLANG_PASS(checkForOutOfBoundAccess, sink);

        if (requiredLoweringPassSet.missingReturn)
            SLANG_PASS(checkForMissingReturns, sink, target, false);

        // For some targets, we are more restrictive about what types are allowed
        // to be used as shader parameters in ConstantBuffer/ParameterBlock.
        // We will check for these restrictions here.
        SLANG_PASS(checkForInvalidShaderParameterType, targetRequest, sink);
    }

    if (sink->getErrorCount() != 0)
        return SLANG_FAIL;


    validateIRModuleIfEnabled(codeGenContext, irModule);

    SLANG_PASS(inferAnyValueSizeWhereNecessary, targetProgram, sink);

    if (sink->getErrorCount() != 0)
        return SLANG_FAIL;

    // If we have any witness tables that are marked as `KeepAlive`,
    // but are not used for dynamic dispatch, unpin them so we don't
    // do unnecessary work to lower them.
    //
    SLANG_PASS(unpinWitnessTables);

    // #11917: Gated on `sumVectorMatrix` to skip this whole-module walk when no
    // sum-reduction IR is present. `kIROp_SumVectorElements` and
    // `kIROp_SumMatrixElements` are produced only by the autodiff transpose pass
    // (slang-ir-autodiff-transpose.cpp), which runs via `finalizeAutoDiffPass`
    // before the last `calcRequiredLoweringPassSet` scan; no autodiff/transpose
    // pass runs between that scan and this call site, so the flag cannot be a
    // false-negative (any sum inst reaching here was seen by that scan).
    if (requiredLoweringPassSet.sumVectorMatrix)
        SLANG_PASS(lowerSumVectorMatrixInsts);

    if (!fastIRSimplificationOptions.minimalOptimization)
    {
        SLANG_PASS(simplifyIR, targetProgram, fastIRSimplificationOptions, sink);
    }
    else if (requiredLoweringPassSet.generics)
    {
        SLANG_PASS(eliminateDeadCode, fastIRSimplificationOptions.deadCodeElimOptions);
    }

    // Tagged union type lowering typically generates more reinterpret instructions.
    //
    // Gated on `taggedUnion` to skip this whole-module walk when no tagged-union IR is present.
    // The tagged-union opcodes are produced only by the typeflow specialization pass, which runs
    // before the last `calcRequiredLoweringPassSet` scan, so the flag cannot be a false-negative
    // (any tagged-union inst reaching here was seen by that scan). When the flag is false the pass
    // would be a no-op and create no reinterpret insts, so leaving `reinterpret` untouched is
    // correct. (Full producer trace in the PR for issue #11917.)
    if (requiredLoweringPassSet.taggedUnion)
    {
        if (SLANG_PASS(lowerTaggedUnionTypes, sink))
            requiredLoweringPassSet.reinterpret = true;
    }

    if (requiredLoweringPassSet.untaggedUnion)
        SLANG_PASS(lowerUntaggedUnionTypes, targetProgram, sink);

    if (requiredLoweringPassSet.reinterpret)
        SLANG_PASS(lowerReinterpret, targetProgram, sink);

    SLANG_PASS(lowerSequentialIDTagCasts, codeGenContext->getLinkage(), sink);
    if (requiredLoweringPassSet.tagOps)
        SLANG_PASS(lowerTagInsts, sink);
    if (requiredLoweringPassSet.tagType)
        SLANG_PASS(lowerTagTypes);

    SLANG_PASS(eliminateDeadCode, fastIRSimplificationOptions.deadCodeElimOptions);

    SLANG_PASS(lowerExistentials, targetProgram, sink);

    // Get rid of weak-use insts and any dictionaries in the module inst.
    SLANG_PASS(removeWeakUseInsts);

    clearTranslationDictionary(irModule);

    if (sink->getErrorCount() != 0)
        return SLANG_FAIL;

    // If we have a target that is GPU like we use the string hashing mechanism
    // but for that to work we need to inline such that calls (or returns) of strings
    // boil down into getStringHash(stringLiteral)
    if (!ArtifactDescUtil::isCpuLikeTarget(artifactDesc))
    {
        // We could fail because
        // 1) It's not inlinable for some reason (for example if it's recursive)
        SLANG_RETURN_ON_FAIL(SLANG_PASS(performTypeInlining, targetProgram, sink));
    }

    if (sink->getErrorCount() != 0)
        return SLANG_FAIL;

    // Must run before the getStringHash check below. `performTypeInlining` inlines a call into its
    // caller but leaves the original callee behind, and that leftover body still holds a
    // getStringHash on its own unfolded parameter. Checking first would reject legal code on the
    // strength of a function that is about to be deleted here.
    eliminateDeadCode(irModule, fastIRSimplificationOptions.deadCodeElimOptions);

    if (!ArtifactDescUtil::isCpuLikeTarget(artifactDesc) &&
        targetProgram->getOptionSet().shouldRunNonEssentialValidation())
    {
        // We could fail because (perhaps, somehow) end up with getStringHash that the operand
        // is not a string literal
        SLANG_RETURN_ON_FAIL(SLANG_PASS(checkGetStringHashInsts, sink));
    }

    SLANG_PASS(lowerTuples, sink);
    if (sink->getErrorCount() != 0)
        return SLANG_FAIL;

    SLANG_PASS(generateAnyValueMarshallingFunctions, targetProgram);
    if (sink->getErrorCount() != 0)
        return SLANG_FAIL;

    // Don't need to run any further target-dependent passes if we are generating code
    // for host vm.
    if (target == CodeGenTarget::HostVM)
    {
        SLANG_PASS(performForceInlining);
        // Autodiff can leave void differential parameters and matching call arguments, but the
        // bytecode constants section cannot represent void values. Remove them before emission,
        // as the later target pipelines do.
        SLANG_PASS(cleanUpVoidType);
        SLANG_PASS(simplifyIR, targetProgram, defaultIRSimplificationOptions, sink);
        return SLANG_OK;
    }

    // After dynamic dispatch logic is resolved into ordinary function calls,
    // we can now run our stage specialization logic.
    if (requiredLoweringPassSet.specializeStageSwitch)
        SLANG_PASS(specializeStageSwitch);
    if (sink->getErrorCount() != 0)
        return SLANG_FAIL;
    validateIRModuleIfEnabled(codeGenContext, irModule);

    switch (target)
    {
    case CodeGenTarget::SPIRV:
    case CodeGenTarget::SPIRVAssembly:
    case CodeGenTarget::HLSL:
        break;
    case CodeGenTarget::CUDASource:
        {
            auto targetCaps = targetRequest->getTargetCaps();
            if (!targetCaps.implies(CapabilityAtom::optix_coopvec))
                SLANG_PASS(lowerCooperativeVectors, sink);
        }
        break;
    default:
        SLANG_PASS(lowerCooperativeVectors, sink);
    }

    // Inline calls to any functions marked with [__unsafeInlineEarly] or [ForceInline].
    SLANG_PASS(performForceInlining);

    // Specialization can introduce dead code that could trip
    // up downstream passes like type legalization, so we
    // will run a DCE pass to clean up after the specialization.
    //
    if (fastIRSimplificationOptions.minimalOptimization)
    {
        // Since we force-inlined functions, we need to clean up
        // dead-branches that may have been revealed due to operations
        // like inlining allowing us to find dead branches.
        // These must be cleaned since otherwise static_assert's will falsely
        // detect true due to dead branches with static_assert not being removed.
        SLANG_PASS(

            applySparseConditionalConstantPropagation,
            targetProgram,
            sink);
        SLANG_PASS(eliminateDeadCode, deadCodeEliminationOptions);
    }
    else
    {
        SLANG_PASS(simplifyIR, targetProgram, defaultIRSimplificationOptions, sink);
    }

    // Report checkpointing information.
    if (codeGenContext->shouldReportCheckpointIntermediates())
    {
        reportCheckpointIntermediates(codeGenContext, sink, irModule);
    }

    validateIRModuleIfEnabled(codeGenContext, irModule);

    if ((target == CodeGenTarget::HLSL || isD3DTarget(targetRequest)) &&
        requiredLoweringPassSet.barrierFlagValidation)
    {
        SLANG_PASS(validateBarrierFlagsForHLSL, sink);
        if (sink->getErrorCount() != 0)
            return SLANG_FAIL;
    }

    // On non-HLSL targets, there isn't an implementation of `AppendStructuredBuffer`
    // and `ConsumeStructuredBuffer` types, so we lower them into normal struct types
    // of `RWStructuredBuffer` typed fields now.
    //
    // Gated on `appendConsumeStructuredBuffer` to skip this whole-module walk when
    // neither type is present. `calcRequiredLoweringPassSet` flags accumulate across the
    // post-link and post-specialization scans (they are not reset between them). These
    // types are produced by the front-end and are never synthesized by an IR pass, so in
    // particular none is created after the last scan: any instance present here was
    // recorded by a scan and set the flag, and the gate can never be a false-negative
    // (skip a needed lowering). The flag can only be stale-true (e.g. an unused buffer
    // dead-code-eliminated after a scan), a harmless no-op walk — so gating is
    // behavior-preserving.
    if (target != CodeGenTarget::HLSL && requiredLoweringPassSet.appendConsumeStructuredBuffer)
    {
        SLANG_PASS(lowerAppendConsumeStructuredBuffers, targetProgram, sink);
    }

    switch (target)
    {
    default:
        if (!ArtifactDescUtil::isCpuLikeTarget(artifactDesc))
            break;
        [[fallthrough]];
    case CodeGenTarget::HLSL:
    case CodeGenTarget::Metal:
    case CodeGenTarget::MetalLib:
    case CodeGenTarget::MetalLibAssembly:
    case CodeGenTarget::WGSL:
        if (requiredLoweringPassSet.combinedTextureSamplers)
            SLANG_PASS(lowerCombinedTextureSamplers, codeGenContext, sink);
        break;
    }

    if (codeGenContext->getTargetProgram()->getOptionSet().getBoolOption(
            CompilerOptionName::VulkanEmitReflection))
    {
        SLANG_PASS(addUserTypeHintDecorations);
    }

    SLANG_PASS(legalizeEmptyArray, sink);

    SLANG_PASS(legalizeVectorTypes, sink);

    // For CUDA targets, always inline global constants to avoid dynamic initialization
    // of __device__ variables rejected by NVRTC. This runs independently of the broader
    // resource/existential type legalization, which remains disabled for CUDA.
    //
    // We also need this pass on the CPU targets in shader mode, as global
    // constants may reference global parameters, which can't be emitted as
    // constants.
    if (target == CodeGenTarget::CUDASource ||
        (isCPUTarget(targetRequest) && isKernelTarget(target)) ||
        options.shouldLegalizeExistentialAndResourceTypes)
    {
        SLANG_PASS(inlineGlobalConstantsForLegalization);
    }

    // We don't need the legalize pass for C/C++ based types
    if (options.shouldLegalizeExistentialAndResourceTypes)
    {
        if (isMetalTarget(targetRequest))
        {
            // Metal is a special target in that we want to legalize constant buffer
            // types as if it is a bindful target, and skip legalizing parameter block
            // types as if it is a bindless target.
            // To achieve this, we want to ensure that all resource typed fields in parameter
            // blocks are translated into descriptor handles first before running the resource
            // type legalization pass for metal, so that type legalization pass won't mess
            // around with them.
            BufferElementTypeLoweringOptions bufferElementTypeLoweringOptions = {};
            bufferElementTypeLoweringOptions.loweringPolicyKind =
                BufferElementTypeLoweringPolicyKind::MetalParameterBlock;
            SLANG_PASS(
                lowerBufferElementTypeToStorageType,
                targetProgram,
                bufferElementTypeLoweringOptions);
        }

        // The Slang language allows interfaces to be used like
        // ordinary types (including placing them in constant
        // buffers and entry-point parameter lists), but then
        // getting them to lay out in a reasonable way requires
        // us to treat fields/variables with interface type
        // *as if* they were pointers to heap-allocated "objects."
        //
        // Specialization will have replaced fields/variables
        // with interface types like `IFoo` with fields/variables
        // with pointer-like types like `ExistentialBox<SomeType>`.
        //
        // We need to legalize these pointer-like types away,
        // which involves two main changes:
        //
        //  1. Any `ExistentialBox<...>` fields need to be moved
        //  out of their enclosing `struct` type, so that the layout
        //  of the enclosing type is computed as if the field had
        //  zero size.
        //
        //  2. Once an `ExistentialBox<X>` has been floated out
        //  of its parent and landed somwhere permanent (e.g., either
        //  a dedicated variable, or a field of constant buffer),
        //  we need to replace it with just an `X`, after which we
        //  will have (more) legal shader code.
        //
        // For DXIL/HLSL with NVAPI and SPIRV: add dummy fields to empty ray payloads
        if (isD3DTarget(targetRequest) || isSPIRV(targetRequest->getTarget()))
        {
            SLANG_PASS(legalizeEmptyRayPayloadsForHLSL);
        }

        // For DXIL only: unwrap ForceVarIntoRayPayloadStructTemporarily instructions
        // (must run before legalizeExistentialTypeLayout removes empty struct parameters)
        if (isD3DTarget(targetRequest))
        {
            SLANG_PASS(legalizeNonStructParameterToStructForHLSL);

            // HLSL SM 6.7+ requires every member of a `[raypayload]` struct to declare
            // both a `read(...)` and a `write(...)` qualifier. The call-site fill above
            // only covers payload structs reached through a `TraceRay`-style call, so a
            // user-authored struct with one-sided PAQ that only reaches a hit shader
            // (e.g. a per-stage-compiled shader library) would slip through. Fill any
            // missing per-side PAQs structurally on every `[raypayload]` struct.
            auto profile = getEffectiveTargetProfile(
                targetProgram->getTargetReq(),
                targetProgram->getOptionSet());
            if (profile.getFamily() == ProfileFamily::DX &&
                profile.getVersion() >= ProfileVersion::DX_6_7)
            {
                SLANG_PASS(legalizeRayPayloadAccessQualifiersForHLSL);
            }
        }

        if (requiredLoweringPassSet.existentialTypeLayout)
        {
            SLANG_PASS(legalizeExistentialTypeLayout, targetProgram, sink);
        }

        validateIRModuleIfEnabled(codeGenContext, irModule);

        if (!validateStructuredBufferResourceTypes(irModule, sink, targetRequest))
            return SLANG_FAIL;

        // Many of our target languages and/or downstream compilers
        // don't support `struct` types that have resource-type fields.
        // In order to work around this limitation, we will rewrite the
        // IR so that any structure types with resource-type fields get
        // split into a "tuple" that comprises the ordinary fields (still
        // bundles up as a `struct`) and one element for each resource-type
        // field (recursively).
        //
        // What used to be individual variables/parameters/arguments/etc.
        // then become multiple variables/parameters/arguments/etc.
        //
        SLANG_PASS(legalizeResourceTypes, targetProgram, sink);

        // We also need to legalize empty types for Metal targets.
        switch (target)
        {
        case CodeGenTarget::Metal:
        case CodeGenTarget::MetalLib:
        case CodeGenTarget::MetalLibAssembly:
            SLANG_PASS(legalizeEmptyTypes, targetProgram, sink);
            break;
        }
        //  Debugging output of legalization
        validateIRModuleIfEnabled(codeGenContext, irModule);
    }
    else
    {
        // On CPU/CUDA targets, we simply elminate any empty types if
        // they are not part of public interface.
        SLANG_PASS(legalizeEmptyTypes, targetProgram, sink);
    }

    if (isCPUTargetViaLLVM(targetRequest))
    {
        // The LLVM targets are special in that we always lower all matrices
        // into arrays of vectors, because matrices aren't (yet?) built-in types
        // in LLVM. Due to this, lowerBufferElementTypeToStorageType needs to
        // occur earlier than usual, because otherwise it no longer knows what
        // is a matrix and can't generate proper code for column/row-major
        // matrix loads/stores.
        BufferElementTypeLoweringOptions bufferElementTypeLoweringOptions = {};
        bufferElementTypeLoweringOptions.loweringPolicyKind =
            BufferElementTypeLoweringPolicyKind::LLVM;
        SLANG_PASS(
            lowerBufferElementTypeToStorageType,
            targetProgram,
            bufferElementTypeLoweringOptions);
    }

    SLANG_PASS(legalizeMatrixTypes, targetProgram, sink);

    // Once specialization and type legalization have been performed,
    // we should perform some of our basic optimization steps again,
    // to see if we can clean up any temporaries created by legalization.
    // (e.g., things that used to be aggregated might now be split up,
    // so that we can work with the individual fields).
    if (fastIRSimplificationOptions.minimalOptimization)
        SLANG_PASS(eliminateDeadCode, deadCodeEliminationOptions);
    else
        SLANG_PASS(simplifyIR, targetProgram, fastIRSimplificationOptions, sink);

    // Enforce that no untyped descriptor-heap handle (`ResourceDescriptorHeap[i]` /
    // `SamplerDescriptorHeap[j]`) survives to emit: lower any that peephole did not collapse to its
    // underlying `uint` index. Gated on `untypedResourceHandle` so the whole-module walk is skipped
    // when no such handle is present. The producing intrinsics live in `hlsl.meta.slang` and are
    // lowered at AST->IR time, before the last `calcRequiredLoweringPassSet` scan; no pass between
    // that scan and here synthesizes these ops, so the flag cannot be a false-negative.
    if (requiredLoweringPassSet.untypedResourceHandle)
        SLANG_PASS(lowerUntypedResourceHandleToUInt);

    if (requiredLoweringPassSet.dynamicResourceHeap)
        SLANG_PASS(lowerDynamicResourceHeap, targetProgram, sink);

    validateIRModuleIfEnabled(codeGenContext, irModule);

    // After type legalization and subsequent SSA cleanup we expect
    // that any resource types passed to functions are exposed
    // as their own top-level parameters (which might have
    // resource or array-of-...-resource types).
    //
    // Many of our targets place restrictions on how certain
    // resource types can be used, so that having them as
    // function parameters, reults, etc. is invalid.
    // We clean up the usages of resource values here.
    SLANG_PASS(specializeResourceUsage, codeGenContext);

    // Specialize calls to functions with values loaded from an immutable location,
    // so that we directly load the value inside the callee, instead of loading the
    // value outside of the callee and copy it in. This is necessary to avoid copying
    // large values (e.g. arrays) in registers, where most of the elements are not
    // actually used.
    SLANG_PASS(specializeFuncsForBufferLoadArgs, codeGenContext);

    // Push `structuredBufferLoad` to the end of access chain to avoid loading unnecessary data.
    SLANG_PASS(deferBufferLoad, codeGenContext);

    // We also want to specialize calls to functions that
    // takes unsized array parameters if possible.
    SLANG_PASS(specializeArrayParameters, codeGenContext);

    validateIRModuleIfEnabled(codeGenContext, irModule);

    // Process `static_assert` after the specialization is done.
    // Some information for `static_assert` is available only after the specialization.
    checkStaticAssert(irModule->getModuleInst(), sink);

    switch (target)
    {
    case CodeGenTarget::HLSL:
        {
            // For HLSL(fxc) only, we need to "wrap" any
            // structured buffers defined over matrix types so
            // that they instead use an intermediate `struct`.
            // This is required to get those targets to respect
            // the options for matrix layout set via `#pragma`
            // or command-line options.
            //
            SLANG_PASS(wrapStructuredBuffersOfMatrices);
            validateIRModuleIfEnabled(codeGenContext, irModule);
        }
        break;
    case CodeGenTarget::Metal:
    case CodeGenTarget::MetalLib:
        // Metal does not allow `ConstantBuffer<StructuredBuffer<T>>`, so we need to create
        // a wrapper struct for the `StructuredBuffer<T>`.
        SLANG_PASS(wrapCBufferElementsForMetal);
        break;
    default:
        break;
    }

    // For all targets, we translate load/store operations
    // of aggregate types from/to byte-address buffers into
    // stores of individual scalar or vector values.
    //
    if (requiredLoweringPassSet.byteAddressBuffer)
    {
        ByteAddressBufferLegalizationOptions byteAddressBufferOptions;

        // Depending on the target, we may decide to do
        // more aggressive translation that reduces the
        // load/store operations down to invididual scalars
        // (splitting up vector ops).
        //
        switch (target)
        {
        default:
            if (isCPUTargetViaLLVM(targetRequest))
            {
                // We need to scalarize the loads on LLVM CPU targets, as
                // ByteAddressBuffer loads & stores may only have an alignment
                // of 4, meaning that vector loads / stores could be misaligned
                // and cause crashes with SSE, AVX etc. SIMD instruction sets.
                byteAddressBufferOptions.scalarizeVectorLoadStore = true;
            }
            break;

        case CodeGenTarget::GLSL:
        case CodeGenTarget::SPIRV:
        case CodeGenTarget::SPIRVAssembly:
            // For GLSL targets, we want to translate the vector load/store
            // operations into scalar ops. This is in part as a simplification,
            // but it also ensures that our generated code respects the lax
            // alignment rules for D3D byte-address buffers (the base address
            // of a buffer need not be more than 4-byte aligned, and loads
            // of vectors need only be aligned based on their element type).
            //
            // Slang IR supports a variant of `Load<T>` on byte-address buffers
            // that will have greater alignment than required by D3D. The
            // alignment information is inferred from the operation like a
            // `Load4Aligned<T>` that returns a `vector<4,T>` that assumes a
            // `4*sizeof(T)` alignment. We may choose to disable that in favor
            // of byte-address indexing by setting this flag to true.
            byteAddressBufferOptions.scalarizeVectorLoadStore = false;

            // For GLSL targets, there really isn't a low-level concept
            // of a byte-address buffer at all, and the standard "shader storage
            // buffer" (SSBO) feature is a lot closer to an HLSL structured
            // buffer for our purposes.
            //
            // In particular, each SSBO can only have a single element type,
            // so that even with bitcasts we can't have a single buffer declaration
            // (e.g., one with `uint` elements) service all load/store operations
            // (e.g., a `half` value can't be stored atomically if there are
            // `uint` elements, unless we use explicit atomics).
            //
            // In order to simplify things, we will translate byte-address buffer
            // ops to equivalent structured-buffer ops for GLSL targets, where
            // each unique type being loaded/stored yields a different global
            // parameter declaration of the buffer.
            //
            byteAddressBufferOptions.translateToStructuredBufferOps = true;
            break;
        case CodeGenTarget::Metal:
        case CodeGenTarget::MetalLib:
        case CodeGenTarget::MetalLibAssembly:
            byteAddressBufferOptions.scalarizeVectorLoadStore = true;
            byteAddressBufferOptions.treatGetEquivalentStructuredBufferAsGetThis = true;
            byteAddressBufferOptions.translateToStructuredBufferOps = false;
            byteAddressBufferOptions.lowerBasicTypeOps = true;
            break;
        case CodeGenTarget::WGSL:
        case CodeGenTarget::WGSLSPIRV:
        case CodeGenTarget::WGSLSPIRVAssembly:
            byteAddressBufferOptions.scalarizeVectorLoadStore = true;
            byteAddressBufferOptions.treatGetEquivalentStructuredBufferAsGetThis = true;
            byteAddressBufferOptions.translateToStructuredBufferOps = false;
            byteAddressBufferOptions.lowerBasicTypeOps = true;
            byteAddressBufferOptions.useBitCastFromUInt = true;
            break;
        }

        // We also need to decide whether to translate
        // any "leaf" load/store operations over to
        // use only unsigned-integer types and then
        // bit-cast, or if we prefer to leave them
        // as load/store of the original type.
        //
        switch (target)
        {
        case CodeGenTarget::HLSL:
            {
                auto profile = getEffectiveTargetProfile(
                    targetProgram->getTargetReq(),
                    targetProgram->getOptionSet());
                if (profile.getFamily() == ProfileFamily::DX)
                {
                    if (profile.getVersion() <= ProfileVersion::DX_5_0)
                    {
                        // Fxc and earlier dxc versions do not support
                        // a templates `.Load<T>` operation on byte-address
                        // buffers, and instead need us to emit separate
                        // `uint` loads and then bit-cast over to
                        // the correct type.
                        //
                        byteAddressBufferOptions.useBitCastFromUInt = true;
                    }
                }
            }
            break;

        default:
            break;
        }

        SLANG_PASS(
            legalizeByteAddressBufferOps,
            session,
            targetProgram,
            codeGenContext->getSink(),
            byteAddressBufferOptions);
    }

    if (isCPUTargetViaLLVM(targetRequest))
    {
        // On CPU LLVM targets, the target itself has no concept of resource
        // types (CPUs have no opinions on what a texture is), so we can lower
        // those to concrete types here.
        //
        // We perform this after ByteAddressBuffer op lowering so that we don't
        // have to deal with misaligned pointers.
        SLANG_PASS(lowerCPUResourceTypes, codeGenContext);
    }

    // For SPIR-V, this function is called elsewhere, so that it can happen after address space
    // specialization
    if (target != CodeGenTarget::SPIRV && target != CodeGenTarget::SPIRVAssembly)
    {
        bool skipFuncParamValidation = true;
        SLANG_PASS(validateAtomicOperations, skipFuncParamValidation, sink);
    }

    // For CUDA targets only, we will need to turn operations
    // the implicitly reference the "active mask" into ones
    // that use (and pass around) an explicit mask instead.
    //
    switch (target)
    {
    case CodeGenTarget::CUDASource:
    case CodeGenTarget::CUDAHeader:
    case CodeGenTarget::PTX:
        {
            SLANG_PASS(synthesizeActiveMask, codeGenContext->getSink());

            validateIRModuleIfEnabled(codeGenContext, irModule);
        }
        break;

    default:
        break;
    }

    switch (target)
    {
    case CodeGenTarget::GLSL:
    case CodeGenTarget::SPIRV:
    case CodeGenTarget::WGSL:
        SLANG_PASS(resolveTextureFormat);
        break;
    }

    // Specialization can expose references to global varying builtins that were
    // previously hidden behind generic/interface dispatch. Translate all global
    // varying inputs/outputs now, after specialization, but before target
    // entry-point legalization.
    if (requiredLoweringPassSet.globalVaryingVar)
        SLANG_PASS(translateGlobalVaryingVar, codeGenContext);

    if (requiredLoweringPassSet.resolveVaryingInputRef)
        SLANG_PASS(resolveVaryingInputRef);

    SLANG_PASS(fixEntryPointCallsites);

    // For GLSL only, we will need to perform "legalization" of
    // the entry point and any entry-point parameters.
    //
    // TODO: We should consider moving this legalization work
    // as late as possible, so that it doesn't affect how other
    // optimization passes need to work.
    //
    switch (target)
    {
    case CodeGenTarget::GLSL:
    case CodeGenTarget::SPIRV:
    case CodeGenTarget::SPIRVAssembly:
        {
            ShaderExtensionTracker glslExtensionTracker;
            ShaderExtensionTracker* glslExtensionTrackerPtr =
                options.sourceEmitter
                    ? as<ShaderExtensionTracker>(options.sourceEmitter->getExtensionTracker())
                    : &glslExtensionTracker;

            SLANG_PASS(
                legalizeEntryPointsForGLSL,
                session,
                irEntryPoints,
                codeGenContext,
                glslExtensionTrackerPtr);

            // GLSL and SPIR-V both require an integer `switch` selector; a `switch` on a
            // `bool` reaches here unchanged, so rewrite it to an integer switch.
            SLANG_PASS(legalizeBoolSwitchForTargetsRequiringIntSwitch);

            validateIRModuleIfEnabled(codeGenContext, irModule);
        }
        break;
    case CodeGenTarget::Metal:
    case CodeGenTarget::MetalLib:
    case CodeGenTarget::MetalLibAssembly:
        {
            SLANG_PASS(legalizeIRForMetal, targetProgram, sink);
        }
        break;
    case CodeGenTarget::CSource:
    case CodeGenTarget::CPPSource:
    case CodeGenTarget::CPPHeader:
    case CodeGenTarget::ShaderLLVMIR:
    case CodeGenTarget::ShaderObjectCode:
    case CodeGenTarget::ShaderHostCallable:
        {
            SLANG_PASS(
                legalizeEntryPointVaryingParamsForCPU,
                targetProgram,
                codeGenContext->getSink());
        }
        break;

    case CodeGenTarget::CUDASource:
    case CodeGenTarget::CUDAHeader:
        {
            SLANG_PASS(legalizeEntryPointVaryingParamsForCUDA, codeGenContext->getSink());
        }
        break;

    case CodeGenTarget::WGSL:
    case CodeGenTarget::WGSLSPIRV:
    case CodeGenTarget::WGSLSPIRVAssembly:
        {
            // WGSL, like GLSL and SPIR-V, requires an integer `switch` selector.
            SLANG_PASS(legalizeBoolSwitchForTargetsRequiringIntSwitch);
            SLANG_PASS(legalizeIRForWGSL, targetProgram, sink);
        }
        break;

    default:
        break;
    }

    if (!isSPIRV(targetRequest->getTarget()))
    {
        SLANG_PASS(floatNonUniformResourceIndex, NonUniformResourceIndexFloatMode::Textual);
    }

    if (isD3DTarget(targetRequest) || isKhronosTarget(targetRequest) ||
        isWGPUTarget(targetRequest) || isMetalTarget(targetRequest))
        SLANG_PASS(legalizeLogicalAndOr, targetProgram);

    // Create aliases for all dynamic resource parameters.
    if (requiredLoweringPassSet.dynamicResource && isKhronosTarget(targetRequest))
        SLANG_PASS(legalizeDynamicResourcesForGLSL, codeGenContext);

    // Legalize `ImageSubscript` loads.
    switch (target)
    {
    case CodeGenTarget::MetalLibAssembly:
    case CodeGenTarget::MetalLib:
    case CodeGenTarget::Metal:
    case CodeGenTarget::GLSL:
    case CodeGenTarget::SPIRV:
    case CodeGenTarget::SPIRVAssembly:
        {
            SLANG_PASS(legalizeImageSubscript, targetRequest, sink);
        }
        break;
    default:
        break;
    }

    // Legalize constant buffer loads.
    switch (target)
    {
    case CodeGenTarget::GLSL:
    case CodeGenTarget::SPIRV:
    case CodeGenTarget::SPIRVAssembly:
        {
            SLANG_PASS(legalizeConstantBufferLoadForGLSL);
            SLANG_PASS(legalizeDispatchMeshPayloadForGLSL);
        }
        break;
    default:
        break;
    }

    switch (target)
    {
    default:
        break;
    case CodeGenTarget::HLSL:
    case CodeGenTarget::GLSL:
    case CodeGenTarget::WGSL:
        SLANG_PASS(moveGlobalVarInitializationToEntryPoints, targetProgram);
        break;
    // For SPIR-V to SROA across 2 entry-points a value must not be a global
    case CodeGenTarget::SPIRV:
    case CodeGenTarget::SPIRVAssembly:
        SLANG_PASS(moveGlobalVarInitializationToEntryPoints, targetProgram);
        if (targetProgram->getOptionSet().getBoolOption(
                CompilerOptionName::EnableExperimentalPasses))
            SLANG_PASS(introduceExplicitGlobalContext, target);
        SLANG_PASS(transformParamsToConstRef, codeGenContext->getSink());
        break;
    case CodeGenTarget::Metal:
    case CodeGenTarget::CPPSource:
    case CodeGenTarget::CPPHeader:
    case CodeGenTarget::CUDASource:
    case CodeGenTarget::CUDAHeader:
        // For CUDA/OptiX like targets, add our pass to replace inout parameter copies with
        // direct pointers
        SLANG_PASS(undoParameterCopy);
        // Transform struct parameters to use ConstRef for better performance
        if (isCPUTarget(targetRequest) || isCUDATarget(targetRequest) ||
            isMetalTarget(targetRequest))
        {
            SLANG_PASS(transformParamsToConstRef, codeGenContext->getSink());
        }
        validateIRModuleIfEnabled(codeGenContext, irModule);
        [[fallthrough]];
    case CodeGenTarget::ShaderLLVMIR:
    case CodeGenTarget::ShaderObjectCode:
    case CodeGenTarget::ShaderHostCallable:
        SLANG_PASS(moveGlobalVarInitializationToEntryPoints, targetProgram);
        SLANG_PASS(introduceExplicitGlobalContext, target);
        if (target == CodeGenTarget::CPPSource || target == CodeGenTarget::CPPHeader)
        {
            SLANG_PASS(convertEntryPointPtrParamsToRawPtrs);
        }
        validateIRModuleIfEnabled(codeGenContext, irModule);
        break;
    }

    // TODO: our current dynamic dispatch pass will remove all uses of witness tables.
    // If we are going to support function-pointer based, "real" modular dynamic dispatch,
    // we will need to disable this pass.
    SLANG_PASS(stripLegalizationOnlyInstructions);

    switch (target)
    {
    // On targets that don't support default initialization, remove 'raw' default construct
    // insts because our code-gen will not have any way to emit them.
    //
    case CodeGenTarget::SPIRV:
        if (targetProgram->shouldEmitSPIRVDirectly())
            SLANG_PASS(removeRawDefaultConstructors);
        break;
    case CodeGenTarget::ShaderLLVMIR:
    case CodeGenTarget::HostLLVMIR:
    case CodeGenTarget::HostObjectCode:
    case CodeGenTarget::ShaderObjectCode:
    case CodeGenTarget::ShaderSharedLibrary:
    case CodeGenTarget::HostExecutable:
    case CodeGenTarget::HostSharedLibrary:
    case CodeGenTarget::ShaderHostCallable:
        if (isCPUTargetViaLLVM(targetRequest))
            SLANG_PASS(removeRawDefaultConstructors);
        break;
    default:
        break;
    }

    validateIRModuleIfEnabled(codeGenContext, irModule);

    // Validate vectors and matrices according to what the target allows
    SLANG_PASS(validateVectorsAndMatrices, sink, targetRequest);

    // The resource-based specialization pass above
    // may create specialized versions of functions, but
    // it does not try to completely eliminate the original
    // functions, so there might still be invalid code in
    // our IR module.
    //
    // We run DCE pass again to clean things up.
    //
    SLANG_PASS(eliminateDeadCode, deadCodeEliminationOptions);

    // Check the remaining LateRequireCapability IR insts
    //
    // #11917: Gated on `lateRequireCapability` to skip this whole-module walk
    // (and its entry-point reference-graph build) when no
    // `kIROp_LateRequireCapability` IR is present. That opcode is produced only
    // by the front end (slang-lower-to-ir.cpp), so it is present before the last
    // `calcRequiredLoweringPassSet` scan; the flag cannot be a false-negative.
    // The pass diagnoses only from these insts, so when the flag is false it is a
    // pure no-op and gating drops no diagnostic.
    if (requiredLoweringPassSet.lateRequireCapability)
        SLANG_PASS(processLateRequireCapabilityInsts, codeGenContext, sink);

    SLANG_PASS(cleanUpVoidType);

    if (isKhronosTarget(targetRequest))
    {
        // As a fallback, if the above specialization steps failed to remove resource type
        // parameters, we will inline the functions in question to make sure we can produce
        // valid GLSL.
        SLANG_PASS(performGLSLResourceReturnFunctionInlining, targetProgram);
    }
    validateIRModuleIfEnabled(codeGenContext, irModule);


    // Lower the `getRegisterIndex` and `getRegisterSpace` intrinsics.
    //
    if (requiredLoweringPassSet.bindingQuery)
        SLANG_PASS(lowerBindingQueries, sink);

    // For some small improvement in type safety we represent these as opaque
    // structs instead of regular arrays.
    //
    // If any have survived this far, change them back to regular (decorated)
    // arrays that the emitters can deal with.
    if (requiredLoweringPassSet.meshOutput)
        SLANG_PASS(legalizeMeshOutputTypes);


    // Lower all bit_cast operations on complex types into leaf-level
    // bit_cast on basic types.
    if (requiredLoweringPassSet.bitcast)
        SLANG_PASS(lowerBitCast, targetProgram, sink);

    // Rewrite functions that return arrays to return them via `out` parameter,
    // since our target languages doesn't allow returning arrays.
    if (!isMetalTarget(targetRequest) && !isSPIRV(target))
        SLANG_PASS(legalizeArrayReturnType);

    if (isKhronosTarget(targetRequest) || target == CodeGenTarget::HLSL)
    {
        SLANG_PASS(legalizeUniformBufferLoad);
        if (targetProgram->getOptionSet().getBoolOption(CompilerOptionName::VulkanInvertY))
            SLANG_PASS(invertYOfPositionOutput);
        if (targetProgram->getOptionSet().getBoolOption(CompilerOptionName::VulkanUseDxPositionW))
            SLANG_PASS(rcpWOfPositionInput);
    }

    BufferElementTypeLoweringOptions bufferElementTypeLoweringOptions = {};
    if (isWGPUTarget(targetRequest))
        bufferElementTypeLoweringOptions.loweringPolicyKind =
            BufferElementTypeLoweringPolicyKind::WGSL;
    else if (isKhronosTarget(targetRequest))
        bufferElementTypeLoweringOptions.loweringPolicyKind =
            BufferElementTypeLoweringPolicyKind::KhronosTarget;
    else if (isMetalTarget(targetRequest))
        bufferElementTypeLoweringOptions.loweringPolicyKind =
            BufferElementTypeLoweringPolicyKind::Metal;
    else
        bufferElementTypeLoweringOptions.loweringPolicyKind =
            BufferElementTypeLoweringPolicyKind::Default;
    SLANG_PASS(
        lowerBufferElementTypeToStorageType,
        targetProgram,
        bufferElementTypeLoweringOptions);

    // If we are generating code for glsl or metal, perform address space propagation now.
    // For SPIRV, we will do that during spirv legalization that happens after
    // `linkAndOptimizeIR`.
    if (target == CodeGenTarget::GLSL)
    {
        NoOpInitialAddressSpaceAssigner addrSpaceAssigner;
        SLANG_PASS(specializeAddressSpace, &addrSpaceAssigner);
    }
    else if (isMetalTarget(targetRequest))
    {
        SLANG_PASS(specializeAddressSpaceForMetal);
    }
    else if (isWGPUTarget(targetRequest))
    {
        SLANG_PASS(specializeAddressSpaceForWGSL);
    }

    bool emitSpirvDirectly = targetProgram->shouldEmitSPIRVDirectly();

    if (isKhronosTarget(targetRequest) && emitSpirvDirectly)
    {
        // If we are emitting SPIR-V directly, we should try to specialize function calls
        // whose argument is an access chain into a global variable/buffer directly after
        // lowerBufferElementTypeToStorageType, so that we can be more SPIRV conformant by
        // eliminating the case where an access chain is passed as function argument.
        // This is disallowed by SPIRV rule 2.16.1 when VariablePointer is not declared.
        SLANG_PASS(specializeFuncsForBufferLoadArgs, codeGenContext);
    }

    // If we are generating code for CUDA, we should translate all immutable buffer loads to
    // using `__ldg` intrinsic for improved performance.
    if (isCUDATarget(targetRequest))
    {
        SLANG_PASS(lowerImmutableBufferLoadForCUDA, targetProgram);
    }

    SLANG_PASS(performForceInlining);

    if (emitSpirvDirectly)
    {
        SLANG_PASS(performIntrinsicFunctionInlining);
    }

    SLANG_PASS(eliminateMultiLevelBreak, targetProgram);

    if (!fastIRSimplificationOptions.minimalOptimization)
    {
        IRSimplificationOptions simplificationOptions = fastIRSimplificationOptions;
        simplificationOptions.cfgOptions.removeTrivialSingleIterationLoops = true;
        SLANG_PASS(simplifyIR, targetProgram, simplificationOptions, sink);
    }

    if (isKhronosTarget(targetRequest) && !emitSpirvDirectly)
    {
        SLANG_PASS(legalizeModesOfNonCopyableOpaqueTypedParamsForGLSL, codeGenContext);
    }

    // Required for AD 2.0 which can create empty types.
    // TODO: Maybe make this conditional (only touch the optimizable types).
    // to make it more narrowly scoped.
    //
    SLANG_PASS(legalizeEmptyTypes, targetProgram, sink);

    // As a late step, we need to take the SSA-form IR and move things *out*
    // of SSA form, by eliminating all "phi nodes" (block parameters) and
    // introducing explicit temporaries instead. Doing this at the IR level
    // means that subsequent emit logic doesn't need to contend with the
    // complexities of blocks with parameters.
    //
    {
        // Get the liveness mode.
        const LivenessMode livenessMode =
            codeGenContext->shouldTrackLiveness() ? LivenessMode::Enabled : LivenessMode::Disabled;
        //
        // Downstream targets may benefit from having live-range information for
        // local variables, and our IR currently encodes a reasonably good version
        // of that information. At this point we will insert live-range markers
        // for local variables, on when such markers are requested.
        //
        // After this point in optimization, any passes that introduce new
        // temporary variables into the IR module should take responsibility for
        // producing their own live-range information.
        //
        if (isEnabled(livenessMode))
        {
            SLANG_PASS(LivenessUtil::addVariableRangeStarts, livenessMode);
        }

        // We only want to accumulate locations if liveness tracking is enabled.
        PhiEliminationOptions phiEliminationOptions;
        if (isKhronosTarget(targetRequest) && emitSpirvDirectly)
        {
            phiEliminationOptions.eliminateCompositeTypedPhiOnly = false;
            phiEliminationOptions.useRegisterAllocation = true;
        }
        SLANG_PASS(eliminatePhis, livenessMode, phiEliminationOptions);
        // If liveness is enabled add liveness ranges based on the accumulated liveness
        // locations

        if (isEnabled(livenessMode))
        {
            SLANG_PASS(LivenessUtil::addRangeEnds, livenessMode);
        }
    }

    // TODO: We need to insert the logic that fixes variable scoping issues
    // here (rather than doing it very late in the emit process), because
    // otherwise the `applyGLSLLiveness()` operation below wouldn't be
    // able to see the live-range information that pass would need to add.
    // For now we are avoiding that problem by simply *not* emitting live-range
    // information when we fix variable scoping later on.

    // Depending on the target, certain things that were represented ass
    // single IR instructions will need to be emitted with the help of
    // function declaratons in output high-level code.
    //
    // One example of this is the live-range information, which needs
    // to be output to GLSL code that uses a glslang extension for
    // supporting function declarations that map directly to SPIR-V opcodes.
    //
    // We execute a pass here to transform any live-range instructions
    // in the module into function calls, for the targets that require it.
    //
    if (codeGenContext->shouldTrackLiveness())
    {
        if (isKhronosTarget(targetRequest))
        {
            SLANG_PASS(applyGLSLLiveness);
        }
    }

    if (isKhronosTarget(targetRequest) && emitSpirvDirectly)
    {
        SLANG_PASS(replaceLocationIntrinsicsWithRaytracingObject, targetProgram, sink);
    }

    validateIRModuleIfEnabled(codeGenContext, irModule);

    // Run a final round of simplifications to clean up unused things after phi-elimination.
    SLANG_PASS(simplifyNonSSAIR, targetProgram, fastIRSimplificationOptions, sink);

    // Metal rejects pointer-to-pointer types in buffer pointee types (e.g.
    // `device int* device*` as a struct field in a [[buffer(N)]] binding).
    //
    // Required predecessors:
    //   (a) specializeAddressSpaceForMetal — needs real pointer types
    //   (b) the main lowerBufferElementTypeToStorageType — matrix/bool
    //       fields must already be lowered
    //   (c) the main eliminatePhis — so the late eliminatePhis below
    //       only processes phis introduced by this pass
    //
    // Metal buffer element types go through three lowerBufferElementTypeToStorageType
    // invocations:
    //   1. MetalParameterBlock (~line 1606): resource fields -> DescriptorHandle
    //   2. Default/Khronos (~line 2225): matrix/bool -> lowered representations
    //   3. MetalPointerLowering (here): pointer fields -> UIntPtr
    // Each can decorate types with [PhysicalType]; pass 3 uses
    // shouldSkipPhysicalTypes = false to re-process types from passes 1 and 2.
    //
    // This does not conflict with the earlier MetalParameterBlock run
    // because they target orthogonal field kinds within the same types:
    // that pass converts resource fields to DescriptorHandle; this one
    // converts pointer fields to UIntPtr (shouldSkipPhysicalTypes returns
    // false to re-process types already decorated by that earlier pass).
    //
    // Safety: this sequence is safe to run in every Metal compilation,
    // even when no pointer fields are present. processModule scans all
    // global buffer types; if needsElementLowering returns false for all
    // of them, no types are created and no IR is modified, making the
    // subsequent passes no-ops on unchanged IR.
    if (isMetalTarget(targetRequest))
    {
        BufferElementTypeLoweringOptions metalPtrOptions;
        metalPtrOptions.loweringPolicyKind =
            BufferElementTypeLoweringPolicyKind::MetalPointerLowering;
        SLANG_PASS(lowerBufferElementTypeToStorageType, targetProgram, metalPtrOptions);

        // Materialize the [ForceInline] pack/unpack helpers the pass
        // creates. This is a module-wide call, but at this point in the
        // pipeline all prior [ForceInline] functions have already been
        // inlined and removed by the earlier performForceInlining call.
        // The only remaining [ForceInline] functions are the pack/unpack
        // helpers just created above.
        SLANG_PASS(performForceInlining);

        // The loop-based pack/unpack for large arrays (>kMaxArraySizeToUnroll)
        // introduces block parameters via emitLoopBlocks. Since the main
        // eliminatePhis already ran, these new phis must be eliminated
        // before emission.
        //
        // Liveness is disabled because liveness markers serve downstream
        // GLSL targets via applyGLSLLiveness; Metal does not consume them,
        // and the markers were already finalized before the main
        // eliminatePhis. LivenessMode::Disabled skips marker insertion;
        // it does not strip or invalidate markers placed by earlier passes.
        PhiEliminationOptions phiEliminationOptions;
        SLANG_PASS(eliminatePhis, LivenessMode::Disabled, phiEliminationOptions);

        // Address-space specialization is not re-run here — it already
        // executed against the original typed IR and must not see the
        // lowered UIntPtr types. eliminateMultiLevelBreak is unnecessary
        // because the generated loops are single-level. Full simplifyIR
        // (which includes constructSSA) is counterproductive because
        // eliminatePhis just took the IR out of SSA form — the emitter
        // expects non-SSA IR at this point.
        SLANG_PASS(simplifyNonSSAIR, targetProgram, fastIRSimplificationOptions, sink);
    }

    // We include one final step to (optionally) dump the IR and validate
    // it after all of the optimization passes are complete. This should
    // reflect the IR that code is generated from as closely as possible.
    //
    validateIRModuleIfEnabled(codeGenContext, irModule);

    if ((target != CodeGenTarget::SPIRV) && (target != CodeGenTarget::SPIRVAssembly))
    {
        // We need to perform a final pass to ensure that all the
        // variables in the IR module have their scopes set correctly.
        //
        // This is a separate pass because it needs to run after
        // all the other optimization passes have been performed.

        SLANG_PASS(applyVariableScopeCorrection, targetRequest);
        validateIRModuleIfEnabled(codeGenContext, irModule);
    }

    // Runs after target-specific lowering so it only captures cooperative types that remain
    // as native constructs visible to the driver (see ICooperativeTypesMetadata docs).
    {
        auto targetCaps = targetRequest->getTargetCaps();
        if (targetCaps.atLeastOneSetImpliedInOther(
                CapabilitySet(CapabilityName::cooperative_matrix)) ==
                CapabilitySet::ImpliesReturnFlags::Implied ||
            targetCaps.atLeastOneSetImpliedInOther(CapabilitySet(
                CapabilityName::cooperative_vector)) == CapabilitySet::ImpliesReturnFlags::Implied)
        {
            SLANG_PASS(collectCooperativeMetadata, sink, *metadata);
        }
    }

    if (targetProgram->getOptionSet().getBoolOption(CompilerOptionName::EmbedDownstreamIR))
    {
        SLANG_PASS(unexportNonEmbeddableIR, target);
    }

    {
        auto targetCaps = targetRequest->getTargetCaps();
        if (target != CodeGenTarget::PyTorchCppBinding &&
            targetCaps.atLeastOneSetImpliedInOther(CapabilitySet(
                CapabilityName::descriptor_handle)) == CapabilitySet::ImpliesReturnFlags::Implied)
        {
            if (!targetProgram->getOrCreateLayout(sink))
                return SLANG_FAIL;
        }
    }
    SLANG_PASS(collectMetadata, targetProgram, *metadata);

    if (!targetProgram->getOptionSet().shouldPerformMinimumOptimizations())
        SLANG_PASS(checkUnsupportedInst, codeGenContext->getTargetReq(), sink);

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

#undef SLANG_PASS
}

SlangResult CodeGenContext::emitEntryPointsSourceFromIR(ComPtr<IArtifact>& outArtifact)
{
    SLANG_PROFILE;

    outArtifact.setNull();

    auto session = getSession();
    auto sink = getSink();
    auto sourceManager = getSourceManager();
    auto target = getTargetFormat();
    auto targetRequest = getTargetReq();
    auto targetProgram = getTargetProgram();

    auto lineDirectiveMode = targetProgram->getOptionSet().getLineDirectiveMode();
    // We will generally use C-style line directives in order to give the user good
    // source locations on error messages from downstream compilers, but there are
    // a few exceptions.
    if (lineDirectiveMode == LineDirectiveMode::Default)
    {

        switch (targetRequest->getTarget())
        {

        case CodeGenTarget::GLSL:
            // We want to maximize compatibility with downstream tools.
            lineDirectiveMode = LineDirectiveMode::GLSL;
            break;

        case CodeGenTarget::WGSLSPIRVAssembly:
        case CodeGenTarget::WGSLSPIRV:
        case CodeGenTarget::WGSL:
            // WGSL doesn't support line directives.
            // See https://github.com/gpuweb/gpuweb/issues/606.
            lineDirectiveMode = LineDirectiveMode::None;
            break;
        }
    }

    ComPtr<IBoxValue<SourceMap>> sourceMap;

    // If SourceMap is enabled, we create one and associate it with the sourceWriter
    if (lineDirectiveMode == LineDirectiveMode::SourceMap)
    {
        sourceMap = new BoxValue<SourceMap>;
    }

    SourceWriter sourceWriter(sourceManager, lineDirectiveMode, sourceMap);

    CLikeSourceEmitter::Desc desc;

    desc.codeGenContext = this;

    if (getEntryPointCount() == 1)
    {
        auto entryPoint = getEntryPoint(getSingleEntryPointIndex());
        desc.entryPointStage = entryPoint->getStage();
        desc.effectiveProfile = getEffectiveProfile(entryPoint, targetRequest);
    }
    else
    {
        desc.entryPointStage = Stage::Unknown;
        desc.effectiveProfile =
            getEffectiveTargetProfile(targetRequest, targetProgram->getOptionSet());
    }
    desc.sourceWriter = &sourceWriter;

    // Define here, because must be in scope longer than the sourceEmitter, as sourceEmitter might
    // reference items in the linkedIR module
    LinkedIR linkedIR;

    RefPtr<CLikeSourceEmitter> sourceEmitter;
    SourceLanguage sourceLanguage = CLikeSourceEmitter::getSourceLanguage(target);

    switch (target)
    {
    default:
        switch (sourceLanguage)
        {
        case SourceLanguage::CPP:
            {
                sourceEmitter = new CPPSourceEmitter(desc);
                break;
            }
        case SourceLanguage::GLSL:
            {
                sourceEmitter = new GLSLSourceEmitter(desc);
                break;
            }
        case SourceLanguage::HLSL:
            {
                sourceEmitter = new HLSLSourceEmitter(desc);
                break;
            }
        case SourceLanguage::CUDA:
            {
                sourceEmitter = new CUDASourceEmitter(desc);
                break;
            }
        case SourceLanguage::Metal:
            {
                sourceEmitter = new MetalSourceEmitter(desc);
                break;
            }
        case SourceLanguage::WGSL:
            {
                sourceEmitter = new WGSLSourceEmitter(desc);
                break;
            }
        default:
            break;
        }
        break;
    case CodeGenTarget::PyTorchCppBinding:
        sourceEmitter = new TorchCppSourceEmitter(desc);
        break;
    }

    if (!sourceEmitter)
    {
        sink->diagnose(Diagnostics::UnableToGenerateCodeForTarget{
            .target = TypeTextUtil::getCompileTargetName(SlangCompileTarget(target))});
        return SLANG_FAIL;
    }

    SLANG_RETURN_ON_FAIL(sourceEmitter->init());

    ComPtr<IArtifactPostEmitMetadata> metadata;
    {
        LinkingAndOptimizationOptions linkingAndOptimizationOptions;

        linkingAndOptimizationOptions.sourceEmitter = sourceEmitter;

        switch (sourceLanguage)
        {
        default:
            break;

        case SourceLanguage::CPP:
        case SourceLanguage::C:
        case SourceLanguage::CUDA:
            linkingAndOptimizationOptions.shouldLegalizeExistentialAndResourceTypes = false;
            break;
        }

        SLANG_RETURN_ON_FAIL(linkAndOptimizeIR(this, linkingAndOptimizationOptions, linkedIR));

        auto irModule = linkedIR.module;

        // Perform final simplifications to help emit logic to generate more compact code.
        simplifyForEmit(irModule, targetRequest);

        metadata = linkedIR.metadata;

        // After all of the required optimization and legalization
        // passes have been performed, we can emit target code from
        // the IR module.
        //
        sourceEmitter->emitModule(irModule, sink);
    }

    String code = sourceWriter.getContent();
    sourceWriter.clearContent();

    // Now that we've emitted the code for all the declarations in the file,
    // it is time to stitch together the final output.

    // There may be global-scope modifiers that we should emit now
    // Supress emitting line directives when emitting preprocessor directives since
    // these preprocessor directives may be required to appear in the first line
    // of the output. An example is that the "#version" line in a GLSL source must
    // appear before anything else.
    sourceWriter.supressLineDirective();

    // When emitting front matter we can emit the target-language-specific directives
    // needed to get the default matrix layout to match what was requested
    // for the given target.
    //
    // Note: we do not rely on the defaults for the target language,
    // because a user could take the HLSL/GLSL generated by Slang and pass
    // it to another compiler with non-default options specified on
    // the command line, leading to all kinds of trouble.
    //
    // TODO: We need an approach to "global" layout directives that will work
    // in the presence of multiple modules. If modules A and B were each
    // compiled with different assumptions about how layout is performed,
    // then types/variables defined in those modules should be emitted in
    // a way that is consistent with that layout...

    // Emit any front matter
    sourceEmitter->emitFrontMatter(targetRequest);

    switch (target)
    {
    case CodeGenTarget::PyTorchCppBinding:
        sourceWriter.emit(get_slang_torch_prelude());
        break;
    default:
        if (isHeterogeneousTarget(target))
        {
            sourceWriter.emit(get_slang_cpp_host_prelude());
        }
        else
        {
            // Get the prelude
            String prelude = session->getPreludeForLanguage(sourceLanguage);
            sourceWriter.emit(prelude);
        }
        break;
    }

    // Emit anything that goes before the contents of the code generated for the module
    sourceEmitter->emitPreModule();

    sourceWriter.resumeLineDirective();

    // Get the content built so far from the front matter/prelude/preModule
    // By getting in this way, the content is no longer referenced by the sourceWriter.
    String finalResult = sourceWriter.getContentAndClear();

    // Append the modules output code
    finalResult.append(code);

    finalResult.append(sourceWriter.getContentAndClear());

    // Write out the result

    auto artifact = ArtifactUtil::createArtifactForCompileTarget(asExternal(target));
    artifact->addRepresentationUnknown(StringBlob::moveCreate(finalResult));

    ArtifactUtil::addAssociated(artifact, metadata);

    if (sourceMap)
    {
        auto sourceMapArtifact = ArtifactUtil::createArtifact(ArtifactDesc::make(
            ArtifactKind::Json,
            ArtifactPayload::SourceMap,
            ArtifactStyle::None));

        sourceMapArtifact->addRepresentation(sourceMap);

        artifact->addAssociated(sourceMapArtifact);
    }

    outArtifact.swap(artifact);
    return SLANG_OK;
}

SlangResult emitSPIRVFromIR(
    CodeGenContext* codeGenContext,
    IRModule* irModule,
    const List<IRFunc*>& irEntryPoints,
    List<uint8_t>& spirvOut);

// Helper class to assist in stepping through the SPIRV instructions.
class SpirvInstructionHelper
{
public:
    // The result id of the OpExtInstImport instruction, eg.g %2 below
    // %2 = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
    uint32_t m_nonSemanticDebugInfoExtSetId = 0;

    // The index of the SPIRV words in the blob.
    // The first 5 words are the header as defined by the SPIRV spec.
    enum SpvWordIndex
    {
        SPV_INDEX_MAGIC_NUMBER,
        SPV_INDEX_VERSION_NUMBER,
        SPV_INDEX_GENERATOR_NUMBER,
        SPV_INDEX_BOUND,
        SPV_INDEX_SCHEMA,
        SPV_INDEX_INSTRUCTION_START,
    };

    // An instruction in the SPIRV blob. This points to the first word of the instruction.
    struct SpvInstruction
    {
        SpvInstruction(SpvWord* word)
            : word(word)
        {
        }

        uint16_t getOpCode() const { return word[0] & 0xFFFF; }
        uint16_t getWordCountForInst() const { return (word[0] >> 16) & 0xFFFF; }
        SpvWord getOperand(uint32_t index) const { return word[index + 1]; }

        // Helper function to interpret the instruction as a string and
        // extract the string value.
        String getStringFromInst() const
        {
            String result;
            for (uint32_t i = 1; i < getWordCountForInst(); i++)
            {
                SpvWord op = getOperand(i);
                for (int b = 0; b < 4; ++b)
                {
                    char c = (char)((op >> (b * 8)) & 0xFF);
                    if (c == '\0')
                        return result;
                    result.append(c);
                }
            }
            return result;
        }

        SpvWord* word = nullptr;
    };

    SpirvInstructionHelper() {}

    // Load the SPIRV instructions from the artifact into a data blob that
    // we can read.
    SlangResult loadBlob(ComPtr<IArtifact>& artifact)
    {
        m_nonSemanticDebugInfoExtSetId = 0;
        m_words.clear();
        m_headerWords.clear();

        ComPtr<ISlangBlob> spirvBlob;
        SlangResult res = artifact->loadBlob(ArtifactKeep::Yes, spirvBlob.writeRef());
        if (SLANG_FAILED(res) || !spirvBlob ||
            spirvBlob->getBufferSize() < SPV_INDEX_INSTRUCTION_START * sizeof(SpvWord))
            return SLANG_FAIL;

        // Populate the full array of SPIR-V words.
        m_words.addRange(
            reinterpret_cast<const SpvWord*>(spirvBlob->getBufferPointer()),
            spirvBlob->getBufferSize() / sizeof(SpvWord));

        // Populate the header words. These are the first 5 words of the SPIR-V
        // blob and are treated differently from the rest of the instructions.
        m_headerWords.clear();
        m_headerWords.addRange(m_words.getBuffer(), SPV_INDEX_INSTRUCTION_START);

        return SLANG_OK;
    }

    // Get the header words.
    const List<SpvWord>& getHeaderWords() const { return m_headerWords; }

    Index getWordCount() const { return m_words.getCount(); }

    // Visit all SPIRV instructions (excluding header words), invoking the callback for each
    // instruction. The callback should be a function or lambda with signature: void(const
    // SpvInstruction&).
    template<typename Func>
    void visitInstructions(Func&& callback)
    {
        // Instructions start after the header (first 5 words)
        constexpr size_t kHeaderWordCount =
            static_cast<size_t>(SpvWordIndex::SPV_INDEX_INSTRUCTION_START);
        size_t i = kHeaderWordCount;
        while (i < (size_t)m_words.getCount())
        {
            SpvWord* wordPtr = m_words.getBuffer() + i;
            SpvInstruction inst(wordPtr);
            callback(inst);
            uint16_t wordCount = inst.getWordCountForInst();
            if (wordCount == 0)
                break; // Prevent infinite loop on malformed input
            i += wordCount;
        }
    }

private:
    // The full array of SPIRV words.
    List<SpvWord> m_words;

    // The header words.
    List<SpvWord> m_headerWords;
};

// Helper function that takes an artifact populated with SPIRV instructions
// after the spirv-opt step, and a previously created but empty
// strippedArtifact. The artifact is unmodified, and the strippedArtifact
// will contain all the artifact's instructions except for debug instructions.
static SlangResult stripDbgSpirvFromArtifact(
    ComPtr<IArtifact>& artifact,
    ComPtr<IArtifact>& strippedArtifact)
{
    // Standard debug opcodes to strip out. This mimics the behavior of
    // spirv-opt.
    static const uint16_t debugOpCodeVals[] = {
        SpvOpSourceContinued,
        SpvOpSource,
        SpvOpSourceExtension,
        SpvOpString,
        SpvOpName,
        SpvOpMemberName,
        SpvOpModuleProcessed,
        SpvOpLine,
        SpvOpNoLine};
    // If the instruction is an extended instruction, then we also need
    // to check if the instruction number is for a debug instruction as
    // listed in slang-emit-spirv-ops-debug-info-ext.h
    static const uint32_t debugExtInstVals[] = {
        NonSemanticShaderDebugInfo100DebugCompilationUnit,
        NonSemanticShaderDebugInfo100DebugTypeBasic,
        NonSemanticShaderDebugInfo100DebugTypePointer,
        NonSemanticShaderDebugInfo100DebugTypeQualifier,
        NonSemanticShaderDebugInfo100DebugTypeArray,
        NonSemanticShaderDebugInfo100DebugTypeVector,
        NonSemanticShaderDebugInfo100DebugTypeFunction,
        NonSemanticShaderDebugInfo100DebugTypeComposite,
        NonSemanticShaderDebugInfo100DebugTypeMember,
        NonSemanticShaderDebugInfo100DebugFunction,
        NonSemanticShaderDebugInfo100DebugScope,
        NonSemanticShaderDebugInfo100DebugNoScope,
        NonSemanticShaderDebugInfo100DebugInlinedAt,
        NonSemanticShaderDebugInfo100DebugLocalVariable,
        NonSemanticShaderDebugInfo100DebugGlobalVariable,
        NonSemanticShaderDebugInfo100DebugInlinedVariable,
        NonSemanticShaderDebugInfo100DebugDeclare,
        NonSemanticShaderDebugInfo100DebugValue,
        NonSemanticShaderDebugInfo100DebugExpression,
        NonSemanticShaderDebugInfo100DebugSource,
        NonSemanticShaderDebugInfo100DebugFunctionDefinition,
        NonSemanticShaderDebugInfo100DebugSourceContinued,
        NonSemanticShaderDebugInfo100DebugLine,
        NonSemanticShaderDebugInfo100DebugEntryPoint,
        NonSemanticShaderDebugInfo100DebugTypeMatrix,
    };

    // Hash sets for easier lookup.
    HashSet<uint16_t> debugOpCodes;
    for (auto val : debugOpCodeVals)
        debugOpCodes.add(val);
    HashSet<uint32_t> debugExtInstNumbers;
    for (auto val : debugExtInstVals)
        debugExtInstNumbers.add(val);

    SpirvInstructionHelper spirvInstructionHelper;
    SLANG_RETURN_ON_FAIL(spirvInstructionHelper.loadBlob(artifact));

    const auto& headerWords = spirvInstructionHelper.getHeaderWords();

    List<uint8_t> spirvWordsList;
    if (auto totalWordCapacity = headerWords.getCount() + spirvInstructionHelper.getWordCount())
    {
        const Index byteCapacity = totalWordCapacity * sizeof(SpvWord);
        spirvWordsList.reserve(byteCapacity);
    }

    spirvWordsList.addRange(
        reinterpret_cast<const uint8_t*>(headerWords.getBuffer()),
        headerWords.getCount() * sizeof(SpvWord));

    // First find the DebugBuildIdentifier instruction, and keep track of which string
    // it refers to, this string needs to be kept in the final output.
    SpvWord debugStringId = 0;
    spirvInstructionHelper.visitInstructions(
        [&](const SpirvInstructionHelper::SpvInstruction& inst)
        {
            if (inst.getOpCode() == SpvOpExtInst)
            {
                if (inst.getOperand(3) == NonSemanticShaderDebugInfo100DebugBuildIdentifier)
                {
                    debugStringId = inst.getOperand(4);
                    return;
                }
            }
            else if (inst.getOpCode() == SpvOpExtInstImport)
            {
                // looking for result id of "OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
                auto importName = inst.getStringFromInst();
                if (importName == "NonSemantic.Shader.DebugInfo.100")
                {
                    spirvInstructionHelper.m_nonSemanticDebugInfoExtSetId = inst.getOperand(0);
                }
            }
        });

    // Iterate over the instructions from the artifact and add them to the list
    // only if they are not debug instructions. We also get the debug build hash
    // to use as the filename for the debug spirv file.
    String debugBuildHash;
    spirvInstructionHelper.visitInstructions(
        [&](const SpirvInstructionHelper::SpvInstruction& inst)
        {
            if (debugOpCodes.contains(inst.getOpCode()))
            {
                // We can only strip strings if they are not being used by the
                // DebugBuildIdentifier instruction.
                bool foundDebugString = false;
                if (inst.getOpCode() == SpvOpString && inst.getOperand(0) == debugStringId)
                {
                    debugBuildHash = inst.getStringFromInst();
                    foundDebugString = true;
                }
                if (!foundDebugString)
                {
                    return;
                }
            }
            // Also check if the instruction is an extended instruction containing DebugInfo.
            if (inst.getOpCode() == SpvOpExtInst)
            {
                // Ignore this if the instruction contains DebugInfo and is from the debug import
                if (debugExtInstNumbers.contains(inst.getOperand(3)) &&
                    inst.getOperand(2) == spirvInstructionHelper.m_nonSemanticDebugInfoExtSetId)
                {
                    return;
                }
            }
            // Otherwise this is a non-debug instruction and should be included.
            spirvWordsList.addRange(
                reinterpret_cast<const uint8_t*>(inst.word),
                inst.getWordCountForInst() * sizeof(SpvWord));
        });

    // Create the stripped artifact using the above created instruction list.
    strippedArtifact->addRepresentationUnknown(ListBlob::moveCreate(spirvWordsList));

    // Set the name of the artifact to the debug build hash so it can be used
    // as the filename for the debug spirv file.
    artifact->setName(debugBuildHash.getBuffer());

    return SLANG_OK;
}

static bool shouldRunSPIRVValidation(CodeGenContext* codeGenContext)
{
    auto& optionSet = codeGenContext->getTargetProgram()->getOptionSet();

    // If the user requested to skip validation, or if we are generating an incomplete library
    // containing linkage decorations (Vulkan validation rules doesn't allow this), we skip
    // validation.
    if (optionSet.getBoolOption(CompilerOptionName::SkipSPIRVValidation) ||
        optionSet.getBoolOption(CompilerOptionName::IncompleteLibrary))
        return false;

    // Also check the environment variable SLANG_RUN_SPIRV_VALIDATION.
    // If it is set to "1", we should run validation.
    StringBuilder runSpirvValEnvVar;
    PlatformUtil::getEnvironmentVariable(
        UnownedStringSlice("SLANG_RUN_SPIRV_VALIDATION"),
        runSpirvValEnvVar);
    if (runSpirvValEnvVar.getUnownedSlice() == "1")
    {
        return true;
    }

    return false;
}

// Helper function to create an artifact from IR used internally by
// emitSPIRVForEntryPointsDirectly.
static SlangResult createArtifactFromIR(
    CodeGenContext* codeGenContext,
    IRModule* irModule,
    List<IRFunc*> irEntryPoints,
    ComPtr<IArtifact>& artifact,
    ComPtr<IArtifact>& dbgArtifact)
{
    List<uint8_t> spirv, outSpirv;
    SLANG_RETURN_ON_FAIL(emitSPIRVFromIR(codeGenContext, irModule, irEntryPoints, spirv));

    // If SPIR-V emission reported any errors, do not continue with
    // downstream linking/validation/optimization on partial output.
    if (codeGenContext->getSink()->getErrorCount() != 0)
    {
        return SLANG_FAIL;
    }

    auto targetRequest = codeGenContext->getTargetReq();
    auto targetCompilerOptions = targetRequest->getOptionSet();

#if 0
    String optErr;
    if (SLANG_FAILED(optimizeSPIRV(spirv, optErr, outSpirv)))
    {
        codeGenContext->getSink()->diagnose(Diagnostics::SpirvOptFailed{.error = optErr});
        spirv = _Move(outSpirv);
    }
#endif

    // Decide whether any downstream (slang-glslang / SPIRV-Tools) work is actually required
    // for this artifact before paying the cost of loading the downstream compiler module.
    //
    // Slang emits and legalizes SPIR-V natively, so the natively emitted blob added above is
    // already a complete, valid module. The downstream `slang-glslang` compiler is only needed
    // to: (a) run the SPIRV-Tools optimizer when an optimization level above `None` is
    // requested; (b) link multiple SPIR-V modules together when separately compiled / embedded
    // downstream modules are present; (c) validate the result when SPIR-V validation is enabled;
    // or (d) emit separate debug info, which is produced as a side effect of the downstream
    // compile step. When none of these apply -- for example a single-module `-O0 -target spirv`
    // compile -- the natively emitted SPIR-V is the final output, so we skip loading
    // `slang-glslang` entirely and leave the artifact as-is. This keeps a default `-O0` SPIR-V
    // compile free of any dependency on the `slang-glslang` module (issue #11662); without this
    // gate the load was unconditional and a build without `slang-glslang` failed with E00100
    // even though no downstream work was needed.
    bool isPrecompilation = codeGenContext->getTargetProgram()->getOptionSet().getBoolOption(
        CompilerOptionName::EmbedDownstreamIR);

    // Collect the SPIR-V modules that would participate in a downstream link. This walks IR
    // only (it does not need the downstream compiler), so it is safe to do before deciding
    // whether to load that compiler, and we reuse the result for the link below.
    List<uint32_t*> spirvFiles;
    List<uint32_t> spirvSizes;
    const bool downstreamLinkingAllowed =
        !isPrecompilation && !codeGenContext->shouldSkipDownstreamLinking();
    if (downstreamLinkingAllowed)
    {
        // Start with the SPIR-V we just generated.
        // SPIRV-Tools-link expects the size in 32-bit words
        // whereas the spirv blob size is in bytes.
        spirvFiles.add((uint32_t*)spirv.getBuffer());
        spirvSizes.add(int(spirv.getCount()) / 4);

        // Iterate over all modules in the linkedIR. For each module, if it
        // contains an embedded downstream ir instruction, add it to the list
        // of spirv files.
        auto program = codeGenContext->getProgram();

        program->enumerateIRModules(
            [&](IRModule* irModule)
            {
                for (auto globalInst : irModule->getModuleInst()->getChildren())
                {
                    if (auto inst = as<IREmbeddedDownstreamIR>(globalInst))
                    {
                        if (inst->getTarget() == CodeGenTarget::SPIRV)
                        {
                            auto slice = inst->getBlob()->getStringSlice();
                            spirvFiles.add((uint32_t*)slice.begin());
                            spirvSizes.add(int(slice.getLength()) / 4);
                        }
                    }
                }
            });

        SLANG_ASSERT(int(spirv.getCount()) % 4 == 0);
        SLANG_ASSERT(spirvFiles.getCount() == spirvSizes.getCount());
    }

    const bool needsLink = downstreamLinkingAllowed && spirvFiles.getCount() > 1;
    // `-Xspirv-opt <flag>` selects individual optimizer passes explicitly, so it must run the
    // optimizer even at `-O0` (where the preset is empty). Detecting them here also keeps a plain
    // `-O0` compile -- with no such flags, and no link/validation/separate-debug-info -- from
    // loading `slang-glslang` (issue #11662).
    List<String> spirvOptArgs =
        codeGenContext->getTargetProgram()->getOptionSet().getDownstreamArgs("spirv-opt");
    const bool needsOptimization =
        codeGenContext->getTargetProgram()->getOptionSet().getOptimizationLevel() !=
            OptimizationLevel::None ||
        spirvOptArgs.getCount() != 0;
    const bool needsValidation = shouldRunSPIRVValidation(codeGenContext);
    const bool needsSeparateDebugInfo = targetCompilerOptions.shouldEmitSeparateDebugInfo();
    const bool needsDownstreamCompiler =
        needsLink || needsOptimization || needsValidation || needsSeparateDebugInfo;

    artifact->addRepresentationUnknown(
        needsDownstreamCompiler ? ListBlob::create(spirv) : ListBlob::moveCreate(spirv));

    IDownstreamCompiler* compiler = needsDownstreamCompiler
                                        ? codeGenContext->getSession()->getOrLoadDownstreamCompiler(
                                              PassThroughMode::SpirvOpt,
                                              codeGenContext->getSink())
                                        : nullptr;
    if (compiler)
    {
#if 0
        // Dump the unoptimized/unlinked SPIRV after lowering from slang IR -> SPIRV
        compiler->disassemble((uint32_t*)spirv.getBuffer(), int(spirv.getCount() / 4));
#endif

        if (needsLink)
        {
            ComPtr<IArtifact> linkedArtifact;
            SlangResult linkresult = compiler->link(
                (const uint32_t**)spirvFiles.getBuffer(),
                (const uint32_t*)spirvSizes.getBuffer(),
                (uint32_t)spirvFiles.getCount(),
                linkedArtifact.writeRef());

            if (linkresult == SLANG_E_NOT_AVAILABLE)
            {
                // The linker never ran, so the compile fails for an environmental reason the user
                // cannot infer from a bare `SLANG_FAIL`.
                codeGenContext->getSink()->diagnose(Diagnostics::DownstreamLinkingUnavailable{});
                return SLANG_FAIL;
            }

            if (linkresult != SLANG_OK)
            {
                return SLANG_FAIL;
            }

            ComPtr<ISlangBlob> blob;
            linkedArtifact->loadBlob(ArtifactKeep::No, blob.writeRef());
            artifact = _Move(linkedArtifact);
        }

        if (needsValidation)
        {
            const SlangResult validationResult =
                compiler->validate((uint32_t*)spirv.getBuffer(), int(spirv.getCount() / 4));

            if (validationResult == SLANG_E_NOT_AVAILABLE)
            {
                // The validator never ran, so disassembling here would wrongly imply the SPIR-V was
                // found invalid. Fail the compile rather than falling through: validation was
                // requested, and publishing the artifact would hand a caller SPIR-V that nothing
                // checked. `error` severity alone does not stop this path -- only `Severity::Fatal`
                // and above abort a compile.
                codeGenContext->getSink()->diagnose(Diagnostics::SpirvValidationUnavailable{});
                return SLANG_FAIL;
            }
            else if (SLANG_FAILED(validationResult))
            {
                // Disassemble the rejected SPIR-V into the diagnostic so the failure is legible,
                // then report it. `SpirvValidationFailed` is `internal` severity, so this
                // `diagnose()` aborts the compile by throwing; the abort unwinds to the
                // API-boundary guard that catches it. The `return SLANG_FAIL` below is reached
                // only if that severity is ever lowered below `Severity::Fatal`.
                compiler->disassemble((uint32_t*)spirv.getBuffer(), int(spirv.getCount() / 4));
                codeGenContext->getSink()->diagnose(Diagnostics::SpirvValidationFailed{});
                return SLANG_FAIL;
            }
        }

        ComPtr<IArtifact> optimizedArtifact;
        DownstreamCompileOptions downstreamOptions;
        downstreamOptions.sourceArtifacts = makeSlice(artifact.readRef(), 1);
        downstreamOptions.targetType = SLANG_SPIRV;
        downstreamOptions.sourceLanguage = SLANG_SOURCE_LANGUAGE_SPIRV;

        // Forward the `-Xspirv-opt` args (collected above) to the downstream optimizer, where they
        // register on top of the `-OX` preset -- or as the only passes at `-O0`, whose preset is
        // empty. The allocator owns the copied arg strings and slice array, so it must outlive the
        // compile() call below.
        SliceAllocator allocator;
        downstreamOptions.compilerSpecificArguments = allocator.allocate(spirvOptArgs);
        switch (codeGenContext->getTargetProgram()->getOptionSet().getOptimizationLevel())
        {
        case OptimizationLevel::None:
            downstreamOptions.optimizationLevel = DownstreamCompileOptions::OptimizationLevel::None;
            break;
        case OptimizationLevel::Default:
            downstreamOptions.optimizationLevel =
                DownstreamCompileOptions::OptimizationLevel::Default;
            break;
        case OptimizationLevel::High:
            downstreamOptions.optimizationLevel = DownstreamCompileOptions::OptimizationLevel::High;
            break;
        case OptimizationLevel::Maximal:
            downstreamOptions.optimizationLevel =
                DownstreamCompileOptions::OptimizationLevel::Maximal;
            break;
        default:
            SLANG_ASSERT(!"Unhandled optimization level");
            break;
        }
        auto downstreamStartTime = std::chrono::high_resolution_clock::now();
        if (SLANG_SUCCEEDED(compiler->compile(downstreamOptions, optimizedArtifact.writeRef())))
        {
            // Check if we need to output a separate SPIRV file containing debug info. If so
            // then strip all debug instructions from the artifact. The dbgArtifact will still
            // contain all instructions.
            if (targetCompilerOptions.shouldEmitSeparateDebugInfo())
            {
                auto strippedArtifact = ArtifactUtil::createArtifactForCompileTarget(SLANG_SPIRV);
                SLANG_RETURN_ON_FAIL(
                    stripDbgSpirvFromArtifact(optimizedArtifact, strippedArtifact));
                artifact = _Move(strippedArtifact);
                dbgArtifact = _Move(optimizedArtifact);
            }
            else
                artifact = _Move(optimizedArtifact);
        }
        auto downstreamElapsedTime =
            (std::chrono::high_resolution_clock::now() - downstreamStartTime).count() * 0.000000001;
        codeGenContext->getSession()->addDownstreamCompileTime(downstreamElapsedTime);

        SLANG_RETURN_ON_FAIL(
            passthroughDownstreamDiagnostics(codeGenContext->getSink(), compiler, artifact));
    }

    return SLANG_OK;
}

SlangResult emitSPIRVForEntryPointsDirectly(
    CodeGenContext* codeGenContext,
    ComPtr<IArtifact>& outArtifact)
{
    // Outside because we want to keep IR in scope whilst we are processing emits
    LinkedIR linkedIR;
    LinkingAndOptimizationOptions linkingAndOptimizationOptions;
    SLANG_RETURN_ON_FAIL(
        linkAndOptimizeIR(codeGenContext, linkingAndOptimizationOptions, linkedIR));

    auto irModule = linkedIR.module;
    auto irEntryPoints = linkedIR.entryPoints;


    auto targetRequest = codeGenContext->getTargetReq();
    auto targetCompilerOptions = targetRequest->getOptionSet();

    // Create the artifact containing the main SPIRV data, and the debug SPIRV
    // data if requested by the command line arg -separate-debug-info.
    Slang::ComPtr<Slang::IArtifact> dbgArtifact;
    auto artifact =
        ArtifactUtil::createArtifactForCompileTarget(asExternal(codeGenContext->getTargetFormat()));
    SLANG_RETURN_ON_FAIL(
        createArtifactFromIR(codeGenContext, irModule, irEntryPoints, artifact, dbgArtifact));
    ArtifactUtil::addAssociated(artifact, linkedIR.metadata);

    // Associate the debug artifact with the main artifact.
    // EndToEndCompileRequest::generateOutput will read this data
    // and produce a .dbg.spv file for this child artifact.
    if (targetCompilerOptions.shouldEmitSeparateDebugInfo())
    {
        artifact->addAssociated(dbgArtifact);

        auto artifactPostEmitMetadata =
            static_cast<ArtifactPostEmitMetadata*>(linkedIR.metadata.get());
        artifactPostEmitMetadata->addRef();
        artifactPostEmitMetadata->m_debugBuildIdentifier = dbgArtifact->getName();
    }

    outArtifact.swap(artifact);

    return SLANG_OK;
}

SlangResult emitHostVMCode(CodeGenContext* codeGenContext, ComPtr<IArtifact>& outArtifact)
{
    LinkedIR linkedIR;
    LinkingAndOptimizationOptions linkingAndOptimizationOptions;
    SLANG_RETURN_ON_FAIL(
        linkAndOptimizeIR(codeGenContext, linkingAndOptimizationOptions, linkedIR));

    VMByteCodeBuilder byteCode;
    SLANG_RETURN_ON_FAIL(emitVMByteCodeForEntryPoints(codeGenContext, linkedIR, byteCode));

    String slangDeclaration;
    SLANG_RETURN_ON_FAIL(
        emitSlangDeclarationsForEntryPoints(codeGenContext, linkedIR, slangDeclaration));

    slang::SessionDesc sessionDesc = {};
    ComPtr<slang::ISession> slangSession;
    SLANG_RETURN_ON_FAIL(
        codeGenContext->getSession()->createSession(sessionDesc, slangSession.writeRef()));
    auto linkage = static_cast<Linkage*>(slangSession.get());

    ComPtr<ISlangBlob> diagnostics;
    auto module = slangSession->loadModuleFromSource(
        "kernel",
        "kernel.slang",
        StringBlob::create(slangDeclaration),
        diagnostics.writeRef());
    if (!module)
        return SLANG_FAIL;
    RefPtr<Module> newModule = new Module(linkage);
    newModule->setModuleDecl(static_cast<Module*>(module)->getModuleDecl());
    newModule->setIRModule(linkedIR.module);
    newModule->setName("kernels");
    SLANG_RETURN_ON_FAIL(newModule->serialize(byteCode.kernelBlob.writeRef()));

    ComPtr<slang::IBlob> byteCodeBlob;
    SLANG_RETURN_ON_FAIL(byteCode.serialize(byteCodeBlob.writeRef()));

    outArtifact = ArtifactUtil::createArtifactForCompileTarget(SLANG_HOST_VM);
    outArtifact->addRepresentationUnknown(byteCodeBlob);

    return SLANG_OK;
}

SlangResult emitLLVMForEntryPoints(CodeGenContext* codeGenContext, ComPtr<IArtifact>& outArtifact)
{
    auto target = codeGenContext->getTargetFormat();

    ISlangSharedLibrary* library = codeGenContext->getSession()->getOrLoadSlangLLVM();
    if (!library)
    {
        codeGenContext->getSink()->diagnose(Diagnostics::UnableToGenerateCodeForTarget{
            .target = TypeTextUtil::getCompileTargetName(SlangCompileTarget(target))});
        return SLANG_FAIL;
    }

    LinkedIR linkedIR;
    LinkingAndOptimizationOptions linkingAndOptimizationOptions;
    linkingAndOptimizationOptions.shouldLegalizeExistentialAndResourceTypes = false;
    SLANG_RETURN_ON_FAIL(
        linkAndOptimizeIR(codeGenContext, linkingAndOptimizationOptions, linkedIR));

    auto irModule = linkedIR.module;

    ComPtr<ISlangBlob> blob;

    switch (target)
    {
    // At this point there should be no difference between host style and shader
    // style from LLVM's perspective: the shader style has already been
    // lowered/legalized into host style.
    case CodeGenTarget::HostObjectCode:
    case CodeGenTarget::ShaderObjectCode:
        {
            IArtifact* artifact = nullptr;
            emitLLVMObjectFromIR(codeGenContext, irModule, &artifact);
            outArtifact = ComPtr<IArtifact>(artifact);
        }
        break;
    case CodeGenTarget::HostLLVMIR:
    case CodeGenTarget::ShaderLLVMIR:
        {
            IArtifact* artifact = nullptr;
            emitLLVMAssemblyFromIR(codeGenContext, irModule, &artifact);
            outArtifact = ComPtr<IArtifact>(artifact);
        }
        break;
    case CodeGenTarget::HostHostCallable:
    case CodeGenTarget::ShaderHostCallable:
        {
            IArtifact* artifact = nullptr;
            emitLLVMJITFromIR(codeGenContext, irModule, &artifact);
            outArtifact = ComPtr<IArtifact>(artifact);
        }
        break;
    }

    return SLANG_OK;
}

} // namespace Slang
