Repository: shader-slang/slang
File: source/slang/slang-ir-hlsl-legalize.cpp (C++)

Code before:
                        auto paramType = funcType->getParamType(i);
                        // Only copy back if the parameter is a pointer-like type (out/inout/ref)
                        if (!isPtrLikeOrHandleType(paramType))
                            continue;

                        builder.setInsertAfter(call);
                        builder.emitStore(
                            forceStructArg,
                            builder.emitFieldAddress(
                                builder.getPtrType(_dataField->getFieldType()),
                                structVar,
                                _dataField->getKey()));
                    }
                }
                break;
            }
        }
    }
}

void legalizeNonStructParameterToStructForHLSL(IRModule* module)
{
    for (auto globalInst : module->getGlobalInsts())
    {
        // Search all global instructions for ForceVarIntoStructTemporarily wrappers.
        // These can appear in functions, generics, or other structures.
        searchChildrenForForceVarIntoStructTemporarily(module, globalInst);
    }
}

void legalizeEmptyRayPayloadsForHLSL(IRModule* module)
{
    // DXIL/HLSL with NVAPI requires non-empty ray payload structs because
    // the NvInvokeHitObject macro expects a Payload argument.
    IRBuilder builder(module);

    for (auto globalInst : module->getGlobalInsts())
    {
        auto structType = as<IRStructType>(globalInst);
        if (!structType)
            continue;

        // Check if this struct has ray payload decoration
        auto rayPayloadDec = structType->findDecoration<IRRayPayloadDecoration>();
        auto vulkanRayPayloadDec = structType->findDecoration<IRVulkanRayPayloadDecoration>();
        bool isRayPayload = rayPayloadDec != nullptr || vulkanRayPayloadDec != nullptr;

        if (!isRayPayload)
            continue;

        // Check if the struct is empty (has no fields)

Review comment (suggestion):
I don't think this removal is a right way.
Because the implementation of `searchChildrenForForceVarIntoStructTemporarily()` handles only Block and Call.
Struct cannot have Call.

Also at this stage of legalization shouldn't be dealing with the generics anymore, because it is very close to the emit stage already and linking and generics should have been all resolved.

Code after:
{
    // DXIL/HLSL with NVAPI requires non-empty ray payload structs because
    // the NvInvokeHitObject macro expects a Payload argument.
    IRBuilder builder(module);

    // First, collect all empty ray payload structs to process.
    // We must collect first because the processing phase inserts new global
    // instructions (struct keys, string values) which would invalidate the iterator.
    List<IRStructType*> emptyRayPayloadStructs;

    for (auto globalInst : module->getGlobalInsts())
    {
        auto structType = as<IRStructType>(globalInst);
        if (!structType)
            continue;

        // Check if this struct has ray payload decoration
        auto rayPayloadDec = structType->findDecoration<IRRayPayloadDecoration>();
        auto vulkanRayPayloadDec = structType->findDecoration<IRVulkanRayPayloadDecoration>();
        bool isRayPayload = rayPayloadDec != nullptr || vulkanRayPayloadDec != nullptr;

        if (!isRayPayload)
            continue;

        // Check if the struct is empty (has no fields)
        if (structType->getFields().begin() != structType->getFields().end())
            continue;

        emptyRayPayloadStructs.add(structType);
    }

    // Now process the collected structs
    for (auto structType : emptyRayPayloadStructs)
    {
        // Add a dummy field to the empty ray payload struct
        // Insert the key BEFORE the struct type so it's defined before being referenced
        builder.setInsertBefore(structType);
        auto dummyKey = builder.createStructKey();
        builder.addNameHintDecoration(dummyKey, UnownedStringSlice("_slang_dummy"));

        // Add stage access decorations that ray payload fields require
        IRInst* stageName = builder.getStringValue(UnownedStringSlice("caller"));
        builder.addDecoration(dummyKey, kIROp_StageReadAccessDecoration, &stageName, 1);
        builder.addDecoration(dummyKey, kIROp_StageWriteAccessDecoration, &stageName, 1);

        builder.createStructField(structType, dummyKey, builder.getIntType());

        // Now find and update all makeStruct instructions that create this struct type.
        List<IRInst*> makeStructsToUpdate;
        for (auto use = structType->firstUse; use; use = use->nextUse)
        {