diff options
| author | Ellie Hermaszewska <ellieh@nvidia.com> | 2024-10-29 14:49:26 +0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-10-29 14:49:26 +0800 |
| commit | f65d756bff8d4c5cbc15bd0322a2ae8e6b896a21 (patch) | |
| tree | ea1d61342cd29368e19135000ec2948813096205 /source/slang/slang-ir-use-uninitialized-values.cpp | |
| parent | a729c15e9dce9f5116a38afc66329ab2ca4cea54 (diff) | |
format
* format
* Minor test fixes
* enable checking cpp format in ci
Diffstat (limited to 'source/slang/slang-ir-use-uninitialized-values.cpp')
| -rw-r--r-- | source/slang/slang-ir-use-uninitialized-values.cpp | 1098 |
1 files changed, 549 insertions, 549 deletions
diff --git a/source/slang/slang-ir-use-uninitialized-values.cpp b/source/slang/slang-ir-use-uninitialized-values.cpp index fea55de8d..390d1a9fc 100644 --- a/source/slang/slang-ir-use-uninitialized-values.cpp +++ b/source/slang/slang-ir-use-uninitialized-values.cpp @@ -1,701 +1,701 @@ #include "slang-ir-use-uninitialized-values.h" + #include "slang-ir-insts.h" #include "slang-ir-reachability.h" -#include "slang-ir.h" #include "slang-ir-util.h" +#include "slang-ir.h" namespace Slang { - static bool isMetaOp(IRInst* inst) - { - switch (inst->getOp()) - { - // These instructions only look at the parameter's type, - // so passing an undefined value to them is permissible - case kIROp_IsBool: - case kIROp_IsInt: - case kIROp_IsUnsignedInt: - case kIROp_IsSignedInt: - case kIROp_IsHalf: - case kIROp_IsFloat: - case kIROp_IsVector: - case kIROp_GetNaturalStride: - case kIROp_TypeEquals: - return true; - default: - break; - } - - return false; - } - - static bool isUninitializedValue(IRInst* inst) - { - // Also consider var since it does not - // automatically mean it will be initialized - // (at least not as the user may have intended) - return (inst->m_op == kIROp_undefined) - || (inst->m_op == kIROp_Var); - } - - static bool isUnmodifying(IRFunc *func) +static bool isMetaOp(IRInst* inst) +{ + switch (inst->getOp()) { - auto intr = func->findDecoration<IRIntrinsicOpDecoration>(); - return (intr && intr->getIntrinsicOp() == kIROp_Unmodified); + // These instructions only look at the parameter's type, + // so passing an undefined value to them is permissible + case kIROp_IsBool: + case kIROp_IsInt: + case kIROp_IsUnsignedInt: + case kIROp_IsSignedInt: + case kIROp_IsHalf: + case kIROp_IsFloat: + case kIROp_IsVector: + case kIROp_GetNaturalStride: + case kIROp_TypeEquals: return true; + default: break; } - enum ParameterCheckType - { - Never, // Parameter does NOT to be checked for uninitialization (e.g. is `in` or special type) - AsOut, // Parameter DOES need to be checked for usage before initializations - AsInOut // Parameter DOES need to be checked to see if it is ever written to - }; - - static ParameterCheckType isPotentiallyUnintended(IRParam* param, Stage stage, int index) - { - IRType* type = param->getFullType(); - if (auto out = as<IROutType>(param->getFullType())) - { - // Don't check `out Vertices<T>` or `out Indices<T>` parameters - // in mesh shaders. - // TODO: we should find a better way to represent these mesh shader - // parameters so they conform to the initialize before use convention. - // For example, we can use a `OutputVetices` and `OutputIndices` type - // to represent an output, like `OutputPatch` in domain shader. - // For now, we just skip the check for these parameters. - switch (out->getValueType()->getOp()) - { - case kIROp_VerticesType: - case kIROp_IndicesType: - case kIROp_PrimitivesType: - return Never; - default: - break; - } - - return AsOut; - } - else if (auto inout = as<IRInOutType>(type)) - { - // TODO: some way to check if the method - // is actually used for autodiff - if (as<IRDifferentialPairUserCodeType>(inout->getValueType())) - return Never; + return false; +} - switch (stage) - { - case Stage::AnyHit: - case Stage::ClosestHit: - // In HLSL the payload is required to be `inout` - return (index == 0) ? Never : AsInOut; - case Stage::Geometry: - // Second parameter is the triangle stream - return (index == 1) ? Never : AsInOut; - default: - break; - } +static bool isUninitializedValue(IRInst* inst) +{ + // Also consider var since it does not + // automatically mean it will be initialized + // (at least not as the user may have intended) + return (inst->m_op == kIROp_undefined) || (inst->m_op == kIROp_Var); +} - return AsInOut; - } +static bool isUnmodifying(IRFunc* func) +{ + auto intr = func->findDecoration<IRIntrinsicOpDecoration>(); + return (intr && intr->getIntrinsicOp() == kIROp_Unmodified); +} - return Never; - } +enum ParameterCheckType +{ + Never, // Parameter does NOT to be checked for uninitialization (e.g. is `in` or special type) + AsOut, // Parameter DOES need to be checked for usage before initializations + AsInOut // Parameter DOES need to be checked to see if it is ever written to +}; - static bool isAliasable(IRInst* inst) +static ParameterCheckType isPotentiallyUnintended(IRParam* param, Stage stage, int index) +{ + IRType* type = param->getFullType(); + if (auto out = as<IROutType>(param->getFullType())) { - switch (inst->getOp()) + // Don't check `out Vertices<T>` or `out Indices<T>` parameters + // in mesh shaders. + // TODO: we should find a better way to represent these mesh shader + // parameters so they conform to the initialize before use convention. + // For example, we can use a `OutputVetices` and `OutputIndices` type + // to represent an output, like `OutputPatch` in domain shader. + // For now, we just skip the check for these parameters. + switch (out->getValueType()->getOp()) { - // These instructions generate (implicit) references to inst - case kIROp_FieldExtract: - case kIROp_FieldAddress: - case kIROp_GetElement: - case kIROp_GetElementPtr: - case kIROp_InOutImplicitCast: - return true; - default: - break; + case kIROp_VerticesType: + case kIROp_IndicesType: + case kIROp_PrimitivesType: return Never; + default: break; } - return false; + return AsOut; } - - static bool isDifferentiableFunc(IRInst* func) + else if (auto inout = as<IRInOutType>(type)) { - for (auto decor = func->getFirstDecoration(); decor; decor = decor->getNextDecoration()) + // TODO: some way to check if the method + // is actually used for autodiff + if (as<IRDifferentialPairUserCodeType>(inout->getValueType())) + return Never; + + switch (stage) { - switch (decor->getOp()) - { - case kIROp_ForwardDerivativeDecoration: - case kIROp_ForwardDifferentiableDecoration: - case kIROp_BackwardDerivativeDecoration: - case kIROp_BackwardDifferentiableDecoration: - case kIROp_UserDefinedBackwardDerivativeDecoration: - return true; - default: - break; - } + case Stage::AnyHit: + case Stage::ClosestHit: + // In HLSL the payload is required to be `inout` + return (index == 0) ? Never : AsInOut; + case Stage::Geometry: + // Second parameter is the triangle stream + return (index == 1) ? Never : AsInOut; + default: break; } - return false; + return AsInOut; } - static IRInst* resolveSpecialization(IRSpecialize* spec) - { - IRInst* base = spec->getBase(); - IRGeneric* generic = as<IRGeneric>(base); - return findInnerMostGenericReturnVal(generic); - } + return Never; +} - // The `upper` field contains the struct that the type is - // is contained in. It is used to check for empty structs. - static bool canIgnoreType(IRType* type, IRType* upper) +static bool isAliasable(IRInst* inst) +{ + switch (inst->getOp()) { - // In case specialization returns a function instead - if (!type) - return true; + // These instructions generate (implicit) references to inst + case kIROp_FieldExtract: + case kIROp_FieldAddress: + case kIROp_GetElement: + case kIROp_GetElementPtr: + case kIROp_InOutImplicitCast: return true; + default: break; + } - if (as<IRVoidType>(type)) - return true; + return false; +} - // For structs, ignore if its empty - if (auto str = as<IRStructType>(type)) +static bool isDifferentiableFunc(IRInst* func) +{ + for (auto decor = func->getFirstDecoration(); decor; decor = decor->getNextDecoration()) + { + switch (decor->getOp()) { - int count = 0; - for (auto field : str->getFields()) - { - IRType* ftype = field->getFieldType(); - count += !canIgnoreType(ftype, type); - } - - return (count == 0); + case kIROp_ForwardDerivativeDecoration: + case kIROp_ForwardDifferentiableDecoration: + case kIROp_BackwardDerivativeDecoration: + case kIROp_BackwardDifferentiableDecoration: + case kIROp_UserDefinedBackwardDerivativeDecoration: return true; + default: break; } + } - // Nothing to initialize for a pure interface - if (as<IRInterfaceType>(type)) - return true; + return false; +} - // For pointers, check the value type (primarily for globals) - if (auto ptr = as<IRPtrType>(type)) - { - // Avoid the recursive step if its a - // recursive structure like a linked list - IRType* ptype = ptr->getValueType(); - if(auto resolvedType = as<IRType>(getResolvedInstForDecorations(ptype))) - ptype = resolvedType; - return (ptype != upper) && canIgnoreType(ptype, upper); - } +static IRInst* resolveSpecialization(IRSpecialize* spec) +{ + IRInst* base = spec->getBase(); + IRGeneric* generic = as<IRGeneric>(base); + return findInnerMostGenericReturnVal(generic); +} - // In the case of specializations, check returned type - if (auto spec = as<IRSpecialize>(type)) - { - IRInst* inner = resolveSpecialization(spec); - IRType* innerType = as<IRType>(inner); - return canIgnoreType(innerType, upper); - } +// The `upper` field contains the struct that the type is +// is contained in. It is used to check for empty structs. +static bool canIgnoreType(IRType* type, IRType* upper) +{ + // In case specialization returns a function instead + if (!type) + return true; - return false; - } + if (as<IRVoidType>(type)) + return true; - static List<IRInst*> getAliasableInstructions(IRInst* inst) + // For structs, ignore if its empty + if (auto str = as<IRStructType>(type)) { - List<IRInst*> addresses; - - addresses.add(inst); - for (auto use = inst->firstUse; use; use = use->nextUse) + int count = 0; + for (auto field : str->getFields()) { - IRInst* user = use->getUser(); - - // Meta instructions only use the argument type - if (isMetaOp(user) || !isAliasable(user)) - continue; - - addresses.addRange(getAliasableInstructions(user)); + IRType* ftype = field->getFieldType(); + count += !canIgnoreType(ftype, type); } - return addresses; + return (count == 0); } - enum InstructionUsageType - { - None, // Instruction neither stores nor loads from the soruce (e.g. meta operations) - Store, // Instruction acts as a write to the source - StoreParent, // Instruction's parent acts as a write to the source - Load // Instruciton acts as a load from the source - }; + // Nothing to initialize for a pure interface + if (as<IRInterfaceType>(type)) + return true; - static InstructionUsageType getCallUsageType(IRCall* call, IRInst* inst) + // For pointers, check the value type (primarily for globals) + if (auto ptr = as<IRPtrType>(type)) { - IRInst* callee = call->getCallee(); - - // Resolve the actual function - IRFunc* ftn = nullptr; - IRFuncType* ftype = nullptr; - if (auto spec = as<IRSpecialize>(callee)) - ftn = as<IRFunc>(resolveSpecialization(spec)); - - // Differentiable functions are mostly ignored, treated as having inout parameters - else if (as<IRForwardDifferentiate>(callee)) - return Store; - else if (as<IRBackwardDifferentiate>(callee)) - return Store; + // Avoid the recursive step if its a + // recursive structure like a linked list + IRType* ptype = ptr->getValueType(); + if (auto resolvedType = as<IRType>(getResolvedInstForDecorations(ptype))) + ptype = resolvedType; + return (ptype != upper) && canIgnoreType(ptype, upper); + } - else if (auto wit = as<IRLookupWitnessMethod>(callee)) - ftype = as<IRFuncType>(wit->getFullType()); - else - ftn = as<IRFunc>(callee); + // In the case of specializations, check returned type + if (auto spec = as<IRSpecialize>(type)) + { + IRInst* inner = resolveSpecialization(spec); + IRType* innerType = as<IRType>(inner); + return canIgnoreType(innerType, upper); + } - // Find the argument index so we can fetch the type - int index = 0; + return false; +} - auto args = call->getArgsList(); - for (int i = 0; i < args.getCount(); i++) - { - if (args[i] == inst) - { - index = i; - break; - } - } +static List<IRInst*> getAliasableInstructions(IRInst* inst) +{ + List<IRInst*> addresses; - if (ftn) - ftype = as<IRFuncType>(ftn->getFullType()); + addresses.add(inst); + for (auto use = inst->firstUse; use; use = use->nextUse) + { + IRInst* user = use->getUser(); - if (!ftype) - return None; + // Meta instructions only use the argument type + if (isMetaOp(user) || !isAliasable(user)) + continue; - // Consider it as a store if its passed - // as an out/inout/ref parameter - auto type = unwrapAttributedType(ftype->getParamType(index)); - return (as<IROutType>(type) || as<IRInOutType>(type) || as<IRRefType>(type)) ? Store : Load; + addresses.addRange(getAliasableInstructions(user)); } - static InstructionUsageType getInstructionUsageType(IRInst* user, IRInst* inst) - { - // Meta intrinsics (which evaluate on type) do nothing - if (isMetaOp(user)) - return None; + return addresses; +} - // Ignore instructions generating more aliases - if (isAliasable(user)) - return None; +enum InstructionUsageType +{ + None, // Instruction neither stores nor loads from the soruce (e.g. meta operations) + Store, // Instruction acts as a write to the source + StoreParent, // Instruction's parent acts as a write to the source + Load // Instruciton acts as a load from the source +}; - switch (user->getOp()) +static InstructionUsageType getCallUsageType(IRCall* call, IRInst* inst) +{ + IRInst* callee = call->getCallee(); + + // Resolve the actual function + IRFunc* ftn = nullptr; + IRFuncType* ftype = nullptr; + if (auto spec = as<IRSpecialize>(callee)) + ftn = as<IRFunc>(resolveSpecialization(spec)); + + // Differentiable functions are mostly ignored, treated as having inout parameters + else if (as<IRForwardDifferentiate>(callee)) + return Store; + else if (as<IRBackwardDifferentiate>(callee)) + return Store; + + else if (auto wit = as<IRLookupWitnessMethod>(callee)) + ftype = as<IRFuncType>(wit->getFullType()); + else + ftn = as<IRFunc>(callee); + + // Find the argument index so we can fetch the type + int index = 0; + + auto args = call->getArgsList(); + for (int i = 0; i < args.getCount(); i++) + { + if (args[i] == inst) { - case kIROp_loop: - case kIROp_unconditionalBranch: - // TODO: Ignore branches for now - return None; - - case kIROp_Call: - // Function calls can be either - // stores or loads depending on - // whether the callee takes it - // in as a out parameter or not - return getCallUsageType(as<IRCall>(user), inst); - - // These instructions will store data... - case kIROp_Store: - case kIROp_SwizzledStore: - case kIROp_SPIRVAsm: - case kIROp_AtomicStore: - return Store; + index = i; + break; + } + } - case kIROp_SPIRVAsmOperandInst: - // For SPIRV asm instructions, need to check out the entire - // block when doing reachability checks - return StoreParent; + if (ftn) + ftype = as<IRFuncType>(ftn->getFullType()); - case kIROp_MakeExistential: - case kIROp_MakeExistentialWithRTTI: - // For specializing generic structs - return Store; - - // Miscellaenous cases - case kIROp_ManagedPtrAttach: - case kIROp_Unmodified: - return Store; + if (!ftype) + return None; - default: - // Default case is that if the instruction is a pointer, it - // is considered a store, otherwise a load. - if (as<IRPtrTypeBase>(user->getDataType())) - return Store; - return Load; - } + // Consider it as a store if its passed + // as an out/inout/ref parameter + auto type = unwrapAttributedType(ftype->getParamType(index)); + return (as<IROutType>(type) || as<IRInOutType>(type) || as<IRRefType>(type)) ? Store : Load; +} + +static InstructionUsageType getInstructionUsageType(IRInst* user, IRInst* inst) +{ + // Meta intrinsics (which evaluate on type) do nothing + if (isMetaOp(user)) + return None; + + // Ignore instructions generating more aliases + if (isAliasable(user)) + return None; + + switch (user->getOp()) + { + case kIROp_loop: + case kIROp_unconditionalBranch: + // TODO: Ignore branches for now + return None; + + case kIROp_Call: + // Function calls can be either + // stores or loads depending on + // whether the callee takes it + // in as a out parameter or not + return getCallUsageType(as<IRCall>(user), inst); + + // These instructions will store data... + case kIROp_Store: + case kIROp_SwizzledStore: + case kIROp_SPIRVAsm: + case kIROp_AtomicStore: return Store; + + case kIROp_SPIRVAsmOperandInst: + // For SPIRV asm instructions, need to check out the entire + // block when doing reachability checks + return StoreParent; + + case kIROp_MakeExistential: + case kIROp_MakeExistentialWithRTTI: + // For specializing generic structs + return Store; + + // Miscellaenous cases + case kIROp_ManagedPtrAttach: + case kIROp_Unmodified: return Store; + + default: + // Default case is that if the instruction is a pointer, it + // is considered a store, otherwise a load. + if (as<IRPtrTypeBase>(user->getDataType())) + return Store; + return Load; } +} - static void collectSpecialCaseInstructions(List<IRInst*>& stores, IRBlock* block) +static void collectSpecialCaseInstructions(List<IRInst*>& stores, IRBlock* block) +{ + for (auto inst = block->getFirstInst(); inst; inst = inst->next) { - for (auto inst = block->getFirstInst(); inst; inst = inst->next) - { - if (as<IRGenericAsm>(inst)) - stores.add(inst); - } + if (as<IRGenericAsm>(inst)) + stores.add(inst); } +} - static void collectInstructionByUsage(List<IRInst*>& stores, List<IRInst*>& loads, IRInst* user, IRInst* inst) +static void collectInstructionByUsage( + List<IRInst*>& stores, + List<IRInst*>& loads, + IRInst* user, + IRInst* inst) +{ + InstructionUsageType usage = getInstructionUsageType(user, inst); + switch (usage) { - InstructionUsageType usage = getInstructionUsageType(user, inst); - switch (usage) - { - case Load: return loads.add(user); - case Store: return stores.add(user); - case StoreParent: return stores.add(user->getParent()); - } + case Load: return loads.add(user); + case Store: return stores.add(user); + case StoreParent: return stores.add(user->getParent()); } +} - static void cancelLoads(ReachabilityContext& reachability, const List<IRInst*>& stores, List<IRInst*>& loads) +static void cancelLoads( + ReachabilityContext& reachability, + const List<IRInst*>& stores, + List<IRInst*>& loads) +{ + // Remove all loads which are reachable from stores + for (auto store : stores) { - // Remove all loads which are reachable from stores - for (auto store : stores) + for (Index i = 0; i < loads.getCount();) { - for (Index i = 0; i < loads.getCount(); ) - { - if (reachability.isInstReachable(store, loads[i])) - loads.fastRemoveAt(i); - else - i++; - } + if (reachability.isInstReachable(store, loads[i])) + loads.fastRemoveAt(i); + else + i++; } } +} - static void collectAliasableLoadStores(IRInst* inst, List<IRInst*>& stores, List<IRInst*>& loads) - { - auto addresses = getAliasableInstructions(inst); +static void collectAliasableLoadStores(IRInst* inst, List<IRInst*>& stores, List<IRInst*>& loads) +{ + auto addresses = getAliasableInstructions(inst); - for (auto alias : addresses) - { - // TODO: Mark specific parts assigned to for partial initialization checks - for (auto use = alias->firstUse; use; use = use->nextUse) - collectInstructionByUsage(stores, loads, use->getUser(), alias); - } + for (auto alias : addresses) + { + // TODO: Mark specific parts assigned to for partial initialization checks + for (auto use = alias->firstUse; use; use = use->nextUse) + collectInstructionByUsage(stores, loads, use->getUser(), alias); } +} + +static List<IRInst*> getUnresolvedParamLoads( + ReachabilityContext& reachability, + IRFunc* func, + IRInst* inst) +{ + // Partition instructions + List<IRInst*> stores; + List<IRInst*> loads; + + collectAliasableLoadStores(inst, stores, loads); - static List<IRInst*> getUnresolvedParamLoads(ReachabilityContext &reachability, IRFunc* func, IRInst* inst) + // Special cases for parameters + for (const auto& b : func->getBlocks()) { - // Partition instructions - List<IRInst*> stores; - List<IRInst*> loads; + collectSpecialCaseInstructions(stores, b); - collectAliasableLoadStores(inst, stores, loads); + auto t = b->getTerminator(); + if (as<IRReturn>(t)) + loads.add(t); + } - // Special cases for parameters - for (const auto& b : func->getBlocks()) - { - collectSpecialCaseInstructions(stores, b); + cancelLoads(reachability, stores, loads); - auto t = b->getTerminator(); - if (as<IRReturn>(t)) - loads.add(t); - } + return loads; +} - cancelLoads(reachability, stores, loads); +static List<IRInst*> getUnresolvedVariableLoads(ReachabilityContext& reachability, IRInst* inst) +{ + // Partition instructions + List<IRInst*> stores; + List<IRInst*> loads; - return loads; - } + collectAliasableLoadStores(inst, stores, loads); - static List<IRInst*> getUnresolvedVariableLoads(ReachabilityContext &reachability, IRInst* inst) - { - // Partition instructions - List<IRInst*> stores; - List<IRInst*> loads; + cancelLoads(reachability, stores, loads); - collectAliasableLoadStores(inst, stores, loads); + return loads; +} - cancelLoads(reachability, stores, loads); +static bool isInstStoredInto(ReachabilityContext& reachability, IRInst* reference, IRInst* inst) +{ + List<IRInst*> stores; + List<IRInst*> loads; - return loads; + for (auto alias : getAliasableInstructions(inst)) + { + for (auto use = alias->firstUse; use; use = use->nextUse) + collectInstructionByUsage(stores, loads, use->getUser(), alias); } - static bool isInstStoredInto(ReachabilityContext& reachability, IRInst* reference, IRInst* inst) + for (auto store : stores) { - List<IRInst*> stores; - List<IRInst*> loads; + if (reachability.isInstReachable(store, reference)) + return true; + } - for (auto alias : getAliasableInstructions(inst)) - { - for (auto use = alias->firstUse; use; use = use->nextUse) - collectInstructionByUsage(stores, loads, use->getUser(), alias); - } + return false; +} - for (auto store : stores) - { - if (reachability.isInstReachable(store, reference)) - return true; - } +static IRInst* traceInstOrigin(IRInst* inst) +{ + if (auto load = as<IRLoad>(inst)) + return traceInstOrigin(load->getPtr()); - return false; - } + return inst; +} - static IRInst* traceInstOrigin(IRInst* inst) +static bool isReturnedValue(IRInst* inst) +{ + for (auto use = inst->firstUse; use; use = use->nextUse) { - if (auto load = as<IRLoad>(inst)) - return traceInstOrigin(load->getPtr()); + IRInst* user = use->getUser(); + if (as<IRReturn>(user)) + return true; - return inst; + // Loading from a Ptr type should be + // treated as an aliased path to any return + IRLoad* load = as<IRLoad>(user); + if (load && isReturnedValue(load)) + return true; } + return false; +} - static bool isReturnedValue(IRInst* inst) +static bool isDirectlyWrittenTo(IRInst* inst) +{ + for (auto use = inst->firstUse; use; use = use->nextUse) { - for (auto use = inst->firstUse; use; use = use->nextUse) - { - IRInst* user = use->getUser(); - if (as<IRReturn>(user)) - return true; - - // Loading from a Ptr type should be - // treated as an aliased path to any return - IRLoad *load = as<IRLoad>(user); - if (load && isReturnedValue(load)) - return true; - } - return false; + InstructionUsageType usage = getInstructionUsageType(use->getUser(), inst); + if (usage == Store || usage == StoreParent) + return true; } - static bool isDirectlyWrittenTo(IRInst* inst) - { - for (auto use = inst->firstUse; use; use = use->nextUse) - { - InstructionUsageType usage = getInstructionUsageType(use->getUser(), inst); - if (usage == Store || usage == StoreParent) - return true; - } + return false; +} - return false; - } +static List<IRStructField*> checkFieldsFromExit( + ReachabilityContext& reachability, + IRReturn* ret, + IRStructType* type) +{ + IRInst* origin = traceInstOrigin(ret->getVal()); - static List<IRStructField*> checkFieldsFromExit(ReachabilityContext& reachability, IRReturn* ret, IRStructType* type) - { - IRInst* origin = traceInstOrigin(ret->getVal()); - - // We don't want to warn on delegated construction - if (!isUninitializedValue(origin)) - return {}; - - // Check if the origin instruction is ever written to - if (isDirectlyWrittenTo(origin)) - return {}; - - // Now we can look for all references to fields - HashSet<IRStructKey*> usedKeys; - for (auto use = origin->firstUse; use; use = use->nextUse) - { - IRInst* user = use->getUser(); - - auto fieldAddress = as<IRFieldAddress>(user); - if (!fieldAddress || !isInstStoredInto(reachability, ret, user)) - continue; + // We don't want to warn on delegated construction + if (!isUninitializedValue(origin)) + return {}; - IRInst* field = fieldAddress->getField(); - usedKeys.add(as<IRStructKey>(field)); - } + // Check if the origin instruction is ever written to + if (isDirectlyWrittenTo(origin)) + return {}; - List<IRStructField*> uninitializedFields; + // Now we can look for all references to fields + HashSet<IRStructKey*> usedKeys; + for (auto use = origin->firstUse; use; use = use->nextUse) + { + IRInst* user = use->getUser(); - auto fields = type->getFields(); - for (auto field : fields) - { - if (canIgnoreType(field->getFieldType(), nullptr)) - continue; + auto fieldAddress = as<IRFieldAddress>(user); + if (!fieldAddress || !isInstStoredInto(reachability, ret, user)) + continue; - if (!usedKeys.contains(field->getKey())) - uninitializedFields.add(field); - } - - return uninitializedFields; + IRInst* field = fieldAddress->getField(); + usedKeys.add(as<IRStructKey>(field)); } - static void checkConstructor(IRFunc* func, ReachabilityContext& reachability, DiagnosticSink* sink) + List<IRStructField*> uninitializedFields; + + auto fields = type->getFields(); + for (auto field : fields) { - auto constructor = func->findDecoration<IRConstructorDecorartion>(); - if (!constructor) - return; + if (canIgnoreType(field->getFieldType(), nullptr)) + continue; - IRStructType* stype = as<IRStructType>(func->getResultType()); - if (!stype) - return; + if (!usedKeys.contains(field->getKey())) + uninitializedFields.add(field); + } - // Don't bother giving warnings if its not being used - bool synthesized = constructor->getSynthesizedStatus(); - if (synthesized && !func->firstUse) - return; - - auto printWarnings = [&](const List<IRStructField*>& fields, IRReturn* ret) - { - for (auto field : fields) - { - if (synthesized) - { - sink->diagnose(field->getKey(), - Diagnostics::fieldNotDefaultInitialized, - stype, - field->getKey()); - } - else - { - sink->diagnose(ret, - Diagnostics::constructorUninitializedField, - field->getKey()); - } - } + return uninitializedFields; +} + +static void checkConstructor(IRFunc* func, ReachabilityContext& reachability, DiagnosticSink* sink) +{ + auto constructor = func->findDecoration<IRConstructorDecorartion>(); + if (!constructor) + return; - }; + IRStructType* stype = as<IRStructType>(func->getResultType()); + if (!stype) + return; - // Work backwards, get exit points and find sources - for (auto block : func->getBlocks()) + // Don't bother giving warnings if its not being used + bool synthesized = constructor->getSynthesizedStatus(); + if (synthesized && !func->firstUse) + return; + + auto printWarnings = [&](const List<IRStructField*>& fields, IRReturn* ret) + { + for (auto field : fields) { - for (auto inst = block->getFirstInst(); inst; inst = inst->next) + if (synthesized) { - auto ret = as<IRReturn>(inst); - if (!ret) - continue; - - auto fields = checkFieldsFromExit(reachability, ret, stype); - printWarnings(fields, ret); + sink->diagnose( + field->getKey(), + Diagnostics::fieldNotDefaultInitialized, + stype, + field->getKey()); + } + else + { + sink->diagnose(ret, Diagnostics::constructorUninitializedField, field->getKey()); } } - } + }; - static void checkParameterAsOut(ReachabilityContext &reachability, IRFunc* func, IRParam* param, DiagnosticSink* sink) + // Work backwards, get exit points and find sources + for (auto block : func->getBlocks()) { - auto loads = getUnresolvedParamLoads(reachability, func, param); - for (auto load : loads) + for (auto inst = block->getFirstInst(); inst; inst = inst->next) { - sink->diagnose(load, - as<IRTerminatorInst>(load) - ? Diagnostics::returningWithUninitializedOut - : Diagnostics::usingUninitializedOut, - param); + auto ret = as<IRReturn>(inst); + if (!ret) + continue; + + auto fields = checkFieldsFromExit(reachability, ret, stype); + printWarnings(fields, ret); } } +} - static void checkUninitializedValues(IRFunc* func, DiagnosticSink* sink) +static void checkParameterAsOut( + ReachabilityContext& reachability, + IRFunc* func, + IRParam* param, + DiagnosticSink* sink) +{ + auto loads = getUnresolvedParamLoads(reachability, func, param); + for (auto load : loads) { - // Differentiable functions will generate undefined values - // strictly so that they can be set in a differentiable way - if (isDifferentiableFunc(func)) - return; + sink->diagnose( + load, + as<IRTerminatorInst>(load) ? Diagnostics::returningWithUninitializedOut + : Diagnostics::usingUninitializedOut, + param); + } +} - auto firstBlock = func->getFirstBlock(); - if (!firstBlock) - return; +static void checkUninitializedValues(IRFunc* func, DiagnosticSink* sink) +{ + // Differentiable functions will generate undefined values + // strictly so that they can be set in a differentiable way + if (isDifferentiableFunc(func)) + return; + + auto firstBlock = func->getFirstBlock(); + if (!firstBlock) + return; - ReachabilityContext reachability(func); + ReachabilityContext reachability(func); - // Used for a further analysis and to skip usual return checks - auto constructor = func->findDecoration<IRConstructorDecorartion>(); + // Used for a further analysis and to skip usual return checks + auto constructor = func->findDecoration<IRConstructorDecorartion>(); - // Special checks for stages e.g. raytracing shader - Stage stage = Stage::Unknown; - if (auto entry = func->findDecoration<IREntryPointDecoration>()) - stage = entry->getProfile().getStage(); + // Special checks for stages e.g. raytracing shader + Stage stage = Stage::Unknown; + if (auto entry = func->findDecoration<IREntryPointDecoration>()) + stage = entry->getProfile().getStage(); - // Check out parameters - if (!isUnmodifying(func)) + // Check out parameters + if (!isUnmodifying(func)) + { + int index = 0; + for (auto param : firstBlock->getParams()) { - int index = 0; - for (auto param : firstBlock->getParams()) - { - ParameterCheckType checkType = isPotentiallyUnintended(param, stage, index); - if (checkType == AsOut) - checkParameterAsOut(reachability, func, param, sink); - index++; - } + ParameterCheckType checkType = isPotentiallyUnintended(param, stage, index); + if (checkType == AsOut) + checkParameterAsOut(reachability, func, param, sink); + index++; } + } - // Check ordinary instructions - for (auto block : func->getBlocks()) + // Check ordinary instructions + for (auto block : func->getBlocks()) + { + for (auto inst = block->getFirstInst(); inst; inst = inst->getNextInst()) { - for (auto inst = block->getFirstInst(); inst; inst = inst->getNextInst()) + if (!isUninitializedValue(inst)) + continue; + + // This will be looked into later + if (constructor && isReturnedValue(inst)) + continue; + + IRType* type = inst->getFullType(); + if (canIgnoreType(type, nullptr)) + continue; + + auto loads = getUnresolvedVariableLoads(reachability, inst); + for (auto load : loads) { - if (!isUninitializedValue(inst)) - continue; - - // This will be looked into later - if (constructor && isReturnedValue(inst)) - continue; - - IRType* type = inst->getFullType(); - if (canIgnoreType(type, nullptr)) - continue; - - auto loads = getUnresolvedVariableLoads(reachability, inst); - for (auto load : loads) - { - sink->diagnose(load, - Diagnostics::usingUninitializedVariable, - inst); - } + sink->diagnose(load, Diagnostics::usingUninitializedVariable, inst); } } - - // Separate analysis for constructors - checkConstructor(func, reachability, sink); } - static void checkUninitializedGlobals(IRGlobalVar* variable, DiagnosticSink* sink) - { - IRType* type = variable->getFullType(); - if (canIgnoreType(type, nullptr)) - return; + // Separate analysis for constructors + checkConstructor(func, reachability, sink); +} - // Check for semantic decorations - // (e.g. globals like gl_GlobalInvocationID) - if (variable->findDecoration<IRSemanticDecoration>()) - return; +static void checkUninitializedGlobals(IRGlobalVar* variable, DiagnosticSink* sink) +{ + IRType* type = variable->getFullType(); + if (canIgnoreType(type, nullptr)) + return; + + // Check for semantic decorations + // (e.g. globals like gl_GlobalInvocationID) + if (variable->findDecoration<IRSemanticDecoration>()) + return; - if (variable->findDecoration<IRGlobalInputDecoration>()) + if (variable->findDecoration<IRGlobalInputDecoration>()) + return; + + // Check for initialization blocks + for (auto inst : variable->getChildren()) + { + if (as<IRBlock>(inst)) return; + } + + auto addresses = getAliasableInstructions(variable); - // Check for initialization blocks - for (auto inst : variable->getChildren()) + List<IRInst*> loads; + for (auto alias : addresses) + { + for (auto use = alias->firstUse; use; use = use->nextUse) { - if (as<IRBlock>(inst)) + InstructionUsageType usage = getInstructionUsageType(use->getUser(), alias); + if (usage == Store || usage == StoreParent) return; - } - - auto addresses = getAliasableInstructions(variable); - - List<IRInst*> loads; - for (auto alias : addresses) - { - for (auto use = alias->firstUse; use; use = use->nextUse) - { - InstructionUsageType usage = getInstructionUsageType(use->getUser(), alias); - if (usage == Store || usage == StoreParent) - return; - if (usage == Load) - loads.add(use->getUser()); - } + if (usage == Load) + loads.add(use->getUser()); } + } - for (auto load : loads) - { - sink->diagnose(load, - Diagnostics::usingUninitializedGlobalVariable, - variable); - } + for (auto load : loads) + { + sink->diagnose(load, Diagnostics::usingUninitializedGlobalVariable, variable); } +} - void checkForUsingUninitializedValues(IRModule* module, DiagnosticSink* sink) +void checkForUsingUninitializedValues(IRModule* module, DiagnosticSink* sink) +{ + for (auto inst : module->getGlobalInsts()) { - for (auto inst : module->getGlobalInsts()) + if (auto func = as<IRFunc>(inst)) { - if (auto func = as<IRFunc>(inst)) - { - checkUninitializedValues(func, sink); - } - else if (auto generic = as<IRGeneric>(inst)) - { - auto retVal = findGenericReturnVal(generic); - if (auto funcVal = as<IRFunc>(retVal)) - checkUninitializedValues(funcVal, sink); - } - else if (auto global = as<IRGlobalVar>(inst)) - { - checkUninitializedGlobals(global, sink); - } + checkUninitializedValues(func, sink); + } + else if (auto generic = as<IRGeneric>(inst)) + { + auto retVal = findGenericReturnVal(generic); + if (auto funcVal = as<IRFunc>(retVal)) + checkUninitializedValues(funcVal, sink); + } + else if (auto global = as<IRGlobalVar>(inst)) + { + checkUninitializedGlobals(global, sink); } } } +} // namespace Slang |
