https://github.com/shader-slang/slang
Raw File
Tip revision: 0586f3298fa7d554fa2682103eefba88740d6758 authored by jsmall-nvidia on 18 January 2023, 19:11:50 UTC
Upgrade slang-llvm-13.x-33 (#2600)
Tip revision: 0586f32
slang-ir-legalize-types.cpp
// slang-ir-legalize-types.cpp

// This file implements type legalization for the IR.
// It uses the core legalization logic in
// `legalize-types.{h,cpp}` to decide what to do with
// the types, while this file handles the actual
// rewriting of the IR to use the new types.
//
// This pass should only be applied to IR that has been
// fully specialized (no more generics/interfaces), so
// that the concrete type of everything is known.

#include "../compiler-core/slang-name.h"

#include "slang-ir.h"
#include "slang-ir-clone.h"
#include "slang-ir-insts.h"
#include "slang-legalize-types.h"
#include "slang-mangle.h"

namespace Slang
{

LegalVal LegalVal::tuple(RefPtr<TuplePseudoVal> tupleVal)
{
    SLANG_ASSERT(tupleVal->elements.getCount());

    LegalVal result;
    result.flavor = LegalVal::Flavor::tuple;
    result.obj = tupleVal;
    return result;
}

LegalVal LegalVal::pair(RefPtr<PairPseudoVal> pairInfo)
{
    LegalVal result;
    result.flavor = LegalVal::Flavor::pair;
    result.obj = pairInfo;
    return result;
}

LegalVal LegalVal::pair(
    LegalVal const&     ordinaryVal,
    LegalVal const&     specialVal,
    RefPtr<PairInfo>    pairInfo)
{
    if (ordinaryVal.flavor == LegalVal::Flavor::none)
        return specialVal;

    if (specialVal.flavor == LegalVal::Flavor::none)
        return ordinaryVal;


    RefPtr<PairPseudoVal> obj = new PairPseudoVal();
    obj->ordinaryVal = ordinaryVal;
    obj->specialVal = specialVal;
    obj->pairInfo = pairInfo;

    return LegalVal::pair(obj);
}

LegalVal LegalVal::implicitDeref(LegalVal const& val)
{
    RefPtr<ImplicitDerefVal> implicitDerefVal = new ImplicitDerefVal();
    implicitDerefVal->val = val;

    LegalVal result;
    result.flavor = LegalVal::Flavor::implicitDeref;
    result.obj = implicitDerefVal;
    return result;
}

LegalVal LegalVal::getImplicitDeref() const
{
    SLANG_ASSERT(flavor == Flavor::implicitDeref);
    return as<ImplicitDerefVal>(obj)->val;
}

LegalVal LegalVal::wrappedBuffer(
    LegalVal const& baseVal,
    LegalElementWrapping const& elementInfo)
{
    RefPtr<WrappedBufferPseudoVal> obj = new WrappedBufferPseudoVal();
    obj->base = baseVal;
    obj->elementInfo = elementInfo;

    LegalVal result;
    result.flavor = LegalVal::Flavor::wrappedBuffer;
    result.obj = obj;
    return result;
}

//

IRTypeLegalizationContext::IRTypeLegalizationContext(
    IRModule* inModule)
{
    session = inModule->getSession();
    module = inModule;

    auto sharedBuilder = &sharedBuilderStorage;
    sharedBuilder->init(module);

    builder = &builderStorage;
    builder->init(sharedBuilder);
}

static void registerLegalizedValue(
    IRTypeLegalizationContext*  context,
    IRInst*                     irValue,
    LegalVal const&             legalVal)
{
    context->mapValToLegalVal[irValue] = legalVal;
}

struct IRGlobalNameInfo
{
    IRInst*         globalVar;
    UInt            counter;
};

static LegalVal declareVars(
    IRTypeLegalizationContext*  context,
    IROp                        op,
    LegalType                   type,
    IRTypeLayout*               typeLayout,
    LegalVarChain const&        varChain,
    UnownedStringSlice          nameHint,
    IRInst*                     leafVar,
    IRGlobalNameInfo*           globalNameInfo,
    bool                        isSpecial);

    /// Unwrap a value with flavor `wrappedBuffer`
    ///
    /// The original `legalPtrOperand` has a wrapped-buffer type
    /// which encodes the way that, e.g., a `ConstantBuffer<Foo>`
    /// where `Foo` includes interface types, got legalized
    /// into a buffer that stores a `Foo` value plus addition
    /// fields for the concrete types that got plugged in.
    ///
    /// The `elementInfo` is the layout information for the
    /// modified ("wrapped") buffer type, and specifies how
    /// the logical element type was expanded into actual fields.
    ///
    /// This function returns a new value that undoes all of
    /// the wrapping and produces a new `LegalVal` that matches
    /// the nominal type of the original buffer.
    ///
static LegalVal unwrapBufferValue(
    IRTypeLegalizationContext*  context,
    LegalVal                    legalPtrOperand,
    LegalElementWrapping const& elementInfo);

    /// Perform any actions required to materialize `val` into a usable value.
    ///
    /// Certain case of `LegalVal` (currently just the `wrappedBuffer` case) are
    /// suitable for use to represent a variable, but cannot be used directly
    /// in computations, because their structured needs to be "unwrapped."
    ///
    /// This function unwraps any `val` that needs it, which may involve
    /// emitting additional IR instructions, and returns the unmodified
    /// `val` otherwise.
    ///
static LegalVal maybeMaterializeWrappedValue(
    IRTypeLegalizationContext*  context,
    LegalVal                    val)
{
    if(val.flavor != LegalVal::Flavor::wrappedBuffer)
        return val;

    auto wrappedBufferVal = val.getWrappedBuffer();
    return unwrapBufferValue(
        context,
        wrappedBufferVal->base,
        wrappedBufferVal->elementInfo);
}

// Take a value that is being used as an operand,
// and turn it into the equivalent legalized value.
static LegalVal legalizeOperand(
    IRTypeLegalizationContext*    context,
    IRInst*                    irValue)
{
    LegalVal legalVal;
    if( context->mapValToLegalVal.TryGetValue(irValue, legalVal) )
    {
        return maybeMaterializeWrappedValue(context, legalVal);
    }

    // For now, assume that anything not covered
    // by the mapping is legal as-is.

    return LegalVal::simple(irValue);
}

    /// Helper type for legalization an IR `call` instruction
struct LegalCallBuilder
{
    LegalCallBuilder(
        IRTypeLegalizationContext* context,
        IRCall* call)
        : m_context(context)
        , m_call(call)
    {}

        /// The context for legalization
    IRTypeLegalizationContext* m_context = nullptr;

        /// The `call` instruction we are legalizing
    IRCall* m_call = nullptr;

        /// The legalized arguments for the call
    List<IRInst*> m_args;

        /// Add a logical argument to the call (which may map to zero or mmore actual arguments)
    void addArg(
        LegalVal const& val)
    {
        // In order to add the argument(s) for `val`,
        // we will recurse over its structure.

        switch (val.flavor)
        {
        case LegalVal::Flavor::none:
            break;

        case LegalVal::Flavor::simple:
            m_args.add(val.getSimple());
            break;

        case LegalVal::Flavor::implicitDeref:
            addArg(val.getImplicitDeref());
            break;

        case LegalVal::Flavor::pair:
            {
                auto pairVal = val.getPair();
                addArg(pairVal->ordinaryVal);
                addArg(pairVal->specialVal);
            }
            break;

        case LegalVal::Flavor::tuple:
            {
                auto tuplePsuedoVal = val.getTuple();
                for (auto elem : val.getTuple()->elements)
                {
                    addArg(elem.val);
                }
            }
            break;

        default:
            SLANG_UNEXPECTED("uhandled val flavor");
            break;
        }
    }

        /// Build a new call based on the original, given the expected `resultType`.
        ///
        /// Returns a value representing the result of the call.
    LegalVal build(LegalType const& resultType)
    {
        // We can recursively decompose the cases for
        // how to legalize a call based on the expected
        // result type.
        //
        switch (resultType.flavor)
        {
        case LegalType::Flavor::simple:
            // In the case where the result type is simple,
            // we can directly emit the `call` instruction
            // and use the result as our single result value.
            //
            return LegalVal::simple(_emitCall(resultType.getSimple()));

        case LegalType::Flavor::none:
            // In the case where there is no result type,
            // that is equivalent to the call returning `void`.
            //
            // We directly emit the call and then return an
            // empty value to represent the result.
            //
            _emitCall(m_context->builder->getVoidType());
            return LegalVal();

        case LegalVal::Flavor::implicitDeref:
            // An `implicitDeref` wraps a single value, so we can simply
            // unwrap, recurse on the innter value, and then wrap up
            // the result.
            //
            return LegalVal::implicitDeref(build(resultType.getImplicitDeref()->valueType));

        case LegalVal::Flavor::pair:
            {
                // A `pair` type consists of both an ordinary part and a special part.
                //
                auto pairType = resultType.getPair();

                // The ordinary part will be used as the direct result of the call,
                // while the special part will need to be returned via an `out`
                // argument.
                //
                // We will start by emitting the declaration(s) needed for those
                // `out` arguments that represent the special part, and adding
                // them to the argument list. Basically this step is declaring
                // local variables that will hold the special part of the result,
                // and it returns a value that repsents those variables.
                //
                auto specialVal = _addOutArg(pairType->specialType);

                // Once the argument values for the special part are set up,
                // we can recurse on the ordinary part and emit the actual
                // call operation (which will include our new arguments).
                //
                auto ordinaryVal = build(pairType->ordinaryType);

                // The resulting value will be a pair of the ordinary value
                // (returned from the `call` instruction) and the special value
                // (declared as zero or more local variables).
                //
                RefPtr<PairPseudoVal> pairVal = new PairPseudoVal();
                pairVal->pairInfo = pairType->pairInfo;
                pairVal->ordinaryVal = ordinaryVal;
                pairVal->specialVal = specialVal;
                return LegalVal::pair(pairVal);
            }
            break;

        case LegalVal::Flavor::tuple:
            {
                // A `tuple` value consists of zero or more elements
                // that are each of a "special" type. We will handle
                // *all* of those values as `out` arguments akin to
                // what we did for the special half of a pair type
                // above.
                //
                auto resultVal = _addOutArg(resultType);

                // In this case there was no "ordinary" part to the
                // result type of the function, so we know that
                // the legalization funciton/call will use a `void`
                // result type.
                //
                _emitCall(m_context->builder->getVoidType());
                return resultVal;
            }
            break;

        default:
            // TODO: implement legalization of non-simple return types
            SLANG_UNEXPECTED("unimplemented legalized return type for IRCall.");
        }
    }

private:

        /// Add an `out` argument to the call, to capture the given `resultType`.
    LegalVal _addOutArg(LegalType const& resultType)
    {
        switch (resultType.flavor)
        {
        case LegalType::Flavor::simple:
            {
                // In the leaf case we have a simple type, and
                // we just want to declare a local variable based on it.
                //
                auto simpleType = resultType.getSimple();
                auto builder = m_context->builder;

                // Recall that a local variable in our IR represents a *pointer*
                // to storage of the appropriate type.
                //
                auto varPtr = builder->emitVar(simpleType);

                // We need to pass that pointer as an argument to our new
                // `call` instruction, so that it can receive the value
                // written by the callee.
                //
                m_args.add(varPtr);

                // Note: Because `varPtr` is a pointer to the value we want,
                // we have the small problem of needing to return a `LegalVal`
                // that has dereferenced the value after the call.
                //
                // We solve this problem by inserting as `load` from our
                // new variable immediately after the call, before going
                // and resetting the insertion point to continue inserting
                // stuff before the call (which is where we wnat the local
                // variable declarations to go).
                //
                // TODO: Confirm that this logic can't go awry if (somehow)
                // there is no instruction after `m_call`. That should not
                // be possible inside of a function body, but it could in
                // theory be a problem if we ever have top-level module-scope
                // code representing initialization of constants and/or globals.
                //
                builder->setInsertBefore(m_call->getNextInst());
                auto val = builder->emitLoad(simpleType, varPtr);
                builder->setInsertBefore(m_call);

                return LegalVal::simple(val);
            }
            break;

            // The remaining cases are a straightforward structural recursion
            // on top of the base case above.

        case LegalType::Flavor::none:
            return LegalVal();

        case LegalVal::Flavor::implicitDeref:
            return LegalVal::implicitDeref(_addOutArg(resultType.getImplicitDeref()->valueType));

        case LegalVal::Flavor::pair:
            {
                auto pairType = resultType.getPair();
                auto specialVal = _addOutArg(pairType->specialType);
                auto ordinaryVal = _addOutArg(pairType->ordinaryType);

                RefPtr<PairPseudoVal> pairVal = new PairPseudoVal();
                pairVal->pairInfo = pairType->pairInfo;
                pairVal->ordinaryVal = ordinaryVal;
                pairVal->specialVal = specialVal;

                return LegalVal::pair(pairVal);
            }
            break;

        case LegalVal::Flavor::tuple:
            {
                auto tuplePsuedoType = resultType.getTuple();

                RefPtr<TuplePseudoVal> tupleVal = new TuplePseudoVal();
                for (auto typeElement : tuplePsuedoType->elements)
                {
                    TuplePseudoVal::Element valElement;
                    valElement.key = typeElement.key;
                    valElement.val = _addOutArg(typeElement.type);
                    tupleVal->elements.add(valElement);
                }

                return LegalVal::tuple(tupleVal);
            }
            break;

        default:
            // TODO: implement legalization of non-simple return types
            SLANG_UNEXPECTED("unimplemented legalized return type for IRCall.");
        }
    }

        /// Emit the actual `call` instruction given an IR result type
    IRInst* _emitCall(IRType* resultType)
    {
        // The generated call will include all of the arguments that have
        // been added up to this point, which includes those that were
        // added to represent legalized parts of the result type.
        //
        return m_context->builder->emitCallInst(
            resultType,
            m_call->getCallee(),
            m_args.getCount(),
            m_args.getBuffer());
    }
};


static LegalVal legalizeCall(
    IRTypeLegalizationContext*    context,
    IRCall* callInst)
{
    LegalCallBuilder builder(context, callInst);

    auto argCount = callInst->getArgCount();
    for( UInt i = 0; i < argCount; i++ )
    {
        auto legalArg = legalizeOperand(context, callInst->getArg(i));
        builder.addArg(legalArg);
    }

    auto legalResultType = legalizeType(context, callInst->getFullType());
    return builder.build(legalResultType);
}

    /// Helper type for legalizing a `returnVal` instruction
struct LegalReturnBuilder
{
    LegalReturnBuilder(IRTypeLegalizationContext* context, IRReturn* returnInst)
        : m_context(context)
        , m_returnInst(returnInst)
    {}

        /// Emit code to perform a return of `val`
    void returnVal(LegalVal val)
    {
        auto builder = m_context->builder;

        switch (val.flavor)
        {
        case LegalVal::Flavor::simple:
            // The case of a simple value is easy: just emit a `returnVal`.
            //
            builder->emitReturn(val.getSimple());
            break;

        case LegalVal::Flavor::none:
            // The case of an empty/void value is also easy: emit a `return`.
            //
            builder->emitReturn();
            break;

        case LegalVal::Flavor::implicitDeref:
            returnVal(val.getImplicitDeref());
            break;

        case LegalVal::Flavor::pair:
            {
                // The case for a pair value is the main interesting one.
                // We need to write the special part of the return value
                // to the `out` parameters that were declared to capture
                // it, and then return the ordinary part of the value
                // like normal.
                //
                // Note that the order here matters, because we need to
                // emit the code that writes to the `out` parameters
                // before the `return` instruction.
                //
                auto pairVal = val.getPair();
                _writeResultParam(pairVal->specialVal);
                returnVal(pairVal->ordinaryVal);
            }
            break;

        case LegalVal::Flavor::tuple:
            {
                // The tuple case is kind of a degenerate combination
                // of the `pair` and `none` cases: we need to emit
                // writes to the `out` parameters declared to capture
                // the tuple (all of it), and then we do a `return`
                // of `void` because there is no ordinary result to
                // capture.
                //
                _writeResultParam(val);
                builder->emitReturn();
            }
            break;

        default:
            // TODO: implement legalization of non-simple return types
            SLANG_UNEXPECTED("unimplemented legalized return type for IRReturn.");
        }
    }

private:

        /// Write `val` to the `out` parameters of the enclosing function
    void _writeResultParam(LegalVal const& val)
    {
        switch (val.flavor)
        {
        case LegalVal::Flavor::simple:
            {
                // The leaf case here is the interesting one.
                //
                // We know that if we are writing to `out` parameters to
                // represent the function result then the function must
                // have been legalized in a way that introduced those parameters.
                // We thus need to look up the information on how the
                // function got legalized so that we can identify the
                // new parameters.
                //
                // TODO: One detail worth confirming here is whether there
                // could ever be a case where a `return` instruction gets legalized
                // before its outer function does.
                //
                if( !m_parentFuncInfo )
                {
                    // We start by searching for the ancestor instruction
                    // that represents the function (or other code-bearing value)
                    // that holds this instruction.
                    //
                    auto p = m_returnInst->getParent();
                    while( p && !as<IRGlobalValueWithCode>(p) )
                    {
                        p = p->parent;
                    }

                    // We expect that the parent is actually an IR function.
                    //
                    // TODO: What about the case where we have an `IRGlobalVar`
                    // of a type that needs legalization, and teh variable has
                    // an initializer? For now, I believe that case is disallowed
                    // in the legalization for global variables.
                    //
                    auto parentFunc = as<IRFunc>(p);
                    SLANG_ASSERT(parentFunc);
                    if(!parentFunc)
                        return;

                    // We also expect that extended legalization information was
                    // recorded for the function.
                    //
                    RefPtr<LegalFuncInfo> parentFuncInfo;
                    if( !m_context->mapFuncToInfo.TryGetValue(parentFunc, parentFuncInfo) )
                    {
                        // If we fail to find the extended information then either:
                        //
                        // * The parent function has not been legalized yet. This would
                        //   be a violation of our assumption about ordering of legalization.
                        //
                        // * The parent function was legalized, but didn't require any
                        //   additional IR parameters to represent its result. This would
                        //   be a violation of our assumption that the declared result type
                        //   of a function and the type at `return` sites inside the function
                        //   need to match.
                        //
                        SLANG_ASSERT(parentFuncInfo);
                        return;
                    }

                    // If we find the extended information, then this is the first
                    // leaf parameter we are dealing with, so we set up to read through
                    // the parameters starting at index zero.
                    //
                    m_parentFuncInfo = parentFuncInfo;
                    m_resultParamCounter = 0;
                }
                SLANG_ASSERT(m_parentFuncInfo);

                // The recursion through the result `val` will iterate over the
                // leaf parameters in the same order they should have been declared,
                // so the parameter we need to write to will be the next one in order.
                //
                // We expect that the parameter index must be in range, beacuse otherwise
                // the recursion here and the recursion that declared the parameters are
                // mismatched in terms of how they traversed the hierarchical representation
                // of `LegalVal` / `LegalType`.
                //
                Index resultParamIndex = m_resultParamCounter++;
                SLANG_ASSERT(resultParamIndex >= 0);
                SLANG_ASSERT(resultParamIndex < m_parentFuncInfo->resultParamVals.getCount());

                // Once we've identified the right parameter, we can emit a `store`
                // to write the value that the function wants to output.
                //
                // Note that an `out` parameter is represented with a pointer type
                // in the IR, so that the `IRParam` here represents a pointer to
                // the value that will receive the result.
                //
                auto resultParamPtr = m_parentFuncInfo->resultParamVals[resultParamIndex];
                m_context->builder->emitStore(resultParamPtr, val.getSimple());
            }
            break;

            // The remaining cases are just a straightforward recursion
            // over the structure of the `val`.

        case LegalVal::Flavor::none:
            break;

        case LegalVal::Flavor::implicitDeref:
            _writeResultParam(val.getImplicitDeref());
            break;

        case LegalVal::Flavor::pair:
            {
                auto pairVal = val.getPair();
                _writeResultParam(pairVal->ordinaryVal);
                _writeResultParam(pairVal->specialVal);
            }
            break;

        case LegalVal::Flavor::tuple:
            {
                auto tupleVal = val.getTuple();
                for (auto element : tupleVal->elements)
                {
                    _writeResultParam(element.val);
                }
            }
            break;

        default:
            // TODO: implement legalization of non-simple return types
            SLANG_UNEXPECTED("unimplemented legalized return type for IRReturn.");
        }
    }

    IRTypeLegalizationContext* m_context = nullptr;
    IRReturn* m_returnInst = nullptr;

    RefPtr<LegalFuncInfo> m_parentFuncInfo;
    Index m_resultParamCounter = 0;
};

static LegalVal legalizeRetVal(
    IRTypeLegalizationContext*  context,
    LegalVal                    retVal,
    IRReturn*                   returnInst)
{
    LegalReturnBuilder builder(context, returnInst);
    builder.returnVal(retVal);
    return LegalVal();
}

static LegalVal legalizeLoad(
    IRTypeLegalizationContext*    context,
    LegalVal                    legalPtrVal)
{
    switch (legalPtrVal.flavor)
    {
    case LegalVal::Flavor::none:
        return LegalVal();

    case LegalVal::Flavor::simple:
        {
            return LegalVal::simple(
                context->builder->emitLoad(legalPtrVal.getSimple()));
        }
        break;

    case LegalVal::Flavor::implicitDeref:
        // We have turne a pointer(-like) type into its pointed-to (value)
        // type, and so the operation of loading goes away; we just use
        // the underlying value.
        return legalPtrVal.getImplicitDeref();

    case LegalVal::Flavor::pair:
        {
            auto ptrPairVal = legalPtrVal.getPair();

            auto ordinaryVal = legalizeLoad(context, ptrPairVal->ordinaryVal);
            auto specialVal = legalizeLoad(context, ptrPairVal->specialVal);
            return LegalVal::pair(ordinaryVal, specialVal, ptrPairVal->pairInfo);
        }

    case LegalVal::Flavor::tuple:
        {
            // We need to emit a load for each element of
            // the tuple.
            auto ptrTupleVal = legalPtrVal.getTuple();
            RefPtr<TuplePseudoVal> tupleVal = new TuplePseudoVal();

            for (auto ee : legalPtrVal.getTuple()->elements)
            {
                TuplePseudoVal::Element element;
                element.key = ee.key;
                element.val = legalizeLoad(context, ee.val);

                tupleVal->elements.add(element);
            }
            return LegalVal::tuple(tupleVal);
        }
        break;

    default:
        SLANG_UNEXPECTED("unhandled case");
        break;
    }
}

static LegalVal legalizeStore(
    IRTypeLegalizationContext*    context,
    LegalVal                    legalPtrVal,
    LegalVal                    legalVal)
{
    switch (legalPtrVal.flavor)
    {
    case LegalVal::Flavor::none:
        return LegalVal();

    case LegalVal::Flavor::simple:
    {
        context->builder->emitStore(legalPtrVal.getSimple(), legalVal.getSimple());
        return legalVal;
    }
    break;

    case LegalVal::Flavor::implicitDeref:
        // TODO: what is the right behavior here?
        //
        // The crux of the problem is that we may legalize a pointer-to-pointer
        // type in cases where one of the two needs to become an implicit-deref,
        // so that we have `PtrA<PtrB<Thing>>` become, say, `PtrA<Thing>` with
        // an `implicitDeref` wrapper. When we encounter a store to that
        // wrapped value, we seemingly need to know whether the original code
        // meant to store to `*ptrPtr` or `**ptrPtr`, and need to legalize
        // the result accordingly...
        //
        if( legalVal.flavor == LegalVal::Flavor::implicitDeref )
            return legalizeStore(context, legalPtrVal.getImplicitDeref(), legalVal.getImplicitDeref());
        else
            return legalizeStore(context, legalPtrVal.getImplicitDeref(), legalVal);

    case LegalVal::Flavor::pair:
        {
            auto destPair = legalPtrVal.getPair();
            auto valPair = legalVal.getPair();
            legalizeStore(context, destPair->ordinaryVal, valPair->ordinaryVal);
            legalizeStore(context, destPair->specialVal, valPair->specialVal);
            return LegalVal();
        }

    case LegalVal::Flavor::tuple:
        {
            // We need to emit a store for each element of
            // the tuple.
            auto destTuple = legalPtrVal.getTuple();
            auto valTuple = legalVal.getTuple();
            SLANG_ASSERT(destTuple->elements.getCount() == valTuple->elements.getCount());

            for (Index i = 0; i < valTuple->elements.getCount(); i++)
            {
                legalizeStore(context, destTuple->elements[i].val, valTuple->elements[i].val);
            }
            return legalVal;
        }
        break;

    default:
        SLANG_UNEXPECTED("unhandled case");
        break;
    }
}

static LegalVal legalizeFieldExtract(
    IRTypeLegalizationContext*  context,
    LegalType                   type,
    LegalVal                    legalStructOperand,
    IRStructKey*                fieldKey)
{
    auto builder = context->builder;

    if (type.flavor == LegalType::Flavor::none)
        return LegalVal();

    switch (legalStructOperand.flavor)
    {
    case LegalVal::Flavor::none:
        return LegalVal();

    case LegalVal::Flavor::simple:
        return LegalVal::simple(
            builder->emitFieldExtract(
                type.getSimple(),
                legalStructOperand.getSimple(),
                fieldKey));

    case LegalVal::Flavor::pair:
        {
            // There are two sides, the ordinary and the special,
            // and we basically just dispatch to both of them.
            auto pairVal = legalStructOperand.getPair();
            auto pairInfo = pairVal->pairInfo;
            auto pairElement = pairInfo->findElement(fieldKey);
            if (!pairElement)
            {
                SLANG_UNEXPECTED("didn't find tuple element");
                UNREACHABLE_RETURN(LegalVal());
            }

            // If the field we are extracting has a pair type,
            // that means it exists on both the ordinary and
            // special sides.
            RefPtr<PairInfo> fieldPairInfo;
            LegalType ordinaryType = type;
            LegalType specialType = type;
            if (type.flavor == LegalType::Flavor::pair)
            {
                auto fieldPairType = type.getPair();
                fieldPairInfo = fieldPairType->pairInfo;
                ordinaryType = fieldPairType->ordinaryType;
                specialType = fieldPairType->specialType;
            }

            LegalVal ordinaryVal;
            LegalVal specialVal;

            if (pairElement->flags & PairInfo::kFlag_hasOrdinary)
            {
                ordinaryVal = legalizeFieldExtract(
                    context,
                    ordinaryType,
                    pairVal->ordinaryVal,
                    fieldKey);
            }

            if (pairElement->flags & PairInfo::kFlag_hasSpecial)
            {
                specialVal = legalizeFieldExtract(
                    context,
                    specialType,
                    pairVal->specialVal,
                    fieldKey);
            }
            return LegalVal::pair(ordinaryVal, specialVal, fieldPairInfo);
        }
        break;

    case LegalVal::Flavor::tuple:
        {
            // The operand is a tuple of pointer-like
            // values, we want to extract the element
            // corresponding to a field. We will handle
            // this by simply returning the corresponding
            // element from the operand.
            auto ptrTupleInfo = legalStructOperand.getTuple();
            for (auto ee : ptrTupleInfo->elements)
            {
                if (ee.key == fieldKey)
                {
                    return ee.val;
                }
            }

            // TODO: we can legally reach this case now
            // when the field is "ordinary".

            SLANG_UNEXPECTED("didn't find tuple element");
            UNREACHABLE_RETURN(LegalVal());
        }

    default:
        SLANG_UNEXPECTED("unhandled");
        UNREACHABLE_RETURN(LegalVal());
    }
}

static LegalVal legalizeFieldExtract(
    IRTypeLegalizationContext*    context,
    LegalType                   type,
    LegalVal                    legalPtrOperand,
    LegalVal                    legalFieldOperand)
{
    // We don't expect any legalization to affect
    // the "field" argument.
    auto fieldKey = legalFieldOperand.getSimple();

    return legalizeFieldExtract(
        context,
        type,
        legalPtrOperand,
        (IRStructKey*) fieldKey);
}

    /// Take a value of some buffer/pointer type and unwrap it according to provided info.
static LegalVal unwrapBufferValue(
    IRTypeLegalizationContext*  context,
    LegalVal                    legalPtrOperand,
    LegalElementWrapping const& elementInfo)
{
    // The `elementInfo` tells us how a non-simple element
    // type was wrapped up into a new structure types used
    // as the element type of the buffer.
    //
    // This function will recurse through the structure of
    // `elementInfo` to pull out all the required data from
    // the buffer represented by `legalPtrOperand`.

    switch( elementInfo.flavor )
    {
    default:
        SLANG_UNEXPECTED("unhandled");
        UNREACHABLE_RETURN(LegalVal());
        break;

    case LegalElementWrapping::Flavor::none:
        return LegalVal();

    case LegalElementWrapping::Flavor::simple:
        {
            // In the leaf case, we just had to store some
            // data of a simple type in the buffer. We can
            // produce a valid result by computing the
            // address of the field used to represent the
            // element, and then returning *that* as if
            // it were the buffer type itself.
            //
            // (Basically instead of `someBuffer` we will
            // end up with `&(someBuffer->field)`.
            //
            auto builder = context->getBuilder();

            auto simpleElementInfo = elementInfo.getSimple();
            auto valPtr = builder->emitFieldAddress(
                builder->getPtrType(simpleElementInfo->type),
                legalPtrOperand.getSimple(),
                simpleElementInfo->key);

            return LegalVal::simple(valPtr);
        }

    case LegalElementWrapping::Flavor::implicitDeref:
        {
            // If the element type was logically `ImplicitDeref<T>`,
            // then we declared actual fields based on `T`, and
            // we need to extract references to those fields and
            // wrap them up in an `implicitDeref` value.
            //
            auto derefField = elementInfo.getImplicitDeref();
            auto baseVal = unwrapBufferValue(context, legalPtrOperand, derefField->field);
            return LegalVal::implicitDeref(baseVal);
        }

    case LegalElementWrapping::Flavor::pair:
        {
            // If the element type was logically a `Pair<O,S>`
            // then we encoded fields for both `O` and `S` into
            // the actual element type, and now we need to
            // extract references to both and pair them up.
            //
            auto pairField = elementInfo.getPair();
            auto pairInfo = pairField->pairInfo;

            auto ordinaryVal = unwrapBufferValue(context, legalPtrOperand, pairField->ordinary);
            auto specialVal = unwrapBufferValue(context, legalPtrOperand, pairField->special);
            return LegalVal::pair(ordinaryVal, specialVal, pairInfo);
        }

    case LegalElementWrapping::Flavor::tuple:
        {
            // If the element type was logically a `Tuple<E0, E1, ...>`
            // then we encoded fields for each of the `Ei` and
            // need to extract references to all of them and
            // encode them as a tuple.
            //
            auto tupleField = elementInfo.getTuple();

            RefPtr<TuplePseudoVal> obj = new TuplePseudoVal();
            for( auto ee : tupleField->elements )
            {
                auto elementVal = unwrapBufferValue(
                    context,
                    legalPtrOperand,
                    ee.field);

                TuplePseudoVal::Element element;
                element.key = ee.key;
                element.val = unwrapBufferValue(
                    context,
                    legalPtrOperand,
                    ee.field);
                obj->elements.add(element);
            }

            return LegalVal::tuple(obj);
        }
    }
}

static IRType* getPointedToType(
    IRTypeLegalizationContext*  context,
    IRType*                     ptrType)
{
    auto valueType = tryGetPointedToType(context->builder, ptrType);
    if( !valueType )
    {
        SLANG_UNEXPECTED("expected a pointer type during type legalization");
    }
    return valueType;
}

static LegalType getPointedToType(
    IRTypeLegalizationContext*  context,
    LegalType                   type)
{
    switch( type.flavor )
    {
    case LegalType::Flavor::none:
        return LegalType();

    case LegalType::Flavor::simple:
        return LegalType::simple(getPointedToType(context, type.getSimple()));

    case LegalType::Flavor::implicitDeref:
        return type.getImplicitDeref()->valueType;

    case LegalType::Flavor::pair:
        {
            auto pairType = type.getPair();
            auto ordinary = getPointedToType(context, pairType->ordinaryType);
            auto special = getPointedToType(context, pairType->specialType);
            return LegalType::pair(ordinary, special, pairType->pairInfo);
        }

    case LegalType::Flavor::tuple:
        {
            auto tupleType = type.getTuple();
            RefPtr<TuplePseudoType> resultTuple = new TuplePseudoType();
            for( auto ee : tupleType->elements )
            {
                TuplePseudoType::Element resultElement;
                resultElement.key = ee.key;
                resultElement.type = getPointedToType(context, ee.type);
                resultTuple->elements.add(resultElement);
            }
            return LegalType::tuple(resultTuple);
        }

    default:
        SLANG_UNEXPECTED("unhandled case in type legalization");
        UNREACHABLE_RETURN(LegalType());
    }
}

static LegalVal legalizeFieldAddress(
    IRTypeLegalizationContext*    context,
    LegalType                   type,
    LegalVal                    legalPtrOperand,
    IRStructKey*                fieldKey)
{
    auto builder = context->builder;
    if (type.flavor == LegalType::Flavor::none)
        return LegalVal();

    switch (legalPtrOperand.flavor)
    {
    case LegalVal::Flavor::none:
        return LegalVal();

    case LegalVal::Flavor::simple:
        switch( type.flavor )
        {
        case LegalType::Flavor::implicitDeref:
            // TODO: Should this case be needed?
            return legalizeFieldAddress(
                context,
                type.getImplicitDeref()->valueType,
                legalPtrOperand,
                fieldKey);

        default:
            return LegalVal::simple(
                builder->emitFieldAddress(
                    type.getSimple(),
                    legalPtrOperand.getSimple(),
                    fieldKey));
        }

    case LegalVal::Flavor::pair:
        {
            // There are two sides, the ordinary and the special,
            // and we basically just dispatch to both of them.
            auto pairVal = legalPtrOperand.getPair();
            auto pairInfo = pairVal->pairInfo;
            auto pairElement = pairInfo->findElement(fieldKey);
            if (!pairElement)
            {
                SLANG_UNEXPECTED("didn't find tuple element");
                UNREACHABLE_RETURN(LegalVal());
            }

            // If the field we are extracting has a pair type,
            // that means it exists on both the ordinary and
            // special sides.
            RefPtr<PairInfo> fieldPairInfo;
            LegalType ordinaryType = type;
            LegalType specialType = type;
            if (type.flavor == LegalType::Flavor::pair)
            {
                auto fieldPairType = type.getPair();
                fieldPairInfo = fieldPairType->pairInfo;
                ordinaryType = fieldPairType->ordinaryType;
                specialType = fieldPairType->specialType;
            }

            LegalVal ordinaryVal;
            LegalVal specialVal;

            if (pairElement->flags & PairInfo::kFlag_hasOrdinary)
            {
                ordinaryVal = legalizeFieldAddress(
                    context,
                    ordinaryType,
                    pairVal->ordinaryVal,
                    fieldKey);
            }

            if (pairElement->flags & PairInfo::kFlag_hasSpecial)
            {
                specialVal = legalizeFieldAddress(
                    context,
                    specialType,
                    pairVal->specialVal,
                    fieldKey);
            }
            return LegalVal::pair(ordinaryVal, specialVal, fieldPairInfo);
        }
        break;

    case LegalVal::Flavor::tuple:
        {
            // The operand is a tuple of pointer-like
            // values, we want to extract the element
            // corresponding to a field. We will handle
            // this by simply returning the corresponding
            // element from the operand.
            auto ptrTupleInfo = legalPtrOperand.getTuple();
            for (auto ee : ptrTupleInfo->elements)
            {
                if (ee.key == fieldKey)
                {
                    return ee.val;
                }
            }

            // TODO: we can legally reach this case now
            // when the field is "ordinary".

            SLANG_UNEXPECTED("didn't find tuple element");
            UNREACHABLE_RETURN(LegalVal());
        }

    case LegalVal::Flavor::implicitDeref:
        {
            // The original value had a level of indirection
            // that is now being removed, so should not be
            // able to get at the *address* of the field any
            // more, and need to resign ourselves to just
            // getting at the field *value* and then
            // adding an implicit dereference on top of that.
            //
            auto implicitDerefVal = legalPtrOperand.getImplicitDeref();
            auto valueType = getPointedToType(context, type);
            return LegalVal::implicitDeref(legalizeFieldExtract(context, valueType, implicitDerefVal, fieldKey));
        }

    default:
        SLANG_UNEXPECTED("unhandled");
        UNREACHABLE_RETURN(LegalVal());
    }
}

static LegalVal legalizeFieldAddress(
    IRTypeLegalizationContext*    context,
    LegalType                   type,
    LegalVal                    legalPtrOperand,
    LegalVal                    legalFieldOperand)
{
    // We don't expect any legalization to affect
    // the "field" argument.
    auto fieldKey = legalFieldOperand.getSimple();

    return legalizeFieldAddress(
        context,
        type,
        legalPtrOperand,
        (IRStructKey*) fieldKey);
}

static LegalVal legalizeGetElement(
    IRTypeLegalizationContext*  context,
    LegalType                   type,
    LegalVal                    legalPtrOperand,
    IRInst*                    indexOperand)
{
    auto builder = context->builder;

    switch (legalPtrOperand.flavor)
    {
    case LegalVal::Flavor::none:
        return LegalVal();

    case LegalVal::Flavor::simple:
        return LegalVal::simple(
            builder->emitElementExtract(
                type.getSimple(),
                legalPtrOperand.getSimple(),
                indexOperand));

    case LegalVal::Flavor::pair:
        {
            // There are two sides, the ordinary and the special,
            // and we basically just dispatch to both of them.
            auto pairVal = legalPtrOperand.getPair();
            auto pairInfo = pairVal->pairInfo;

            LegalType ordinaryType = type;
            LegalType specialType = type;
            if (type.flavor == LegalType::Flavor::pair)
            {
                auto pairType = type.getPair();
                ordinaryType = pairType->ordinaryType;
                specialType = pairType->specialType;
            }

            LegalVal ordinaryVal = legalizeGetElement(
                context,
                ordinaryType,
                pairVal->ordinaryVal,
                indexOperand);

            LegalVal specialVal = legalizeGetElement(
                context,
                specialType,
                pairVal->specialVal,
                indexOperand);

            return LegalVal::pair(ordinaryVal, specialVal, pairInfo);
        }
        break;

    case LegalVal::Flavor::tuple:
        {
            // The operand is a tuple of pointer-like
            // values, we want to extract the element
            // corresponding to a field. We will handle
            // this by simply returning the corresponding
            // element from the operand.
            auto ptrTupleInfo = legalPtrOperand.getTuple();

            RefPtr<TuplePseudoVal> resTupleInfo = new TuplePseudoVal();

            auto tupleType = type.getTuple();
            SLANG_ASSERT(tupleType);

            auto elemCount = ptrTupleInfo->elements.getCount();
            SLANG_ASSERT(elemCount == tupleType->elements.getCount());

            for(Index ee = 0; ee < elemCount; ++ee)
            {
                auto ptrElem = ptrTupleInfo->elements[ee];
                auto elemType = tupleType->elements[ee].type;

                TuplePseudoVal::Element resElem;
                resElem.key = ptrElem.key;
                resElem.val = legalizeGetElement(
                    context,
                    elemType,
                    ptrElem.val,
                    indexOperand);

                resTupleInfo->elements.add(resElem);
            }

            return LegalVal::tuple(resTupleInfo);
        }

    default:
        SLANG_UNEXPECTED("unhandled");
        UNREACHABLE_RETURN(LegalVal());
    }
}

static LegalVal legalizeGetElement(
    IRTypeLegalizationContext*  context,
    LegalType                   type,
    LegalVal                    legalPtrOperand,
    LegalVal                    legalIndexOperand)
{
    // We don't expect any legalization to affect
    // the "index" argument.
    auto indexOperand = legalIndexOperand.getSimple();

    return legalizeGetElement(
        context,
        type,
        legalPtrOperand,
        indexOperand);
}

static LegalVal legalizeGetElementPtr(
    IRTypeLegalizationContext*  context,
    LegalType                   type,
    LegalVal                    legalPtrOperand,
    IRInst*                    indexOperand)
{
    auto builder = context->builder;

    switch (legalPtrOperand.flavor)
    {
    case LegalVal::Flavor::none:
        return LegalVal();

    case LegalVal::Flavor::simple:
        return LegalVal::simple(
            builder->emitElementAddress(
                type.getSimple(),
                legalPtrOperand.getSimple(),
                indexOperand));

    case LegalVal::Flavor::pair:
        {
            // There are two sides, the ordinary and the special,
            // and we basically just dispatch to both of them.
            auto pairVal = legalPtrOperand.getPair();
            auto pairInfo = pairVal->pairInfo;

            LegalType ordinaryType = type;
            LegalType specialType = type;
            if (type.flavor == LegalType::Flavor::pair)
            {
                auto pairType = type.getPair();
                ordinaryType = pairType->ordinaryType;
                specialType = pairType->specialType;
            }

            LegalVal ordinaryVal = legalizeGetElementPtr(
                context,
                ordinaryType,
                pairVal->ordinaryVal,
                indexOperand);

            LegalVal specialVal = legalizeGetElementPtr(
                context,
                specialType,
                pairVal->specialVal,
                indexOperand);

            return LegalVal::pair(ordinaryVal, specialVal, pairInfo);
        }
        break;

    case LegalVal::Flavor::tuple:
        {
            // The operand is a tuple of pointer-like
            // values, we want to extract the element
            // corresponding to a field. We will handle
            // this by simply returning the corresponding
            // element from the operand.
            auto ptrTupleInfo = legalPtrOperand.getTuple();

            RefPtr<TuplePseudoVal> resTupleInfo = new TuplePseudoVal();

            auto tupleType = type.getTuple();
            SLANG_ASSERT(tupleType);

            auto elemCount = ptrTupleInfo->elements.getCount();
            SLANG_ASSERT(elemCount == tupleType->elements.getCount());

            for(Index ee = 0; ee < elemCount; ++ee)
            {
                auto ptrElem = ptrTupleInfo->elements[ee];
                auto elemType = tupleType->elements[ee].type;

                TuplePseudoVal::Element resElem;
                resElem.key = ptrElem.key;
                resElem.val = legalizeGetElementPtr(
                    context,
                    elemType,
                    ptrElem.val,
                    indexOperand);

                resTupleInfo->elements.add(resElem);
            }

            return LegalVal::tuple(resTupleInfo);
        }

    case LegalVal::Flavor::implicitDeref:
        {
            // The original value used to be a pointer to an array,
            // and somebody is trying to get at an element pointer.
            // Now we just have an array (wrapped with an implicit
            // dereference) and need to just fetch the chosen element
            // instead (and then wrap the element value with an
            // implicit dereference).
            //
            // The result type for our `getElement` instruction needs
            // to be the type *pointed to* by `type`, and not `type.
            //
            auto valueType = getPointedToType(context, type);

            auto implicitDerefVal = legalPtrOperand.getImplicitDeref();
            return LegalVal::implicitDeref(legalizeGetElement(
                context,
                valueType,
                implicitDerefVal,
                indexOperand));
        }

    default:
        SLANG_UNEXPECTED("unhandled");
        UNREACHABLE_RETURN(LegalVal());
    }
}

static LegalVal legalizeGetElementPtr(
    IRTypeLegalizationContext*  context,
    LegalType                   type,
    LegalVal                    legalPtrOperand,
    LegalVal                    legalIndexOperand)
{
    // We don't expect any legalization to affect
    // the "index" argument.
    auto indexOperand = legalIndexOperand.getSimple();

    return legalizeGetElementPtr(
        context,
        type,
        legalPtrOperand,
        indexOperand);
}

static LegalVal legalizeMakeStruct(
    IRTypeLegalizationContext*  context,
    LegalType                   legalType,
    LegalVal const*             legalArgs,
    UInt                        argCount)
{
    auto builder = context->builder;

    switch(legalType.flavor)
    {
    case LegalType::Flavor::none:
        return LegalVal();

    case LegalType::Flavor::simple:
        {
            List<IRInst*> args;
            for(UInt aa = 0; aa < argCount; ++aa)
            {
                // Ignore none values.
                if (legalArgs[aa].flavor == LegalVal::Flavor::none)
                    continue;

                // Note: we assume that all the arguments
                // must be simple here, because otherwise
                // the `struct` type with them as fields
                // would not be simple...
                //
                args.add(legalArgs[aa].getSimple());
            }
            return LegalVal::simple(
                builder->emitMakeStruct(
                    legalType.getSimple(),
                    args.getCount(),
                    args.getBuffer()));
        }

    case LegalType::Flavor::pair:
        {
            // There are two sides, the ordinary and the special,
            // and we basically just dispatch to both of them.
            auto pairType = legalType.getPair();
            auto pairInfo = pairType->pairInfo;
            LegalType ordinaryType = pairType->ordinaryType;
            LegalType specialType = pairType->specialType;

            List<LegalVal> ordinaryArgs;
            List<LegalVal> specialArgs;
            UInt argCounter = 0;
            for(auto ee : pairInfo->elements)
            {
                UInt argIndex = argCounter++;
                LegalVal arg = legalArgs[argIndex];

                if( arg.flavor == LegalVal::Flavor::pair )
                {
                    // The argument is itself a pair
                    auto argPair = arg.getPair();
                    ordinaryArgs.add(argPair->ordinaryVal);
                    specialArgs.add(argPair->specialVal);
                }
                else if(ee.flags & Slang::PairInfo::kFlag_hasOrdinary)
                {
                    ordinaryArgs.add(arg);
                }
                else if(ee.flags & Slang::PairInfo::kFlag_hasSpecial)
                {
                    specialArgs.add(arg);
                }
            }

            LegalVal ordinaryVal = legalizeMakeStruct(
                context,
                ordinaryType,
                ordinaryArgs.getBuffer(),
                ordinaryArgs.getCount());

            LegalVal specialVal = legalizeMakeStruct(
                context,
                specialType,
                specialArgs.getBuffer(),
                specialArgs.getCount());

            return LegalVal::pair(ordinaryVal, specialVal, pairInfo);
        }
        break;

    case LegalType::Flavor::tuple:
        {
            // We are constructing a tuple of values from
            // the individual fields. We need to identify
            // for each tuple element what field it uses,
            // and then extract that field's value.

            auto tupleType = legalType.getTuple();

            RefPtr<TuplePseudoVal> resTupleInfo = new TuplePseudoVal();
            UInt argCounter = 0;
            for(auto typeElem : tupleType->elements)
            {
                auto elemKey = typeElem.key;
                UInt argIndex = argCounter++;
                SLANG_ASSERT(argIndex < argCount);

                LegalVal argVal = legalArgs[argIndex];

                TuplePseudoVal::Element resElem;
                resElem.key = elemKey;
                resElem.val = argVal;

                resTupleInfo->elements.add(resElem);
            }
            return LegalVal::tuple(resTupleInfo);
        }

    default:
        SLANG_UNEXPECTED("unhandled");
        UNREACHABLE_RETURN(LegalVal());
    }
}

static LegalVal legalizeInst(
    IRTypeLegalizationContext*    context,
    IRInst*                     inst,
    LegalType                   type,
    LegalVal const*             args)
{
    switch (inst->getOp())
    {
    case kIROp_Load:
        return legalizeLoad(context, args[0]);

    case kIROp_GetValueFromBoundInterface:
        return args[0];

    case kIROp_FieldAddress:
        return legalizeFieldAddress(context, type, args[0], args[1]);

    case kIROp_FieldExtract:
        return legalizeFieldExtract(context, type, args[0], args[1]);

    case kIROp_GetElement:
        return legalizeGetElement(context, type, args[0], args[1]);

    case kIROp_GetElementPtr:
        return legalizeGetElementPtr(context, type, args[0], args[1]);

    case kIROp_Store:
        return legalizeStore(context, args[0], args[1]);

    case kIROp_Call:
        return legalizeCall(context, (IRCall*)inst);
    case kIROp_Return:
        return legalizeRetVal(context, args[0], (IRReturn*)inst);
    case kIROp_MakeStruct:
        return legalizeMakeStruct(
            context,
            type,
            args,
            inst->getOperandCount());
    case kIROp_undefined:
        return LegalVal();
    case kIROp_GpuForeach:
        // This case should only happen when compiling for a target that does not support GpuForeach
        return LegalVal();
    default:
        // TODO: produce a user-visible diagnostic here
        SLANG_UNEXPECTED("non-simple operand(s)!");
        break;
    }
}

IRVarLayout* findVarLayout(IRInst* value)
{
    if (auto layoutDecoration = value->findDecoration<IRLayoutDecoration>())
        return as<IRVarLayout>(layoutDecoration->getLayout());
    return nullptr;
}

static UnownedStringSlice findNameHint(IRInst* inst)
{
    if( auto nameHintDecoration = inst->findDecoration<IRNameHintDecoration>() )
    {
        return nameHintDecoration->getName();
    }
    return UnownedStringSlice();
}

static LegalVal legalizeLocalVar(
    IRTypeLegalizationContext*    context,
    IRVar*                irLocalVar)
{
    // Legalize the type for the variable's value
    auto originalValueType = irLocalVar->getDataType()->getValueType();
    auto legalValueType = legalizeType(
        context,
        originalValueType);

    auto originalRate = irLocalVar->getRate();

    IRVarLayout* varLayout = findVarLayout(irLocalVar);
    IRTypeLayout* typeLayout = varLayout ? varLayout->getTypeLayout() : nullptr;

    // If we've decided to do implicit deref on the type,
    // then go ahead and declare a value of the pointed-to type.
    LegalType maybeSimpleType = legalValueType;
    while (maybeSimpleType.flavor == LegalType::Flavor::implicitDeref)
    {
        maybeSimpleType = maybeSimpleType.getImplicitDeref()->valueType;
    }

    switch (maybeSimpleType.flavor)
    {
    case LegalType::Flavor::simple:
        {
            // Easy case: the type is usable as-is, and we
            // should just do that.
            auto type = maybeSimpleType.getSimple();
            type = context->builder->getPtrType(type);
            if( originalRate )
            {
                type = context->builder->getRateQualifiedType(
                    originalRate,
                    type);
            }
            irLocalVar->setFullType(type);
            return LegalVal::simple(irLocalVar);
        }

    default:
    {
        // TODO: We don't handle rates in this path.

        context->insertBeforeLocalVar = irLocalVar;

        LegalVarChainLink varChain(LegalVarChain(), varLayout);

        UnownedStringSlice nameHint = findNameHint(irLocalVar);
        context->builder->setInsertBefore(irLocalVar);
        LegalVal newVal = declareVars(context, kIROp_Var, legalValueType, typeLayout, varChain, nameHint, irLocalVar, nullptr, context->isSpecialType(originalValueType));

        // Remove the old local var.
        irLocalVar->removeFromParent();
        // add old local var to list
        context->replacedInstructions.add(irLocalVar);
        return newVal;
    }
    break;
    }
}

static LegalVal legalizeParam(
    IRTypeLegalizationContext*  context,
    IRParam*                    originalParam)
{
    auto legalParamType = legalizeType(context, originalParam->getFullType());
    if (legalParamType.flavor == LegalType::Flavor::simple)
    {
        // Simple case: things were legalized to a simple type,
        // so we can just use the original parameter as-is.
        originalParam->setFullType(legalParamType.getSimple());
        return LegalVal::simple(originalParam);
    }
    else
    {
        // Complex case: we need to insert zero or more new parameters,
        // which will replace the old ones.

        context->insertBeforeParam = originalParam;

        UnownedStringSlice nameHint = findNameHint(originalParam);

        context->builder->setInsertBefore(originalParam);
        auto newVal = declareVars(context, kIROp_Param, legalParamType, nullptr, LegalVarChain(), nameHint, originalParam, nullptr, context->isSpecialType(originalParam->getDataType()));

        originalParam->removeFromParent();
        context->replacedInstructions.add(originalParam);
        return newVal;
    }
}

static LegalVal legalizeFunc(
    IRTypeLegalizationContext*  context,
    IRFunc*                     irFunc);

static LegalVal legalizeGlobalVar(
    IRTypeLegalizationContext*    context,
    IRGlobalVar*                irGlobalVar);

static LegalVal legalizeGlobalParam(
    IRTypeLegalizationContext*  context,
    IRGlobalParam*              irGlobalParam);

static LegalVal legalizeInst(
    IRTypeLegalizationContext*  context,
    IRInst*                     inst)
{
    // Any additional instructions we need to emit
    // in the process of legalizing `inst` should
    // by default be insertied right before `inst`.
    //
    context->builder->setInsertBefore(inst);

    // Special-case certain operations
    switch (inst->getOp())
    {
    case kIROp_Var:
        return legalizeLocalVar(context, cast<IRVar>(inst));

    case kIROp_Param:
        return legalizeParam(context, cast<IRParam>(inst));

    case kIROp_WitnessTable:
        // Just skip these.
        break;

    case kIROp_Func:
        return legalizeFunc(context, cast<IRFunc>(inst));

    case kIROp_GlobalVar:
        return legalizeGlobalVar(context, cast<IRGlobalVar>(inst));

    case kIROp_GlobalParam:
        return legalizeGlobalParam(context, cast<IRGlobalParam>(inst));

    case kIROp_Block:
        return LegalVal::simple(inst);

    default:
        break;
    }

    if(as<IRAttr>(inst))
        return LegalVal::simple(inst);


    // We will iterate over all the operands, extract the legalized
    // value of each, and collect them in an array for subsequent use.
    //
    auto argCount = inst->getOperandCount();
    List<LegalVal> legalArgs;
    //
    // Along the way we will also note whether there were any operands
    // with non-simple legalized values.
    //
    bool anyComplex = false;
    for (UInt aa = 0; aa < argCount; ++aa)
    {
        auto oldArg = inst->getOperand(aa);
        auto legalArg = legalizeOperand(context, oldArg);
        legalArgs.add(legalArg);

        if (legalArg.flavor != LegalVal::Flavor::simple)
            anyComplex = true;
    }

    // We must also legalize the type of the instruction, since that
    // is implicitly one of its operands.
    //
    LegalType legalType = legalizeType(context, inst->getFullType());

    // If there was nothing interesting that occured for the operands
    // then we can re-use this instruction as-is.
    //
    if (!anyComplex && legalType.flavor == LegalType::Flavor::simple)
    {
        // While the operands are all "simple," they might not necessarily
        // be equal to the operands we started with.
        //
        for (UInt aa = 0; aa < argCount; ++aa)
        {
            auto legalArg = legalArgs[aa];
            inst->setOperand(aa, legalArg.getSimple());
        }

        inst->setFullType(legalType.getSimple());

        return LegalVal::simple(inst);
    }

    // We have at least one "complex" operand, and we
    // need to figure out what to do with it. The anwer
    // will, in general, depend on what we are doing.

    // We will set up the IR builder so that any new
    // instructions generated will be placed before
    // the location of the original instruction.
    auto builder = context->builder;
    builder->setInsertBefore(inst);

    LegalVal legalVal = legalizeInst(
        context,
        inst,
        legalType,
        legalArgs.getBuffer());

    // After we are done, we will eliminate the
    // original instruction by removing it from
    // the IR.
    //
    inst->removeFromParent();
    context->replacedInstructions.add(inst);

    // The value to be used when referencing
    // the original instruction will now be
    // whatever value(s) we created to replace it.
    return legalVal;
}

    /// Helper type for legalizing the signature of an `IRFunc`
struct LegalFuncBuilder
{
    LegalFuncBuilder(IRTypeLegalizationContext* context)
        : m_context(context)
    {}

        /// Construct a legalized value to represent `oldFunc`
    LegalVal build(IRFunc* oldFunc)
    {
        // We can start by computing what the type signature of the
        // legalized function should be, based on the type signature
        // of the original.
        //
        IRFuncType* oldFuncType = oldFunc->getDataType();

        // Each parameter of the original function will translate into
        // zero or more parameters in the legalized function signature.
        //
        UInt oldParamCount = oldFuncType->getParamCount();
        for (UInt pp = 0; pp < oldParamCount; ++pp)
        {
            auto legalParamType = legalizeType(m_context, oldFuncType->getParamType(pp));
            _addParam(legalParamType);
        }

        // We will record how many parameters resulted from
        // legalization of the original / "base" parameter list.
        // This number will help us in computing how many parameters
        // were added to capture the result type of the function.
        //
        Index baseLegalParamCount = m_paramTypes.getCount();

        // Next we add a result type to the function based on the
        // legalized result type of the original function.
        //
        // It is possible that this process will had one or more
        // `out` parameters to represent parts of the result type
        // that couldn't be passed via the ordinary function result.
        //
        auto legalResultType = legalizeType(m_context, oldFuncType->getResultType());
        _addResult(legalResultType);

        // If any part of the result type required new function parameters
        // to be introduced, then we want to know how many there were.
        // These additional function paameters will always come after the original
        // parameters, so that they don't shift around call sites too much.
        //
        // TODO: Where we put the added `out` parameters in the signature may
        // have performance implications when it starts interacting with ABI
        // (e.g., most ABIs assign parameters to registers from left to right,
        // so parameters later in the list are more likely to be passed through
        // memory; we'd need to decide whether the base parameters or the
        // legalized result parameters should be prioritized for register
        // allocation).
        //
        Index resultParamCount = m_paramTypes.getCount() - baseLegalParamCount;

        // If we didn't bottom out on a result type for the legalized function,
        // then we should default to returning `void`.
        //
        auto irBuilder = m_context->builder;
        if( !m_resultType )
        {
            m_resultType = irBuilder->getVoidType();
        }

        // We will compute the new IR type for the function and install it
        // as the data type of original function.
        //
        // Note: This is one of the few cases where the legalization pass
        // prefers to modify an IR node in-place rather than create a distinct
        // legalized copy of it.
        //
        auto newFuncType = irBuilder->getFuncType(
            m_paramTypes.getCount(),
            m_paramTypes.getBuffer(),
            m_resultType);
        irBuilder->setDataType(oldFunc, newFuncType);

        // If the function required any new parameters to be created
        // to represent the result/return type, then we need to
        // actually add the appropriate IR parameters to represent
        // that stuff as well.
        //
        if( resultParamCount != 0 )
        {
            // Only a function with a body will need this additonal
            // step, since the function parameters are stored on the
            // first block of the body.
            //
            auto firstBlock = oldFunc->getFirstBlock();
            if( firstBlock )
            {
                // Because legalization of this function required us
                // to introduce new parameters, we need to allocate
                // a data structure to record the identities of those
                // new parameters so that they can be looked up when
                // legalizing the body of the function.
                //
                // In particular, we will use this information when
                // legalizing `return` instructions in the function body,
                // since those will need to store at least part of
                // the reuslt value into the newly-declared parameter(s).
                //
                RefPtr<LegalFuncInfo> funcInfo = new LegalFuncInfo();
                m_context->mapFuncToInfo.Add(oldFunc, funcInfo);

                // We know that our new parameters need to come after
                // those that were declared for the "base" parameters
                // of the original function.
                //
                auto firstResultParamIndex = baseLegalParamCount;
                auto firstOrdinaryInst = firstBlock->getFirstOrdinaryInst();
                for( Index i = 0; i < resultParamCount; ++i )
                {
                    // Note: The parameter types that were added to
                    // the `m_paramTypes` array already account for the
                    // fact that these are `out` parameters, since that
                    // impacts the function type signature as well.
                    // We do *not* need to wrap `paramType` in an `Out<...>`
                    // type here.
                    //
                    auto paramType = m_paramTypes[firstResultParamIndex + i];
                    auto param = irBuilder->createParam(paramType);
                    param->insertBefore(firstOrdinaryInst);

                    funcInfo->resultParamVals.add(param);
                }
            }
        }

        // Note: at this point we do *not* apply legalization to the parameters
        // of the function or its body; those are left for the recursive part
        // of the overall legalization pass to handle.

        return LegalVal::simple(oldFunc);
    }


private:
    IRTypeLegalizationContext* m_context = nullptr;;

        /// The types of the parameters of the legalized function
    List<IRType*> m_paramTypes;

        /// The result type of the legalized function (can be null to represent `void`)
    IRType* m_resultType = nullptr;

        /// Add a parameter of type `t` to the function signature
    void _addParam(LegalType t)
    {
        // This logic is a simple recursion over the structure of `t`,
        // with the leaf case adding parameters of simple IR type.

        switch (t.flavor)
        {
        case LegalType::Flavor::none:
            break;

        case LegalType::Flavor::simple:
            m_paramTypes.add(t.getSimple());
            break;

        case LegalType::Flavor::implicitDeref:
            {
                auto imp = t.getImplicitDeref();
                _addParam(imp->valueType);
            }
            break;
        case LegalType::Flavor::pair:
            {
                auto pairInfo = t.getPair();
                _addParam(pairInfo->ordinaryType);
                _addParam(pairInfo->specialType);
            }
            break;
        case LegalType::Flavor::tuple:
            {
                auto tup = t.getTuple();
                for (auto & elem : tup->elements)
                    _addParam(elem.type);
            }
            break;
        default:
            SLANG_UNEXPECTED("unknown legalized type flavor");
        }
    }

        /// Set the logical result type of the legalized function to `t`
    void _addResult(LegalType t)
    {
        switch (t.flavor)
        {
        case LegalType::Flavor::simple:
            // The simple case is when the result type is a simple IR
            // type, and we can use it directly as the return type.
            //
            m_resultType = t.getSimple();
            break;


        case LegalType::Flavor::none:
            // The case where we have no result type is also simple,
            // becaues we can leave `m_resultType` as null to represent
            // a `void` result type.
            break;

        case LegalType::Flavor::implicitDeref:
            {
                // An `implicitDeref` is a wrapper around another legal
                // type, so we can simply set the result type to the
                // unwrapped inner type.
                //
                auto imp = t.getImplicitDeref();
                _addResult(imp->valueType);
            }
            break;

        case LegalType::Flavor::pair:
            {
                // The `pair` case is the first interesting one.
                //
                // We will set the actual result type of the operation
                // to the ordinary side of the pair, while any special
                // part of the pair will be returned via fresh `out`
                // parameters insteqad.
                //
                auto pairInfo = t.getPair();
                _addResult(pairInfo->ordinaryType);
                _addOutParam(pairInfo->specialType);
            }
            break;

        case LegalType::Flavor::tuple:
            {
                // In the `tuple` case we have zero or more types,
                // and there is no distinguished primary one that
                // should become the result type of the legalized function.
                //
                // We will instead declare fresh `out` parameters to
                // capture all the outputs in the tuple.
                //
                auto tup = t.getTuple();
                for( auto & elem : tup->elements )
                {
                    _addOutParam(elem.type);
                }
            }
            break;

        default:
            SLANG_UNEXPECTED("unknown legalized type flavor");
        }
    }

        /// Add a single `out` parameter based on type `t`.
    void _addOutParam(LegalType t)
    {
        switch (t.flavor)
        {
        case LegalType::Flavor::simple:
            // The simple case here is almost the same as `_addParam()`,
            // except we wrap the simple type in `Out<...>` to indicate
            // that we are producing an `out` parameter.
            //
            m_paramTypes.add(m_context->builder->getOutType(t.getSimple()));
            break;

        // The remaining cases are all simple recursion on the
        // structure of `t`.

        case LegalType::Flavor::none:
            break;

        case LegalType::Flavor::implicitDeref:
            {
                auto imp = t.getImplicitDeref();
                _addOutParam(imp->valueType);
            }
            break;
        case LegalType::Flavor::pair:
            {
                auto pairInfo = t.getPair();
                _addOutParam(pairInfo->ordinaryType);
                _addOutParam(pairInfo->specialType);
            }
            break;
        case LegalType::Flavor::tuple:
            {
                auto tup = t.getTuple();
                for( auto & elem : tup->elements )
                {
                    _addOutParam(elem.type);
                }
            }
            break;
        default:
            SLANG_UNEXPECTED("unknown legalized type flavor");
        }
    }
};

static LegalVal legalizeFunc(
    IRTypeLegalizationContext*  context,
    IRFunc*                     irFunc)
{
    LegalFuncBuilder builder(context);
    return builder.build(irFunc);
}

static LegalVal declareSimpleVar(
    IRTypeLegalizationContext*  context,
    IROp                        op,
    IRType*                     type,
    IRTypeLayout*               typeLayout,
    LegalVarChain const&        varChain,
    UnownedStringSlice          nameHint,
    IRInst*                     leafVar,
    IRGlobalNameInfo*           globalNameInfo)
{
    SLANG_UNUSED(globalNameInfo);

    IRVarLayout* varLayout = createVarLayout(context->builder, varChain, typeLayout);

    IRBuilder* builder = context->builder;

    IRInst*    irVar = nullptr;
    LegalVal    legalVarVal;

    switch (op)
    {
    case kIROp_GlobalVar:
        {
            auto globalVar = builder->createGlobalVar(type);
            globalVar->removeFromParent();
            globalVar->insertBefore(context->insertBeforeGlobal);

            irVar = globalVar;
            legalVarVal = LegalVal::simple(irVar);
        }
        break;

    case kIROp_GlobalParam:
        {
            auto globalParam = builder->createGlobalParam(type);
            globalParam->removeFromParent();
            globalParam->insertBefore(context->insertBeforeGlobal);

            irVar = globalParam;
            legalVarVal = LegalVal::simple(globalParam);
        }
        break;

    case kIROp_Var:
        {
            builder->setInsertBefore(context->insertBeforeLocalVar);
            auto localVar = builder->emitVar(type);
            
            irVar = localVar;
            legalVarVal = LegalVal::simple(irVar);

        }
        break;

    case kIROp_Param:
        {
            auto param = builder->emitParam(type);
            param->insertBefore(context->insertBeforeParam);

            irVar = param;
            legalVarVal = LegalVal::simple(irVar);
        }
        break;

    default:
        SLANG_UNEXPECTED("unexpected IR opcode");
        break;
    }

    if (irVar)
    {
        if (varLayout)
        {
            builder->addLayoutDecoration(irVar, varLayout);
        }

        if( nameHint.getLength() )
        {
            context->builder->addNameHintDecoration(irVar, nameHint);
        }

        if( leafVar )
        {
            for( auto decoration : leafVar->getDecorations() )
            {
                switch( decoration->getOp() )
                {
                case kIROp_FormatDecoration:
                    cloneDecoration(decoration, irVar);
                    break;

                default:
                    break;
                }
            }
        }

    }

    return legalVarVal;
}

    /// Add layout information for the fields of a wrapped buffer type.
    ///
    /// A wrapped buffer type encodes a buffer like `ConstantBuffer<Foo>`
    /// where `Foo` might have interface-type fields that have been
    /// specialized to a concrete type. E.g.:
    ///
    ///     struct Car { IDriver driver; int mph; };
    ///     ConstantBuffer<Car> machOne;
    ///
    /// In a case where the `machOne.driver` field has been specialized
    /// to the type `SpeedRacer`, we need to generate a legalized
    /// buffer layout something like:
    ///
    ///     struct Car_0 { int mph; }
    ///     struct Wrapped { Car_0 car; SpeedRacer card_d; }
    ///     ConstantBuffer<Wrapped> machOne;
    ///
    /// The layout information for the existing `machOne` clearly
    /// can't apply because we have a new element type with new fields.
    ///
    /// This function is used to recursively fill in the layout for
    /// the fields of the `Wrapped` type, using information recorded
    /// when the legal wrapped buffer type was created.
    ///
static void _addFieldsToWrappedBufferElementTypeLayout(
    IRBuilder*                      irBuilder,
    IRTypeLayout*                   elementTypeLayout,  // layout of the original field type
    IRStructTypeLayout::Builder*    newTypeLayout,      // layout we are filling in
    LegalElementWrapping const&     elementInfo,        // information on how the original type got wrapped
    LegalVarChain const&            varChain,           // chain of variables that is leading to this field
    bool                            isSpecial)          // should we assume a leaf field is a special (interface) type?
{
    // The way we handle things depends primary on the
    // `elementInfo`, because that tells us how things
    // were wrapped up when the type was legalized.

    switch( elementInfo.flavor )
    {
    case LegalElementWrapping::Flavor::none:
        // A leaf `none` value meant there was nothing
        // to encode for a particular field (probably
        // had a `void` or empty structure type).
        break;

    case LegalElementWrapping::Flavor::simple:
        {
            auto simpleInfo = elementInfo.getSimple();

            // A `simple` wrapping means we hit a leaf
            // field that can be encoded directly.
            // What we do here depends on whether we've
            // reached an ordinary field of the original
            // data type, or if we've reached a leaf
            // field of interface type.
            //
            // We've been tracking a `varChain` that
            // remembers all the parent `struct` fields
            // we've navigated through to get here, and
            // that information has been tracking two
            // different pieces of layout:
            //
            // * The "primary" layout represents the storage
            // of the buffer element type as we usually
            // think of its (e.g., the bytes starting at offset zero).
            //
            // * The "pending" layout tells us where all the
            // fields representing concrete types plugged in
            // for interface-type slots got placed.
            //
            // We have tunneled down info to tell us which case
            // we should use (`isSpecial`).
            //
            // Most of the logic is the same between the two
            // cases. We will be computing layout information
            // for a field of the new/wrapped buffer element type.
            //
            IRVarLayout* newFieldLayout = nullptr;
            if(isSpecial)
            {
                // In the special case, that field will be laid out
                // based on the "pending" var chain, and the type
                // of the pending data for the element.
                //
                newFieldLayout = createSimpleVarLayout(
                    irBuilder,
                    varChain.pendingChain,
                    elementTypeLayout->getPendingDataTypeLayout());
            }
            else
            {
                // The ordinary case just uses the primary layout
                // information and the primary/nominal type of
                // the field.
                //
                newFieldLayout = createSimpleVarLayout(
                    irBuilder,
                    varChain.primaryChain,
                    elementTypeLayout);
            }

            // Either way, we add the new field to the struct type
            // layout we are building, and also update the mapping
            // information so that we can find the field layout
            // based on the IR key for the struct field.
            //
            newTypeLayout->addField(simpleInfo->key, newFieldLayout);
        }
        break;

    case LegalElementWrapping::Flavor::implicitDeref:
        {
            // This is the case where a field in the element type
            // has been legalized from `SomePtrLikeType<T>` to
            // `T`, so there is a different in levels of indirection.
            //
            // We need to recurse and see how the type `T`
            // got laid out to know what field(s) it might comprise.
            //
            auto implicitDerefInfo = elementInfo.getImplicitDeref();
            _addFieldsToWrappedBufferElementTypeLayout(
                irBuilder,
                elementTypeLayout,
                newTypeLayout,
                implicitDerefInfo->field,
                varChain,
                isSpecial);
            return;
        }
        break;

    case LegalElementWrapping::Flavor::pair:
        {
            // The pair case is the first main workhorse where
            // if we had a type that mixed ordinary and interface-type
            // fields, it would get split into an ordinary part
            // and a "special" part, each of which might comprise
            // zero or more fields.
            //
            // Here we recurse on both the ordinary and special
            // sides, and the only interesting tidbit is that
            // we pass along appropriate values for the `isSpecial`
            // flag so that we act appropriately upon running
            // into a leaf field.
            //
            auto pairElementInfo = elementInfo.getPair();
            _addFieldsToWrappedBufferElementTypeLayout(
                irBuilder,
                elementTypeLayout,
                newTypeLayout,
                pairElementInfo->ordinary,
                varChain,
                false);
            _addFieldsToWrappedBufferElementTypeLayout(
                irBuilder,
                elementTypeLayout,
                newTypeLayout,
                pairElementInfo->special,
                varChain,
                true);
        }
        break;

    case LegalElementWrapping::Flavor::tuple:
        {
            auto tupleInfo = elementInfo.getTuple();

            // There is an extremely special case that we need to deal with here,
            // which is the case where the original element/field had an interface
            // type, which was subject to static specialization.
            //
            // In such a case, the layout will show a simple type layout for
            // the field/element itself (just uniform data) plus a "pending"
            // layout for any fields related to the static specialization.
            //
            // In contrast, the actual IR type structure will have been turned
            // into a `struct` type with multiple fields, one of which is a
            // pseudo-pointer to the "pending" data. That field would require
            // legalization, sending us down this path.
            //
            // The situation here is that we have an `elementTypeLayout` that
            // is for a single field of interface type, but an `elementInfo`
            // that corresponds to a struct with 3 or more fields (the tuple
            // that was introduced to represent the interface type).
            //
            // We expect that `elementInfo` will represent a tuple with
            // only a single element, and that element will reference the third
            // field of the tuple/struct (the payload).
            //
            // What we want to do in this case is instead add the fields
            // corresponding to the payload type, which are stored as
            // the pending type layout on `elementTypeLayout`.
            //
            if( isSpecial )
            {
                if( auto existentialTypeLayout = as<IRExistentialTypeLayout>(elementTypeLayout) )
                {
                    if( auto pendingTypeLayout = existentialTypeLayout->getPendingDataTypeLayout() )
                    {
                        SLANG_ASSERT(tupleInfo->elements.getCount() == 1);

                        for( auto ee : tupleInfo->elements )
                        {
                            _addFieldsToWrappedBufferElementTypeLayout(
                                irBuilder,
                                existentialTypeLayout,
                                newTypeLayout,
                                ee.field,
                                varChain,
                                true);
                        }

                        return;
                    }
                }
            }

            // A tuple comes up when we've turned an aggregate
            // with one or more interface-type fields into
            // distinct fields at the top level.
            //
            // For the most part we just recurse on each field,
            // but note that we set the `isSpecial` flag on
            // the recursive calls, since we never use tuples
            // to store anything that isn't special.

            for( auto ee : tupleInfo->elements )
            {
                auto oldFieldLayout = getFieldLayout(elementTypeLayout, ee.key);
                SLANG_ASSERT(oldFieldLayout);

                LegalVarChainLink fieldChain(varChain, oldFieldLayout);

                _addFieldsToWrappedBufferElementTypeLayout(
                    irBuilder,
                    oldFieldLayout->getTypeLayout(),
                    newTypeLayout,
                    ee.field,
                    fieldChain,
                    true);
            }
        }
        break;

    default:
        SLANG_UNEXPECTED("unhandled element wrapping flavor");
        break;
    }
}

    /// Add offset information for `kind` to `resultVarLayout`,
    /// if it doesn't already exist, and adjust the offset so
    /// that it will represent an offset relative to the
    /// "primary" data for the surrounding type, rather than
    /// being relative to the "pending" data.
    ///
static void _addOffsetVarLayoutEntry(
    IRVarLayout::Builder*   resultVarLayout,
    LegalVarChain const&    varChain,
    LayoutResourceKind      kind)
{
    // If the target already has an offset for this kind, bail out.
    //
    if(resultVarLayout->usesResourceKind(kind))
        return;

    // Add the `ResourceInfo` that will represent the offset for
    // this resource kind (it will be initialized to zero by default)
    //
    auto resultResInfo = resultVarLayout->findOrAddResourceInfo(kind);

    // Add in any contributions from the "pending" var chain, since
    // that chain of offsets will accumulate to get the leaf offset
    // within the pending data, which in this case we assume amounts
    // to an *absolute* offset.
    //
    for(auto vv = varChain.pendingChain; vv; vv = vv->next )
    {
        if( auto chainResInfo = vv->varLayout->findOffsetAttr(kind) )
        {
            resultResInfo->offset += chainResInfo->getOffset();
            resultResInfo->space += chainResInfo->getSpace();
        }
    }

    // Subtract any contributions from the primary var chain, since
    // we want the resulting offset to be relative to the same
    // base as that chain.
    //
    for(auto vv = varChain.primaryChain; vv; vv = vv->next )
    {
        if( auto chainResInfo = vv->varLayout->findOffsetAttr(kind) )
        {
            resultResInfo->offset -= chainResInfo->getOffset();
            resultResInfo->space -= chainResInfo->getSpace();
        }
    }
}

    /// Create a variable layout for an field with "pending" type.
    ///
    /// The given `typeLayout` should represent the type of a field
    /// that is being stored in "pending" data, but that now needs
    /// to be made relative to the "primary" data, because we are
    /// legalizing the pending data out of the code.
    ///
static IRVarLayout* _createOffsetVarLayout(
    IRBuilder*              irBuilder,
    LegalVarChain const&    varChain,
    IRTypeLayout*           typeLayout)
{
    IRVarLayout::Builder resultVarLayoutBuilder(irBuilder, typeLayout);

    // For every resource kind the type consumes, we will
    // compute an adjusted offset for the variable that
    // encodes the (absolute) offset of the pending data
    // in `varChain` relative to its primary data.
    //
    for( auto resInfo : typeLayout->getSizeAttrs() )
    {
        _addOffsetVarLayoutEntry(&resultVarLayoutBuilder, varChain, resInfo->getResourceKind());
    }

    return resultVarLayoutBuilder.build();
}

    /// Place offset information from `srcResInfo` onto `dstLayout`,
    /// offset by whatever is in `offsetVarLayout`
static void addOffsetResInfo(
    IRVarLayout::Builder*   dstLayout,
    IRVarOffsetAttr*        srcResInfo,
    IRVarLayout*            offsetVarLayout)
{
    auto kind = srcResInfo->getResourceKind();
    auto dstResInfo = dstLayout->findOrAddResourceInfo(kind);

    dstResInfo->offset = srcResInfo->getOffset();
    dstResInfo->space = srcResInfo->getSpace();

    if( auto offsetResInfo = offsetVarLayout->findOffsetAttr(kind) )
    {
        dstResInfo->offset += offsetResInfo->getOffset();
        dstResInfo->space += offsetResInfo->getSpace();
    }
}

    /// Create layout information for a wrapped buffer type.
    ///
    /// A wrapped buffer type encodes a buffer like `ConstantBuffer<Foo>`
    /// where `Foo` might have interface-type fields that have been
    /// specialized to a concrete type.
    ///
    /// Consider:
    ///
    ///     struct Car { IDriver driver; int mph; };
    ///     ConstantBuffer<Car> machOne;
    ///
    /// In a case where the `machOne.driver` field has been specialized
    /// to the type `SpeedRacer`, we need to generate a legalized
    /// buffer layout something like:
    ///
    ///     struct Car_0 { int mph; }
    ///     struct Wrapped { Car_0 car; SpeedRacer card_d; }
    ///     ConstantBuffer<Wrapped> machOne;
    ///
    /// The layout information for the existing `machOne` clearly
    /// can't apply because we have a new element type with new fields.
    ///
    /// This function is used to create a layout for a legalized
    /// buffer type that requires wrapping, based on the original
    /// type layout information and the variable layout information
    /// of the surrounding context (e.g., the global shader parameter
    /// that has this type).
    ///
static IRTypeLayout* _createWrappedBufferTypeLayout(
    IRBuilder*                  irBuilder,
    IRTypeLayout*               oldTypeLayout,
    WrappedBufferPseudoType*    wrappedBufferTypeInfo,
    LegalVarChain const&        outerVarChain)
{
    // We shouldn't get invoked unless there was a parameter group type,
    // so we will sanity check for that just to be sure.
    //
    auto oldParameterGroupTypeLayout = as<IRParameterGroupTypeLayout>(oldTypeLayout);
    SLANG_ASSERT(oldParameterGroupTypeLayout);
    if(!oldParameterGroupTypeLayout)
        return oldTypeLayout;

    // The original type must have been split between the direct/primary
    // data and some amount of "pending" data to deal with interface-type
    // data in the element type of the parameter group.
    //
    // The legalization step will have already flattened the data inside of
    // the group to a single `struct` type, which places the primary data first,
    // and then any pending data into additional fields.
    //
    // Our job is to compute a type layout that we can apply to that new
    // element type, and to a parameter group surrounding it, that will
    // re-create the original intention of the split layout (both primary
    // and pending data) for a type that now only has the "primary" data.
    //

    IRParameterGroupTypeLayout::Builder newTypeLayoutBuilder(irBuilder);
    newTypeLayoutBuilder.addResourceUsageFrom(oldTypeLayout);

    // Any fields in the "pending" data will have offset information
    // that is relative to the pending data for their parent, and so on.
    // We need to compute layout information that only includes primary
    // data, so any offset information that is relative to the pending data
    // needs to instead be relative to the primary data. That amounts to
    // computing the absolute offset of each pending field, and then
    // subtracting off the absolute offset of the primary data.
    //
    // We will compute the offset that needs to be added up front,
    // and store it in the form of a `VarLayout`. The offsets we need
    // can be computed from the `outerVarChain`, and we only need to
    // store offset information for resource kinds actually consumed
    // by the pending data type for the buffer as a whole (e.g., we
    // don't need to apply offsetting to uniform bytes, because
    // those don't show up in the resource usage of a constant buffer
    // itself, and so the offsets already *are* relative to the start
    // of the buffer).
    //
    auto offsetVarLayout = _createOffsetVarLayout(
        irBuilder,
        outerVarChain,
        oldTypeLayout->getPendingDataTypeLayout());
    LegalVarChainLink offsetVarChain(LegalVarChain(), offsetVarLayout);

    // We will start our construction of the pieces of the output
    // type layout by looking at the "container" type/variable.
    //
    // A parameter block or constant buffer in Slang needs to
    // distinguish between the resource usage of the thing in
    // the block/buffer, vs. the resource usage of the block/buffer
    // itself. Consider:
    //
    //      struct Material { float4 color; Texture2D tex; }
    //      ConstantBuffer<Material> gMat;
    //
    // When compiling for Vulkan, the `gMat` constant buffer needs
    // a `binding`, and the `tex` field does too, so the overall
    // resource usage of `gMat` is two bindings, but we need a
    // way to encode which of those bindings goes to `gMat.tex`
    // and which to the constant buffer for `gMat` itself.
    //
    {
        // We will start by extracting the "primary" part of the old
        // container type/var layout, and constructing new objects
        // that will represent the layout for our wrapped buffer.
        //
        auto oldPrimaryContainerVarLayout = oldParameterGroupTypeLayout->getContainerVarLayout();
        auto oldPrimaryContainerTypeLayout = oldPrimaryContainerVarLayout->getTypeLayout();

        IRTypeLayout::Builder newContainerTypeLayoutBuilder(irBuilder);
        newContainerTypeLayoutBuilder.addResourceUsageFrom(oldPrimaryContainerTypeLayout);

        if( auto oldPendingContainerVarLayout = oldPrimaryContainerVarLayout->getPendingVarLayout() )
        {
            // Whatever resources were allocated for the pending data type,
            // our new combined container type needs to account for them
            // (e.g., if we didn't have a constant buffer in the primary
            // data, but one got allocated in the pending data, we need
            // to end up with type layout information that includes a
            // constnat buffer).
            //
            auto oldPendingContainerTypeLayout = oldPendingContainerVarLayout->getTypeLayout();
            newContainerTypeLayoutBuilder.addResourceUsageFrom(oldPendingContainerTypeLayout);
        }
        auto newContainerTypeLayout = newContainerTypeLayoutBuilder.build();



        IRVarLayout::Builder newContainerVarLayoutBuilder(irBuilder, newContainerTypeLayout);

        // Whatever got allocated for the primary container should get copied
        // over to the new layout (e.g., if we allocated a constant buffer
        // for `gMat` then we need to retain that information).
        //
        for( auto resInfo : oldPrimaryContainerVarLayout->getOffsetAttrs() )
        {
            auto newResInfo = newContainerVarLayoutBuilder.findOrAddResourceInfo(resInfo->getResourceKind());
            newResInfo->offset = resInfo->getOffset();
            newResInfo->space = resInfo->getSpace();
        }

        // It is possible that a constant buffer and/or space didn't get
        // allocated for the "primary" data, but ended up being required for
        // the "pending" data (this would happen if, e.g., a constant buffer
        // didn't appear to have any uniform data in it, but then once we
        // plugged in concrete types for interface fields it did...), so
        // we need to account for that case and copy over the relevant
        // resource usage from the pending data, if there is any.
        //
        if( auto oldPendingContainerVarLayout = oldPrimaryContainerVarLayout->getPendingVarLayout() )
        {
            // We also need to add offset information based on the "pending"
            // var layout, but we need to deal with the fact that this information
            // is currently stored relative to the pending var layout for the surrounding
            // context (passed in as `outerVarChain.pendingChain`), but we need it to be
            // relative to the primary layout for the surrounding context (`outerVarChain.primaryChain`).
            // This is where the `offsetVarLayout` we computed above comes
            // in handy, because it represents the value(s) we need to
            // add to each of the per-resource-kind offsets.
            //
            for( auto resInfo : oldPendingContainerVarLayout->getOffsetAttrs() )
            {
                addOffsetResInfo(&newContainerVarLayoutBuilder, resInfo, offsetVarLayout);
            }
        }

        auto newContainerVarLayout = newContainerVarLayoutBuilder.build();
        newTypeLayoutBuilder.setContainerVarLayout(newContainerVarLayout);
    }

    // Now that we've dealt with the container variable, we can turn
    // our attention to the element type. This is the part that
    // actually got legalized and required us to create a "wrapped"
    // buffer type in the first place, so we know that it will
    // have both primary and "pending" parts.
    //
    // Let's start by extracting the fields we care about from
    // the original element type/var layout, and constructing
    // the objects we'll use to represent the type/var layout for
    // the new element type.
    //
    auto oldElementVarLayout = oldParameterGroupTypeLayout->getElementVarLayout();
    auto oldElementTypeLayout = oldElementVarLayout->getTypeLayout();

    // Now matter what, the element type of a wrapped buffer
    // will always have a structure type.
    //
    IRStructTypeLayout::Builder newElementTypeLayoutBuilder(irBuilder);

    // The `wrappedBufferTypeInfo` that was passed in tells
    // us how the fields of the original type got turned into
    // zero or more fields in the new element type, so we
    // need to follow its recursive structure to build
    // layout information for each of the new fields.
    //
    // We will track a "chain" of parent variables that
    // determines how we got to each leaf field, and is
    // used to add up the offsets that will be stored
    // in the new `VarLayout`s that get created.
    // We know we need to add in some offsets (usually
    // negative) to any fields that were pending data,
    // so we will account for that in the initial
    // chain of outer variables that we pass in.
    //
    LegalVarChain varChainForElementType;
    varChainForElementType.primaryChain = nullptr;
    varChainForElementType.pendingChain = offsetVarChain.primaryChain;

    _addFieldsToWrappedBufferElementTypeLayout(
        irBuilder,
        oldElementTypeLayout,
        &newElementTypeLayoutBuilder,
        wrappedBufferTypeInfo->elementInfo,
        varChainForElementType,
        true);

    auto newElementTypeLayout = newElementTypeLayoutBuilder.build();

    // A parameter group type layout holds a `VarLayout` for the element type,
    // which encodes the offset of the element type with respect to the
    // start of the parameter group as a whole (e.g., to handle the case
    // where a constant buffer needs a `binding`, and so does its
    // element type, so the offset to the first `binding` for the element
    // type is one, not zero.
    //
    LegalVarChainLink elementVarChain(LegalVarChain(), oldParameterGroupTypeLayout->getElementVarLayout());
    auto newElementVarLayout = createVarLayout(irBuilder, elementVarChain, newElementTypeLayout);

    newTypeLayoutBuilder.setElementVarLayout(newElementVarLayout);

    // For legacy/API reasons, we also need to compute a version of the
    // element type where the offset stored in the `elementVarLayout`
    // gets "baked in" to the fields of the element type.
    //
    // TODO: For IR-based layout information the offset layout should
    // not really be required, and it is only being used in a few places
    // that could in principle be refactored. We need to make sure to
    // do that cleanup eventually.
    //
    newTypeLayoutBuilder.setOffsetElementTypeLayout(
        applyOffsetToTypeLayout(
            irBuilder,
            newElementTypeLayout,
            newElementVarLayout));

    return newTypeLayoutBuilder.build();
}

static LegalVal declareVars(
    IRTypeLegalizationContext*  context,
    IROp                        op,
    LegalType                   type,
    IRTypeLayout*               inTypeLayout,
    LegalVarChain const&        inVarChain,
    UnownedStringSlice          nameHint,
    IRInst*                     leafVar,
    IRGlobalNameInfo*           globalNameInfo,
    bool                        isSpecial)
{
    LegalVarChain varChain = inVarChain;
    IRTypeLayout* typeLayout = inTypeLayout;
    if( isSpecial )
    {
        if( varChain.pendingChain )
        {
            varChain.primaryChain = varChain.pendingChain;
            varChain.pendingChain = nullptr;
        }
        if( typeLayout )
        {
            if( auto pendingTypeLayout = typeLayout->getPendingDataTypeLayout() )
            {
                typeLayout = pendingTypeLayout;
            }
        }
    }

    switch (type.flavor)
    {
    case LegalType::Flavor::none:
        return LegalVal();

    case LegalType::Flavor::simple:
        return declareSimpleVar(context, op, type.getSimple(), typeLayout, varChain, nameHint, leafVar, globalNameInfo);
        break;

    case LegalType::Flavor::implicitDeref:
        {
            // Just declare a variable of the pointed-to type,
            // since we are removing the indirection.

            auto val = declareVars(
                context,
                op,
                type.getImplicitDeref()->valueType,
                typeLayout,
                varChain,
                nameHint,
                leafVar,
                globalNameInfo,
                isSpecial);
            return LegalVal::implicitDeref(val);
        }
        break;

    case LegalType::Flavor::pair:
        {
            auto pairType = type.getPair();
            auto ordinaryVal = declareVars(context, op, pairType->ordinaryType, typeLayout, varChain, nameHint, leafVar, globalNameInfo, false);
            auto specialVal = declareVars(context, op, pairType->specialType, typeLayout, varChain, nameHint, leafVar, globalNameInfo, true);
            return LegalVal::pair(ordinaryVal, specialVal, pairType->pairInfo);
        }

    case LegalType::Flavor::tuple:
        {
            // Declare one variable for each element of the tuple
            auto tupleType = type.getTuple();

            RefPtr<TuplePseudoVal> tupleVal = new TuplePseudoVal();

            for (auto ee : tupleType->elements)
            {
                auto fieldLayout = getFieldLayout(typeLayout, ee.key);
                IRTypeLayout* fieldTypeLayout = fieldLayout ? fieldLayout->getTypeLayout() : nullptr;

                // If we have a type layout coming in, we really expect to have a layout for each field.
                SLANG_ASSERT(fieldLayout || !typeLayout);

                // If we are processing layout information, then
                // we need to create a new link in the chain
                // of variables that will determine offsets
                // for the eventual leaf fields...
                //
                LegalVarChainLink newVarChain(varChain, fieldLayout);

                UnownedStringSlice fieldNameHint;
                String joinedNameHintStorage;
                if( nameHint.getLength() )
                {
                    if( auto fieldNameHintDecoration = ee.key->findDecoration<IRNameHintDecoration>() )
                    {
                        joinedNameHintStorage.append(nameHint);
                        joinedNameHintStorage.append(".");
                        joinedNameHintStorage.append(fieldNameHintDecoration->getName());

                        fieldNameHint = joinedNameHintStorage.getUnownedSlice();
                    }

                }

                LegalVal fieldVal = declareVars(
                    context,
                    op,
                    ee.type,
                    fieldTypeLayout,
                    newVarChain,
                    fieldNameHint,
                    ee.key,
                    globalNameInfo,
                    true);

                TuplePseudoVal::Element element;
                element.key = ee.key;
                element.val = fieldVal;
                tupleVal->elements.add(element);
            }

            return LegalVal::tuple(tupleVal);
        }
        break;

    case LegalType::Flavor::wrappedBuffer:
        {
            auto wrappedBuffer = type.getWrappedBuffer();

            auto wrappedTypeLayout = _createWrappedBufferTypeLayout(
                context->builder,
                typeLayout,
                wrappedBuffer,
                varChain);

            auto innerVal = declareSimpleVar(
                context,
                op,
                wrappedBuffer->simpleType,
                wrappedTypeLayout,
                varChain,
                nameHint,
                leafVar,
                globalNameInfo);

            return LegalVal::wrappedBuffer(innerVal, wrappedBuffer->elementInfo);
        }

    default:
        SLANG_UNEXPECTED("unhandled");
        UNREACHABLE_RETURN(LegalVal());
        break;
    }
}

static LegalVal legalizeGlobalVar(
    IRTypeLegalizationContext*    context,
    IRGlobalVar*                irGlobalVar)
{
    // Legalize the type for the variable's value
    auto originalValueType = irGlobalVar->getDataType()->getValueType();
    auto legalValueType = legalizeType(
        context,
        originalValueType);

    switch (legalValueType.flavor)
    {
    case LegalType::Flavor::simple:
        // Easy case: the type is usable as-is, and we
        // should just do that.
        context->builder->setDataType(
            irGlobalVar,
            context->builder->getPtrType(
                legalValueType.getSimple()));
        return LegalVal::simple(irGlobalVar);

    default:
        {
            context->insertBeforeGlobal = irGlobalVar;

            IRGlobalNameInfo globalNameInfo;
            globalNameInfo.globalVar = irGlobalVar;
            globalNameInfo.counter = 0;

            UnownedStringSlice nameHint = findNameHint(irGlobalVar);
            context->builder->setInsertBefore(irGlobalVar);
            LegalVal newVal = declareVars(context, kIROp_GlobalVar, legalValueType, nullptr, LegalVarChain(), nameHint, irGlobalVar, &globalNameInfo, context->isSpecialType(originalValueType));

            // Register the new value as the replacement for the old
            registerLegalizedValue(context, irGlobalVar, newVal);

            // Remove the old global from the module.
            irGlobalVar->removeFromParent();
            context->replacedInstructions.add(irGlobalVar);

            return newVal;
        }
        break;
    }
}

static LegalVal legalizeGlobalParam(
    IRTypeLegalizationContext*  context,
    IRGlobalParam*              irGlobalParam)
{
    // Legalize the type for the variable's value
    auto legalValueType = legalizeType(
        context,
        irGlobalParam->getFullType());

    IRVarLayout* varLayout = findVarLayout(irGlobalParam);
    IRTypeLayout* typeLayout = varLayout ? varLayout->getTypeLayout() : nullptr;

    switch (legalValueType.flavor)
    {
    case LegalType::Flavor::simple:
        // Easy case: the type is usable as-is, and we
        // should just do that.
        irGlobalParam->setFullType(legalValueType.getSimple());
        return LegalVal::simple(irGlobalParam);

    default:
        {
            context->insertBeforeGlobal = irGlobalParam;

            LegalVarChainLink varChain(LegalVarChain(), varLayout);

            IRGlobalNameInfo globalNameInfo;
            globalNameInfo.globalVar = irGlobalParam;
            globalNameInfo.counter = 0;

            // TODO: need to handle initializer here!

            UnownedStringSlice nameHint = findNameHint(irGlobalParam);
            context->builder->setInsertBefore(irGlobalParam);
            LegalVal newVal = declareVars(context, kIROp_GlobalParam, legalValueType, typeLayout, varChain, nameHint, irGlobalParam, &globalNameInfo, context->isSpecialType(irGlobalParam->getDataType()));

            // Register the new value as the replacement for the old
            registerLegalizedValue(context, irGlobalParam, newVal);

            // Remove the old global from the module.
            irGlobalParam->removeFromParent();
            context->replacedInstructions.add(irGlobalParam);

            return newVal;
        }
        break;
    }
}

struct IRTypeLegalizationPass
{
    IRTypeLegalizationContext* context;

    // The goal of this pass is to ensure that legalization has been
    // applied to each instruction in a module. We also want to
    // ensure that an insturction doesn't get processed until after
    // all of its operands have been processed.
    //
    // The basic idea will be to maintain a work list of instructions
    // that are able to be processed, and also a set to track which
    // instructions have ever been added to the work list.

    List<IRInst*> workList;
    HashSet<IRInst*> addedToWorkListSet;

    // We will add a simple query to check whether an instruciton
    // has been put on the work list before (or if it should be
    // treated *as if* it has been placed on the work list).
    //
    bool hasBeenAddedToWorkList(IRInst* inst)
    {
        // Sometimes we end up with instructions that have a null
        // operand (mostly as the type field of key instructions
        // like the module itself).
        //
        // We want to treat such null pointers like we would an
        // already-processed instruction.
        //
        if(!inst) return true;

        // HACK(tfoley): In most cases it is structurally invalid for our
        // IR to have a cycle where following the operands (or type) of
        // instructions can lead back to the original instruction.
        //
        // (Note that circular dependencies are still possible, but they
        // must generally be from *children* of an instruction back
        // to the instruction itself. E.g., an instruction in the body
        // of a function can directly or indirectly depend on that function.)
        //
        // The one key counterexample is with interface types, because their
        // requirements and the expected types of those requirements are
        // encoded as operands. A typical method on inteface `I` will have a type
        // that involves a `ThisType<I>` parameter for `this`, and that creates
        // a cycle back to `I`.
        //
        // In our type legalization pass we are going to manually break that
        // cycle by treating all `IRInterfaceRequirementEntry` instructions
        // as having already been processed, since there is no particular
        // need for us to handle them as part of legalization.
        //
        if(inst->getOp() == kIROp_InterfaceRequirementEntry) return true;

        return addedToWorkListSet.Contains(inst);
    }

    // Next we define a convenience routine for adding something to the work list.
    //
    void addToWorkList(IRInst* inst)
    {
        // We want to avoid adding anything we've already added or processed.
        //
        if(addedToWorkListSet.Contains(inst))
            return;

        workList.add(inst);
        addedToWorkListSet.Add(inst);
    }

    void processModule(IRModule* module)
    {
        // In order to process an entire module, we start by adding the
        // root module insturction to our work list, and then we will
        // proceed to process instructions until the work list goes dry.
        //
        addToWorkList(module->getModuleInst());
        while( workList.getCount() != 0 )
        {
            // The order of items in the work list is signficiant;
            // later entries could depend on earlier ones. As such, we
            // cannot just do something like the `fastRemoveAt(...)`
            // operation that could potentially lead to instructions
            // being processed in a different order than they were added.
            //
            // Instead, we will make a copy of the current work list
            // at each step, and swap in an empty work list to be added
            // to with any new instructions.
            //
            List<IRInst*> workListCopy;
            Swap(workListCopy, workList);

            // Now we simply process each instruction on the copy of
            // the work list, knowing that `processInst` may add additional
            // instructions to the original work list.
            //
            for( auto inst : workListCopy )
            {
                processInst(inst);
            }
        }

        // After we are done, there might be various instructions that
        // were marked for deletion, but have not yet been cleaned up.
        //
        // We will clean up all those unnecessary instructions now.
        //
        for (auto& lv : context->replacedInstructions)
        {
            lv->removeAndDeallocate();
        }
    }

    void processInst(IRInst* inst)
    {
        // It is possible that an insturction we
        // encounterer during the legalization process
        // will be one that was already removed or
        // otherwise made redundant.
        //
        // We want to skip such instructions since there
        // would not be a valid location at which to
        // store their replacements.
        //
        if(!inst->getParent() && inst->getOp() != kIROp_Module)
            return;

        // The main logic for legalizing an instruction is defined
        // earlier in this file.
        //
        // Our primary task here is to legalize the instruction, and then
        // register the result of legalization as the proper value
        // for that instruction.
        //
        LegalVal legalVal = legalizeInst(context, inst);
        registerLegalizedValue(context, inst, legalVal);

        // Once the instruction has been processed, we need to consider
        // whether any other instructions might now be ready to process.
        //
        // An instruction `i` might have been blocked by `inst` if:
        //
        // * `inst` was an operand (including the type operand) of `i`, or
        // * `inst` was the parent of `i`.
        //
        // To turn those relationships around, we want to check instructions
        // `i` where:
        //
        // * `i` is a user of `inst`, or
        // * `i` is a child of `inst`.
        // 
        for( auto use = inst->firstUse; use; use = use->nextUse )
        {
            auto user = use->getUser();
            maybeAddToWorkList(user);
        }
        for( auto child : inst->getDecorationsAndChildren() )
        {
            maybeAddToWorkList(child);
        }
    }

    void maybeAddToWorkList(IRInst* inst)
    {
        // Here we have an `inst` that might be ready to go on
        // the work list, but we need to check that it would
        // be valid to put it on there.
        //
        // First, we don't want to add something if it has
        // already been added.
        //
        if(hasBeenAddedToWorkList(inst))
            return;

        // Next, we don't want to add something if its parent
        // hasn't been added already.
        //
        if(!hasBeenAddedToWorkList(inst->getParent()))
            return;

        // Finally, we don't want to add something if its
        // type and/or operands haven't all been added.
        //
        if(!hasBeenAddedToWorkList(inst->getFullType()))
            return;
        Index operandCount = (Index) inst->getOperandCount();
        for( Index i = 0; i < operandCount; ++i )
        {
            auto operand = inst->getOperand(i);
            if(!hasBeenAddedToWorkList(operand))
                return;
        }

        // If all those checks pass, then we are ready to
        // process `inst`, and we will add it to our work list.
        //
        addToWorkList(inst);
    }
};

static void legalizeTypes(
    IRTypeLegalizationContext*    context)
{
    IRTypeLegalizationPass pass;
    pass.context = context;

    pass.processModule(context->module);
}

// We use the same basic type legalization machinery for both simplifying
// away resource-type fields nested in `struct`s and for shuffling around
// exisential-box fields to get the layout right.
//
// The differences between the two passes come down to some very small
// distinctions about what types each pass considers "special" (e.g.,
// resources in one case and existential boxes in the other), along
// with what they want to do when a uniform/constant buffer needs to
// be made where the element type is non-simple (that is, includes
// some fields of "special" type).
//
// The resource case is then the simpler one:
//
struct IRResourceTypeLegalizationContext : IRTypeLegalizationContext
{
    IRResourceTypeLegalizationContext(IRModule* module)
        : IRTypeLegalizationContext(module)
    {}

    bool isSpecialType(IRType* type) override
    {
        // For resource type legalization, the "special" types
        // we are working with are resource types.
        //
        return isResourceType(type);
    }

    LegalType createLegalUniformBufferType(
        IROp        op,
        LegalType   legalElementType) override
    {
        // The appropriate strategy for legalizing uniform buffers
        // with resources inside already exists, so we can delegate to it.
        //
        return createLegalUniformBufferTypeForResources(
            this,
            op,
            legalElementType);
    }
};

// The case for legalizing existential box types is then similar.
//
struct IRExistentialTypeLegalizationContext : IRTypeLegalizationContext
{
    IRExistentialTypeLegalizationContext(IRModule* module)
        : IRTypeLegalizationContext(module)
    {}

    bool isSpecialType(IRType* inType) override
    {
        // The "special" types for our purposes are existential
        // boxes, or arrays thereof.
        //
        auto type = unwrapArray(inType);
        return as<IRPseudoPtrType>(type) != nullptr;
    }

    LegalType createLegalUniformBufferType(
        IROp        op,
        LegalType   legalElementType) override
    {
        // We'll delegate the logic for creating uniform buffers
        // over a mix of ordinary and existential-box types to
        // a subroutine so it can live near the resource case.
        //
        // TODO: We should eventually try to refactor this code
        // so that related functionality is grouped together.
        //
        return createLegalUniformBufferTypeForExistentials(
            this,
            op,
            legalElementType);
    }
};

// The main entry points that are used when transforming IR code
// to get it ready for lower-level codegen are then simple
// wrappers around `legalizeTypes()` that pick an appropriately
// specialized context type to use to get the job done.

void legalizeResourceTypes(
    IRModule*       module,
    DiagnosticSink* sink)
{
    SLANG_UNUSED(sink);

    IRResourceTypeLegalizationContext context(module);
    legalizeTypes(&context);
}

void legalizeExistentialTypeLayout(
    IRModule*       module,
    DiagnosticSink* sink)
{
    SLANG_UNUSED(module);
    SLANG_UNUSED(sink);

    IRExistentialTypeLegalizationContext context(module);
    legalizeTypes(&context);
}


}
back to top