https://github.com/shader-slang/slang
Raw File
Tip revision: d386e27dc319b2feb362acd430ff5c640d8c6fba authored by jsmall-nvidia on 02 June 2020, 19:26:51 UTC
Added spGetBuildTagString. (#1365)
Tip revision: d386e27
slang-lower-to-ir.cpp
// lower.cpp
#include "slang-lower-to-ir.h"

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

#include "slang-check.h"
#include "slang-ir.h"
#include "slang-ir-constexpr.h"
#include "slang-ir-dce.h"
#include "slang-ir-inline.h"
#include "slang-ir-insts.h"
#include "slang-ir-missing-return.h"
#include "slang-ir-sccp.h"
#include "slang-ir-ssa.h"
#include "slang-ir-strip.h"
#include "slang-ir-validate.h"
#include "slang-ir-string-hash.h"

#include "slang-mangle.h"
#include "slang-type-layout.h"
#include "slang-visitor.h"

namespace Slang
{

// This file implements lowering of the Slang AST to a simpler SSA
// intermediate representation.
//
// IR is generated in a context (`IRGenContext`), which tracks the current
// location in the IR where code should be emitted (e.g., what basic
// block to add instructions to). Lowering a statement will emit some
// number of instructions to the context, and possibly change the
// insertion point (because of control flow).
//
// When lowering an expression we have a more interesting challenge, for
// two main reasons:
//
// 1. There might be types that are representible in the AST, but which
//    we don't want to support natively in the IR. An example is a `struct`
//    type with both ordinary and resource-type members; we might want to
//    split values with such a type into distinct values during lowering.
//
// 2. We need to handle the difference between l-value and r-value expressions,
//    and in particular the fact that HLSL/Slang supports complicated sorts
//    of l-values (e.g., `someVector.zxy` is an l-value, even though it can't
//    be represented by a single pointer), and also allows l-values to appear
//    in multiple contexts (not just the left-hand side of assignment, but
//    also as an argument to match an `out` or `in out` parameter).
//
// Our solution to both of these problems is the same. Rather than having
// the lowering of an expression return a single IR-level value (`IRInst*`),
// we have it return a more complex type (`LoweredValInfo`) which can represent
// a wider range of conceptual "values" which might correspond to multiple IR-level
// values, and/or represent a pointer to an l-value rather than the r-value itself.

// We want to keep the representation of a `LoweringValInfo` relatively light
// - right now it is just a single pointer plus a "tag" to distinguish the cases.
//
// This means that cases that can't fit in a single pointer need a heap allocation
// to store their payload. For simplicity we represent all of these with a class
// hierarchy:
//
struct ExtendedValueInfo : RefObject
{};

// This case is used to indicate a value that is a reference
// to an AST-level subscript declaration.
//
struct SubscriptInfo : ExtendedValueInfo
{
    DeclRef<SubscriptDecl> declRef;
};

// This case is used to indicate a reference to an AST-level
// subscript operation bound to particular arguments.
//
// For example in a case like this:
//
//     RWStructuredBuffer<Foo> gBuffer;
//     ... gBuffer[someIndex] ...
//
// the expression `gBuffer[someIndex]` will be lowered to
// a value that references `RWStructureBuffer<Foo>::operator[]`
// with arguments `(gBuffer, someIndex)`.
//
// Such a value can be an l-value, and depending on the context
// where it is used, can lower into a call to either the getter
// or setter operations of the subscript.
//
struct BoundSubscriptInfo : ExtendedValueInfo
{
    DeclRef<SubscriptDecl>  declRef;
    IRType*                 type;
    List<IRInst*>           args;
};

// Some cases of `ExtendedValueInfo` need to
// recursively contain `LoweredValInfo`s, and
// so we forward declare them here and fill
// them in later.
//
struct BoundMemberInfo;
struct SwizzledLValueInfo;


// This type is our core representation of lowered values.
// In the simple case, it just wraps an `IRInst*`.
// More complex cases, representing l-values or aggregate
// values are also supported.
struct LoweredValInfo
{
    // Which of the cases of value are we looking at?
    enum class Flavor
    {
        // No value (akin to a null pointer)
        None,

        // A simple IR value
        Simple,

        // An l-value represented as an IR
        // pointer to the value
        Ptr,

        // A member declaration bound to a particular `this` value
        BoundMember,

        // A reference to an AST-level subscript operation
        Subscript,

        // An AST-level subscript operation bound to a particular
        // object and arguments.
        BoundSubscript,

        // The result of applying swizzling to an l-value
        SwizzledLValue,
    };

    union
    {
        IRInst*            val;
        ExtendedValueInfo*  ext;
    };
    Flavor flavor;

    LoweredValInfo()
    {
        flavor = Flavor::None;
        val = nullptr;
    }

    LoweredValInfo(IRType* t)
    {
        flavor = Flavor::Simple;
        val = t;
    }

    static LoweredValInfo simple(IRInst* v)
    {
        LoweredValInfo info;
        info.flavor = Flavor::Simple;
        info.val = v;
        return info;
    }

    static LoweredValInfo ptr(IRInst* v)
    {
        LoweredValInfo info;
        info.flavor = Flavor::Ptr;
        info.val = v;
        return info;
    }

    static LoweredValInfo boundMember(
        BoundMemberInfo*    boundMemberInfo);

    BoundMemberInfo* getBoundMemberInfo()
    {
        SLANG_ASSERT(flavor == Flavor::BoundMember);
        return (BoundMemberInfo*)ext;
    }

    static LoweredValInfo subscript(
        SubscriptInfo* subscriptInfo);

    SubscriptInfo* getSubscriptInfo()
    {
        SLANG_ASSERT(flavor == Flavor::Subscript);
        return (SubscriptInfo*)ext;
    }

    static LoweredValInfo boundSubscript(
        BoundSubscriptInfo* boundSubscriptInfo);

    BoundSubscriptInfo* getBoundSubscriptInfo()
    {
        SLANG_ASSERT(flavor == Flavor::BoundSubscript);
        return (BoundSubscriptInfo*)ext;
    }

    static LoweredValInfo swizzledLValue(
        SwizzledLValueInfo* extInfo);

    SwizzledLValueInfo* getSwizzledLValueInfo()
    {
        SLANG_ASSERT(flavor == Flavor::SwizzledLValue);
        return (SwizzledLValueInfo*)ext;
    }
};

// Represents some declaration bound to a particular
// object. For example, if we had `obj.f` where `f`
// is a member function, we'd use a `BoundMemberInfo`
// to represnet this.
//
// Note: This case is largely avoided by special-casing
// in the handling of calls (like `obj.f(arg)`), but
// it is being left here as an example of what we might
// need/want to do in the long term.
struct BoundMemberInfo : ExtendedValueInfo
{
    // The base object
    LoweredValInfo  base;

    // The (AST-level) declaration reference.
    DeclRef<Decl> declRef;

    // The type of this value
    IRType* type;
};

// Represents the result of a swizzle operation in
// an l-value context. A swizzle without duplicate
// elements is allowed as an l-value, even if the
// element are non-contiguous (`.xz`) or out of
// order (`.zxy`).
//
struct SwizzledLValueInfo : ExtendedValueInfo
{
    // The type of the expression.
    IRType*         type;

    // The base expression (this should be an l-value)
    LoweredValInfo  base;

    // The number of elements in the swizzle
    UInt            elementCount;

    // THe indices for the elements being swizzled
    UInt            elementIndices[4];
};

LoweredValInfo LoweredValInfo::boundMember(
    BoundMemberInfo*    boundMemberInfo)
{
    LoweredValInfo info;
    info.flavor = Flavor::BoundMember;
    info.ext = boundMemberInfo;
    return info;
}

LoweredValInfo LoweredValInfo::subscript(
    SubscriptInfo* subscriptInfo)
{
    LoweredValInfo info;
    info.flavor = Flavor::Subscript;
    info.ext = subscriptInfo;
    return info;
}

LoweredValInfo LoweredValInfo::boundSubscript(
    BoundSubscriptInfo* boundSubscriptInfo)
{
    LoweredValInfo info;
    info.flavor = Flavor::BoundSubscript;
    info.ext = boundSubscriptInfo;
    return info;
}

LoweredValInfo LoweredValInfo::swizzledLValue(
        SwizzledLValueInfo* extInfo)
{
    LoweredValInfo info;
    info.flavor = Flavor::SwizzledLValue;
    info.ext = extInfo;
    return info;
}

// An "environment" for mapping AST declarations to IR values.
//
// This is required because in some cases we might lower the
// same AST declaration to the IR multiple times (e.g., when
// a generic transitively contains multiple functions, we
// will emit a distinct IR generic for each function, with
// its own copies of the generic parameters).
//
struct IRGenEnv
{
    // Map an AST-level declaration to the IR-level value that represents it.
    Dictionary<Decl*, LoweredValInfo> mapDeclToValue;

    // The next outer env around this one
    IRGenEnv*   outer = nullptr;
};

struct SharedIRGenContext
{
    SharedIRGenContext(
        Session*        session,
        DiagnosticSink* sink,
        bool obfuscateCode, 
        ModuleDecl*     mainModuleDecl = nullptr)
        : m_session(session)
        , m_sink(sink)
        , m_obfuscateCode(obfuscateCode)
        , m_mainModuleDecl(mainModuleDecl)
    {}

    Session*        m_session = nullptr;
    DiagnosticSink* m_sink = nullptr;
    bool            m_obfuscateCode = false;
    ModuleDecl*     m_mainModuleDecl = nullptr;

    // The "global" environment for mapping declarations to their IR values.
    IRGenEnv globalEnv;

    // Map an AST-level declaration of an interface
    // requirement to the IR-level "key" that
    // is used to fetch that requirement from a
    // witness table.
    Dictionary<Decl*, IRStructKey*> interfaceRequirementKeys;

    // Arrays we keep around strictly for memory-management purposes:

    // Any extended values created during lowering need
    // to be cleaned up after the fact. We don't try
    // to reference-count these along the way because
    // they need to get stored into a `union` inside `LoweredValInfo`
    List<RefPtr<ExtendedValueInfo>> extValues;

    // Map from an AST-level statement that can be
    // used as the target of a `break` or `continue`
    // to the appropriate basic block to jump to.
    Dictionary<Stmt*, IRBlock*> breakLabels;
    Dictionary<Stmt*, IRBlock*> continueLabels;

    // List of all string literals used in user code, regardless
    // of how they were used (i.e., whether or not they were hashed).
    //
    // This does *not* collect:
    // * String literals that were only used for attributes/modifiers in
    //   the user's code (e.g., `"compute"` in `[shader("compute")]`)
    // * Any IR string literals constructed for the purpose of decorations,
    //   reflection, or other meta-data that did not appear as a literal
    //   in the source code.
    //
    List<IRInst*> m_stringLiterals;
};


struct IRGenContext
{
    ASTBuilder* astBuilder;

    // Shared state for the IR generation process
    SharedIRGenContext* shared;

    // environment for mapping AST decls to IR values
    IRGenEnv*   env;

    // IR builder to use when building code under this context
    IRBuilder* irBuilder;

    // The value to use for any `this` expressions
    // that appear in the current context.
    //
    // TODO: If we ever allow nesting of (non-static)
    // types, then we may need to support references
    // to an "outer `this`", and this representation
    // might be insufficient.
    LoweredValInfo thisVal;

    explicit IRGenContext(SharedIRGenContext* inShared, ASTBuilder* inAstBuilder)
        : shared(inShared)
        , astBuilder(inAstBuilder)
        , env(&inShared->globalEnv)
        , irBuilder(nullptr)
    {}

    Session* getSession()
    {
        return shared->m_session;
    }

    DiagnosticSink* getSink()
    {
        return shared->m_sink;
    }

    ModuleDecl* getMainModuleDecl()
    {
        return shared->m_mainModuleDecl;
    }
};

void setGlobalValue(SharedIRGenContext* sharedContext, Decl* decl, LoweredValInfo value)
{
    sharedContext->globalEnv.mapDeclToValue[decl] = value;
}

void setGlobalValue(IRGenContext* context, Decl* decl, LoweredValInfo value)
{
    setGlobalValue(context->shared, decl, value);
}

void setValue(IRGenContext* context, Decl* decl, LoweredValInfo value)
{
    context->env->mapDeclToValue[decl] = value;
}

ModuleDecl* findModuleDecl(Decl* decl)
{
    for (auto dd = decl; dd; dd = dd->parentDecl)
    {
        if (auto moduleDecl = as<ModuleDecl>(dd))
            return moduleDecl;
    }
    return nullptr;
}

bool isFromStdLib(Decl* decl)
{
    for (auto dd = decl; dd; dd = dd->parentDecl)
    {
        if (dd->hasModifier<FromStdLibModifier>())
            return true;
    }
    return false;
}

bool isImportedDecl(IRGenContext* context, Decl* decl)
{
    // If the declaration has the extern attribute then it must be imported
    // from another module
    //
    // The [__extern] attribute is a very special case feature (aka "a hack") that allows a symbol to be declared
    // as if it is part of the current module for AST purposes, but then expects to be imported from another IR module.
    // For that linkage to work, both the exporting and importing modules must have the same name (which would
    // usually indicate that they are the same module).
    //
    // Note that in practice for matching during linking uses the fully qualified name - including module name.
    // Thus using extern __attribute isn't useful for symbols that are imported via `import`, only symbols
    // that notionally come from the same module but are split into separate compilations (as can be done with -module-name)
    if (decl->findModifier<ExternAttribute>())
    {
        return true;
    }

    ModuleDecl* moduleDecl = findModuleDecl(decl);
    if (!moduleDecl)
        return false;

    // HACK: don't treat standard library code as
    // being imported for right now, just because
    // we don't load its IR in the same way as
    // for other imports.
    //
    // TODO: Fix this the right way, by having standard
    // library declarations have IR modules that we link
    // in via the normal means.
    if (isFromStdLib(decl))
        return false;

    if (moduleDecl != context->getMainModuleDecl())
        return true;

    return false;
}

    /// Is `decl` a function that should be force-inlined early in compilation (before linking)?
static bool isForceInlineEarly(Decl* decl)
{
    if(decl->hasModifier<UnsafeForceInlineEarlyAttribute>())
        return true;

    return false;
}

    /// Should the given `decl` nested in `parentDecl` be treated as a static rather than instance declaration?
bool isEffectivelyStatic(
    Decl*           decl,
    ContainerDecl*  parentDecl);

// Ensure that a version of the given declaration has been emitted to the IR
LoweredValInfo ensureDecl(
    IRGenContext*   context,
    Decl*           decl);

// Emit code as needed to construct a reference to the given declaration with
// any needed specializations in place.
LoweredValInfo emitDeclRef(
    IRGenContext*   context,
    DeclRef<Decl>   declRef,
    IRType*         type);

IRInst* getSimpleVal(IRGenContext* context, LoweredValInfo lowered);

int32_t getIntrinsicOp(
    Decl*                   decl,
    IntrinsicOpModifier*    intrinsicOpMod)
{
    int32_t op = intrinsicOpMod->op;
    if(op != 0)
        return op;

    // No specified modifier? Then we need to look it up
    // based on the name of the declaration...

    auto name = decl->getName();
    auto nameText = getUnownedStringSliceText(name);

    IROp irOp = findIROp(nameText);
    SLANG_ASSERT(irOp != kIROp_Invalid);
    SLANG_ASSERT(int32_t(irOp) >= 0);
    return int32_t(irOp);
}

// Given a `LoweredValInfo` for something callable, along with a
// bunch of arguments, emit an appropriate call to it.
LoweredValInfo emitCallToVal(
    IRGenContext*   context,
    IRType*         type,
    LoweredValInfo  funcVal,
    UInt            argCount,
    IRInst* const* args)
{
    auto builder = context->irBuilder;
    switch (funcVal.flavor)
    {
    case LoweredValInfo::Flavor::None:
        SLANG_UNEXPECTED("null function");
    default:
        return LoweredValInfo::simple(
            builder->emitCallInst(type, getSimpleVal(context, funcVal), argCount, args));
    }
}

LoweredValInfo lowerRValueExpr(
    IRGenContext*   context,
    Expr*           expr);

IRType* lowerType(
    IRGenContext*   context,
    Type*           type);

static IRType* lowerType(
    IRGenContext*   context,
    QualType const& type)
{
    return lowerType(context, type.type);
}

// Given a `DeclRef` for something callable, along with a bunch of
// arguments, emit an appropriate call to it.
LoweredValInfo emitCallToDeclRef(
    IRGenContext*   context,
    IRType*         type,
    DeclRef<Decl>   funcDeclRef,
    IRType*         funcType,
    UInt            argCount,
    IRInst* const* args)
{
    auto builder = context->irBuilder;


    if (auto subscriptDeclRef = funcDeclRef.as<SubscriptDecl>())
    {
        // A reference to a subscript declaration is a special case,
        // because it is not possible to call a subscript directly;
        // we must call one of its accessors.
        //
        // TODO: everything here will also apply to propery declarations
        // once we have them, so some of this code might be shared
        // some day.

        DeclRef<GetterDecl> getterDeclRef;
        bool justAGetter = true;
        for (auto accessorDeclRef : getMembersOfType<AccessorDecl>(subscriptDeclRef, MemberFilterStyle::Instance))
        {
            // We want to track whether this subscript has any accessors other than
            // `get` (assuming that everything except `get` can be used for setting...).

            if (auto foundGetterDeclRef = accessorDeclRef.as<GetterDecl>())
            {
                // We found a getter.
                getterDeclRef = foundGetterDeclRef;
            }
            else
            {
                // There was something other than a getter, so we can't
                // invoke an accessor just now.
                justAGetter = false;
            }
        }

        if (!justAGetter || !getterDeclRef)
        {
            // We can't perform an actual call right now, because
            // this expression might appear in an r-value or l-value
            // position (or *both* if it is being passed as an argument
            // for an `in out` parameter!).
            //
            // Instead, we will construct a special-case value to
            // represent the latent subscript operation (abstractly
            // this is a reference to a storage location).

            // The abstract storage location will need to include
            // all the arguments being passed to the subscript operation.

            RefPtr<BoundSubscriptInfo> boundSubscript = new BoundSubscriptInfo();
            boundSubscript->declRef = subscriptDeclRef;
            boundSubscript->type = type;
            boundSubscript->args.addRange(args, argCount);

            context->shared->extValues.add(boundSubscript);

            return LoweredValInfo::boundSubscript(boundSubscript);
        }

        // Otherwise we are just call the getter, and so that
        // is what we need to be emitting a call to...
        funcDeclRef = getterDeclRef;
    }

    auto funcDecl = funcDeclRef.getDecl();
    if(auto intrinsicOpModifier = funcDecl->findModifier<IntrinsicOpModifier>())
    {
        // The intrinsic op maps to a single IR instruction,
        // so we will emit an instruction with the chosen
        // opcode, and the arguments to the call as its operands.
        //
        auto intrinsicOp = getIntrinsicOp(funcDecl, intrinsicOpModifier);
        return LoweredValInfo::simple(builder->emitIntrinsicInst(
            type,
            IROp(intrinsicOp),
            argCount,
            args));
    }

    if( auto ctorDeclRef = funcDeclRef.as<ConstructorDecl>() )
    {
        if(!ctorDeclRef.getDecl()->body)
        {
            // HACK: For legacy reasons, all of the built-in initializers
            // in the standard library are declared without proper
            // intrinsic-op modifiers, so we will assume that an
            // initializer without a body should map to `kIROp_Construct`.
            //
            // TODO: We should make all the initializers in the
            // standard library have either a body or a proper
            // intrinsic-op modifier.
            //
            // TODO: We should eliminate `kIROp_Construct` from the
            // IR completely, in favor of more detailed/specific ops
            // that cover the cases we actually care about.
            //
            return LoweredValInfo::simple(builder->emitConstructorInst(type, argCount, args));
        }
    }

    // Fallback case is to emit an actual call.
    //
    // TODO: We are constructing a type that we expect the function
    // being called to have here, but that type doesn't account
    // for `in` vs. `out`/`inout` parameters, so it could easily
    // be wrong. We should sort out why this path in the code
    // even needs to be computing a type (rather than taking
    // it directly from the declaration).
    //
    if(!funcType)
    {
        List<IRType*> argTypes;
        for(UInt ii = 0; ii < argCount; ++ii)
        {
            argTypes.add(args[ii]->getDataType());
        }
        funcType = builder->getFuncType(argCount, argTypes.getBuffer(), type);
    }
    LoweredValInfo funcVal = emitDeclRef(context, funcDeclRef, funcType);
    return emitCallToVal(context, type, funcVal, argCount, args);
}

LoweredValInfo emitCallToDeclRef(
    IRGenContext*           context,
    IRType*                 type,
    DeclRef<Decl>           funcDeclRef,
    IRType*                 funcType,
    List<IRInst*> const&    args)
{
    return emitCallToDeclRef(context, type, funcDeclRef, funcType, args.getCount(), args.getBuffer());
}

IRInst* getFieldKey(
    IRGenContext*       context,
    DeclRef<VarDecl>    field)
{
    return getSimpleVal(context, emitDeclRef(context, field, context->irBuilder->getKeyType()));
}

LoweredValInfo extractField(
    IRGenContext*       context,
    IRType*             fieldType,
    LoweredValInfo      base,
    DeclRef<VarDecl>    field)
{
    IRBuilder* builder = context->irBuilder;

    switch (base.flavor)
    {
    default:
        {
            IRInst* irBase = getSimpleVal(context, base);
            return LoweredValInfo::simple(
                builder->emitFieldExtract(
                    fieldType,
                    irBase,
                    getFieldKey(context, field)));
        }
        break;

    case LoweredValInfo::Flavor::BoundMember:
    case LoweredValInfo::Flavor::BoundSubscript:
        {
            // The base value is one that is trying to defer a get-vs-set
            // decision, so we will need to do the same.

            RefPtr<BoundMemberInfo> boundMemberInfo = new BoundMemberInfo();
            boundMemberInfo->type = fieldType;
            boundMemberInfo->base = base;
            boundMemberInfo->declRef = field;

            context->shared->extValues.add(boundMemberInfo);
            return LoweredValInfo::boundMember(boundMemberInfo);
        }
        break;

    case LoweredValInfo::Flavor::Ptr:
        {
            // We are "extracting" a field from an lvalue address,
            // which means we should just compute an lvalue
            // representing the field address.
            IRInst* irBasePtr = base.val;
            return LoweredValInfo::ptr(
                builder->emitFieldAddress(
                    builder->getPtrType(fieldType),
                    irBasePtr,
                    getFieldKey(context, field)));
        }
        break;
    }
}



LoweredValInfo materialize(
    IRGenContext*   context,
    LoweredValInfo  lowered)
{
    auto builder = context->irBuilder;

top:
    switch(lowered.flavor)
    {
    case LoweredValInfo::Flavor::None:
    case LoweredValInfo::Flavor::Simple:
    case LoweredValInfo::Flavor::Ptr:
        return lowered;

    case LoweredValInfo::Flavor::BoundSubscript:
        {
            auto boundSubscriptInfo = lowered.getBoundSubscriptInfo();

            // We are being asked to extract a value from a subscript call
            // (e.g., `base[index]`). We will first check if the subscript
            // declared a getter and use that if possible, and then fall
            // back to a `ref` accessor if one is defined.
            //
            // (Picking the `get` over the `ref` accessor simplifies things
            // in case the `get` operation has a natural translation for
            // a target, while the general `ref` case does not...)

            auto getters = getMembersOfType<GetterDecl>(boundSubscriptInfo->declRef, MemberFilterStyle::Instance);
            if (getters.getCount())
            {
                lowered = emitCallToDeclRef(
                    context,
                    boundSubscriptInfo->type,
                    *getters.begin(),
                    nullptr,
                    boundSubscriptInfo->args);
                goto top;
            }

            auto refAccessors = getMembersOfType<RefAccessorDecl>(boundSubscriptInfo->declRef, MemberFilterStyle::Instance);
            if(refAccessors.getCount())
            {
                // The `ref` accessor will return a pointer to the value, so
                // we need to reflect that in the type of our `call` instruction.
                IRType* ptrType = context->irBuilder->getPtrType(boundSubscriptInfo->type);

                LoweredValInfo refVal = emitCallToDeclRef(
                    context,
                    ptrType,
                    *refAccessors.begin(),
                    nullptr,
                    boundSubscriptInfo->args);

                // The result from the call needs to be implicitly dereferenced,
                // so that it can work as an l-value of the desired result type.
                lowered = LoweredValInfo::ptr(getSimpleVal(context, refVal));

                goto top;
            }

            SLANG_UNEXPECTED("subscript had no getter");
            UNREACHABLE_RETURN(LoweredValInfo());
        }
        break;

    case LoweredValInfo::Flavor::BoundMember:
        {
            auto boundMemberInfo = lowered.getBoundMemberInfo();
            auto base = materialize(context, boundMemberInfo->base);

            auto declRef = boundMemberInfo->declRef;
            if( auto fieldDeclRef = declRef.as<VarDecl>() )
            {
                lowered = extractField(context, boundMemberInfo->type, base, fieldDeclRef);
                goto top;
            }
            else
            {

                SLANG_UNEXPECTED("unexpected member flavor");
                UNREACHABLE_RETURN(LoweredValInfo());
            }
        }
        break;

    case LoweredValInfo::Flavor::SwizzledLValue:
        {
            auto swizzleInfo = lowered.getSwizzledLValueInfo();

            return LoweredValInfo::simple(builder->emitSwizzle(
                swizzleInfo->type,
                getSimpleVal(context, swizzleInfo->base),
                swizzleInfo->elementCount,
                swizzleInfo->elementIndices));
        }

    default:
        SLANG_UNEXPECTED("unhandled value flavor");
        UNREACHABLE_RETURN(LoweredValInfo());
    }

}

IRInst* getSimpleVal(IRGenContext* context, LoweredValInfo lowered)
{
    auto builder = context->irBuilder;

    // First, try to eliminate any "bound" operations along the chain,
    // so that we are dealing with an ordinary value, or an l-value pointer.
    lowered = materialize(context, lowered);

    switch(lowered.flavor)
    {
    case LoweredValInfo::Flavor::None:
        return nullptr;

    case LoweredValInfo::Flavor::Simple:
        return lowered.val;

    case LoweredValInfo::Flavor::Ptr:
        return builder->emitLoad(lowered.val);

    default:
        SLANG_UNEXPECTED("unhandled value flavor");
        UNREACHABLE_RETURN(nullptr);
    }
}

LoweredValInfo lowerVal(
    IRGenContext*   context,
    Val*            val);

IRInst* lowerSimpleVal(
    IRGenContext*   context,
    Val*            val)
{
    auto lowered = lowerVal(context, val);
    return getSimpleVal(context, lowered);
}

LoweredValInfo lowerLValueExpr(
    IRGenContext*   context,
    Expr*           expr);

void assign(
    IRGenContext*           context,
    LoweredValInfo const&   left,
    LoweredValInfo const&   right);

IRInst* getAddress(
    IRGenContext*           context,
    LoweredValInfo const&   inVal,
    SourceLoc               diagnosticLocation);

void lowerStmt(
    IRGenContext*   context,
    Stmt*           stmt);

LoweredValInfo lowerDecl(
    IRGenContext*   context,
    DeclBase*       decl);

IRType* getIntType(
    IRGenContext* context)
{
    return context->irBuilder->getBasicType(BaseType::Int);
}

static IRGeneric* getOuterGeneric(IRInst* gv)
{
    auto parentBlock = as<IRBlock>(gv->getParent());
    if (!parentBlock) return nullptr;

    auto parentGeneric = as<IRGeneric>(parentBlock->getParent());
    return parentGeneric;
}

static void addLinkageDecoration(
    IRGenContext*               context,
    IRInst*                     inInst,
    Decl*                       decl,
    UnownedStringSlice const&   mangledName)
{
    // If the instruction is nested inside one or more generics,
    // then the mangled name should really apply to the outer-most
    // generic, and not the declaration nested inside.

    auto builder = context->irBuilder;

    IRInst* inst = inInst;
    while (auto outerGeneric = getOuterGeneric(inst))
    {
        inst = outerGeneric;
    }

    if(isImportedDecl(context, decl))
    {
        builder->addImportDecoration(inst, mangledName);
    }
    else
    {
        builder->addExportDecoration(inst, mangledName);
    }
}

static void addLinkageDecoration(
    IRGenContext*               context,
    IRInst*                     inst,
    Decl*                       decl)
{
     String mangledName = getMangledName(context->astBuilder, decl);

     if (context->shared->m_obfuscateCode)
     {
         mangledName = getHashedName(mangledName.getUnownedSlice());
     }

    addLinkageDecoration(context, inst, decl, mangledName.getUnownedSlice());
}

IRStructKey* getInterfaceRequirementKey(
    IRGenContext*   context,
    Decl*           requirementDecl)
{
    IRStructKey* requirementKey = nullptr;
    if(context->shared->interfaceRequirementKeys.TryGetValue(requirementDecl, requirementKey))
    {
        return requirementKey;
    }

    IRBuilder builderStorage = *context->irBuilder;
    auto builder = &builderStorage;

    builder->setInsertInto(builder->sharedBuilder->module->getModuleInst());

    // Construct a key to serve as the representation of
    // this requirement in the IR, and to allow lookup
    // into the declaration.
    requirementKey = builder->createStructKey();

    addLinkageDecoration(context, requirementKey, requirementDecl);

    context->shared->interfaceRequirementKeys.Add(requirementDecl, requirementKey);

    return requirementKey;
}


SubstitutionSet lowerSubstitutions(IRGenContext* context, SubstitutionSet subst);
//

struct ValLoweringVisitor : ValVisitor<ValLoweringVisitor, LoweredValInfo, LoweredValInfo>
{
    IRGenContext* context;

    IRBuilder* getBuilder() { return context->irBuilder; }

    LoweredValInfo visitVal(Val* /*val*/)
    {
        SLANG_UNIMPLEMENTED_X("value lowering");
        UNREACHABLE_RETURN(LoweredValInfo());
    }

    LoweredValInfo visitGenericParamIntVal(GenericParamIntVal* val)
    {
        return emitDeclRef(context, val->declRef,
            lowerType(context, getType(context->astBuilder, val->declRef)));
    }

    LoweredValInfo visitDeclaredSubtypeWitness(DeclaredSubtypeWitness* val)
    {
        return emitDeclRef(context, val->declRef,
            context->irBuilder->getWitnessTableType());
    }

    LoweredValInfo visitTransitiveSubtypeWitness(
        TransitiveSubtypeWitness* val)
    {
        // The base (subToMid) will turn into a value with
        // witness-table type.
        IRInst* baseWitnessTable = lowerSimpleVal(context, val->subToMid);

        // The next step should map to an interface requirement
        // that is itself an interface conformance, so the result
        // of lowering this value should be a "key" that we can
        // use to look up a witness table.
        IRInst* requirementKey = getInterfaceRequirementKey(context, val->midToSup.getDecl());

        // TODO: There are some ugly cases here if `midToSup` is allowed
        // to be an arbitrary witness, rather than just a declared one,
        // and we should probably change the front-end representation
        // to reflect the right constraints.

        return LoweredValInfo::simple(getBuilder()->emitLookupInterfaceMethodInst(
            nullptr,
            baseWitnessTable,
            requirementKey));
    }

    LoweredValInfo visitTaggedUnionSubtypeWitness(
        TaggedUnionSubtypeWitness* val)
    {
        // The sub-type in this case is a tagged union `A | B | ...`,
        // and the witness holds an array of witnesses showing that each
        // "case" (`A`, `B`, etc.) is a subtype of the super-type.

        // We will start by getting the IR-level representation of the
        // sub type (the tagged union type).
        //
        auto irTaggedUnionType = lowerType(context, val->sub);

        // We can turn each of those per-case witnesses into a witness
        // table value:
        //
        auto caseCount = val->caseWitnesses.getCount();
        List<IRInst*> caseWitnessTables;
        for( auto caseWitness : val->caseWitnesses )
        {
            auto caseWitnessTable = lowerSimpleVal(context, caseWitness);
            caseWitnessTables.add(caseWitnessTable);
        }

        // Now we need to synthesize a witness table for the tagged union
        // value, showing how it can implement all of the requirements
        // of the super type by delegating to the appropriate implementation
        // on a per-case basis.
        //
        // We will assume here that the super-type is an interface, and it
        // will be left to the front-end to ensure this property.
        //
        auto supDeclRefType = as<DeclRefType>(val->sup);
        if(!supDeclRefType)
        {
            SLANG_UNEXPECTED("super-type not a decl-ref type when generating tagged union witness table");
            UNREACHABLE_RETURN(LoweredValInfo());
        }
        auto supInterfaceDeclRef = supDeclRefType->declRef.as<InterfaceDecl>();
        if( !supInterfaceDeclRef )
        {
            SLANG_UNEXPECTED("super-type not an interface type when generating tagged union witness table");
            UNREACHABLE_RETURN(LoweredValInfo());
        }

        auto irWitnessTable = getBuilder()->createWitnessTable();

        // Now we will iterate over the requirements (members) of the
        // interface and try to synthesize an appropriate value for each.
        //
        for( auto reqDeclRef : getMembers(supInterfaceDeclRef) )
        {
            // TODO: if there are any members we shouldn't process as a requirement,
            // then we should detect and skip them here.
            //

            // Every interface requirement will have a unique key that is used
            // when looking up the requirement in a concrete witness table.
            //
            auto irReqKey = getInterfaceRequirementKey(context, reqDeclRef.getDecl());

            // We expect that each of the witness tables in `caseWitnessTables`
            // will have an entry to match these keys. However, we may not
            // have a concrete `IRWitnessTable` for each of the case types, either
            // because they are a specialization of a generic (so that the witness
            // table reference is a `specialize` instruction at this point), or
            // they are a type external to this module (so that we have a declaration
            // rather than a definition of the witness table).

            // Our task is to create an IR value that can satisfy the interface
            // requirement for the tagged union type, by appropriately delegating
            // to the implementations of the same requirement in the case types.
            //
            IRInst* irSatisfyingVal = nullptr;



            if(auto callableDeclRef = reqDeclRef.as<CallableDecl>())
            {
                // We have something callable, so we need to synthesize
                // a function to satisfy it.
                //
                auto irFunc = getBuilder()->createFunc();
                irSatisfyingVal = irFunc;

                IRBuilder subBuilderStorage;
                auto subBuilder = &subBuilderStorage;
                subBuilder->sharedBuilder = getBuilder()->sharedBuilder;
                subBuilder->setInsertInto(irFunc);

                // We will start by setting up the function parameters,
                // which live in the entry block of the IR function.
                //
                auto entryBlock = subBuilder->emitBlock();
                subBuilder->setInsertInto(entryBlock);

                // Create a `this` parameter of the tagged-union type.
                //
                // TODO: need to handle the `[mutating]` case here...
                //
                auto irThisType = irTaggedUnionType;
                auto irThisParam = subBuilder->emitParam(irThisType);

                List<IRType*> irParamTypes;
                irParamTypes.add(irThisType);

                // Create the remaining parameters of the callable,
                // using a decl-ref specialized to the tagged union
                // type (so that things like associated types are
                // mapped to the correct witness value).
                //
                List<IRParam*> irParams;
                for( auto paramDeclRef : getMembersOfType<ParamDecl>(callableDeclRef) )
                {
                    // TODO: need to handle `out` and `in out` here. Over all
                    // there is a lot of duplication here with the existing logic
                    // for emitting the signature of a `CallableDecl`, and we should
                    // try to re-use that if at all possible.
                    //
                    auto irParamType = lowerType(context, getType(context->astBuilder, paramDeclRef));
                    auto irParam = subBuilder->emitParam(irParamType);

                    irParams.add(irParam);
                    irParamTypes.add(irParamType);
                }

                auto irResultType = lowerType(context, getResultType(context->astBuilder, callableDeclRef));

                auto irFuncType = subBuilder->getFuncType(
                    irParamTypes,
                    irResultType);
                irFunc->setFullType(irFuncType);

                // The first thing our function needs to do is extract the tag
                // from the incoming `this` parameter.
                //
                auto irTagVal = subBuilder->emitExtractTaggedUnionTag(irThisParam);

                // Next we want to emit a `switch` on the tag value, but before we
                // do that we need to generate the code for each of the cases so that
                // our `switch` has somewhere to branch to.
                //
                List<IRInst*> switchCaseOperands;

                IRBlock* defaultLabel = nullptr;

                for( Index ii = 0; ii < caseCount; ++ii )
                {
                    auto caseTag = subBuilder->getIntValue(irTagVal->getDataType(), ii);

                    subBuilder->setInsertInto(irFunc);
                    auto caseLabel = subBuilder->emitBlock();

                    if(!defaultLabel)
                        defaultLabel = caseLabel;

                    switchCaseOperands.add(caseTag);
                    switchCaseOperands.add(caseLabel);

                    subBuilder->setInsertInto(caseLabel);

                    // We need to look up the satisfying value for this interface
                    // requirement on the witness table of the particular case value.
                    //
                    // We already have the witness table, and the requirement key is
                    // just `irReqKey`.
                    //
                    auto caseWitnessTable = caseWitnessTables[ii];

                    // The subtle bit here is determining the type we expect the
                    // satisfying value to have, since that depends on the actual
                    // type that is satisfying the requirement.
                    //
                    IRType* caseResultType = irResultType;
                    IRType* caseFuncType = nullptr;
                    auto caseFunc = subBuilder->emitLookupInterfaceMethodInst(
                        caseFuncType,
                        caseWitnessTable,
                        irReqKey);

                    // We are going to emit a `call` to the satisfying value
                    // for the case type, so we will collect the arguments for that call.
                    //
                    List<IRInst*> caseArgs;

                    // The `this` argument to the call will need to represent the
                    // appropriate field of our tagged union.
                    //
                    IRType* caseThisType = (IRType*) irTaggedUnionType->getOperand(ii);
                    auto caseThisArg = subBuilder->emitExtractTaggedUnionPayload(
                        caseThisType,
                        irThisParam, caseTag);
                    caseArgs.add(caseThisArg);

                    // The remaining arguments to the call will just be forwarded from
                    // the parameters of the wrapper function.
                    //
                    // TODO: This would need to change if/when we started allowing `This` type
                    // or associated-type parameters to be used at call sites where a tagged
                    // union is used.
                    //
                    for( auto param : irParams )
                    {
                        caseArgs.add(param);
                    }

                    auto caseCall = subBuilder->emitCallInst(caseResultType, caseFunc, caseArgs);

                    if( as<IRVoidType>(irResultType->getDataType()) )
                    {
                        subBuilder->emitReturn();
                    }
                    else
                    {
                        subBuilder->emitReturn(caseCall);
                    }
                }

                // We will create a block to represent the supposedly-unreachable
                // code that will run if no `case` matches.
                //
                subBuilder->setInsertInto(irFunc);
                auto invalidLabel = subBuilder->emitBlock();
                subBuilder->setInsertInto(invalidLabel);
                subBuilder->emitUnreachable();

                if(!defaultLabel) defaultLabel = invalidLabel;

                // Now we have enough information to go back and emit the `switch` instruction
                // into the entry block.
                subBuilder->setInsertInto(entryBlock);
                subBuilder->emitSwitch(
                    irTagVal,       // value to `switch` on
                    invalidLabel,   // `break` label (block after the `switch` statement ends)
                    defaultLabel,   // `default` label (where to go if no `case` matches)
                    switchCaseOperands.getCount(),
                    switchCaseOperands.getBuffer());
            }
            else
            {
                // TODO: We need to handle other cases of interface requirements.
                SLANG_UNEXPECTED("unexpceted interface requirement when generating tagged union witness table");
                UNREACHABLE_RETURN(LoweredValInfo());
            }

            // Once we've generating a value to satisfying the requirement, we install
            // it into the witness table for our tagged-union type.
            //
            getBuilder()->createWitnessTableEntry(irWitnessTable, irReqKey, irSatisfyingVal);
        }

        return LoweredValInfo::simple(irWitnessTable);
    }

    LoweredValInfo visitConstantIntVal(ConstantIntVal* val)
    {
        // TODO: it is a bit messy here that the `ConstantIntVal` representation
        // has no notion of a *type* associated with the value...

        auto type = getIntType(context);
        return LoweredValInfo::simple(getBuilder()->getIntValue(type, val->value));
    }

    IRFuncType* visitFuncType(FuncType* type)
    {
        IRType* resultType = lowerType(context, type->getResultType());
        UInt paramCount = type->getParamCount();
        List<IRType*> paramTypes;
        for (UInt pp = 0; pp < paramCount; ++pp)
        {
            paramTypes.add(lowerType(context, type->getParamType(pp)));
        }
        return getBuilder()->getFuncType(
            paramCount,
            paramTypes.getBuffer(),
            resultType);
    }

    IRType* visitDeclRefType(DeclRefType* type)
    {
        auto declRef = type->declRef;
        auto decl = declRef.getDecl();

        // Check for types with teh `__intrinsic_type` modifier.
        if(decl->findModifier<IntrinsicTypeModifier>())
        {
            return lowerSimpleIntrinsicType(type);
        }


        return (IRType*) getSimpleVal(
            context,
            emitDeclRef(context, declRef,
                context->irBuilder->getTypeKind()));
    }

    IRType* visitNamedExpressionType(NamedExpressionType* type)
    {
        return (IRType*)getSimpleVal(context, dispatchType(type->getCanonicalType()));
    }

    IRType* visitBasicExpressionType(BasicExpressionType* type)
    {
        return getBuilder()->getBasicType(
            type->baseType);
    }

    IRType* visitVectorExpressionType(VectorExpressionType* type)
    {
        auto elementType = lowerType(context, type->elementType);
        auto elementCount = lowerSimpleVal(context, type->elementCount);

        return getBuilder()->getVectorType(
            elementType,
            elementCount);
    }

    IRType* visitMatrixExpressionType(MatrixExpressionType* type)
    {
        auto elementType = lowerType(context, type->getElementType());
        auto rowCount = lowerSimpleVal(context, type->getRowCount());
        auto columnCount = lowerSimpleVal(context, type->getColumnCount());

        return getBuilder()->getMatrixType(
            elementType,
            rowCount,
            columnCount);
    }

    IRType* visitArrayExpressionType(ArrayExpressionType* type)
    {
        auto elementType = lowerType(context, type->baseType);
        if (type->arrayLength)
        {
            auto elementCount = lowerSimpleVal(context, type->arrayLength);
            return getBuilder()->getArrayType(
                elementType,
                elementCount);
        }
        else
        {
            return getBuilder()->getUnsizedArrayType(
                elementType);
        }
    }

    // Lower a type where the type declaration being referenced is assumed
    // to be an intrinsic type, which can thus be lowered to a simple IR
    // type with the appropriate opcode.
    IRType* lowerSimpleIntrinsicType(DeclRefType* type)
    {
        auto intrinsicTypeModifier = type->declRef.getDecl()->findModifier<IntrinsicTypeModifier>();
        SLANG_ASSERT(intrinsicTypeModifier);
        IROp op = IROp(intrinsicTypeModifier->irOp);
        return getBuilder()->getType(op);
    }

    // Lower a type where the type declaration being referenced is assumed
    // to be an intrinsic type with a single generic type parameter, and
    // which can thus be lowered to a simple IR type with the appropriate opcode.
    IRType* lowerGenericIntrinsicType(DeclRefType* type, Type* elementType)
    {
        auto intrinsicTypeModifier = type->declRef.getDecl()->findModifier<IntrinsicTypeModifier>();
        SLANG_ASSERT(intrinsicTypeModifier);
        IROp op = IROp(intrinsicTypeModifier->irOp);
        IRInst* irElementType = lowerType(context, elementType);
        return getBuilder()->getType(
            op,
            1,
            &irElementType);
    }

    IRType* lowerGenericIntrinsicType(DeclRefType* type, Type* elementType, IntVal* count)
    {
        auto intrinsicTypeModifier = type->declRef.getDecl()->findModifier<IntrinsicTypeModifier>();
        SLANG_ASSERT(intrinsicTypeModifier);
        IROp op = IROp(intrinsicTypeModifier->irOp);
        IRInst* irElementType = lowerType(context, elementType);

        IRInst* irCount = lowerSimpleVal(context, count);

        IRInst* const operands[2] =
        {
            irElementType,
            irCount,
        };

        return getBuilder()->getType(
            op,
            SLANG_COUNT_OF(operands),
            operands);
    }

    IRType* visitResourceType(ResourceType* type)
    {
        return lowerGenericIntrinsicType(type, type->elementType);
    }

    IRType* visitSamplerStateType(SamplerStateType* type)
    {
        return lowerSimpleIntrinsicType(type);
    }

    IRType* visitBuiltinGenericType(BuiltinGenericType* type)
    {
        return lowerGenericIntrinsicType(type, type->elementType);
    }

    IRType* visitUntypedBufferResourceType(UntypedBufferResourceType* type)
    {
        return lowerSimpleIntrinsicType(type);
    }

    IRType* visitHLSLPatchType(HLSLPatchType* type)
    {
        Type* elementType = type->getElementType();
        IntVal* count = type->getElementCount();

        return lowerGenericIntrinsicType(type, elementType, count);
    }

    IRType* visitExtractExistentialType(ExtractExistentialType* type)
    {
        auto declRef = type->declRef;
        auto existentialType = lowerType(context, getType(context->astBuilder, declRef));
        IRInst* existentialVal = getSimpleVal(context, emitDeclRef(context, declRef, existentialType));
        return getBuilder()->emitExtractExistentialType(existentialVal);
    }

    LoweredValInfo visitExtractExistentialSubtypeWitness(ExtractExistentialSubtypeWitness* witness)
    {
        auto declRef = witness->declRef;
        auto existentialType = lowerType(context, getType(context->astBuilder, declRef));
        IRInst* existentialVal = getSimpleVal(context, emitDeclRef(context, declRef, existentialType));
        return LoweredValInfo::simple(getBuilder()->emitExtractExistentialWitnessTable(existentialVal));
    }

    LoweredValInfo visitTaggedUnionType(TaggedUnionType* type)
    {
        // A tagged union type will lower into an IR `union` over the cases,
        // along with an IR `struct` with a field for the union and a tag.
        // (Note: we are placing the tag after the payload to avoid padding
        // in the case where the payload is more aligned than the tag)
        //
        // TODO: should we be lowering directly like this, or have
        // an IR-level representation of tagged unions?
        //

        List<IRType*> irCaseTypes;
        for(auto caseType : type->caseTypes)
        {
            auto irCaseType = lowerType(context, caseType);
            irCaseTypes.add(irCaseType);
        }

        auto irType = getBuilder()->getTaggedUnionType(irCaseTypes);
        if(!irType->findDecoration<IRLinkageDecoration>())
        {
            // We need a way for later passes to attach layout information
            // to this type, so we will give it a mangled name here.
            //
            getBuilder()->addExportDecoration(
                irType,
                getMangledTypeName(context->astBuilder, type).getUnownedSlice());
        }
        return LoweredValInfo::simple(irType);
    }

    LoweredValInfo visitExistentialSpecializedType(ExistentialSpecializedType* type)
    {
        auto irBaseType = lowerType(context, type->baseType);

        List<IRInst*> slotArgs;
        for(auto arg : type->args)
        {
            auto irArgVal = lowerSimpleVal(context, arg.val);
            slotArgs.add(irArgVal);

            if(auto witness = arg.witness)
            {
                auto irArgWitness = lowerSimpleVal(context, witness);
                slotArgs.add(irArgWitness);
            }
        }

        auto irType = getBuilder()->getBindExistentialsType(irBaseType, slotArgs.getCount(), slotArgs.getBuffer());
        return LoweredValInfo::simple(irType);
    }

    LoweredValInfo visitThisType(ThisType* type)
    {
        // TODO: In theory, we should only run into a `ThisType` when lowering a concrete
        // declaration defined on an interface type (e.g., via an `extension`).
        //
        // There is an open question of how we should emit a concrete method (say) defined
        // on an `interface` type. We could emit the code in "object-oriented" style,
        // passing in a `this` parameter of type `IFoo`, or we could emit it in a "generic"
        // type where the whole member is wrapped in a generic on `<This : IFoo>`.
        //
        // The generic option has the benefit of having a clear solution in the case of
        // static members that don't have a `this` parameter, but might still need `This`,
        // but we have so far favored the "object-oriented" lowering for code involving
        // bare interface types.
        //
        // For now we punt and emit the `ThisType` of an interface `IFoo` as `IFoo`.
        //
        return emitDeclRef(context, type->interfaceDeclRef, getBuilder()->getTypeKind());
    }


    // We do not expect to encounter the following types in ASTs that have
    // passed front-end semantic checking.
#define UNEXPECTED_CASE(NAME) IRType* visit##NAME(NAME*) { SLANG_UNEXPECTED(#NAME); UNREACHABLE_RETURN(nullptr); }
    UNEXPECTED_CASE(GenericDeclRefType)
    UNEXPECTED_CASE(TypeType)
    UNEXPECTED_CASE(ErrorType)
    UNEXPECTED_CASE(InitializerListType)
    UNEXPECTED_CASE(OverloadGroupType)
    UNEXPECTED_CASE(NamespaceType)
#undef UNEXPECTED_CASE
};

LoweredValInfo lowerVal(
    IRGenContext*   context,
    Val*            val)
{
    ValLoweringVisitor visitor;
    visitor.context = context;
    return visitor.dispatch(val);
}

IRType* lowerType(
    IRGenContext*   context,
    Type*           type)
{
    ValLoweringVisitor visitor;
    visitor.context = context;
    return (IRType*) getSimpleVal(context, visitor.dispatchType(type));
}

void addVarDecorations(
    IRGenContext*   context,
    IRInst*         inst,
    Decl*           decl)
{
    auto builder = context->irBuilder;
    for(RefPtr<Modifier> mod : decl->modifiers)
    {
        if(as<HLSLNoInterpolationModifier>(mod))
        {
            builder->addInterpolationModeDecoration(inst, IRInterpolationMode::NoInterpolation);
        }
        else if(as<HLSLNoPerspectiveModifier>(mod))
        {
            builder->addInterpolationModeDecoration(inst, IRInterpolationMode::NoPerspective);
        }
        else if(as<HLSLLinearModifier>(mod))
        {
            builder->addInterpolationModeDecoration(inst, IRInterpolationMode::Linear);
        }
        else if(as<HLSLSampleModifier>(mod))
        {
            builder->addInterpolationModeDecoration(inst, IRInterpolationMode::Sample);
        }
        else if(as<HLSLCentroidModifier>(mod))
        {
            builder->addInterpolationModeDecoration(inst, IRInterpolationMode::Centroid);
        }
        else if(as<VulkanRayPayloadAttribute>(mod))
        {
            builder->addSimpleDecoration<IRVulkanRayPayloadDecoration>(inst);
        }
        else if(as<VulkanCallablePayloadAttribute>(mod))
        {
            builder->addSimpleDecoration<IRVulkanCallablePayloadDecoration>(inst);
        }
        else if(as<VulkanHitAttributesAttribute>(mod))
        {
            builder->addSimpleDecoration<IRVulkanHitAttributesDecoration>(inst);
        }
        else if(as<GloballyCoherentModifier>(mod))
        {
            builder->addSimpleDecoration<IRGloballyCoherentDecoration>(inst);
        }
        else if(as<PreciseModifier>(mod))
        {
            builder->addSimpleDecoration<IRPreciseDecoration>(inst);
        }
        else if(auto formatAttr = as<FormatAttribute>(mod))
        {
            builder->addFormatDecoration(inst, formatAttr->format);
        }

        // TODO: what are other modifiers we need to propagate through?
    }
}

/// If `decl` has a modifier that should turn into a
/// rate qualifier, then apply it to `inst`.
void maybeSetRate(
    IRGenContext*   context,
    IRInst*         inst,
    Decl*           decl)
{
    auto builder = context->irBuilder;

    if (decl->hasModifier<HLSLGroupSharedModifier>())
    {
        inst->setFullType(builder->getRateQualifiedType(
            builder->getGroupSharedRate(),
            inst->getFullType()));
    }
}

static String getNameForNameHint(
    IRGenContext*   context,
    Decl*           decl)
{
    // We will use a bit of an ad hoc convention here for now.

    Name* leafName = decl->getName();

    // Handle custom name for a global parameter group (e.g., a `cbuffer`)
    if(auto reflectionNameModifier = decl->findModifier<ParameterGroupReflectionName>())
    {
        leafName = reflectionNameModifier->nameAndLoc.name;
    }

    // There is no point in trying to provide a name hint for something with no name,
    // or with an empty name
    if(!leafName)
        return String();
    if(leafName->text.getLength() == 0)
        return String();


    if(auto varDecl = as<VarDeclBase>(decl))
    {
        // For an ordinary local variable, global variable,
        // parameter, or field, we will just use the name
        // as declared, and now work in anything from
        // its parent declaration(s).
        //
        // TODO: consider whether global/static variables should
        // follow different rules.
        //
        return leafName->text;
    }

    // For other cases of declaration, we want to consider
    // merging its name with the name of its parent declaration.
    auto parentDecl = decl->parentDecl;

    // Skip past a generic parent, if we are a declaration nested in a generic.
    if(auto genericParentDecl = as<GenericDecl>(parentDecl))
        parentDecl = genericParentDecl->parentDecl;

    // A `ModuleDecl` can have a name too, but in the common case
    // we don't want to generate name hints that include the module
    // name, simply because they would lead to every global symbol
    // getting a much longer name.
    //
    // TODO: We should probably include the module name for symbols
    // being `import`ed, and not for symbols being compiled directly
    // (those coming from a module that had no name given to it).
    //
    // For now we skip past a `ModuleDecl` parent.
    //
    if(auto moduleParentDecl = as<ModuleDecl>(parentDecl))
        parentDecl = moduleParentDecl->parentDecl;

    if(!parentDecl)
    {
        return leafName->text;
    }

    auto parentName = getNameForNameHint(context, parentDecl);
    if(parentName.getLength() == 0)
    {
        return leafName->text;
    }

    // We will now construct a new `Name` to use as the hint,
    // combining the name of the parent and the leaf declaration.

    StringBuilder sb;
    sb.append(parentName);
    sb.append(".");
    sb.append(leafName->text);

    return sb.ProduceString();
}

/// Try to add an appropriate name hint to the instruction,
/// that can be used for back-end code emission or debug info.
static void addNameHint(
    IRGenContext*   context,
    IRInst*         inst,
    Decl*           decl)
{
    String name = getNameForNameHint(context, decl);
    if(name.getLength() == 0)
        return;
    context->irBuilder->addNameHintDecoration(inst, name.getUnownedSlice());
}

/// Add a name hint based on a fixed string.
static void addNameHint(
    IRGenContext*   context,
    IRInst*         inst,
    char const*     text)
{
    if (context->shared->m_obfuscateCode)
    {
        return;
    }

    context->irBuilder->addNameHintDecoration(inst, UnownedTerminatedStringSlice(text));
}

LoweredValInfo createVar(
    IRGenContext*   context,
    IRType*         type,
    Decl*           decl = nullptr)
{
    auto builder = context->irBuilder;
    auto irAlloc = builder->emitVar(type);

    if (decl)
    {
        maybeSetRate(context, irAlloc, decl);

        addVarDecorations(context, irAlloc, decl);

        builder->addHighLevelDeclDecoration(irAlloc, decl);

        addNameHint(context, irAlloc, decl);
    }

    return LoweredValInfo::ptr(irAlloc);
}

void addArgs(
    IRGenContext*   context,
    List<IRInst*>* ioArgs,
    LoweredValInfo  argInfo)
{
    auto& args = *ioArgs;
    switch( argInfo.flavor )
    {
    case LoweredValInfo::Flavor::Simple:
    case LoweredValInfo::Flavor::Ptr:
    case LoweredValInfo::Flavor::SwizzledLValue:
    case LoweredValInfo::Flavor::BoundSubscript:
    case LoweredValInfo::Flavor::BoundMember:
        args.add(getSimpleVal(context, argInfo));
        break;

    default:
        SLANG_UNIMPLEMENTED_X("addArgs case");
        break;
    }
}

//

// When we try to turn a `LoweredValInfo` into an address of some temporary storage,
// we can either do it "aggressively" or not (what we'll call the "default" behavior,
// although it isn't strictly more common).
//
// The case that this is mostly there to address is when somebody writes an operation
// like:
//
//      foo[a] = b;
//
// In that case, we might as well just use the `set` accessor if there is one, rather
// than complicate things. However, in more complex cases like:
//
//      foo[a].x = b;
//
// there is no way to satisfy the semantics of the code the user wrote (in terms of
// only writing one vector component, and not a full vector) by using the `set`
// accessor, and we need to be "aggressive" in turning the lvalue `foo[a]` into
// an address.
//
// TODO: realistically IR lowering is too early to be binding to this choice,
// because different accessors might be supported on different targets.
//
enum class TryGetAddressMode
{
    Default,
    Aggressive,
};

/// Try to coerce `inVal` into a `LoweredValInfo::ptr()` with a simple address.
LoweredValInfo tryGetAddress(
    IRGenContext*           context,
    LoweredValInfo const&   inVal,
    TryGetAddressMode       mode);

    /// Represents the "direction" that a parameter is being passed (e.g., `in` or `out`
enum ParameterDirection
{
    kParameterDirection_In,     ///< Copy in
    kParameterDirection_Out,    ///< Copy out
    kParameterDirection_InOut,  ///< Copy in, copy out
    kParameterDirection_Ref,    ///< By-reference
};

    /// Compute the direction for a parameter based on its declaration
ParameterDirection getParameterDirection(VarDeclBase* paramDecl)
{
    if( paramDecl->hasModifier<RefModifier>() )
    {
        // The AST specified `ref`:
        return kParameterDirection_Ref;
    }
    if( paramDecl->hasModifier<InOutModifier>() )
    {
        // The AST specified `inout`:
        return kParameterDirection_InOut;
    }
    if (paramDecl->hasModifier<OutModifier>())
    {
        // We saw an `out` modifier, so now we need
        // to check if there was a paired `in`.
        if(paramDecl->hasModifier<InModifier>())
            return kParameterDirection_InOut;
        else
            return kParameterDirection_Out;
    }
    else
    {
        // No direction modifier, or just `in`:
        return kParameterDirection_In;
    }
}

    /// Compute the direction for a `this` parameter based on the declaration of its parent function
ParameterDirection getThisParamDirection(Decl* parentDecl)
{
    // Applications can opt in to a mutable `this` parameter,
    // by applying the `[mutating]` attribute to their
    // declaration.
    //
    if( parentDecl->hasModifier<MutatingAttribute>() )
    {
        return kParameterDirection_InOut;
    }

    // TODO: If/when we support user-defined subscripts or properties,
    // we should probably make the `set` accessor on those default to
    // `[mutating]` rather than require users to specify it. There
    // might need to be a `[nonmutating]` modifier for the rare case
    // where a user wants to opt out.

    // For now we make any `this` parameter default to `in`.
    //
    return kParameterDirection_In;
}

DeclRef<Decl> createDefaultSpecializedDeclRefImpl(IRGenContext* context, Decl* decl)
{
    DeclRef<Decl> declRef;
    declRef.decl = decl;
    declRef.substitutions = createDefaultSubstitutions(context->astBuilder, decl);
    return declRef;
}
//
// The client should actually call the templated wrapper, to preserve type information.
template<typename D>
DeclRef<D> createDefaultSpecializedDeclRef(IRGenContext* context, D* decl)
{
    DeclRef<Decl> declRef = createDefaultSpecializedDeclRefImpl(context, decl);
    return declRef.as<D>();
}

    /// Get the type of the `this` parameter introduced by `parentDeclRef`, or null.
    ///
    /// E.g., if `parentDeclRef` is a `struct` declaration, then this will
    /// return the type of that `struct`.
    ///
    /// If this function is called on a declaration that does not itself directly
    /// introduce a notion of `this`, then null will be returned. Note that this
    /// includes things like function declarations themselves, which inherit the
    /// definition of `this` from their parent/outer declaration.
    ///
RefPtr<Type> getThisParamTypeForContainer(
    IRGenContext*   context,
    DeclRef<Decl>   parentDeclRef)
{
    if( auto aggTypeDeclRef = parentDeclRef.as<AggTypeDecl>() )
    {
        return DeclRefType::create(context->astBuilder, aggTypeDeclRef);
    }
    else if( auto extensionDeclRef = parentDeclRef.as<ExtensionDecl>() )
    {
        return getTargetType(context->astBuilder, extensionDeclRef);
    }

    return nullptr;
}

RefPtr<Type> getThisParamTypeForCallable(
    IRGenContext*   context,
    DeclRef<Decl>   callableDeclRef)
{
    auto parentDeclRef = callableDeclRef.getParent();

    if(auto subscriptDeclRef = parentDeclRef.as<SubscriptDecl>())
        parentDeclRef = subscriptDeclRef.getParent();

    if(auto genericDeclRef = parentDeclRef.as<GenericDecl>())
        parentDeclRef = genericDeclRef.getParent();

    return getThisParamTypeForContainer(context, parentDeclRef);
}


//

template<typename Derived>
struct ExprLoweringVisitorBase : ExprVisitor<Derived, LoweredValInfo>
{
    IRGenContext* context;

    IRBuilder* getBuilder() { return context->irBuilder; }
    ASTBuilder* getASTBuilder() { return context->astBuilder; }

    // Lower an expression that should have the same l-value-ness
    // as the visitor itself.
    LoweredValInfo lowerSubExpr(Expr* expr)
    {
        IRBuilderSourceLocRAII sourceLocInfo(getBuilder(), expr->loc);
        return this->dispatch(expr);
    }


    LoweredValInfo visitVarExpr(VarExpr* expr)
    {
        LoweredValInfo info = emitDeclRef(
            context,
            expr->declRef,
            lowerType(context, expr->type));
        return info;
    }

    LoweredValInfo visitOverloadedExpr(OverloadedExpr* /*expr*/)
    {
        SLANG_UNEXPECTED("overloaded expressions should not occur in checked AST");
        UNREACHABLE_RETURN(LoweredValInfo());
    }

    LoweredValInfo visitOverloadedExpr2(OverloadedExpr2* /*expr*/)
    {
        SLANG_UNEXPECTED("overloaded expressions should not occur in checked AST");
        UNREACHABLE_RETURN(LoweredValInfo());
    }

    LoweredValInfo visitIndexExpr(IndexExpr* expr)
    {
        auto type = lowerType(context, expr->type);
        auto baseVal = lowerSubExpr(expr->baseExpression);
        auto indexVal = getSimpleVal(context, lowerRValueExpr(context, expr->indexExpression));

        return subscriptValue(type, baseVal, indexVal);
    }

    LoweredValInfo visitThisExpr(ThisExpr* /*expr*/)
    {
        return context->thisVal;
    }

    LoweredValInfo visitMemberExpr(MemberExpr* expr)
    {
        auto loweredType = lowerType(context, expr->type);
        auto loweredBase = lowerRValueExpr(context, expr->baseExpression);

        auto declRef = expr->declRef;
        if (auto fieldDeclRef = declRef.as<VarDecl>())
        {
            // Okay, easy enough: we have a reference to a field of a struct type...
            return extractField(loweredType, loweredBase, fieldDeclRef);
        }
        else if (auto callableDeclRef = declRef.as<CallableDecl>())
        {
            RefPtr<BoundMemberInfo> boundMemberInfo = new BoundMemberInfo();
            boundMemberInfo->type = nullptr;
            boundMemberInfo->base = loweredBase;
            boundMemberInfo->declRef = callableDeclRef;
            return LoweredValInfo::boundMember(boundMemberInfo);
        }
        else if(auto constraintDeclRef = declRef.as<TypeConstraintDecl>())
        {
            // The code is making use of a "witness" that a value of
            // some generic type conforms to an interface.
            //
            // For now we will just emit the base expression as-is.
            // TODO: we may need to insert an explicit instruction
            // for a cast here (that could become a no-op later).
            return loweredBase;
        }

        SLANG_UNIMPLEMENTED_X("codegen for subscript expression");
        UNREACHABLE_RETURN(LoweredValInfo());
    }

    // We will always lower a dereference expression (`*ptr`)
    // as an l-value, since that is the easiest way to handle it.
    LoweredValInfo visitDerefExpr(DerefExpr* expr)
    {
        auto loweredBase = lowerRValueExpr(context, expr->base);

        // TODO: handle tupel-type for `base`

        // The type of the lowered base must by some kind of pointer,
        // in order for a dereference to make senese, so we just
        // need to extract the value type from that pointer here.
        //
        IRInst* loweredBaseVal = getSimpleVal(context, loweredBase);
        IRType* loweredBaseType = loweredBaseVal->getDataType();

        if (as<IRPointerLikeType>(loweredBaseType)
            || as<IRPtrTypeBase>(loweredBaseType))
        {
            // Note that we do *not* perform an actual `load` operation
            // here, but rather just use the pointer value to construct
            // an appropriate `LoweredValInfo` representing the underlying
            // dereference.
            //
            // This is important so that an expression like `&((*foo).bar)`
            // (which is desugared from `&foo->bar`) can be handled; such
            // an expression does *not* perform a dereference at runtime,
            // and is just a bit of pointer math.
            //
            return LoweredValInfo::ptr(loweredBaseVal);
        }
        else
        {
            SLANG_UNIMPLEMENTED_X("codegen for deref expression");
            UNREACHABLE_RETURN(LoweredValInfo());
        }
    }

    LoweredValInfo visitParenExpr(ParenExpr* expr)
    {
        return lowerSubExpr(expr->base);
    }

    LoweredValInfo getSimpleDefaultVal(IRType* type)
    {
        if(auto basicType = as<IRBasicType>(type))
        {
            switch( basicType->getBaseType() )
            {
            default:
                SLANG_UNEXPECTED("missing case for getting IR default value");
                UNREACHABLE_RETURN(LoweredValInfo());
                break;

            case BaseType::Bool:
                return LoweredValInfo::simple(getBuilder()->getBoolValue(false));

            case BaseType::Int8:
            case BaseType::Int16:
            case BaseType::Int:
            case BaseType::Int64:
            case BaseType::UInt8:
            case BaseType::UInt16:
            case BaseType::UInt:
            case BaseType::UInt64:
                return LoweredValInfo::simple(getBuilder()->getIntValue(type, 0));

            case BaseType::Half:
            case BaseType::Float:
            case BaseType::Double:
                return LoweredValInfo::simple(getBuilder()->getFloatValue(type, 0.0));
            }
        }

        SLANG_UNEXPECTED("missing case for getting IR default value");
        UNREACHABLE_RETURN(LoweredValInfo());
    }

    LoweredValInfo getDefaultVal(Type* type)
    {
        auto irType = lowerType(context, type);
        if (auto basicType = as<BasicExpressionType>(type))
        {
            return getSimpleDefaultVal(irType);
        }
        else if (auto vectorType = as<VectorExpressionType>(type))
        {
            UInt elementCount = (UInt) getIntVal(vectorType->elementCount);

            auto irDefaultValue = getSimpleVal(context, getDefaultVal(vectorType->elementType));

            List<IRInst*> args;
            for(UInt ee = 0; ee < elementCount; ++ee)
            {
                args.add(irDefaultValue);
            }
            return LoweredValInfo::simple(
                getBuilder()->emitMakeVector(irType, args.getCount(), args.getBuffer()));
        }
        else if (auto matrixType = as<MatrixExpressionType>(type))
        {
            UInt rowCount = (UInt) getIntVal(matrixType->getRowCount());

            auto rowType = matrixType->getRowType();

            auto irDefaultValue = getSimpleVal(context, getDefaultVal(rowType));

            List<IRInst*> args;
            for(UInt rr = 0; rr < rowCount; ++rr)
            {
                args.add(irDefaultValue);
            }
            return LoweredValInfo::simple(
                getBuilder()->emitMakeMatrix(irType, args.getCount(), args.getBuffer()));
        }
        else if (auto arrayType = as<ArrayExpressionType>(type))
        {
            UInt elementCount = (UInt) getIntVal(arrayType->arrayLength);

            auto irDefaultElement = getSimpleVal(context, getDefaultVal(arrayType->baseType));

            List<IRInst*> args;
            for(UInt ee = 0; ee < elementCount; ++ee)
            {
                args.add(irDefaultElement);
            }

            return LoweredValInfo::simple(
                getBuilder()->emitMakeArray(irType, args.getCount(), args.getBuffer()));
        }
        else if (auto declRefType = as<DeclRefType>(type))
        {
            DeclRef<Decl> declRef = declRefType->declRef;
            if (auto aggTypeDeclRef = declRef.as<AggTypeDecl>())
            {
                List<IRInst*> args;
                for (auto ff : getMembersOfType<VarDecl>(aggTypeDeclRef, MemberFilterStyle::Instance))
                {
                    auto irFieldVal = getSimpleVal(context, getDefaultVal(ff));
                    args.add(irFieldVal);
                }

                return LoweredValInfo::simple(
                    getBuilder()->emitMakeStruct(irType, args.getCount(), args.getBuffer()));
            }
        }

        SLANG_UNEXPECTED("unexpected type when creating default value");
        UNREACHABLE_RETURN(LoweredValInfo());
    }

    LoweredValInfo getDefaultVal(VarDeclBase* decl)
    {
        if(auto initExpr = decl->initExpr)
        {
            return lowerRValueExpr(context, initExpr);
        }
        else
        {
            return getDefaultVal(decl->type);
        }
    }

    LoweredValInfo visitInitializerListExpr(InitializerListExpr* expr)
    {
        // Allocate a temporary of the given type
        auto type = expr->type;
        IRType* irType = lowerType(context, type);
        List<IRInst*> args;

        UInt argCount = expr->args.getCount();

        // If the initializer list was empty, then the user was
        // asking for default initialization, which should apply
        // to (almost) any type.
        //
        if(argCount == 0)
        {
            return getDefaultVal(type.type);
        }

        // Now for each argument in the initializer list,
        // fill in the appropriate field of the result
        if (auto arrayType = as<ArrayExpressionType>(type))
        {
            UInt elementCount = (UInt) getIntVal(arrayType->arrayLength);

            for (UInt ee = 0; ee < argCount; ++ee)
            {
                auto argExpr = expr->args[ee];
                LoweredValInfo argVal = lowerRValueExpr(context, argExpr);
                args.add(getSimpleVal(context, argVal));
            }
            if(elementCount > argCount)
            {
                auto irDefaultValue = getSimpleVal(context, getDefaultVal(arrayType->baseType));
                for(UInt ee = argCount; ee < elementCount; ++ee)
                {
                    args.add(irDefaultValue);
                }
            }

            return LoweredValInfo::simple(
                getBuilder()->emitMakeArray(irType, args.getCount(), args.getBuffer()));
        }
        else if (auto vectorType = as<VectorExpressionType>(type))
        {
            UInt elementCount = (UInt) getIntVal(vectorType->elementCount);

            for (UInt ee = 0; ee < argCount; ++ee)
            {
                auto argExpr = expr->args[ee];
                LoweredValInfo argVal = lowerRValueExpr(context, argExpr);
                args.add(getSimpleVal(context, argVal));
            }
            if(elementCount > argCount)
            {
                auto irDefaultValue = getSimpleVal(context, getDefaultVal(vectorType->elementType));
                for(UInt ee = argCount; ee < elementCount; ++ee)
                {
                    args.add(irDefaultValue);
                }
            }

            return LoweredValInfo::simple(
                getBuilder()->emitMakeVector(irType, args.getCount(), args.getBuffer()));
        }
        else if (auto matrixType = as<MatrixExpressionType>(type))
        {
            UInt rowCount = (UInt) getIntVal(matrixType->getRowCount());

            for (UInt rr = 0; rr < argCount; ++rr)
            {
                auto argExpr = expr->args[rr];
                LoweredValInfo argVal = lowerRValueExpr(context, argExpr);
                args.add(getSimpleVal(context, argVal));
            }
            if(rowCount > argCount)
            {
                auto rowType = matrixType->getRowType();
                auto irDefaultValue = getSimpleVal(context, getDefaultVal(rowType));

                for(UInt rr = argCount; rr < rowCount; ++rr)
                {
                    args.add(irDefaultValue);
                }
            }

            return LoweredValInfo::simple(
                getBuilder()->emitMakeMatrix(irType, args.getCount(), args.getBuffer()));
        }
        else if (auto declRefType = as<DeclRefType>(type))
        {
            DeclRef<Decl> declRef = declRefType->declRef;
            if (auto aggTypeDeclRef = declRef.as<AggTypeDecl>())
            {
                UInt argCounter = 0;
                for (auto ff : getMembersOfType<VarDecl>(aggTypeDeclRef, MemberFilterStyle::Instance))
                {
                    UInt argIndex = argCounter++;
                    if (argIndex < argCount)
                    {
                        auto argExpr = expr->args[argIndex];
                        LoweredValInfo argVal = lowerRValueExpr(context, argExpr);
                        args.add(getSimpleVal(context, argVal));
                    }
                    else
                    {
                        auto irDefaultValue = getSimpleVal(context, getDefaultVal(ff));
                        args.add(irDefaultValue);
                    }
                }

                return LoweredValInfo::simple(
                    getBuilder()->emitMakeStruct(irType, args.getCount(), args.getBuffer()));
            }
        }

        // If none of the above cases matched, then we had better
        // have zero arguments in the initializer list, in which
        // case we are just looking for default initialization.
        //
        SLANG_UNEXPECTED("unhandled case for initializer list codegen");
        UNREACHABLE_RETURN(LoweredValInfo());
    }

    LoweredValInfo visitBoolLiteralExpr(BoolLiteralExpr* expr)
    {
        return LoweredValInfo::simple(context->irBuilder->getBoolValue(expr->value));
    }

    LoweredValInfo visitIntegerLiteralExpr(IntegerLiteralExpr* expr)
    {
        auto type = lowerType(context, expr->type);
        return LoweredValInfo::simple(context->irBuilder->getIntValue(type, expr->value));
    }

    LoweredValInfo visitFloatingPointLiteralExpr(FloatingPointLiteralExpr* expr)
    {
        auto type = lowerType(context, expr->type);
        return LoweredValInfo::simple(context->irBuilder->getFloatValue(type, expr->value));
    }

    LoweredValInfo visitStringLiteralExpr(StringLiteralExpr* expr)
    {
        auto irLit = context->irBuilder->getStringValue(expr->value.getUnownedSlice());
        context->shared->m_stringLiterals.add(irLit);
        return LoweredValInfo::simple(irLit);
    }

    LoweredValInfo visitAggTypeCtorExpr(AggTypeCtorExpr* /*expr*/)
    {
        SLANG_UNIMPLEMENTED_X("codegen for aggregate type constructor expression");
        UNREACHABLE_RETURN(LoweredValInfo());
    }

    // After a call to a function with `out` or `in out`
    // parameters, we may need to copy data back into
    // the l-value locations used for output arguments.
    //
    // During lowering of the argument list, we build
    // up a list of these "fixup" assignments that need
    // to be performed.
    struct OutArgumentFixup
    {
        LoweredValInfo dst;
        LoweredValInfo src;
    };

        /// Add argument(s) corresponding to one parameter to a call
        ///
        /// The `argExpr` is the AST-level expression being passed as an argument to the call.
        /// The `paramType` and `paramDirection` represent what is known about the receiving
        /// parameter of the callee (e.g., if the parameter `in`, `inout`, etc.).
        /// The `ioArgs` array receives the IR-level argument(s) that are added for the given
        /// argument expression.
        /// The `ioFixups` array receives any "fixup" code that needs to be run *after* the
        /// call completes (e.g., to move from a scratch variable used for an `inout` argument back
        /// into the original location).
        ///
    void addCallArgsForParam(
        IRType*                 paramType,
        ParameterDirection      paramDirection,
        Expr*                   argExpr,
        List<IRInst*>*          ioArgs,
        List<OutArgumentFixup>* ioFixups)
    {
        switch(paramDirection)
        {
        case kParameterDirection_Ref:
            {
                // A `ref` qualified parameter must be implemented with by-reference
                // parameter passing, so the argument value should be lowered as
                // an l-value.
                //
                LoweredValInfo loweredArg = lowerLValueExpr(context, argExpr);

                // According to our "calling convention" we need to
                // pass a pointer into the callee. Unlike the case for
                // `out` and `inout` below, it is never valid to do
                // copy-in/copy-out for a `ref` parameter, so we just
                // pass in the actual pointer.
                //
                IRInst* argPtr = getAddress(context, loweredArg, argExpr->loc);
                (*ioArgs).add(argPtr);
            }
            break;

        case kParameterDirection_Out:
        case kParameterDirection_InOut:
            {
                // This is a `out` or `inout` parameter, and so
                // the argument must be lowered as an l-value.

                LoweredValInfo loweredArg = lowerLValueExpr(context, argExpr);

                // According to our "calling convention" we need to
                // pass a pointer into the callee.
                //
                // A naive approach would be to just take the address
                // of `loweredArg` above and pass it in, but that
                // has two issues:
                //
                // 1. The l-value might not be something that has a single
                //    well-defined "address" (e.g., `foo.xzy`).
                //
                // 2. The l-value argument might actually alias some other
                //    storage that the callee will access (e.g., we are
                //    passing in a global variable, or two `out` parameters
                //    are being passed the same location in an array).
                //
                // In each of these cases, the safe option is to create
                // a temporary variable to use for argument-passing,
                // and then do copy-in/copy-out around the call.
                //
                // TODO: We should consider ruling out case (2) as undefined
                // behavior, and specify that whether `inout` and `out` are
                // handled via copy-in-copy-out or by-reference parameter
                // passing is an implementation detail. That would allow
                // us to avoid introducing a copy except where it is required
                // for the semantics of (1).

                LoweredValInfo tempVar = createVar(context, paramType);

                // If the parameter is `in out` or `inout`, then we need
                // to ensure that we pass in the original value stored
                // in the argument, which we accomplish by assigning
                // from the l-value to our temp.
                if(paramDirection == kParameterDirection_InOut)
                {
                    assign(context, tempVar, loweredArg);
                }

                // Now we can pass the address of the temporary variable
                // to the callee as the actual argument for the `in out`
                SLANG_ASSERT(tempVar.flavor == LoweredValInfo::Flavor::Ptr);
                (*ioArgs).add(tempVar.val);

                // Finally, after the call we will need
                // to copy in the other direction: from our
                // temp back to the original l-value.
                OutArgumentFixup fixup;
                fixup.src = tempVar;
                fixup.dst = loweredArg;

                (*ioFixups).add(fixup);

            }
            break;

        default:
            {
                // This is a pure input parameter, and so we will
                // pass it as an r-value.
                LoweredValInfo loweredArg = lowerRValueExpr(context, argExpr);
                addArgs(context, ioArgs, loweredArg);
            }
            break;
        }
    }

    void addDirectCallArgs(
        InvokeExpr*             expr,
        DeclRef<CallableDecl>   funcDeclRef,
        List<IRInst*>*         ioArgs,
        List<OutArgumentFixup>* ioFixups)
    {
        UInt argCount = expr->arguments.getCount();
        UInt argCounter = 0;
        for (auto paramDeclRef : getMembersOfType<ParamDecl>(funcDeclRef))
        {
            auto paramDecl = paramDeclRef.getDecl();
            IRType* paramType = lowerType(context, getType(getASTBuilder(), paramDeclRef));
            auto paramDirection = getParameterDirection(paramDecl);

            UInt argIndex = argCounter++;
            RefPtr<Expr> argExpr;
            if(argIndex < argCount)
            {
                argExpr = expr->arguments[argIndex];
            }
            else
            {
                // We have run out of arguments supplied at the call site,
                // but there are still parameters remaining. This must mean
                // that these parameters have default argument expressions
                // associated with them.
                argExpr = getInitExpr(getASTBuilder(), paramDeclRef);

                // Assert that such an expression must have been present.
                SLANG_ASSERT(argExpr);

                // TODO: The approach we are taking here to default arguments
                // is simplistic, and has consequences for the front-end as
                // well as binary serialization of modules.
                //
                // We could consider some more refined approaches where, e.g.,
                // functions with default arguments generate multiple IR-level
                // functions, that compute and provide the default values.
                //
                // Alternatively, each parameter with defaults could be generated
                // into its own callable function that provides the default value,
                // so that calling modules can call into a pre-generated function.
                //
                // Each of these options involves trade-offs, and we need to
                // make a conscious decision at some point.
            }

            addCallArgsForParam(paramType, paramDirection, argExpr, ioArgs, ioFixups);
        }
    }

    // Add arguments that appeared directly in an argument list
    // to the list of argument values for a call.
    void addDirectCallArgs(
        InvokeExpr*             expr,
        DeclRef<Decl>           funcDeclRef,
        List<IRInst*>*         ioArgs,
        List<OutArgumentFixup>* ioFixups)
    {
        if (auto callableDeclRef = funcDeclRef.as<CallableDecl>())
        {
            addDirectCallArgs(expr, callableDeclRef, ioArgs, ioFixups);
        }
        else
        {
            SLANG_UNEXPECTED("callee was not a callable decl");
        }
    }

    void addFuncBaseArgs(
        LoweredValInfo funcVal,
        List<IRInst*>* ioArgs)
    {
        switch (funcVal.flavor)
        {
        default:
            return;
        }
    }

    void applyOutArgumentFixups(List<OutArgumentFixup> const& fixups)
    {
        for (auto fixup : fixups)
        {
            assign(context, fixup.dst, fixup.src);
        }
    }

    struct ResolvedCallInfo
    {
        DeclRef<Decl>   funcDeclRef;
        Expr*           baseExpr = nullptr;
    };

    // Try to resolve a the function expression for a call
    // into a reference to a specific declaration, along
    // with some contextual information about the declaration
    // we are calling.
    bool tryResolveDeclRefForCall(
        RefPtr<Expr>        funcExpr,
        ResolvedCallInfo*   outInfo)
    {
        // TODO: unwrap any "identity" expressions that might
        // be wrapping the callee.

        // First look to see if the expression references a
        // declaration at all.
        auto declRefExpr = as<DeclRefExpr>(funcExpr);
        if(!declRefExpr)
            return false;

        // A little bit of future proofing here: if we ever
        // allow higher-order functions, then we might be
        // calling through a variable/field that has a function
        // type, but is not itself a function.
        // In such a case we should be careful to not statically
        // resolve things.
        //
        if(auto callableDecl = as<CallableDecl>(declRefExpr->declRef.getDecl()))
        {
            // Okay, the declaration is directly callable, so we can continue.
        }
        else
        {
            // The callee declaration isn't itself a callable (it must have
            // a function type, though).
            return false;
        }

        // Now we can look at the specific kinds of declaration references,
        // and try to tease them apart.
        if (auto memberFuncExpr = as<MemberExpr>(funcExpr))
        {
            outInfo->funcDeclRef = memberFuncExpr->declRef;
            outInfo->baseExpr = memberFuncExpr->baseExpression;
            return true;
        }
        else if (auto staticMemberFuncExpr = as<StaticMemberExpr>(funcExpr))
        {
            outInfo->funcDeclRef = staticMemberFuncExpr->declRef;
            return true;
        }
        else if (auto varExpr = as<VarExpr>(funcExpr))
        {
            outInfo->funcDeclRef = varExpr->declRef;
            return true;
        }
        else
        {
            // Seems to be a case of declaration-reference we don't know about.
            SLANG_UNEXPECTED("unknown declaration reference kind");
            return false;
        }
    }


    LoweredValInfo visitInvokeExpr(InvokeExpr* expr)
    {
        auto type = lowerType(context, expr->type);

        // We are going to look at the syntactic form of
        // the "function" expression, so that we can avoid
        // a lot of complexity that would come from lowering
        // it as a general expression first, and then trying
        // to apply it. For example, given `obj.f(a,b)` we
        // will try to detect that we are trying to compute
        // something like `ObjType::f(obj, a, b)` (in pseudo-code),
        // rather than trying to construct a meaningful
        // intermediate value for `obj.f` first.
        //
        // Note that this doe not preclude having support
        // for directly generating code from `obj.f` - it
        // just may be that such usage is more complicated.

        // Along the way, we may end up collecting additional
        // arguments that will be part of the call.
        List<IRInst*> irArgs;

        // We will also collect "fixup" actions that need
        // to be performed after the call, in order to
        // copy the final values for `out` parameters
        // back to their arguments.
        List<OutArgumentFixup> argFixups;

        auto funcExpr = expr->functionExpr;
        ResolvedCallInfo resolvedInfo;
        if( tryResolveDeclRefForCall(funcExpr, &resolvedInfo) )
        {
            // In this case we know exactly what declaration we
            // are going to call, and so we can resolve things
            // appropriately.
            auto funcDeclRef = resolvedInfo.funcDeclRef;
            auto baseExpr = resolvedInfo.baseExpr;

            // First comes the `this` argument if we are calling
            // a member function:
            if( baseExpr )
            {
                auto thisType = getThisParamTypeForCallable(context, funcDeclRef);
                auto irThisType = lowerType(context, thisType);
                addCallArgsForParam(
                    irThisType,
                    getThisParamDirection(funcDeclRef.getDecl()),
                    baseExpr,
                    &irArgs,
                    &argFixups);
            }

            // Then we have the "direct" arguments to the call.
            // These may include `out` and `inout` arguments that
            // require "fixup" work on the other side.
            //
            auto funcType = lowerType(context, funcExpr->type);
            addDirectCallArgs(expr, funcDeclRef, &irArgs, &argFixups);
            auto result = emitCallToDeclRef(
                context,
                type,
                funcDeclRef,
                funcType,
                irArgs);
            applyOutArgumentFixups(argFixups);
            return result;
        }

        // TODO: In this case we should be emitting code for the callee as
        // an ordinary expression, then emitting the arguments according
        // to the type information on the callee (e.g., which parameters
        // are `out` or `inout`, and then finally emitting the `call`
        // instruction.
        //
        // We don't currently have the case of emitting arguments according
        // to function type info (instead of declaration info), and really
        // this case can't occur unless we start adding first-class functions
        // to the source language.
        //
        // For now we just bail out with an error.
        //
        SLANG_UNEXPECTED("could not resolve target declaration for call");
        UNREACHABLE_RETURN(LoweredValInfo());
    }

    LoweredValInfo visitCastToInterfaceExpr(
        CastToInterfaceExpr* expr)
    {
        // We have an expression that is "up-casting" some concrete value
        // to an existential type (aka interface type), using a subtype witness
        // (which will lower as a witness table) to show that the conversion
        // is valid.
        //
        // At the IR level, this will become a `makeExistential` instruction,
        // which collects the above information into a single IR-level value.
        // A dynamic CPU implementation of Slang might encode an existential
        // as a "fat pointer" representation, which includes a pointer to
        // data for the concrete value, plus a pointer to the witness table.
        //
        // Note: if/when Slang supports more general existential types, such
        // as compositions of interface (e.g., `IReadable & IWritable`), then
        // we should probably extend the AST and IR mechanism here to accept
        // a sequence of witness tables.
        //
        auto existentialType = lowerType(context, expr->type);
        auto concreteValue = getSimpleVal(context, lowerRValueExpr(context, expr->valueArg));
        auto witnessTable = lowerSimpleVal(context, expr->witnessArg);
        auto existentialValue = getBuilder()->emitMakeExistential(existentialType, concreteValue, witnessTable);
        return LoweredValInfo::simple(existentialValue);
    }

    LoweredValInfo subscriptValue(
        IRType*         type,
        LoweredValInfo  baseVal,
        IRInst*         indexVal)
    {
        auto builder = getBuilder();

        // The `tryGetAddress` operation will take a complex value representation
        // and try to turn it into a single pointer, if possible.
        //
        baseVal = tryGetAddress(context, baseVal, TryGetAddressMode::Aggressive);

        // The `materialize` operation should ensure that we only have to deal
        // with the small number of base cases for lowered value representations.
        //
        baseVal = materialize(context, baseVal);

        switch (baseVal.flavor)
        {
        case LoweredValInfo::Flavor::Simple:
            return LoweredValInfo::simple(
                builder->emitElementExtract(
                    type,
                    getSimpleVal(context, baseVal),
                    indexVal));

        case LoweredValInfo::Flavor::Ptr:
            return LoweredValInfo::ptr(
                builder->emitElementAddress(
                    context->irBuilder->getPtrType(type),
                    baseVal.val,
                    indexVal));

        default:
            SLANG_UNIMPLEMENTED_X("subscript expr");
            UNREACHABLE_RETURN(LoweredValInfo());
        }

    }

    LoweredValInfo extractField(
        IRType*             fieldType,
        LoweredValInfo      base,
        DeclRef<VarDecl>    field)
    {
        return Slang::extractField(context, fieldType, base, field);
    }

    LoweredValInfo visitStaticMemberExpr(StaticMemberExpr* expr)
    {
        return emitDeclRef(context, expr->declRef,
            lowerType(context, expr->type));
    }

    LoweredValInfo visitGenericAppExpr(GenericAppExpr* /*expr*/)
    {
        SLANG_UNIMPLEMENTED_X("generic application expression during code generation");
        UNREACHABLE_RETURN(LoweredValInfo());
    }

    LoweredValInfo visitSharedTypeExpr(SharedTypeExpr* /*expr*/)
    {
        SLANG_UNIMPLEMENTED_X("shared type expression during code generation");
        UNREACHABLE_RETURN(LoweredValInfo());
    }

    LoweredValInfo visitTaggedUnionTypeExpr(TaggedUnionTypeExpr* /*expr*/)
    {
        SLANG_UNIMPLEMENTED_X("tagged union type expression during code generation");
        UNREACHABLE_RETURN(LoweredValInfo());
    }

    LoweredValInfo visitThisTypeExpr(ThisTypeExpr* /*expr*/)
    {
        SLANG_UNIMPLEMENTED_X("this-type expression during code generation");
        UNREACHABLE_RETURN(LoweredValInfo());
    }

    LoweredValInfo visitAssignExpr(AssignExpr* expr)
    {
        // Because our representation of lowered "values"
        // can encompass l-values explicitly, we can
        // lower assignment easily. We just lower the left-
        // and right-hand sides, and then perform an assignment
        // based on the resulting values.
        //
        auto leftVal = lowerLValueExpr(context, expr->left);
        auto rightVal = lowerRValueExpr(context, expr->right);
        assign(context, leftVal, rightVal);

        // The result value of the assignment expression is
        // the value of the left-hand side (and it is expected
        // to be an l-value).
        return leftVal;
    }

    LoweredValInfo visitLetExpr(LetExpr* expr)
    {
        // TODO: deal with the case where we might want to capture
        // a reference to the bound value...

        auto initVal = lowerLValueExpr(context, expr->decl->initExpr);
        setGlobalValue(context, expr->decl, initVal);
        auto bodyVal = lowerSubExpr(expr->body);
        return bodyVal;
    }

    LoweredValInfo visitExtractExistentialValueExpr(ExtractExistentialValueExpr* expr)
    {
        auto existentialType = lowerType(context, getType(getASTBuilder(), expr->declRef));
        auto existentialVal = getSimpleVal(context, emitDeclRef(context, expr->declRef, existentialType));

        auto openedType = lowerType(context, expr->type);

        return LoweredValInfo::simple(getBuilder()->emitExtractExistentialValue(openedType, existentialVal));
    }
};

struct LValueExprLoweringVisitor : ExprLoweringVisitorBase<LValueExprLoweringVisitor>
{
    // When visiting a swizzle expression in an l-value context,
    // we need to construct a "swizzled l-value."
    LoweredValInfo visitMatrixSwizzleExpr(MatrixSwizzleExpr*)
    {
        SLANG_UNIMPLEMENTED_X("matrix swizzle lvalue case");
    }

    // When visiting a swizzle expression in an l-value context,
    // we need to construct a "sizzled l-value."
    LoweredValInfo visitSwizzleExpr(SwizzleExpr* expr)
    {
        auto irType = lowerType(context, expr->type);
        auto loweredBase = lowerRValueExpr(context, expr->base);

        RefPtr<SwizzledLValueInfo> swizzledLValue = new SwizzledLValueInfo();
        swizzledLValue->type = irType;

        UInt elementCount = (UInt)expr->elementCount;
        swizzledLValue->elementCount = elementCount;

        // As a small optimization, we will detect if the base expression
        // has also lowered into a swizzle and only return a single
        // swizzle instead of nested swizzles.
        //
        // E.g., if we have input like `foo[i].zw.y` we should optimize it
        // down to just `foo[i].w`.
        //
        if(loweredBase.flavor == LoweredValInfo::Flavor::SwizzledLValue)
        {
            auto baseSwizzleInfo = loweredBase.getSwizzledLValueInfo();

            // Our new swizzle will use the same base expression (e.g.,
            // `foo[i]` in our example above), but will need to remap
            // the swizzle indices it uses.
            //
            swizzledLValue->base = baseSwizzleInfo->base;
            for (UInt ii = 0; ii < elementCount; ++ii)
            {
                // First we get the swizzle element of the "outer" swizzle,
                // as it was written by the user. In our running example of
                // `foo[i].zw.y` this is the `y` element reference.
                //
                UInt originalElementIndex = UInt(expr->elementIndices[ii]);

                // Next we will use that original element index to figure
                // out which of the elements of the original swizzle this
                // should map to.
                //
                // In our example, `y` means index 1, and so we fetch
                // element 1 from the inner swizzle sequence `zw`, to get `w`.
                //
                SLANG_ASSERT(originalElementIndex < baseSwizzleInfo->elementCount);
                UInt remappedElementIndex = baseSwizzleInfo->elementIndices[originalElementIndex];

                swizzledLValue->elementIndices[ii] = remappedElementIndex;
            }
        }
        else
        {
            // In the default case, we can just copy the indices being
            // used for the swizzle over directly from the expression,
            // and use the base as-is.
            //
            swizzledLValue->base = loweredBase;
            for (UInt ii = 0; ii < elementCount; ++ii)
            {
                swizzledLValue->elementIndices[ii] = (UInt) expr->elementIndices[ii];
            }
        }

        context->shared->extValues.add(swizzledLValue);
        return LoweredValInfo::swizzledLValue(swizzledLValue);
    }
};

struct RValueExprLoweringVisitor : ExprLoweringVisitorBase<RValueExprLoweringVisitor>
{
    // A matrix swizzle in an r-value context can save time by just
    // emitting the matrix swizzle instructions directly.
    LoweredValInfo visitMatrixSwizzleExpr(MatrixSwizzleExpr* expr)
    {
        auto resultType = lowerType(context, expr->type);
        auto base = lowerSubExpr(expr->base);
        auto matType = as<MatrixExpressionType>(expr->base->type.type);
        if (!matType)
            SLANG_UNEXPECTED("Expected a matrix type in matrix swizzle");
        auto subscript2 = lowerType(context, matType->getElementType());
        auto subscript1 = lowerType(context, matType->getRowType());

        auto builder = getBuilder();

        auto irIntType = getIntType(context);

        UInt elementCount = (UInt)expr->elementCount;
        IRInst* irExtracts[4];
        for (UInt ii = 0; ii < elementCount; ++ii)
        {
            auto index1 = builder->getIntValue(
                irIntType,
                (IRIntegerValue)expr->elementCoords[ii].row);
            auto index2 = builder->getIntValue(
                irIntType,
                (IRIntegerValue)expr->elementCoords[ii].col);
            // First index expression
            auto irExtract1 = subscriptValue(
                subscript1,
                base,
                index1);
            // Second index expression
            irExtracts[ii] = getSimpleVal(context, subscriptValue(
                subscript2,
                irExtract1,
                index2));
        }
        auto irVector = builder->emitMakeVector(
            resultType,
            elementCount,
            irExtracts
        );

        return LoweredValInfo::simple(irVector);
    }

    // A swizzle in an r-value context can save time by just
    // emitting the swizzle instructions directly.
    LoweredValInfo visitSwizzleExpr(SwizzleExpr* expr)
    {
        auto irType = lowerType(context, expr->type);
        auto irBase = getSimpleVal(context, lowerRValueExpr(context, expr->base));

        auto builder = getBuilder();

        auto irIntType = getIntType(context);

        UInt elementCount = (UInt)expr->elementCount;
        IRInst* irElementIndices[4];
        for (UInt ii = 0; ii < elementCount; ++ii)
        {
            irElementIndices[ii] = builder->getIntValue(
                irIntType,
                (IRIntegerValue)expr->elementIndices[ii]);
        }

        auto irSwizzle = builder->emitSwizzle(
            irType,
            irBase,
            elementCount,
            &irElementIndices[0]);

        return LoweredValInfo::simple(irSwizzle);
    }
};

LoweredValInfo lowerLValueExpr(
    IRGenContext*   context,
    Expr*           expr)
{
    IRBuilderSourceLocRAII sourceLocInfo(context->irBuilder, expr->loc);

    LValueExprLoweringVisitor visitor;
    visitor.context = context;
    return visitor.dispatch(expr);
}

LoweredValInfo lowerRValueExpr(
    IRGenContext*   context,
    Expr*           expr)
{
    IRBuilderSourceLocRAII sourceLocInfo(context->irBuilder, expr->loc);

    RValueExprLoweringVisitor visitor;
    visitor.context = context;
    return visitor.dispatch(expr);
}

struct StmtLoweringVisitor : StmtVisitor<StmtLoweringVisitor>
{
    IRGenContext* context;

    IRBuilder* getBuilder() { return context->irBuilder; }

    void visitEmptyStmt(EmptyStmt*)
    {
        // Nothing to do.
    }

    void visitUnparsedStmt(UnparsedStmt*)
    {
        SLANG_UNEXPECTED("UnparsedStmt not supported by IR");
    }

    void visitCaseStmtBase(CaseStmtBase*)
    {
        SLANG_UNEXPECTED("`case` or `default` not under `switch`");
    }

    void visitCompileTimeForStmt(CompileTimeForStmt* stmt)
    {
        // The user is asking us to emit code for the loop
        // body for each value in the given integer range.
        // For now, we will handle this by repeatedly lowering
        // the body statement, with the loop variable bound
        // to a different integer literal value each time.
        //
        // TODO: eventually we might handle this as just an
        // ordinary loop, with an `[unroll]` attribute on
        // it that we would respect.

        auto rangeBeginVal = getIntVal(stmt->rangeBeginVal);
        auto rangeEndVal = getIntVal(stmt->rangeEndVal);

        if (rangeBeginVal >= rangeEndVal)
            return;

        auto varDecl = stmt->varDecl;
        auto varType = lowerType(context, varDecl->type);

        IRGenEnv subEnvStorage;
        IRGenEnv* subEnv = &subEnvStorage;
        subEnv->outer = context->env;

        IRGenContext subContextStorage = *context;
        IRGenContext* subContext = &subContextStorage;
        subContext->env = subEnv;



        for (IntegerLiteralValue ii = rangeBeginVal; ii < rangeEndVal; ++ii)
        {
            auto constVal = getBuilder()->getIntValue(
                varType,
                ii);

            subEnv->mapDeclToValue[varDecl] = LoweredValInfo::simple(constVal);

            lowerStmt(subContext, stmt->body);
        }
    }

    // Create a basic block in the current function,
    // so that it can be used for a label.
    IRBlock* createBlock()
    {
        return getBuilder()->createBlock();
    }

    /// Does the given block have a terminator?
    bool isBlockTerminated(IRBlock* block)
    {
        return block->getTerminator() != nullptr;
    }

    /// Emit a branch to the target block if the current
    /// block being inserted into is not already terminated.
    void emitBranchIfNeeded(IRBlock* targetBlock)
    {
        auto builder = getBuilder();
        auto currentBlock = builder->getBlock();

        // Don't emit if there is no current block.
        if(!currentBlock)
            return;

        // Don't emit if the block already has a terminator.
        if(isBlockTerminated(currentBlock))
            return;

        // The block is unterminated, so cap it off with
        // a terminator that branches to the target.
        builder->emitBranch(targetBlock);
    }

    /// Insert a block at the current location (ending
    /// the previous block with an unconditional jump
    /// if needed).
    void insertBlock(IRBlock* block)
    {
        auto builder = getBuilder();

        auto prevBlock = builder->getBlock();
        auto parentFunc = prevBlock ? prevBlock->getParent() : builder->getFunc();

        // If the previous block doesn't already have
        // a terminator instruction, then be sure to
        // emit a branch to the new block.
        emitBranchIfNeeded(block);

        // Add the new block to the function we are building,
        // and setit as the block we will be inserting into.
        parentFunc->addBlock(block);
        builder->setInsertInto(block);
    }

    // Start a new block at the current location.
    // This is just the composition of `createBlock`
    // and `insertBlock`.
    IRBlock* startBlock()
    {
        auto block = createBlock();
        insertBlock(block);
        return block;
    }

    /// Start a new block if there isn't a current
    /// block that we can append to.
    ///
    /// The `stmt` parameter is the statement we
    /// are about to emit.
    void startBlockIfNeeded(Stmt* stmt)
    {
        auto builder = getBuilder();
        auto currentBlock = builder->getBlock();

        // If there is a current block and it hasn't
        // been terminated, then we can just use that.
        if(currentBlock && !isBlockTerminated(currentBlock))
        {
            return;
        }

        // We are about to emit code *after* a terminator
        // instruction, and there is no label to allow
        // branching into this code, so whatever we are
        // about to emit is going to be unreachable.
        //
        // Let's diagnose that here just to help the user.
        //
        // TODO: We might want to have a more robust check
        // for unreachable code based on IR analysis instead,
        // at which point we'd probably disable this check.
        //
        context->getSink()->diagnose(stmt, Diagnostics::unreachableCode);

        startBlock();
    }

    void visitIfStmt(IfStmt* stmt)
    {
        auto builder = getBuilder();
        startBlockIfNeeded(stmt);

        auto condExpr = stmt->predicate;
        auto thenStmt = stmt->positiveStatement;
        auto elseStmt = stmt->negativeStatement;

        auto irCond = getSimpleVal(context,
            lowerRValueExpr(context, condExpr));

        if (elseStmt)
        {
            auto thenBlock = createBlock();
            auto elseBlock = createBlock();
            auto afterBlock = createBlock();

            builder->emitIfElse(irCond, thenBlock, elseBlock, afterBlock);

            insertBlock(thenBlock);
            lowerStmt(context, thenStmt);
            emitBranchIfNeeded(afterBlock);

            insertBlock(elseBlock);
            lowerStmt(context, elseStmt);

            insertBlock(afterBlock);
        }
        else
        {
            auto thenBlock = createBlock();
            auto afterBlock = createBlock();

            builder->emitIf(irCond, thenBlock, afterBlock);

            insertBlock(thenBlock);
            lowerStmt(context, thenStmt);

            insertBlock(afterBlock);
        }
    }

    void addLoopDecorations(
        IRInst* inst,
        Stmt*   stmt)
    {
        if( stmt->findModifier<UnrollAttribute>() )
        {
            getBuilder()->addLoopControlDecoration(inst, kIRLoopControl_Unroll);
        }
        // TODO: handle other cases here
    }

    void visitForStmt(ForStmt* stmt)
    {
        auto builder = getBuilder();
        startBlockIfNeeded(stmt);

        // The initializer clause for the statement
        // can always safetly be emitted to the current block.
        if (auto initStmt = stmt->initialStatement)
        {
            lowerStmt(context, initStmt);
        }

        // We will create blocks for the various places
        // we need to jump to inside the control flow,
        // including the blocks that will be referenced
        // by `continue` or `break` statements.
        auto loopHead = createBlock();
        auto bodyLabel = createBlock();
        auto breakLabel = createBlock();
        auto continueLabel = createBlock();

        // Register the `break` and `continue` labels so
        // that we can find them for nested statements.
        context->shared->breakLabels.Add(stmt, breakLabel);
        context->shared->continueLabels.Add(stmt, continueLabel);

        // Emit the branch that will start out loop,
        // and then insert the block for the head.

        auto loopInst = builder->emitLoop(
            loopHead,
            breakLabel,
            continueLabel);

        addLoopDecorations(loopInst, stmt);

        insertBlock(loopHead);

        // Now that we are within the header block, we
        // want to emit the expression for the loop condition:
        if (auto condExpr = stmt->predicateExpression)
        {
            auto irCondition = getSimpleVal(context,
                lowerRValueExpr(context, stmt->predicateExpression));

            // Now we want to `break` if the loop condition is false.
            builder->emitLoopTest(
                irCondition,
                bodyLabel,
                breakLabel);
        }

        // Emit the body of the loop
        insertBlock(bodyLabel);
        lowerStmt(context, stmt->statement);

        // Insert the `continue` block
        insertBlock(continueLabel);
        if (auto incrExpr = stmt->sideEffectExpression)
        {
            lowerRValueExpr(context, incrExpr);
        }

        // At the end of the body we need to jump back to the top.
        emitBranchIfNeeded(loopHead);

        // Finally we insert the label that a `break` will jump to
        insertBlock(breakLabel);
    }

    void visitWhileStmt(WhileStmt* stmt)
    {
        // Generating IR for `while` statement is similar to a
        // `for` statement, but without a lot of the complications.

        auto builder = getBuilder();
        startBlockIfNeeded(stmt);

        // We will create blocks for the various places
        // we need to jump to inside the control flow,
        // including the blocks that will be referenced
        // by `continue` or `break` statements.
        auto loopHead = createBlock();
        auto bodyLabel = createBlock();
        auto breakLabel = createBlock();

        // A `continue` inside a `while` loop always
        // jumps to the head of hte loop.
        auto continueLabel = loopHead;

        // Register the `break` and `continue` labels so
        // that we can find them for nested statements.
        context->shared->breakLabels.Add(stmt, breakLabel);
        context->shared->continueLabels.Add(stmt, continueLabel);

        // Emit the branch that will start out loop,
        // and then insert the block for the head.

        auto loopInst = builder->emitLoop(
            loopHead,
            breakLabel,
            continueLabel);

        addLoopDecorations(loopInst, stmt);

        insertBlock(loopHead);

        // Now that we are within the header block, we
        // want to emit the expression for the loop condition:
        if (auto condExpr = stmt->predicate)
        {
            auto irCondition = getSimpleVal(context,
                lowerRValueExpr(context, condExpr));

            // Now we want to `break` if the loop condition is false.
            builder->emitLoopTest(
                irCondition,
                bodyLabel,
                breakLabel);
        }

        // Emit the body of the loop
        insertBlock(bodyLabel);
        lowerStmt(context, stmt->statement);

        // At the end of the body we need to jump back to the top.
        emitBranchIfNeeded(loopHead);

        // Finally we insert the label that a `break` will jump to
        insertBlock(breakLabel);
    }

    void visitDoWhileStmt(DoWhileStmt* stmt)
    {
        // Generating IR for `do {...} while` statement is similar to a
        // `while` statement, just with the test in a different place

        auto builder = getBuilder();
        startBlockIfNeeded(stmt);

        // We will create blocks for the various places
        // we need to jump to inside the control flow,
        // including the blocks that will be referenced
        // by `continue` or `break` statements.
        auto loopHead = createBlock();
        auto testLabel = createBlock();
        auto breakLabel = createBlock();

        // A `continue` inside a `do { ... } while ( ... )` loop always
        // jumps to the loop test.
        auto continueLabel = testLabel;

        // Register the `break` and `continue` labels so
        // that we can find them for nested statements.
        context->shared->breakLabels.Add(stmt, breakLabel);
        context->shared->continueLabels.Add(stmt, continueLabel);

        // Emit the branch that will start out loop,
        // and then insert the block for the head.

        auto loopInst = builder->emitLoop(
            loopHead,
            breakLabel,
            continueLabel);

        addLoopDecorations(loopInst, stmt);

        insertBlock(loopHead);

        // Emit the body of the loop
        lowerStmt(context, stmt->statement);

        insertBlock(testLabel);

        // Now that we are within the header block, we
        // want to emit the expression for the loop condition:
        if (auto condExpr = stmt->predicate)
        {
            auto irCondition = getSimpleVal(context,
                lowerRValueExpr(context, condExpr));

            // Now we want to `break` if the loop condition is false,
            // otherwise we will jump back to the head of the loop.
            builder->emitLoopTest(
                irCondition,
                loopHead,
                breakLabel);
        }

        // Finally we insert the label that a `break` will jump to
        insertBlock(breakLabel);
    }

    void visitExpressionStmt(ExpressionStmt* stmt)
    {
        startBlockIfNeeded(stmt);

        // The statement evaluates an expression
        // (for side effects, one assumes) and then
        // discards the result. As such, we simply
        // lower the expression, and don't use
        // the result.
        //
        // Note that we lower using the l-value path,
        // so that an expression statement that names
        // a location (but doesn't load from it)
        // will not actually emit a load.
        lowerLValueExpr(context, stmt->expression);
    }

    void visitDeclStmt(DeclStmt* stmt)
    {
        startBlockIfNeeded(stmt);

        // For now, we lower a declaration directly
        // into the current context.
        //
        // TODO: We may want to consider whether
        // nested type/function declarations should
        // be lowered into the global scope during
        // IR generation, or whether they should
        // be lifted later (pushing capture analysis
        // down to the IR).
        //
        lowerDecl(context, stmt->decl);
    }

    void visitSeqStmt(SeqStmt* stmt)
    {
        // To lower a sequence of statements,
        // just lower each in order
        for (auto ss : stmt->stmts)
        {
            lowerStmt(context, ss);
        }
    }

    void visitBlockStmt(BlockStmt* stmt)
    {
        // To lower a block (scope) statement,
        // just lower its body. The IR doesn't
        // need to reflect the scoping of the AST.
        lowerStmt(context, stmt->body);
    }

    void visitReturnStmt(ReturnStmt* stmt)
    {
        startBlockIfNeeded(stmt);

        // A `return` statement turns into a return
        // instruction. If the statement had an argument
        // expression, then we need to lower that to
        // a value first, and then emit the resulting value.
        if( auto expr = stmt->expression )
        {
            auto loweredExpr = lowerRValueExpr(context, expr);

            getBuilder()->emitReturn(getSimpleVal(context, loweredExpr));
        }
        else
        {
            getBuilder()->emitReturn();
        }
    }

    void visitDiscardStmt(DiscardStmt* stmt)
    {
        startBlockIfNeeded(stmt);
        getBuilder()->emitDiscard();
    }

    void visitBreakStmt(BreakStmt* stmt)
    {
        startBlockIfNeeded(stmt);

        // Semantic checking is responsible for finding
        // the statement taht this `break` breaks out of
        auto parentStmt = stmt->parentStmt;
        SLANG_ASSERT(parentStmt);

        // We just need to look up the basic block that
        // corresponds to the break label for that statement,
        // and then emit an instruction to jump to it.
        IRBlock* targetBlock = nullptr;
        context->shared->breakLabels.TryGetValue(parentStmt, targetBlock);
        SLANG_ASSERT(targetBlock);
        getBuilder()->emitBreak(targetBlock);
    }

    void visitContinueStmt(ContinueStmt* stmt)
    {
        startBlockIfNeeded(stmt);

        // Semantic checking is responsible for finding
        // the loop that this `continue` statement continues
        auto parentStmt = stmt->parentStmt;
        SLANG_ASSERT(parentStmt);


        // We just need to look up the basic block that
        // corresponds to the continue label for that statement,
        // and then emit an instruction to jump to it.
        IRBlock* targetBlock = nullptr;
        context->shared->continueLabels.TryGetValue(parentStmt, targetBlock);
        SLANG_ASSERT(targetBlock);
        getBuilder()->emitContinue(targetBlock);
    }

    // Lowering a `switch` statement can get pretty involved,
    // so we need to track a bit of extra data:
    struct SwitchStmtInfo
    {
        // The block that will be made to contain the `switch` statement
        IRBlock* initialBlock = nullptr;

        // The label for the `default` case, if any.
        IRBlock*    defaultLabel = nullptr;

        // The label of the current "active" case block.
        IRBlock*    currentCaseLabel = nullptr;

        // Has anything been emitted to the current "active" case block?
        bool anythingEmittedToCurrentCaseBlock = false;

        // The collected (value, label) pairs for
        // all the `case` statements.
        List<IRInst*>  cases;
    };

    // We need a label to use for a `case` or `default` statement,
    // so either create one here, or re-use the current one if
    // that is okay.
    IRBlock* getLabelForCase(SwitchStmtInfo* info)
    {
        // Look at the "current" label we are working with.
        auto currentCaseLabel = info->currentCaseLabel;

        // If there is a current block, and it is empty,
        // then it is still a viable target (we are in
        // a case of "trivial fall-through" from the previous
        // block).
        if(currentCaseLabel && !info->anythingEmittedToCurrentCaseBlock)
        {
            return currentCaseLabel;
        }

        // Othwerise, we need to start a new block and use that.
        IRBlock* newCaseLabel = createBlock();

        // Note: if the previous block failed
        // to end with a `break`, then inserting
        // this block will append an unconditional
        // branch to the end of it that will target
        // this block.
        insertBlock(newCaseLabel);

        info->currentCaseLabel = newCaseLabel;
        info->anythingEmittedToCurrentCaseBlock = false;
        return newCaseLabel;
    }

    bool hasSwitchCases(Stmt* inStmt)
    {
        Stmt* stmt = inStmt;
        // Unwrap any surrounding `{ ... }` so we can look
        // at the statement inside.
        while (auto blockStmt = as<BlockStmt>(stmt))
        {
            stmt = blockStmt->body;
            continue;
        }

        if (auto seqStmt = as<SeqStmt>(stmt))
        {
            // Walk through the children looking for cases
            for (auto childStmt : seqStmt->stmts)
            {
                if (hasSwitchCases(childStmt))
                {
                    return true;
                }
            }
        }
        else if (auto caseStmt = as<CaseStmt>(stmt))
        {
            return true;
        }
        else if (auto defaultStmt = as<DefaultStmt>(stmt))
        {
            // A 'default:' is a kind of case. 
            return true;
        }

        return false;
    }

    // Given a statement that appears as (or in) the body
    // of a `switch` statement
    void lowerSwitchCases(Stmt* inStmt, SwitchStmtInfo* info)
    {
        // TODO: in the general case (e.g., if we were going
        // to eventual lower to an unstructured format like LLVM),
        // the Right Way to handle C-style `switch` statements
        // is just to emit the body directly as "normal" statements,
        // and then treat `case` and `default` as special statements
        // that start a new block and register a label with the
        // enclosing `switch`.
        //
        // For now we will assume that any `case` and `default`
        // statements need to be directly nested under the `switch`,
        // and so we can find them with a simpler walk.

        Stmt* stmt = inStmt;

        // Unwrap any surrounding `{ ... }` so we can look
        // at the statement inside.
        while(auto blockStmt = as<BlockStmt>(stmt))
        {
            stmt = blockStmt->body;
            continue;
        }

        if(auto seqStmt = as<SeqStmt>(stmt))
        {
            // Walk through teh children and process each.
            for(auto childStmt : seqStmt->stmts)
            {
                lowerSwitchCases(childStmt, info);
            }
        }
        else if(auto caseStmt = as<CaseStmt>(stmt))
        {
            // A full `case` statement has a value we need
            // to test against. It is expected to be a
            // compile-time constant, so we will emit
            // it like an expression here, and then hope
            // for the best.
            //
            // TODO: figure out something cleaner.

            // Actually, one gotcha is that if we ever allow non-constant
            // expressions here (or anything that requires instructions
            // to be emitted to yield its value), then those instructions
            // need to go into an appropriate block.

            IRGenContext subContext = *context;
            IRBuilder subBuilder = *getBuilder();
            subBuilder.setInsertInto(info->initialBlock);
            subContext.irBuilder = &subBuilder;
            auto caseVal = getSimpleVal(context, lowerRValueExpr(&subContext, caseStmt->expr));

            // Figure out where we are branching to.
            auto label = getLabelForCase(info);

            // Add this `case` to the list for the enclosing `switch`.
            info->cases.add(caseVal);
            info->cases.add(label);
        }
        else if(auto defaultStmt = as<DefaultStmt>(stmt))
        {
            auto label = getLabelForCase(info);

            // We expect to only find a single `default` stmt.
            SLANG_ASSERT(!info->defaultLabel);

            info->defaultLabel = label;
        }
        else if(auto emptyStmt = as<EmptyStmt>(stmt))
        {
            // Special-case empty statements so they don't
            // mess up our "trivial fall-through" optimization.
        }
        else
        {
            // We have an ordinary statement, that needs to get
            // emitted to the current case block.
            if(!info->currentCaseLabel)
            {
                // It possible in full C/C++ to have statements
                // before the first `case`. Usually these are
                // unreachable, unless they start with a label.
                //
                // We'll ignore them here, figuring they are
                // dead. If we ever add `LabelStmt` then we'd
                // need to emit these statements to a dummy
                // block just in case.
            }
            else
            {
                // Emit the code to our current case block,
                // and record that we've done so.
                lowerStmt(context, stmt);
                info->anythingEmittedToCurrentCaseBlock = true;
            }
        }
    }

    void visitSwitchStmt(SwitchStmt* stmt)
    {
        auto builder = getBuilder();
        startBlockIfNeeded(stmt);

        // Given a statement:
        //
        //      switch( CONDITION )
        //      {
        //      case V0:
        //          S0;
        //          break;
        //
        //      case V1:
        //      default:
        //          S1;
        //          break;
        //      }
        //
        // we want to generate IR like:
        //
        //      let %c = <CONDITION>;
        //      switch %c,          // value to switch on
        //          %breakLabel,    // join point (and break target)
        //          %s1,            // default label
        //          %v0,            // first case value
        //          %s0,            // first case label
        //          %v1,            // second case value
        //          %s1             // second case label
        //  s0:
        //      <S0>
        //      break %breakLabel
        //  s1:
        //      <S1>
        //      break %breakLabel
        //  breakLabel:
        //

        // First emit code to compute the condition:
        auto conditionVal = getSimpleVal(context, lowerRValueExpr(context, stmt->condition));

        // Check for any cases or default. 
        if (!hasSwitchCases(stmt->body))
        {
            // If we don't have any case/default then nothing inside switch can be executed (other than condition)
            // so we are done.
            return;
        }

        // Remember the initial block so that we can add to it
        // after we've collected all the `case`s
        auto initialBlock = builder->getBlock();

        // Next, create a block to use as the target for any `break` statements
        auto breakLabel = createBlock();

        // Register the `break` label so
        // that we can find it for nested statements.
        context->shared->breakLabels.Add(stmt, breakLabel);

        builder->setInsertInto(initialBlock->getParent());

        // Iterate over the body of the statement, looking
        // for `case` or `default` statements:
        SwitchStmtInfo info;
        info.initialBlock = initialBlock;
        info.defaultLabel = nullptr;
        lowerSwitchCases(stmt->body, &info);

        // TODO: once we've discovered the cases, we should
        // be able to make a quick pass over the list and eliminate
        // any cases that have the exact same label as the `default`
        // case, since these don't actually need to be represented.

        // If the current block (the end of the last
        // `case`) is not terminated, then terminate with a
        // `break` operation.
        //
        // Double check that we aren't in the initial
        // block, so we don't get tripped up on an
        // empty `switch`.
        auto curBlock = builder->getBlock();
        if(curBlock != initialBlock)
        {
            // Is the block already terminated?
            if(!curBlock->getTerminator())
            {
                // Not terminated, so add one.
                builder->emitBreak(breakLabel);
            }
        }

        // If there was no `default` statement, then the
        // default case will just branch directly to the end.
        auto defaultLabel = info.defaultLabel ? info.defaultLabel : breakLabel;

        // Now that we've collected the cases, we are
        // prepared to emit the `switch` instruction
        // itself.
        builder->setInsertInto(initialBlock);
        builder->emitSwitch(
            conditionVal,
            breakLabel,
            defaultLabel,
            info.cases.getCount(),
            info.cases.getBuffer());

        // Finally we insert the label that a `break` will jump to
        // (and that control flow will fall through to otherwise).
        // This is the block that subsequent code will go into.
        insertBlock(breakLabel);
        context->shared->breakLabels.Remove(stmt);
    }
};

void lowerStmt(
    IRGenContext*   context,
    Stmt*           stmt)
{
    IRBuilderSourceLocRAII sourceLocInfo(context->irBuilder, stmt->loc);

    StmtLoweringVisitor visitor;
    visitor.context = context;

    try
    {
        visitor.dispatch(stmt);
    }
    // Don't emit any context message for an explicit `AbortCompilationException`
    // because it should only happen when an error is already emitted.
    catch(const AbortCompilationException&) { throw; }
    catch(...)
    {
        context->getSink()->noteInternalErrorLoc(stmt->loc);
        throw;
    }
}

/// Create and return a mutable temporary initialized with `val`
static LoweredValInfo moveIntoMutableTemp(
    IRGenContext*           context,
    LoweredValInfo const&   val)
{
    IRInst* irVal = getSimpleVal(context, val);
    auto type = irVal->getDataType();
    auto var = createVar(context, type);

    assign(context, var, LoweredValInfo::simple(irVal));
    return var;
}

LoweredValInfo tryGetAddress(
    IRGenContext*           context,
    LoweredValInfo const&   inVal,
    TryGetAddressMode       mode)
{
    LoweredValInfo val = inVal;

    switch(val.flavor)
    {
    case LoweredValInfo::Flavor::Ptr:
        // The `Ptr` case means that we already have an IR value with
        // the address of our value. Easy!
        return val;

    case LoweredValInfo::Flavor::BoundSubscript:
        {
            // If we are are trying to turn a subscript operation like `buffer[index]`
            // into a pointer, then we need to find a `ref` accessor declared
            // as part of the subscript operation being referenced.
            //
            auto subscriptInfo = val.getBoundSubscriptInfo();

            // We don't want to immediately bind to a `ref` accessor if there is
            // a `set` accessor available, unless we are in an "aggressive" mode
            // where we really want/need a pointer to be able to make progress.
            //
            if(mode != TryGetAddressMode::Aggressive
                && getMembersOfType<SetterDecl>(subscriptInfo->declRef, MemberFilterStyle::Instance).isNonEmpty())
            {
                // There is a setter that we should consider using,
                // so don't go and aggressively collapse things just yet.
                return val;
            }

            auto refAccessors = getMembersOfType<RefAccessorDecl>(subscriptInfo->declRef, MemberFilterStyle::Instance);
            if(refAccessors.isNonEmpty())
            {
                // The `ref` accessor will return a pointer to the value, so
                // we need to reflect that in the type of our `call` instruction.
                IRType* ptrType = context->irBuilder->getPtrType(subscriptInfo->type);

                LoweredValInfo refVal = emitCallToDeclRef(
                    context,
                    ptrType,
                    *refAccessors.begin(),
                    nullptr,
                    subscriptInfo->args);

                // The result from the call should be a pointer, and it
                // is the address that we wanted in the first place.
                return LoweredValInfo::ptr(getSimpleVal(context, refVal));
            }

            // Otherwise, there was no `ref` accessor, and so it is not possible
            // to materialize this location into a pointer for whatever purpose
            // we have in mind (e.g., passing it to an atomic operation).
        }
        break;

    case LoweredValInfo::Flavor::BoundMember:
        {
            auto boundMemberInfo = val.getBoundMemberInfo();

            // If we hit this case, then it means that we have a reference
            // to a single field in something, but for whatever reason the
            // higher-level logic was not able to turn it into a pointer
            // already (maybe the base value for the field reference is
            // a `BoundSubscript`, etc.).
            //
            // We need to read the entire base value out, modify the field
            // we care about, and then write it back.

            auto declRef = boundMemberInfo->declRef;
            if( auto fieldDeclRef = declRef.as<VarDecl>() )
            {
                auto baseVal = boundMemberInfo->base;
                auto basePtr = tryGetAddress(context, baseVal, TryGetAddressMode::Aggressive);

                return extractField(context, boundMemberInfo->type, basePtr, fieldDeclRef);
            }

        }
        break;

    case LoweredValInfo::Flavor::SwizzledLValue:
        {
            auto originalSwizzleInfo = val.getSwizzledLValueInfo();
            auto originalBase = originalSwizzleInfo->base;

            UInt elementCount = originalSwizzleInfo->elementCount;

            auto newBase = tryGetAddress(context, originalBase, TryGetAddressMode::Aggressive);
            RefPtr<SwizzledLValueInfo> newSwizzleInfo = new SwizzledLValueInfo();
            context->shared->extValues.add(newSwizzleInfo);

            newSwizzleInfo->base = newBase;
            newSwizzleInfo->type = originalSwizzleInfo->type;
            newSwizzleInfo->elementCount = elementCount;
            for(UInt ee = 0; ee < elementCount; ++ee)
                newSwizzleInfo->elementIndices[ee] = originalSwizzleInfo->elementIndices[ee];

            return LoweredValInfo::swizzledLValue(newSwizzleInfo);
        }
        break;

    // TODO: are there other cases we need to handled here?

    default:
        break;
    }

    // If none of the special cases above applied, then we werent' able to make
    // this value into a pointer, and we should just return it as-is.
    return val;
}

IRInst* getAddress(
    IRGenContext*           context,
    LoweredValInfo const&   inVal,
    SourceLoc               diagnosticLocation)
{
    LoweredValInfo val = tryGetAddress(context, inVal, TryGetAddressMode::Aggressive);

    if( val.flavor == LoweredValInfo::Flavor::Ptr )
    {
        return val.val;
    }

    context->getSink()->diagnose(diagnosticLocation, Diagnostics::invalidLValueForRefParameter);
    return nullptr;
}

void assign(
    IRGenContext*           context,
    LoweredValInfo const&   inLeft,
    LoweredValInfo const&   inRight)
{
    LoweredValInfo left = inLeft;
    LoweredValInfo right = inRight;

    // Before doing the case analysis on the shape of the `left` value,
    // we might as well go ahead and see if we can coerce it into
    // a simple pointer, since that would make our life a lot easier
    // when handling complex cases.
    //
    left = tryGetAddress(context, left, TryGetAddressMode::Default);

    auto builder = context->irBuilder;

top:
    switch (left.flavor)
    {
    case LoweredValInfo::Flavor::Ptr:
        {
            // The `left` value is just a pointer, so we can emit
            // a store to it directly.
            //
            builder->emitStore(
                left.val,
                getSimpleVal(context, right));
        }
        break;

    case LoweredValInfo::Flavor::SwizzledLValue:
        {
            // The `left` value is of the form `<base>.<swizzleElements>`.
            // How we will handle this depends on what `base` looks like:
            auto swizzleInfo = left.getSwizzledLValueInfo();
            auto loweredBase = swizzleInfo->base;

            // Note that the call to `tryGetAddress` at the start should
            // ensure that `loweredBase` has been simplified as much as
            // possible (e.g., if it is possible to turn it into a
            // `LoweredValInfo::ptr()` then that will have been done).

            switch( loweredBase.flavor )
            {
            default:
                {
                    // Our fallback position is to lower via a temporary, e.g.:
                    //
                    //      float4 tmp = <base>;
                    //      tmp.xyz = float3(...);
                    //      <base> = tmp;
                    //

                    // Load from the base value
                    IRInst* irLeftVal = getSimpleVal(context, loweredBase);

                    // Extract a simple value for the right-hand side
                    IRInst* irRightVal = getSimpleVal(context, right);

                    // Apply the swizzle
                    IRInst* irSwizzled = builder->emitSwizzleSet(
                        irLeftVal->getDataType(),
                        irLeftVal,
                        irRightVal,
                        swizzleInfo->elementCount,
                        swizzleInfo->elementIndices);

                    // And finally, store the value back where we got it.
                    //
                    // Note: this is effectively a recursive call to
                    // `assign()`, so we do a simple tail-recursive call here.
                    left = loweredBase;
                    right = LoweredValInfo::simple(irSwizzled);
                    goto top;
                }
                break;

            case LoweredValInfo::Flavor::Ptr:
                {
                    // We are writing through a pointer, which might be
                    // pointing into a UAV or other memory resource, so
                    // we can't introduce use a temporary like the case
                    // above, because then we would read and write bytes
                    // that are not strictly required for the store.
                    //
                    // Note that the messy case of a "swizzle of a swizzle"
                    // was handled already in lowering of a `SwizzleExpr`,
                    // so that we don't need to deal with that case here.
                    //
                    // TODO: we may need to consider whether there is
                    // enough value in a masked store like this to keep
                    // it around, in comparison to a simpler model where
                    // we simply form a pointer to each of the vector
                    // elements and write to them individually.
                    //
                    // TODO: we might also consider just special-casing
                    // single-element swizzles so that the common case
                    // can turn into a simple `store` instead of a
                    // `swizzledStore`.
                    //
                    IRInst* irRightVal = getSimpleVal(context, right);
                    builder->emitSwizzledStore(
                        loweredBase.val,
                        irRightVal,
                        swizzleInfo->elementCount,
                        swizzleInfo->elementIndices);
                }
                break;
            }
        }
        break;

    case LoweredValInfo::Flavor::BoundSubscript:
        {
            // The `left` value refers to a subscript operation on
            // a resource type, bound to particular arguments, e.g.:
            // `someStructuredBuffer[index]`.
            //
            // When storing to such a value, we need to emit a call
            // to the appropriate builtin "setter" accessor, if there
            // is one, and then fall back to a `ref` accessor if
            // there is no setter.
            //
            auto subscriptInfo = left.getBoundSubscriptInfo();

            // Search for an appropriate "setter" declaration
            auto setters = getMembersOfType<SetterDecl>(subscriptInfo->declRef, MemberFilterStyle::Instance);
            if (setters.isNonEmpty())
            {
                auto allArgs = subscriptInfo->args;
                addArgs(context, &allArgs, right);

                emitCallToDeclRef(
                    context,
                    builder->getVoidType(),
                    *setters.begin(),
                    nullptr,
                    allArgs);
                return;
            }

            auto refAccessors = getMembersOfType<RefAccessorDecl>(subscriptInfo->declRef, MemberFilterStyle::Instance);
            if(refAccessors.isNonEmpty())
            {
                // The `ref` accessor will return a pointer to the value, so
                // we need to reflect that in the type of our `call` instruction.
                IRType* ptrType = context->irBuilder->getPtrType(subscriptInfo->type);

                LoweredValInfo refVal = emitCallToDeclRef(
                    context,
                    ptrType,
                    *refAccessors.begin(),
                    nullptr,
                    subscriptInfo->args);

                // The result from the call needs to be implicitly dereferenced,
                // so that it can work as an l-value of the desired result type.
                left = LoweredValInfo::ptr(getSimpleVal(context, refVal));

                // Tail-recursively attempt assignment again on the new l-value.
                goto top;
            }

            // No setter found? Then we have an error!
            SLANG_UNEXPECTED("no setter found");
            break;
        }
        break;

    case LoweredValInfo::Flavor::BoundMember:
        {
            auto boundMemberInfo = left.getBoundMemberInfo();

            // If we hit this case, then it means that we are trying to set
            // a single field in someting that is not atomically set-able.
            // (e.g., an element of a value where the `subscript` operation
            // has `get` and `set` but not a `ref` accessor).
            //
            // We need to read the entire base value out, modify the field
            // we care about, and then write it back.

            auto declRef = boundMemberInfo->declRef;
            if( auto fieldDeclRef = declRef.as<VarDecl>() )
            {
                // materialize the base value and move it into
                // a mutable temporary if needed
                auto baseVal = boundMemberInfo->base;
                auto tempVal = moveIntoMutableTemp(context, baseVal);

                // extract the field l-value out of the temporary
                auto tempFieldVal = extractField(context, boundMemberInfo->type, tempVal, fieldDeclRef);

                // assign to the field of the temporary l-value
                assign(context, tempFieldVal, right);

                // write back the modified temporary to the base l-value
                assign(context, baseVal, tempVal);

                return;
            }
            else
            {
                SLANG_UNEXPECTED("handled member flavor");
            }

        }
        break;

    default:
        SLANG_UNIMPLEMENTED_X("assignment");
        break;
    }
}

struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
{
    IRGenContext*   context;

    DiagnosticSink* getSink() { return context->getSink(); }

    IRBuilder* getBuilder()
    {
        return context->irBuilder;
    }

    LoweredValInfo visitDeclBase(DeclBase* /*decl*/)
    {
        SLANG_UNIMPLEMENTED_X("decl catch-all");
        UNREACHABLE_RETURN(LoweredValInfo());
    }

    LoweredValInfo visitDecl(Decl* /*decl*/)
    {
        SLANG_UNIMPLEMENTED_X("decl catch-all");
        UNREACHABLE_RETURN(LoweredValInfo());
    }

    LoweredValInfo visitExtensionDecl(ExtensionDecl* decl)
    {
        for (auto & member : decl->members)
            ensureDecl(context, member);
        return LoweredValInfo();
    }

#define IGNORED_CASE(NAME) \
    LoweredValInfo visit##NAME(NAME*) { return LoweredValInfo(); }

    IGNORED_CASE(ImportDecl)
    IGNORED_CASE(EmptyDecl)
    IGNORED_CASE(SyntaxDecl)
    IGNORED_CASE(AttributeDecl)
    IGNORED_CASE(NamespaceDecl)

#undef IGNORED_CASE

    LoweredValInfo visitTypeDefDecl(TypeDefDecl* decl)
    {
        // A type alias declaration may be generic, if it is
        // nested under a generic type/function/etc.
        //
        NestedContext nested(this);
        auto subBuilder = nested.getBuilder();
        auto subContext = nested.getContext();
        IRGeneric* outerGeneric = emitOuterGenerics(subContext, decl, decl);

        // TODO: if a type alias declaration can have linkage,
        // we will need to lower it to some kind of global
        // value in the IR so that we can attach a name to it.
        //
        // For now, we can only attach a name *if* the type
        // alias is somehow generic.
        if(outerGeneric)
        {
            addLinkageDecoration(context, outerGeneric, decl);
        }

        auto type = lowerType(subContext, decl->type.type);

        return LoweredValInfo::simple(finishOuterGenerics(subBuilder, type));
    }

    LoweredValInfo visitGenericTypeParamDecl(GenericTypeParamDecl* /*decl*/)
    {
        return LoweredValInfo();
    }

    LoweredValInfo visitGenericTypeConstraintDecl(GenericTypeConstraintDecl* decl)
    {
        // This might be a type constraint on an associated type,
        // in which case it should lower as the key for that
        // interface requirement.
        if(auto assocTypeDecl = as<AssocTypeDecl>(decl->parentDecl))
        {
            // TODO: might need extra steps if we ever allow
            // generic associated types.


            if(auto interfaceDecl = as<InterfaceDecl>(assocTypeDecl->parentDecl))
            {
                // Okay, this seems to be an interface rquirement, and
                // we should lower it as such.
                return LoweredValInfo::simple(getInterfaceRequirementKey(decl));
            }
        }

        if(auto globalGenericParamDecl = as<GlobalGenericParamDecl>(decl->parentDecl))
        {
            // This is a constraint on a global generic type parameters,
            // and so it should lower as a parameter of its own.

            auto inst = getBuilder()->emitGlobalGenericWitnessTableParam();
            addLinkageDecoration(context, inst, decl);
            return LoweredValInfo::simple(inst);
        }

        // Otherwise we really don't expect to see a type constraint
        // declaration like this during lowering, because a generic
        // should have set up a parameter for any constraints as
        // part of being lowered.

        SLANG_UNEXPECTED("generic type constraint during lowering");
        UNREACHABLE_RETURN(LoweredValInfo());
    }

    LoweredValInfo visitGlobalGenericParamDecl(GlobalGenericParamDecl* decl)
    {
        auto inst = getBuilder()->emitGlobalGenericTypeParam();
        addLinkageDecoration(context, inst, decl);
        return LoweredValInfo::simple(inst);
    }

    LoweredValInfo visitGlobalGenericValueParamDecl(GlobalGenericValueParamDecl* decl)
    {
        auto builder = getBuilder();
        auto type = lowerType(context, decl->type);
        auto inst = builder->emitGlobalGenericParam(type);
        addLinkageDecoration(context, inst, decl);
        return LoweredValInfo::simple(inst);
    }


    void lowerWitnessTable(
        IRGenContext*                               subContext,
        WitnessTable*                               astWitnessTable,
        IRWitnessTable*                             irWitnessTable,
        Dictionary<WitnessTable*, IRWitnessTable*>  mapASTToIRWitnessTable)
    {
        auto subBuilder = subContext->irBuilder;

        for(auto entry : astWitnessTable->requirementDictionary)
        {
            auto requiredMemberDecl = entry.Key;
            auto satisfyingWitness = entry.Value;

            auto irRequirementKey = getInterfaceRequirementKey(requiredMemberDecl);
            IRInst* irSatisfyingVal = nullptr;

            switch(satisfyingWitness.getFlavor())
            {
            case RequirementWitness::Flavor::declRef:
                {
                    auto satisfyingDeclRef = satisfyingWitness.getDeclRef();
                    irSatisfyingVal = getSimpleVal(subContext,
                        emitDeclRef(subContext, satisfyingDeclRef,
                        // TODO: we need to know what type to plug in here...
                        nullptr));
                }
                break;

            case RequirementWitness::Flavor::val:
                {
                    auto satisfyingVal = satisfyingWitness.getVal();
                    irSatisfyingVal = lowerSimpleVal(subContext, satisfyingVal);
                }
                break;

            case RequirementWitness::Flavor::witnessTable:
                {
                    auto astReqWitnessTable = satisfyingWitness.getWitnessTable();
                    IRWitnessTable* irSatisfyingWitnessTable = nullptr;
                    if(!mapASTToIRWitnessTable.TryGetValue(astReqWitnessTable, irSatisfyingWitnessTable))
                    {
                        // Need to construct a sub-witness-table
                        irSatisfyingWitnessTable = subBuilder->createWitnessTable();

                        // Recursively lower the sub-table.
                        lowerWitnessTable(
                            subContext,
                            astReqWitnessTable,
                            irSatisfyingWitnessTable,
                            mapASTToIRWitnessTable);

                        irSatisfyingWitnessTable->moveToEnd();
                    }
                    irSatisfyingVal = irSatisfyingWitnessTable;
                }
                break;

            default:
                SLANG_UNEXPECTED("handled requirement witness case");
                break;
            }


            subBuilder->createWitnessTableEntry(
                irWitnessTable,
                irRequirementKey,
                irSatisfyingVal);
        }
    }

    LoweredValInfo visitInheritanceDecl(InheritanceDecl* inheritanceDecl)
    {
        // An inheritance clause inside of an `interface`
        // declaration should not give rise to a witness
        // table, because it represents something the
        // interface requires, and not what it provides.
        //
        auto parentDecl = inheritanceDecl->parentDecl;
        if (auto parentInterfaceDecl = as<InterfaceDecl>(parentDecl))
        {
            return LoweredValInfo::simple(getInterfaceRequirementKey(inheritanceDecl));
        }
        //
        // We also need to cover the case where an `extension`
        // declaration is being used to add a conformance to
        // an existing `interface`:
        //
        if(auto parentExtensionDecl = as<ExtensionDecl>(parentDecl))
        {
            auto targetType = parentExtensionDecl->targetType;
            if(auto targetDeclRefType = as<DeclRefType>(targetType))
            {
                if(auto targetInterfaceDeclRef = targetDeclRefType->declRef.as<InterfaceDecl>())
                {
                    return LoweredValInfo::simple(getInterfaceRequirementKey(inheritanceDecl));
                }
            }
        }

        // Find the type that is doing the inheriting.
        // Under normal circumstances it is the type declaration that
        // is the parent for the inheritance declaration, but if
        // the inheritance declaration is on an `extension` declaration,
        // then we need to identify the type being extended.
        //
        RefPtr<Type> subType;
        if (auto extParentDecl = as<ExtensionDecl>(parentDecl))
        {
            subType = extParentDecl->targetType.type;
        }
        else
        {
            subType = DeclRefType::create(context->astBuilder, makeDeclRef(parentDecl));
        }

        // What is the super-type that we have declared we inherit from?
        RefPtr<Type> superType = inheritanceDecl->base.type;

        // Construct the mangled name for the witness table, which depends
        // on the type that is conforming, and the type that it conforms to.
        //
        // TODO: This approach doesn't really make sense for generic `extension` conformances.
        auto mangledName = getMangledNameForConformanceWitness(context->astBuilder, subType, superType);

        // A witness table may need to be generic, if the outer
        // declaration (either a type declaration or an `extension`)
        // is generic.
        //
        NestedContext nested(this);
        auto subBuilder = nested.getBuilder();
        auto subContext = nested.getContext();
        emitOuterGenerics(subContext, inheritanceDecl, inheritanceDecl);

        // Lower the super-type to force its declaration to be lowered.
        //
        // Note: we are using the "sub-context" here because the
        // type being inherited from could reference generic parameters,
        // and we need those parameters to lower as references to
        // the parameters of our IR-level generic.
        //
        lowerType(subContext, superType);

        // Create the IR-level witness table
        auto irWitnessTable = subBuilder->createWitnessTable();
        addLinkageDecoration(context, irWitnessTable, inheritanceDecl, mangledName.getUnownedSlice());

        // Register the value now, rather than later, to avoid any possible infinite recursion.
        setGlobalValue(context, inheritanceDecl, LoweredValInfo::simple(irWitnessTable));

        // Make sure that all the entries in the witness table have been filled in,
        // including any cases where there are sub-witness-tables for conformances
        Dictionary<WitnessTable*, IRWitnessTable*> mapASTToIRWitnessTable;
        lowerWitnessTable(
            subContext,
            inheritanceDecl->witnessTable,
            irWitnessTable,
            mapASTToIRWitnessTable);

        irWitnessTable->moveToEnd();

        return LoweredValInfo::simple(finishOuterGenerics(subBuilder, irWitnessTable));
    }

    LoweredValInfo visitDeclGroup(DeclGroup* declGroup)
    {
        // To lower a group of declarations, we just
        // lower each one individually.
        //
        for (auto decl : declGroup->decls)
        {
            IRBuilderSourceLocRAII sourceLocInfo(context->irBuilder, decl->loc);

            // Note: I am directly invoking `dispatch` here,
            // instead of `ensureDecl` just to try and
            // make sure that we don't accidentally
            // emit things to an outer context.
            //
            // TODO: make sure that can't happen anyway.
            dispatch(decl);
        }

        return LoweredValInfo();
    }

    LoweredValInfo visitSubscriptDecl(SubscriptDecl* decl)
    {
        // A subscript operation may encompass one or more
        // accessors, and these are what should actually
        // get lowered (they are effectively functions).

        for (auto accessor : decl->getMembersOfType<AccessorDecl>())
        {
            if (accessor->hasModifier<IntrinsicOpModifier>())
                continue;

            ensureDecl(context, accessor);
        }

        // The subscript declaration itself won't correspond
        // to anything in the lowered program, so we don't
        // bother creating a representation here.
        //
        // Note: We may want to have a specific lowered value
        // that can represent the combination of callables
        // that make up the subscript operation.
        return LoweredValInfo();
    }

    bool isGlobalVarDecl(VarDecl* decl)
    {
        auto parent = decl->parentDecl;
        if (as<NamespaceDeclBase>(parent))
        {
            // Variable declared at global/namespace scope? -> Global.
            return true;
        }
        else if(as<AggTypeDeclBase>(parent))
        {
            if(decl->hasModifier<HLSLStaticModifier>())
            {
                // A `static` member variable is effectively global.
                return true;
            }
        }

        return false;
    }

    bool isMemberVarDecl(VarDecl* decl)
    {
        auto parent = decl->parentDecl;
        if (as<AggTypeDecl>(parent))
        {
            // A variable declared inside of an aggregate type declaration is a member.
            return true;
        }

        return false;
    }

    LoweredValInfo lowerGlobalShaderParam(VarDecl* decl)
    {
        IRType* paramType = lowerType(context, decl->getType());

        auto builder = getBuilder();

        auto irParam = builder->createGlobalParam(paramType);
        auto paramVal = LoweredValInfo::simple(irParam);

        addLinkageDecoration(context, irParam, decl);
        addNameHint(context, irParam, decl);
        maybeSetRate(context, irParam, decl);
        addVarDecorations(context, irParam, decl);

        if (decl)
        {
            builder->addHighLevelDeclDecoration(irParam, decl);
        }

        // A global variable's SSA value is a *pointer* to
        // the underlying storage.
        setGlobalValue(context, decl, paramVal);

        irParam->moveToEnd();

        return paramVal;
    }

    LoweredValInfo lowerConstantDeclCommon(VarDeclBase* decl, IRGenContext* subContext)
    {
        auto builder = subContext->irBuilder;
        auto initExpr = decl->initExpr;

        // We want to be able to support cases where a global constant is defined in
        // another module and we should not bind to its value at (front-end) compile
        // time. We handle this by adding a level of indirection where a global constant
        // is represented as an IR node with zero or one operands. In the zero-operand
        // case the node represents a global constant with an unknown value (perhaps
        // an imported constant), while in the one-operand case the operand gives us
        // the concrete value to use for the constant.
        //
        // Using a level of indirection also gives us a well-defined place to attach
        // annotation information like name hints, since otherwise two constants
        // with the same value would map to identical IR nodes.
        //
        // TODO: For now we detect whether or not to include the value operand based on
        // whether we see an initial-value expression in the AST declaration, but
        // eventually we might base this on whether or not the value should be accessible
        // to the module we are lowering.

        IRInst* irConstant = nullptr;
        if(!initExpr)
        {
            // If we don't know the value we want to use, then we just create
            // a global constant IR node with the right type.
            //
            auto irType = lowerType(subContext, decl->getType());
            irConstant = builder->emitGlobalConstant(irType);
        }
        else
        {
            // We lower the value expression directly, which yields a
            // global instruction to represent the value. There is
            // no guarantee that this instruction is unique (e.g.,
            // if we have two different constants definitions both
            // with the value `5`, then we might have only a single
            // instruction to represent `5`.
            //
            auto irInitVal = getSimpleVal(subContext, lowerRValueExpr(subContext, initExpr));

            // We construct a distinct IR instruction to represent the
            // constant itself, with the value as an operand.
            //
            irConstant = builder->emitGlobalConstant(
                irInitVal->getFullType(),
                irInitVal);
        }

        // All of the attributes/decorations we can attach
        // belong on the IR constant node.
        //
        addLinkageDecoration(context, irConstant, decl);
        addNameHint(context, irConstant, decl);
        addVarDecorations(context, irConstant, decl);
        getBuilder()->addHighLevelDeclDecoration(irConstant, decl);

        // Register the value that was emitted as the value
        // for any references to the constant from elsewhere
        // in the code.
        //
        auto constantVal = LoweredValInfo::simple(irConstant);
        setGlobalValue(context, decl, constantVal);

        return constantVal;
    }

    LoweredValInfo lowerGlobalConstantDecl(VarDecl* decl)
    {
        return lowerConstantDeclCommon(decl, context);
    }

    LoweredValInfo lowerGlobalVarDecl(VarDecl* decl)
    {
        // A non-`static` global is actually a shader parameter in HLSL.
        //
        // TODO: We should probably make that case distinct at the AST
        // level as well, since global shader parameters are fairly
        // different from global variables.
        //
        if(isGlobalShaderParameter(decl))
        {
            return lowerGlobalShaderParam(decl);
        }

        // A `static const` global is actually a compile-time constant.
        //
        if (decl->hasModifier<HLSLStaticModifier>() && decl->hasModifier<ConstModifier>())
        {
            return lowerGlobalConstantDecl(decl);
        }

        IRType* varType = lowerType(context, decl->getType());

        auto builder = getBuilder();

        IRGlobalValueWithCode* irGlobal = builder->createGlobalVar(varType);
        LoweredValInfo globalVal = LoweredValInfo::ptr(irGlobal);

        addLinkageDecoration(context, irGlobal, decl);
        addNameHint(context, irGlobal, decl);

        maybeSetRate(context, irGlobal, decl);

        addVarDecorations(context, irGlobal, decl);

        if (decl)
        {
            builder->addHighLevelDeclDecoration(irGlobal, decl);
        }

        // A global variable's SSA value is a *pointer* to
        // the underlying storage.
        setGlobalValue(context, decl, globalVal);

        if (isImportedDecl(decl))
        {
            // Always emit imported declarations as declarations,
            // and not definitions.
        }
        else if( auto initExpr = decl->initExpr )
        {
            IRBuilder subBuilderStorage = *getBuilder();
            IRBuilder* subBuilder = &subBuilderStorage;

            subBuilder->setInsertInto(irGlobal);

            IRGenContext subContextStorage = *context;
            IRGenContext* subContext = &subContextStorage;

            subContext->irBuilder = subBuilder;

            // TODO: set up a parent IR decl to put the instructions into

            IRBlock* entryBlock = subBuilder->emitBlock();
            subBuilder->setInsertInto(entryBlock);

            LoweredValInfo initVal = lowerLValueExpr(subContext, initExpr);
            subContext->irBuilder->emitReturn(getSimpleVal(subContext, initVal));
        }

        irGlobal->moveToEnd();

        return globalVal;
    }

    bool isFunctionStaticVarDecl(VarDeclBase* decl)
    {
        // Only a variable marked `static` can be static.
        if(!decl->findModifier<HLSLStaticModifier>())
            return false;

        // The immediate parent of a function-scope variable
        // declaration will be a `ScopeDecl`.
        //
        // TODO: right now the parent links for scopes are *not*
        // set correctly, so we can't just scan up and look
        // for a function in the parent chain...
        auto parent = decl->parentDecl;
        if( as<ScopeDecl>(parent) )
        {
            return true;
        }

        return false;
    }

    IRInst* defaultSpecializeOuterGeneric(
        IRInst*         outerVal,
        IRType*         type,
        GenericDecl*    genericDecl)
    {
        auto builder = getBuilder();

        // We need to specialize any generics that are further out...
        auto specialiedOuterVal = defaultSpecializeOuterGenerics(
            outerVal,
            builder->getGenericKind(),
            genericDecl);

        List<IRInst*> genericArgs;

        // Walk the parameters of the generic, and emit an argument for each,
        // which will be a reference to binding for that parameter in the
        // current scope.
        //
        // First we start with type and value parameters,
        // in the order they were declared.
        for (auto member : genericDecl->members)
        {
            if (auto typeParamDecl = as<GenericTypeParamDecl>(member))
            {
                genericArgs.add(getSimpleVal(context, ensureDecl(context, typeParamDecl)));
            }
            else if (auto valDecl = as<GenericValueParamDecl>(member))
            {
                genericArgs.add(getSimpleVal(context, ensureDecl(context, valDecl)));
            }
        }
        // Then we emit constraint parameters, again in
        // declaration order.
        for (auto member : genericDecl->members)
        {
            if (auto constraintDecl = as<GenericTypeConstraintDecl>(member))
            {
                genericArgs.add(getSimpleVal(context, ensureDecl(context, constraintDecl)));
            }
        }

        return builder->emitSpecializeInst(type, specialiedOuterVal, genericArgs.getCount(), genericArgs.getBuffer());
    }

    IRInst* defaultSpecializeOuterGenerics(
        IRInst* val,
        IRType* type,
        Decl*   decl)
    {
        if(!val) return nullptr;

        auto parentVal = val->getParent();
        while(parentVal)
        {
            if(as<IRGeneric>(parentVal))
                break;
            parentVal = parentVal->getParent();
        }
        if(!parentVal)
            return val;

        for(auto pp = decl->parentDecl; pp; pp = pp->parentDecl)
        {
            if(auto genericAncestor = as<GenericDecl>(pp))
            {
                return defaultSpecializeOuterGeneric(parentVal, type, genericAncestor);
            }
        }

        return val;
    }

    struct NestedContext
    {
        IRGenEnv        subEnvStorage;
        IRBuilder       subBuilderStorage;
        IRGenContext    subContextStorage;

        NestedContext(DeclLoweringVisitor* outer)
            : subBuilderStorage(*outer->getBuilder())
            , subContextStorage(*outer->context)
        {
            auto outerContext = outer->context;

            subEnvStorage.outer = outerContext->env;

            subContextStorage.irBuilder = &subBuilderStorage;
            subContextStorage.env = &subEnvStorage;
        }

        IRBuilder* getBuilder() { return &subBuilderStorage; }
        IRGenContext* getContext() { return &subContextStorage; }
    };

    LoweredValInfo lowerFunctionStaticConstVarDecl(
        VarDeclBase* decl)
    {
        // We need to insert the constant at a level above
        // the function being emitted. This will usually
        // be the global scope, but it might be an outer
        // generic if we are lowering a generic function.
        //
        NestedContext nestedContext(this);
        auto subBuilder = nestedContext.getBuilder();
        auto subContext = nestedContext.getContext();

        subBuilder->setInsertInto(subBuilder->getFunc()->getParent());

        return lowerConstantDeclCommon(decl, subContext);
    }

    LoweredValInfo lowerFunctionStaticVarDecl(
        VarDeclBase*    decl)
    {
        // We know the variable is `static`, but it might also be `const.
        if(decl->hasModifier<ConstModifier>())
            return lowerFunctionStaticConstVarDecl(decl);

        // A global variable may need to be generic, if one
        // of the outer declarations is generic.
        NestedContext nestedContext(this);
        auto subBuilder = nestedContext.getBuilder();
        auto subContext = nestedContext.getContext();
        subBuilder->setInsertInto(subBuilder->getModule()->getModuleInst());
        emitOuterGenerics(subContext, decl, decl);

        IRType* subVarType = lowerType(subContext, decl->getType());

        IRGlobalValueWithCode* irGlobal = subBuilder->createGlobalVar(subVarType);
        addVarDecorations(subContext, irGlobal, decl);

        addNameHint(context, irGlobal, decl);
        maybeSetRate(context, irGlobal, decl);

        subBuilder->addHighLevelDeclDecoration(irGlobal, decl);

        // We are inside of a function, and that function might be generic,
        // in which case the `static` variable will be lowered to another
        // generic. Let's start with a terrible example:
        //
        //      interface IHasCount { int getCount(); }
        //      int incrementCounter<T : IHasCount >(T val) {
        //          static int counter = 0;
        //          counter += val.getCount();
        //          return counter;
        //      }
        //
        // In this case, `incrementCounter` will lower to a function
        // nested in a generic, while `counter` will be lowered to
        // a global variable nested in a *different* generic.
        // The net result is something like this:
        //
        //      int counter<T:IHasCount> = 0;
        //
        //      int incrementCounter<T:IHasCount>(T val) {
        //          counter<T> += val.getCount();
        //          return counter<T>;
        //
        // The references to `counter` inside of `incrementCounter`
        // become references to `counter<T>`.
        //
        // At the IR level, this means that the value we install
        // for `decl` needs to be a specialized reference to `irGlobal`,
        // for any outer generics.
        //
        IRType* varType = lowerType(context, decl->getType());
        IRType* varPtrType = getBuilder()->getPtrType(varType);
        auto irSpecializedGlobal = defaultSpecializeOuterGenerics(irGlobal, varPtrType, decl);
        LoweredValInfo globalVal = LoweredValInfo::ptr(irSpecializedGlobal);
        setValue(context, decl, globalVal);

        // A `static` variable with an initializer needs special handling,
        // at least if the initializer isn't a compile-time constant.
        if( auto initExpr = decl->initExpr )
        {
            // We must create an ordinary global `bool isInitialized = false`
            // to represent whether we've initialized this before.
            // Then emit code like:
            //
            //      if(!isInitialized) { <globalVal> = <initExpr>; isInitialized = true; }
            //
            // TODO: we could conceivably optimize this by detecting
            // when the `initExpr` lowers to just a reference to a constant,
            // and then either deleting the extra code structure there,
            // or not generating it in the first place. That is a bit
            // more complexity than I'm ready for at the moment.
            //

            // Of course, if we are under a generic, then the Boolean
            // variable need to be generic as well!
            NestedContext nestedBoolContext(this);
            auto boolBuilder = nestedBoolContext.getBuilder();
            auto boolContext = nestedBoolContext.getContext();
            boolBuilder->setInsertInto(boolBuilder->getModule()->getModuleInst());
            emitOuterGenerics(boolContext, decl, decl);

            auto irBoolType = boolBuilder->getBoolType();
            auto irBool = boolBuilder->createGlobalVar(irBoolType);
            boolBuilder->setInsertInto(irBool);
            boolBuilder->setInsertInto(boolBuilder->createBlock());
            boolBuilder->emitReturn(boolBuilder->getBoolValue(false));

            auto boolVal = LoweredValInfo::ptr(defaultSpecializeOuterGenerics(irBool, irBoolType, decl));


            // Okay, with our global Boolean created, we can move on to
            // generating the code we actually care about, back in the original function.

            auto builder = getBuilder();

            auto initBlock = builder->createBlock();
            auto afterBlock = builder->createBlock();

            builder->emitIfElse(getSimpleVal(context, boolVal), afterBlock, initBlock, afterBlock);

            builder->insertBlock(initBlock);
            LoweredValInfo initVal = lowerLValueExpr(context, initExpr);
            assign(context, globalVal, initVal);
            assign(context, boolVal, LoweredValInfo::simple(builder->getBoolValue(true)));
            builder->emitBranch(afterBlock);

            builder->insertBlock(afterBlock);
        }

        irGlobal->moveToEnd();
        finishOuterGenerics(subBuilder, irGlobal);
        return globalVal;
    }

    LoweredValInfo visitGenericValueParamDecl(GenericValueParamDecl* decl)
    {
        return emitDeclRef(context, makeDeclRef(decl),
            lowerType(context, decl->type));
    }

    LoweredValInfo visitVarDecl(VarDecl* decl)
    {
        // Detect global (or effectively global) variables
        // and handle them differently.
        if (isGlobalVarDecl(decl))
        {
            return lowerGlobalVarDecl(decl);
        }

        if(isFunctionStaticVarDecl(decl))
        {
            return lowerFunctionStaticVarDecl(decl);
        }

        if(isMemberVarDecl(decl))
        {
            return lowerMemberVarDecl(decl);
        }

        // A user-defined variable declaration will usually turn into
        // an `alloca` operation for the variable's storage,
        // plus some code to initialize it and then store to the variable.

        IRType* varType = lowerType(context, decl->getType());

        // As a special case, an immutable local variable with an
        // initializer can just lower to the SSA value of its initializer.
        //
        if(as<LetDecl>(decl))
        {
            if(auto initExpr = decl->initExpr)
            {
                auto initVal = lowerRValueExpr(context, initExpr);
                initVal = LoweredValInfo::simple(getSimpleVal(context, initVal));
                setGlobalValue(context, decl, initVal);
                return initVal;
            }
        }


        LoweredValInfo varVal = createVar(context, varType, decl);

        if( auto initExpr = decl->initExpr )
        {
            auto initVal = lowerRValueExpr(context, initExpr);

            assign(context, varVal, initVal);
        }

        setGlobalValue(context, decl, varVal);

        return varVal;
    }

    IRStructKey* getInterfaceRequirementKey(Decl* requirementDecl)
    {
        return Slang::getInterfaceRequirementKey(context, requirementDecl);
    }

    LoweredValInfo visitInterfaceDecl(InterfaceDecl* decl)
    {
        // The members of an interface will turn into the keys that will
        // be used for lookup operations into witness
        // tables that promise conformance to the interface.
        //
        // TODO: we don't handle the case here of an interface
        // with concrete/default implementations for any
        // of its members.
        //
        // TODO: If we want to support using an interface as
        // an existential type, then we might need to emit
        // a witness table for the interface type's conformance
        // to its own interface.
        //
        for (auto requirementDecl : decl->members)
        {
            getInterfaceRequirementKey(requirementDecl);

            // As a special case, any type constraints placed
            // on an associated type will *also* need to be turned
            // into requirement keys for this interface.
            if (auto associatedTypeDecl = as<AssocTypeDecl>(requirementDecl))
            {
                for (auto constraintDecl : associatedTypeDecl->getMembersOfType<TypeConstraintDecl>())
                {
                    getInterfaceRequirementKey(constraintDecl);
                }
            }
        }


        NestedContext nestedContext(this);
        auto subBuilder = nestedContext.getBuilder();
        auto subContext = nestedContext.getContext();

        // Emit any generics that should wrap the actual type.
        emitOuterGenerics(subContext, decl, decl);

        IRInterfaceType* irInterface = subBuilder->createInterfaceType();
        addNameHint(context, irInterface, decl);
        addLinkageDecoration(context, irInterface, decl);
        subBuilder->setInsertInto(irInterface);

        // TODO: are there any interface members that should be
        // nested inside the interface type itself?

        irInterface->moveToEnd();

        addTargetIntrinsicDecorations(irInterface, decl);


        return LoweredValInfo::simple(finishOuterGenerics(subBuilder, irInterface));
    }

    LoweredValInfo visitEnumCaseDecl(EnumCaseDecl* decl)
    {
        // A case within an `enum` decl will lower to a value
        // of the `enum`'s "tag" type.
        //
        // TODO: a bit more work will be needed if we allow for
        // enum cases that have payloads, because then we need
        // a function that constructs the value given arguments.
        //
        NestedContext nestedContext(this);
        auto subContext = nestedContext.getContext();

        // Emit any generics that should wrap the actual type.
        emitOuterGenerics(subContext, decl, decl);

        return lowerRValueExpr(subContext, decl->tagExpr);
    }

    LoweredValInfo visitEnumDecl(EnumDecl* decl)
    {
        // Given a declaration of a type, we need to make sure
        // to output "witness tables" for any interfaces this
        // type has declared conformance to.
        for( auto inheritanceDecl : decl->getMembersOfType<InheritanceDecl>() )
        {
            ensureDecl(context, inheritanceDecl);
        }

        NestedContext nestedContext(this);
        auto subBuilder = nestedContext.getBuilder();
        auto subContext = nestedContext.getContext();
        emitOuterGenerics(subContext, decl, decl);

        // An `enum` declaration will currently lower directly to its "tag"
        // type, so that any references to the `enum` become referenes to
        // the tag type instead.
        //
        // TODO: if we ever support `enum` types with payloads, we would
        // need to make the `enum` lower to some kind of custom "tagged union"
        // type.

        IRType* loweredTagType = lowerType(subContext, decl->tagType);

        return LoweredValInfo::simple(finishOuterGenerics(subBuilder, loweredTagType));
    }

    LoweredValInfo visitAggTypeDecl(AggTypeDecl* decl)
    {
        // Don't generate an IR `struct` for intrinsic types
        if(decl->findModifier<IntrinsicTypeModifier>() || decl->findModifier<BuiltinTypeModifier>())
        {
            return LoweredValInfo();
        }

        // Given a declaration of a type, we need to make sure
        // to output "witness tables" for any interfaces this
        // type has declared conformance to.
        for( auto inheritanceDecl : decl->getMembersOfType<InheritanceDecl>() )
        {
            ensureDecl(context, inheritanceDecl);
        }

        // We are going to create nested IR building state
        // to use when emitting the members of the type.
        //
        NestedContext nestedContext(this);
        auto subBuilder = nestedContext.getBuilder();
        auto subContext = nestedContext.getContext();

        // Emit any generics that should wrap the actual type.
        emitOuterGenerics(subContext, decl, decl);

        IRStructType* irStruct = subBuilder->createStructType();
        addNameHint(context, irStruct, decl);
        addLinkageDecoration(context, irStruct, decl);

        subBuilder->setInsertInto(irStruct);

        for (auto fieldDecl : decl->getMembersOfType<VarDeclBase>())
        {
            if (fieldDecl->hasModifier<HLSLStaticModifier>())
            {
                // A `static` field is actually a global variable,
                // and we should emit it as such.
                ensureDecl(context, fieldDecl);
                continue;
            }

            // Each ordinary field will need to turn into a struct "key"
            // that is used for fetching the field.
            IRInst* fieldKeyInst = getSimpleVal(context,
                ensureDecl(context, fieldDecl));
            auto fieldKey = as<IRStructKey>(fieldKeyInst);
            SLANG_ASSERT(fieldKey);

            // Note: we lower the type of the field in the "sub"
            // context, so that any generic parameters that were
            // set up for the type can be referenced by the field type.
            IRType* fieldType = lowerType(
                subContext,
                fieldDecl->getType());

            // Then, the parent `struct` instruction itself will have
            // a "field" instruction.
            subBuilder->createStructField(
                irStruct,
                fieldKey,
                fieldType);
        }

        // There may be members not handled by the above logic (e.g.,
        // member functions), but we will not immediately force them
        // to be emitted here, so as not to risk a circular dependency.
        //
        // Instead we will force emission of all children of aggregate
        // type declarations later, from the top-level emit logic.

        irStruct->moveToEnd();
        addTargetIntrinsicDecorations(irStruct, decl);

        return LoweredValInfo::simple(finishOuterGenerics(subBuilder, irStruct));
    }

    LoweredValInfo lowerMemberVarDecl(VarDecl* fieldDecl)
    {
        // Each field declaration in the AST translates into
        // a "key" that can be used to extract field values
        // from instances of struct types that contain the field.
        //
        // It is correct to say struct *types* because a `struct`
        // nested under a generic can be used to realize a number
        // of different concrete types, but all of these types
        // will use the same space of keys.

        auto builder = getBuilder();
        auto irFieldKey = builder->createStructKey();
        addNameHint(context, irFieldKey, fieldDecl);

        addVarDecorations(context, irFieldKey, fieldDecl);

        addLinkageDecoration(context, irFieldKey, fieldDecl);

        if (auto semanticModifier = fieldDecl->findModifier<HLSLSimpleSemantic>())
        {
            builder->addSemanticDecoration(irFieldKey, semanticModifier->name.getName()->text.getUnownedSlice());
        }

        // We allow a field to be marked as a target intrinsic,
        // so that we can override its mangled name in the
        // output for the chosen target.
        addTargetIntrinsicDecorations(irFieldKey, fieldDecl);


        return LoweredValInfo::simple(irFieldKey);
    }




    // When lowering something callable (most commonly a function declaration),
    // we need to construct an appropriate parameter list for the IR function
    // that folds in any contributions from both the declaration itself *and*
    // its parent declaration(s).
    //
    // For example, given code like:
    //
    //     struct Foo { int bar(float y) { ... } };
    //
    // we need to generate IR-level code something like:
    //
    //     func Foo_bar(Foo this, float y) -> int;
    //
    // that is, the `this` parameter has become explicit.
    //
    // The same applies to generic parameters, and these
    // should apply even if the nested declaration is `static`:
    //
    //     struct Foo<T> { static int bar(T y) { ... } };
    //
    // becomes:
    //
    //     func Foo_bar<T>(T y) -> int;
    //
    // In order to implement this, we are going to do a recursive
    // walk over a declaration and its parents, collecting separate
    // lists of ordinary and generic parameters that will need
    // to be included in the final declaration's parameter list.
    //
    // When doing code generation for an ordinary value parameter,
    // we mostly care about its type, and then also its "direction"
    // (`in`, `out`, `in out`). We sometimes need acess to the
    // original declaration so that we can inspect it for meta-data,
    // but in some cases there is no such declaration (e.g., a `this`
    // parameter doesn't get an explicit declaration in the AST).
    // To handle this we break out the relevant data into derived
    // structures:
    //
    struct ParameterInfo
    {
        // This AST-level type of the parameter
        RefPtr<Type>        type;

        // The direction (`in` vs `out` vs `in out`)
        ParameterDirection  direction;

        // The variable/parameter declaration for
        // this parameter (if any)
        VarDeclBase*        decl;

        // Is this the representation of a `this` parameter?
        bool                isThisParam = false;
    };
    //
    // We need a way to be able to create a `ParameterInfo` given the declaration
    // of a parameter:
    //
    ParameterInfo getParameterInfo(VarDeclBase* paramDecl)
    {
        ParameterInfo info;
        info.type = paramDecl->getType();
        info.decl = paramDecl;
        info.direction = getParameterDirection(paramDecl);
        info.isThisParam = false;
        return info;
    }
    //

    // Here's the declaration for the type to hold the lists:
    struct ParameterLists
    {
        List<ParameterInfo> params;
    };
    //
    // Because there might be a `static` declaration somewhere
    // along the lines, we need to be careful to prohibit adding
    // non-generic parameters in some cases.
    enum ParameterListCollectMode
    {
        // Collect everything: ordinary and generic parameters.
        kParameterListCollectMode_Default,


        // Only collect generic parameters.
        kParameterListCollectMode_Static,
    };
    //
    // We also need to be able to detect whether a declaration is
    // either explicitly or implicitly treated as `static`:
    ParameterListCollectMode getModeForCollectingParentParameters(
        Decl*           decl,
        ContainerDecl*  parentDecl)
    {
        // If we have a `static` parameter, then it is obvious
        // that we should use the `static` mode
        if(isEffectivelyStatic(decl, parentDecl))
            return kParameterListCollectMode_Static;

        // Otherwise, let's default to collecting everything
        return kParameterListCollectMode_Default;
    }
    //
    // When dealing with a member function, we need to be able to add the `this`
    // parameter for the enclosing type:
    //
    void addThisParameter(
        ParameterDirection  direction,
        Type*               type,
        ParameterLists*     ioParameterLists)
    {
        ParameterInfo info;
        info.type = type;
        info.decl = nullptr;
        info.direction = direction;
        info.isThisParam = true;

        ioParameterLists->params.add(info);
    }
    //
    // And here is our function that will do the recursive walk:
    void collectParameterLists(
        Decl*                       decl,
        ParameterLists*             ioParameterLists,
        ParameterListCollectMode    mode)
    {
        // The parameters introduced by any "parent" declarations
        // will need to come first, so we'll deal with that
        // logic here.
        if( auto parentDecl = decl->parentDecl )
        {
            // Compute the mode to use when collecting parameters from
            // the outer declaration. The most important question here
            // is whether parameters of the outer declaration should
            // also count as parameters of the inner declaration.
            ParameterListCollectMode innerMode = getModeForCollectingParentParameters(decl, parentDecl);

            // Don't down-grade our `static`-ness along the chain.
            if(innerMode < mode)
                innerMode = mode;

            // Now collect any parameters from the parent declaration itself
            collectParameterLists(parentDecl, ioParameterLists, innerMode);

            // We also need to consider whether the inner declaration needs to have a `this`
            // parameter corresponding to the outer declaration.
            if( innerMode != kParameterListCollectMode_Static )
            {
                ParameterDirection direction = getThisParamDirection(decl);
                auto thisType = getThisParamTypeForContainer(context, createDefaultSpecializedDeclRef(context, parentDecl));
                if(thisType)
                {
                    addThisParameter(direction, thisType, ioParameterLists);
                }
            }
        }

        // Once we've added any parameters based on parent declarations,
        // we can see if this declaration itself introduces parameters.
        //
        if( auto callableDecl = as<CallableDecl>(decl) )
        {
            // Don't collect parameters from the outer scope if
            // we are in a `static` context.
            if( mode == kParameterListCollectMode_Default )
            {
                for( auto paramDecl : callableDecl->getParameters() )
                {
                    ioParameterLists->params.add(getParameterInfo(paramDecl));
                }
            }
        }
    }

    bool isImportedDecl(Decl* decl)
    {
        return Slang::isImportedDecl(context, decl);
    }

    bool isConstExprVar(Decl* decl)
    {
        if( decl->hasModifier<ConstExprModifier>() )
        {
            return true;
        }
        else if(decl->hasModifier<HLSLStaticModifier>() && decl->hasModifier<ConstModifier>())
        {
            return true;
        }

        return false;
    }

    IRType* maybeGetConstExprType(IRType* type, Decl* decl)
    {
        if(isConstExprVar(decl))
        {
            return getBuilder()->getRateQualifiedType(
                getBuilder()->getConstExprRate(),
                type);
        }

        return type;
    }

    IRGeneric* emitOuterGeneric(
        IRGenContext*   subContext,
        GenericDecl*    genericDecl,
        Decl*           leafDecl)
    {
        auto subBuilder = subContext->irBuilder;

        // Of course, a generic might itself be nested inside of other generics...
        emitOuterGenerics(subContext, genericDecl, leafDecl);

        // We need to create an IR generic

        auto irGeneric = subBuilder->emitGeneric();
        subBuilder->setInsertInto(irGeneric);

        auto irBlock = subBuilder->emitBlock();
        subBuilder->setInsertInto(irBlock);

        // Now emit any parameters of the generic
        //
        // First we start with type and value parameters,
        // in the order they were declared.
        for (auto member : genericDecl->members)
        {
            if (auto typeParamDecl = as<GenericTypeParamDecl>(member))
            {
                // TODO: use a `TypeKind` to represent the
                // classifier of the parameter.
                auto param = subBuilder->emitParam(nullptr);
                addNameHint(context, param, typeParamDecl);
                setValue(subContext, typeParamDecl, LoweredValInfo::simple(param));
            }
            else if (auto valDecl = as<GenericValueParamDecl>(member))
            {
                auto paramType = lowerType(subContext, valDecl->getType());
                auto param = subBuilder->emitParam(paramType);
                addNameHint(context, param, valDecl);
                setValue(subContext, valDecl, LoweredValInfo::simple(param));
            }
        }
        // Then we emit constraint parameters, again in
        // declaration order.
        for (auto member : genericDecl->members)
        {
            if (auto constraintDecl = as<GenericTypeConstraintDecl>(member))
            {
                // TODO: use a `WitnessTableKind` to represent the
                // classifier of the parameter.
                auto param = subBuilder->emitParam(nullptr);
                addNameHint(context, param, constraintDecl);
                setValue(subContext, constraintDecl, LoweredValInfo::simple(param));
            }
        }

        return irGeneric;
    }

    // If the given `decl` is enclosed in any generic declarations, then
    // emit IR-level generics to represent them.
    // The `leafDecl` represents the inner-most declaration we are actually
    // trying to emit, which is the one that should receive the mangled name.
    //
    IRGeneric* emitOuterGenerics(IRGenContext* subContext, Decl* decl, Decl* leafDecl)
    {
        for(auto pp = decl->parentDecl; pp; pp = pp->parentDecl)
        {
            if(auto genericAncestor = as<GenericDecl>(pp))
            {
                return emitOuterGeneric(subContext, genericAncestor, leafDecl);
            }
        }

        return nullptr;
    }

    // If any generic declarations have been created by `emitOuterGenerics`,
    // then finish them off by emitting `return` instructions for the
    // values that they should produce.
    //
    // Return the outer-most generic (if there is one), or the original
    // value (if there were no generics), which should be the IR-level
    // representation of the original declaration.
    //
    IRInst* finishOuterGenerics(
        IRBuilder*  subBuilder,
        IRInst*     val)
    {
        IRInst* v = val;
        for(;;)
        {
            auto parentBlock = as<IRBlock>(v->getParent());
            if (!parentBlock) break;

            auto parentGeneric = as<IRGeneric>(parentBlock->getParent());
            if (!parentGeneric) break;

            subBuilder->setInsertInto(parentBlock);
            subBuilder->emitReturn(v);
            parentGeneric->moveToEnd();

            // There might be more outer generics,
            // so we need to loop until we run out.
            v = parentGeneric;
        }
        return v;
    }

    // Attach target-intrinsic decorations to an instruction,
    // based on modifiers on an AST declaration.
    void addTargetIntrinsicDecorations(
        IRInst* irInst,
        Decl*   decl)
    {
        auto builder = getBuilder();

        for (auto targetMod : decl->getModifiersOfType<TargetIntrinsicModifier>())
        {
            String definition;
            auto definitionToken = targetMod->definitionToken;
            if (definitionToken.type == TokenType::StringLiteral)
            {
                definition = getStringLiteralTokenValue(definitionToken);
            }
            else if(definitionToken.type == TokenType::Identifier)
            {
                definition = definitionToken.getContent();
            }
            else
            {
                definition = decl->getName()->text;
            }

            UnownedStringSlice targetName;
            auto& targetToken = targetMod->targetToken;
            if( targetToken.type != TokenType::Unknown )
            {
                targetName = targetToken.getContent();
            }

            builder->addTargetIntrinsicDecoration(irInst, targetName, definition.getUnownedSlice());
        }
    }

        /// Is `decl` a member function (or effectively a member function) when considered as a stdlib declaration?
    bool isStdLibMemberFuncDecl(
        CallableDecl*   decl)
    {
        // Constructors aren't really member functions, insofar
        // as they aren't called with a `this` parameter.
        //
        // TODO: We may also want to exclude `static` functions
        // here for the same reason, but this routine is only
        // used for the stdlib, where we don't currently have
        // any `static` member functions to worry about.
        //
        if(as<ConstructorDecl>(decl))
            return false;

        auto dd = decl->parentDecl;
        for(;;)
        {
            if(auto genericDecl = as<GenericDecl>(dd))
            {
                dd = genericDecl->parentDecl;
                continue;
            }

            if( auto subscriptDecl = as<SubscriptDecl>(dd) )
            {
                dd = subscriptDecl->parentDecl;
            }

            break;
        }

        // Note: the use of `AggTypeDeclBase` here instead of just
        // `AggTypeDecl` means that we consider a declaration that
        // is under a `struct` *or* an `extension` to be a member
        // function for our purposes.
        //
        if(as<AggTypeDeclBase>(dd))
            return true;

        return false;
    }

        /// Add a "catch-all" decoration for a stdlib function if it would be needed
    void addCatchAllIntrinsicDecorationIfNeeded(
        IRInst*             irInst,
        FunctionDeclBase*   decl)
    {
        // We don't need an intrinsic decoration on a function that has a body,
        // since the body can be used as the "catch-all" case.
        //
        if(decl->body)
            return;

        // Only standard library declarations should get any kind of catch-all
        // treatment by default. Declarations in user case are responsible
        // for marking things as target intrinsics if they want to go down
        // that (unsupported) route.
        //
        if(!isFromStdLib(decl))
            return;

        // No need to worry about functions that lower to intrinsic IR opcodes
        // (or pseudo-ops).
        //
        if(decl->findModifier<IntrinsicOpModifier>())
            return;

        // We also don't need an intrinsic decoration if the function already
        // had a catch-all case on one of its target overloads.
        //
        for( auto f = decl->primaryDecl; f; f = f->nextDecl )
        {
            for(auto targetMod : f->getModifiersOfType<TargetIntrinsicModifier>())
            {
                // If we find a catch-all case (marked as either *no* target
                // token or an empty target name), then we should bail out.
                //
                if(targetMod->targetToken.type == TokenType::Unknown)
                    return;
                else if(!targetMod->targetToken.hasContent())
                    return;
            }
        }

        String definition;
        
        // If we have a member function, then we want the default intrinsic
        // definition to reflect this fact so that we can emit it correctly
        // (the assumption is that a catch-all definition of a member function
        // is itself implemented as a member function).
        //
        if( isStdLibMemberFuncDecl(decl) )
        {
            // We will mark member functions by appending a `.` to the
            // start of their name.
            //
            definition.append(".");
        }

        // We want to output the name of the declaration,
        // but in some cases the actual `decl` that has
        // to be emitted is not the one with the name.
        //
        // In particular, an accessor declaration (e.g.,
        // a `get`ter` in a subscript or property) doesn't
        // have a name, but its parent should.
        //
        Decl* declForName = decl;
        if(auto accessorDecl = as<AccessorDecl>(decl))
            declForName = decl->parentDecl;

        definition.append(getText(declForName->getName()));

        getBuilder()->addTargetIntrinsicDecoration(irInst, UnownedStringSlice(), definition.getUnownedSlice());
    }

    void addParamNameHint(IRInst* inst, ParameterInfo info)
    {
        if(auto decl = info.decl)
        {
            addNameHint(context, inst, decl);
        }
        else if( info.isThisParam )
        {
            addNameHint(context, inst, "this");
        }
    }

    IRIntLit* _getIntLitFromAttribute(IRBuilder* builder, Attribute* attrib)
    {
        attrib->args.getCount();
        SLANG_ASSERT(attrib->args.getCount() ==1);
        Expr* expr = attrib->args[0];
        auto intLitExpr = as<IntegerLiteralExpr>(expr);
        SLANG_ASSERT(intLitExpr);
        return as<IRIntLit>(builder->getIntValue(builder->getIntType(), intLitExpr->value));
    }

    IRStringLit* _getStringLitFromAttribute(IRBuilder* builder, Attribute* attrib)
    {
        attrib->args.getCount();
        SLANG_ASSERT(attrib->args.getCount() == 1);
        Expr* expr = attrib->args[0];

        auto stringLitExpr = as<StringLiteralExpr>(expr);
        SLANG_ASSERT(stringLitExpr);
        return as<IRStringLit>(builder->getStringValue(stringLitExpr->value.getUnownedSlice()));
    }

    LoweredValInfo lowerFuncDecl(FunctionDeclBase* decl)
    {
        // We are going to use a nested builder, because we will
        // change the parent node that things get nested into.
        //
        NestedContext nestedContext(this);
        auto subBuilder = nestedContext.getBuilder();
        auto subContext = nestedContext.getContext();

        // The actual `IRFunction` that we emit needs to be nested
        // inside of one `IRGeneric` for every outer `GenericDecl`
        // in the declaration hierarchy.

        emitOuterGenerics(subContext, decl, decl);

        // Collect the parameter lists we will use for our new function.
        ParameterLists parameterLists;
        collectParameterLists(decl, &parameterLists, kParameterListCollectMode_Default);

        // TODO: if there are any generic parameters in the collected list, then
        // we need to output an IR function with generic parameters (or a generic
        // with a nested function... the exact representation is still TBD).

        // In most cases the return type for a declaration can be read off the declaration
        // itself, but things get a bit more complicated when we have to deal with
        // accessors for subscript declarations (and eventually for properties).
        //
        // We compute a declaration to use for looking up the return type here:
        CallableDecl* declForReturnType = decl;
        if (auto accessorDecl = as<AccessorDecl>(decl))
        {
            // We are some kind of accessor, so the parent declaration should
            // know the correct return type to expose.
            //
            auto parentDecl = accessorDecl->parentDecl;
            if (auto subscriptDecl = as<SubscriptDecl>(parentDecl))
            {
                declForReturnType = subscriptDecl;
            }
        }

        // need to create an IR function here

        IRFunc* irFunc = subBuilder->createFunc();
        addNameHint(context, irFunc, decl);
        addLinkageDecoration(context, irFunc, decl);

        List<IRType*> paramTypes;

        for( auto paramInfo : parameterLists.params )
        {
            IRType* irParamType = lowerType(subContext, paramInfo.type);

            switch( paramInfo.direction )
            {
            case kParameterDirection_In:
                // Simple case of a by-value input parameter.
                break;

            // If the parameter is declared `out` or `inout`,
            // then we will represent it with a pointer type in
            // the IR, but we will use a specialized pointer
            // type that encodes the parameter direction information.
            case kParameterDirection_Out:
                irParamType = subBuilder->getOutType(irParamType);
                break;
            case kParameterDirection_InOut:
                irParamType = subBuilder->getInOutType(irParamType);
                break;
            case kParameterDirection_Ref:
                irParamType = subBuilder->getRefType(irParamType);
                break;

            default:
                SLANG_UNEXPECTED("unknown parameter direction");
                break;
            }

            // If the parameter was explicitly marked as being a compile-time
            // constant (`constexpr`), then attach that information to its
            // IR-level type explicitly.
            if( paramInfo.decl )
            {
                irParamType = maybeGetConstExprType(irParamType, paramInfo.decl);
            }

            paramTypes.add(irParamType);
        }

        auto irResultType = lowerType(subContext, declForReturnType->returnType);

        if (auto setterDecl = as<SetterDecl>(decl))
        {
            // We are lowering a "setter" accessor inside a subscript
            // declaration, which means we don't want to *return* the
            // stated return type of the subscript, but instead take
            // it as a parameter.
            //
            IRType* irParamType = irResultType;
            paramTypes.add(irParamType);

            // Instead, a setter always returns `void`
            //
            irResultType = subBuilder->getVoidType();
        }

        if( auto refAccessorDecl = as<RefAccessorDecl>(decl) )
        {
            // A `ref` accessor needs to return a *pointer* to the value
            // being accessed, rather than a simple value.
            irResultType = subBuilder->getPtrType(irResultType);
        }

        auto irFuncType = subBuilder->getFuncType(
            paramTypes.getCount(),
            paramTypes.getBuffer(),
            irResultType);
        irFunc->setFullType(irFuncType);

        subBuilder->setInsertInto(irFunc);

        // If a function is imported from another module then
        // we usually don't want to emit it as a definition, and
        // will instead only emit a declaration for it with an
        // appropriate `[import(...)]` linkage decoration.
        //
        // However, if the function is marked with `[__unsafeForceInlineEarly]`
        // then we need to make sure the IR for its definition is available
        // to the mandatory optimization passes.
        //
        // TODO: The design here means that we will re-emit the inline
        // function from its AST in every module that uses it. We should
        // instead have logic to clone the target function in from the
        // pre-generated IR for the module that defines it (or do some kind
        // of minimal linking to bring in the inline functions).
        //
        if (isImportedDecl(decl) && !isForceInlineEarly(decl))
        {
            // Always emit imported declarations as declarations,
            // and not definitions.
        }
        else if (!decl->body)
        {
            // This is a function declaration without a body.
            // In Slang we currently try not to support forward declarations
            // (although we might have to give in eventually), so
            // this case should really only occur for builtin declarations.
        }
        else
        {
            // This is a function definition, so we need to actually
            // construct IR for the body...
            IRBlock* entryBlock = subBuilder->emitBlock();
            subBuilder->setInsertInto(entryBlock);

            UInt paramTypeIndex = 0;
            for( auto paramInfo : parameterLists.params )
            {
                auto irParamType = paramTypes[paramTypeIndex++];

                LoweredValInfo paramVal;

                switch( paramInfo.direction )
                {
                default:
                    {
                        // The parameter is being used for input/output purposes,
                        // so it will lower to an actual parameter with a pointer type.
                        //
                        // TODO: Is this the best representation we can use?

                        IRParam* irParamPtr = subBuilder->emitParam(irParamType);
                        if(auto paramDecl = paramInfo.decl)
                        {
                            addVarDecorations(context, irParamPtr, paramDecl);
                            subBuilder->addHighLevelDeclDecoration(irParamPtr, paramDecl);
                        }
                        addParamNameHint(irParamPtr, paramInfo);

                        paramVal = LoweredValInfo::ptr(irParamPtr);

                        // TODO: We might want to copy the pointed-to value into
                        // a temporary at the start of the function, and then copy
                        // back out at the end, so that we don't have to worry
                        // about things like aliasing in the function body.
                        //
                        // For now we will just use the storage that was passed
                        // in by the caller, knowing that our current lowering
                        // at call sites will guarantee a fresh/unique location.
                    }
                    break;

                case kParameterDirection_In:
                    {
                        // Simple case of a by-value input parameter.
                        //
                        // We start by declaring an IR parameter of the same type.
                        //
                        auto paramDecl = paramInfo.decl;
                        IRParam* irParam = subBuilder->emitParam(irParamType);
                        if( paramDecl )
                        {
                            addVarDecorations(context, irParam, paramDecl);
                            subBuilder->addHighLevelDeclDecoration(irParam, paramDecl);
                        }
                        addParamNameHint(irParam, paramInfo);
                        paramVal = LoweredValInfo::simple(irParam);
                        //
                        // HLSL allows a function parameter to be used as a local
                        // variable in the function body (just like C/C++), so
                        // we need to support that case as well.
                        //
                        // However, if we notice that the parameter was marked
                        // `const`, then we can skip this step.
                        //
                        // TODO: we should consider having all parameter be implicitly
                        // immutable except in a specific "compatibility mode."
                        //
                        if(paramDecl && paramDecl->findModifier<ConstModifier>())
                        {
                            // This parameter was declared to be immutable,
                            // so there should be no assignment to it in the
                            // function body, and we don't need a temporary.
                        }
                        else
                        {
                            // The parameter migth get used as a temporary in
                            // the function body. We will allocate a mutable
                            // local variable for is value, and then assign
                            // from the parameter to the local at the start
                            // of the function.
                            //
                            auto irLocal = subBuilder->emitVar(irParamType);
                            auto localVal = LoweredValInfo::ptr(irLocal);
                            assign(subContext, localVal, paramVal);
                            //
                            // When code later in the body of the function refers
                            // to the parameter declaration, it will actually refer
                            // to the value stored in the local variable.
                            //
                            paramVal = localVal;
                        }
                    }
                    break;
                }

                if( auto paramDecl = paramInfo.decl )
                {
                    setValue(subContext, paramDecl, paramVal);
                }

                if (paramInfo.isThisParam)
                {
                    subContext->thisVal = paramVal;
                }
            }

            if (auto setterDecl = as<SetterDecl>(decl))
            {
                // Add the IR parameter for the new value
                IRType* irParamType = irResultType;
                auto irParam = subBuilder->emitParam(irParamType);
                addNameHint(context, irParam, "newValue");

                // TODO: we need some way to wire this up to the `newValue`
                // or whatever name we give for that parameter inside
                // the setter body.
            }

            {

                auto attr = decl->findModifier<PatchConstantFuncAttribute>();

                // I needed to test for patchConstantFuncDecl here
                // because it is only set if validateEntryPoint is called with Hull as the required stage
                // If I just build domain shader, and then the attribute exists, but patchConstantFuncDecl is not set
                // and thus leads to a crash.
                if (attr && attr->patchConstantFuncDecl)
                {
                    // We need to lower the function
                    FuncDecl* patchConstantFunc = attr->patchConstantFuncDecl;
                    assert(patchConstantFunc);

                    // Convert the patch constant function into IRInst
                    IRInst* irPatchConstantFunc = getSimpleVal(context, ensureDecl(subContext, patchConstantFunc));

                    // Attach a decoration so that our IR function references
                    // the patch constant function.
                    //
                    subContext->irBuilder->addPatchConstantFuncDecoration(
                        irFunc,
                        irPatchConstantFunc);

                }
            }


            // We will now set about emitting the code for the body of
            // the function/callable.
            //
            // In the case of an initializer ("constructor") declaration,
            // the `this` value is not a parameter, but rather a placeholder
            // for the value that will be returned. We thus need to set up
            // a local variable to represent this value.
            //
            auto constructorDecl = as<ConstructorDecl>(decl);
            if(constructorDecl)
            {
                auto thisVar = subContext->irBuilder->emitVar(irResultType);
                subContext->thisVal = LoweredValInfo::ptr(thisVar);
            }

            // We lower whatever statement was stored on the declaration
            // as the body of the new IR function.
            //
            lowerStmt(subContext, decl->body);

            // We need to carefully add a terminator instruction to the end
            // of the body, in case the user didn't do so.
            //
            if (!subContext->irBuilder->getBlock()->getTerminator())
            {
                if(constructorDecl)
                {
                    // A constructor declaration should return the
                    // value of the `this` variable that was set
                    // up at the start.
                    //
                    // TODO: This should also apply if any code
                    // path in an initializer/constructor attempts
                    // to do an early `return;`.
                    //
                    subContext->irBuilder->emitReturn(
                        getSimpleVal(subContext, subContext->thisVal));
                }
                else if(as<IRVoidType>(irResultType))
                {
                    // `void`-returning function can get an implicit
                    // return on exit of the body statement.
                    subContext->irBuilder->emitReturn();
                }
                else
                {
                    // Value-returning function is expected to `return`
                    // on every control-flow path. We need to enforce
                    // this by putting an `unreachable` terminator here,
                    // and then emit a dataflow error if this block
                    // can't be eliminated.
                    subContext->irBuilder->emitMissingReturn();
                }
            }
        }

        getBuilder()->addHighLevelDeclDecoration(irFunc, decl);

        // If this declaration was marked as being an intrinsic for a particular
        // target, then we should reflect that here.
        for( auto targetMod : decl->getModifiersOfType<SpecializedForTargetModifier>() )
        {
            // `targetMod` indicates that this particular declaration represents
            // a specialized definition of the particular function for the given
            // target, and we need to reflect that at the IR level.

            getBuilder()->addTargetDecoration(irFunc, targetMod->targetToken.getContent());
        }

        // If this declaration was marked as having a target-specific lowering
        // for a particular target, then handle that here.
        addTargetIntrinsicDecorations(irFunc, decl);

        addCatchAllIntrinsicDecorationIfNeeded(irFunc, decl);

        // If this declaration requires certain GLSL extension (or a particular GLSL version)
        // for it to be usable, then declare that here.
        //
        // TODO: We should wrap this an `SpecializedForTargetModifier` together into a single
        // case for enumerating the "capabilities" that a declaration requires.
        //
        for(auto extensionMod : decl->getModifiersOfType<RequiredGLSLExtensionModifier>())
        {
            getBuilder()->addRequireGLSLExtensionDecoration(irFunc, extensionMod->extensionNameToken.getContent());
        }
        for(auto versionMod : decl->getModifiersOfType<RequiredGLSLVersionModifier>())
        {
            getBuilder()->addRequireGLSLVersionDecoration(irFunc, Int(getIntegerLiteralValue(versionMod->versionNumberToken)));
        }
        for (auto versionMod : decl->getModifiersOfType<RequiredSPIRVVersionModifier>())
        {
            getBuilder()->addRequireSPIRVVersionDecoration(irFunc, versionMod->version);
        }
        for (auto versionMod : decl->getModifiersOfType<RequiredCUDASMVersionModifier>())
        {
            getBuilder()->addRequireCUDASMVersionDecoration(irFunc, versionMod->version);
        }

        if (auto attr = decl->findModifier<InstanceAttribute>())
        {
            IRIntLit* intLit = _getIntLitFromAttribute(getBuilder(), attr);
            getBuilder()->addDecoration(irFunc, kIROp_InstanceDecoration, intLit);
        }

        if(auto attr = decl->findModifier<MaxVertexCountAttribute>())
        {
            IRIntLit* intLit = _getIntLitFromAttribute(getBuilder(), attr);
            getBuilder()->addDecoration(irFunc, kIROp_MaxVertexCountDecoration, intLit);
        }

        if(auto attr = decl->findModifier<NumThreadsAttribute>())
        {
            auto builder = getBuilder();
            IRType* intType = builder->getIntType();

            IRInst* operands[3] = {
                builder->getIntValue(intType, attr->x),
                builder->getIntValue(intType, attr->y),
                builder->getIntValue(intType, attr->z)
            };

           builder->addDecoration(irFunc, kIROp_NumThreadsDecoration, operands, 3);
        }

        if(decl->findModifier<ReadNoneAttribute>())
        {
            getBuilder()->addSimpleDecoration<IRReadNoneDecoration>(irFunc);
        }

        if (decl->findModifier<EarlyDepthStencilAttribute>())
        {
            getBuilder()->addSimpleDecoration<IREarlyDepthStencilDecoration>(irFunc);
        }

        if (auto attr = decl->findModifier<DomainAttribute>())
        {
            IRStringLit* stringLit = _getStringLitFromAttribute(getBuilder(), attr);
            getBuilder()->addDecoration(irFunc, kIROp_DomainDecoration, stringLit);
        }

        if (auto attr = decl->findModifier<PartitioningAttribute>())
        {
            IRStringLit* stringLit = _getStringLitFromAttribute(getBuilder(), attr);
            getBuilder()->addDecoration(irFunc, kIROp_PartitioningDecoration, stringLit);
        }

        if (auto attr = decl->findModifier<OutputTopologyAttribute>())
        {
            IRStringLit* stringLit = _getStringLitFromAttribute(getBuilder(), attr);
            getBuilder()->addDecoration(irFunc, kIROp_OutputTopologyDecoration, stringLit);
        }

        if (auto attr = decl->findModifier<OutputControlPointsAttribute>())
        {
            IRIntLit* intLit = _getIntLitFromAttribute(getBuilder(), attr);
            getBuilder()->addDecoration(irFunc, kIROp_OutputControlPointsDecoration, intLit);
        }

        if(decl->findModifier<UnsafeForceInlineEarlyAttribute>())
        {
            getBuilder()->addDecoration(irFunc, kIROp_UnsafeForceInlineEarlyDecoration);
        }

        // For convenience, ensure that any additional global
        // values that were emitted while outputting the function
        // body appear before the function itself in the list
        // of global values.
        irFunc->moveToEnd();
        return LoweredValInfo::simple(finishOuterGenerics(subBuilder, irFunc));
    }

    LoweredValInfo visitGenericDecl(GenericDecl * genDecl)
    {
        // TODO: Should this just always visit/lower the inner decl?

        if (auto innerFuncDecl = as<FunctionDeclBase>(genDecl->inner))
            return ensureDecl(context, innerFuncDecl);
        else if (auto innerStructDecl = as<StructDecl>(genDecl->inner))
        {
            ensureDecl(context, innerStructDecl);
            return LoweredValInfo();
        }
        else if( auto extensionDecl = as<ExtensionDecl>(genDecl->inner) )
        {
            return ensureDecl(context, extensionDecl);
        }
        SLANG_RELEASE_ASSERT(false);
        UNREACHABLE_RETURN(LoweredValInfo());
    }

    LoweredValInfo visitFunctionDeclBase(FunctionDeclBase* decl)
    {
        // A function declaration may have multiple, target-specific
        // overloads, and we need to emit an IR version of each of these.

        // The front end will form a linked list of declarations with
        // the same signature, whenever there is any kind of redeclaration.
        // We will look to see if that linked list has been formed.
        auto primaryDecl = decl->primaryDecl;

        if (!primaryDecl)
        {
            // If there is no linked list then we are in the ordinary
            // case with a single declaration, and no special handling
            // is needed.
            return lowerFuncDecl(decl);
        }

        // Otherwise, we need to walk the linked list of declarations
        // and make sure to emit IR code for any targets that need it.

        // TODO: Need to be careful about how this is approached,
        // to avoid emitting a bunch of extra definitions in the IR.

        auto primaryFuncDecl = as<FunctionDeclBase>(primaryDecl);
        SLANG_ASSERT(primaryFuncDecl);
        LoweredValInfo result = lowerFuncDecl(primaryFuncDecl);
        for (auto dd = primaryDecl->nextDecl; dd; dd = dd->nextDecl)
        {
            auto funcDecl = as<FunctionDeclBase>(dd);
            SLANG_ASSERT(funcDecl);
            lowerFuncDecl(funcDecl);
        }
        return result;
    }
};

LoweredValInfo lowerDecl(
    IRGenContext*   context,
    DeclBase*       decl)
{
    IRBuilderSourceLocRAII sourceLocInfo(context->irBuilder, decl->loc);

    DeclLoweringVisitor visitor;
    visitor.context = context;

    try
    {
        return visitor.dispatch(decl);
    }
    // Don't emit any context message for an explicit `AbortCompilationException`
    // because it should only happen when an error is already emitted.
    catch(const AbortCompilationException&) { throw; }
    catch(...)
    {
        context->getSink()->noteInternalErrorLoc(decl->loc);
        throw;
    }
}

// Ensure that a version of the given declaration has been emitted to the IR
LoweredValInfo ensureDecl(
    IRGenContext*   context,
    Decl*           decl)
{
    auto shared = context->shared;

    LoweredValInfo result;

    // Look for an existing value installed in this context
    auto env = context->env;
    while(env)
    {
        if(env->mapDeclToValue.TryGetValue(decl, result))
            return result;

        env = env->outer;
    }

    IRBuilder subIRBuilder;
    subIRBuilder.sharedBuilder = context->irBuilder->sharedBuilder;
    subIRBuilder.setInsertInto(subIRBuilder.sharedBuilder->module->getModuleInst());

    IRGenEnv subEnv;
    subEnv.outer = context->env;

    IRGenContext subContext = *context;
    subContext.irBuilder = &subIRBuilder;
    subContext.env = &subEnv;

    result = lowerDecl(&subContext, decl);

    // By default assume that any value we are lowering represents
    // something that should be installed globally.
    setGlobalValue(shared, decl, result);

    return result;
}

IRInst* lowerSubstitutionArg(
    IRGenContext*   context,
    Val*            val)
{
    if (auto type = dynamicCast<Type>(val))
    {
        return lowerType(context, type);
    }
    else if (auto declaredSubtypeWitness = as<DeclaredSubtypeWitness>(val))
    {
        // We need to look up the IR-level representation of the witness (which will be a witness table).
        auto irWitnessTable = getSimpleVal(
            context,
            emitDeclRef(
                context,
                declaredSubtypeWitness->declRef,
                context->irBuilder->getWitnessTableType()));
        return irWitnessTable;
    }
    else
    {
        SLANG_UNIMPLEMENTED_X("value cases");
        UNREACHABLE_RETURN(nullptr);
    }
}

// Can the IR lowered version of this declaration ever be an `IRGeneric`?
bool canDeclLowerToAGeneric(RefPtr<Decl> decl)
{
    // A callable decl lowers to an `IRFunc`, and can be generic
    if(as<CallableDecl>(decl)) return true;

    // An aggregate type decl lowers to an `IRStruct`, and can be generic
    if(as<AggTypeDecl>(decl)) return true;

    // An inheritance decl lowers to an `IRWitnessTable`, and can be generic
    if(as<InheritanceDecl>(decl)) return true;

    // A `typedef` declaration nested under a generic will turn into
    // a generic that returns a type (a simple type-level function).
    if(as<TypeDefDecl>(decl)) return true;

    return false;
}

LoweredValInfo emitDeclRef(
    IRGenContext*           context,
    RefPtr<Decl>            decl,
    RefPtr<Substitutions>   subst,
    IRType*                 type)
{
    // We need to proceed by considering the specializations that
    // have been put in place.

    // Ignore any global generic type substitutions during lowering.
    // Really, we don't even expect these to appear.
    while(auto globalGenericSubst = as<GlobalGenericParamSubstitution>(subst))
        subst = globalGenericSubst->outer;

    // If the declaration would not get wrapped in a `IRGeneric`,
    // even if it is nested inside of an AST `GenericDecl`, then
    // we should also ignore any generic substitutions.
    if(!canDeclLowerToAGeneric(decl))
    {
        while(auto genericSubst = as<GenericSubstitution>(subst))
            subst = genericSubst->outer;
    }

    // In the simplest case, there is no specialization going
    // on, and the decl-ref turns into a reference to the
    // lowered IR value for the declaration.
    if(!subst)
    {
        LoweredValInfo loweredDecl = ensureDecl(context, decl);
        return loweredDecl;
    }

    // Otherwise, we look at the kind of substitution, and let it guide us.
    if(auto genericSubst = subst.as<GenericSubstitution>())
    {
        // A generic substitution means we will need to output
        // a `specialize` instruction to specialize the generic.
        //
        // First we want to emit the value without generic specialization
        // applied, to get a correct value for it.
        //
        // Note: we only "unwrap" a single layer from the
        // substitutions here, because the underlying declaration
        // might be nested in multiple generics, or it might
        // come from an interface.
        //
        LoweredValInfo genericVal = emitDeclRef(
            context,
            decl,
            genericSubst->outer,
            context->irBuilder->getGenericKind());

        // There's no reason to specialize something that maps to a NULL pointer.
        if (genericVal.flavor == LoweredValInfo::Flavor::None)
            return LoweredValInfo();

        // We can only really specialize things that map to single values.
        // It would be an error if we got a non-`None` value that
        // wasn't somehow a single value.
        auto irGenericVal = getSimpleVal(context, genericVal);

        // We have the IR value for the generic we'd like to specialize,
        // and now we need to get the value for the arguments.
        List<IRInst*> irArgs;
        for (auto argVal : genericSubst->args)
        {
            auto irArgVal = lowerSimpleVal(context, argVal);
            SLANG_ASSERT(irArgVal);
            irArgs.add(irArgVal);
        }

        // Once we have both the generic and its arguments,
        // we can emit a `specialize` instruction and use
        // its value as the result.
        auto irSpecializedVal = context->irBuilder->emitSpecializeInst(
            type,
            irGenericVal,
            irArgs.getCount(),
            irArgs.getBuffer());

        return LoweredValInfo::simple(irSpecializedVal);
    }
    else if(auto thisTypeSubst = subst.as<ThisTypeSubstitution>())
    {
        if(decl.Ptr() == thisTypeSubst->interfaceDecl)
        {
            // This is a reference to the interface type itself,
            // through the this-type substitution, so it is really
            // a reference to the this-type.
            return lowerType(context, thisTypeSubst->witness->sub);
        }

        // Somebody is trying to look up an interface requirement
        // "through" some concrete type. We need to lower this decl-ref
        // as a lookup of the corresponding member in a witness table.
        //
        // The witness table itself is referenced by the this-type
        // substitution, so we can just lower that.
        //
        // Note: unlike the case for generics above, in the interface-lookup
        // case, we don't end up caring about any further outer substitutions.
        // That is because even if we are naming `ISomething<Foo>.doIt()`,
        // a method inside a generic interface, we don't actually care
        // about the substitution of `Foo` for the parameter `T` of
        // `ISomething<T>`. That is because we really care about the
        // witness table for the concrete type that conforms to `ISomething<Foo>`.
        //
        auto irWitnessTable = lowerSimpleVal(context, thisTypeSubst->witness);
        //
        // The key to use for looking up the interface member is
        // derived from the declaration.
        //
        auto irRequirementKey = getInterfaceRequirementKey(context, decl);
        //
        // Those two pieces of information tell us what we need to
        // do in order to look up the value that satisfied the requirement.
        //
        auto irSatisfyingVal = context->irBuilder->emitLookupInterfaceMethodInst(
            type,
            irWitnessTable,
            irRequirementKey);
        return LoweredValInfo::simple(irSatisfyingVal);
    }
    else
    {
        SLANG_UNEXPECTED("uhandled substitution type");
        UNREACHABLE_RETURN(LoweredValInfo());
    }
}

LoweredValInfo emitDeclRef(
    IRGenContext*   context,
    DeclRef<Decl>   declRef,
    IRType*         type)
{
    return emitDeclRef(
        context,
        declRef.decl,
        declRef.substitutions.substitutions,
        type);
}

static void lowerFrontEndEntryPointToIR(
    IRGenContext*   context,
    EntryPoint*     entryPoint)
{
    // TODO: We should emit an entry point as a dedicated IR function
    // (distinct from the IR function used if it were called normally),
    // with a mangled name based on the original function name plus
    // the stage for which it is being compiled as an entry point (so
    // that entry points for distinct stages always have distinct names).
    //
    // For now we just have an (implicit) constraint that a given
    // function should only be used as an entry point for one stage,
    // and any such function should *not* be used as an ordinary function.

    auto entryPointFuncDecl = entryPoint->getFuncDecl();

    auto builder = context->irBuilder;
    builder->setInsertInto(builder->getModule()->getModuleInst());

    auto loweredEntryPointFunc = getSimpleVal(context,
        ensureDecl(context, entryPointFuncDecl));

    // Attach a marker decoration so that we recognize
    // this as an entry point.
    //
    IRInst* instToDecorate = loweredEntryPointFunc;
    if(auto irGeneric = as<IRGeneric>(instToDecorate))
    {
        instToDecorate = findGenericReturnVal(irGeneric);
    }

    {
        Name* entryPointName = entryPoint->getFuncDecl()->getName();
        builder->addEntryPointDecoration(instToDecorate, entryPoint->getProfile(), entryPointName->text.getUnownedSlice());
    }

    // Go through the entry point parameters creating decorations from layout as appropriate
    // But only if this is a definition not a declaration
    if (isDefinition(instToDecorate))
    {
        FilteredMemberList<ParamDecl> params = entryPointFuncDecl->getParameters();

        IRGlobalValueWithParams* valueWithParams = as<IRGlobalValueWithParams>(instToDecorate);
        if (valueWithParams)
        {
            IRParam* irParam = valueWithParams->getFirstParam();

            for (auto param : params)
            {
                if (auto modifier = param->findModifier<HLSLGeometryShaderInputPrimitiveTypeModifier>())
                {
                    IROp op = kIROp_Invalid;

                    if (as<HLSLTriangleModifier>(modifier))
                        op = kIROp_TriangleInputPrimitiveTypeDecoration;
                    else if (as<HLSLPointModifier>(modifier))
                        op = kIROp_PointInputPrimitiveTypeDecoration;
                    else if (as<HLSLLineModifier>(modifier))
                        op = kIROp_LineInputPrimitiveTypeDecoration; 
                    else if (as<HLSLLineAdjModifier>(modifier))
                        op = kIROp_LineAdjInputPrimitiveTypeDecoration;
                    else if (as<HLSLTriangleAdjModifier>(modifier))
                        op = kIROp_TriangleAdjInputPrimitiveTypeDecoration;

                    if (op != kIROp_Invalid)
                    {
                        builder->addDecoration(irParam, op);
                    }
                    else
                    {
                        SLANG_UNEXPECTED("unhandled primitive type");
                    }
                }

                irParam = irParam->getNextParam();
            }
        }
    }
}

static void lowerProgramEntryPointToIR(
    IRGenContext*                               context,
    EntryPoint*                                 entryPoint,
    EntryPoint::EntryPointSpecializationInfo*   specializationInfo)
{
    auto entryPointFuncDeclRef = entryPoint->getFuncDeclRef();
    if(specializationInfo)
        entryPointFuncDeclRef = specializationInfo->specializedFuncDeclRef;

    // First, lower the entry point like an ordinary function

    auto entryPointFuncType = lowerType(context, getFuncType(context->astBuilder, entryPointFuncDeclRef));

    auto builder = context->irBuilder;
    builder->setInsertInto(builder->getModule()->getModuleInst());

    auto loweredEntryPointFunc = getSimpleVal(context,
        emitDeclRef(context, entryPointFuncDeclRef, entryPointFuncType));

    if(!loweredEntryPointFunc->findDecoration<IRLinkageDecoration>())
    {
        builder->addExportDecoration(loweredEntryPointFunc, getMangledName(context->astBuilder, entryPointFuncDeclRef).getUnownedSlice());
    }

    // We may have shader parameters of interface/existential type,
    // which need us to supply concrete type information for specialization.
    //
    if(specializationInfo && specializationInfo->existentialSpecializationArgs.getCount() != 0)
    {
        List<IRInst*> existentialSlotArgs;
        for(auto arg : specializationInfo->existentialSpecializationArgs )
        {

            auto irArgType = lowerSimpleVal(context, arg.val);
            existentialSlotArgs.add(irArgType);

            if(auto witness = arg.witness)
            {
                auto irWitnessTable = lowerSimpleVal(context, witness);
                existentialSlotArgs.add(irWitnessTable);
            }
        }

        builder->addBindExistentialSlotsDecoration(loweredEntryPointFunc, existentialSlotArgs.getCount(), existentialSlotArgs.getBuffer());
    }
}

    /// Ensure that `decl` and all relevant declarations under it get emitted.
static void ensureAllDeclsRec(
    IRGenContext*   context,
    Decl*           decl)
{
    ensureDecl(context, decl);

    // Note: We are checking here for aggregate type declarations, and
    // not for `ContainerDecl`s in general. This is because many kinds
    // of container declarations will already take responsibility for emitting
    // their children directly (e.g., a function declaration is responsible
    // for emitting its own parameters).
    //
    // Aggregate types are the main case where we can emit an outer declaration
    // and not the stuff nested inside of it.
    //
    if(auto containerDecl = as<AggTypeDeclBase>(decl))
    {
        for (auto memberDecl : containerDecl->members)
        {
            ensureAllDeclsRec(context, memberDecl);
        }
    }
    else if (auto genericDecl = as<GenericDecl>(decl))
    {
        ensureAllDeclsRec(context, genericDecl->inner);
    }
}

IRModule* generateIRForTranslationUnit(
    ASTBuilder* astBuilder,
    TranslationUnitRequest* translationUnit)
{
    auto compileRequest = translationUnit->compileRequest;

    SharedIRGenContext sharedContextStorage(
        translationUnit->getSession(),
        translationUnit->compileRequest->getSink(),
        translationUnit->compileRequest->getLinkage()->m_obfuscateCode,
        translationUnit->getModuleDecl());
    SharedIRGenContext* sharedContext = &sharedContextStorage;

    IRGenContext contextStorage(sharedContext, astBuilder);
    IRGenContext* context = &contextStorage;

    SharedIRBuilder sharedBuilderStorage;
    SharedIRBuilder* sharedBuilder = &sharedBuilderStorage;
    sharedBuilder->module = nullptr;
    sharedBuilder->session = compileRequest->getSession();

    IRBuilder builderStorage;
    IRBuilder* builder = &builderStorage;
    builder->sharedBuilder = sharedBuilder;

    IRModule* module = builder->createModule();
    sharedBuilder->module = module;

    context->irBuilder = builder;

    // We need to emit IR for all public/exported symbols
    // in the translation unit.
    //
    // For now, we will assume that *all* global-scope declarations
    // represent public/exported symbols.

    // First, ensure that all entry points have been emitted,
    // in case they require special handling.
    for (auto entryPoint : translationUnit->getEntryPoints())
    {
        lowerFrontEndEntryPointToIR(context, entryPoint);
    }

    //
    // Next, ensure that all other global declarations have
    // been emitted.
    for (auto decl : translationUnit->getModuleDecl()->members)
    {
        ensureAllDeclsRec(context, decl);
    }

    // Build a global instruction to hold all the string
    // literals used in the module.
    {
        auto& stringLits = sharedContext->m_stringLiterals;
        auto stringLitCount = stringLits.getCount();
        if(stringLitCount != 0)
        {
            builder->setInsertInto(module->getModuleInst());
            builder->emitIntrinsicInst(builder->getVoidType(), kIROp_GlobalHashedStringLiterals, stringLitCount, stringLits.getBuffer());
        }
    }

#if 0
    fprintf(stderr, "### GENERATED\n");
    dumpIR(module);
    fprintf(stderr, "###\n");
#endif

    validateIRModuleIfEnabled(compileRequest, module);

    // We will perform certain "mandatory" optimization passes now.
    // These passes serve two purposes:
    //
    // 1. To simplify the code that we use in backend compilation,
    // or when serializing/deserializing modules, so that we can
    // amortize this effort when we compile multiple entry points
    // that use the same module(s).
    //
    // 2. To ensure certain semantic properties that can't be
    // validated without dataflow information. For example, we want
    // to detect when a variable might be used before it is initialized.

    // Note: if you need to debug the IR that is created before
    // any mandatory optimizations have been applied, then
    // uncomment this line while debugging.

    //      dumpIR(module);

    // First, inline calls to any functions that have been
    // marked for mandatory "early" inlining.
    //
    performMandatoryEarlyInlining(module);

    // Next, attempt to promote local variables to SSA
    // temporaries whenever possible.
    constructSSA(module);

    // Do basic constant folding and dead code elimination
    // using Sparse Conditional Constant Propagation (SCCP)
    //
    applySparseConditionalConstantPropagation(module);

    // Propagate `constexpr`-ness through the dataflow graph (and the
    // call graph) based on constraints imposed by different instructions.
    propagateConstExpr(module, compileRequest->getSink());

    // TODO: give error messages if any `undefined` or
    // `unreachable` instructions remain.

    checkForMissingReturns(module, compileRequest->getSink());

    // The "mandatory" optimization passes may make use of the
    // `IRHighLevelDeclDecoration` type to relate IR instructions
    // back to AST-level code in order to improve the quality
    // of diagnostics that are emitted.
    //
    // While it is important for these passes to have access
    // to AST-level information, allowing that information to
    // flow into later steps (e.g., code generation) could lead
    // to unclean layering of the parts of the compiler.
    // In principle, back-end steps should not need to know where
    // IR code came from.
    //
    // In order to avoid problems, we run a pass here to strip
    // out any decorations that should not be relied upon by
    // later passes.
    //
    {
        // Because we are already stripping out undesired decorations,
        // this is also a convenient place to remove any `IRNameHintDecoration`s
        // in the case where we are obfuscating code. We handle this
        // by setting up the options for the stripping pass appropriately.
        //
        IRStripOptions stripOptions;

        Linkage* linkage = compileRequest->getLinkage();

        stripOptions.shouldStripNameHints = linkage->m_obfuscateCode;
        stripOptions.stripSourceLocs = linkage->m_obfuscateCode;

        stripFrontEndOnlyInstructions(module, stripOptions);

        // Stripping out decorations could leave some dead code behind
        // in the module, and in some cases that extra code is also
        // undesirable (e.g., the string literals referenced by name-hint
        // decorations are just as undesirable as the decorations themselves).
        // To clean up after ourselves we also run a dead-code elimination
        // pass here, but make sure to set our options so that we don't
        // eliminate anything that has been marked for export.
        //
        IRDeadCodeEliminationOptions options;
        options.keepExportsAlive = true;
        eliminateDeadCode(module, options);
    }

    // TODO: consider doing some more aggressive optimizations
    // (in particular specialization of generics) here, so
    // that we can avoid doing them downstream.
    //
    // Note: doing specialization or inlining involving code
    // from other modules potentially makes the IR we generate
    // "fragile" in that we'd now need to recompile when
    // a module we depend on changes.

    validateIRModuleIfEnabled(compileRequest, module);

    // If we are being asked to dump IR during compilation,
    // then we can dump the initial IR for the module here.
    if(compileRequest->shouldDumpIR)
    {
        DiagnosticSinkWriter writer(compileRequest->getSink());
        dumpIR(module, &writer);
    }

    return module;
}

    /// Context for generating IR code to represent a `SpecializedComponentType`
struct SpecializedComponentTypeIRGenContext : ComponentTypeVisitor
{
    DiagnosticSink* sink;
    Linkage* linkage;
    Session* session;
    IRGenContext* context;
    IRBuilder* builder;

    RefPtr<IRModule> process(
        SpecializedComponentType*   componentType,
        DiagnosticSink*             inSink)
    {
        sink = inSink;

        linkage = componentType->getLinkage();
        session = linkage->getSessionImpl();

        SharedIRGenContext sharedContextStorage(
            session,
            sink,
            linkage->m_obfuscateCode
        );
        SharedIRGenContext* sharedContext = &sharedContextStorage;

        IRGenContext contextStorage(sharedContext, linkage->getASTBuilder());
        context = &contextStorage;

        SharedIRBuilder sharedBuilderStorage;
        SharedIRBuilder* sharedBuilder = &sharedBuilderStorage;
        sharedBuilder->module = nullptr;
        sharedBuilder->session = session;

        IRBuilder builderStorage;
        builder = &builderStorage;
        builder->sharedBuilder = sharedBuilder;

        RefPtr<IRModule> module = builder->createModule();
        sharedBuilder->module = module;

        builder->setInsertInto(module->getModuleInst());

        context->irBuilder = builder;

        componentType->acceptVisitor(this, nullptr);

        return module;
    }

    void visitEntryPoint(EntryPoint* entryPoint, EntryPoint::EntryPointSpecializationInfo* specializationInfo) SLANG_OVERRIDE
    {
        // We need to emit symbols for all of the entry
        // points in the program; this is especially
        // important in the case where a generic entry
        // point is being specialized.
        //
        lowerProgramEntryPointToIR(context, entryPoint, specializationInfo);
    }

    void visitModule(Module* module, Module::ModuleSpecializationInfo* specializationInfo) SLANG_OVERRIDE
    {
        // We've hit a leaf module, so we should be able to bind any global
        // generic type parameters here...
        //
        if( specializationInfo )
        {
            for( auto genericArgInfo : specializationInfo->genericArgs )
            {
                IRInst* irParam = getSimpleVal(context, ensureDecl(context, genericArgInfo.paramDecl));
                IRInst* irVal = lowerSimpleVal(context, genericArgInfo.argVal);

                // bind `irParam` to `irVal`
                builder->emitBindGlobalGenericParam(irParam, irVal);
            }

            auto shaderParamCount = module->getShaderParamCount();
            Index existentialArgOffset = 0;

            for( Index ii = 0; ii < shaderParamCount; ++ii )
            {
                auto shaderParam = module->getShaderParam(ii);
                auto specializationArgCount = shaderParam.specializationParamCount;

                IRInst* irParam = getSimpleVal(context, ensureDecl(context, shaderParam.paramDeclRef));
                List<IRInst*> irSlotArgs;
                for( Index jj = 0; jj < specializationArgCount; ++jj )
                {
                    auto& specializationArg = specializationInfo->existentialArgs[existentialArgOffset++];

                    auto irType = lowerSimpleVal(context, specializationArg.val);
                    auto irWitness = lowerSimpleVal(context, specializationArg.witness);

                    irSlotArgs.add(irType);
                    irSlotArgs.add(irWitness);
                }

                builder->addBindExistentialSlotsDecoration(
                    irParam,
                    irSlotArgs.getCount(),
                    irSlotArgs.getBuffer());
            }
        }
    }

    void visitComposite(CompositeComponentType* composite, CompositeComponentType::CompositeSpecializationInfo* specializationInfo) SLANG_OVERRIDE
    {
        visitChildren(composite, specializationInfo);
    }

    void visitSpecialized(SpecializedComponentType* specialized) SLANG_OVERRIDE
    {
        visitChildren(specialized);
    }
};

RefPtr<IRModule> generateIRForSpecializedComponentType(
    SpecializedComponentType*   componentType,
    DiagnosticSink*             sink)
{
    SpecializedComponentTypeIRGenContext context;
    return context.process(componentType, sink);
}


RefPtr<IRModule> TargetProgram::getOrCreateIRModuleForLayout(DiagnosticSink* sink)
{
    getOrCreateLayout(sink);
    return m_irModuleForLayout;
}

    /// Specialized IR generation context for when generating IR for layouts.
struct IRLayoutGenContext : IRGenContext
{
    IRLayoutGenContext(SharedIRGenContext* shared, ASTBuilder* astBuilder)
        : IRGenContext(shared, astBuilder)
    {}

        /// Cache for custom key instructions used for entry-point parameter layout information.
    Dictionary<ParamDecl*, IRStructKey*> mapEntryPointParamToKey;
};

    /// Lower an AST-level type layout to an IR-level type layout.
IRTypeLayout* lowerTypeLayout(
    IRLayoutGenContext* context,
    TypeLayout*         typeLayout);

    /// Lower an AST-level variable layout to an IR-level variable layout.
IRVarLayout* lowerVarLayout(
    IRLayoutGenContext* context,
    VarLayout*          varLayout);

    /// Shared code for most `lowerTypeLayout` cases.
    ///
    /// Handles copying of resource usage and pending data type layout
    /// from the AST `typeLayout` to the specified `builder`.
    ///
static IRTypeLayout* _lowerTypeLayoutCommon(
    IRLayoutGenContext*     context,
    IRTypeLayout::Builder*  builder,
    TypeLayout*             typeLayout)
{
    for( auto resInfo : typeLayout->resourceInfos )
    {
        builder->addResourceUsage(resInfo.kind, resInfo.count);
    }

    if( auto pendingTypeLayout = typeLayout->pendingDataTypeLayout )
    {
        builder->setPendingTypeLayout(
            lowerTypeLayout(context, pendingTypeLayout));
    }

    return builder->build();
}

IRTypeLayout* lowerTypeLayout(
    IRLayoutGenContext* context,
    TypeLayout*         typeLayout)
{
    // TODO: We chould consider caching the layouts we create based on `typeLayout`
    // and re-using them. This isn't strictly necessary because we emit the
    // instructions as "hoistable" which should give us de-duplication, and it wouldn't
    // help much until/unless the AST level gets less wasteful about how it computes layout.

    // We will use casting to detect if `typeLayout` is
    // one of the cases that requires a dedicated sub-type
    // of IR type layout.
    //
    if( auto paramGroupTypeLayout = as<ParameterGroupTypeLayout>(typeLayout) )
    {
        IRParameterGroupTypeLayout::Builder builder(context->irBuilder);

        builder.setContainerVarLayout(
            lowerVarLayout(context, paramGroupTypeLayout->containerVarLayout));
        builder.setElementVarLayout(
            lowerVarLayout(context, paramGroupTypeLayout->elementVarLayout));
        builder.setOffsetElementTypeLayout(
            lowerTypeLayout(context, paramGroupTypeLayout->offsetElementTypeLayout));

        return _lowerTypeLayoutCommon(context, &builder, paramGroupTypeLayout);
    }
    else if( auto structTypeLayout = as<StructTypeLayout>(typeLayout) )
    {
        IRStructTypeLayout::Builder builder(context->irBuilder);

        for( auto fieldLayout : structTypeLayout->fields )
        {
            auto fieldDecl = fieldLayout->varDecl;

            IRStructKey* irFieldKey = nullptr;
            if(auto paramDecl = as<ParamDecl>(fieldDecl) )
            {
                // There is a subtle special case here.
                //
                // A `StructTypeLayout` might be used to represent
                // the parameters of an entry point, and this is the
                // one and only case where the "fields" being used
                // might actually be `ParamDecl`s.
                //
                // The IR encoding of structure type layouts relies
                // on using field "key" instructions to identify
                // the fields, but these don't exist (by default)
                // for function parameters.
                //
                // To get around this problem we will create key
                // instructions to stand in for the entry-point parameters
                // as needed when generating layout.
                //
                // We need to cache the generated keys on the context,
                // so that if we run into another type layout for the
                // same entry point we will re-use the same keys.
                //
                if( !context->mapEntryPointParamToKey.TryGetValue(paramDecl, irFieldKey) )
                {
                    irFieldKey = context->irBuilder->createStructKey();

                    // TODO: It might eventually be a good idea to attach a mangled
                    // name to the key we just generated (derived from the entry point
                    // and parameter name), even though parameters don't usually have
                    // linkage.
                    //
                    // Doing so would ensure that if we ever combined partial layout
                    // information from different modules they would agree on the key
                    // to use for entry-point parameters.
                    //
                    // For now this is a non-issue because both the creation and use
                    // of these keys will be local to a single `IREntryPointLayout`,
                    // and we don't support combination at a finer granularity than that.

                    context->mapEntryPointParamToKey.Add(paramDecl, irFieldKey);
                }
            }
            else
            {
                IRInst* irFieldKeyInst = getSimpleVal(context,
                    ensureDecl(context, fieldDecl));
                irFieldKey = as<IRStructKey>(irFieldKeyInst);
            }
            SLANG_ASSERT(irFieldKey);

            auto irFieldLayout = lowerVarLayout(context, fieldLayout);
            builder.addField(irFieldKey, irFieldLayout);
        }

        return _lowerTypeLayoutCommon(context, &builder, structTypeLayout);
    }
    else if( auto arrayTypeLayout = as<ArrayTypeLayout>(typeLayout) )
    {
        auto irElementTypeLayout = lowerTypeLayout(context, arrayTypeLayout->elementTypeLayout);
        IRArrayTypeLayout::Builder builder(context->irBuilder, irElementTypeLayout);
        return _lowerTypeLayoutCommon(context, &builder, arrayTypeLayout);
    }
    else if( auto taggedUnionTypeLayout = as<TaggedUnionTypeLayout>(typeLayout) )
    {
        IRTaggedUnionTypeLayout::Builder builder(context->irBuilder, taggedUnionTypeLayout->tagOffset);

        for( auto caseTypeLayout : taggedUnionTypeLayout->caseTypeLayouts )
        {
            builder.addCaseTypeLayout(
                lowerTypeLayout(
                    context,
                    caseTypeLayout));
        }

        return _lowerTypeLayoutCommon(context, &builder, taggedUnionTypeLayout);
    }
    else if( auto streamOutputTypeLayout = as<StreamOutputTypeLayout>(typeLayout) )
    {
        auto irElementTypeLayout = lowerTypeLayout(context, streamOutputTypeLayout->elementTypeLayout);

        IRStreamOutputTypeLayout::Builder builder(context->irBuilder, irElementTypeLayout);
        return _lowerTypeLayoutCommon(context, &builder, streamOutputTypeLayout);
    }
    else if( auto matrixTypeLayout = as<MatrixTypeLayout>(typeLayout) )
    {
        // TODO: Our support for explicit layouts on matrix types is minimal, so whether
        // or not we even include `IRMatrixTypeLayout` doesn't impact any behavior we
        // currently test.
        //
        // Our handling of matrix types and their layout needs a complete overhaul, but
        // that isn't something we can get to right away, so we'll just try to pass
        // along this data as best we can for now.

        IRMatrixTypeLayout::Builder builder(context->irBuilder, matrixTypeLayout->mode);
        return _lowerTypeLayoutCommon(context, &builder, matrixTypeLayout);
    }
    else
    {
        // If no special case applies we will build a generic `IRTypeLayout`.
        //
        IRTypeLayout::Builder builder(context->irBuilder);
        return _lowerTypeLayoutCommon(context, &builder, typeLayout);
    }
}

IRVarLayout* lowerVarLayout(
    IRLayoutGenContext* context,
    VarLayout*          varLayout)
{
    auto irTypeLayout = lowerTypeLayout(context, varLayout->typeLayout);
    IRVarLayout::Builder irLayoutBuilder(context->irBuilder, irTypeLayout);

    for( auto resInfo : varLayout->resourceInfos )
    {
        auto irResInfo = irLayoutBuilder.findOrAddResourceInfo(resInfo.kind);
        irResInfo->offset = resInfo.index;
        irResInfo->space = resInfo.space;
    }

    if( auto pendingVarLayout = varLayout->pendingVarLayout )
    {
        irLayoutBuilder.setPendingVarLayout(
            lowerVarLayout(context, pendingVarLayout));
    }

    // We will only generate layout information with *either* a system-value
    // semantic or a user-defined semantic, and we will always check for
    // the system-value semantic first because the AST-level representation
    // seems to encode both when a system-value semantic is present.
    //
    if( varLayout->systemValueSemantic.getLength() )
    {
        irLayoutBuilder.setSystemValueSemantic(
            varLayout->systemValueSemantic,
            varLayout->systemValueSemanticIndex);
    }
    else if( varLayout->semanticName.getLength() )
    {
        irLayoutBuilder.setUserSemantic(
            varLayout->semanticName,
            varLayout->semanticIndex);
    }

    if( varLayout->stage != Stage::Unknown )
    {
        irLayoutBuilder.setStage(varLayout->stage);
    }

    return irLayoutBuilder.build();
}

    /// Handle the lowering of an entry-point result layout to the IR
IRVarLayout* lowerEntryPointResultLayout(
    IRLayoutGenContext* context,
    VarLayout*          layout)
{
    // The easy case is when there is a non-null `layout`, because we
    // can handle it like any other var layout.
    //
    if(layout)
        return lowerVarLayout(context, layout);

    // Right now the AST-level layout logic will leave a null layout
    // for the result when an entry point has a `void` result type.
    //
    // TODO: We should fix this at the AST level instead of the IR,
    // but doing so would impact reflection, where clients could
    // be using a null check to test for a `void` result.
    //
    // As a workaround, we will create an empty type layout and
    // an empty var layout that represents it, consistent with the
    // way that a `void` value consumes no resources.
    //
    IRTypeLayout::Builder typeLayoutBuilder(context->irBuilder);
    auto irTypeLayout = typeLayoutBuilder.build();
    IRVarLayout::Builder varLayoutBuilder(context->irBuilder, irTypeLayout);
    return varLayoutBuilder.build();
}

    /// Lower AST-level layout information for an entry point to the IR
IREntryPointLayout* lowerEntryPointLayout(
    IRLayoutGenContext* context,
    EntryPointLayout*   entryPointLayout)
{
    auto irParamsLayout = lowerVarLayout(context, entryPointLayout->parametersLayout);
    auto irResultLayout = lowerEntryPointResultLayout(context, entryPointLayout->resultLayout);

    return context->irBuilder->getEntryPointLayout(
        irParamsLayout,
        irResultLayout);
}

RefPtr<IRModule> TargetProgram::createIRModuleForLayout(DiagnosticSink* sink)
{
    if(m_irModuleForLayout)
        return m_irModuleForLayout;


    // Okay, now we need to fill it in.

    auto programLayout = getOrCreateLayout(sink);
    if(!programLayout)
        return nullptr;

    auto program = getProgram();
    auto linkage = program->getLinkage();
    auto session = linkage->getSessionImpl();

    SharedIRGenContext sharedContextStorage(
        session,
        sink,
        linkage->m_obfuscateCode);
    auto sharedContext = &sharedContextStorage;

    ASTBuilder* astBuilder = linkage->getASTBuilder();

    IRLayoutGenContext contextStorage(sharedContext, astBuilder);
    auto context = &contextStorage;

    SharedIRBuilder sharedBuilderStorage;
    auto sharedBuilder = &sharedBuilderStorage;
    sharedBuilder->module = nullptr;
    sharedBuilder->session = session;

    IRBuilder builderStorage;
    auto builder = &builderStorage;
    builder->sharedBuilder = sharedBuilder;

    RefPtr<IRModule> irModule = builder->createModule();
    sharedBuilder->module = irModule;

    builder->setInsertInto(irModule->getModuleInst());

    context->irBuilder = builder;


    // Okay, now we need to walk through and decorate everything.
    auto globalStructLayout = getScopeStructLayout(programLayout);
    for(auto globalVarPair : globalStructLayout->mapVarToLayout)
    {
        auto varDecl = globalVarPair.Key;
        auto varLayout = globalVarPair.Value;

        // Ensure that an `[import(...)]` declaration for the variable
        // has been emitted to this module, so that we will have something
        // to decorate.
        //
        auto irVar = getSimpleVal(context, ensureDecl(context, varDecl));

        auto irLayout = lowerVarLayout(context, varLayout);

        // Now attach the decoration to the variable.
        //
        builder->addLayoutDecoration(irVar, irLayout);
    }

    for( auto entryPointLayout : programLayout->entryPoints )
    {
        auto funcDeclRef = entryPointLayout->entryPoint;

        // HACK: skip over entry points that came from deserialization,
        // and thus don't have AST-level information for us to work with.
        //
        if(!funcDeclRef)
            continue;

        auto irFuncType = lowerType(context, getFuncType(astBuilder, funcDeclRef));
        auto irFunc = getSimpleVal(context, emitDeclRef(context, funcDeclRef, irFuncType));

        if( !irFunc->findDecoration<IRLinkageDecoration>() )
        {
            builder->addImportDecoration(irFunc, getMangledName(astBuilder, funcDeclRef).getUnownedSlice());
        }

        auto irEntryPointLayout = lowerEntryPointLayout(context, entryPointLayout);

        builder->addLayoutDecoration(irFunc, irEntryPointLayout);
    }

    for( auto taggedUnionTypeLayout : programLayout->taggedUnionTypeLayouts )
    {
        auto taggedUnionType = taggedUnionTypeLayout->getType();
        auto irType = lowerType(context, taggedUnionType);

        auto irTypeLayout = lowerTypeLayout(context, taggedUnionTypeLayout);

        builder->addLayoutDecoration(irType, irTypeLayout);
    }

    // Lets strip and run DCE here
    if (linkage->m_obfuscateCode)
    {
        IRStripOptions stripOptions;

        stripOptions.shouldStripNameHints = linkage->m_obfuscateCode;
        stripOptions.stripSourceLocs = linkage->m_obfuscateCode;

        stripFrontEndOnlyInstructions(irModule, stripOptions);

        IRDeadCodeEliminationOptions options;
        options.keepExportsAlive = true;
        options.keepLayoutsAlive = true;

        // Eliminate any dead code
        eliminateDeadCode(irModule, options);
    }

    m_irModuleForLayout = irModule;
    return irModule;
}



} // namespace Slang
back to top