From 063cbeaaea2fb00a10c6058ea4a9632092772ea5 Mon Sep 17 00:00:00 2001 From: ArielG-NV <159081215+ArielG-NV@users.noreply.github.com> Date: Thu, 7 Aug 2025 00:22:22 -0700 Subject: 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 Co-authored-by: Claude Co-authored-by: slangbot Co-authored-by: slangbot <186143334+slangbot@users.noreply.github.com> --- source/slang/slang-ir-redundancy-removal.cpp | 137 ++++++++++++++++++++++----- 1 file changed, 113 insertions(+), 24 deletions(-) (limited to 'source/slang/slang-ir-redundancy-removal.cpp') 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(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(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(inst)) { - for (auto prev = inst->getPrevInst(); prev; prev = prev->getPrevInst()) - { - if (auto store = as(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(inst)) { changed |= tryRemoveRedundantStore(func, store); } - inst = nextInst; + else if (auto getElementPtr = as(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( + getElementPtr, + [&](IRLoad* load) + { + traverseUsers( + 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(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( + fieldAddress, + [&](IRLoad* load) + { + traverseUsers( + 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; -- cgit v1.2.3