summaryrefslogtreecommitdiff
path: root/source/slang/slang-ir-use-uninitialized-values.cpp
diff options
context:
space:
mode:
authorEllie Hermaszewska <ellieh@nvidia.com>2024-10-29 14:49:26 +0800
committerGitHub <noreply@github.com>2024-10-29 14:49:26 +0800
commitf65d756bff8d4c5cbc15bd0322a2ae8e6b896a21 (patch)
treeea1d61342cd29368e19135000ec2948813096205 /source/slang/slang-ir-use-uninitialized-values.cpp
parenta729c15e9dce9f5116a38afc66329ab2ca4cea54 (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.cpp1098
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