diff options
| author | ArielG-NV <159081215+ArielG-NV@users.noreply.github.com> | 2025-08-07 00:22:22 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-08-07 07:22:22 +0000 |
| commit | 063cbeaaea2fb00a10c6058ea4a9632092772ea5 (patch) | |
| tree | b4412347d6c264c3b1a84ec971921a5e2fe76134 /source/slang/slang-ir-redundancy-removal.cpp | |
| parent | 9e2685853033f4286feaf22d04a755a7395d95ce (diff) | |
Initial copy elision pass (#8042)
Fixes #7574
Changes:
* Add an initial (fairly simple) optimization pass which is able to
eliminate redundant copies.
* Our current existing optimizer passes remove redundant load/store very
robustly, this pass will focus on other cases of copy elimination
* Primary approach is to make all functions which are `in T` and `T` is
trivial to copy into a `__constref T`. We then (depending on scenario)
manually insert a variable+load if a pass-by-reference is not possible;
otherwise we pass by `constref`.
* Added optimizations to eliminate redundant code which causes
`constref` to fail to compile
---------
Co-authored-by: Harsh Aggarwal <haaggarwal@nvidia.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: slangbot <ellieh+slangbot@nvidia.com>
Co-authored-by: slangbot <186143334+slangbot@users.noreply.github.com>
Diffstat (limited to 'source/slang/slang-ir-redundancy-removal.cpp')
| -rw-r--r-- | source/slang/slang-ir-redundancy-removal.cpp | 137 |
1 files changed, 113 insertions, 24 deletions
diff --git a/source/slang/slang-ir-redundancy-removal.cpp b/source/slang/slang-ir-redundancy-removal.cpp index a6dac723e..1feab47dd 100644 --- a/source/slang/slang-ir-redundancy-removal.cpp +++ b/source/slang/slang-ir-redundancy-removal.cpp @@ -361,43 +361,132 @@ bool tryRemoveRedundantStore(IRGlobalValueWithCode* func, IRStore* store) return false; } +// Checks if we can change or have a modified rootVar +// at some point. +bool isExternallyModifiableAddr(IRInst* rootVar) +{ + if (!rootVar) + return false; + + auto ptr = as<IRConstRefType>(rootVar->getDataType()); + if (!ptr) + return true; + + // Only a UserPointer can potentially be modified and changed to point to a different address + // if constRef. This may happen from a different thread even if constref to the current thread. + auto addrSpace = ptr->getAddressSpace(); + if (addrSpace == AddressSpace::UserPointer) + return true; + + return false; +} + +bool tryRemoveRedundantLoad(IRGlobalValueWithCode* func, IRLoad* load) +{ + bool changed = false; + + // If the load is preceeded by a store without any side-effect insts + // in-between, remove the load. + for (auto prev = load->getPrevInst(); prev; prev = prev->getPrevInst()) + { + if (auto store = as<IRStore>(prev)) + { + if (store->getPtr() == load->getPtr()) + { + auto value = store->getVal(); + load->replaceUsesWith(value); + load->removeAndDeallocate(); + changed = true; + break; + } + } + + if (canInstHaveSideEffectAtAddress(func, prev, load->getPtr())) + { + break; + } + } + + return changed; +} + bool eliminateRedundantLoadStore(IRGlobalValueWithCode* func) { bool changed = false; for (auto block : func->getBlocks()) { - for (auto inst = block->getFirstInst(); inst;) + IRInst* nextInst = nullptr; + for (auto inst = block->getFirstInst(); inst; inst = nextInst) { - auto nextInst = inst->getNextInst(); + nextInst = inst->getNextInst(); if (auto load = as<IRLoad>(inst)) { - for (auto prev = inst->getPrevInst(); prev; prev = prev->getPrevInst()) - { - if (auto store = as<IRStore>(prev)) - { - if (store->getPtr() == load->getPtr()) - { - // If the load is preceeded by a store without any side-effect insts - // in-between, remove the load. - auto value = store->getVal(); - load->replaceUsesWith(value); - load->removeAndDeallocate(); - changed = true; - break; - } - } - - if (canInstHaveSideEffectAtAddress(func, prev, load->getPtr())) - { - break; - } - } + changed |= tryRemoveRedundantLoad(func, load); } else if (auto store = as<IRStore>(inst)) { changed |= tryRemoveRedundantStore(func, store); } - inst = nextInst; + else if (auto getElementPtr = as<IRGetElementPtr>(inst)) + { + auto rootAddr = getRootAddr(getElementPtr); + if (isExternallyModifiableAddr(rootAddr)) + continue; + + // GetElement(Load(GetElementPtr(x)))) ==> Load(GetElementPtr(GetElementPtr(x))) + // The benefit is that any GetAddr(Load(...)) can then transitively be optimized + // out. + // This can only be done if we have no side-effects. `constref` never has + // single-invocation side-effects. + traverseUsers<IRLoad>( + getElementPtr, + [&](IRLoad* load) + { + traverseUsers<IRGetElement>( + load, + [&](IRGetElement* getElement) + { + IRBuilder builder(getElement); + builder.setInsertBefore(getElement); + auto newGetElementPtr = builder.emitElementAddress( + getElementPtr, + getElement->getIndex()); + auto newLoad = builder.emitLoad(newGetElementPtr); + getElement->replaceUsesWith(newLoad); + changed = true; + }); + }); + } + else if (auto fieldAddress = as<IRFieldAddress>(inst)) + { + auto rootAddr = getRootAddr(fieldAddress); + if (isExternallyModifiableAddr(rootAddr)) + continue; + + // ExtractField(Load(GetFieldAddr(x)))) ==> Load(GetFieldAddr(GetFieldAddr(x))) + // The benefit is that any GetAddr(Load(...)) can then transitively be optimized + // out. + // This can only be done if we have no side-effects. `constref` never has + // single-invocation side-effects. + traverseUsers<IRLoad>( + fieldAddress, + [&](IRLoad* load) + { + traverseUsers<IRFieldExtract>( + load, + [&](IRFieldExtract* fieldExtract) + { + IRBuilder builder(fieldExtract); + builder.setInsertBefore(fieldExtract); + auto newGetFieldAddress = builder.emitFieldAddress( + fieldAddress, + fieldExtract->getField()); + auto newLoad = builder.emitLoad(newGetFieldAddress); + fieldExtract->replaceUsesWith(newLoad); + changed = true; + }); + }); + } } } return changed; |
