https://github.com/shader-slang/slang
Raw File
Tip revision: 768e62f6c7541439e2edc18dad5fb3846d2e05f9 authored by Yong He on 10 October 2022, 22:59:45 UTC
Support multi-level break + single-return conversion + general inline. (#2436)
Tip revision: 768e62f
slang-ir-lower-existential.cpp
// slang-ir-lower-generic-existential.cpp

#include "slang-ir-lower-existential.h"
#include "slang-ir-generics-lowering-context.h"
#include "slang-ir.h"
#include "slang-ir-insts.h"
#include "slang-ir-util.h"

namespace Slang
{
    bool isCPUTarget(TargetRequest* targetReq);
    bool isCUDATarget(TargetRequest* targetReq);

    struct ExistentialLoweringContext
    {
        SharedGenericsLoweringContext* sharedContext;

        void processMakeExistential(IRMakeExistentialWithRTTI* inst)
        {
            IRBuilder builderStorage(sharedContext->sharedBuilderStorage);
            auto builder = &builderStorage;
            builder->setInsertBefore(inst);
            auto value = inst->getWrappedValue();
            auto valueType = sharedContext->lowerType(builder, value->getDataType());
            if (valueType->getOp() == kIROp_ComPtrType)
                return;
            auto witnessTableType = cast<IRWitnessTableTypeBase>(inst->getWitnessTable()->getDataType());
            auto interfaceType = witnessTableType->getConformanceType();
            if (interfaceType->findDecoration<IRComInterfaceDecoration>())
                return;
            auto witnessTableIdType = builder->getWitnessTableIDType((IRType*)interfaceType);
            auto anyValueSize = sharedContext->getInterfaceAnyValueSize(interfaceType, inst->sourceLoc);
            auto anyValueType = builder->getAnyValueType(anyValueSize);
            auto rttiType = builder->getRTTIHandleType();
            auto tupleType = builder->getTupleType(rttiType, witnessTableIdType, anyValueType);

            IRInst* rttiObject = inst->getRTTI();
            if (auto type = as<IRType>(rttiObject))
            {
                rttiObject = sharedContext->maybeEmitRTTIObject(type);
                rttiObject = builder->emitGetAddress(rttiType, rttiObject);
            }
            IRInst* packedValue = value;
            if (valueType->getOp() != kIROp_AnyValueType)
                packedValue = builder->emitPackAnyValue(anyValueType, value);
            IRInst* tupleArgs[] = {rttiObject, inst->getWitnessTable(), packedValue};
            auto tuple = builder->emitMakeTuple(tupleType, 3, tupleArgs);
            inst->replaceUsesWith(tuple);
            inst->removeAndDeallocate();
        }

        // Translates `createExistentialObject` insts, which takes a user defined
        // type id and user defined value and turns into an existential value,
        // into a `makeTuple` inst that makes the tuple representing the lowered
        // existential value.
        void processCreateExistentialObject(IRCreateExistentialObject* inst)
        {
            IRBuilder builderStorage(sharedContext->sharedBuilderStorage);
            auto builder = &builderStorage;
            builder->setInsertBefore(inst);

            // The result type of this `createExistentialObject` inst should already
            // be lowered into a `TupleType(rttiType, WitnessTableIDType, AnyValueType)`
            // in the previous `lowerGenericType` pass.
            auto tupleType = inst->getDataType();
            auto witnessTableIdType = cast<IRWitnessTableIDType>(tupleType->getOperand(1));
            auto anyValueType = cast<IRAnyValueType>(tupleType->getOperand(2));

            // Create a null value for `rttiObject` for now since it will not be used.
            auto uint2Type = builder->getVectorType(
                builder->getUIntType(), builder->getIntValue(builder->getIntType(), 2));
            IRInst* zero = builder->getIntValue(builder->getUIntType(), 0);
            IRInst* zeroVectorArgs[] = { zero, zero };
            IRInst* rttiObject = builder->emitMakeVector(uint2Type, 2, zeroVectorArgs);

            // Pack the user provided value into `AnyValue`.
            IRInst* packedValue = inst->getValue();
            if (packedValue->getDataType()->getOp() != kIROp_AnyValueType)
                packedValue = builder->emitPackAnyValue(anyValueType, packedValue);

            // Use the user provided `typeID` value as the witness table ID field in the
            // newly constructed tuple.
            // All `WitnessTableID` types are lowered into `uint2`s, so we need to create
            // a `uint2` value from `typeID` to stay consistent with the convention.
            IRInst* vectorArgs[2] = {
                inst->getTypeID(), builder->getIntValue(builder->getUIntType(), 0)};
            
            IRInst* typeIdValue = builder->emitMakeVector(uint2Type, 2, vectorArgs);
            typeIdValue = builder->emitBitCast(witnessTableIdType, typeIdValue);
            IRInst* tupleArgs[] = {rttiObject, typeIdValue, packedValue};
            auto tuple = builder->emitMakeTuple(tupleType, 3, tupleArgs);
            inst->replaceUsesWith(tuple);
            inst->removeAndDeallocate();
        }

        IRInst* extractTupleElement(IRBuilder* builder, IRInst* value, UInt index)
        {
            auto tupleType = cast<IRTupleType>(sharedContext->lowerType(builder, value->getDataType()));
            auto getElement = builder->emitGetTupleElement(
                (IRType*)tupleType->getOperand(index),
                value,
                index);
            return getElement;
        }

        void processExtractExistentialElement(IRInst* extractInst, UInt elementId)
        {
            IRBuilder builderStorage(sharedContext->sharedBuilderStorage);
            auto builder = &builderStorage;
            builder->setInsertBefore(extractInst);

            IRInst* element = nullptr;
            if (isComInterfaceType(extractInst->getOperand(0)->getDataType()))
            {
                // If this is an COM interface, the elements (witness table/rtti) are just the interface value itself.
                element = extractInst->getOperand(0);
            }
            else
            {
                element = extractTupleElement(builder, extractInst->getOperand(0), elementId);
            }
            extractInst->replaceUsesWith(element);
            extractInst->removeAndDeallocate();
        }

        void processExtractExistentialValue(IRExtractExistentialValue* inst)
        {
            processExtractExistentialElement(inst, 2);
        }

        void processExtractExistentialWitnessTable(IRExtractExistentialWitnessTable* inst)
        {
            processExtractExistentialElement(inst, 1);
        }

        void processExtractExistentialType(IRExtractExistentialType* inst)
        {
            processExtractExistentialElement(inst, 0);
        }

        void processGetValueFromBoundInterface(IRGetValueFromBoundInterface* inst)
        {
            IRBuilder builderStorage(sharedContext->sharedBuilderStorage);
            auto builder = &builderStorage;
            builder->setInsertBefore(inst);
            if (inst->getDataType()->getOp() == kIROp_ClassType)
            {
                return;
            }
            // A value of interface will lower as a tuple, and
            // the third element of that tuple represents the
            // concrete value that was put into the existential.
            //
            auto element = extractTupleElement(builder, inst->getOperand(0), 2);
            auto elementType = element->getDataType();

            // There are two cases we expect to see for that
            // tuple element.
            //
            IRInst* replacement = nullptr;
            if(as<IRPseudoPtrType>(elementType))
            {
                // The first case is when legacy static specialization
                // is applied, and the element is a "pseudo-pointer."
                //
                // Semantically, we should emit a (pseudo-)load from the pseudo-pointer
                // to go from `PseudoPtr<T>` to `T`.
                //
                // TODO: Actually introduce and emit a "psedudo-load" instruction
                // here. For right now we are just using the value directly and
                // downstream passes seem okay with it, but it isn't really
                // type-correct to be doing this.
                //
                replacement = element;
            }
            else
            {
                // The second case is when the dynamic-dispatch layout is
                // being used, and the element is an "any-value."
                //
                // In this case we need to emit an unpacking operation
                // to get from `AnyValue` to `T`.
                //
                SLANG_ASSERT(as<IRAnyValueType>(elementType));
                replacement = builder->emitUnpackAnyValue(inst->getFullType(), element);
            }

            inst->replaceUsesWith(replacement);
            inst->removeAndDeallocate();
        }

        void processInst(IRInst* inst)
        {
            if (auto makeExistential = as<IRMakeExistentialWithRTTI>(inst))
            {
                processMakeExistential(makeExistential);
            }
            else if (auto createExistentialObject = as<IRCreateExistentialObject>(inst))
            {
                processCreateExistentialObject(createExistentialObject);
            }
            else if (auto getValueFromBoundInterface = as<IRGetValueFromBoundInterface>(inst))
            {
                processGetValueFromBoundInterface(getValueFromBoundInterface);
            }
            else if (auto extractExistentialVal = as<IRExtractExistentialValue>(inst))
            {
                processExtractExistentialValue(extractExistentialVal);
            }
            else if (auto extractExistentialType = as<IRExtractExistentialType>(inst))
            {
                processExtractExistentialType(extractExistentialType);
            }
            else if (auto extractExistentialWitnessTable = as<IRExtractExistentialWitnessTable>(inst))
            {
                processExtractExistentialWitnessTable(extractExistentialWitnessTable);
            }
        }

        void processModule()
        {
            // We start by initializing our shared IR building state,
            // since we will re-use that state for any code we
            // generate along the way.
            //
            SharedIRBuilder* sharedBuilder = &sharedContext->sharedBuilderStorage;
            sharedBuilder->init(sharedContext->module);

            sharedContext->addToWorkList(sharedContext->module->getModuleInst());

            while (sharedContext->workList.getCount() != 0)
            {
                IRInst* inst = sharedContext->workList.getLast();

                sharedContext->workList.removeLast();
                sharedContext->workListSet.Remove(inst);

                processInst(inst);

                for (auto child = inst->getLastChild(); child; child = child->getPrevInst())
                {
                    sharedContext->addToWorkList(child);
                }
            }
        }
    };


    void lowerExistentials(SharedGenericsLoweringContext* sharedContext)
    {
        ExistentialLoweringContext context;
        context.sharedContext = sharedContext;
        context.processModule();
    }
}
back to top