diff options
Diffstat (limited to 'source/slang/slang-ir-util.cpp')
| -rw-r--r-- | source/slang/slang-ir-util.cpp | 246 |
1 files changed, 215 insertions, 31 deletions
diff --git a/source/slang/slang-ir-util.cpp b/source/slang/slang-ir-util.cpp index 8584ea95e..551a72fc7 100644 --- a/source/slang/slang-ir-util.cpp +++ b/source/slang/slang-ir-util.cpp @@ -17,6 +17,14 @@ bool isPointerOfType(IRInst* type, IROp opCode) return false; } +bool isUserPointerType(IRInst* type) +{ + auto ptrType = as<IRPtrType>(type); + if (!ptrType) + return false; + return ptrType->getAddressSpace() == AddressSpace::UserPointer; +} + IRType* getVectorElementType(IRType* type) { if (auto vectorType = as<IRVectorType>(type)) @@ -792,35 +800,212 @@ IRInst* getRootAddr(IRInst* addr, List<IRInst*>& outAccessChain, List<IRInst*>* return addr; } -// A simple and conservative address aliasing check. -bool canAddressesPotentiallyAlias(IRGlobalValueWithCode* func, IRInst* addr1, IRInst* addr2) +IRInst* getRootBufferOrAddr(IRInst* addr) { - if (addr1 == addr2) - return true; + auto rootAddr = getRootAddr(addr); + if (as<IRRWStructuredBufferGetElementPtr>(rootAddr)) + { + auto bufferHandle = rootAddr->getOperand(0); + // Check if the bufferHandle itself is a load from a global parameter. + if (auto load = as<IRLoad>(bufferHandle)) + { + auto newRoot = getRootAddr(load->getPtr()); + if (newRoot->getOp() == kIROp_GlobalParam) + return newRoot; + } + } + return rootAddr; +} + +// The aliasing class of an address. This is used to determine +// if two addresses may alias. +enum class AddressAliasingClass +{ + Unknown, + UserPointer, // A user pointer into global memory + Var, // A thread-local or groupshared var. + ConstantBuffer, // A constant buffer or parameter block. + BoundBuffer, // A bound buffer. + BoundTexture, // A bound texture resource. + DescriptorHandle, // A bindless buffer or resource. +}; + +AddressAliasingClass getAliasingClass(IRInst* addr) +{ + if (auto globalParam = as<IRGlobalParam>(addr)) + { + auto type = unwrapArray(globalParam->getDataType()); + if (!type) + return AddressAliasingClass::Unknown; + switch (type->getOp()) + { + case kIROp_TextureType: + return AddressAliasingClass::BoundTexture; + case kIROp_HLSLStructuredBufferType: + case kIROp_HLSLRWStructuredBufferType: + case kIROp_HLSLAppendStructuredBufferType: + case kIROp_HLSLConsumeStructuredBufferType: + case kIROp_HLSLRasterizerOrderedStructuredBufferType: + case kIROp_HLSLByteAddressBufferType: + case kIROp_HLSLRWByteAddressBufferType: + case kIROp_HLSLRasterizerOrderedByteAddressBufferType: + case kIROp_GLSLShaderStorageBufferType: + return AddressAliasingClass::BoundBuffer; + case kIROp_ConstantBufferType: + case kIROp_ParameterBlockType: + return AddressAliasingClass::ConstantBuffer; + case kIROp_PtrType: + if (isUserPointerType(type)) + return AddressAliasingClass::UserPointer; + return AddressAliasingClass::Unknown; + case kIROp_DynamicResourceType: + return AddressAliasingClass::DescriptorHandle; + default: + return AddressAliasingClass::Unknown; + } + } + else if (as<IRVar>(addr)) + return AddressAliasingClass::Var; + else if (as<IRGlobalVar>(addr)) + return AddressAliasingClass::Var; + else if (as<IRRWStructuredBufferGetElementPtr>(addr)) + return AddressAliasingClass::DescriptorHandle; + else if (as<IRCastDescriptorHandleToResource>(addr)) + return AddressAliasingClass::DescriptorHandle; - // Two variables can never alias. - addr1 = getRootAddr(addr1); - addr2 = getRootAddr(addr2); + auto type = addr->getDataType(); + if (isUserPointerType(type)) + return AddressAliasingClass::UserPointer; + return AddressAliasingClass::Unknown; +} - // Global addresses can alias with anything. - if (!isChildInstOf(addr1, func)) +bool canAddrClassesAlias(AddressAliasingClass c1, AddressAliasingClass c2) +{ + if (c1 == AddressAliasingClass::Unknown || c2 == AddressAliasingClass::Unknown) return true; - if (!isChildInstOf(addr2, func)) + switch (c1) + { + case AddressAliasingClass::Unknown: return true; + case AddressAliasingClass::UserPointer: + case AddressAliasingClass::Var: + // A users pointer or var can only alias with another + // object that is either a user pointer or var. + // + // Generally, a var should never alias with anything else that isn't a var, + // if we never allow the user to take address of a local var. + // We don't allow taking addresses of a local var on most GPU targets, but + // we currently do expose an internal intrinsic to do so when targeting CPU. + // We should consider disallowing this across the board, or enable more aggresive + // criteria when targeting GPU backends. + // For now we stay conservative and just report true even when addr1 is var and + // addr2 is not rooted from a var. + // + return c2 == AddressAliasingClass::UserPointer || c2 == AddressAliasingClass::Var; + case AddressAliasingClass::BoundBuffer: + case AddressAliasingClass::BoundTexture: + // A bound resource can only alias with another + // object that is a bound resource or descriptor handle + return c2 == c1 || c2 == AddressAliasingClass::DescriptorHandle; + + case AddressAliasingClass::DescriptorHandle: + // Can alias with any other resource. + switch (c2) + { + case AddressAliasingClass::BoundBuffer: + case AddressAliasingClass::BoundTexture: + case AddressAliasingClass::DescriptorHandle: + return true; + default: + return false; + } + case AddressAliasingClass::ConstantBuffer: + // Constant buffer cannot alias with anything. + return false; + } + // For any other unknown case, assume they may alias. + return true; +} + +// Has `var` being used in a way that may allow it to alias with a user pointer? +bool canVarAliasWithUserPointer(TargetRequest* target, IRInst* var) +{ + if (target && !isCPUTarget(target)) + { + // We don't allow taking the address of a variable on anything other + // than the CPU target. Therefore a var can never alias with a user + // pointer on these targets. + return false; + } + + SLANG_UNUSED(var); + return true; +} + +// A simple and conservative address aliasing check. +bool canAddressesPotentiallyAlias( + TargetRequest* target, + IRGlobalValueWithCode* func, + IRInst* addr1, + IRInst* addr2) +{ + if (addr1 == addr2) + return true; + + addr1 = getRootBufferOrAddr(addr1); + addr2 = getRootBufferOrAddr(addr2); + + auto addr1Class = getAliasingClass(addr1); + auto addr2Class = getAliasingClass(addr2); - if (addr1->getOp() == kIROp_Var && addr2->getOp() == kIROp_Var && addr1 != addr2) + if (!canAddrClassesAlias(addr1Class, addr2Class)) return false; + if (addr1Class == addr2Class) + { + // For these classes of addresses, the identity of the root + // determines whether or not the addresse can alias. + // Note that we assume two different bound resources can never + // alias, and two different variables can never alias. + switch (addr1Class) + { + case AddressAliasingClass::Var: + case AddressAliasingClass::BoundBuffer: + case AddressAliasingClass::BoundTexture: + case AddressAliasingClass::ConstantBuffer: + if (addr1 != addr2) + return false; + break; + } + } + // A param and a var can never alias. if (addr1->getOp() == kIROp_Param && addr1->getParent() == func->getFirstBlock() && addr2->getOp() == kIROp_Var || addr1->getOp() == kIROp_Var && addr2->getOp() == kIROp_Param && addr2->getParent() == func->getFirstBlock()) return false; + + // If one addr is user pointer and one addr is a var, + // they can never alias, if the user code never took the address of + // the var. + if (addr1Class == AddressAliasingClass::Var && addr2Class == AddressAliasingClass::UserPointer) + { + return canVarAliasWithUserPointer(target, addr1); + } + if (addr2Class == AddressAliasingClass::Var && addr1Class == AddressAliasingClass::UserPointer) + { + return canVarAliasWithUserPointer(target, addr2); + } return true; } +bool canAddressesPotentiallyAlias(IRGlobalValueWithCode* func, IRInst* addr1, IRInst* addr2) +{ + return canAddressesPotentiallyAlias(nullptr, func, addr1, addr2); +} + bool isPtrLikeOrHandleType(IRInst* type) { if (!type) @@ -1141,15 +1326,15 @@ bool areCallArgumentsSideEffectFree(IRCall* call, SideEffectAnalysisOptions opti if (isBitSet(options, SideEffectAnalysisOptions::UseDominanceTree)) dom = module->findOrCreateDominatorTree(parentFunc); - // If the pointer argument is a local variable (thus can't alias with other addresses) - // and it is never read from in the function, we can safely treat the call as having - // no side-effect. - // This is a conservative test, but is sufficient to detect the most common case where - // a temporary variable is used as the inout argument and the result stored in the temp - // variable isn't being used elsewhere in the parent func. + // If the pointer argument is a local variable (thus can't alias with other + // addresses) and it is never read from in the function, we can safely treat the + // call as having no side-effect. This is a conservative test, but is sufficient to + // detect the most common case where a temporary variable is used as the inout + // argument and the result stored in the temp variable isn't being used elsewhere in + // the parent func. // - // A more aggresive test can check all other address uses reachable from the call site - // and see if any of them are aliasing with the argument. + // A more aggresive test can check all other address uses reachable from the call + // site and see if any of them are aliasing with the argument. for (auto use = arg->firstUse; use; use = use->nextUse) { if (as<IRDecoration>(use->getUser())) @@ -1323,8 +1508,8 @@ bool doesCalleeHaveSideEffect(IRInst* callee) } } - // If the callee has no side effect, check if any of its associated functions have side effect. - // If so, we want to keep the callee around. + // If the callee has no side effect, check if any of its associated functions have side + // effect. If so, we want to keep the callee around. // // Typically, once the relevant pass has completed, the association is removed, // and at that point we can remove the function. @@ -2230,13 +2415,12 @@ void legalizeDefUse(IRGlobalValueWithCode* func) !(as<IRVar>(inst) && loopHeaderBlockMap.containsKey(block))) continue; - // Normally, if the common dominator is not `block`, we can simply move the definition - // to the common dominator. - // An exception is when the common dominator is the target block of a - // loop. - // Another exception is when a var in the loop condition block is accessed both inside - // and outside the loop. It is technically visible, but effects on the 'var' are not - // visible outside the loop, so we'll need to hoist it out of the loop. + // Normally, if the common dominator is not `block`, we can simply move the + // definition to the common dominator. An exception is when the common dominator is + // the target block of a loop. Another exception is when a var in the loop condition + // block is accessed both inside and outside the loop. It is technically visible, + // but effects on the 'var' are not visible outside the loop, so we'll need to hoist + // it out of the loop. // // Note that after normalization, loops are in the form of: // ``` @@ -2377,9 +2561,9 @@ bool canOperationBeSpecConst(IROp op, IRType* resultType, IRInst* const* fixedAr // Returns true for ops that can be declared as an operation under `OpSpecConstantOp`. // // Integer arithmetic and comparison operations can be `OpSpecConstantOp` with the `Shader` - // capability, while floating-point arithmetic and comparison operations require the `Kernel` - // capability. We only support `Shader` capability for now, return false when floating-point - // arithmetic/comparison is encountered. + // capability, while floating-point arithmetic and comparison operations require the + // `Kernel` capability. We only support `Shader` capability for now, return false when + // floating-point arithmetic/comparison is encountered. switch (op) { case kIROp_Add: |
