Repository: shader-slang/slang
File: source/slang/slang-ir-redundancy-removal.cpp (C++)

Code before:
bool removeRedundancy(IRModule* module, bool hoistLoopInvariantInsts)
{
    bool changed = false;
    for (auto inst : module->getGlobalInsts())
    {
        if (auto genericInst = as<IRGeneric>(inst))
        {
            removeRedundancyInFunc(genericInst, hoistLoopInvariantInsts);
            inst = findGenericReturnVal(genericInst);
        }
        if (auto func = as<IRFunc>(inst))
        {
            changed |= removeRedundancyInFunc(func, hoistLoopInvariantInsts);
        }
    }
    return changed;
}

bool isAddressMutable(IRInst* inst)
{
    auto rootType = getRootAddr(inst)->getDataType();
    switch (rootType->getOp())
    {
    case kIROp_ParameterBlockType:
    case kIROp_ConstantBufferType:
    case kIROp_StructuredBufferLoad:
    case kIROp_GetStructuredBufferPtr:
    case kIROp_ConstRefType:
        return false; // immutable
    }

    if (auto ptr = as<IRPtrTypeBase>(rootType))
        return ptr->getAccessQualifier() == AccessQualifier::ReadWrite;

    return true; // mutable
}

/// Eliminate redundant temporary variable copies in load-store patterns.
/// This optimization looks for patterns where a value is loaded from memory
/// and immediately stored to a temporary variable, which is then only used
/// in read-only contexts. In such cases, the temporary variable and the
/// load-store indirection can be eliminated by using the original memory
/// location directly.
/// Returns true if any changes were made.
static bool eliminateRedundantTemporaryCopyInFunc(IRFunc* func)
{
    // Consider the following IR pattern:
    // ```
    // let %temp = var
    // let %value = load(%sourcePtr)
    // store(%temp, %value)

Review comment (suggestion):
This is not correct. The only case that `kIROp_StructuredBufferLoad` can be an address root is in `StructuredBuffer<int*> buf`, and `let ptr = buf[0]`. In this case, the address `ptr` itself is actually mutable.

Code after:
bool removeRedundancy(IRModule* module, bool hoistLoopInvariantInsts)
{
    bool changed = false;
    for (auto inst : module->getGlobalInsts())
    {
        if (auto genericInst = as<IRGeneric>(inst))
        {
            removeRedundancyInFunc(genericInst, hoistLoopInvariantInsts);
            inst = findGenericReturnVal(genericInst);
        }
        if (auto func = as<IRFunc>(inst))
        {
            changed |= removeRedundancyInFunc(func, hoistLoopInvariantInsts);
        }
    }
    return changed;
}

bool isAddressMutable(IRInst* inst)
{
    auto rootType = getRootAddr(inst)->getDataType();
    switch (rootType->getOp())
    {
    case kIROp_ParameterBlockType:
    case kIROp_ConstantBufferType:
    case kIROp_ConstRefType:
        return false; // immutable

    // We should consider StructuredBuffer as mutable by default, since the resources may alias.
    // There could be anotherRWStructuredBuffer pointing to the same memory location as the
    // structured buffer.
    case kIROp_StructuredBufferLoad:
    case kIROp_GetStructuredBufferPtr:
        return true; // mutable
    }

    // Similarly, IRPtrTypeBase should also be considered writable always,
    // because there can be aliasing.

    return true; // mutable
}

/// Eliminate redundant temporary variable copies in load-store patterns.
/// This optimization looks for patterns where a value is loaded from memory
/// and immediately stored to a temporary variable, which is then only used
/// in read-only contexts. In such cases, the temporary variable and the
/// load-store indirection can be eliminated by using the original memory
/// location directly.
/// Returns true if any changes were made.
static bool eliminateRedundantTemporaryCopyInFunc(IRFunc* func)
{